From 41aa6297316ab2816afab3528fa26356174fd3ca Mon Sep 17 00:00:00 2001 From: Michael Liu Date: Wed, 4 Sep 2019 21:52:02 +0800 Subject: [PATCH 001/371] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E7=AE=80?= =?UTF-8?q?=E5=8D=95=E6=B3=9B=E5=9E=8B=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 翻译 堆栈类 和 RandomList 子小节,自此简单泛型小节翻译完成。 --- docs/book/20-Generics.md | 99 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index b1592835..b87508fe 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -259,6 +259,105 @@ public class TupleTest { 在上面的程序中,`new` 表达式有些啰嗦。本章稍后会介绍,如何利用 *泛型方法* 简化它们。 +### 一个堆栈类 + +接下来我们看一个稍微复杂一点的例子:堆栈。在[集合](./12-Collections.md)一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。 + +这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。 + +```java +// generics/LinkedStack.java +// 用链式结构实现的堆栈 + +public class LinkedStack { + private static class Node { + U item; + Node next; + + Node() { item = null; next = null; } + Node(U item, Node next) { + this.item = item; + this.next = next; + } + + boolean end() { + return item == null && next == null; + } + } + + private Node top = new Node<>(); // 栈顶 + + public void push(T item) { + top = new Node<>(item, top); + } + + public T pop() { + T result = top.item; + if (!top.end()) { + top = top.next; + } + return result; + } + + public static void main(String[] args) { + LinkedStack lss = new LinkedStack<>(); + for (String s : "Phasers on stun!".split(" ")) { + lss.push(s); + } + String s; + while ((s = lss.pop()) != null) { + System.out.println(s); + } + } +} +``` + +输出结果: + +```java +stun! +on +Phasers +``` + +内部类 `Node` 也是一个泛型,它拥有自己的类型参数。 + +这个例子使用了一个 *末端标识* (end sentinel) 来判断栈何时为空。这个末端标识是在构造 `LinkedStack` 时创建的。然后,每次调用 `push()` 就会创建一个 `Node` 对象,并将其链接到前一个 `Node` 对象。当你调用 `pop()` 方法时,总是返回 `top.item`,然后丢弃当前 `top` 所指向的 `Node`,并将 `top` 指向下一个 `Node`,除非到达末端标识,这时就不能再移动 `top` 了。如果已经到达末端,程序还继续调用 `pop()` 方法,它只能得到 `null`,说明栈已经空了。 + +### RandomList + +作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用它的 `select()` 方法时都随机返回一个元素。如果希望这种列表可以适用于各种类型,就需要使用泛型: + +```java +// generics/RandomList.java +import java.util.*; +import java.util.stream.*; + +public class RandomList extends ArrayList { + private Random rand = new Random(47); + + public T select() { + return get(rand.nextInt(size())); + } + + public static void main(String[] args) { + RandomList rs = new RandomList<>(); + Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); + IntStream.range(0, 11).forEach(i -> + System.out.print(rs.select() + " ")); + ); + } +} +``` + +输出结果: + +```java +brown over fox quick quick dog brown The brown lazy brown +``` + +`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。 + ## 泛型接口 From b71e7bb3c7c3041a34ddc0fd8a80aa3e38777ec1 Mon Sep 17 00:00:00 2001 From: Michael Liu Date: Fri, 6 Sep 2019 21:31:39 +0800 Subject: [PATCH 002/371] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20-=20=E6=B3=9B?= =?UTF-8?q?=E5=9E=8B=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 泛型接口小节翻译完成 --- docs/book/20-Generics.md | 206 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index b87508fe..984f4f34 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -359,11 +359,215 @@ brown over fox quick quick dog brown The brown lazy brown `RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。 - ## 泛型接口 +泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。 + +一般而言,一个生成器只定义一个方法,用于创建对象。例如 `java.util.function` 类库中的 `Supplier` 就是一个生成器,调用其 `get()` 获取对象。`get()` 是泛型方法,返回值为类型参数 `T`。 + +为了演示 `Supplier`,我们需要定义几个类。下面是个咖啡相关的继承体系: + +```java +// generics/coffee/Coffee.java +package generics.coffee; + +public class Coffee { + private static long counter = 0; + private final long id = counter++; + + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } +} + + +// generics/coffee/Latte.java +package generics.coffee; +public class Latte extends Coffee {} + + +// generics/coffee/Mocha.java +package generics.coffee; +public class Mocha extends Coffee {} + + +// generics/coffee/Cappuccino.java +package generics.coffee; +public class Cappuccino extends Coffee {} + + +// generics/coffee/Americano.java +package generics.coffee; +public class Americano extends Coffee {} + + +// generics/coffee/Breve.java +package generics.coffee; +public class Breve extends Coffee {} +``` + +现在,我们可以编写一个类,实现 `Supplier` 接口,它能够随机生成不同类型的 `Coffee` 对象: + +```java +// generics/coffee/CoffeeSupplier.java +// {java generics.coffee.CoffeeSupplier} +package generics.coffee; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +public class CoffeeSupplier +implements Supplier, Iterable { + private Class[] types = { Latte.class, Mocha.class, + Cappuccino.class, Americano.class, Breve.class }; + private static Random rand = new Random(47); + + public CoffeeSupplier() {} + // For iteration: + private int size = 0; + public CoffeeSupplier(int sz) { size = sz; } + + @Override + public Coffee get() { + try { + return (Coffee) types[rand.nextInt(types.length)].newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + class CoffeeIterator implements Iterator { + int count = size; + @Override + public boolean hasNext() { return count > 0; } + @Override + public Coffee next() { + count--; + return CoffeeSupplier.this.get(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public Iterator iterator() { + return new CoffeeIterator(); + } + + public static void main(String[] args) { + Stream.generate(new CoffeeSupplier()) + .limit(5) + .forEach(System.out::println); + for (Coffee c : new CoffeeSupplier(5)) { + System.out.println(c); + } + } +} +``` + +输出结果: + +```java +Americano 0 +Latte 1 +Americano 2 +Mocha 3 +Mocha 4 +Breve 5 +Americano 6 +Latte 7 +Cappuccino 8 +Cappuccino 9 +``` + +参数化的 `Supplier` 接口确保 `get()` 返回值是参数的类型。`CoffeeSupplier` 同时还实现了 `Iterable` 接口,所以能用于 *for-in* 语句。不过,它还需要知道何时终止循环,这正是第二个构造函数的作用。 + +下面是另一个实现 `Supplier` 接口的例子,它负责生成 Fibonacci 数列: + +```java +// generics/Fibonacci.java +// Generate a Fibonacci sequence +import java.util.function.*; +import java.util.stream.*; + +public class Fibonacci implements Supplier { + private int count = 0; + @Override + public Integer get() { return fib(count++); } + + private int fib(int n) { + if(n < 2) return 1; + return fib(n-2) + fib(n-1); + } + + public static void main(String[] args) { + Stream.generate(new Fibonacci()) + .limit(18) + .map(n -> n + " ") + .forEach(System.out::print); + } +} +``` + +输出结果: + +```java +1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 +``` + +虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。 + +如果还想更进一步,编写一个实现了 `Iterator` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterator` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。 + +有多种方法可以实现适配器。例如,可以通过继承来创建适配器类: + +```java +// generics/IterableFibonacci.java +// Adapt the Fibonacci class to make it Iterable +import java.util.*; + +public class IterableFibonacci +extends Fibonacci implements Iterable { + private int n; + public IterableFibonacci(int count) { n = count; } + + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { return n > 0; } + @Override + public Integer next() { + n--; + return IterableFibonacci.this.get(); + } + @Override + public void remove() { // Not implemented + throw new UnsupportedOperationException(); + } + }; + } + + public static void main(String[] args) { + for(int i : new IterableFibonacci(18)) + System.out.print(i + " "); + } +} +``` + +输出结果: + +```java +1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 +``` + +在循环语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。 + ## 泛型方法 From d4fc1d7f1a83ceedafd8cd39b8632b0a163d58f5 Mon Sep 17 00:00:00 2001 From: StormZhao <1106009050@qq.com> Date: Wed, 11 Sep 2019 13:14:32 +0800 Subject: [PATCH 003/371] Update 04-Operators.md --- docs/book/04-Operators.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index ebccf668..8c949961 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -691,8 +691,7 @@ public class URShift { ``` 11111111111111111111111111111111 1111111111111111111111 -1111111111111111111111111111111111111111111111111111111 -111111111 +1111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 From 8e53689f74b8faa23344ccd349630ce2c11530e3 Mon Sep 17 00:00:00 2001 From: zhangzw Date: Wed, 11 Sep 2019 21:35:29 +0800 Subject: [PATCH 004/371] =?UTF-8?q?=E9=83=A8=E5=88=86=E6=9B=B4=E6=96=B01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 162 +++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 9 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 4bdd7294..d129610e 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -248,9 +248,9 @@ Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入 这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得您的生活比旧的替代品更加轻松。但是,您仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 - Parallel Streams(并发流) -到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在您对该语法(作为一个粉丝,我希望)感到满意,您可以获得额外的好处:您可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 +到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在您对该语法(作为一个粉丝,我希望)感到满意,您可以获得额外的好处:您可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 -添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 +添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 - 创建和运行任务 任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。您创建一个任务,然后将其交给Executorto运行。 @@ -666,7 +666,7 @@ current是使用线程安全的**AtomicInteger**类定义的,可以防止竞 试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用get()。添加limit(),你说“只需要这些。”基本上,当你将parallel()与limit()结合使用时,你要求随机输出 - 这可能对你正在解决的问题很好。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。 -什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示: +什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示: ```java // concurrent/ParallelStreamPuzzle3.java @@ -702,7 +702,7 @@ public class ParallelStreamPuzzle3 { 为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -您还可以看到boxed()的添加,它接受int流并将其转换为Integer流。 +您还可以看到boxed()的添加,它接受int流并将其转换为Integer流。 现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 @@ -860,7 +860,7 @@ NapTask[9] pool-1-thread-1 */ ``` -一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。 +一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。 ```java // concurrent/MoreTasksAfterShutdown.java @@ -1235,7 +1235,7 @@ public class QuittableTask implements Runnable { 虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。 -- [1]:只要运行标志为true,此任务的run()方法将继续。 +- [1]:只要运行标志为true,此任务的run()方法将继续。 - [2]: 显示仅在任务退出时发生。 需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,您没有任何反馈来告诉您已经做错了。通常,您编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 @@ -1267,7 +1267,7 @@ public class QuittingTasks { */ ``` -我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 +我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 ## CompletableFuture类 @@ -1428,8 +1428,150 @@ Machina0: complete ``` 在这里,我们还添加了一个**Timer**,它向我们展示每一步增加100毫秒,还有一些额外的开销。 -**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。 +**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效[^5]。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。 +回调**thenApply()**开始一个操作,在这种情况下,在完成所有任务之前,不会完成**e CompletableFuture**的创建。虽然这有时很有用,但是启动所有任务通常更有价值,这样就可以运行时继续前进并执行其他操作。我们通过在操作结束时添加Async来实现此目的: + +```java +// concurrent/CompletableApplyAsync.java +import java.util.concurrent.*; +import onjava.*; +public class CompletableApplyAsync { + public static void main(String[] args) { + Timer timer = new Timer(); + CompletableFuture cf = + CompletableFuture.completedFuture( + new Machina(0)) + .thenApplyAsync(Machina::work) + .thenApplyAsync(Machina::work) + .thenApplyAsync(Machina::work) + .thenApplyAsync(Machina::work); + System.out.println(timer.duration()); + System.out.println(cf.join()); + System.out.println(timer.duration()) + } +} +/* Output: +116 +Machina0: ONE +Machina0: TWO +Machina0:THREE +Machina0: complete +Machina0: complete +552 +*/ +``` + +同步调用(我们通常使用得那种)意味着“当你完成工作时,返回”,而异步调用以意味着“立刻返回但是继续后台工作。”正如你所看到的,**cf**的创建现在发生得跟快。每次调用 **thenApplyAsync()** 都会立刻返回,因此可以进行下一次调用,整个链接序列的完成速度比以前快得快。 + +事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了main()进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。 + +这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将您需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。 + +幸运的是,您需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,CompletableFuture为您管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 + +- 其他操作 +当您查看CompletableFuture的Javadoc时,您会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有thenApply(),thenApplyAsync()和thenApplyAsync()的第二种形式,它接受运行任务的Executor(在本书中我们忽略了Executor选项)。 + +这是一个显示所有“基本”操作的示例,它们不涉及组合两个CompletableFutures或异常(我们将在稍后查看)。首先,我们将重复使用两个实用程序以提供简洁和方便: + +```java +// concurrent/CompletableUtilities.java +package onjava; import java.util.concurrent.*; +public class CompletableUtilities { + // Get and show value stored in a CF: + public static void showr(CompletableFuture c) { + try { + System.out.println(c.get()); + } catch(InterruptedException + | ExecutionException e) { + throw new RuntimeException(e); + } + } + // For CF operations that have no value: + public static void voidr(CompletableFuture c) { + try { + c.get(); // Returns void + } catch(InterruptedException + | ExecutionException e) { + throw new RuntimeException(e); + } + } +} +``` + +showr()在CompletableFuture 上调用get()并显示结果,捕获两个可能的异常。voidr()是CompletableFuture 的showr()版本,即CompletableFutures,仅在任务完成或失败时显示。 + +为简单起见,以下CompletableFutures只包装整数。cfi()是一个方便的方法,它在完成的CompletableFuture 中包装一个int: + +```java +// concurrent/CompletableOperations.java +import java.util.concurrent.*; +import static onjava.CompletableUtilities.*; +public class CompletableOperations { + static CompletableFuture cfi(int i) { + return CompletableFuture.completedFuture( Integer.valueOf(i)); + } + public static void main(String[] args) { + showr(cfi(1)); // Basic test + voidr(cfi(2).runAsync(() -> + System.out.println("runAsync"))); + voidr(cfi(3).thenRunAsync(() -> + System.out.println("thenRunAsync"))); + voidr(CompletableFuture.runAsync(() -> + System.out.println("runAsync is static"))); + showr(CompletableFuture.supplyAsync(() -> 99)); + voidr(cfi(4).thenAcceptAsync(i -> + System.out.println("thenAcceptAsync: " + i))); + showr(cfi(5).thenApplyAsync(i -> i + 42)); + showr(cfi(6).thenComposeAsync(i -> cfi(i + 99))); + CompletableFuture c = cfi(7); + c.obtrudeValue(111); + showr(c); + showr(cfi(8).toCompletableFuture()); + c = new CompletableFuture<>(); + c.complete(9); + showr(c); + c = new CompletableFuture<>(); + c.cancel(true); + System.out.println("cancelled: " + c.isCancelled()); + System.out.println("completed exceptionally: " + + c.isCompletedExceptionally()); + System.out.println("done: " + c.isDone()); + System.out.println(c); + c = new CompletableFuture<>(); + System.out.println(c.getNow(777)); + c = new CompletableFuture<>(); + c.thenApplyAsync(i -> i + 42) + .thenApplyAsync(i -> i * 12); + System.out.println("dependents: " + c.getNumberOfDependents()); + c.thenApplyAsync(i -> i / 2); + System.out.println("dependents: " + c.getNumberOfDependents()); + } +} +/* Output: +1 +runAsync +thenRunAsync +runAsync is static +99 +thenAcceptAsync: 4 +47 +105 +111 +8 +9 +cancelled: true +completed exceptionally: true +done: true +java.util.concurrent.CompletableFuture@6d311334[Complet ed exceptionally] +777 +dependents: 1 +dependents: 2 +*/ +``` + +main()包含一系列可由其int值引用的测试。cfi(1)演示了showr()正常工作。cfi(2)是调用runAsync()的示例。由于Runnable不产生返回值,因此结果是CompletableFuture ,因此使用voidr()。 ## 死锁 @@ -1451,6 +1593,8 @@ Machina0: complete [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 - +[^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 +[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 +
\ No newline at end of file From b6a67c2c477fc14eb039fe5779a0696370e06971 Mon Sep 17 00:00:00 2001 From: FateNight Date: Thu, 12 Sep 2019 16:23:32 +0800 Subject: [PATCH 005/371] Update 09-Polymorphism.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改笔误变量 --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index a6093b4a..53e02e1f 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -585,7 +585,7 @@ public class FieldAccess { 输出: ``` -sup.field = 0, super.getField() = 1 +sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0 ``` From 6f936dffa1507f56aae07fe13effeb78bfa9ad1f Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Wed, 18 Sep 2019 21:33:29 +0800 Subject: [PATCH 006/371] Update 10-Interfaces.md --- docs/book/10-Interfaces.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 927861c5..a1f487a7 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -857,7 +857,7 @@ package interfaces.filters; public class Waveform { private static long counter; - private final long id = count++; + private final long id = counter++; @Override public String toString() { @@ -1041,7 +1041,7 @@ class FilterAdapter implements Processor { } } -punlic class FilterProcessor { +public class FilterProcessor { public static void main(String[] args) { Waveform w = new Waveform(); Applicator.apply(new FilterAdapter(new LowPass(1.0)), w); From 0f81b3c2bce071fae87c9d277f5c474ca447063d Mon Sep 17 00:00:00 2001 From: xiangflight Date: Tue, 17 Sep 2019 22:32:51 +0800 Subject: [PATCH 007/371] =?UTF-8?q?revision[14]=20=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E6=80=A7=E6=A0=A1=E8=AE=A2=E5=AE=8C14=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 72 ++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 6e5e3232..15c18adb 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1135,9 +1135,9 @@ Not much of a cheese shop really ## Optional类 -在我们查看终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流为空时会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好的提示我们(也就是说,没有异常)? +在我们学习终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)? -**Optional** 可以实现这样的功能。首先确保准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。它们包括: +**Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括: - `findFirst()` 返回一个包含第一个元素的 **Optional** 对象,如果流为空则返回 **Optional.empty** - `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty** @@ -1147,7 +1147,7 @@ Not much of a cheese shop really 对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。 -以下是对空流进行的所有这些操作的简单测试: +以下是对空流进行所有这些操作的简单测试: ```java // streams/OptionalsFromEmptyStreams.java @@ -1201,8 +1201,9 @@ import java.util.stream.*; class OptionalBasics { static void test(Optional optString) { if(optString.isPresent()) - System.out.println(optString.get()); else - System.out.println("Nothing inside!"); + System.out.println(optString.get()); + else + System.out.println("Nothing inside!"); } public static void main(String[] args) { test(Stream.of("Epithets").findFirst()); @@ -1221,14 +1222,15 @@ Nothing inside! 当你接收到 **Optional** 对象时,应首先调用 `isPresent()` 检查其中是否包含元素。如果存在,可使用 `get()` 获取。 + ### 便利函数 有许多便利函数可以解包 **Optional** ,这简化了上述“对所包含的对象的检查和执行操作”的过程: - `ifPresent(Consumer)`:当值存在时调用 **Consumer**,否则什么也不做。 - `orElse(otherObject)`:如果值存在则直接返回,否则生成 **otherObject**。 -- `orElseGet(Supplier)`:如果值存在直接生成对象,否则使用 **Supplier** 函数生成一个可替代对象。 -- `orElseThrow(Supplier)`:如果值存在直接生成对象,否则使用 **Supplier** 函数生成一个异常。 +- `orElseGet(Supplier)`:如果值存在则直接返回,否则使用 **Supplier** 函数生成一个可替代对象。 +- `orElseThrow(Supplier)`:如果值存在直接返回,否则使用 **Supplier** 函数生成一个异常。 如下是针对不同便利函数的简单演示: @@ -1258,8 +1260,7 @@ public class Optionals { try { System.out.println(optString.orElseThrow( () -> new Exception("Supplied"))); - } - catch(Exception e) { + } catch(Exception e) { System.out.println("Caught " + e); } } @@ -1299,10 +1300,10 @@ Caught java.lang.Exception: Supplied `test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。 -`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在[异常](./15-Exceptions.md) 这一章节中学习。 - +`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在 [异常](./15-Exceptions.md) 这一章节中学习。 + ### 创建 Optional 当我们在自己的代码中加入 **Optional** 时,可以使用下面 3 个静态方法: @@ -1363,7 +1364,7 @@ Null - `flatMap(Function)`:同 `map()`,但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。 -以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时删除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例: +以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时移除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例: ```java @@ -1675,17 +1676,18 @@ Signal(dash) Signal(dash) ``` -在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的行为。 - +在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。 + ## 终端操作 -这些操作获取一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。 +这些操作接收一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。 -### 转化数组 + +### 转化为数组 - `toArray()`:将流转换成适当类型的数组。 - `toArray(generator)`:在特殊情况下,生成器用于分配自定义的数组存储。 @@ -1704,17 +1706,17 @@ public class RandInts { } ``` -上例将100个数值范围在0-10000之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。 +上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。 ### 应用最终操作 -- `forEach(Consumer)`:常见的,如 `System.out::println` 作为 **Consumer** 函数。 +- `forEach(Consumer)`:你已经看到过很多次 `System.out::println` 作为 **Consumer** 函数。 - `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。 -第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问。这里简单介绍下`parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。这对内部循环是可行的。 +第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。 -`parallel()` 看似简单,实则棘手。更多内容,在稍后学习的[并发编程](24-Concurrent-Programming.md) 编程章节。 +`parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。 下例引入了 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例: @@ -1748,19 +1750,20 @@ public class ForEach { 258 555 693 861 961 429 868 200 522 207 288 128 551 589 ``` -为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果熟悉均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。 +为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。 在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。 + ### 收集 - `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。 -- `collect(Supplier, BiConsumer, BiConsumer)`:同上,参数1 **Supplier** 创建了一个新结果集合,参数2 **BiConsumer** 将下一个元素包含到结果中,参数3 **BiConsumer** 用于将两个值组合起来。 +- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。 -在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现。可通过查看 `java.util.stream.Collectors` 部分的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。 +在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 `java.util.stream.Collectors` 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。 -假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过 `Collectors.toCollection()` 来为任何类型的集合提供构造函数引用。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例: +假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过将集合的构造函数引用传递给 `Collectors.toCollection()`,从而构建任何类型的集合。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例: ```java // streams/TreeSetOfWords.java @@ -1795,7 +1798,7 @@ stream, streams, throws, toCollection, trim, util, void, words2] ``` -**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 +**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\W+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 我们也可以在流中生成 **Map**。代码示例: @@ -1846,7 +1849,7 @@ public class MapCollector { {688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N} ``` -**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。如果我们能以某种方式组合两个流,那就再好不过了,但 Java 在这个问题上与我们斗争。所以我创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 方法允许我们在 `stream()` 方法中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。 +**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。如果能以某种方式组合两个流,那就再好不过了,但 Java 在这个问题上不如我们所愿。所以我创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 方法允许我们在 `stream()` 方法中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。 在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。 @@ -1880,11 +1883,12 @@ cheese 在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。 + ### 组合所有流元素 - `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。 - `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。 -- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显示的组合 `map()` 和 `reduce()` 来更简单的表达这一点。 +- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。 如下是一个用于演示 `reduce()` 的示例: @@ -1943,10 +1947,10 @@ Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结 ### 匹配 - `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。 -- `anyMatch(Predicate)`:如果流中的一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。 +- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。 - `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。 -你已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()` 和 `anyMatch()` 的用法基本上是等同的。让我们探究短路行为。为了创建消除冗余代码的 ` show()` 方法,我们必须首先发现如何概括地描述所有三个匹配器操作,然后将其转换为称为 **Matcher** 的接口: +你已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()` 和 `anyMatch()` 的用法基本上是等同的。让我们探究短路行为。为了创建消除冗余代码的 ` show()` 方法,我们必须首先发现如何统一地描述所有三个匹配器操作,然后将其转换为称作 **Matcher** 的接口: ```java // streams/Matching.java @@ -1988,9 +1992,9 @@ public class Matching { 1 2 3 4 5 6 7 8 9 true ``` -**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数并且只返回 true 或者 false。它的第一个参数使我们要测试的流,第二个参数是一个谓词 **Predicate**。因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。 +**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数并且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。 -`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于像我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。 +`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。 ### 元素查找 @@ -2053,9 +2057,10 @@ public class LastElement { three ``` -`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字可选类型( numeric optional type),否则你使用的 **Optional** 类型为 `Optional`。 +`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字 **Optional** 类型( numeric optional type),否则使用 **Optional** 类型,就像上例中的 `Optional`。 + ### 信息 - `count()`:流中的元素个数。 @@ -2101,7 +2106,7 @@ you - `average()` :求取流元素平均值。 - `max()` 和 `min()`:因为这些操作在数字流上面,所以不需要 **Comparator**。 - `sum()`:对所有流元素进行求和。 -- `summaryStatistics()`:生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,但是你可以直接使用方法产生所有的数据。 +- `summaryStatistics()`:生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,因为你可以使用直接的方法产生所有的数据。 ```java // streams/NumericStreamInfo.java @@ -2135,4 +2140,5 @@ IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998} 流改变并极大地提升了 Java 编程的性质,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流动。在本书的剩余部分,我们将尽可能地使用流。 +
From fc6290acd48eeae46e9bad11471c11067488cf54 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Thu, 19 Sep 2019 11:54:41 +0800 Subject: [PATCH 008/371] fix typo -> chapter 6 --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index b589ee4f..ded064c6 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1186,7 +1186,7 @@ Mugs(int) new Mugs(1) completed ``` -看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持"匿名内部类"(参见"内部类"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,示例初始化子句是在两个构造器之前执行的。 +看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持"匿名内部类"(参见"内部类"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,实例初始化子句是在两个构造器之前执行的。 From 0cb74d1550a2415b4151e25514caa6b8d3004e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E9=AA=8F?= Date: Thu, 19 Sep 2019 22:31:20 +0800 Subject: [PATCH 009/371] =?UTF-8?q?=E5=B0=86field=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BB=8E=E5=B1=9E=E6=80=A7=E6=94=B9=E4=B8=BA=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=EF=BC=8C=E7=9B=B8=E5=AF=B9=E6=9D=A5=E8=AF=B4=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=9B=B4=E5=B8=B8=E7=94=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/03-Objects-Everywhere.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index 8829e427..95cf3898 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -219,9 +219,9 @@ ATypeName a = new ATypeName(); 到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。 -### 属性 +### 字段 -当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和属性(field)。类的属性可以是基本类型,也可以是引用类型。如果类的属性是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其属性的空间。通常,属性不在对象间共享。下面是一个具有某些属性的类的代码示例: +当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和字段(field)。类的字段可以是基本类型,也可以是引用类型。如果类的字段是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其字段的空间。通常,字段不在对象间共享。下面是一个具有某些字段的类的代码示例: ```java class DataOnly { @@ -237,7 +237,7 @@ class DataOnly { DataOnly data = new DataOnly(); ``` -我们必须通过这个对象的引用来指定属性值。格式:对象名称.方法名称或属性名称。代码示例: +我们必须通过这个对象的引用来指定字段值。格式:对象名称.方法名称或字段名称。代码示例: ```java data.i = 47; @@ -256,7 +256,7 @@ class DataOnly { ### 基本类型默认值 -如果类的成员变量(属性)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。 +如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。 | 基本类型 | 初始值 | | :-----: |:-----: | @@ -269,9 +269,9 @@ class DataOnly { | float | 0.0f | | double | 0.0d | -这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的属性始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。 +这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。 -这种默认值的赋予并不适用于局部变量 —— 那些不属于类的属性的变量。 因此,若在方法中定义的基本类型数据,如下: +这种默认值的赋予并不适用于局部变量 —— 那些不属于类的字段的变量。 因此,若在方法中定义的基本类型数据,如下: ```java int x; @@ -405,7 +405,7 @@ import java.util.*; 一些面向对象的语言使用类数据(class data)和类方法(class method),表示静态数据和方法只是作为类,而不是类的某个特定对象而存在的。有时 Java 文献也使用这些术语。 -我们可以在类的属性或方法前添加 `static` 关键字来表示这是一个静态属性或静态方法。 代码示例: +我们可以在类的字段或方法前添加 `static` 关键字来表示这是一个静态字段或静态方法。 代码示例: ```java class StaticTest { @@ -428,7 +428,7 @@ StaticTest.i++; `++` 运算符将会使变量结果 + 1。此时 `st1.i` 和 `st2.i` 的值都变成了 48。 -使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态属性或方法 [^7]。 代码示例: +使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态字段或方法 [^7]。 代码示例: ```java class Incrementable { @@ -546,7 +546,7 @@ os.arch=amd64 java.io.tmpdir=C:\Users\Bruce\AppData\Local\Temp\ ``` -`main()` 方法中的第一行会输出所有的系统属性,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的属性。该例中我们使用到了 **user.name** 和 **java.library.path**。 +`main()` 方法中的第一行会输出所有的系统字段,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的字段。该例中我们使用到了 **user.name** 和 **java.library.path**。 ### 编译和运行 From b4c2c95fcb7210c08641dd2c3e66841b221472e1 Mon Sep 17 00:00:00 2001 From: withthewind Date: Fri, 20 Sep 2019 10:34:28 +0800 Subject: [PATCH 010/371] Commit Appendix: Benefits and Costs of Static Type Checking - Quote --- .../book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md b/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md index 071a3c32..e5d149fd 100644 --- a/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md +++ b/docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md @@ -2,7 +2,7 @@ # 附录:静态语言类型检查 - +> 这是一本我多年来撰写的经过编辑过的论文集,论文集试图将静态检查语言和动态语言之间的争论放到一个正确的角度。还有一个前言部分,描述了我最近对这个话题的思考和见解。 ## 前言 From 9cf3a5c79e827c1db974c0fb2514ebaa7ad4c158 Mon Sep 17 00:00:00 2001 From: withthewind Date: Fri, 20 Sep 2019 15:21:39 +0800 Subject: [PATCH 011/371] Commit Appendix: Javadoc - Opening Paragraph --- docs/book/Appendix-Javadoc.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md index 5ea99544..cc25c5b9 100644 --- a/docs/book/Appendix-Javadoc.md +++ b/docs/book/Appendix-Javadoc.md @@ -3,6 +3,17 @@ # 附录:文档注释 +编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,那么每次更改代码时更改文档都会变得很繁琐。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这完整的画面,您需要一个特殊的注释语法来标记文档,以及一个工具来将这些注释提取为有用的表单中。这就是Java所做的。 + +提取注释的工具称为Javadoc,它是 JDK 安装的一部分。它使用Java编译器中的一些技术来寻找特殊的注释标记。它不仅提取由这些标记所标记的信息,还提取与注释相邻的类名或方法名。通过这种方式,您就可以用最少的工作量来生成合适的程序文档。 + +Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于Javadoc,您有一个简单的标准来创建文档,因此您可以期望所有Java libraries都有文档。 + +此外,您可以编写自己的Javadoc处理程序doclet,对于 Javadoc(例如,以不同的格式生成输出)。 + +以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。 + +
\ No newline at end of file From 1963e693589da1bf0c6badb639d89674259e1603 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 21 Sep 2019 12:02:02 +0800 Subject: [PATCH 012/371] Update 12-Collections.md --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 407dcd96..61f47709 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -224,7 +224,7 @@ public class AddingGroups { `Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。 -也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `delete()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误: +也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `remove()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误: ```java // collections/AsListInference.java From 81eaaaa0bcd6aa257b59e5fa4c0683cbb0d56501 Mon Sep 17 00:00:00 2001 From: withthewind Date: Sat, 21 Sep 2019 12:58:42 +0800 Subject: [PATCH 013/371] Commit Appendix: Javadoc - Syntax --- docs/book/Appendix-Javadoc.md | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md index cc25c5b9..02a5e059 100644 --- a/docs/book/Appendix-Javadoc.md +++ b/docs/book/Appendix-Javadoc.md @@ -13,6 +13,42 @@ Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于 以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。 +## 句法规则 + +所有Javadoc指令都发生在以 **/**** 开头(但仍然以 ***/** 结尾)的注释中。 + +使用Javadoc有两种主要方法: + +嵌入HTML或使用“doc标签”。独立的doc标签是指令它以 **@** 开头,放在注释行的开头。(然而,前面的 ***** 将被忽略。)可能会出现内联doc标签 + +Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被花括号包围。 + +有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子: + +```java + +// javadoc/Documentation1.java +/** 一个类注释 */ +public class Documentation1 { + /** 一个属性注释 */ + public int i; + /** 一个方法注释 */ + public void f() {} +} + +``` + +Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 + +默认情况下,将忽略对 **私有成员** 和包访问成员的注释(请参阅["隐藏实现"](/docs/book/07-Implementation-Hiding.md)一章),并且您将看不到任何输出。 + +这是有道理的,因为仅客户端程序员的观点是,在文件外部可以使用 **公共成员** 和 **受保护成员** 。 您可以使用 **-private** 标志和包含 **私人** 成员。 + +要通过Javadoc处理前面的代码,命令是: + +**javadoc Documentation1.java** + +这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览您的类。 From dec81b2dfd1bdf8392d55f6521fc3371c130901b Mon Sep 17 00:00:00 2001 From: withthewind Date: Sat, 21 Sep 2019 13:10:32 +0800 Subject: [PATCH 014/371] Update Fix Arrays Link Failure --- docs/book/21-Arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 7e874a02..c8444fd1 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -4,7 +4,7 @@ # 第二十一章 数组 -> 在[初始化和清理](/book/06-Housekeeping.md)一章的最后,你已经学过如何定义和初始化一个数组。 +> 在[初始化和清理](/docs/book/06-Housekeeping.md)一章的最后,你已经学过如何定义和初始化一个数组。 简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 **集合** (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。 From 8284bc4d734ab4cfac97c71156f6cd78e0dba5d0 Mon Sep 17 00:00:00 2001 From: withthewind Date: Sat, 21 Sep 2019 13:14:15 +0800 Subject: [PATCH 015/371] Insert Arrays - Collection Link --- docs/book/21-Arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index c8444fd1..955f84b8 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -19,7 +19,7 @@ 将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据的能力。在Java中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。 -速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合]( ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个**ArrayList**的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。 +速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](/docs/book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个**ArrayList**的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。 数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 **RuntimeException** 的异常提醒,这表明你的程序中存在错误。 From 7ab6d7486412d297c09510517c38a53dd89e0ce7 Mon Sep 17 00:00:00 2001 From: withthewind Date: Sat, 21 Sep 2019 13:16:14 +0800 Subject: [PATCH 016/371] Update Fix Arrays Link Failure --- docs/book/21-Arrays.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 955f84b8..a14ab4e7 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -91,7 +91,7 @@ Sphere 9 */ ``` -**Suppliers.create()** 方法在[泛型](/book/20-Generics.md)中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用[ ] 来随机存取元素,而一个List 使用诸如add()和get()等方法。数组和ArrayList之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](book/12-Collections.md)中看到的,集合的功能明显多于数组。随着Java自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。 +**Suppliers.create()** 方法在[泛型](/docs/book/20-Generics.md)中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用[ ] 来随机存取元素,而一个List 使用诸如add()和get()等方法。数组和ArrayList之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](/docs/book/12-Collections.md)中看到的,集合的功能明显多于数组。随着Java自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。 ### 用于显示数组的实用程序 @@ -361,7 +361,7 @@ public class IceCreamFlavors { **flaverset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后选择从数组 **FLAVORS** 中随机选择flavors并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何的对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者在其他的什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。 -如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/generics) 中介绍的 **元组** 。 +如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](/docs/book/20-Generics.md) 中介绍的 **元组** 。 注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串 From ab4879e32cc84e3ca13dbbaf09255f25390f02e5 Mon Sep 17 00:00:00 2001 From: cogitates Date: Sun, 22 Sep 2019 18:07:30 +0800 Subject: [PATCH 017/371] =?UTF-8?q?Fix=20issue=20#117=20=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E6=95=B0=E7=BB=84=E9=83=A8=E5=88=86=20=E7=AC=AC=E5=85=AB?= =?UTF-8?q?=EF=BC=8C=E7=AC=AC=E4=B9=9D=EF=BC=8C=E7=AC=AC=E5=8D=81=E5=B0=8F?= =?UTF-8?q?=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: cogitates --- docs/book/21-Arrays.md | 1055 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1055 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 7e874a02..28f4021f 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -822,14 +822,1069 @@ public class SimpleSetAll { ## 增量生成 +这是一个方法库,用于为不同类型生成增量值。 + +这些被作为内部类来生成容易记住的名字;比如,为了使用 **Integer** 工具你可以用 **new Conut.Interger()** , 如果你想要使用基本数据类型 **int** 工具,你可以用 **new Count.Pint()** (基本类型的名字不能被直接使用,所以它们都在前面添加一个 **P** 来表示基本数据类型'primitive', 我们的第一选择是使用基本类型名字后面跟着下划线,比如 **int_** 和 **double_** ,但是这种方式违背Java的命名习惯)。每个包装类的生成器都使用 **get()** 方法实现了它的 **Supplier** 。要使用**Array.setAll()** ,一个重载的 **get(int n)** 方法要接受(并忽略)其参数,以便接受 **setAll()** 传递的索引值。 + +注意,通过使用包装类的名称作为内部类名,我们必须调用 **java.lang** 包来保证我们可以使用实际包装类的名字: + +```java +// onjava/Count.java +// Generate incremental values of different types +package onjava; +import java.util.*; +import java.util.function.*; +import static onjava.ConvertTo.*; + +public interface Count { + class Boolean + implements Supplier { + private boolean b = true; + @Override + public java.lang.Boolean get() { + b = !b; + return java.lang.Boolean.valueOf(b); + } + public java.lang.Boolean get(int n) { + return get(); + } + public java.lang.Boolean[] array(int sz) { + java.lang.Boolean[] result = + new java.lang.Boolean[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pboolean { + private boolean b = true; + public boolean get() { + b = !b; + return b; + } + public boolean get(int n) { return get(); } + public boolean[] array(int sz) { + return primitive(new Boolean().array(sz)); + } + } + class Byte + implements Supplier { + private byte b; + @Override + public java.lang.Byte get() { return b++; } + public java.lang.Byte get(int n) { + return get(); + } + public java.lang.Byte[] array(int sz) { + java.lang.Byte[] result = + new java.lang.Byte[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pbyte { + private byte b; + public byte get() { return b++; } + public byte get(int n) { return get(); } + public byte[] array(int sz) { + return primitive(new Byte().array(sz)); + } + } + char[] CHARS = + "abcdefghijklmnopqrstuvwxyz".toCharArray(); + class Character + implements Supplier { + private int i; + @Override + public java.lang.Character get() { + i = (i + 1) % CHARS.length; + return CHARS[i]; + } + public java.lang.Character get(int n) { + return get(); + } + public java.lang.Character[] array(int sz) { + java.lang.Character[] result = + new java.lang.Character[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pchar { + private int i; + public char get() { + i = (i + 1) % CHARS.length; + return CHARS[i]; + } + public char get(int n) { return get(); } + public char[] array(int sz) { + return primitive(new Character().array(sz)); + } + } + class Short + implements Supplier { + short s; + @Override + public java.lang.Short get() { return s++; } + public java.lang.Short get(int n) { + return get(); + } + public java.lang.Short[] array(int sz) { + java.lang.Short[] result = + new java.lang.Short[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pshort { + short s; + public short get() { return s++; } + public short get(int n) { return get(); } + public short[] array(int sz) { + return primitive(new Short().array(sz)); + } + } + class Integer + implements Supplier { + int i; + @Override + public java.lang.Integer get() { return i++; } + public java.lang.Integer get(int n) { + return get(); + } + public java.lang.Integer[] array(int sz) { + java.lang.Integer[] result = + new java.lang.Integer[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pint implements IntSupplier { + int i; + public int get() { return i++; } + public int get(int n) { return get(); } + @Override + public int getAsInt() { return get(); } + public int[] array(int sz) { + return primitive(new Integer().array(sz)); + } + } + class Long + implements Supplier { + private long l; + @Override + public java.lang.Long get() { return l++; } + public java.lang.Long get(int n) { + return get(); + } + public java.lang.Long[] array(int sz) { + java.lang.Long[] result = + new java.lang.Long[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Plong implements LongSupplier { + private long l; + public long get() { return l++; } + public long get(int n) { return get(); } + @Override + public long getAsLong() { return get(); } + public long[] array(int sz) { + return primitive(new Long().array(sz)); + } + } + class Float + implements Supplier { + private int i; + @Override + public java.lang.Float get() { + return java.lang.Float.valueOf(i++); + } + public java.lang.Float get(int n) { + return get(); + } + public java.lang.Float[] array(int sz) { + java.lang.Float[] result = + new java.lang.Float[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pfloat { + private int i; + public float get() { return i++; } + public float get(int n) { return get(); } + public float[] array(int sz) { + return primitive(new Float().array(sz)); + } + } + class Double + implements Supplier { + private int i; + @Override + public java.lang.Double get() { + return java.lang.Double.valueOf(i++); + } + public java.lang.Double get(int n) { + return get(); + } + public java.lang.Double[] array(int sz) { + java.lang.Double[] result = + new java.lang.Double[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pdouble implements DoubleSupplier { + private int i; + public double get() { return i++; } + public double get(int n) { return get(); } + @Override + public double getAsDouble() { return get(0); } + public double[] array(int sz) { + return primitive(new Double().array(sz)); + } + } +} + +``` + +对于 **int** ,**long** ,**double** 这三个有特殊 **Supplier** 接口的原始数据类型来说,**Pint** , **Plong** 和 **Pdouble** 实现了这些接口。 + +这里是对 **Count** 的测试,这同样给我们提供了如何使用它的例子: + +```java +// arrays/TestCount.java +// Test counting generators +import java.util.*; +import java.util.stream.*; +import onjava.*; +import static onjava.ArrayShow.*; + +public class TestCount { + static final int SZ = 5; + public static void main(String[] args) { + System.out.println("Boolean"); + Boolean[] a1 = new Boolean[SZ]; + Arrays.setAll(a1, new Count.Boolean()::get); + show(a1); + a1 = Stream.generate(new Count.Boolean()) + .limit(SZ + 1).toArray(Boolean[]::new); + show(a1); + a1 = new Count.Boolean().array(SZ + 2); + show(a1); + boolean[] a1b = + new Count.Pboolean().array(SZ + 3); + show(a1b); + + System.out.println("Byte"); + Byte[] a2 = new Byte[SZ]; + Arrays.setAll(a2, new Count.Byte()::get); + show(a2); + a2 = Stream.generate(new Count.Byte()) + .limit(SZ + 1).toArray(Byte[]::new); + show(a2); + a2 = new Count.Byte().array(SZ + 2); + show(a2); + byte[] a2b = new Count.Pbyte().array(SZ + 3); + show(a2b); + + System.out.println("Character"); + Character[] a3 = new Character[SZ]; + Arrays.setAll(a3, new Count.Character()::get); + show(a3); + a3 = Stream.generate(new Count.Character()) + .limit(SZ + 1).toArray(Character[]::new); + show(a3); + a3 = new Count.Character().array(SZ + 2); + show(a3); + char[] a3b = new Count.Pchar().array(SZ + 3); + show(a3b); + + System.out.println("Short"); + Short[] a4 = new Short[SZ]; + Arrays.setAll(a4, new Count.Short()::get); + show(a4); + a4 = Stream.generate(new Count.Short()) + .limit(SZ + 1).toArray(Short[]::new); + show(a4); + a4 = new Count.Short().array(SZ + 2); + show(a4); + short[] a4b = new Count.Pshort().array(SZ + 3); + show(a4b); + + System.out.println("Integer"); + int[] a5 = new int[SZ]; + Arrays.setAll(a5, new Count.Integer()::get); + show(a5); + Integer[] a5b = + Stream.generate(new Count.Integer()) + .limit(SZ + 1).toArray(Integer[]::new); + show(a5b); + a5b = new Count.Integer().array(SZ + 2); + show(a5b); + a5 = IntStream.generate(new Count.Pint()) + .limit(SZ + 1).toArray(); + show(a5); + a5 = new Count.Pint().array(SZ + 3); + show(a5); + + System.out.println("Long"); + long[] a6 = new long[SZ]; + Arrays.setAll(a6, new Count.Long()::get); + show(a6); + Long[] a6b = Stream.generate(new Count.Long()) + .limit(SZ + 1).toArray(Long[]::new); + show(a6b); + a6b = new Count.Long().array(SZ + 2); + show(a6b); + a6 = LongStream.generate(new Count.Plong()) + .limit(SZ + 1).toArray(); + show(a6); + a6 = new Count.Plong().array(SZ + 3); + show(a6); + + System.out.println("Float"); + Float[] a7 = new Float[SZ]; + Arrays.setAll(a7, new Count.Float()::get); + show(a7); + a7 = Stream.generate(new Count.Float()) + .limit(SZ + 1).toArray(Float[]::new); + show(a7); + a7 = new Count.Float().array(SZ + 2); + show(a7); + float[] a7b = new Count.Pfloat().array(SZ + 3); + show(a7b); + + System.out.println("Double"); + double[] a8 = new double[SZ]; + Arrays.setAll(a8, new Count.Double()::get); + show(a8); + Double[] a8b = + Stream.generate(new Count.Double()) + .limit(SZ + 1).toArray(Double[]::new); + show(a8b); + a8b = new Count.Double().array(SZ + 2); + show(a8b); + a8 = DoubleStream.generate(new Count.Pdouble()) + .limit(SZ + 1).toArray(); + show(a8); + a8 = new Count.Pdouble().array(SZ + 3); + show(a8); + } +} +/* Output: +Boolean +[false, true, false, true, false] +[false, true, false, true, false, true] +[false, true, false, true, false, true, false] +[false, true, false, true, false, true, false, true] +Byte +[0, 1, 2, 3, 4] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6] +[0, 1, 2, 3, 4, 5, 6, 7] +Character +[b, c, d, e, f] +[b, c, d, e, f, g] +[b, c, d, e, f, g, h] +[b, c, d, e, f, g, h, i] +Short +[0, 1, 2, 3, 4] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6] +[0, 1, 2, 3, 4, 5, 6, 7] +Integer +[0, 1, 2, 3, 4] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6, 7] +Long +[0, 1, 2, 3, 4] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6] +[0, 1, 2, 3, 4, 5] +[0, 1, 2, 3, 4, 5, 6, 7] +Float +[0.0, 1.0, 2.0, 3.0, 4.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] +Double +[0.0, 1.0, 2.0, 3.0, 4.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0] +[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] +*/ + +``` + +注意到原始数组类型 **int[]** ,**long[]** ,**double[]** 可以直接被 **Arrays.setAll()** 填充,但是其他的原始类型都要求用包装器类型的数组。 + +通过 **Stream.generate()** 创建的包装数组显示了 **toArray()** 的重载用法,在这里你应该提供给它要创建的数组类型的构造器。 ## 随机生成 +我们可以按照 **Count.java** 的结构创建一个生成随机值的工具: + +```java +// onjava/Rand.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. +// Generate random values of different types +package onjava; +import java.util.*; +import java.util.function.*; +import static onjava.ConvertTo.*; + +public interface Rand { + int MOD = 10_000; + class Boolean + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Boolean get() { + return r.nextBoolean(); + } + public java.lang.Boolean get(int n) { + return get(); + } + public java.lang.Boolean[] array(int sz) { + java.lang.Boolean[] result = + new java.lang.Boolean[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pboolean { + public boolean[] array(int sz) { + return primitive(new Boolean().array(sz)); + } + } + class Byte + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Byte get() { + return (byte)r.nextInt(MOD); + } + public java.lang.Byte get(int n) { + return get(); + } + public java.lang.Byte[] array(int sz) { + java.lang.Byte[] result = + new java.lang.Byte[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pbyte { + public byte[] array(int sz) { + return primitive(new Byte().array(sz)); + } + } + class Character + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Character get() { + return (char)r.nextInt('a', 'z' + 1); + } + public java.lang.Character get(int n) { + return get(); + } + public java.lang.Character[] array(int sz) { + java.lang.Character[] result = + new java.lang.Character[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pchar { + public char[] array(int sz) { + return primitive(new Character().array(sz)); + } + } + class Short + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Short get() { + return (short)r.nextInt(MOD); + } + public java.lang.Short get(int n) { + return get(); + } + public java.lang.Short[] array(int sz) { + java.lang.Short[] result = + new java.lang.Short[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pshort { + public short[] array(int sz) { + return primitive(new Short().array(sz)); + } + } + class Integer + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Integer get() { + return r.nextInt(MOD); + } + public java.lang.Integer get(int n) { + return get(); + } + public java.lang.Integer[] array(int sz) { + int[] primitive = new Pint().array(sz); + java.lang.Integer[] result = + new java.lang.Integer[sz]; + for(int i = 0; i < sz; i++) + result[i] = primitive[i]; + return result; + } + } + class Pint implements IntSupplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public int getAsInt() { + return r.nextInt(MOD); + } + public int get(int n) { return getAsInt(); } + public int[] array(int sz) { + return r.ints(sz, 0, MOD).toArray(); + } + } + class Long + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Long get() { + return r.nextLong(MOD); + } + public java.lang.Long get(int n) { + return get(); + } + public java.lang.Long[] array(int sz) { + long[] primitive = new Plong().array(sz); + java.lang.Long[] result = + new java.lang.Long[sz]; + for(int i = 0; i < sz; i++) + result[i] = primitive[i]; + return result; + } + } + class Plong implements LongSupplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public long getAsLong() { + return r.nextLong(MOD); + } + public long get(int n) { return getAsLong(); } + public long[] array(int sz) { + return r.longs(sz, 0, MOD).toArray(); + } + } + class Float + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Float get() { + return (float)trim(r.nextDouble()); + } + public java.lang.Float get(int n) { + return get(); + } + public java.lang.Float[] array(int sz) { + java.lang.Float[] result = + new java.lang.Float[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } + class Pfloat { + public float[] array(int sz) { + return primitive(new Float().array(sz)); + } + } + static double trim(double d) { + return + ((double)Math.round(d * 1000.0)) / 100.0; + } + class Double + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public java.lang.Double get() { + return trim(r.nextDouble()); + } + public java.lang.Double get(int n) { + return get(); + } + public java.lang.Double[] array(int sz) { + double[] primitive = + new Rand.Pdouble().array(sz); + java.lang.Double[] result = + new java.lang.Double[sz]; + for(int i = 0; i < sz; i++) + result[i] = primitive[i]; + return result; + } + } + class Pdouble implements DoubleSupplier { + SplittableRandom r = new SplittableRandom(47); + @Override + public double getAsDouble() { + return trim(r.nextDouble()); + } + public double get(int n) { + return getAsDouble(); + } + public double[] array(int sz) { + double[] result = r.doubles(sz).toArray(); + Arrays.setAll(result, + n -> result[n] = trim(result[n])); + return result; + } + } + class String + implements Supplier { + SplittableRandom r = new SplittableRandom(47); + private int strlen = 7; // Default length + public String() {} + public String(int strLength) { + strlen = strLength; + } + @Override + public java.lang.String get() { + return r.ints(strlen, 'a', 'z' + 1) + .collect(StringBuilder::new, + StringBuilder::appendCodePoint, + StringBuilder::append).toString(); + } + public java.lang.String get(int n) { + return get(); + } + public java.lang.String[] array(int sz) { + java.lang.String[] result = + new java.lang.String[sz]; + Arrays.setAll(result, n -> get()); + return result; + } + } +} + +``` + +对于除了 **int** 、 **long** 和 **double** 之外的所有基本类型元素生成器,只生成数组,而不是 Count 中看到的完整操作集。这只是一个设计选择,因为本书不需要额外的功能。 + +下面是对所有 **Rand** 工具的测试: + +```java +// arrays/TestRand.java +// Test random generators +import java.util.*; +import java.util.stream.*; +import onjava.*; +import static onjava.ArrayShow.*; + +public class TestRand { + static final int SZ = 5; + public static void main(String[] args) { + System.out.println("Boolean"); + Boolean[] a1 = new Boolean[SZ]; + Arrays.setAll(a1, new Rand.Boolean()::get); + show(a1); + a1 = Stream.generate(new Rand.Boolean()) + .limit(SZ + 1).toArray(Boolean[]::new); + show(a1); + a1 = new Rand.Boolean().array(SZ + 2); + show(a1); + boolean[] a1b = + new Rand.Pboolean().array(SZ + 3); + show(a1b); + + System.out.println("Byte"); + Byte[] a2 = new Byte[SZ]; + Arrays.setAll(a2, new Rand.Byte()::get); + show(a2); + a2 = Stream.generate(new Rand.Byte()) + .limit(SZ + 1).toArray(Byte[]::new); + show(a2); + a2 = new Rand.Byte().array(SZ + 2); + show(a2); + byte[] a2b = new Rand.Pbyte().array(SZ + 3); + show(a2b); + + System.out.println("Character"); + Character[] a3 = new Character[SZ]; + Arrays.setAll(a3, new Rand.Character()::get); + show(a3); + a3 = Stream.generate(new Rand.Character()) + .limit(SZ + 1).toArray(Character[]::new); + show(a3); + a3 = new Rand.Character().array(SZ + 2); + show(a3); + char[] a3b = new Rand.Pchar().array(SZ + 3); + show(a3b); + + System.out.println("Short"); + Short[] a4 = new Short[SZ]; + Arrays.setAll(a4, new Rand.Short()::get); + show(a4); + a4 = Stream.generate(new Rand.Short()) + .limit(SZ + 1).toArray(Short[]::new); + show(a4); + a4 = new Rand.Short().array(SZ + 2); + show(a4); + short[] a4b = new Rand.Pshort().array(SZ + 3); + show(a4b); + + System.out.println("Integer"); + int[] a5 = new int[SZ]; + Arrays.setAll(a5, new Rand.Integer()::get); + show(a5); + Integer[] a5b = + Stream.generate(new Rand.Integer()) + .limit(SZ + 1).toArray(Integer[]::new); + show(a5b); + a5b = new Rand.Integer().array(SZ + 2); + show(a5b); + a5 = IntStream.generate(new Rand.Pint()) + .limit(SZ + 1).toArray(); + show(a5); + a5 = new Rand.Pint().array(SZ + 3); + show(a5); + + System.out.println("Long"); + long[] a6 = new long[SZ]; + Arrays.setAll(a6, new Rand.Long()::get); + show(a6); + Long[] a6b = Stream.generate(new Rand.Long()) + .limit(SZ + 1).toArray(Long[]::new); + show(a6b); + a6b = new Rand.Long().array(SZ + 2); + show(a6b); + a6 = LongStream.generate(new Rand.Plong()) + .limit(SZ + 1).toArray(); + show(a6); + a6 = new Rand.Plong().array(SZ + 3); + show(a6); + + System.out.println("Float"); + Float[] a7 = new Float[SZ]; + Arrays.setAll(a7, new Rand.Float()::get); + show(a7); + a7 = Stream.generate(new Rand.Float()) + .limit(SZ + 1).toArray(Float[]::new); + show(a7); + a7 = new Rand.Float().array(SZ + 2); + show(a7); + float[] a7b = new Rand.Pfloat().array(SZ + 3); + show(a7b); + + System.out.println("Double"); + double[] a8 = new double[SZ]; + Arrays.setAll(a8, new Rand.Double()::get); + show(a8); + Double[] a8b = + Stream.generate(new Rand.Double()) + .limit(SZ + 1).toArray(Double[]::new); + show(a8b); + a8b = new Rand.Double().array(SZ + 2); + show(a8b); + a8 = DoubleStream.generate(new Rand.Pdouble()) + .limit(SZ + 1).toArray(); + show(a8); + a8 = new Rand.Pdouble().array(SZ + 3); + show(a8); + + System.out.println("String"); + String[] s = new String[SZ - 1]; + Arrays.setAll(s, new Rand.String()::get); + show(s); + s = Stream.generate(new Rand.String()) + .limit(SZ).toArray(String[]::new); + show(s); + s = new Rand.String().array(SZ + 1); + show(s); + + Arrays.setAll(s, new Rand.String(4)::get); + show(s); + s = Stream.generate(new Rand.String(4)) + .limit(SZ).toArray(String[]::new); + show(s); + s = new Rand.String(4).array(SZ + 1); + show(s); + } +} +/* Output: +Boolean +[true, false, true, true, true] +[true, false, true, true, true, false] +[true, false, true, true, true, false, false] +[true, false, true, true, true, false, false, true] +Byte +[123, 33, 101, 112, 33] +[123, 33, 101, 112, 33, 31] +[123, 33, 101, 112, 33, 31, 0] +[123, 33, 101, 112, 33, 31, 0, -72] +Character +[b, t, p, e, n] +[b, t, p, e, n, p] +[b, t, p, e, n, p, c] +[b, t, p, e, n, p, c, c] +Short +[635, 8737, 3941, 4720, 6177] +[635, 8737, 3941, 4720, 6177, 8479] +[635, 8737, 3941, 4720, 6177, 8479, 6656] +[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768] +Integer +[635, 8737, 3941, 4720, 6177] +[635, 8737, 3941, 4720, 6177, 8479] +[635, 8737, 3941, 4720, 6177, 8479, 6656] +[635, 8737, 3941, 4720, 6177, 8479] +[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768] +Long +[6882, 3765, 692, 9575, 4439] +[6882, 3765, 692, 9575, 4439, 2638] +[6882, 3765, 692, 9575, 4439, 2638, 4011] +[6882, 3765, 692, 9575, 4439, 2638] +[6882, 3765, 692, 9575, 4439, 2638, 4011, 9610] +Float +[4.83, 2.89, 2.9, 1.97, 3.01] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28] +Double +[4.83, 2.89, 2.9, 1.97, 3.01] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28] +String +[btpenpc, cuxszgv, gmeinne, eloztdv] +[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc] +[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc, ygpoalk] +[btpe, npcc, uxsz, gvgm, einn, eelo] +[btpe, npcc, uxsz, gvgm, einn] +[btpe, npcc, uxsz, gvgm, einn, eelo] +*/ + +``` + +注意(除了 **String** 部分之外),这段代码与 **TestCount.java** 中的代码相同,**Count** 被 **Rand** 替换。 ## 泛型和基本数组 +在本章的前面,我们被提醒,泛型不能和基元一起工作。在这种情况下,我们必须从基元数组转换为包装类型的数组,并且还必须从另一个方向转换。下面是一个转换器可以同时对所有类型的数据执行操作: + +```java +// onjava/ConvertTo.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. +package onjava; + +public interface ConvertTo { + static boolean[] primitive(Boolean[] in) { + boolean[] result = new boolean[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; // Autounboxing + return result; + } + static char[] primitive(Character[] in) { + char[] result = new char[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static byte[] primitive(Byte[] in) { + byte[] result = new byte[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static short[] primitive(Short[] in) { + short[] result = new short[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static int[] primitive(Integer[] in) { + int[] result = new int[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static long[] primitive(Long[] in) { + long[] result = new long[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static float[] primitive(Float[] in) { + float[] result = new float[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static double[] primitive(Double[] in) { + double[] result = new double[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + // Convert from primitive array to wrapped array: + static Boolean[] boxed(boolean[] in) { + Boolean[] result = new Boolean[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; // Autoboxing + return result; + } + static Character[] boxed(char[] in) { + Character[] result = new Character[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Byte[] boxed(byte[] in) { + Byte[] result = new Byte[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Short[] boxed(short[] in) { + Short[] result = new Short[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Integer[] boxed(int[] in) { + Integer[] result = new Integer[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Long[] boxed(long[] in) { + Long[] result = new Long[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Float[] boxed(float[] in) { + Float[] result = new Float[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } + static Double[] boxed(double[] in) { + Double[] result = new Double[in.length]; + for(int i = 0; i < in.length; i++) + result[i] = in[i]; + return result; + } +} +``` + +**primitive()** 的每个版本都创建一个准确长度的适当基元数组,然后从包装类的 **in** 数组中复制元素。如果任何包装的数组元素是 **null** ,你将得到一个异常(这是合理的—否则无法选择有意义的值进行替换)。注意在这个任务中自动装箱如何发生。 + +下面是对 **ConvertTo** 中所有方法的测试: + +```java +// arrays/TestConvertTo.java +import java.util.*; +import onjava.*; +import static onjava.ArrayShow.*; +import static onjava.ConvertTo.*; + +public class TestConvertTo { + static final int SIZE = 6; + public static void main(String[] args) { + Boolean[] a1 = new Boolean[SIZE]; + Arrays.setAll(a1, new Rand.Boolean()::get); + boolean[] a1p = primitive(a1); + show("a1p", a1p); + Boolean[] a1b = boxed(a1p); + show("a1b", a1b); + + Byte[] a2 = new Byte[SIZE]; + Arrays.setAll(a2, new Rand.Byte()::get); + byte[] a2p = primitive(a2); + show("a2p", a2p); + Byte[] a2b = boxed(a2p); + show("a2b", a2b); + + Character[] a3 = new Character[SIZE]; + Arrays.setAll(a3, new Rand.Character()::get); + char[] a3p = primitive(a3); + show("a3p", a3p); + Character[] a3b = boxed(a3p); + show("a3b", a3b); + + Short[] a4 = new Short[SIZE]; + Arrays.setAll(a4, new Rand.Short()::get); + short[] a4p = primitive(a4); + show("a4p", a4p); + Short[] a4b = boxed(a4p); + show("a4b", a4b); + + Integer[] a5 = new Integer[SIZE]; + Arrays.setAll(a5, new Rand.Integer()::get); + int[] a5p = primitive(a5); + show("a5p", a5p); + Integer[] a5b = boxed(a5p); + show("a5b", a5b); + + Long[] a6 = new Long[SIZE]; + Arrays.setAll(a6, new Rand.Long()::get); + long[] a6p = primitive(a6); + show("a6p", a6p); + Long[] a6b = boxed(a6p); + show("a6b", a6b); + + Float[] a7 = new Float[SIZE]; + Arrays.setAll(a7, new Rand.Float()::get); + float[] a7p = primitive(a7); + show("a7p", a7p); + Float[] a7b = boxed(a7p); + show("a7b", a7b); + + Double[] a8 = new Double[SIZE]; + Arrays.setAll(a8, new Rand.Double()::get); + double[] a8p = primitive(a8); + show("a8p", a8p); + Double[] a8b = boxed(a8p); + show("a8b", a8b); + } +} +/* Output: +a1p: [true, false, true, true, true, false] +a1b: [true, false, true, true, true, false] +a2p: [123, 33, 101, 112, 33, 31] +a2b: [123, 33, 101, 112, 33, 31] +a3p: [b, t, p, e, n, p] +a3b: [b, t, p, e, n, p] +a4p: [635, 8737, 3941, 4720, 6177, 8479] +a4b: [635, 8737, 3941, 4720, 6177, 8479] +a5p: [635, 8737, 3941, 4720, 6177, 8479] +a5b: [635, 8737, 3941, 4720, 6177, 8479] +a6p: [6882, 3765, 692, 9575, 4439, 2638] +a6b: [6882, 3765, 692, 9575, 4439, 2638] +a7p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +a7b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +a8p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +a8b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] +*/ +``` +在每种情况下,原始数组都是为包装类型创建的,并使用 **Arrays.setAll()** 填充,正如我们在 **TestCouner.java** 中所做的那样(这也验证了 **Arrays.setAll()** 是否能同 **Integer** ,**Long** ,和 **Double** )。然后 **ConvertTo.primitive()** 将包装器数组转换为对应的基元数组,**ConverTo.boxed()** 将其转换回来。 ## 数组元素修改 From 7d962aee47fcbd6c4dc7f76c91e14498a5c42ec4 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Tue, 24 Sep 2019 09:54:41 +0800 Subject: [PATCH 018/371] Update 12-Collections.md --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 61f47709..fa296446 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -68,7 +68,7 @@ ndOrangesWithoutGenerics.java:23) 在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。 -在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。 +在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。 通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型: ```java From bcb69e55401657ffaea19dee5108b9a859bddac3 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Tue, 24 Sep 2019 09:57:43 +0800 Subject: [PATCH 019/371] Update 10-Interfaces.md --- docs/book/10-Interfaces.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index a1f487a7..10441b5e 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -1100,7 +1100,7 @@ class ActionCharacter { class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { public void swim() {} - pubilc void fly() {} + public void fly() {} } public class Adventure { @@ -1211,7 +1211,7 @@ public class HorrorShow { } ``` -接口 **DangerousMonster** 是 **Monster** 简单扩展的一个新接口,类 **DragonZilla** 实现了这个接口。 +接口 **DangerousMonster** 是 **Monster** 简单扩展的一个新接口,类 **DragonZilla** 实现了这个接口。 **Vampire** 中使用的语法仅适用于接口继承。通常来说,**extends** 只能用于单一类,但是在构建接口时可以引用多个基类接口。注意到,接口名之间用逗号分隔。 From 29e4ee9113116ee823f83a1265f90e291d76560b Mon Sep 17 00:00:00 2001 From: limingyang Date: Tue, 24 Sep 2019 16:39:39 +0800 Subject: [PATCH 020/371] =?UTF-8?q?Update=2006-HouseKeeping.md=20=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E7=A4=BA=E4=BE=8B=E4=BB=A3=E7=A0=81=E4=B8=AD=E7=BC=BA?= =?UTF-8?q?=E5=B0=91=E7=9A=84long=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/06-Housekeeping.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index ded064c6..c0e98a15 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -311,6 +311,12 @@ public class PrimitiveOverloading { f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); System.out.println(); } + void testLong() { + long x = 0; + System.out.print("long: "); + f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x); + System.out.println(); + } void testFloat() { float x = 0; System.out.print("float: "); @@ -331,6 +337,7 @@ public class PrimitiveOverloading { p.testByte(); p.testShort(); p.testInt(); + p.testLong(); p.testFloat(); p.testDouble(); } @@ -345,6 +352,7 @@ char: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double) byte: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double) short: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double) int: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double) +long: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double) float: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double) double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double) ``` From ffd495928aebcbdfce5ce3ba301d6eb7506eafb7 Mon Sep 17 00:00:00 2001 From: Trency Date: Tue, 24 Sep 2019 20:17:12 +0800 Subject: [PATCH 021/371] Signed-off-by: Trency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix issue #28: 翻译类型信息部分 动态代理小节 --- docs/book/19-Type-Information.md | 197 +++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 43a27d5c..65c5f0ac 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1405,6 +1405,203 @@ java ShowMethods ShowMethods ## 动态代理 +*代理*是基本的设计模式之一。它是你插入的对象,代替“真实”对象以提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构: + +```java +// typeinfo/SimpleProxyDemo.java + +interface Interface { + void doSomething(); + void somethingElse(String arg); +} + +class RealObject implements Interface { + @Override + public void doSomething() { + System.out.println("doSomething"); + } + @Override + public void somethingElse(String arg) { + System.out.println("somethingElse " + arg); + } +} + +class SimpleProxy implements Interface { + private Interface proxied; + SimpleProxy(Interface proxied) { + this.proxied = proxied; + } + @Override + public void doSomething() { + System.out.println("SimpleProxy doSomething"); + proxied.doSomething(); + } + @Override + public void somethingElse(String arg) { + System.out.println( + "SimpleProxy somethingElse " + arg); + proxied.somethingElse(arg); + } +} + +class SimpleProxyDemo { + public static void consumer(Interface iface) { + iface.doSomething(); + iface.somethingElse("bonobo"); + } + public static void main(String[] args) { + consumer(new RealObject()); + consumer(new SimpleProxy(new RealObject())); + } +} +/* Output: +doSomething +somethingElse bonobo +SimpleProxy doSomething +doSomething +SimpleProxy somethingElse bonobo +somethingElse bonobo +*/ +``` + +因为`consumer()`接受`Interface`,所以它不知道获得的是`RealObject`还是`SimpleProxy`,因为两者都实现了`Interface`。 +但是,在客户端和`RealObject`之间插入的`SimpleProxy`执行操作,然后在`RealObject`上调用相同的方法。 + +当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对`RealObject`中方法的调用,或衡量此类调用的开销,该怎么办?这不是你要写入到程序中的代码,而且代理使你可以很轻松地添加或删除它。 + +Java的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是`SimpleProxyDemo.java`使用动态代理重写的例子: + +```java +// typeinfo/SimpleDynamicProxy.java +import java.lang.reflect.*; + +class DynamicProxyHandler implements InvocationHandler { + private Object proxied; + DynamicProxyHandler(Object proxied) { + this.proxied = proxied; + } + @Override + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { + System.out.println( + "**** proxy: " + proxy.getClass() + + ", method: " + method + ", args: " + args); + if(args != null) + for(Object arg : args) + System.out.println(" " + arg); + return method.invoke(proxied, args); + } +} + +class SimpleDynamicProxy { + public static void consumer(Interface iface) { + iface.doSomething(); + iface.somethingElse("bonobo"); + } + public static void main(String[] args) { + RealObject real = new RealObject(); + consumer(real); + // Insert a proxy and call again: + Interface proxy = (Interface)Proxy.newProxyInstance( + Interface.class.getClassLoader(), + new Class, + new DynamicProxyHandler(real)); + consumer(proxy); + } +} +/* Output: +doSomething +somethingElse bonobo +**** proxy: class $Proxy0, method: public abstract void +Interface.doSomething(), args: null +doSomething +**** proxy: class $Proxy0, method: public abstract void +Interface.somethingElse(java.lang.String), args: +[Ljava.lang.Object;@6bc7c054 + bonobo +somethingElse bonobo +*/ +``` + +可以通过调用静态方法`Proxy.newProxyInstance()`来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口`InvocationHandler`的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。 + +`invoke()`方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在`invoke()`内的代理上调用方法时要小心,因为通过接口的调用是通过代理重定向的。 + +通常执行代理操作,然后使用`Method.invoke()`传递必要的参数将请求转发给代理对象。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用: + +```java +// typeinfo/SelectingMethods.java +// Looking for particular methods in a dynamic proxy +import java.lang.reflect.*; + +class MethodSelector implements InvocationHandler { + private Object proxied; + MethodSelector(Object proxied) { + this.proxied = proxied; + } + @Override + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if(method.getName().equals("interesting")) + System.out.println( + "Proxy detected the interesting method"); + return method.invoke(proxied, args); + } +} + +interface SomeMethods { + void boring1(); + void boring2(); + void interesting(String arg); + void boring3(); +} + +class Implementation implements SomeMethods { + @Override + public void boring1() { + System.out.println("boring1"); + } + @Override + public void boring2() { + System.out.println("boring2"); + } + @Override + public void interesting(String arg) { + System.out.println("interesting " + arg); + } + @Override + public void boring3() { + System.out.println("boring3"); + } +} + +class SelectingMethods { + public static void main(String[] args) { + SomeMethods proxy = + (SomeMethods)Proxy.newProxyInstance( + SomeMethods.class.getClassLoader(), + new Class, + new MethodSelector(new Implementation())); + proxy.boring1(); + proxy.boring2(); + proxy.interesting("bonobo"); + proxy.boring3(); + } +} +/* Output: +boring1 +boring2 +Proxy detected the interesting method +interesting bonobo +boring3 +*/ +``` + +在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。 + +动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在Erich Gamma等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](./25-Patterns.md)一章。 ## Optional类 From 89626a2cb8fe0792cd0004b69afc7f5c5545866e Mon Sep 17 00:00:00 2001 From: StormZhao Date: Sun, 29 Sep 2019 15:29:08 +0800 Subject: [PATCH 022/371] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 多台->多态 --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index b59490f7..d80001c3 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -443,7 +443,7 @@ CREATE TABLE MEMBER( REFERENCE VARCHAR(30) PRIMARY KEY); ``` -主方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载,并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在,将表名存储起来。然后读取这个类的所有字段,并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话,在对应的处理块中将构造出相应的数据库列的字符串片段。注意,由于注解没有继承机制,如果要获取近似多台的行为,使用 `getDeclaredAnnotations()` 似乎是唯一的方式。 +主方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载,并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在,将表名存储起来。然后读取这个类的所有字段,并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话,在对应的处理块中将构造出相应的数据库列的字符串片段。注意,由于注解没有继承机制,如果要获取近似多态的行为,使用 `getDeclaredAnnotations()` 似乎是唯一的方式。 嵌套的 **@Constraint** 注解被传递给 `getConstraints()`方法,并用它来构造一个包含 SQL 约束的 String 对象。 From 0bfc5b58faae0924fa60dc846970cbbbd5b0eec5 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Tue, 8 Oct 2019 19:13:18 +0800 Subject: [PATCH 023/371] revision[15] end in rethrowing exception --- docs/book/15-Exceptions.md | 45 ++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 6d17b601..923bd944 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -5,7 +5,7 @@ > Java 的基本理念是“结构不佳的代码不能运行”。 -改进的错误恢复机制是提供代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在 Java 中它显得格外重要,因为 Java 的主要目标之一就是创建供他人使用的程序构件。 +改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在 Java 中它显得格外重要,因为 Java 的主要目标之一就是创建供他人使用的程序构件。 发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。 @@ -21,19 +21,17 @@ Java 中的异常处理的目的在于通过使用少于目前数量的代码来 ## 异常概念 - - C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不关我的事”。所以,程序员不去检查错误情形也就不足为奇了(何况对某些错误情形的检查确实很无聊)。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。 -解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以迫溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理则建立在 C++的基础之上(尽管看上去更像 Object Pascal)。 +解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++的基础之上(尽管看上去更像 Object Pascal)。 -“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。 +“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在那里将作出正确的决定。 -异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。 +异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。 -## 基本异常 + - +## 基本异常 异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。 @@ -41,7 +39,7 @@ C 以及其他早期语言常常具有多种错误处理模式,这些模式往 当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。 -举一个抛出异常的简单例子。对于对象引用 t,传给你的时候可能尚未被初始化。所以在使用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样就把错误信息传播到了“更大”的环境中。这被称为抛出一个异常,看起来像这样: +举一个抛出异常的简单例子。对于对象引用 t,传给你的时候可能尚未被初始化。所以在使用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样就把错误信息传播到了“更大”的环境中。这被称为*抛出一个异常*,看起来像这样: ```java if(t == null) @@ -52,7 +50,9 @@ if(t == null) 异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。 -异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++这样的语言中,这可真是个问题,尤其是 C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略了问题,从而陷入了完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。 +异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++ 这样的语言中,这可真是个问题,尤其是 C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略问题,从而会陷入完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。 + + ### 异常参数 @@ -64,9 +64,11 @@ throw new NullPointerException("t = null"); 不久读者将看到,要把这个字符串的内容提取出来可以有多种不同的方法。 -关键字 throw 将产生许多有趣的结果。在使用 new 创建了异常对象之后,此对象的引用将传给 throw。尽管返回的异常对象其类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。 +关键字 **throw** 将产生许多有趣的结果。在使用 **new** 创建了异常对象之后,此对象的引用将传给 **throw**。尽管异常对象的类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。 + +抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层级。) -抛出异常与方法正常返回值的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层次。)此外,能够抛出任意类型的 Throwable 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。) +此外,能够抛出任意类型的 **Throwable** 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,唯一的信息只有异常的类型名,而在异常对象内部没有任何有意义的信息。) ## 异常捕获 @@ -82,7 +84,7 @@ try { } ``` -对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读,因为代码的目标是弄清楚错误检查。 +对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读,因为代码的意图和错误检查不是混淆在一起的。 ### 异常处理程序 @@ -101,7 +103,7 @@ try { // etc. ``` -每个 catch 子句(异常处理程序)看起来就像是接收一个且仅接收一个特殊类型的参数的方法。可以在处理程序的内部使用标识符(id1,id2 等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。 +每个 catch 子句(异常处理程序)看起来就像是接收且仅接收一个特殊类型的参数的方法。可以在处理程序的内部使用标识符(id1,id2 等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。 异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句结束,则处理程序的查找过程结束。注意,只有匹配的 catch 子句才能得到执行;这与 switch 语句不同,switch 语句需要在每一个 case 后面跟一个 break,以避免执行后续的 case 子句。 @@ -109,11 +111,11 @@ try { ### 终止与恢复 -异常处理理论上有两种基本模型。Java 支持终止模型(它是 Java 和 C++所支持的模型)。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。 +异常处理理论上有两种基本模型。Java 支持终止模型(它是 Java 和 C++所支持的模型)。在这种模型中,将假设错误非常严重,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。 另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用 Java 实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者,把 try 块放在 while 循环里,这样就不断地进入 try 块,直到得到满意的结果。 -长久以来,尽管程序员们使用的操作系统支持恢复模型的异常处理,但他们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。 +在过去,使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。 @@ -265,7 +267,7 @@ LoggingExceptions.main(LoggingExceptions.java:25) Caught LoggingException ``` -静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写人的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一 java.io.PrintWriter 对象作为参数(PrintWriter 会在[附录:I/O 流 ]() 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。 +静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一个 java.io.PrintWriter 对象作为参数(PrintWriter 会在[附录:I/O 流 ]() 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。 尽管由于 LoggingException 将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便,并因此不需要客户端程序员的干预就可以自动运行,但是更常见的情形是我们需要捕获和记录其他人编写的异常,因此我们必须在异常处理程序中生成日志消息; @@ -448,7 +450,7 @@ Throwable fillInStackTrace() 用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。 -此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass)也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName)方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimple Name0 方法。 +此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass)也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName)方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。 下面的例子演示了如何使用 Exception 类型的方法: @@ -522,7 +524,7 @@ public class SameHandler { } ``` -通过 Java 7 的多重捕获机制,你可以讲不同类型的异常使用“或”将它们组合起来,只在一个 catch 块中使用: +通过 Java 7 的多重捕获机制,你可以使用“或”将不同类型的异常组合起来,只需要一行 catch 语句: ```java // exceptions/MultiCatch.java @@ -532,6 +534,7 @@ public class MultiCatch { void f() { try { x(); + } catch(Except1 | Except2 | Except3 | Except4 e) { process(); } @@ -539,7 +542,7 @@ public class MultiCatch { } ``` -或者以其他组合的方式: +或者以其他的组合方式: ```java // exceptions/MultiCatch2.java @@ -559,7 +562,7 @@ public class MultiCatch2 { } ``` -这对书写更整洁的代码很有帮助 +这对书写更整洁的代码很有帮助。 ### 栈轨迹 From 7fbaa82d9020d299415a808521bc880a23ee9d6f Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Tue, 8 Oct 2019 19:34:48 +0800 Subject: [PATCH 024/371] =?UTF-8?q?=E6=95=B0=E7=BB=84=E5=85=83=E7=B4=A0?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=92=8C=E6=95=B0=E7=BB=84=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E5=B0=8F=E8=8A=82=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 118 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index fe3a2989..c74ad264 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -1228,7 +1228,7 @@ Double ## 随机生成 -我们可以按照 **Count.java** 的结构创建一个生成随机值的工具: +我们可以按照 **Count.java** 的结构创建一个生成随机值的工具: ```java // onjava/Rand.java @@ -1889,10 +1889,124 @@ a8b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] ## 数组元素修改 +传递给 **Arrays.setAll()** 的生成器函数可以使用它接收到的数组索引修改现有的数组元素: + +```JAVA +// arrays/ModifyExisting.java + +import java.util.*; +import onjava.*; +import static onjava.ArrayShow.*; + +public class ModifyExisting { + public static void main(String[] args) { + double[] da = new double[7]; + Arrays.setAll(da, new Rand.Double()::get); + show(da); + Arrays.setAll(da, n -> da[n] / 100); // [1] + show(da); + + } +} + +/* Output: +[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99] +[0.0483, 0.028900000000000002, 0.028999999999999998, +0.0197, 0.0301, 0.0018, 0.009899999999999999] +*/ + +``` + +[1] Lambdas在这里特别有用,因为数组总是在lambda表达式的范围内。 + ## 数组并行 +我们很快就不得不面对并行的主题。例如,“并行”一词在许多Java库方法中使用。您可能听说过类似“并行程序运行得更快”这样的说法,这是有道理的—当您可以有多个处理器时,为什么只有一个处理器在您的程序上工作呢? 如果您认为您应该利用其中的“并行”,这是很容易被原谅的。 +要是这么简单就好了。不幸的是,通过采用这种方法,您可以很容易地编写比非并行版本运行速度更慢的代码。在你深刻理解所有的问题之前,并行编程看起来更像是一门艺术而非科学。 +以下是简短的版本:用简单的方法编写代码。不要开始处理并行性,除非它成为一个问题。您仍然会遇到并行性。在本章中,我们将介绍一些为并行执行而编写的Java库方法。因此,您必须对它有足够的了解,以便进行基本的讨论,并避免出现错误。 + +在阅读并发编程这一章之后,您将更深入地理解它(但是,唉,这还远远不够。只是这些的话,充分理解这个主题是不可能的)。 +在某些情况下,即使您只有一个处理器,无论您是否显式地尝试并行,并行实现是惟一的、最佳的或最符合逻辑的选择。它是一个可以一直使用的工具,所以您必须了解它的相关问题。 + +最好从数据的角度来考虑并行性。对于大量数据(以及可用的额外处理器),并行可能会有所帮助。但您也可能使事情变得更糟。 + +在本书的其余部分,我们将遇到不同的情况: + +- 1、所提供的惟一选项是并行的。这很简单,因为我们别无选择,只能使用它。这种情况是比较罕见的。 + +- 2、有多个选项,但是并行版本(通常是最新的版本)被设计成在任何地方都可以使用(甚至在那些不关心并行性的代码中),如案例#1。我们将按预期使用并行版本。 + +- 3、案例1和案例2并不经常发生。相反,您将遇到某些算法的两个版本,一个用于并行使用,另一个用于正常使用。我将描述并行的一个,但不会在普通代码中使用它,因为它也许会产生所有可能的问题。 + +我建议您在自己的代码中采用这种方法。 + +![http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](要进一步了解为什么这是一个难题,请参阅Doug Lea的文章。) + +**parallelSetAll()** + +流式编程产生优雅的代码。例如,假设我们想要创建一个数值由从零开始填充的长数组: + +```JAVA +// arrays/CountUpward.java + +import java.util.stream.LongStream; + +public class CountUpward { + static long[] fillCounted(int size) { + return LongStream.iterate(0, i -> i + 1).limit(size).toArray(); + } + + public static void main(String[] args) { + long[] l1 = fillCounted(20); // No problem + show(l1); + // On my machine, this runs out of heap space: + // - long[] l2 = fillCounted(10_000_000); + } +} + +/* Output: +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, +16, 17, 18, 19] +*/ +``` + +**流** 实际上可以存储到将近1000万,但是之后就会耗尽堆空间。常规的 **setAll()** 是有效的,但是如果我们能更快地处理如此大量的数字,那就更好了。 +我们可以使用 **setAll()** 初始化更大的数组。如果速度成为一个问题,**Arrays.parallelSetAll()** 将(可能)更快地执行初始化(请记住并行性中描述的问题)。 + +```JAVA + +// arrays/ParallelSetAll.java + +import onjava.*; +import java.util.Arrays; + +public class ParallelSetAll { + static final int SIZE = 10_000_000; + + static void intArray() { + int[] ia = new int[SIZE]; + Arrays.setAll(ia, new Rand.Pint()::get); + Arrays.parallelSetAll(ia, new Rand.Pint()::get); + } + + static void longArray() { + long[] la = new long[SIZE]; + Arrays.setAll(la, new Rand.Plong()::get); + Arrays.parallelSetAll(la, new Rand.Plong()::get); + } + + public static void main(String[] args) { + intArray(); + longArray(); + } +} +``` + +数组分配和初始化是在单独的方法中执行的,因为如果两个数组都在 **main()** 中分配,它会耗尽内存(至少在我的机器上是这样。还有一些方法可以告诉Java在启动时分配更多的内存)。 + + ## Arrays工具类 @@ -1929,4 +2043,4 @@ a8b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18] -
\ No newline at end of file +
From f6b7d3b972250ccba5b3c5b66c99a508c100e8b6 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Tue, 8 Oct 2019 20:49:21 +0800 Subject: [PATCH 025/371] =?UTF-8?q?Arrays=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index c74ad264..010963f1 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2011,6 +2011,39 @@ public class ParallelSetAll { ## Arrays工具类 +您已经看到了 **java.util.Arrays** 中的 **fill()** 和 **setAll()/parallelSetAll()** 。该类包含许多其他有用的 **静态** 程序方法,我们将对此进行研究。 + +概述: + +- **asList()**: 获取任何序列或数组,并将其转换为一个 **列表集合** (集合章节介绍了此方法)。 + +- **copyOf()**:以新的长度创建现有数组的新副本。 + +- **copyOfRange()**:创建现有数组的一部分的新副本。 + +- **equals()**:比较两个数组是否相等。 + +- **deepEquals()**:多维数组的相等性比较。 + +- **stream()**:生成数组元素的流。 + +- **hashCode()**:生成数组的哈希值(您将在附录中了解这意味着什么:理解equals()和hashCode())。 + +- **deepHashCode()**: 多维数组的哈希值。 + +- **sort()**:排序数组 + +- **parallelSort()**:对数组进行并行排序,以提高速度。 + +- **binarySearch()**:在已排序的数组中查找元素。 + +- **parallelPrefix()**:使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。 + +- **spliterator()**:从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。 + +- **toString()**:为数组生成一个字符串表示。你在整个章节中经常看到这种用法。 + +- **deepToString()**:为多维数组生成一个字符串。你在整个章节中经常看到这种用法。对于所有基本类型和对象,所有这些方法都是重载的。 ## 数组拷贝 From 034b8ac5fc0d3c9adef762cd830866c22ecb5e3e Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 6 Sep 2019 14:53:14 +0800 Subject: [PATCH 026/371] autocommit --- docs/book/14-Streams.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 15c18adb..7dc2b685 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1798,7 +1798,16 @@ stream, streams, throws, toCollection, trim, util, void, words2] ``` +<<<<<<< HEAD **Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\W+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 +======= +**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 + + +>>>>>>> autocommit 我们也可以在流中生成 **Map**。代码示例: @@ -1849,12 +1858,16 @@ public class MapCollector { {688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N} ``` -**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。如果能以某种方式组合两个流,那就再好不过了,但 Java 在这个问题上不如我们所愿。所以我创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 方法允许我们在 `stream()` 方法中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。 +**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以这里创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 允许我们在 `stream()` 中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。 在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。 在大多数情况下,你可以在 `java.util.stream.Collectors`寻找到你想要的预先定义好的 **Collector**。在少数情况下当你找不到想要的时候,你可以使用第二种形式的 ` collect()`。 我基本上把它留作更高级的练习,但是这里有一个例子给出了基本想法: + + ```java // streams/SpecialCollector.java import java.util.*; From 626b7b53b3aebb2260a1acbebc44ffc95cd9ddf0 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Wed, 9 Oct 2019 09:22:22 +0800 Subject: [PATCH 027/371] autocommit --- docs/book/14-Streams.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 7dc2b685..5e56e294 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1798,16 +1798,12 @@ stream, streams, throws, toCollection, trim, util, void, words2] ``` -<<<<<<< HEAD -**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\W+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,**words2** 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于 3 的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 -======= **Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 ->>>>>>> autocommit 我们也可以在流中生成 **Map**。代码示例: From 10b84275d4395c9067d3b2563a33c95383649c00 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Wed, 9 Oct 2019 09:24:34 +0800 Subject: [PATCH 028/371] close issue#270 --- docs/book/19-Type-Information.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 65c5f0ac..3276d244 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1505,7 +1505,7 @@ class SimpleDynamicProxy { // Insert a proxy and call again: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), - new Class, + new Class[]{ Interface.class }, new DynamicProxyHandler(real)); consumer(proxy); } @@ -1582,7 +1582,7 @@ class SelectingMethods { SomeMethods proxy = (SomeMethods)Proxy.newProxyInstance( SomeMethods.class.getClassLoader(), - new Class, + new Class[]{ Interface.class }, new MethodSelector(new Implementation())); proxy.boring1(); proxy.boring2(); From b93ff77d09ad96bd99a4b7a1c2f29e3d8ac0af64 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Wed, 9 Oct 2019 13:59:50 +0800 Subject: [PATCH 029/371] =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=8B=B7=E8=B4=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 95 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 010963f1..80e760de 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2048,6 +2048,101 @@ public class ParallelSetAll { ## 数组拷贝 +与使用for循环手工执行复制相比,**copyOf()** 和 **copyOfRange()** 复制数组要快得多。这些方法被重载以处理所有类型。 + +我们开始复制数组的整数和整数: +```JAVA +// arrays/ArrayCopying.java +// Demonstrate Arrays.copy() and Arrays.copyOf() + +import onjava.*; + +import java.util.Arrays; + +import static onjava.ArrayShow.*; + +class Sup { + // Superclass + private int id; + + Sup(int n) { + id = n; + } + + @Override + public String toString() { + return getClass().getSimpleName() + id; + } +} + +class Sub extends Sup { // Subclass + + Sub(int n) { + super(n); + } +} + +public class ArrayCopying { + public static final int SZ = 15; + + public static void main(String[] args) { + int[] a1 = new int[SZ]; + Arrays.setAll(a1, new Count.Integer()::get); + show("a1", a1); + int[] a2 = Arrays.copyOf(a1, a1.length); // [1] + // Prove they are distinct arrays: + Arrays.fill(a1, 1); + show("a1", a1); + show("a2", a2); + // Create a shorter result: + a2 = Arrays.copyOf(a2, a2.length / 2); // [2] + show("a2", a2); + // Allocate more space: + a2 = Arrays.copyOf(a2, a2.length + 5); + show("a2", a2); + // Also copies wrapped arrays: + Integer[] a3 = new Integer[SZ]; // [3] + Arrays.setAll(a3, new Count.Integer()::get); + Integer[] a4 = Arrays.copyOfRange(a3, 4, 12); + show("a4", a4); + Sub[] d = new Sub[SZ / 2]; + Arrays.setAll(d, Sub::new); // Produce Sup[] from Sub[]: + Sup[] b = Arrays.copyOf(d, d.length, Sup[].class); // [4] + show(b); // This "downcast" works fine: + Sub[] d2 = Arrays.copyOf(b, b.length, Sub[].class); // [5] + show(d2); // Bad "downcast" compiles but throws exception: + Sup[] b2 = new Sup[SZ / 2]; + Arrays.setAll(b2, Sup::new); + try { + Sub[] d3 = Arrays.copyOf(b2, b2.length, Sub[].class); // [6] + } catch (Exception e) { + System.out.println(e); + } + } +} +/* Output: a1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] a1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + a2:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]a2:[0, 1, 2, 3, 4, 5, 6]a2:[ + 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]a4:[4, 5, 6, 7, 8, 9, 10, 11][Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6][ + Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6]java.lang.ArrayStoreException */ + +``` + +[1] 这是复制的基本方法;只需给出返回的复制数组的大小。这对于编写需要调整存储大小的算法很有帮助。复制之后,我们把a1的所有元素都设为1,以证明a1的变化不会影响a2中的任何东西。 + +[2] 通过更改最后一个参数,我们可以缩短或延长返回的复制数组。 + +[3] **copyOf()** 和 **copyOfRange()** 也可以使用包装类型。**copyOfRange()** 需要一个开始和结束索引。 + +[4] **copyOf()** 和 **copyOfRange()** 都有一个版本,该版本通过在方法调用的末尾添加目标类型来创建不同类型的数组。我首先想到的是,这可能是一种从原生数组生成包装数组的方法,反之亦然。 +但这没用。它的实际用途是“向上转换”和“向下转换”数组。也就是说,如果您有一个子类型(派生类型)的数组,而您想要一个基类型的数组,那么这些方法将生成所需的数组。 + +[5] 您甚至可以成功地“向下强制转换”,并从超类型的数组生成子类型的数组。这个版本运行良好,因为我们只是“upcast”。 + +[6] 这个“数组转换”将编译,但是如果类型不兼容,您将得到一个运行时异常。在这里,强制将基类型转换为派生类型是非法的,因为派生对象中可能有基对象中没有的属性和方法。 + +实例表明,原生数组和对象数组都可以被复制。但是,如果复制对象的数组,那么只复制引用—不复制对象本身。这称为浅拷贝(有关更多细节,请参阅附录:传递和返回对象)。 + +还有一个方法 **System.arraycopy()** ,它将一个数组复制到另一个已经分配的数组中。这将不会执行自动装箱或自动卸载—两个数组必须是完全相同的类型。 ## 数组比较 From ac7b5dcd3d14f100d8e593019498135ab01e5421 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Wed, 9 Oct 2019 14:01:32 +0800 Subject: [PATCH 030/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B6=85=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 80e760de..8d7f1a1e 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -1942,7 +1942,7 @@ public class ModifyExisting { 我建议您在自己的代码中采用这种方法。 -![http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](要进一步了解为什么这是一个难题,请参阅Doug Lea的文章。) +[http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](要进一步了解为什么这是一个难题,请参阅Doug Lea的文章。) **parallelSetAll()** From c60f0313d49a55a1e3a447acc3660c066fda54d5 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Wed, 9 Oct 2019 14:24:53 +0800 Subject: [PATCH 031/371] =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=AF=94=E8=BE=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 8d7f1a1e..a0c689f2 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2147,6 +2147,68 @@ public class ArrayCopying { ## 数组比较 +**数组** 提供了 **equals()** 来比较一维数组,以及 **deepEquals()** 来比较多维数组。对于所有原生类型和对象,这些方法都是重载的。 + +数组相等的含义:数组必须有相同数量的元素,并且每个元素必须与另一个数组中的对应元素相等,对每个元素使用 **equals()**(对于原生类型,使用原生类型的包装类的 **equals()** 方法;例如,int的Integer.equals()。 + +```JAVA +// arrays/ComparingArrays.java +// Using Arrays.equals() + +import java.util.*; +import onjava.*; + +public class ComparingArrays { + public static final int SZ = 15; + + static String[][] twoDArray() { + String[][] md = new String[5][]; + Arrays.setAll(md, n -> new String[n]); + for (int i = 0; i < md.length; i++) Arrays.setAll(md[i], new Rand.String()::get); + return md; + } + + public static void main(String[] args) { + int[] a1 = new int[SZ], a2 = new int[SZ]; + Arrays.setAll(a1, new Count.Integer()::get); + Arrays.setAll(a2, new Count.Integer()::get); + System.out.println("a1 == a2: " + Arrays.equals(a1, a2)); + a2[3] = 11; + System.out.println("a1 == a2: " + Arrays.equals(a1, a2)); + Integer[] a1w = new Integer[SZ], a2w = new Integer[SZ]; + Arrays.setAll(a1w, new Count.Integer()::get); + Arrays.setAll(a2w, new Count.Integer()::get); + System.out.println("a1w == a2w: " + Arrays.equals(a1w, a2w)); + a2w[3] = 11; + System.out.println("a1w == a2w: " + Arrays.equals(a1w, a2w)); + String[][] md1 = twoDArray(), md2 = twoDArray(); + System.out.println(Arrays.deepToString(md1)); + System.out.println("deepEquals(md1, md2): " + Arrays.deepEquals(md1, md2)); + System.out.println("md1 == md2: " + Arrays.equals(md1, md2)); + md1[4][1] = "#$#$#$#"; + System.out.println(Arrays.deepToString(md1)); + System.out.println("deepEquals(md1, md2): " + Arrays.deepEquals(md1, md2)); + } +} + +/* Output: +a1 == a2: true +a1 == a2: false +a1w == a2w: true +a1w == a2w: false +[[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv, + gmeinne], [btpenpc, cuxszgv, gmeinne, eloztdv]] + deepEquals(md1, md2): true + md1 == md2: false + [[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv, + gmeinne], [btpenpc, #$#$#$#, gmeinne, eloztdv]] + deepEquals(md1, md2): false + */ + ``` + +最初,a1和a2是完全相等的,所以输出是true,但是之后其中一个元素改变了,这使得结果为false。a1w和a2w是对一个封装类型数组重复该练习。 + +**md1** 和 **md2** 是通过 **twoDArray()** 以相同方式初始化的多维字符串数组。注意,**deepEquals()** 返回 **true**,因为它执行了适当的比较,而普通的 **equals()** 错误地返回 **false**。如果我们更改数组中的一个元素,**deepEquals()** 将检测它。 ## 流和数组 From 27e14c65d2287d6d23143e391d3d62a2a9f9b843 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Wed, 9 Oct 2019 14:40:18 +0800 Subject: [PATCH 032/371] =?UTF-8?q?=E6=B5=81=E5=92=8C=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index a0c689f2..ca2f0bfd 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2213,6 +2213,52 @@ a1w == a2w: false ## 流和数组 +**stream()** 方法很容易从某些类型的数组中生成元素流。 + +```JAVA +// arrays/StreamFromArray.java + +import java.util.*; +import onjava.*; + +public class StreamFromArray { + public static void main(String[] args) { + String[] s = new Rand.String().array(10); + Arrays.stream(s).skip(3).limit(5).map(ss -> ss + "!").forEach(System.out::println); + int[] ia = new Rand.Pint().array(10); + Arrays.stream(ia).skip(3).limit(5) + .map(i -> i * 10).forEach(System.out::println); + Arrays.stream(new long[10]); + Arrays.stream(new double[10]); + // Only int, long and double work: + // - Arrays.stream(new boolean[10]); + // - Arrays.stream(new byte[10]); + // - Arrays.stream(new char[10]); + // - Arrays.stream(new short[10]); + // - Arrays.stream(new float[10]); + // For the other types you must use wrapped arrays: + float[] fa = new Rand.Pfloat().array(10); + Arrays.stream(ConvertTo.boxed(fa)); + Arrays.stream(new Rand.Float().array(10)); + } +} +/* Output: + eloztdv! + ewcippc! + ygpoalk! + ljlbynx! + taprwxz! + 47200 + 61770 + 84790 + 66560 + 37680 +*/ +``` + +只有“原生类型” **int**、**long** 和 **double** 可以与 **Arrays.stream()** 一起使用;对于其他的,您必须以某种方式获得一个包装类型的数组。 + +通常,将数组转换为流来生成所需的结果要比直接操作数组容易得多。请注意,即使流已经“用完”(您不能重复使用它),您仍然拥有该数组,因此您可以以其他方式使用它----包括生成另一个流。 ## 数组排序 From fde3af53981bb4664c4dcf114431a6a2f261aa30 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Thu, 10 Oct 2019 20:35:43 +0800 Subject: [PATCH 033/371] =?UTF-8?q?revision[15]=20=E6=8F=AD=E7=A4=BA?= =?UTF-8?q?=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/15-Exceptions.md | 54 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 923bd944..81b82d2e 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -1172,7 +1172,7 @@ finally in 1st try block ### 在 return 中使用 finally -因为 finally 子句,总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行: +因为 finally 子句总是会执行,所以可以从一个方法内的多个点返回,仍然能保证重要的清理工作会执行: ```java // exceptions/MultipleReturns.java @@ -1283,15 +1283,15 @@ public class ExceptionSilencer { try { throw new RuntimeException(); } finally { -// Using 'return' inside the finally block -// will silence any thrown exception. + // Using 'return' inside the finally block + // will silence any thrown exception. return; } } } ``` -如果运行这个程序,就会看到即使抛出了异常,它也不会产生任何输出。 +如果运行这个程序,就会看到即使方法里抛出了异常,它也不会产生任何输出。 @@ -1380,22 +1380,22 @@ public class StormyInning extends Inning implements Storm { 在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。 -接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event()方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHardO,那么此方法抛出什么样的异常都没有问题。 +接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。 异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。 派生类构造器不能捕获基类构造器抛出的异常。 -StormyInning.walk() 不能通过编译的原因是因为:它抛出了异常,而 Inning.walk()) 并没有声明此异常。如果编译器允许这么做的话,就可以在调用 Inning.walk() 的时候不用做异常处理了,而且当把它替换成 Inning 的派生类的对象时,这个方法就有可能会抛出异常,于是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。 +StormyInning.walk() 不能通过编译是因为它抛出了异常,而 Inning.walk() 并没有声明此异常。如果编译器允许这么做的话,就可以在调用 Inning.walk() 的时候不用做异常处理了,而且当把它替换成 Inning 的派生类的对象时,这个方法就有可能会抛出异常,于是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。 -覆盖后的 event()方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在 atBat() 身上,它抛出的是 PopFoul,这个异常是继承自“会被基类的 atBat() 抛出”的 Foul,这样,如果你写的代码是同 Inning 打交道,并且调用了它的 atBat() 的话,那么肯定能捕获 Foul,而 PopFoul 是由 Foul 派生出来的,因此异常处理程序也能捕获 PopFoul。 +覆盖后的 event() 方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在 atBat() 身上,它抛出的是 PopFoul,这个异常是继承自“会被基类的 atBat() 抛出”的 Foul,这样,如果你写的代码是同 Inning 打交道,并且调用了它的 atBat() 的话,那么肯定能捕获 Foul,而 PopFoul 是由 Foul 派生出来的,因此异常处理程序也能捕获 PopFoul。 最后一个值得注意的地方是 main()。这里可以看到,如果处理的刚好是 Stormylnning 对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。 尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。 - + ## 构造器 有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。 @@ -1413,13 +1413,13 @@ public class InputFile { public InputFile(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); -// Other code that might throw exceptions + // Other code that might throw exceptions } catch(FileNotFoundException e) { System.out.println("Could not open " + fname); -// Wasn't open, so don't close it + // Wasn't open, so don't close it throw e; } catch(Exception e) { -// All other exceptions must close it + // All other exceptions must close it try { in.close(); } catch(IOException e2) { @@ -1427,7 +1427,7 @@ public class InputFile { } throw e; // Rethrow } finally { -// Don't close it here!!! + // Don't close it here!!! } } public String getLine() { @@ -1450,9 +1450,9 @@ public class InputFile { } ``` -InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在 try 块中,会使用此文件名建立了 FlleReader 对象。FileReader 对象本身用处并不大,但可以用它来建立 BufferedReader 对象。注意,使用 InputFile 的好处就能是把两步操作合而为一。 +InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在 try 块中,会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大,但可以用它来建立 BufferedReader 对象。注意,使用 InputFile 的好处之一是把两步操作合而为一。 -如果 FileReader 的构造器失败了,将抛出 FileNotFoundException 异常。对于这个异常,并不需要关闭文件,因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件,因为在它们捕获到异常之时,文件已经打开了(当然,如果还有其他方法能抛出 FlleNotFoundException,这个方法就显得有些投机取巧了。这时,通常必须把这些方法分别放到各自的 try 块里),close() 方法也可能会抛出异常,所以尽管它已经在另一个 catch 子句块里了,还是要再用一层 try-catch 对 Java 编译器而言,这只不过是又多了一对花括号。在本地做完处理之后,异常被重新抛出,对于构造器而言这么做是很合适的,因为你总不希望去误导调用方,让他认为“这个对象已经创建完毕,可以使用了”。 +如果 FileReader 的构造器失败了,将抛出 FileNotFoundException 异常。对于这个异常,并不需要关闭文件,因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件,因为在它们捕获到异常之时,文件已经打开了(当然,如果还有其他方法能抛出 FileNotFoundException,这个方法就显得有些投机取巧了。这时,通常必须把这些方法分别放到各自的 try 块里),close() 方法也可能会抛出异常,所以尽管它已经在另一个 catch 子句块里了,还是要再用一层 try-catch,这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后,异常被重新抛出,对于构造器而言这么做是很合适的,因为你总不希望去误导调用方,让他认为“这个对象已经创建完毕,可以使用了”。 在本例中,由于 finally 会在每次完成构造器之后都执行一遍,因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。 @@ -1494,7 +1494,7 @@ public class Cleanup { dispose() successful ``` -请仔细观察这里的逻辑:对 InputFile 对象的构造在其自己的 try 语句块中有效,如果构造失败,将进入外部的 catch 子句,而 dispose() 方法不会被调用。但是,如果构造成功,我们肯定想确保对象能够被清理,因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中,finally 子句在构造失败时是不会执行的,而在构成成功时将总是执行。 +请仔细观察这里的逻辑:对 InputFile 对象的构造在其自己的 try 语句块中有效,如果构造失败,将进入外部的 catch 子句,而 dispose() 方法不会被调用。但是,如果构造成功,我们肯定想确保对象能够被清理,因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中,finally 子句在构造失败时是不会执行的,而在构造成功时将总是执行。 这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个 try-finally 语句块: @@ -1516,32 +1516,32 @@ class NeedsCleanup2 extends NeedsCleanup { } public class CleanupIdiom { public static void main(String[] args) { -// [1]: + // [1]: NeedsCleanup nc1 = new NeedsCleanup(); try { -// ... + // ... } finally { nc1.dispose(); } -// [2]: -// If construction cannot fail, -// you can group objects: + // [2]: + // If construction cannot fail, + // you can group objects: NeedsCleanup nc2 = new NeedsCleanup(); NeedsCleanup nc3 = new NeedsCleanup(); try { -// ... + // ... } finally { nc3.dispose(); // Reverse order of construction nc2.dispose(); } -// [3]: -// If construction can fail you must guard each one: + // [3]: + // If construction can fail you must guard each one: try { NeedsCleanup2 nc4 = new NeedsCleanup2(); try { NeedsCleanup2 nc5 = new NeedsCleanup2(); try { -// ... + // ... } finally { nc5.dispose(); } @@ -1581,7 +1581,7 @@ NeedsCleanup 4 disposed 上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。 -InputFile.java 是一个特别棘手的情况,因为文件被打开(包含所有可能的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常,因此可以调用 dispose() 方法。这是一个很好的例子,因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码(当然,当你不选择代码的设计方式时,你经常会遇到这种情况,因此你必须仍然理解它)。 +InputFile.java 是一个特别棘手的情况,因为文件被打开(包含所有可能的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常,因此可以调用 dispose() 方法。这是一个很好的例子,因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码(当然,你经常会遇到这种你无法选择的代码设计的情况,因此你必须仍然理解它)。 InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子: @@ -1674,9 +1674,9 @@ public class TryWithResources { } ``` -在 Java 7 之前,try 总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(通常或通过异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。 +在 Java 7 之前,try 总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(正常或异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。 -它是如何工作的?在 try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.Autocloseable 接口,这个接口有一个方法,close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象: +它是如何工作的?在 try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.AutoCloseable 接口,这个接口有一个方法:close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象: ```java // exceptions/StreamsAreAutoCloseable.java From 64ea2c545ff70cae0fe14550340d21c6aebdf263 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Thu, 10 Oct 2019 21:04:10 +0800 Subject: [PATCH 034/371] =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 157 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index ca2f0bfd..7eaf7d93 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2263,6 +2263,163 @@ public class StreamFromArray { ## 数组排序 +根据对象的实际类型执行比较排序。一种方法是为不同的类型编写对应的排序方法,但是这样的代码不能复用。 + +编程设计的一个主要目标是“将易变的元素与稳定的元素分开”,在这里,保持不变的代码是一般的排序算法,但是变化的是对象的比较方式。因此,使用策略设计模式而不是将比较代码放入许多不同的排序源码中。使用策略模式时,变化的代码部分被封装在一个单独的类(策略对象)中。 + +您将一个策略对象交给相同的代码,该代码使用策略模式来实现其算法。通过这种方式,您将使用相同的排序代码,使不同的对象表达不同的比较方式。 + +Java有两种方式提供比较功能。第一种方法是通过实现 **java.lang.Comparable** 接口的原生方法。这是一个简单的接口,只含有一个方法 **compareTo()**。该方法接受另一个与参数类型相同的对象作为参数,如果当前对象小于参数,则产生一个负值;如果参数相等,则产生零值;如果当前对象大于参数,则产生一个正值。 + +这里有一个类,它实现了 **Comparable** 接口并演示了可比性,而且使用Java标准库方法 **Arrays.sort()**: + +```JAVA +// arrays/CompType.java +// Implementing Comparable in a class + +import onjava.*; + +import java.util.Arrays; +import java.util.SplittableRandom; + +import static onjava.ArrayShow.*; + +public class CompType implements Comparable { + private static int count = 1; + private static SplittableRandom r = new SplittableRandom(47); + int i; + int j; + + public CompType(int n1, int n2) { + i = n1; + j = n2; + } + + public static CompType get() { + return new CompType(r.nextInt(100), r.nextInt(100)); + } + + public static void main(String[] args) { + CompType[] a = new CompType[12]; + Arrays.setAll(a, n -> get()); + show("Before sorting", a); + Arrays.sort(a); + show("After sorting", a); + } + + @Override + public String toString() { + String result = "[i = " + i + ", j = " + j + "]"; + if (count++ % 3 == 0) result += "\n"; + return result; + } + + @Override + public int compareTo(CompType rv) { + return (i < rv.i ? -1 : (i == rv.i ? 0 : 1)); + } +} +/* Output: +Before sorting: [[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] , + [i = 56, j = 68], [i = 48, j = 93], + [i = 70, j = 7] , [i = 0, j = 25], + [i = 62, j = 34], [i = 50, j = 82] , + [i = 31, j = 67], [i = 66, j = 54], + [i = 21, j = 6] ] +After sorting: [[i = 0, j = 25], [i = 21, j = 6], [i = 31, j = 67] , + [i = 35, j = 37], [i = 41, j = 20], [i = 48, j = 93] , + [i = 50, j = 82], [i = 56, j = 68], [i = 62, j = 34] , + [i = 66, j = 54], [i = 70, j = 7], [i = 77, j = 79] ] +*/ +``` + +当您定义比较方法时,您有责任决定将一个对象与另一个对象进行比较意味着什么。这里,在比较中只使用i值和j值 +将被忽略。 + +**get()** 方法通过使用随机值初始化CompType对象来构建它们。在 **main()** 中,**get()** 与 **Arrays.setAll()** 一起使用,以填充一个 **CompType类型** 数组,然后对其排序。如果没有实现 **Comparable接口**,那么当您试图调用 **sort()** 时,您将在运行时获得一个 **ClassCastException** 。这是因为 **sort()** 将其参数转换为 **Comparable类型**。 + +现在假设有人给了你一个没有实现 **Comparable接口** 的类,或者给了你一个实现 **Comparable接口** 的类,但是你不喜欢它的工作方式而愿意有一个不同的对于此类型的比较方法。为了解决这个问题,创建一个实现 **Comparator** 接口的单独的类(在集合一章中简要介绍)。它有两个方法,**compare()** 和 **equals()**。但是,除了特殊的性能需求外,您不需要实现 **equals()**,因为无论何时创建一个类,它都是隐式地继承自 **Object**,**Object** 有一个equals()。您可以只使用默认的 **Object equals()** 来满足接口的规范。 + +集合类(注意复数;我们将在下一章节讨论它) 包含一个方法 **reverseOrder()**,它生成一个来 **Comparator**(比较器)反转自然排序顺序。这可以应用到比较对象: + +```JAVA +// arrays/Reverse.java +// The Collections.reverseOrder() Comparator + +import onjava.*; + +import java.util.Arrays; +import java.util.Collections; + +import static onjava.ArrayShow.*; + +public class Reverse { + public static void main(String[] args) { + CompType[] a = new CompType[12]; + Arrays.setAll(a, n -> CompType.get()); + show("Before sorting", a); + Arrays.sort(a, Collections.reverseOrder()); + show("After sorting", a); + } +} +/* Output: +Before sorting: [[i = 35, j = 37], [i = 41, j = 20], + [i = 77, j = 79] , [i = 56, j = 68], + [i = 48, j = 93], [i = 70, j = 7], + [i = 0, j = 25], [i = 62, j = 34], + [i = 50, j = 82] , [i = 31, j = 67], + [i = 66, j = 54], [i = 21, j = 6] ] +After sorting: [[i = 77, j = 79], [i = 70, j = 7], + [i = 66, j = 54] , [i = 62, j = 34], + [i = 56, j = 68], [i = 50, j = 82] , + [i = 48, j = 93], [i = 41, j = 20], + [i = 35, j = 37] , [i = 31, j = 67], + [i = 21, j = 6], [i = 0, j = 25] ] +*/ +``` + +您还可以编写自己的比较器。这个比较CompType对象基于它们的j值而不是它们的i值: + +```JAVA +// arrays/ComparatorTest.java +// Implementing a Comparator for a class + +import onjava.*; + +import java.util.Arrays; +import java.util.Comparator; + +import static onjava.ArrayShow.*; + +class CompTypeComparator implements Comparator { + public int compare(CompType o1, CompType o2) { + return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1)); + } +} + +public class ComparatorTest { + public static void main(String[] args) { + CompType[] a = new CompType[12]; + Arrays.setAll(a, n -> CompType.get()); + show("Before sorting", a); + Arrays.sort(a, new CompTypeComparator()); + show("After sorting", a); + } +} +/* Output: +Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] , + [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7] , + [i = 0, j = 25], [i = 62, j = 34], [i = 50, j = 82], + [i = 31, j = 67], [i = 66, j = 54], [i = 21, j = 6] ] +After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] , + [i = 0, j = 25], [i = 62, j = 34], [i = 35, j = 37] , + [i = 66, j = 54], [i = 31, j = 67], [i = 56, j = 68] , + [i = 77, j = 79], [i = 50, j = 82], [i = 48, j = 93] ] +*/ +``` + + + ## binarySearch二分查找 From 91ed1ab436d97ce93433167345c40044ba26aaa5 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Thu, 10 Oct 2019 21:16:14 +0800 Subject: [PATCH 035/371] =?UTF-8?q?Arrays.sort()=E7=9A=84=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 7eaf7d93..f8c38b02 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2418,8 +2418,64 @@ After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] , */ ``` + +## Arrays.sort()的使用 +使用内置的排序方法,您可以对实现了 **Comparable** 接口或具有 **Comparator** 的任何对象数组 或 任何原生数组进行排序。这里我们生成一个随机字符串对象数组并对其排序: +```JAVA +// arrays/StringSorting.java +// Sorting an array of Strings + +import onjava.*; + +import java.util.Arrays; +import java.util.Collections; + +import static onjava.ArrayShow.*; + +public class StringSorting { + public static void main(String[] args) { + String[] sa = new Rand.String().array(20); + show("Before sort", sa); + Arrays.sort(sa); + show("After sort", sa); + Arrays.sort(sa, Collections.reverseOrder()); + show("Reverse sort", sa); + Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER); + show("Case-insensitive sort", sa); + } +} +/* Output: +Before sort: [btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc, + ygpoalk, ljlbynx, taprwxz, bhmupju, cjwzmmr, + anmkkyh, fcjpthl, skddcat, jbvlgwc, mvducuj, + ydpulcq, zehpfmm, zrxmclh, qgekgly, hyoubzl] + +After sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv, + eloztdv, ewcippc, fcjpthl, gmeinne, hyoubzl, + jbvlgwc, ljlbynx, mvducuj, qgekgly, skddcat, + taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh] + +Reverse sort: [zrxmclh, zehpfmm, ygpoalk, ydpulcq,taprwxz, + skddcat, qgekgly, mvducuj, ljlbynx, jbvlgwc, + hyoubzl, gmeinne, fcjpthl, ewcippc, eloztdv, + cuxszgv, cjwzmmr, btpenpc, bhmupju, anmkkyh] + +Case-insensitive sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr, + cuxszgv, eloztdv, ewcippc, fcjpthl, gmeinne, + hyoubzl, jbvlgwc, ljlbynx, mvducuj, qgekgly, + skddcat, taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh] +*/ +``` + +注意字符串排序算法中的输出。它是字典式的,所以它把所有以大写字母开头的单词放在前面,然后是所有以小写字母开头的单词。(电话簿通常是这样分类的。)无论大小写,要将单词组合在一起,请使用 **String.CASE_INSENSITIVE_ORDER** ,如对sort()的最后一次调用所示。 + +Java标准库中使用的排序算法被设计为最适合您正在排序的类型----原生类型的快速排序和对象的归并排序。 + + + +## 并行排序 ## binarySearch二分查找 From 6aa96bfa7552855c34e6addc718d8c3acb7008b9 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Fri, 11 Oct 2019 19:28:32 +0800 Subject: [PATCH 036/371] =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index f8c38b02..8b8f1eca 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2477,6 +2477,44 @@ Java标准库中使用的排序算法被设计为最适合您正在排序的类 ## 并行排序 +如果排序性能是一个问题,那么可以使用 **Java 8 parallelSort()**,它为所有不可预见的情况(包括数组的排序区域或使用了比较器)提供了重载版本。为了查看相比于普通的sort(), **parallelSort()** 的优点,我们使用了用来验证代码时的 **JMH**: + +```java +// arrays/jmh/ParallelSort.java +package arrays.jmh; + +import onjava.*; +import org.openjdk.jmh.annotations.*; + +import java.util.Arrays; + +@State(Scope.Thread) +public class ParallelSort { + private long[] la; + + @Setup + public void setup() { + la = new Rand.Plong().array(100_000); + } + + @Benchmark + public void sort() { + Arrays.sort(la); + } + + @Benchmark + public void parallelSort() { + Arrays.parallelSort(la); + } +} +``` + +**parallelSort()** 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 **Arrays .sort()** 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。 + +您可能会看到不同的结果,但是在我的机器上,并行排序将速度提高了大约3倍。由于并行版本使用起来很简单,所以很容易考虑在任何地方使用它,而不是 +**Arrays.sort ()**。当然,它可能不是那么简单—看看微基准测试。 + + ## binarySearch二分查找 From ecdc6cbb663261ddd527f611904c1c5c630ee7b5 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Fri, 11 Oct 2019 20:11:47 +0800 Subject: [PATCH 037/371] =?UTF-8?q?=E4=BA=8C=E5=88=86=E6=9F=A5=E6=89=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 75 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 8b8f1eca..a6aa6810 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2518,6 +2518,81 @@ public class ParallelSort { ## binarySearch二分查找 +一旦数组被排序,您就可以通过使用 **Arrays.binarySearch()** 来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 **binarySearch()**,结果是不可预测的。下面的示例使用 **Rand.Pint** 类来创建一个填充随机整形值的数组,然后调用 **getAsInt()** (因为 **Rand.Pint** 是一个 **IntSupplier**)来产生搜索值: + +```JAVA +// arrays/ArraySearching.java +// Using Arrays.binarySearch() + +import onjava.*; + +import java.util.Arrays; + +import static onjava.ArrayShow.*; + +public class ArraySearching { + public static void main(String[] args) { + Rand.Pint rand = new Rand.Pint(); + int[] a = new Rand.Pint().array(25); + Arrays.sort(a); + show("Sorted array", a); + while (true) { + int r = rand.getAsInt(); + int location = Arrays.binarySearch(a, r); + if (location >= 0) { + System.out.println("Location of " + r + " is " + location + ", a[" + location + "] is " + a[location]); + break; // Out of while loop + } + } + } +} +/* Output: +Sorted array: [125, 267, 635, 650, 1131, 1506, 1634, 2400, 2766, + 3063, 3768, 3941, 4720, 4762, 4948, 5070, 5682, + 5807, 6177, 6193, 6656, 7021, 8479, 8737, 9954] +Location of 635 is 2, a[2] is 635 +*/ +``` + +在while循环中,随机值作为搜索项生成,直到在数组中找到其中一个为止。 + +如果找到了搜索项,**Arrays.binarySearch()** 将生成一个大于或等于零的值。否则,它将产生一个负值,表示如果手动维护已排序的数组,则应该插入元素的位置。产生的值是 -(插入点) - 1 。插入点是大于键的第一个元素的索引,如果数组中的所有元素都小于指定的键,则是 **a.size()** 。 + +如果数组包含重复的元素,则无法保证找到其中的那些重复项。搜索算法不是为了支持重复的元素,而是为了容忍它们。如果需要没有重复元素的排序列表,可以使用 **TreeSet** (用于维持排序顺序)或 **LinkedHashSet** (用于维持插入顺序)。这些类自动为您处理所有的细节。只有在出现性能瓶颈的情况下,才应该使用手工维护的数组替换这些类中的一个。 + +如果使用比较器(原语数组不允许使用比较器进行排序)对对象数组进行排序,那么在执行 **binarySearch()** (使用重载版本的binarySearch())时必须包含相同的比较器。例如,可以修改 **StringSorting.java** 来执行搜索: + +```JAVA +// arrays/AlphabeticSearch.java +// Searching with a Comparator + +import onjava.*; + +import java.util.Arrays; + +import static onjava.ArrayShow.*; + +public class AlphabeticSearch { + public static void main(String[] args) { + String[] sa = new Rand.String().array(30); + Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER); + show(sa); + int index = Arrays.binarySearch(sa, sa[10], String.CASE_INSENSITIVE_ORDER); + System.out.println("Index: " + index + "\n" + sa[index]); + } +} +/* Output: +[anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv, eloztdv, ewcippc, +ezdeklu, fcjpthl, fqmlgsh, gmeinne, hyoubzl, jbvlgwc, jlxpqds, +ljlbynx, mvducuj, qgekgly, skddcat, taprwxz, uybypgp, vjsszkn, +vniyapk, vqqakbm, vwodhcf, ydpulcq, ygpoalk, yskvett, zehpfmm, +zofmmvm, zrxmclh] +Index: 10 gmeinne +*/ +``` +比较器必须作为第三个参数传递给重载的 **binarySearch()** 。在本例中,成功是有保证的,因为搜索项是从数组本身中选择的。 + + ## parallelPrefix并行前缀 From 2ec8c9eed18a83e5b05c99210683368fccee8a2c Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Fri, 11 Oct 2019 20:24:57 +0800 Subject: [PATCH 038/371] =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 88 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index a6aa6810..b14925c3 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2592,11 +2592,95 @@ Index: 10 gmeinne ``` 比较器必须作为第三个参数传递给重载的 **binarySearch()** 。在本例中,成功是有保证的,因为搜索项是从数组本身中选择的。 - - ## parallelPrefix并行前缀 +没有“prefix()”方法,只有 **parallelPrefix()**。这类似于 **Stream** 类中的 **reduce()** 方法:它对前一个元素和当前元素执行一个操作,并将结果放入当前元素位置: + +```JAVA +// arrays/ParallelPrefix1.java + +import onjava.*; + +import java.util.Arrays; + +import static onjava.ArrayShow.*; + +public class ParallelPrefix1 { + public static void main(String[] args) { + int[] nums = new Count.Pint().array(10); + show(nums); + System.out.println(Arrays.stream(nums).reduce(Integer::sum).getAsInt()); + Arrays.parallelPrefix(nums, Integer::sum); + show(nums); + System.out.println(Arrays.stream(new Count.Pint().array(6)).reduce(Integer::sum).getAsInt()); + } +} +/* Output: +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +45 +[0, 1, 3, 6, 10, 15, 21, 28, 36, 45] +15 +*/ +``` + +这里我们对数组应用Integer::sum。在位置0中,它将先前计算的值(因为没有先前的值)与原始数组位置0中的值组合在一起。在位置1中,它获取之前计算的值(它只是存储在位置0中),并将其与位置1中先前计算的值相结合。依次往复。 + +使用 **Stream.reduce()**,您只能得到最终结果,而使用 **Arrays.parallelPrefix()**,您还可以得到所有中间计算,以确保它们是有用的。注意,第二个 **Stream.reduce()** 计算的结果已经在 **parallelPrefix()** 计算的数组中。 + +使用字符串可能更清楚: + +```JAVA +// arrays/ParallelPrefix2.java + +import onjava.*; + +import java.util.Arrays; + +import static onjava.ArrayShow.*; + +public class ParallelPrefix2 { + public static void main(String[] args) { + String[] strings = new Rand.String(1).array(8); + show(strings); + Arrays.parallelPrefix(strings, (a, b) -> a + b); + show(strings); + } +} +/* Output: +[b, t, p, e, n, p, c, c] +[b, bt, btp, btpe, btpen, btpenp, btpenpc, btpenpcc] +*/ +``` + +如前所述,使用流进行初始化非常优雅,但是对于大型数组,这种方法可能会耗尽堆空间。使用 **setAll()** 执行初始化更节省内存: + +```JAVA +// arrays/ParallelPrefix3.java +// {ExcludeFromTravisCI} + +import java.util.Arrays; + +public class ParallelPrefix3 { + static final int SIZE = 10_000_000; + + public static void main(String[] args) { + long[] nums = new long[SIZE]; + Arrays.setAll(nums, n -> n); + Arrays.parallelPrefix(nums, Long::sum); + System.out.println("First 20: " + nums[19]); + System.out.println("First 200: " + nums[199]); + System.out.println("All: " + nums[nums.length - 1]); + } +} +/* Output: +First 20: 190 +First 200: 19900 +All: 49999995000000 +*/ +``` +因为正确使用 **parallelPrefix()** 可能相当复杂,所以通常应该只在存在内存或速度问题(或两者都有)时使用。否则,**Stream.reduce()** 应该是您的首选。 + ## 本章小结 From 1dc1a75886236c4588301c092123e805a6aa8888 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Fri, 11 Oct 2019 20:50:42 +0800 Subject: [PATCH 039/371] =?UTF-8?q?=E6=95=B0=E7=BB=84=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index b14925c3..74c5e677 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2685,6 +2685,62 @@ All: 49999995000000 ## 本章小结 +Java为固定大小的低级数组提供了合理的支持。这种数组强调的是性能而不是灵活性,就像C和c++数组模型一样。在Java的最初版本中,固定大小的低级数组是绝对必要的,这不仅是因为Java设计人员选择包含原生类型(也考虑到性能),还因为那个版本对集合的支持非常少。因此,在早期的Java版本中,选择数组总是合理的。 + +在Java的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组,即使这样,集合的性能也得到了显著的改进。正如本书其他部分所述,无论如何,性能问题通常不会出现在您设想的地方。 + +使用自动装箱和泛型,在集合中保存原生类型是毫不费力的,这进一步鼓励您用集合替换低级数组。由于泛型产生类型安全的集合,数组在这方面也不再有优势。 + +如本章所述,当您尝试使用泛型时,您将看到泛型对数组是相当不友好的。通常,即使可以让泛型和数组以某种形式一起工作(在下一章中您将看到),在编译期间仍然会出现“unchecked”警告。 + +有几次,当我们讨论特定的例子时,我直接从Java语言设计人员那里听到我应该使用集合而不是数组(我使用数组来演示特定的技术,所以我没有这个选项)。 + +所有这些问题都表明,在使用Java的最新版本进行编程时,应该“优先选择集合而不是数组”。只有当您证明性能是一个问题(并且切换到一个数组实际上会有很大的不同)时,才应该重构到数组。这是一个相当大胆的声明,但是有些语言根本没有固定大小的低级数组。它们只有可调整大小的集合,而且比C/C++/java风格的数组功能多得多。例如,Python有一个使用基本数组语法的列表类型,但是具有更大的功能—您甚至可以从它继承: + +```Python +# arrays/PythonLists.py + +aList=[1,2,3,4,5]print(type(aList)) # +print(aList) # [1,2,3,4,5] + print(aList[4]) # 5Basic list indexing + aList.append(6) # lists can be resized + aList+=[7,8] # Add a list to a list + print(aList) # [1,2,3,4,5,6,7,8] + aSlice=aList[2:4] + print(aSlice) # [3,4] + +class MyList(list): # Inherit from list + # Define a method;'this'pointer is explicit: + def getReversed(self): + reversed=self[:] # Copy list using slices + reversed.reverse() # Built-in list method + return reversed + # No'new'necessary for object creation: + list2=MyList(aList) + print(type(list2)) # + print(list2.getReversed()) # [8,7,6,5,4,3,2,1] + output=""" + + [1, 2, 3, 4, 5] + 5 + [1, 2, 3, 4, 5, 6, 7, 8] + [3, 4] + + [8, 7, 6, 5, 4, 3, 2, 1] + """ + +``` +前一章介绍了基本的Python语法。在这里,通过用方括号包围以逗号分隔的对象序列来创建列表。结果是一个运行时类型为list的对象(print语句的输出显示为同一行中的注释)。打印列表的结果与在Java中使用Arrays.toString()的结果相同。 +通过将 : 操作符放在索引操作中,通过切片来创建列表的子序列。list类型有更多的内置操作,通常只需要序列类型。 +MyList是一个类定义;基类放在括号内。在类内部,def语句生成方法,该方法的第一个参数在Java中自动与之等价,除了在Python中它是显式的,而且标识符self是按约定使用的(它不是关键字)。注意构造函数是如何自动继承的。 + +虽然一切在Python中真的是一个对象(包括整数和浮点类型),你仍然有一个安全门,因为你可以优化性能关键型的部分代码编写扩展的C, c++或使用特殊的工具设计容易加速您的Python代码(有很多)。通过这种方式,可以在不影响性能改进的情况下保持对象的纯度。 + +PHP甚至更进一步,它只有一个数组类型,既充当int索引数组,又充当关联数组(Map)。 + +在经历了这么多年的Java发展之后,我们可以很有趣地推测,如果重新开始,设计人员是否会将原生类型和低级数组放在该语言中(同样在JVM上运行的Scala语言不包括这些)。如果不考虑这些,就有可能开发出一种真正纯粹的面向对象语言(尽管有这样的说法,Java并不是一种纯粹的面向对象语言,这正是因为它的底层缺陷)。关于效率的最初争论总是令人信服的,但是随着时间的推移,我们已经看到了从这个想法向更高层次的组件(如集合)的演进。此外,如果集合可以像在某些语言中一样构建到核心语言中,那么编译器就有更好的机会进行优化。 + +撇开““Green-fields”的推测不谈,我们肯定会被数组所困扰,当你阅读代码时就会看到它们。然而,集合几乎总是更好的选择。 From ca65ccd51b3eb56f14e5b819fe7ea2cbb62ddd3a Mon Sep 17 00:00:00 2001 From: xiangflight Date: Fri, 11 Oct 2019 10:01:52 +0800 Subject: [PATCH 040/371] revision[15] end --- docs/book/15-Exceptions.md | 60 +++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 81b82d2e..653b548d 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -23,7 +23,7 @@ Java 中的异常处理的目的在于通过使用少于目前数量的代码来 C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不关我的事”。所以,程序员不去检查错误情形也就不足为奇了(何况对某些错误情形的检查确实很无聊)。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。 -解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++的基础之上(尽管看上去更像 Object Pascal)。 +解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++ 的基础之上(尽管看上去更像 Object Pascal)。 “异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在那里将作出正确的决定。 @@ -208,7 +208,7 @@ FullConstructors.main(FullConstructors.java:24) 新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键宇明确调用了其基类构造器,它接受一个字符串作为参数。 -在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace0 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本: +在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace() 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本: ```java e.printStackTrace(); @@ -267,7 +267,7 @@ LoggingExceptions.main(LoggingExceptions.java:25) Caught LoggingException ``` -静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一个 java.io.PrintWriter 对象作为参数(PrintWriter 会在[附录:I/O 流 ]() 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。 +静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一个 java.io.PrintWriter 对象作为参数(PrintWriter 会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。 尽管由于 LoggingException 将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便,并因此不需要客户端程序员的干预就可以自动运行,但是更常见的情形是我们需要捕获和记录其他人编写的异常,因此我们必须在异常处理程序中生成日志消息; @@ -914,7 +914,7 @@ DynamicFields.setField(DynamicFields.java:67) 至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField0 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。 -你会注意到,toString0 方法使用了一个 StringBuilder 来创建其结果。在[字符串 ]() 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 +你会注意到,toString0 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,并结合“或(|)”符号。此 Java 7 功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。 @@ -1380,7 +1380,7 @@ public class StormyInning extends Inning implements Storm { 在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。 -接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。 +接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。 异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。 @@ -1402,7 +1402,7 @@ StormyInning.walk() 不能通过编译是因为它抛出了异常,而 Inning.w 你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finaly 子句中却是要被清理的。 -在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类(将在 [附录:I/O 流 ]() 中讨论),这些类的基本用法很简单,你应该很容易明白: +在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类(将在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中讨论),这些类的基本用法很简单,你应该很容易明白: ```java // exceptions/InputFile.java @@ -1458,7 +1458,7 @@ InputFile 的构造器接受字符串作为参数,该字符串表示所要打 getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine(),但是这个异常已经在方法内得到处理,因此 getLine() 不会抛出任何异常。在设计异常时有一个问题:应该把异常全部放在这一层处理;还是先处理一部分,然后再向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。如果用法恰当的话,直接向上层抛出的确能简化编程。在这里,getLine() 方法将异常转换为 RuntimeException,表示一个编程错误。 -用户在不再需要 InputFile 对象时,就必须调用 dispose() 方法,这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源(比如文件句柄),在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面,但我在 [封装 ]() 讲过,你不知道 finalize() 会不会被调用(即使能确定它将被调用,也不知道在什么时候调用),这也是 Java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端程序员,这是他们的责任。 +用户在不再需要 InputFile 对象时,就必须调用 dispose() 方法,这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源(比如文件句柄),在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面,但我在 [封装](./Housekeeping.md) 讲过,你不知道 finalize() 会不会被调用(即使能确定它将被调用,也不知道在什么时候调用),这也是 Java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端程序员,这是他们的责任。 对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的 try 子句: @@ -1624,7 +1624,7 @@ main(String[] args) throws IOException { 1. 需要资源清理 2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。 -一个常见的例子是 jav.io.FileInputstream(将会在[附录:I/O 流 ]() 中提到)。要正确使用它,你必须编写一些棘手的样板代码: +一个常见的例子是 jav.io.FileInputstream(将会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中提到)。要正确使用它,你必须编写一些棘手的样板代码: ```java // exceptions/MessyExceptions.java @@ -1932,7 +1932,7 @@ Caught Sneeze Caught Annoyance ``` -Sneeze 异常会被第一个匹配的 catch 子句捕获,也就是程序里的第一个。然而如果将这个 catch 子句删掉,只留下 Annoyance 的 catch 子句,该程序仍然能运行,因为这次捕获的是 Sneeze 的基类。换句话说,catch(Annoyance e)会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。 +Sneeze 异常会被第一个匹配的 catch 子句捕获,也就是程序里的第一个。然而如果将这个 catch 子句删掉,只留下 Annoyance 的 catch 子句,该程序仍然能运行,因为这次捕获的是 Sneeze 的基类。换句话说,catch(Annoyance a)会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。 如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样: @@ -1946,9 +1946,7 @@ try { } ``` -输出为: - -这样编译器就会发现 Sneeze 的 catch 子句永远也得不到执行,因此它会向你报告错误。 +此时,编译器会发现 Sneeze 的 catch 子句永远得不到执行,因此它会向你报告错误。 @@ -1957,7 +1955,7 @@ try { 异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形” 发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽格。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。 -异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样以来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋向于减少。 +异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。 “被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句,这就导致了吞食则有害(harmful if swallowed)的问题: @@ -1975,11 +1973,11 @@ try { ### 历史 -异常处理起源于 PL/1 和 Mesa 之类的系统中,后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 Ruby 和 C#中。Java 的设计和 C++很相似,只是 Java 的设计者去掉了一些他们认为 C++设计得不好的东西。 +异常处理起源于 PL/1 和 Mesa 之类的系统中,后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 Ruby 和 C# 中。Java 的设计和 C++ 很相似,只是 Java 的设计者去掉了一些他们认为 C++设计得不好的东西。 -为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架,异常处理机制很晚才被加入 C++标准化过程中,这是由 C++的设计者 Bjarne Stroustrup 所倡议。C++的异常模型主要借鉴了 CLU 的做法。然而,当时其他语言已经支持异常处理了:包括 Ada、Smalltalk(两者都有异常处理,但是都没有异常说明),以及 Modula-3(它既有异常处理也有异常说明)。 +为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架,异常处理机制很晚才被加入 C++ 标准化过程中,这是由 C++ 的设计者 Bjarne Stroustrup 所倡议。C++ 的异常模型主要借鉴了 CLU 的做法。然而,当时其他语言已经支持异常处理了:包括 Ada、Smalltalk(两者都有异常处理,但是都没有异常说明),以及 Modula-3(它既有异常处理也有异常说明)。 -Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文。中指出,用瞬时风格(transient fashion)报告错误的语言(如 C 中)有一个主要缺陷,那就是: +Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文中指出,用瞬时风格(transient fashion)报告错误的语言(如 C 中)有一个主要缺陷,那就是: > “....每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常。” @@ -1987,13 +1985,13 @@ Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文。中指 > “....要求程序员把异常处理程序的代码文本附接到会引发异常的调用上,这会降低程序的可读性,使得程序的正常思路被异常处理给破坏了。” -C++中异常的设计参考了 CLU 方式.Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的,因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是,忽略所有的错误,然后使用调试器来跟踪错误。这些程序员知道,使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此,要把他们拉到“使用错误处理”的正轨上,“多出来的”代码决不能太多。我认为,评价 Java 的“被检查的异常”的时候,这一点是很重要的。 +C++ 中异常的设计参考了 CLU 方式。Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的,因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是,忽略所有的错误,然后使用调试器来跟踪错误。这些程序员知道,使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此,要把他们拉到“使用错误处理”的正轨上,“多出来的”代码决不能太多。我认为,评价 Java 的“被检查的异常”的时候,这一点是很重要的。 -C++从 CLU 那里还带来另一种思想:异常说明。这样,就可以用编程的方式在方法的特征签名中,声明这个方法将会抛出异常。异常说明可能有两种意思。一个是“我的代码会产生这种异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理的机制和语法的时候,我们一直在关注“你来处理”部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。 +C++ 从 CLU 那里还带来另一种思想:异常说明。这样,就可以用编程的方式在方法签名中声明这个方法将会抛出异常。异常说明有两个目的:一个是“我的代码会产生这种异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理的机制和语法的时候,我们一直在关注“你来处理”部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。 -C++的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++会调用标准类库的 unexpected() 函数。 +C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++ 不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++ 会调用标准类库的 unexpected() 函数。 -值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于范型用于异常说明的方式存在着一些限制。 +值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于泛型用于异常说明的方式存在着一些限制。 ### 观点 @@ -2029,7 +2027,7 @@ Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者) 我已经听到有人在指责了,他们认为这种言论会令我名誉扫地,会让文明堕落,会导致更高比例的项目失败。他们的信念是应该在编译时指出所有错误,这样才能挽救项目,这种信念可以说是无比坚定的;其实更重要的是要理解编译器的能力限制。在 http://MindView.net/Books/BetterJava 上的补充材料中,我强调了自动构建过程和单元测试的重要性,比起把所有的东西都说成是语法错误,它们的效果可以说是事半功倍。下面这段话是至理名言: -> 好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出了坏程序。 +> 好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出坏程序。 不管怎么说,要让 Java 把“被检查的异常”从语言中去除,这种可能性看来非常渺茫。对语言来说,这个变化可能太激进了点,况且 Sun 的支持者们也非常强大。Sun 有完全向后兼容的历史和策略,实际上所有 Sun 的软件都能在 Sun 的硬件上运行,无论它们有多么古老。然而,如果发现有些“被检查的异常”挡住了路,尤其是发现你不得不去对付那些不知道该如何处理的异常,还是有些办法的。 @@ -2052,7 +2050,7 @@ public class MainException { } ``` -注意,main() 作为一个方法也可以有异常说明,这里异常的类型是 Exception,它也是所有“被检查的异常”的基类。通过把它传递到控制台,就不必在 main() 里写 try-catch 子句了。(不过,实际的文件输人输出操作比这个例子要复杂得多。你将会在[文件 ]() 和[附录:I/O 流 ]() 章节中学到更多) +注意,main() 作为一个方法也可以有异常说明,这里异常的类型是 Exception,它也是所有“被检查的异常”的基类。通过把它传递到控制台,就不必在 main() 里写 try-catch 子句了。(不过,实际的文件输人输出操作比这个例子要复杂得多。你将会在 [文件](./Files.md) 和 [附录:I/O 流](./Appendix-IO-Streams.md) 章节中学到更多) ### 把“被检查的异常”转换为“不检查的异常” @@ -2142,14 +2140,10 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型 本书余下部分将会在合适的时候使用这种“用 RuntimeException 来包装,被检查的异常”的技术。另一种解决方案是创建自己的 RuntimeException 的子类。在这种方式中,不必捕获它,但是希望得到它的其他代码都可以捕获它。 - - ## 异常指南 - - 应该在下列情况下使用异常: 1. 尽可能使用 try-with-resource。 @@ -2173,15 +2167,13 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型 就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。 - - ## 后记:Exception Bizarro World (来自于 2011 年的一篇博文) -我的朋友James Ward正在尝试使用JDBC创建一些非常简单的教学示例,并且不断被检查的异常所挫败。他向我指出 Howard Lewis Ship 的帖子“[被检查的例外的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”。特别是。James 对他必须跳过去做一些应该简单的事情的所有环感到沮丧。即使在finally块中,他也不得不放入更多的try-catch子句,因为关闭连接也会导致异常。它在哪里结束?为了简单起见,你必须在环之后跳过环(请注意,try-with-resources语句可以显着改善这种情况)。 +我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,并且不断被检查的异常所挫败。他向我指出 Howard Lewis Ship 的帖子“[被检查的例外的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”。特别是。James 对他必须跳过去做一些应该简单的事情的所有环感到沮丧。即使在 finally 块中,他也不得不放入更多的 try-catch 子句,因为关闭连接也会导致异常。它在哪里结束?为了简单起见,你必须在环之后跳过环(请注意,try-with-resources语句可以显著改善这种情况)。 -我们开始讨论Go编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。 +我们开始讨论 Go 编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。 我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。 @@ -2197,17 +2189,17 @@ result, err := functionCall() 就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。 -起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题? +起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题? 它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。 -James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。 +James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二者)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。 关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。 -跳入奇异的世界会给 James 带来问题,它为所有程序员增加了更多的工作:因为你无法知道什么时候会发生什么事(你可以随时进入奇怪的世界),你必须添加一些try块来确保没有任何东西从裂缝中滑落。您最终必须进行额外的编程以补偿异常机制(它似乎类似于补偿共享内存并发所需的额外工作)。 +跳入奇异的世界会给 James 带来问题,它为所有程序员增加了更多的工作:因为你无法知道什么时候会发生什么事(你可以随时进入奇怪的世界),你必须添加一些 try 块来确保没有任何东西从裂缝中滑落。您最终必须进行额外的编程以补偿异常机制(它似乎类似于补偿共享内存并发所需的额外工作)。 -Go团队采取了大胆的举动,质疑所有这些,并说,“让我们毫无例外地尝试它,看看会发生什么。”是的,这意味着你通常会在发生错误的地方处理错误,而不是最后将它们聚集在一起尝试块。但这也意味着关于一件事的代码是本地化的,也许这并不是那么糟糕。这也可能意味着您无法轻松组合常见的错误处理代码(除非您确定了常用代码并将其放入函数中,也不是那么糟糕)。但这绝对意味着您不必担心有多个可能的执行路径而且所有这些都需要。 +Go 团队采取了大胆的举动,质疑所有这些,并说,“让我们毫无例外地尝试它,看看会发生什么。”是的,这意味着你通常会在发生错误的地方处理错误,而不是最后将它们聚集在一起 try 块。但这也意味着关于一件事的代码是本地化的,也许这并不是那么糟糕。这也可能意味着您无法轻松组合常见的错误处理代码(除非您确定了常用代码并将其放入函数中,也不是那么糟糕)。但这绝对意味着您不必担心有多个可能的执行路径而且所有这些都需要。 From be04fc9ff580b2d90eedd7c22aebfafe206da479 Mon Sep 17 00:00:00 2001 From: LingCoder <34231795+LingCoder@users.noreply.github.com> Date: Fri, 11 Oct 2019 23:43:18 +0800 Subject: [PATCH 041/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 04cc39ba..0c2a8095 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ - [x] [第十八章 字符串](docs/book/18-Strings.md) - [ ] [第十九章 类型信息](docs/book/19-Type-Information.md) - [ ] [第二十章 泛型](docs/book/20-Generics.md) -- [ ] [第二十一章 数组](docs/book/21-Arrays.md) +- [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) - [x] [第二十三章 注解](docs/book/23-Annotations.md) - [ ] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) From d9a1d27d87242686febb1ee616444325a436f9bb Mon Sep 17 00:00:00 2001 From: xiangflight Date: Sun, 13 Oct 2019 18:30:01 +0800 Subject: [PATCH 042/371] =?UTF-8?q?revision[16]=20=E6=88=AA=E6=AD=A2=20Pre?= =?UTF-8?q?conditions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 66 ++++++++++++---------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 3e3530ae..9def2fd8 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -6,33 +6,33 @@ ### 你永远不能保证你的代码是正确的,你只能证明它是错的。 -让我们先暂停学习编程语言的知识,看看一些代码基础知识。特别是能让你的代码更加健壮的。 - +让我们先暂停编程语言特性的学习,看看一些代码基础知识。特别是能让你的代码更加健壮的知识。 + ## 测试 ### 如果没有测试过,它就是不能工作的。 -Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性过于感到舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试。这说明编译器接受你的语法和基本类型规则,但不意味着你的代码满足程序安全的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过安全性这个目标。迈向代码的第一步就是创建代码测试,争对你的目标检查代码行为。 +Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性感到过于舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试,只是说明编译器接受你代码中的语法和基本类型规则,并不意味着你的代码达到程序的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过这些目标。迈向代码校验的第一步就是创建测试,针对你的目标检查代码的行为。 #### 单元测试 -这个过程是将集成测试构建到你创建的所有代码中,并在当你每次构建你的系统时运行这些测试。这样,构建过程检查不仅仅是检查语法的错误,同时你也教它检查语义错误。 +这个过程是将集成测试构建到你创建的所有代码中,并在每次构建系统时运行这些测试。这样,构建过程不仅能检查语法的错误,同时也能检查语义的错误。 -“单元”指的是测试代码中的一小部分的想法。通常,每个类都有测试检查它所有方法的行为。“系统”测试则是不同的,它检查程序是否满足要求。 +“单元”是指测试一小部分代码 。通常,每个类都有测试来检查它所有方法的行为。“系统”测试则是不同的,它检查的是整个程序是否满足要求。 -C风格的语言,特别是C++,通常会认为性能比安全更重要。用Java编程比C++(一般认为大概快两倍)快的原因是Java的安全性网络:这种特征类似于垃圾回收以及键入检查。通过将单元测试集成到构建过程中,你扩大了这个安全网,有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆重构你的代码。 +C 风格的语言,尤其是 C++,通常会认为性能比安全更重要。用 Java 编程比 C++(一般认为大概快两倍)快的原因是 Java 的安全性保障:比如垃圾回收以及改良的类型检测等特性。通过将单元测试集成到构建过程中,你扩大了这个安全保障,因而有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆地重构你的代码。 -当我意识到,要确保书中代码的正确性时,我自己的测试经历就开始了。这本书通过Gradle构建系统, 你需要安装JDK,你可以通过输入gradlew compileJava编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目(在我看来)任何编程书籍的必备条件——你怎么能相信你没有编译的代码呢? 我发现可以利用搜索和替换在整本书大范围的修改。如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统发现了一个严重的漏洞。 编译程序是毫无疑问的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书并且发现了上面代码的缺陷)。尽管,我收到了来自读者反馈的语法问题。我在实现一个自动化执行测试系统的时候,使用了在早期能看到效果的步骤,但这是迫于出版压力,与此同时我明白我的程序绝对有问题,这些都会变成bug让我自食其果。我也经常收到读者的抱怨说我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者,一开始就没有把它弄对)。为了解决这个问题,我利用Python创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证的。 +我自己的测试经历开始于我意识到要确保书中代码的正确性,书中的所有程序必须能够通过合适的构建系统自动提取、编译。这本书所使用的构建系统是 Gradle。 你只要在安装 JDK 后输入 **gradlew compileJava**,就能编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目,(在我看来)这会很快成为任何编程书籍的必备条件——你怎么能相信没有编译的代码呢? 我还发现我可以使用搜索和替换在整本书进行大范围的修改,如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统中发现了一个严重的漏洞。编译程序毫无疑问是重要的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书会发现书中代码的错误)。但是,我收到了来自读者反馈代码中存在语义问题。当然,这些问题可以通过运行代码发现。我在早期实现一个自动化执行测试系统时尝试了一些不太有效的方式,但迫于出版压力,我明白我的程序绝对有问题,并会以 bug 报告的方式让我自食恶果。我也经常收到读者的抱怨说,我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者一开始就不正确)。为了解决这个问题,我利用 Python 创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证过。 #### JUnit -最初的Junit发布于2000年,大概是基于Java 1.0,因此不能使用Java的反射工具。因此,用旧的JUnit编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为注解一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,Junit通过反射和注解得到了极大的改进,这大大简化了编写单元测试代码的过程。在Java8中,他们甚至增加了对lambdas表达式的支持。本书使用当时最新的Junit5版本 +最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为[注解](./Annotations.md)一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本 -在JUnit最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。 +在 JUnit 最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit 将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。 -让我们尝试一个简单的例子。**CountedList** 继承 **ArrayList** ,添加信息来追踪有多少个**CountedLists**被创建: +让我们尝试一个简单的例子。**CountedList** 继承 **ArrayList** ,添加信息来追踪有多少个 **CountedLists** 被创建: ```java // validating/CountedList.java @@ -43,13 +43,13 @@ public class CountedList extends ArrayList { private static int counter = 0; private int id = counter++; public CountedList() { - System.out.println("CountedList #" + id); + System.out.println("CountedList #" + id); } public int getId() { return id; } } ``` -标准实例是将测试放在它们自己的子目录中。测试还必须放在包中,以便JUnit能够发现它们: +标准做法是将测试放在它们自己的子目录中。测试还必须放在包中,以便 JUnit 能发现它们: ```java // validating/tests/CountedListTest.java @@ -62,12 +62,12 @@ public class CountedListTest { private CountedList list; @BeforeAll static void beforeAllMsg() { - System.out.println(">>> Starting CountedListTest"); + System.out.println(">>> Starting CountedListTest"); } @AfterAll static void afterAllMsg() { - System.out.println(">>> Finished CountedListTest"); + System.out.println(">>> Finished CountedListTest"); } @BeforeEach @@ -75,7 +75,7 @@ private CountedList list; list = new CountedList(); System.out.println("Set up for " + list.getId()); for(int i = 0; i < 3; i++) - list.add(Integer.toString(i)); + list.add(Integer.toString(i)); } @AfterEach @@ -110,8 +110,8 @@ private CountedList list; @Test public void order() { - System.out.println("Running testOrder()"); - compare(list, new String[] { "0", "1", "2" }); + System.out.println("Running testOrder()"); + compare(list, new String[] { "0", "1", "2" }); } @Test @@ -162,45 +162,35 @@ Cleaning up 4 **@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。 -**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在test类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。 +**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在测试类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。 -如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**. +如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**。 -每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是list被分配了一个新的 **CountedList** 对象,然后用 **String“0”、“1”** 和 **“2”** 初始化。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。 +每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是 list 被赋值为一个新的用字符串“0”、“1” 和 “2” 初始化的 **CountedList** 对象。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。 -**insert()** 和 **replace()** 演示了典型的测试方法。JUnit使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(已"assert"开头)验证测试的正确性(更全面的"assert"说明可以在Junit文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个JUnit断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。 +**insert()** 和 **replace()** 演示了典型的测试方法。JUnit 使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(以"assert"开头)验证测试的正确性(更全面的"assert"说明可以在 Junit 文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个 JUnit 断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。 断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。 -**compare()** 是“helper方法”的一个例子,它不是由JUnit执行的,而是被类中的其他测试使用。只要没有**@Test** 注解,Junit就不会运行它,也不需要特定的签名。在这, **compare()** 是 **private** ,表示在测试类使用,但他同样可以是 **public** 。其余的测试方法通过将其重构为 **compare()** 方法来消除重复的代码。 - -本书使用**build.gradle**控制测试,运行本章节的测试,命令: - -**gradlew validating:test** +**compare()** 是“helper方法”的一个例子,它不是由 JUnit 执行的,而是被类中的其他测试使用。只要没有 **@Test** 注解,JUnit 就不会运行它,也不需要特定的签名。在这里,**compare()** 是私有方法 ,表示仅在测试类中使用,但他同样可以是 **public** 。其余的测试方法通过将其重构为 **compare()** 方法来消除重复的代码。 -Gradle不会运行已经运行过的测试,所以如果你没有得到测试结果,先运行: +本书使用 **build.gradle** 控制测试,运行本章节的测试,使用命令:`gradlew validating:test`,Gradle 不会运行已经运行过的测试,所以如果你没有得到测试结果,得先运行:`gradlew validating:clean`。 -**gradlew validating:clean** - -可以用这个命令运行本书的所有测试: +可以用下面这个命令运行本书的所有测试: **gradlew test** -尽管可以用最简单的方法,如CountedListTest.java所示 - -JUnit包含许多额外的测试业务,你可以在其上了解这些结构 - -[junit.org]: junit.org. +尽管可以用最简单的方法,如 **CountedListTest.java** 所示没那样,JUnit 还包括大量的测试结构,你可以到[官网](junit.org)上学习它们。 -Junit是Java最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。 +JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。 #### 测试覆盖率的幻觉 测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage) -计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools) +计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)。 -对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为100%的测试覆盖是唯一可接受的值。这有一个问题,因为100%并不意味着是对测试有效性的最佳测量。你可以测试所有需要它的东西,但是只需要65%的覆盖率。如果需要100%的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。 +对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为 100% 的测试覆盖是唯一可接受的值。这有一个问题,因为 100% 并不意味着是对测试有效性的良好测量。你可以测试所有需要它的东西,但是只需要 65% 的覆盖率。如果需要 100% 的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。 当分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来得到测试质量的任何信息。 From 4fe7ca021061aa20ce186b24f6addeacace98a42 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Mon, 14 Oct 2019 16:19:25 +0800 Subject: [PATCH 043/371] =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/25-Patterns.md | 52 ++++++++++++++++++++++++++++++++++-- docs/images/designproxy.png | Bin 0 -> 58606 bytes 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 docs/images/designproxy.png diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index 345e46dc..16eca6f0 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -78,7 +78,7 @@ public class SingletonPattern { } catch(Exception e) { throw new RuntimeException(e); } - } + } } /* Output: 47 9 */ ``` 创建单例的关键是防止客户端程序员直接创建对象。 在这里,这是通过在Singleton类中将Resource的实现作为私有类来实现的。 @@ -103,6 +103,54 @@ public class SingletonPattern { ## 构建应用程序框架 +应用程序框架允许您从一个类或一组类开始,创建一个新的应用程序,重用现有类中的大部分代码,并根据需要覆盖一个或多个方法来定制应用程序。 + +**模板方法模式** + +应用程序框架中的一个基本概念是模板方法模式,它通常隐藏在底层,通过调用基类中的各种方法来驱动应用程序(为了创建应用程序,您已经覆盖了其中的一些方法)。 + +模板方法模式的一个重要特性是它是在基类中定义的,并且不能更改。它有时是一个 **private** 方法,但实际上总是 **final**。它调用其他基类方法(您覆盖的那些)来完成它的工作,但是它通常只作为初始化过程的一部分被调用(因此框架使用者不一定能够直接调用它)。 + +```Java +// patterns/TemplateMethod.java +// Simple demonstration of Template Method + +abstract class ApplicationFramework { + ApplicationFramework() { + templateMethod(); + } + + abstract void customize1(); + + abstract void customize2(); // "private" means automatically "final": private void templateMethod() { IntStream.range(0, 5).forEach( n -> { customize1(); customize2(); }); }}// Create a new "application": class MyApp extends ApplicationFramework { @Override void customize1() { System.out.print("Hello "); }@Override + + void customize2() { + System.out.println("World!"); + } +} + +public class TemplateMethod { + public static void main(String[] args) { + new MyApp(); + } +} +/* +Output: +Hello World! +Hello World! +Hello World! +Hello World! +Hello World! +*/ +``` + +基类构造函数负责执行必要的初始化,然后启动运行应用程序的“engine”(模板方法模式)(在GUI应用程序中,这个“engine”是主事件循环)。框架使用者只提供 +**customize1()** 和 **customize2()** 的定义,然后“应用程序”已经就绪运行。 + +![](images/designproxy.png) + + + ## 面向实施 @@ -161,4 +209,4 @@ public class SingletonPattern { -
\ No newline at end of file +
diff --git a/docs/images/designproxy.png b/docs/images/designproxy.png new file mode 100644 index 0000000000000000000000000000000000000000..ceefd64fe6149afe98ddd81a19fabe2dbcbc7140 GIT binary patch literal 58606 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 literal 0 HcmV?d00001 From d88a940b6b2a0f31723f901dab5b0792152bc421 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Mon, 14 Oct 2019 16:36:57 +0800 Subject: [PATCH 044/371] =?UTF-8?q?=E9=9D=A2=E5=90=91=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=90=86=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/25-Patterns.md | 72 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index 16eca6f0..cb43a04d 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -149,11 +149,79 @@ Hello World! ![](images/designproxy.png) + +## 面向实现 +代理模式和桥接模式都提供了在代码中使用的代理类;完成工作的真正类隐藏在这个代理类的后面。当您在代理中调用一个方法时,它只是反过来调用实现类中的方法。这两种模式非常相似,所以代理模式只是桥接模式的一种特殊情况。人们倾向于将两者合并,称为代理模式,但是术语“代理”有一个长期的和专门的含义,这可能解释了这两种模式不同的原因。基本思想很简单:从基类派生代理,同时派生一个或多个提供实现的类:创建代理对象时,给它一个可以调用实际工作类的方法的实现。 + + +在结构上,代理模式和桥接模式的区别很简单:代理模式只有一个实现,而桥接模式有多个实现。在设计模式中被认为是不同的:代理模式用于控制对其实现的访问,而桥接模式允许您动态更改实现。但是,如果您扩展了“控制对实现的访问”的概念,那么这两者就可以完美地结合在一起 + +**代理模式** + +如果我们按照上面的关系图实现,它看起来是这样的: + +```Java +// patterns/ProxyDemo.java +// Simple demonstration of the Proxy pattern +interface ProxyBase { + void f(); + + void g(); + + void h(); +} + +class Proxy implements ProxyBase { + private ProxyBase implementation; + + Proxy() { + implementation = new Implementation(); + } + // Pass method calls to the implementation: + @Override + public void f() { implementation.f(); } + @Override + public void g() { implementation.g(); } + @Override + public void h() { implementation.h(); } +} + +class Implementation implements ProxyBase { + public void f() { + System.out.println("Implementation.f()"); + } + + public void g() { + System.out.println("Implementation.g()"); + } + + public void h() { + System.out.println("Implementation.h()"); + } +} + +public class ProxyDemo { + public static void main(String[] args) { + Proxy p = new Proxy(); + p.f(); + p.g(); + p.h(); + } +} +/* +Output: +Implementation.f() +Implementation.g() +Implementation.h() +*/ +``` + +具体实现不需要与代理对象具有相同的接口;只要代理对象以某种方式“代表具体实现的方法调用,那么基本思想就算实现了。然而,拥有一个公共接口是很方便的,因此具体实现必须实现代理对象调用的所有方法。 + +**状态模式** - -## 面向实施 From 25e26909d6d899d29b4c89df8c5504977c3fc9c6 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Mon, 14 Oct 2019 19:10:08 +0800 Subject: [PATCH 045/371] =?UTF-8?q?revision[16]=20=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=96=AD=E8=A8=80=E8=BF=9B=E8=A1=8C=E5=A5=91?= =?UTF-8?q?=E7=BA=A6=E5=BC=8F=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 38 +++++++++++----------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 9def2fd8..adbda18a 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -198,23 +198,23 @@ JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的 ## 前置条件 -前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从Java的断言机制开始来介绍DBC,最后使用谷歌Guava库作为前置条件。 +前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从 Java 的断言机制开始来介绍 DBC,最后使用谷歌的 Guava 库作为前置条件。 #### 断言(Assertions) -断言通过验证在程序执行期间满足某些条件而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段,它表示日历上的月份。这个数字总是介于1-12之间。通过断言检查,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。 +断言通过验证在程序执行期间满足某些条件,从而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段表示日历上的月份。这个数字总是介于 1-12 之间。断言可以检查这个数字,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。 -#### Java断言语法 +#### Java 断言语法 -你可以通过其它程序设计架构来模拟断言的效果,因此,在Java中包含断言的意义在于它们易于编写。断言语句有两种形式 : +你可以通过其它程序设计架构来模拟断言的效果,因此,在 Java 中包含断言的意义在于它们易于编写。断言语句有两种形式 : assert boolean-expression; assert boolean-expression: information-expression; -两者似乎告诉我们 **“我断言这个布尔表达式会产生一个真正的值”**, 否则,将抛出**AssertionError**异常。 +两者似乎告诉我们 **“我断言这个布尔表达式会产生 true”**, 否则,将抛出 **AssertionError** 异常。 -这是**Throwable**的派生类,因此不需要异常规范。 +**AssertionError** 是 **Throwable** 的派生类,因此不需要异常说明。 不幸的是,第一种断言形式的异常不会生成包含布尔表达式的任何信息(与大多数其他语言的断言机制相反)。 @@ -229,7 +229,7 @@ assert boolean-expression: information-expression; // {ThrowsException} public class Assert1 { public static void main(String[] args) { - assert false; + assert false; } } @@ -240,9 +240,9 @@ at Assert1.main(Assert1.java:9) */ ``` -如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** flag, 它也可以表示为: **-enableassertion** , 这将运行程序并执行任何断言语句。 +如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** 标志, 它也可以表示为: **-enableassertion**, 这将运行程序并执行任何断言语句。 -输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本: +输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本: ```java // validating/Assert2.java @@ -264,11 +264,11 @@ at Assert2.main(Assert2.java:8) */ ``` -**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它是否与失败的断言有关。 +**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它包含失败的断言。 -还可以通过类名或包名打开或关闭断言;也就是说,你可以为整个包启用或禁用断言。实现这一点的详细信息在JDK的断言文档中。你想要打开或关闭某些断言时,此特性对于使用断言进行工具化的大型项目非常有用。但是,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。 +你还可以基于类名或包名打开或关闭断言;也就是说,你可以对整个包启用或禁用断言。实现这一点的详细信息在 JDK 的断言文档中。此特性对于使用断言的大型项目来说很有用当你想打开或关闭某些断言时。但是,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。 -这有另一种办法控制你的断言:编程方式,通过链接到类加载器对象(**ClassLoader**)。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以认为你像下面这样悄悄地开启了断言: +你还可以通过编程的方式通过链接到类加载器对象(**ClassLoader**)来控制断言。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以像下面这样悄悄地开启断言: ```java // validating/LoaderAssertions.java @@ -298,7 +298,7 @@ LoaderAssertions.main(LoaderAssertions.java:9) */ ``` -这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数。这是有道理的,然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中: +这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数,这么做是有意义的。然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中: ```java static { @@ -306,19 +306,15 @@ static { // Note intentional side effect of assignment: assert assertionsEnabled = true; if(!assertionsEnabled) - throw new RuntimeException("Assertions disabled"); + throw new RuntimeException("Assertions disabled"); } ``` - - 如果启用断言,然后执行 **assert** 语句,**assertionsEnabled** 变为 **true** 。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert** 语句不执行,**assertionsEnabled** 保持false,将导致异常。 - - #### Guava断言 -因为启用Java本地断言很麻烦,Guava团队添加一个始终启用替换断言的 **Verify** 类。他们建议静态导入 **Verify** 方法: +因为启用 Java 本地断言很麻烦,Guava 团队添加一个始终启用的用来替换断言的 **Verify** 类。他们建议静态导入 **Verify** 方法: ```java // validating/GuavaAssertions.java @@ -373,12 +369,8 @@ Shouldn't be null: arg s */ ``` - - 这里有两个方法,使用变量 **verify()** 和 **verifyNotNull()** 来支持有用的错误消息。注意,**verifyNotNull()** 内置的错误消息通常就足够了,而 **verify()** 太一般,没有有用的默认错误消息。 - - #### 使用断言进行契约式设计 *契约式设计(DbC)*是Bertrand Meyer提出的一个概念,Eiffel语言的发明者,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel也是如此),但是它们创建了一种非正式的DBC编程风格。DbC假定服务供应商与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应商和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。 From c1bd578de91e8c3db18419d69fd1e17411a3ca21 Mon Sep 17 00:00:00 2001 From: X-ljy <17637938901@163.com> Date: Tue, 15 Oct 2019 18:01:06 +0800 Subject: [PATCH 046/371] =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/25-Patterns.md | 198 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index cb43a04d..9bd8e81c 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -221,7 +221,205 @@ Implementation.h() **状态模式** +状态模式向代理对象添加了更多的实现,以及在代理对象的生命周期内从一个实现切换到另一种实现的方法: +```Java +// patterns/StateDemo.java // Simple demonstration of the State pattern +interface StateBase { + void f(); + + void g(); + + void h(); + + void changeImp(StateBase newImp); +} + +class State implements StateBase { + private StateBase implementation; + + State(StateBase imp) { + implementation = imp; + } + + @Override + public void changeImp(StateBase newImp) { + implementation = newImp; + }// Pass method calls to the implementation: @Override public void f() { implementation.f(); } @Override public void g() { implementation.g(); } @Override + + public void h() { + implementation.h(); + } +} + +class Implementation1 implements StateBase { + @Override + public void f() { + System.out.println("Implementation1.f()"); + } + + @Override + public void g() { + System.out.println("Implementation1.g()"); + } + + @Override + public void h() { + System.out.println("Implementation1.h()"); + } + + @Override + public void changeImp(StateBase newImp) { + } +} + +class Implementation2 implements StateBase { + @Override + public void f() { + System.out.println("Implementation2.f()"); + } + + @Override + public void g() { + System.out.println("Implementation2.g()"); + } + + @Override + public void h() { + System.out.println("Implementation2.h()"); + } + + @Override + public void changeImp(StateBase newImp) { + } +} + +public class StateDemo { + static void test(StateBase b) { + b.f(); + b.g(); + b.h(); + } + + public static void main(String[] args) { + StateBase b = new State(new Implementation1()); + test(b); + b.changeImp(new Implementation2()); + test(b); + } +} +/* Output: +Implementation1.f() +Implementation1.g() +Implementation1.h() +Implementation2.f() +Implementation2.g() +Implementation2.h() +*/ +``` + +在main()中,首先使用第一个实现,然后改变成第二个实现。代理模式和状态模式的区别在于它们解决的问题。设计模式中描述的代理模式的常见用途如下: + +1. 远程代理。它在不同的地址空间中代理对象。远程方法调用(RMI)编译器rmic会自动为您创建一个远程代理。 + +2. 虚拟代理。这提供了“懒加载”来根据需要创建“昂贵”的对象。 + +3. 保护代理。当您希望对代理对象有权限访问控制时使用。 + +4. 智能引用。要在被代理的对象被访问时添加其他操作。例如,跟踪特定对象的引用数量,来实现写时复制用法,和防止对象别名。一个更简单的例子是跟踪特定方法的调用数量。您可以将Java引用视为一种保护代理,因为它控制在堆上实例对象的访问(例如,确保不使用空引用)。 + +在设计模式中,代理模式和桥接模式并不是相互关联的,因为它们被赋予(我认为是任意的)不同的结构。桥接模式,特别是使用一个单独的实现,但这似乎对我来说是不必要的,除非你确定该实现是你无法控制的(当然有可能,但是如果您编写所有代码,那么没有理由不从单基类的优雅中受益)。此外,只要代理对象控制对其“前置”对象的访问,代模式理就不需要为其实现使用相同的基类。不管具体情况如何,在代理模式和桥接模式中,代理对象都将方法调用传递给具体实现对象。 + +**状态机** + +桥接模式允许程序员更改实现,状态机利用一个结构来自动地将实现更改到下一个。当前实现表示系统所处的状态,系统在不同状态下的行为不同(因为它使用桥接模式)。基本上,这是一个利用对象的“状态机”。将系统从一种状态移动到另一种状态的代码通常是模板方法模式,如下例所示: + +```Java +// patterns/state/StateMachineDemo.java +// The StateMachine pattern and Template method +// {java patterns.state.StateMachineDemo} +package patterns.state; + +import onjava.Nap; + +interface State { + void run(); +} + +abstract class StateMachine { + protected State currentState; + + Nap(0.5); +System.out.println("Washing"); new + + protected abstract boolean changeState(); + + // Template method: + protected final void runAll() { + while (changeState()) // Customizable + currentState.run(); + } +} + +// A different subclass for each state: +class Wash implements State { + @Override + public void run() { + } +} + +class Spin implements State { + @Override + public void run() { + System.out.println("Spinning"); + new Nap(0.5); + } +} + +class Rinse implements State { + @Override + public void run() { + System.out.println("Rinsing"); + new Nap(0.5); + } +} + +class Washer extends StateMachine { + private int i = 0; + + // The state table: + private State[] states = {new Wash(), new Spin(), new Rinse(), new Spin(),}; + + Washer() { + runAll(); + } + + @Override + public boolean changeState() { + if (i < states.length) { + // Change the state by setting the + // surrogate reference to a new object: + currentState = states[i++]; + return true; + } else return false; + } +} + +public class StateMachineDemo { + public static void main(String[] args) { + new Washer(); + } +} +/* +Output: +Washing +Spinning +Rinsing +Spinning +*/ +``` + +在这里,控制状态的类(本例中是状态机)负责决定下一个状态。然而,状态对象本身也可以决定下一步移动到什么状态,通常基于系统的某种输入。这是更灵活的解决方案。 From ce16903b49dc6ec5ad3a048e965f1c24ac6f376c Mon Sep 17 00:00:00 2001 From: LingCoder <34231795+LingCoder@users.noreply.github.com> Date: Thu, 17 Oct 2019 11:26:26 +0800 Subject: [PATCH 047/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c2a8095..c7beb34d 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ ## 一起交流 -交流群:721698221 OnJava8翻译交流( 点击图标即可加入 )
+交流群:721698221 OnJava8翻译交流( 点击图标即可加入 )
加群时请简单备注下来源或说明
QQGroupQRCode From 22c3a36b641911480e841fda2aa73fd15d0d4d17 Mon Sep 17 00:00:00 2001 From: lanzehao <381574778@qq.com> Date: Thu, 17 Oct 2019 22:59:45 +0800 Subject: [PATCH 048/371] Update 06-Housekeeping.md fix format of print --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index c0e98a15..4721d296 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1421,8 +1421,8 @@ public class VarArgs { static void printArray(Object[] args) { for (Object obj: args) { System.out.print(obj + " "); - System.out.println(); } + System.out.println(); } public static void main(String[] args) { From 202102c6a5591b4cd94f1286397e8d469b0cff5a Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 19 Oct 2019 13:44:58 +0800 Subject: [PATCH 049/371] =?UTF-8?q?fix:=E4=BF=AE=E6=AD=A3=20override=20?= =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/09-Polymorphism.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 53e02e1f..ffd7369e 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -3,7 +3,7 @@ # 第九章 多态 -> 曾经有人请教我 ” Babbage 先生,如果输入错误的数字到机器中,会得出正确结果吗?“ 我无法理解产生如此问题的概念上的困惑。 —— Charles Babbage (1791 - 1871) +> 曾经有人请教我 “ Babbage 先生,如果输入错误的数字到机器中,会得出正确结果吗?” 我无法理解产生如此问题的概念上的困惑。 —— Charles Babbage (1791 - 1871) 多态是面向对象编程语言中,继数据抽象和继承之外的第三个重要特性。 @@ -174,7 +174,7 @@ public static void tune(Instrument i) { Java 中除了 **static** 和 **final** 方法(**private** 方法也是隐式的 **final**)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。 -为什么将一个对象指明为 **final** ?正如前一章所述,它可以防止方法被覆写。但更重要的一点可能是,它有效地”关闭了“动态绑定,或者说告诉编译器不需要对其进行动态绑定。这可以让编译器为 **final** 方法生成更高效的代码。然而,大部分情况下这样做不会对程序的整体性能带来什么改变,因此最好是为了设计使用 **final**,而不是为了提升性能而使用。 +为什么将一个对象指明为 **final** ?正如前一章所述,它可以防止方法被重写。但更重要的一点可能是,它有效地”关闭了“动态绑定,或者说告诉编译器不需要对其进行动态绑定。这可以让编译器为 **final** 方法生成更高效的代码。然而,大部分情况下这样做不会对程序的整体性能带来什么改变,因此最好是为了设计使用 **final**,而不是为了提升性能而使用。 ### 产生正确的行为 @@ -194,7 +194,7 @@ Shape s = new Circle(); 这会创建一个 **Circle** 对象,引用被赋值给 **Shape** 类型的变量 s,这看似错误(将一种类型赋值给另一种类型),然而是没问题的,因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。 -假设你调用了一个基类方法(在各个派生类中都被覆写): +假设你调用了一个基类方法(在各个派生类中都被重写): ```java s.draw() @@ -214,7 +214,7 @@ public class Shape { } ``` -派生类通过覆写这些方法为每个具体的形状提供独一无二的方法行为: +派生类通过重写这些方法为每个具体的形状提供独一无二的方法行为: ```java // polymorphism/shape/Circle.java @@ -469,7 +469,7 @@ Woodwind.play() MIDDLE_C `tune()` 方法可以忽略周围所有代码发生的变化,仍然可以正常运行。这正是我们期待多态能提供的特性。代码中的修改不会破坏程序中其他不应受到影响的部分。换句话说,多态是一项“将改变的事物与不变的事物分离”的重要技术。 -### 陷阱:”覆写“私有方法 +### 陷阱:“重写”私有方法 你可能天真地试图像下面这样做: @@ -503,9 +503,9 @@ public Derived extends PrivateOverride { private f() ``` -你可能期望输出是 **public f()**,然而 **private** 方法也是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重载方法。 +你可能期望输出是 **public f()**,然而 **private** 方法也是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。 -结论是只有非 **private** 方法才能被覆写,但是得小心覆写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。 +结论是只有非 **private** 方法才能被重写,但是得小心重写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。 如果使用了 `@Override` 注解,就能检测出问题: @@ -735,7 +735,7 @@ Sandwich() ### 继承和清理 -在使用组合和继承创建新类时,大部分时候你无需关心清理。子对象通常会留给垃圾收集器处理。如果你存在清理问题,那么必须用心地为新类创建一个 `dispose()` 方法(这里用的是我选择的名称,你可以使用更好的名称)。由于继承,如果有其他特殊的清理工作的话,就必须在派生类中覆写 `dispose()` 方法。当覆写 `dispose()` 方法时,记得调用基类的 `dispose()` 方法,否则基类的清理工作不会发生: +在使用组合和继承创建新类时,大部分时候你无需关心清理。子对象通常会留给垃圾收集器处理。如果你存在清理问题,那么必须用心地为新类创建一个 `dispose()` 方法(这里用的是我选择的名称,你可以使用更好的名称)。由于继承,如果有其他特殊的清理工作的话,就必须在派生类中重写 `dispose()` 方法。当重写 `dispose()` 方法时,记得调用基类的 `dispose()` 方法,否则基类的清理工作不会发生: ```java // polymorphism/Frog.java @@ -972,7 +972,7 @@ Disposing Shared 0 在普通的方法中,动态绑定的调用是在运行时解析的,因为对象不知道它属于方法所在的类还是类的派生类。 -如果在构造器中调用了动态绑定方法,就会用到那个方法的覆写定义。然而,调用的结果难以预料因为被覆写的方法在对象被完全构造出来之前已经被调用,这使得一些 bug 很隐蔽,难以发现。 +如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用,这使得一些 bug 很隐蔽,难以发现。 从概念上讲,构造器的工作就是创建对象(这并非是平常的工作)。在构造器内部,整个对象可能只是部分形成——只知道基类对象已经初始化。如果构造器只是构造对象过程中的一个步骤,且构造的对象所属的类是从构造器所属的类派生出的,那么派生部分在当前构造器被调用时还没有初始化。然而,一个动态绑定的方法调用向外深入到继承层次结构中,它可以调用派生类的方法。如果你在构造器中这么做,就可能调用一个方法,该方法操纵的成员可能还没有初始化——这肯定会带来灾难。 @@ -1024,12 +1024,12 @@ Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 ``` -**Glyph** 的 `draw()` 被设计为可覆写,在 **RoundGlyph** 这个方法被覆写。但是 **Glyph** 的构造器里调用了这个方法,结果调用了 **RoundGlyph** 的 `draw()` 方法,这看起来正是我们的目的。输出结果表明,当 **Glyph** 构造器调用了 `draw()` 时,**radius** 的值不是默认初始值 1 而是 0。这可能会导致在屏幕上只画了一个点或干脆什么都不画,于是我们只能干瞪眼,试图找到程序不工作的原因。 +**Glyph** 的 `draw()` 被设计为可重写,在 **RoundGlyph** 这个方法被重写。但是 **Glyph** 的构造器里调用了这个方法,结果调用了 **RoundGlyph** 的 `draw()` 方法,这看起来正是我们的目的。输出结果表明,当 **Glyph** 构造器调用了 `draw()` 时,**radius** 的值不是默认初始值 1 而是 0。这可能会导致在屏幕上只画了一个点或干脆什么都不画,于是我们只能干瞪眼,试图找到程序不工作的原因。 前一小节描述的初始化顺序并不十分完整,而这正是解决谜团的关键所在。初始化的实际过程是: 1. 在所有事发生前,分配给对象的存储空间会被初始化为二进制 0。 -2. 如前所述调用基类构造器。此时调用覆写后的 `draw()` 方法(是的,在调用 **RoundGraph** 构造器之前调用),由步骤 1 可知,**radius** 的值为 0。 +2. 如前所述调用基类构造器。此时调用重写后的 `draw()` 方法(是的,在调用 **RoundGraph** 构造器之前调用),由步骤 1 可知,**radius** 的值为 0。 3. 按声明顺序初始化成员。 4. 最终调用派生类的构造器。 @@ -1037,13 +1037,13 @@ RoundGlyph.RoundGlyph(), radius = 5 另一方面,应该震惊于输出结果。逻辑方面我们已经做得非常完美,然而行为仍不可思议的错了,编译器也没有报错(C++ 在这种情况下会产生更加合理的行为)。像这样的 bug 很容易被忽略,需要花很长时间才能发现。 -因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在构造器中唯一能安全调用的只有基类的 **final** 方法(包括 **private** 方法,它们自动属于 **final**)。这些方法不能被覆写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。 +因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在构造器中唯一能安全调用的只有基类的 **final** 方法(包括 **private** 方法,它们自动属于 **final**)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。 ## 协变返回类型 -Java 5 中引入了协变返回类型,这表示派生类的被覆写方法可以返回基类方法返回类型的派生类型: +Java 5 中引入了协变返回类型,这表示派生类的被重写方法可以返回基类方法返回类型的派生类型: ```java // polymorphism/CovariantReturn.java @@ -1093,7 +1093,7 @@ Grain Wheat ``` -关键区别在于 Java 5 之前的版本强制要求被覆写的 `process()` 方法必须返回 **Grain** 而不是 **Wheat**,即使 **Wheat** 派生自 **Grain**,因而也应该是一种合法的返回类型。协变返回类型允许返回更具体的 **Wheat** 类型。 +关键区别在于 Java 5 之前的版本强制要求被重写的 `process()` 方法必须返回 **Grain** 而不是 **Wheat**,即使 **Wheat** 派生自 **Grain**,因而也应该是一种合法的返回类型。协变返回类型允许返回更具体的 **Wheat** 类型。 @@ -1160,7 +1160,7 @@ SadActor ### 替代 vs 扩展 -采用“纯粹”的方式创建继承层次结构看上去是最清晰的方法。即只有基类的方法才能在派生类中被覆写,就像下图这样: +采用“纯粹”的方式创建继承层次结构看上去是最清晰的方法。即只有基类的方法才能在派生类中被重写,就像下图这样: ![类图](../images/1562406479787.png) From 99e21476a74b9c0608b1c9cb85075dbdedb5922f Mon Sep 17 00:00:00 2001 From: xiangflight Date: Sat, 19 Oct 2019 17:05:31 +0800 Subject: [PATCH 050/371] =?UTF-8?q?revision[16]=20=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=A9=B1=E5=8A=A8=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 223 +++++++++++++-------------- 1 file changed, 106 insertions(+), 117 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index adbda18a..0e34d906 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -373,21 +373,21 @@ Shouldn't be null: arg s #### 使用断言进行契约式设计 -*契约式设计(DbC)*是Bertrand Meyer提出的一个概念,Eiffel语言的发明者,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel也是如此),但是它们创建了一种非正式的DBC编程风格。DbC假定服务供应商与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应商和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。 +*契约式设计(DbC)*是 Eiffel 语言的发明者 Bertrand Meyer 提出的一个概念,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel 语言也是如此),但是它们创建了一种非正式的 DbC 编程风格。DbC 假定服务供应者与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应者和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。 -**Meyer**认为: +**Meyer** 认为: 1.应该明确指定行为,就好像它是一个契约一样。 2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。 -不管你是否同意,第一条总是对的,在大多数情况下,DbC确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类DbC约束的表达式;如果无法指定约束,则可能对要构建的内容了解得不够。 +不管你是否同意,第一条总是对的,在大多数情况下,DbC 确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类 DbC 约束的表达式;如果无法指定约束,则你可能对要构建的内容了解得不够。 #### 检查指令 -详细研究DbC之前,思考最简单使用断言的办法,**Meyer**称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。 +详细研究 DbC 之前,思考最简单使用断言的办法,**Meyer** 称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。 -在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是作为其中复杂的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。 +在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是复杂反应的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。 检查指令是对你的代码进行补充,当你可以测试并阐明对象或程序的状态时,应该使用它。 @@ -397,7 +397,7 @@ Shouldn't be null: arg s #### 后置条件 -后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在**return**语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。 +后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在 **return** 语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。 #### 不变性 @@ -407,17 +407,17 @@ Shouldn't be null: arg s **2**. 在离开方法之前。 -此外,不变性是关于构造后对象状态的保证。 +此外,不变性是构造后对于对象状态的保证。 -根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 **invariant()** ,它在构造之后以及每个方法的开始和结束时调用。方法可调用如下: +根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 **invariant()** ,它在构造之后以及每个方法的开始和结束时调用。方法以如下方式调用: assert invariant(); 这样,如果出于性能原因禁用断言,就不会产生开销。 -#### 放松DBC检查 或 非严格的DBC +#### 放松 DbC 检查或非严格的 DbC -尽管他强调了前置条件、后置条件和不变性的价值所在,以及在开发过程中使用它们的重要性,Meyer承认在一个产品中包含所有DbC代码并不总是实用的。你可以放松DbC检查,它基于在特定的地方,你可以对代码的信任程度。以下是放松检查的顺序,最安全到最不安全: +尽管 Meyer 强调了前置条件、后置条件和不变性的价值以及在开发过程中使用它们的重要性,他承认在一个产品中包含所有 DbC 代码并不总是实际的。你可以基于对特定位置的代码的信任程度放松 DbC 检查。以下是放松检查的顺序,最安全到最不安全: **1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。 @@ -425,11 +425,11 @@ assert invariant(); **3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。 -**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。**(A)** 在迫切需要性能和概要分析的情况下,将前置条件检查作为瓶颈,**(B)** 并且你有某种合理的保证,即客户端不会违反前置条件(如你自己编写客户端代码的情况)。禁用前置条件检查是可以接受的。 +**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,某些情况下对性能要求很高,通过分析得到前置条件造成了这个瓶颈,而且你有某种合理的保证客户端不会违反前置条件(比如自己编写客户端的情况下),那么禁用前置条件检查是可接受的。 -不应该直接删除检查的代码,因为只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。 +你不应该直接删除检查的代码,而只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。 -#### DBC + 单元测试 +#### DbC + 单元测试 下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。 @@ -447,11 +447,10 @@ assert invariant(); **6**. 不变性:不包含对象的区域必须只有空值。 -下面是实现这些规则的一种方法,为每个DbC元素类型使用显式方法调用。首先,我们创建一个专用的 +下面是实现这些规则的一种方式,为每个 DbC 元素类型使用显式的方法调用。 -##### Exception: - -- ```java +首先,我们创建一个专用的 **Exception**: +```java // validating/CircularQueueException.java package validating; public class CircularQueueException extends RuntimeException { @@ -459,8 +458,10 @@ assert invariant(); super(why); } } - - This is used to report errors with the CircularQueue class: +``` +它用来报告 **CircularQueue** 中出现的错误: + +```java // validating/CircularQueue.java // Demonstration of Design by Contract (DbC) package validating; @@ -469,44 +470,44 @@ assert invariant(); private Object[] data; private int in = 0, // Next available storage space out = 0; // Next gettable object - // Has it wrapped around the circular queue? + // Has it wrapped around the circular queue? private boolean wrapped = false; public CircularQueue(int size) { - data = new Object[size]; - // Must be true after construction: - assert invariant(); - } + data = new Object[size]; + // Must be true after construction: + assert invariant(); + } public boolean empty() { - return !wrapped && in == out; + return !wrapped && in == out; } public boolean full() { - return wrapped && in == out; + return wrapped && in == out; } - public boolean isWrapped() { return wrapped; } + public boolean isWrapped() { return wrapped; } public void put(Object item) { - precondition(item != null, "put() null item"); - precondition(!full(), - "put() into full CircularQueue"); - assert invariant(); - data[in++] = item; - if(in >= data.length) { + precondition(item != null, "put() null item"); + precondition(!full(), + "put() into full CircularQueue"); + assert invariant(); + data[in++] = item; + if(in >= data.length) { in = 0; wrapped = true; - } - assert invariant(); - } + } + assert invariant(); + } public Object get() { - precondition(!empty(), - "get() from empty CircularQueue"); - assert invariant(); - Object returnVal = data[out]; - data[out] = null; - out++; + precondition(!empty(), + "get() from empty CircularQueue"); + assert invariant(); + Object returnVal = data[out]; + data[out] = null; + out++; if(out >= data.length) { out = 0; wrapped = false; @@ -516,65 +517,55 @@ assert invariant(); "Null item in CircularQueue"); assert invariant(); return returnVal; - } + } - // Design-by-contract support methods: + // Design-by-contract support methods: private static void precondition(boolean cond, String msg) { if(!cond) throw new CircularQueueException(msg); } private static boolean postcondition(boolean cond, String msg) { - if(!cond) throw new CircularQueueException(msg); - return true; + if(!cond) throw new CircularQueueException(msg); + return true; } private boolean invariant() { - // Guarantee that no null values are in the - // region of 'data' that holds objects: - for(int i = out; i != in; i = (i + 1) % data.length) - if(data[i] == null) - throw new CircularQueueException( - "null in CircularQueue"); - // Guarantee that only null values are outside the - // region of 'data' that holds objects: - if(full()) return true; - for(int i = in; i != out; i = (i + 1) % data.length) - if(data[i] != null) - throw new CircularQueueException( - "non-null outside of CircularQueue range: " - dump()); - return true; - } + // Guarantee that no null values are in the + // region of 'data' that holds objects: + for(int i = out; i != in; i = (i + 1) % data.length) + if(data[i] == null) + throw new CircularQueueException("null in CircularQueue"); + // Guarantee that only null values are outside the + // region of 'data' that holds objects: + if(full()) return true; + for(int i = in; i != out; i = (i + 1) % data.length) + if(data[i] != null) + throw new CircularQueueException( + "non-null outside of CircularQueue range: " + dump()); + return true; + } - public String dump() { - return "in = " + in + - ", out = " + out + - ", full() = " + full() + - ", empty() = " + empty() + - ", CircularQueue = " + Arrays.asList(data); - } + public String dump() { + return "in = " + in + + ", out = " + out + + ", full() = " + full() + + ", empty() = " + empty() + + ", CircularQueue = " + Arrays.asList(data); + } } - ``` - - **in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。 - - **put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。 - - - - 注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。 +``` - +**in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。 - **postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。 +**put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。 - +注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。 - **dump()** helper方法返回一个包含所有数据的字符串,而不是直接打印数据。这表示你可以展示更多的信息。 +**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。 - +**dump()** 帮助方法返回一个包含所有数据的字符串,而不是直接打印数据。这允许我们用这部分信息做更多事。 - 现在我们可以为类创建JUnit测试: +现在我们可以为类创建 JUnit 测试: ```java // validating/tests/CircularQueueTest.java @@ -588,8 +579,8 @@ assert invariant(); @BeforeEach public void initialize() { while(i < 5) // Pre-load with some data - queue.put(Integer.toString(i++)); - } + queue.put(Integer.toString(i++)); + } // Support methods: private void showFullness() { @@ -614,10 +605,10 @@ assert invariant(); queue.put(Integer.toString(i++)); String msg = ""; try { - queue.put(""); + queue.put(""); } catch(CircularQueueException e) { - msg = e.getMessage(); - System.out.println(msg); + msg = e.getMessage(); + ∂System.out.println(msg); } assertEquals(msg, "put() into full CircularQueue"); showFullness(); @@ -627,10 +618,10 @@ assert invariant(); public void empty() { System.out.println("testEmpty"); while(!queue.empty()) - System.out.println(queue.get()); - String msg = ""; + System.out.println(queue.get()); + String msg = ""; try { - queue.get(); + queue.get(); } catch(CircularQueueException e) { msg = e.getMessage(); System.out.println(msg); @@ -641,37 +632,37 @@ assert invariant(); @Test public void nullPut() { System.out.println("testNullPut"); - String msg = ""; + String msg = ""; try { - queue.put(null); + queue.put(null); } catch(CircularQueueException e) { msg = e.getMessage(); System.out.println(msg); } - assertEquals(msg, "put() null item"); + assertEquals(msg, "put() null item"); } @Test public void circularity() { - System.out.println("testCircularity"); - while(!queue.full()) - queue.put(Integer.toString(i++)); - showFullness(); - assertTrue(queue.isWrapped()); + System.out.println("testCircularity"); + while(!queue.full()) + queue.put(Integer.toString(i++)); + showFullness(); + assertTrue(queue.isWrapped()); while(!queue.empty()) - System.out.println(queue.get()); - showEmptiness(); + System.out.println(queue.get()); + showEmptiness(); while(!queue.full()) - queue.put(Integer.toString(i++)); - showFullness(); + queue.put(Integer.toString(i++)); + showFullness(); while(!queue.empty()) - System.out.println(queue.get()); - showEmptiness(); - } - } + System.out.println(queue.get()); + showEmptiness(); + } + } /* Output: testNullPut put() null item @@ -734,15 +725,15 @@ assert invariant(); */ ``` - **initialize()** 添加了一些数据,因此每个测试的 **CircularQueue** 都是部分满的。**showFullness()** 和 **showempty()** 表明 **CircularQueue** 是满的还是空的,这四种测试方法中的每一种都确保了**CircularQueue**功能在不同地方的正确运行。 +**initialize()** 添加了一些数据,因此每个测试的 **CircularQueue** 都是部分满的。**showFullness()** 和 **showempty()** 表明 **CircularQueue** 是满的还是空的,这四种测试方法中的每一种都确保了 **CircularQueue** 功能在不同的地方正确运行。 -通过将Dbc和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些Dbc测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。 +通过将 Dbc 和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些 Dbc 测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。 #### 使用Guava前置条件 -在非严格DBC中,前置条件是DbC中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为Java在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。 +在非严格的 DbC 中,前置条件是 DbC 中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为 Java 在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。 -谷歌的Guava库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件: +谷歌的 Guava 库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件: ```java // validating/GuavaPreconditions.java @@ -864,12 +855,10 @@ NullPointerException */ ``` -虽然Guava的前置条件适用于所有类型,但我只演示 **字符串(String)** 类型。**test()** 方法需要一个Consumer,因此我们可以传递一个lambda表达式作为第一个参数字符串。以及作为第二个参数传递给lambda的字符串。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给lambda表达式。try块中的第二个 **println**() 仅在lambda表达式成功时才显示; 否则catch将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。 +虽然 Guava 的前置条件适用于所有类型,但我这里只演示 **字符串(String)** 类型。**test()** 方法需要一个Consumer,因此我们可以传递一个 lambda 表达式作为第一个参数,传递给 lambda 表达式的字符串作为第二个参数。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给 lambda 表达式。try 块中的第二个 **println**() 仅在 lambda 表达式成功时才显示; 否则 catch 块将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。 每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以你可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null** 值的对象构造: -/ - ```java / validating/NonNullConstruction.java import static com.google.common.base.Preconditions.*; @@ -887,11 +876,11 @@ public class NonNullConstruction { } ``` -**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()** 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。 +**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()** 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。 最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()** 确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。 **checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。 -所有Guava前置条件对于基本类型和对象都有必要的重载。 +所有的 Guava 前置条件对于基本类型和对象都有必要的重载。 From e9ab85df13c022565efb6f6f2d3c0f3261c88de9 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Wed, 23 Oct 2019 22:10:51 +0800 Subject: [PATCH 051/371] =?UTF-8?q?[revision=2016](=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8JDB=E8=B0=83=E8=AF=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 68 +++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 0e34d906..db0bcd97 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -888,9 +888,9 @@ public class NonNullConstruction { 之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码,而且还可以得到更好的代码设计。 一般情况下这个说法都是正确的。 一旦我想到“我将如何测试我的代码?”,这个想法将使我的代码产生变化,并且往往是从“可测试”转变为“可用”。 -纯粹的TDD主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 **String** 中字符的大小写。 让我们随意添加一些约束:**String** 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。 +纯粹的 TDD 主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 **String** 中字符的大小写。 让我们随意添加一些约束:**String** 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。 -此示例与标准TDD不同,因为它的作用在于接收 **StringInverter** 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 所以 **StringInverter** 将作为接口来满足这个要求: +此示例与标准 TDD 不同,因为它的作用在于接收 **StringInverter** 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 为了满足这个要求,将 **StringInverter** 作为接口: ```java // validating/StringInverter.java @@ -901,7 +901,7 @@ interface StringInverter { } ``` -现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了JUnit5中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。 +现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了 JUnit5 中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。 JUnit5 提供了几种动态生成测试的方法,但这里使用的方法可能是最复杂的。 **DynamicTest.stream() **方法采用了: @@ -1118,7 +1118,7 @@ public class Inverter4 implements StringInverter { 你将从测试输出中看到,每个版本的 **Inverter** 都几乎能通过所有测试。 当你在进行测试优先的开发时会有相同的体验。 -**DynamicStringInverterTests.java** 仅是为了显示TDD过程中不同 **StringInverter** 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 **StringInverter** 类直到它满足所有测试: +**DynamicStringInverterTests.java** 仅是为了显示 TDD 过程中不同 **StringInverter** 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 **StringInverter** 类直到它满足所有测试: ```java // validating/tests/StringInverterTests.java @@ -1199,7 +1199,7 @@ public class StringInverterTests { ### 日志会给出正在运行的程序的各种信息。 -在调试程序中,日志可以是显示程序运行过程中的普通状态数据(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。 +在调试程序中,日志可以是普通状态数据,用于显示程序运行过程(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。 在调试期间,日志也能带来好处。 如果没有日志,你可能会尝试通过插入 **println()** 语句来打印出程序的行为。 本书中的一些例子使用了这种技术,并且在没有调试器的情况下(下文中很快就会介绍这样一个主题),它就是你唯一的工具。 但是,一旦你确定程序正常运行,你可能会将 **println()** 语句注释或者删除。 然而,如果你遇到更多错误,你可能又需要运行它们。因此,如果能够只在需要时轻松启用输出程序状态就好多了。 @@ -1235,7 +1235,7 @@ public class SLF4JLogging { 日志输出中的格式和信息,甚至输出是否正常或“错误”都取决于 SLF4J 所连接的后端程序包是怎样实现的。 在上面的示例中,它连接到的是 **logback** 库(通过本书的 **build.gradle** 文件),并显示为标准输出。 -如果我们修改 **build.gradle **从而不使用内置在 JDK 中的日志包作为后端,则输出显示为错误输出,如下所示: +如果我们修改 **build.gradle** 从而使用内置在 JDK 中的日志包作为后端,则输出显示为错误输出,如下所示: **Aug 16, 2016 5:40:31 PM InfoLogging main** **INFO: hello logging** @@ -1303,6 +1303,62 @@ public class SLF4JLevels { ## 调试 +尽管聪明地使用 **System.out** 或日志信息能给我们带来对程序行为的有效见解,但对于困难问题来说,这种方式就显得笨拙且耗时了。 + +你也可能需要更加深入地理解程序,仅依靠打印日志做不到。此时你需要调试器。除了比打印语句更快更轻易地展示信息以外,调试器还可以设置断点,并在程序运行到这些断点处暂停程序。 + +使用调试器,可以展示任何时刻的程序状态,查看变量的值,一步一步运行程序,连接远程运行的程序等等。特别是当你构建较大规模的系统(bug 容易被掩埋)时,熟练使用调试器是值得的。 + +#### 使用 JDB 调试 + +Java 调试器(JDB)是 JDK 内置的命令行工具。从调试的指令和命令行接口两方面看的话,JDB 至少从概念上是 GNU 调试器(GDB,受 Unix DB 的影响)的继承者。JDB 对于学习调试和执行简单的调试任务来说是有用的,而且知道只要安装了 JDK 就可以使用 JDB 是有帮助的。然而,对于大型项目来说,你可能想要一个图形化的调试器,这在后面会描述。 + +假设你写了如下程序: + +```java +// validating/SimpleDebugging.java +// {ThrowsException} +public class SimpleDebugging { + private static void foo1() { + System.out.println("In foo1"); + foo2(); + } + + private static void foo2() { + System.out.println("In foo2"); + foo3(); + } + + private static void foo3() { + System.out.println("In foo3"); + int j = 1; + j--; + int i = 5 / j; + } + + public static void main(String[] args) { + foo1(); + } +} +/* Output +In foo1 +In foo2 +In foo3 +__[Error Output]__ +Exception in thread "main" +java.lang.ArithmeticException: /by zero +at +SimpleDebugging.foo3(SimpleDebugging.java:17) +at +SimpleDebugging.foo2(SimpleDebugging.java:11) +at +SimpleDebugging.foo1(SimpleDebugging.java:7) +at +SimpleDebugging.main(SimpleDebugging.java:20) +``` + +首先看方法 `foo3()`,问题很明显:除数是 0。但是假如这段代码被埋没在大型程序中(像这里的调用序列暗示的那样)而且你不知道从哪儿开始查找问题。结果呢,异常会给出足够的信息让你定位问题。然而,假设事情更加复杂,你必须更加深入程序中来获得比异常提供的更多的信息。 + ## 基准测试 From f578252d215c3cda8f99cc8656fbb101a8095cc0 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 26 Oct 2019 15:16:09 +0800 Subject: [PATCH 052/371] Update 19-Type-Information.md close #287 --- docs/book/19-Type-Information.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 3276d244..7f0d62f3 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -255,7 +255,7 @@ Canonical name : typeinfo.toys.Toy 另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。 -`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造书。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。 +、Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。 ### 类字面常量 @@ -1617,4 +1617,4 @@ boring3 -
\ No newline at end of file +
From 60f8fb12329d9a5f5d71d3b917907d71f1038515 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 26 Oct 2019 15:30:36 +0800 Subject: [PATCH 053/371] Update 19-Type-Information.md --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 7f0d62f3..cd85f70f 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -255,7 +255,7 @@ Canonical name : typeinfo.toys.Toy 另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。 -、Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。 +`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。 ### 类字面常量 From 3ed1bd121fb3e83e2308a3ac0d48af8208e4d35e Mon Sep 17 00:00:00 2001 From: yrucrew Date: Sun, 27 Oct 2019 12:43:53 +0800 Subject: [PATCH 054/371] Update 08-Reuse.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix:修正statement翻译 --- docs/book/08-Reuse.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index 99e6768a..b422e115 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -144,7 +144,7 @@ castille = Constructed ``` -在 **Bath** 构造函数中,有一行语句在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。 +在 **Bath** 构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。 当调用 `toString()` 时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。 From 79abc64fb80f645764a993abcddd231b21c2f164 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Sun, 27 Oct 2019 16:09:24 +0800 Subject: [PATCH 055/371] =?UTF-8?q?[revision=2016](=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E5=9F=BA=E5=87=86=E6=B5=8B=E8=AF=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 116 ++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index db0bcd97..e449d6a9 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -1309,7 +1309,7 @@ public class SLF4JLevels { 使用调试器,可以展示任何时刻的程序状态,查看变量的值,一步一步运行程序,连接远程运行的程序等等。特别是当你构建较大规模的系统(bug 容易被掩埋)时,熟练使用调试器是值得的。 -#### 使用 JDB 调试 +### 使用 JDB 调试 Java 调试器(JDB)是 JDK 内置的命令行工具。从调试的指令和命令行接口两方面看的话,JDB 至少从概念上是 GNU 调试器(GDB,受 Unix DB 的影响)的继承者。JDB 对于学习调试和执行简单的调试任务来说是有用的,而且知道只要安装了 JDK 就可以使用 JDB 是有帮助的。然而,对于大型项目来说,你可能想要一个图形化的调试器,这在后面会描述。 @@ -1359,10 +1359,124 @@ SimpleDebugging.main(SimpleDebugging.java:20) 首先看方法 `foo3()`,问题很明显:除数是 0。但是假如这段代码被埋没在大型程序中(像这里的调用序列暗示的那样)而且你不知道从哪儿开始查找问题。结果呢,异常会给出足够的信息让你定位问题。然而,假设事情更加复杂,你必须更加深入程序中来获得比异常提供的更多的信息。 +为了运行 JDB,你需要在编译 **SimpleDebugging.java** 时加上 **-g** 标记,从而告诉编译器生成编译信息。然后使用如下命令开始调试程序: + +**jdb SimpleDebugging** + +接着 JDB 就会运行,出现命令行提示。你可以输入 **?** 查看可用的 JDB 命令。 + +这里展示了如何使用交互式追踪一个问题的调试历程: + +**Initializing jdb...** + +**> catch Exception** + +`>` 表明 JDB 在等待输入命令。命令 **catch Exception** 在任何抛出异常的地方设置断点(然而,即使你不显式地设置断点,调试器也会停止— JDB 中好像是默认在异常抛出处设置了异常)。接着命令行会给出如下响应: + +**Deferring exception catch Exception.** + +**It will be set after the class is loaded.** + +继续输入: + +**> run** + +现在程序将运行到下个断点处,在这个例子中就是异常发生的地方。下面是运行 **run** 命令的结果: + +**run SimpleDebugging** + +**Set uncaught java.lang.Throwable** + +**Set deferred uncaught java.lang.Throwable** + +**>** + +**VM Started: In foo1** + +**In foo2** + +**In foo3** + +**Exception occurred: java.lang.ArithmeticException** + +**(uncaught)"thread=main",** + +**SimpleDebugging.foo3(),line=16 bci=15** + +**16 int i = 5 / j** + +程序运行到第16行时发生异常,但是 JDB 在异常发生时就不复存在。调试器还展示了是哪一行导致了异常。你可以使用 **list** 将导致程序终止的执行点列出来: + +**main[1] list** + +**12 private static void foo3() {** + +**13 System.out.println("In foo3");** + +**14 int j = 1;** + +**15 j--;** + +**16 => int i = 5 / j;** + +**17 }** + +**18 public static void main(String[] args) {** + +**19 foo1();** + +**20 }** + +**21 }** + +**/* Output:** + +上述 `=>` 展示了程序将继续运行的执行点。你可以使用命令 **cont**(continue) 继续运行,但是会导致 JDB 在异常发生时退出并打印出栈轨迹信息。 + +命令 **locals** 能转储所有的局部变量值: + +**main[1] locals** + +**Method arguments:** + +**Local variables:** + +**j = 0** + +命令 **wherei** 打印进入当前线程的方法栈中的栈帧信息: + +**main[1] wherei** + +**[1] SimpleDebugging.foo3(SimpleDebugging.java:16), pc =15** + +**[2] SimpleDebugging.foo2(SimpleDebugging.java:10), pc = 8** + +**[3] SimpleDebugging.foo1(SimpleDebugging.java:6), pc = 8** + +**[4] SimpleDebugging.main(SimpleDebugging.java:19), pc = 10** + +**wherei** 后的每一行代表一个方法调用和调用返回点(由程序计数器显示数值)。这里的调用序列是 **main()**, **foo1()**, **foo2()** 和 **foo3()**。 + +因为命令 **list** 展示了执行停止的地方,所以你通常有足够的信息得知发生了什么并修复它。命令 **help** 将会告诉你更多关于 **jdb** 的用法,但是在花更多的时间学习它之前必须明白命令行调试器往往需要花费更多的精力得到结果。使用 **jdb** 学习调试的基础部分,然后转而学习图形界面调试器。 + +### 图形化调试器 + +使用类似 JDB 的命令行调试器是不方便的。它需要显式的命令去查看变量的状态(**locals**, **dump**),列出源代码中的执行点(**list**),查找系统中的线程(**threads**),设置断点(**stop in**, **stop at**)等等。使用图形化调试器只需要点击几下,不需要使用显式的命令就能使用这些特性,而且能查看被调试程序的最新细节。 + +因此,尽管你可能一开始用 JDB 尝试调试,但是你将发现使用图形化调试器能更加高效、更快速地追踪 bug。IBM 的 Eclipse,Oracle 的 NetBeans 和 JetBrains 的 IntelliJ 这些集成开发环境都含有面向 Java 语言的好用的图形化调试器。 + ## 基准测试 +> 我们应该忘掉微小的效率,说的就是这些 97% 的时间:过早的优化是万恶之源。 +> +> ​ —— Donald Knuth + +如果你发现自己正在过早优化的滑坡上,你可能浪费了几个月的时间(如果你雄心勃勃的话)。通常,一个简单直接的编码方法就足够好了。如果你进行了不必要的优化,就会使你的代码变得无谓的复杂和难以理解。 + +基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。 + ## 分析和优化 From cd7e104804a65cbc88731bc7435ad17461a78114 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Tue, 29 Oct 2019 23:02:28 +0800 Subject: [PATCH 056/371] =?UTF-8?q?[feat16](=E6=88=AA=E6=AD=A2=20=E5=BE=AE?= =?UTF-8?q?=E5=9F=BA=E5=87=86=E6=B5=8B=E8=AF=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 96 +++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index e449d6a9..283b2160 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -1469,7 +1469,7 @@ SimpleDebugging.main(SimpleDebugging.java:20) ## 基准测试 -> 我们应该忘掉微小的效率,说的就是这些 97% 的时间:过早的优化是万恶之源。 +> 我们应该忘掉微小的效率提升,说的就是这些 97% 的时间做的事:过早的优化是万恶之源。 > > ​ —— Donald Knuth @@ -1477,6 +1477,100 @@ SimpleDebugging.main(SimpleDebugging.java:20) 基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。 +可以简单地对一个代码片段的执行计时吗?在像 C 这样直接的编程语言中,这个方法的确可行。在像 Java 这样拥有复杂的运行时系统的编程语言中,基准测试变得更有挑战性。为了生成可靠的数据,环境设置必须控制诸如 CPU 频率,节能特性,其他运行在相同机器上的进程,优化器选项等等。 + +### 微基准测试 + +写一个计时工具类从而比较不同代码块的执行速度是具有吸引力的。看上去这会产生一些有用的数据。比如,这里有一个简单的 **Timer** 类,可以用以下两种方式使用它: + +1. 创建一个 **Timer** 对象,执行一些操作然后调用 **Timer** 的 **duration()** 方法产生以毫秒为单位的运行时间。 +2. 向静态的 **duration()** 方法中传入 **Runnable**。任何符合 **Runnable** 接口的类都有一个函数式方法 **run()**,该方法没有入参,且没有返回。 + +```java +// onjava/Timer.java +package onjava; +import static java.util.concurrent.TimeUnit.*; + +public class Timer { + private long start = System.nanoTime(); + + public long duration() { + return NANOSECONDS.toMillis(System.nanoTime() - start); + } + + public static long duration(Runnable test) { + Timer timer = new Timer(); + test.run(); + return timer.duration(); + } +} +``` + +这是一个很直接的计时方式。难道我们不能只运行一些代码然后看它的运行时长吗? + +有许多因素会影响你的结果,即使是生成提示符也会造成计时的混乱。这里举一个看上去天真的例子,它使用了 标准的 Java **Arrays** 库(后面会详细介绍): + +```java +// validating/BadMicroBenchmark.java +// {ExcludeFromTravisCI} +import java.util.*; +import onjava.Timer; + +public class BadMicroBenchmark { + static final int SIZE = 250_000_000; + + public static void main(String[] args) { + try { // For machines with insufficient memory + long[] la = new long[SIZE]; + System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> n))); + System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> n))); + } catch (OutOfMemoryError e) { + System.out.println("Insufficient memory"); + System.exit(0); + } + } + +} +/* Output +setAll: 272 +parallelSetAll: 301 +``` + +**main()** 方法的主体包含在 **try** 语句块中,因为一台机器用光内存后会导致构建停止。 + +对于一个长度为 250,000,000 的 **long** 型(仅仅差一点就会让大部分机器内存溢出)数组,我们比较了 **Arrays.setAll()** 和 **Arrays.parallelSetAll()** 的性能。这个并行的版本会尝试使用多个处理器加快完成任务(尽管我在这一节谈到了一些并行的概念,但是在 [并发编程](./24-Concurrent-Programming.md) 章节我们才会详细讨论这些 )。然而非并行的版本似乎运行得更快,尽管在不同的机器上结果可能不同。 + +**BadMicroBenchmark.java** 中的每一步操作都是独立的,但是如果你的操作依赖于同一资源,那么并行版本运行的速度会骤降,因为不同的进程会竞争相同的那个资源。 + +```java +// validating/BadMicroBenchmark2.java +// Relying on a common resource + +import java.util.*; +import onjava.Timer; + +public class BadMicroBenchmark2 { + static final int SIZE = 5_000_000; + + public static void main(String[] args) { + long[] la = new long[SIZE]; + Random r = new Random(); + System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> r.nextLong()))); + System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> r.nextLong()))); + SplittableRandom sr = new SplittableRandom(); + System.out.println("parallelSetAll: " + Timer.duration(() -> Arrays.parallelSetAll(la, n -> sr.nextLong()))); + System.out.println("setAll: " + Timer.duration(() -> Arrays.setAll(la, n -> sr.nextLong()))); + } +} +/* Output +parallelSetAll: 1147 +setAll: 174 +parallelSetAll: 86 +setAll: 39 +``` + + + ## 分析和优化 From 9f14e3b3e601c38616a1890223c9d367cbbdd33c Mon Sep 17 00:00:00 2001 From: xiangflight Date: Thu, 31 Oct 2019 20:20:37 +0800 Subject: [PATCH 057/371] =?UTF-8?q?feat(chapter=2016):=20JMH=20=E7=9A=84?= =?UTF-8?q?=E5=BC=95=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 168 ++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 283b2160..881cf3cd 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -1471,7 +1471,7 @@ SimpleDebugging.main(SimpleDebugging.java:20) > 我们应该忘掉微小的效率提升,说的就是这些 97% 的时间做的事:过早的优化是万恶之源。 > -> ​ —— Donald Knuth +> ​ —— Donald Knuth 如果你发现自己正在过早优化的滑坡上,你可能浪费了几个月的时间(如果你雄心勃勃的话)。通常,一个简单直接的编码方法就足够好了。如果你进行了不必要的优化,就会使你的代码变得无谓的复杂和难以理解。 @@ -1569,11 +1569,175 @@ parallelSetAll: 86 setAll: 39 ``` +**SplittableRandom** 是为并行算法设计的,它当然看起来比普通的 **Random** 在 **parallelSetAll()** 中运行得更快。 但是看上去还是比非并发的 **setAll()** 运行时间更长,有点难以置信(也许是真的,但我们不能通过一个坏的微基准测试得到这个结论)。 + +这只考虑了微基准测试的问题。Java 虚拟机 Hotspot 也非常影响性能。如果你在测试前没有通过运行代码给 JVM 预热,那么你就会得到“冷”的结果,不能反映出代码在 JVM 预热之后的运行速度(假如你运行的应用没有在预热的 JVM 上运行,你就可能得不到所预期的性能,甚至可能减缓速度)。 + +优化器有时可以检测出你创建了没有使用的东西,或者是部分代码的运行结果对程序没有影响。如果它优化掉你的测试,那么你可能得到不好的结果。 + +一个良好的微基准测试系统能自动地弥补像这样的问题(和很多其他的问题)从而产生合理的结果,但是创建这么一套系统是非常棘手,需要深入的知识。 + +### JMH 的引入 + +截止目前为止,唯一能产生像样结果的 Java 微基准测试系统就是 Java Microbenchmarking Harness,简称 JMH。本书的 **build.gradle** 自动引入了 JMH 的设置,所以你可以轻松地使用它。 + +你可以在命令行编写 JMH 代码并运行它,但是推荐的方式是让 JMH 系统为你运行测试;**build.gradle** 文件已经配置成只需要一条命令就能运行 JMH 测试。 + +JMH 尝试使基准测试变得尽可能简单。例如,我们将使用 JMH 重新编写 **BadMicroBenchmark.java**。这里只有 **@State** 和 **@Benchmark** 这两个注解是必要的。其余的注解要么是为了产生更多易懂的输出,要么是加快基准测试的运行速度(JMH 基准测试通常需要运行很长时间): + +```java +// validating/jmh/JMH1.java +package validating.jmh; +import java.util.*; +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +// Increase these three for more accuracy: +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@Fork(1) +public class JMH1 { + private long[] la; + + @Setup + public void setup() { + la = new long[250_000_000]; + } + + @Benchmark + public void setAll() { + Arrays.setAll(la, n -> n); + } + + public void parallelSetAll() { + Arrays.parallelSetAll(la, n -> n); + } +} +``` + +“forks” 的默认值是 10,意味着每个测试都运行 10 次。为了减少运行时间,这里使用了 **@Fork** 注解来减少这个次数到 1。我还使用了 **@Warmup** 和 **@Measurement** 注解将它们默认的运行次数从 20 减少到 5 次。尽管这降低了整体的准确率,但是结果几乎与使用默认值相同。可以尝试将 **@Warmup**、**@Measurement** 和 **@Fork** 都注释掉然后看使用它们的默认值,结果会有多大显著的差异;一般来说,你应该只能看到长期运行的测试使错误因素减少,而结果没有多大变化。 + +需要使用显式的 gradle 命令才能运行基准测试(在示例代码的根目录处运行)。这能防止耗时的基准测试运行其他的 **gradlew** 命令: + +**gradlew validating:jmh** + +这会花费几分钟的时间,取决于你的机器(如果没有注解上的调整,可能需要几个小时)。控制台会显示 **results.txt** 文件的路径,这个文件统计了运行结果。注意,**results.txt** 包含这一章所有 **jmh** 测试的结果:**JMH1.java**,**JMH2.java** 和 **JMH3.java**。 + +因为输出是绝对时间,所以在不同的机器和操作系统上结果各不相同。重要的因素不是绝对时间,我们真正观察的是一个算法和另一个算法的比较,尤其是哪一个运行得更快,快多少。如果你在自己的机器上运行测试,你将看到不同的结果却有着相同的模式。 + +我在大量的机器上运行了这些测试,尽管不同的机器上得到的绝对值结果不同,但是相对值保持着合理的稳定性。我只列出了 **results.txt** 中适当的片段并加以编辑使输出更加易懂,而且内容大小适合页面。所有测试中的 **Mode** 都以 **avgt** 展示,代表 “平均时长”。**Cnt**(测试的数目)的值是 200,尽管这里的一个例子中配置的 **Cnt** 值是 5。**Units** 是 **us/op**,是 “Microseconds per operation” 的缩写,因此,这个值越小代表性能越高。 + +我同样也展示了使用 warmups、measurements 和 forks 默认值的输出。我删除了示例中相应的注解,就是为了获取更加准确的测试结果(这将花费数小时)。结果中数字的模式应该仍然看起来相同,不论你如何运行测试。 + +下面是 **JMH1.java** 的运行结果: + +**Benchmark Score** + +**JMH1.setAll 196280.2** + +**JMH1.parallelSetAll 195412.9** + +即使像 JMH 这么高级的基准测试工具,基准测试的过程也不容易,练习时需要倍加小心。这里测试产生了反直觉的结果:并行的版本 **parallelSetAll()** 花费了与非并行版本的 **setAll()** 相同的时间,两者似乎都运行了相当长的时间。 + +当创建这个示例时,我假设如果我们要测试数组初始化的话,那么使用非常大的数组是有意义的。所以我选择了尽可能大的数组;如果你实验的话会发现一旦数组的大小超过 2亿5000万,你就开始会得到内存溢出的异常。然而,在这么大的数组上执行大量的操作从而震荡内存系统,产生无法预料的结果是有可能的。不管这个假设是否正确,看上去我们正在测试的并非是我们想测试的内容。 + +考虑其他的因素: + +C:客户端执行操作的线程数量 + +P:并行算法使用的并行数量 + +N:数组的大小:**10^(2*k)**,通常来说,**k=1..7** 足够来练习不同的缓存占用。 + +Q:setter 的操作成本 + +这个 C/P/N/Q 模型在早期 JDK 8 的 Lambda 开发期间付出水面,大多数并行的 Stream 操作(**parallelSetAll()** 也基本相似)都满足这些结论:**N*Q**(主要工作量)对于并发性能尤为重要。并行算法在工作量较少时可能实际运行得更慢。 + +在一些情况下操作竞争如此激烈使得并行毫无帮助,而不管 **N*Q** 有多大。当 **C** 很大时,**P** 就变得不太相关(内部并行在大量的外部并行面前显得多余)。此外,在一些情况下,并行分解会让相同的 **C** 个客户端运行得比它们顺序运行代码更慢。 + +基于这些信息,我们重新运行测试,并在这些测试中使用不同大小的数组(改变 **N**): + +```java +// validating/jmh/JMH2.java +package validating.jmh; +import java.util.*; +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; +@State(Scope.Thread) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@Fork(1) +public class JMH2 { + + private long[] la; + + @Param({ + "1", + "10", + "100", + "1000", + "10000", + "100000", + "1000000", + "10000000", + "100000000", + "250000000" + }) + int size; + + @Setup + public void setup() { + la = new long[size]; + } + + @Benchmark + public void setAll() { + Arrays.setAll(la, n -> n); + } + + @Benchmark + public void parallelSetAll() { + Arrays.parallelSetAll(la, n -> n); + } +} +``` + +**@Param** 会自动地将其自身的值注入到变量中。其自身的值必须是字符串类型,并可以转化为适当的类型,在这个例子中是 **int** 类型。 + +下面是已经编辑过的结果,包含精确计算出的加速数值: + +| JMH2 Benchmark | Size | Score % | Speedup | +| ------------------ | --------- | ---------- | ------- | +| **setAll** | 1 | 0.001 | | +| **parallelSetAll** | 1 | 0.036 | 0.028 | +| **setAll** | 10 | 0.005 | | +| **parallelSetAll** | 10 | 3.965 | 0.001 | +| **setAll** | 100 | 0.031 | | +| **parallelSetAll** | 100 | 3.145 | 0.010 | +| **setAll** | 1000 | 0.302 | | +| **parallelSetAll** | 1000 | 3.285 | 0.092 | +| **setAll** | 10000 | 3.152 | | +| **parallelSetAll** | 10000 | 9.669 | 0.326 | +| **setAll** | 100000 | 34.971 | | +| **parallelSetAll** | 100000 | 20.153 | 1.735 | +| **setAll** | 1000000 | 420.581 | | +| **parallelSetAll** | 1000000 | 165.388 | 2.543 | +| **setAll** | 10000000 | 8160.054 | | +| **parallelSetAll** | 10000000 | 7610.190 | 1.072 | +| **setAll** | 100000000 | 79128.752 | | +| **parallelSetAll** | 100000000 | 76734.671 | 1.031 | +| **setAll** | 250000000 | 199552.121 | | +| **parallelSetAll** | 250000000 | 191791.927 | 1.040 | -## 分析和优化 +## 剖析和优化 From 4d5e9f1b97d656e9396d6d65ae9e9c18b9041346 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Fri, 1 Nov 2019 19:24:22 +0800 Subject: [PATCH 058/371] =?UTF-8?q?feat(chapter=2016):=20=E6=88=AA?= =?UTF-8?q?=E6=AD=A2=E5=89=96=E6=9E=90=E5=92=8C=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 90 ++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 881cf3cd..d0d347da 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -1666,6 +1666,7 @@ package validating.jmh; import java.util.*; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; + @State(Scope.Thread) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -1733,12 +1734,101 @@ public class JMH2 { | **parallelSetAll** | 100000000 | 76734.671 | 1.031 | | **setAll** | 250000000 | 199552.121 | | | **parallelSetAll** | 250000000 | 191791.927 | 1.040 | +可以看到当数组大小达到 10 万左右时,**parallelSetAll()** 开始反超,而后趋于与非并行的运行速度相同。即使它运行速度上胜了,看起来也不足以证明由于并行的存在而使速度变快。 + +**setAll()/parallelSetAll()** 中工作的计算量起很大影响吗?在前面的例子中,我们所做的只有对数组的赋值操作,这可能是最简单的任务。所以即使 **N** 值变大,**N*Q** 也仍然没有达到巨大,所以看起来像是我们没有为并行提供足够的机会(JMH 提供了一种模拟变量 Q 的途径;如果想了解更多的话,可搜索 **Blackhole.consumeCPU**)。 + +我们通过使方法 **f()** 中的任务变得更加复杂,从而产生更多的并行机会: + +```java +// validating/jmh/JMH3.java +package validating.jmh; +import java.util.*; +import org.openjdk.jmh.annotations.*; +import java.util.concurrent.TimeUnit; + +@State(Scope.Thread) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@Warmup(iterations = 5) +@Measurement(iterations = 5) +@Fork(1) +public class JMH3 { + private long[] la; + + @Param({ + "1", + "10", + "100", + "1000", + "10000", + "100000", + "1000000", + "10000000", + "100000000", + "250000000" + }) + int size; + @Setup + public void setup() { + la = new long[size]; + } + + public static long f(long x) { + long quadratic = 42 * x * x + 19 * x + 47; + return Long.divideUnsigned(quadratic, x + 1); + } + + @Benchmark + public void setAll() { + Arrays.setAll(la, n -> f(n)); + } + + @Benchmark + public void parallelSetAll() { + Arrays.parallelSetAll(la, n -> f(n)); + } +} +``` + +**f()** 方法提供了更加复杂且耗时的操作。现在除了简单的给数组赋值外,setAll() 和 parallelSetAll() 都有更多的工作去做,这肯定会影响结果。 + +| JMH2 Benchmark | Size | Score % | Speedup | +| ------------------ | --------- | ----------- | ------- | +| **setAll** | 1 | 0.012 | | +| **parallelSetAll** | 1 | 0.047 | 0.255 | +| **setAll** | 10 | 0.107 | | +| **parallelSetAll** | 10 | 3.894 | 0.027 | +| **setAll** | 100 | 0.990 | | +| **parallelSetAll** | 100 | 3.708 | 0.267 | +| **setAll** | 1000 | 133.814 | | +| **parallelSetAll** | 1000 | 11.747 | 11.391 | +| **setAll** | 10000 | 97.954 | | +| **parallelSetAll** | 10000 | 37.259 | 2.629 | +| **setAll** | 100000 | 988.475 | | +| **parallelSetAll** | 100000 | 276.264 | 3.578 | +| **setAll** | 1000000 | 9203.103 | | +| **parallelSetAll** | 1000000 | 2826.974 | 3.255 | +| **setAll** | 10000000 | 92144.951 | | +| **parallelSetAll** | 10000000 | 28126.202 | 3.276 | +| **setAll** | 100000000 | 921701.863 | | +| **parallelSetAll** | 100000000 | 266750.543 | 3.455 | +| **setAll** | 250000000 | 2299127.273 | | +| **parallelSetAll** | 250000000 | 538173.425 | 4.272 | + +可以看到当数组的大小达到 1000 左右时,**parallelSetAll()** 的运行速度反超了 **setAll()**。看来 **parallelSetAll()** 严重依赖数组中计算的复杂度。这正是基准测试的价值所在,因为我们已经得到了关于 **setAll()** 和 **parallelSetAll()** 间微妙的信息,知道在何时使用它们。 + +这显然不是从阅读 Javadocs 就能得到的。 + +大多数时候,JMH 的简单应用会产生好的结果(正如你将在本书后面例子中所见),但是我们从这里知道,你不能一直假定 JMH 会产生好的结果。 JMH 网站上的范例可以帮助你开始。 ## 剖析和优化 + + ## 风格检测 From ae19492743bf4bc443186a388af4e4f5af7ac3e6 Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Sat, 2 Nov 2019 17:18:52 +0800 Subject: [PATCH 059/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E7=AB=A0=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20=E7=BF=BB=E8=AF=91=E6=9B=B4=E6=96=B0-?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 514 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 514 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 984f4f34..5c7bb8eb 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -570,6 +570,520 @@ extends Fibonacci implements Iterable { ## 泛型方法 +到目前为止,我们已经研究了参数化整个类。其实还可以参数化类中的方法。类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型的并没有什么关系。 + +泛型方法独立于类而改变方法。作为准则,请“尽可能”使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。 + +如果方法是 **static** 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。 + +要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示: + +```java +// generics/GenericMethods.java + +public class GenericMethods { + public void f(T x) { + System.out.println(x.getClass().getName()); + } + + public static void main(String[] args) { + GenericMethods gm = new GenericMethods(); + gm.f(""); + gm.f(1); + gm.f(1.0); + gm.f(1.0F); + gm.f('c'); + gm.f(gm); + } +} +/* Output: +java.lang.String +java.lang.Integer +java.lang.Double +java.lang.Float +java.lang.Character +GenericMethods +*/ +``` + +尽管可以同时对类及其方法进行参数化,但这里未将 **GenericMethods** 类参数化。只有方法 `f()` 具有类型参数,该参数由方法返回类型之前的参数列表指示。 + +对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对`f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。 + +如果使用基本类型调用 `f()` ,自动装箱就开始起作用,自动将基本类型包装在它们对应的包装类型中。 + + +### 变量和泛型方法 + +泛型方法和变长参数列表可以很好地共存: + +```java +// generics/GenericVarargs.java + +import java.util.ArrayList; +import java.util.List; + +public class GenericVarargs { + @SafeVarargs + public static List makeList(T... args) { + List result = new ArrayList<>(); + for (T item : args) + result.add(item); + return result; + } + + public static void main(String[] args) { + List ls = makeList("A"); + System.out.println(ls); + ls = makeList("A", "B", "C"); + System.out.println(ls); + ls = makeList( + "ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); + System.out.println(ls); + } +} +/* Output: +[A] +[A, B, C] +[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, +S, T, U, V, W, X, Y, Z] +*/ +``` + +此处显示的 `makeList()` 方法产生的功能与标准库的 `java.util.Arrays.asList()` 方法相同。 + +`@SafeVarargs` 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。 + + +### 一个泛型的Supplier + +这是一个为任意具有无参构造方法的类生成 **Supplier** 的类。为了减少键入,它还包括一个用于生成 **BasicSupplier** 的泛型方法: + +```java +// onjava/BasicSupplier.java +// Supplier from a class with a no-arg constructor +package onjava; + +import java.util.function.Supplier; + +public class BasicSupplier implements Supplier { + private Class type; + + public BasicSupplier(Class type) { + this.type = type; + } + + @Override + public T get() { + try { + // Assumes type is a public class: + return type.newInstance(); + } catch (InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + // Produce a default Supplier from a type token: + public static Supplier create(Class type) { + return new BasicSupplier<>(type); + } +} +``` + +此类提供了产生以下对象的基本实现: + +1. 是 **public** 的。 因为 **BasicSupplier** 在单独的包中,所以相关的类必须具有 **public** 权限,而不仅仅是包级访问权限。 + +2. 具有无参构造方法。要创建一个这样的 **BasicSupplier** 对象,请调用 `create()` 方法,并将要生成类型的类型令牌传递给它。通用的 `create()` 方法提供了 `BasicSupplier.create(MyType.class)` 这种较简洁的语法来代替较笨拙的 `new BasicSupplier (MyType.class)`。 + +例如,这是一个具有无参构造方法的简单类: + +```java +// generics/CountedObject.java + +public class CountedObject { + private static long counter = 0; + private final long id = counter++; + + public long id() { + return id; + } + + @Override + public String toString() { + return "CountedObject " + id; + } +} +``` + +**CountedObject** 类可以跟踪自身创建了多少个实例,并通过 `toString()` 报告这些实例的数量。 **BasicSupplier** 可以轻松地为 **CountedObject** 创建 **Supplier**: + +```java + // generics/BasicSupplierDemo.java + +import onjava.BasicSupplier; + +import java.util.stream.Stream; + +public class BasicSupplierDemo { + public static void main(String[] args) { + Stream.generate( + BasicSupplier.create(CountedObject.class)) + .limit(5) + .forEach(System.out::println); + } +} +/* Output: +CountedObject 0 +CountedObject 1 +CountedObject 2 +CountedObject 3 +CountedObject 4 +*/ +``` + +泛型方法减少了产生 **Supplier** 对象所需的代码量。 Java 泛型强制传递 **Class** 对象,以便在 `create()` 方法中将其用于类型推断。 + + +### 简化元组的使用 + +使用类型参数推断和静态导入,我们将把早期的元组重写为更通用的库。在这里,我们使用重载的静态方法创建元组: + +```java +// onjava/Tuple.java +// Tuple library using type argument inference +package onjava; + +public class Tuple { + public static Tuple2 tuple(A a, B b) { + return new Tuple2<>(a, b); + } + + public static Tuple3 + tuple(A a, B b, C c) { + return new Tuple3<>(a, b, c); + } + + public static Tuple4 + tuple(A a, B b, C c, D d) { + return new Tuple4<>(a, b, c, d); + } + + public static + Tuple5 tuple(A a, B b, C c, D d, E e) { + return new Tuple5<>(a, b, c, d, e); + } +} +``` + +我们修改 **TupleTest.java** 来测试 **Tuple.java** : + +```java +// generics/TupleTest2.java + +import onjava.Tuple2; +import onjava.Tuple3; +import onjava.Tuple4; +import onjava.Tuple5; + +import static onjava.Tuple.tuple; + +public class TupleTest2 { + static Tuple2 f() { + return tuple("hi", 47); + } + + static Tuple2 f2() { + return tuple("hi", 47); + } + + static Tuple3 g() { + return tuple(new Amphibian(), "hi", 47); + } + + static Tuple4 h() { + return tuple( + new Vehicle(), new Amphibian(), "hi", 47); + } + + static Tuple5 k() { + return tuple(new Vehicle(), new Amphibian(), + "hi", 47, 11.1); + } + + public static void main(String[] args) { + Tuple2 ttsi = f(); + System.out.println(ttsi); + System.out.println(f2()); + System.out.println(g()); + System.out.println(h()); + System.out.println(k()); + } +} +/* Output: +(hi, 47) +(hi, 47) +(Amphibian@14ae5a5, hi, 47) +(Vehicle@135fbaa4, Amphibian@45ee12a7, hi, 47) +(Vehicle@4b67cf4d, Amphibian@7ea987ac, hi, 47, 11.1) +*/ +``` + +请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。 + + +### 一个Set工具 + +对于泛型方法的另一个示例,请考虑由 **Set** 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法: + +```java +// onjava/Sets.java + +package onjava; + +import java.util.HashSet; +import java.util.Set; + +public class Sets { + public static Set union(Set a, Set b) { + Set result = new HashSet<>(a); + result.addAll(b); + return result; + } + + public static + Set intersection(Set a, Set b) { + Set result = new HashSet<>(a); + result.retainAll(b); + return result; + } + + // Subtract subset from superset: + public static Set + difference(Set superset, Set subset) { + Set result = new HashSet<>(superset); + result.removeAll(subset); + return result; + } + + // Reflexive--everything not in the intersection: + public static Set complement(Set a, Set b) { + return difference(union(a, b), intersection(a, b)); + } +} +``` + +前三个方法通过将第一个参数的引用复制到新的 **HashSet** 对象中来复制第一个参数,因此不会直接修改参数集合。因此,返回值是一个新的 **Set** 对象。 + +这四种方法代表数学集合操作: `union()` 返回一个包含两个参数并集的 **Set** , `intersection()` 返回一个包含两个参数集合交集的 **Set** , `difference()` 从 **superset** 中减去 **subset** 的元素 ,而 `complement()` 返回所有不在交集中的元素的 **Set**。作为显示这些方法效果的简单示例的一部分,下面是一个包含不同水彩名称的 **enum** : + +```java +// generics/watercolors/Watercolors.java + +package watercolors; + +public enum Watercolors { + ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW, + ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA, + ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, + PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, + PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, + YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, + BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK +} +``` + +为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](https://github.com/LingCoder/OnJava8/blob/master/docs/book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: + +```java +// generics/WatercolorSets.java + +import watercolors.*; + +import java.util.EnumSet; +import java.util.Set; + +import static watercolors.Watercolors.*; +import static onjava.Sets.*; + +public class WatercolorSets { + public static void main(String[] args) { + Set set1 = + EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE); + Set set2 = + EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER); + System.out.println("set1: " + set1); + System.out.println("set2: " + set2); + System.out.println( + "union(set1, set2): " + union(set1, set2)); + Set subset = intersection(set1, set2); + System.out.println( + "intersection(set1, set2): " + subset); + System.out.println("difference(set1, subset): " + + difference(set1, subset)); + System.out.println("difference(set2, subset): " + + difference(set2, subset)); + System.out.println("complement(set1, set2): " + + complement(set1, set2)); + } +} +/* Output: +set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, +VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, +COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE] +set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, +COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, +SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, +BURNT_UMBER] +union(set1, set2): [BURNT_SIENNA, BRILLIANT_RED, +YELLOW_OCHRE, MAGENTA, SAP_GREEN, CERULEAN_BLUE_HUE, +ULTRAMARINE, VIRIDIAN_HUE, VIOLET, RAW_UMBER, +ROSE_MADDER, PERMANENT_GREEN, BURNT_UMBER, +PHTHALO_BLUE, CRIMSON, COBALT_BLUE_HUE] +intersection(set1, set2): [PERMANENT_GREEN, +CERULEAN_BLUE_HUE, ULTRAMARINE, VIRIDIAN_HUE, +PHTHALO_BLUE, COBALT_BLUE_HUE] +difference(set1, subset): [BRILLIANT_RED, MAGENTA, +VIOLET, CRIMSON, ROSE_MADDER] +difference(set2, subset): [BURNT_SIENNA, YELLOW_OCHRE, +BURNT_UMBER, SAP_GREEN, RAW_UMBER] +complement(set1, set2): [BURNT_SIENNA, BRILLIANT_RED, +YELLOW_OCHRE, MAGENTA, SAP_GREEN, VIOLET, RAW_UMBER, +ROSE_MADDER, BURNT_UMBER, CRIMSON] +*/ +``` + +接下来的例子使用 `Sets.difference()` 方法来展示 **java.util** 包中各种 **Collection** 和 **Map** 类之间的方法差异: + +```java +// onjava/CollectionMethodDifferences.java +// {java onjava.CollectionMethodDifferences} + +package onjava; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; + +public class CollectionMethodDifferences { + static Set methodSet(Class type) { + return Arrays.stream(type.getMethods()) + .map(Method::getName) + .collect(Collectors.toCollection(TreeSet::new)); + } + + static void interfaces(Class type) { + System.out.print("Interfaces in " + + type.getSimpleName() + ": "); + System.out.println( + Arrays.stream(type.getInterfaces()) + .map(Class::getSimpleName) + .collect(Collectors.toList())); + } + + static Set object = methodSet(Object.class); + + static { + object.add("clone"); + } + + static void + difference(Class superset, Class subset) { + System.out.print(superset.getSimpleName() + + " extends " + subset.getSimpleName() + + ", adds: "); + Set comp = Sets.difference( + methodSet(superset), methodSet(subset)); + comp.removeAll(object); // Ignore 'Object' methods + System.out.println(comp); + interfaces(superset); + } + + public static void main(String[] args) { + System.out.println("Collection: " + + methodSet(Collection.class)); + interfaces(Collection.class); + difference(Set.class, Collection.class); + difference(HashSet.class, Set.class); + difference(LinkedHashSet.class, HashSet.class); + difference(TreeSet.class, Set.class); + difference(List.class, Collection.class); + difference(ArrayList.class, List.class); + difference(LinkedList.class, List.class); + difference(Queue.class, Collection.class); + difference(PriorityQueue.class, Queue.class); + System.out.println("Map: " + methodSet(Map.class)); + difference(HashMap.class, Map.class); + difference(LinkedHashMap.class, HashMap.class); + difference(SortedMap.class, Map.class); + difference(TreeMap.class, Map.class); + } +} +/* Output: +Collection: [add, addAll, clear, contains, containsAll, +equals, forEach, hashCode, isEmpty, iterator, +parallelStream, remove, removeAll, removeIf, retainAll, +size, spliterator, stream, toArray] +Interfaces in Collection: [Iterable] +Set extends Collection, adds: [] +Interfaces in Set: [Collection] +HashSet extends Set, adds: [] +Interfaces in HashSet: [Set, Cloneable, Serializable] +LinkedHashSet extends HashSet, adds: [] +Interfaces in LinkedHashSet: [Set, Cloneable, +Serializable] +TreeSet extends Set, adds: [headSet, +descendingIterator, descendingSet, pollLast, subSet, +floor, tailSet, ceiling, last, lower, comparator, +pollFirst, first, higher] +Interfaces in TreeSet: [NavigableSet, Cloneable, +Serializable] +List extends Collection, adds: [replaceAll, get, +indexOf, subList, set, sort, lastIndexOf, listIterator] +Interfaces in List: [Collection] +ArrayList extends List, adds: [trimToSize, +ensureCapacity] +Interfaces in ArrayList: [List, RandomAccess, +Cloneable, Serializable] +LinkedList extends List, adds: [offerFirst, poll, +getLast, offer, getFirst, removeFirst, element, +removeLastOccurrence, peekFirst, peekLast, push, +pollFirst, removeFirstOccurrence, descendingIterator, +pollLast, removeLast, pop, addLast, peek, offerLast, +addFirst] +Interfaces in LinkedList: [List, Deque, Cloneable, +Serializable] +Queue extends Collection, adds: [poll, peek, offer, +element] +Interfaces in Queue: [Collection] +PriorityQueue extends Queue, adds: [comparator] +Interfaces in PriorityQueue: [Serializable] +Map: [clear, compute, computeIfAbsent, +computeIfPresent, containsKey, containsValue, entrySet, +equals, forEach, get, getOrDefault, hashCode, isEmpty, +keySet, merge, put, putAll, putIfAbsent, remove, +replace, replaceAll, size, values] +HashMap extends Map, adds: [] +Interfaces in HashMap: [Map, Cloneable, Serializable] +LinkedHashMap extends HashMap, adds: [] +Interfaces in LinkedHashMap: [Map] +SortedMap extends Map, adds: [lastKey, subMap, +comparator, firstKey, headMap, tailMap] +Interfaces in SortedMap: [Map] +TreeMap extends Map, adds: [descendingKeySet, +navigableKeySet, higherEntry, higherKey, floorKey, +subMap, ceilingKey, pollLastEntry, firstKey, lowerKey, +headMap, tailMap, lowerEntry, ceilingEntry, +descendingMap, pollFirstEntry, lastKey, firstEntry, +floorEntry, comparator, lastEntry] +Interfaces in TreeMap: [NavigableMap, Cloneable, +Serializable] +*/ +``` + +在[第十二章 集合](https://github.com/LingCoder/OnJava8/blob/master/docs/book/12-Collections.md)的[本章小结](https://github.com/LingCoder/OnJava8/blob/master/docs/book/12-Collections.md#本章小结)部分将会用到这里的输出结果。 ## 复杂模型构建 From 74979b1bcbf61328c8947ae6454aa9e2c34e5755 Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Sat, 2 Nov 2019 18:20:37 +0800 Subject: [PATCH 060/371] =?UTF-8?q?=E5=B0=86=E9=93=BE=E6=8E=A5=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=9B=B8=E5=AF=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 5c7bb8eb..c0e48e0f 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1083,7 +1083,7 @@ Serializable] */ ``` -在[第十二章 集合](https://github.com/LingCoder/OnJava8/blob/master/docs/book/12-Collections.md)的[本章小结](https://github.com/LingCoder/OnJava8/blob/master/docs/book/12-Collections.md#本章小结)部分将会用到这里的输出结果。 +在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 ## 复杂模型构建 From 5c93c77f8422b6bae42f18ff7b0a6b51cd0d01b8 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Sun, 3 Nov 2019 22:35:31 +0800 Subject: [PATCH 061/371] =?UTF-8?q?feat(chapter=2016):=20=E7=BB=93?= =?UTF-8?q?=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- docs/book/16-Validating-Your-Code.md | 103 ++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c7beb34d..0022025f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ - [x] [第十三章 函数式编程](docs/book/13-Functional-Programming.md) - [x] [第十四章 流式编程](docs/book/14-Streams.md) - [x] [第十五章 异常](docs/book/15-Exceptions.md) -- [ ] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) +- [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) - [ ] [第十九章 类型信息](docs/book/19-Type-Information.md) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index d0d347da..f143721b 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -1792,7 +1792,7 @@ public class JMH3 { } ``` -**f()** 方法提供了更加复杂且耗时的操作。现在除了简单的给数组赋值外,setAll() 和 parallelSetAll() 都有更多的工作去做,这肯定会影响结果。 +**f()** 方法提供了更加复杂且耗时的操作。现在除了简单的给数组赋值外,**setAll()** 和 **parallelSetAll()** 都有更多的工作去做,这肯定会影响结果。 | JMH2 Benchmark | Size | Score % | Speedup | | ------------------ | --------- | ----------- | ------- | @@ -1827,34 +1827,135 @@ public class JMH3 { ## 剖析和优化 +有时你必须检测程序运行时间花在哪儿,从而看是否可以优化那一块的性能。剖析器可以找到这些导致程序慢的地方,因而你可以找到最轻松,最明显的方式加快程序运行速度。 +剖析器收集的信息能显示程序哪一部分消耗内存,哪个方法最耗时。一些剖析器甚至能关闭垃圾回收,从而帮助限定内存分配的模式。 + +剖析器还可以帮助检测程序中的线程死锁。注意剖析和基准测试的区别。剖析关注的是已经运行在真实数据上的整个程序,而基准测试关注的是程序中隔离的片段,通常是去优化算法。 + +安装 Java 开发工具包(JDK)时会顺带安装一个虚拟的剖析器,叫做 **VisualVM**。它会被自动安装在与 **javac** 相同的目录下,你的执行路径应该已经包含该目录。启动 VisualVM 的控制台命令是: + +**> jvisualvm** + +运行该命令后会弹出一个窗口,其中包括一些指向帮助信息的链接。 + +### 优化准则 + +- 避免为了性能牺牲代码的可读性。 +- 不要独立地看待性能。衡量与带来的收益相比所需投入的工作量。 +- 程序的大小很重要。性能优化通常只对运行了长时间的大型项目有价值。性能通常不是小项目的关注点。 +- 运行起来程序比一心钻研它的性能具有更高的优先级。一旦你已经有了可工作的程序,如有必要的话,你可以使用剖析器提高它的效率。只有当性能是关键因素时,才需要在设计/开发阶段考虑性能。 +- 不要猜测瓶颈发生在哪。运行剖析器,让剖析器告诉你。 +- 无论何时有可能的话,显式地设置实例为 null 表明你不再用它。这对垃圾收集器来说是个有用的暗示。 +- **static final** 修饰的变量会被 JVM 优化从而提高程序的运行速度。因而程序中的常量应该声明 **static final**。 ## 风格检测 +当你在一个团队中工作时(包括尤其是开源项目),让每个人遵循相同的代码风格是非常有帮助的。这样阅读项目的代码时,不会因为风格的不同产生思维上的中断。然而,如果你习惯了某种不同的代码风格,那么记住项目中所有的风格准则对你来说可能是困难的。幸运的是,存在可以指出你代码中不符合风格准则的工具。 + +一个流行的风格检测器是 **Checkstyle**。查看本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **gradle.build** 和 **checkstyle.xml** 文件中配置代码风格的方式。checkstyle.xml 是一个常用检测的集合,其中一些检测被注释掉了以允许使用本书中的代码风格。 + +运行所有风格检测的命令是: + +**gradlew checkstyleMain** + +一些文件仍然产生了风格检测警告,通常是因为这些例子展示了你在生产代码中不会使用的样例。 + +你还可以针对一个具体的章节运行代码检测。例如,下面命令会运行 [Annotations](./23-Annotations.md) 章节的风格检测: + +**gradlew annotations:checkstyleMain** + ## 静态错误分析 +尽管 Java 的静态类型检测可以发现基本的语法错误,其他的分析工具可以发现躲避 **javac** 检测的更加复杂的bug。一个这样的工具叫做 **Findbugs**。本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **build.gradle** 文件包含了 Findbugs 的配置,所以你可以输入如下命令: + +**gradlew findbugsMain** + +这会为每一章生成一个名为 **main.html** 的报告,报告中会说明代码中潜在的问题。Gradle 命令的输出会告诉你每个报告在何处。 + +当你查看报告时,你将会看到很多 false positive 的情况,即代码没问题却报告了问题。我在一些文件中展示了不要做一些事的代码确实是正确的。 + +当我最初看到本书的 Findbugs 报告时,我发现了一些不是技术错误的地方,但能使我改善代码。如果你正在寻找 bug,那么在调试之前运行 Findbugs 是值得的,因为这将可能节省你数小时的时间找到问题。 + ## 代码重审 +单元测试能找到明显重要的 bug 类型,风格检测和 Findbugs 能自动执行代码重审,从而发现额外的问题。最终你走到了必须人为参与进来的地步。代码重审是一个或一群人的一段代码被另一个或一群人阅读和评估的众多方式之一。这最初看起来会使人不安,而且需要情感信任,但它的目的肯定不是羞辱任何人。它的目标是找到程序中的错误,代码重审是最成功的能做到这点的途径之一。可惜的是,它们也经常被认为是“过于昂贵的”(有时这会成为程序员避免代码被重审时感到尴尬的借口)。 + +代码重审可以作为结对编程的一部分,作为代码签入过程的一部分(另一个程序员自动安排上审查新代码的任务)或使用群组预排的方式,即每个人阅读代码并讨论之。后一种方式对于分享知识和营造代码文化是极其有益的。 + ## 结对编程 +结对编程是指两个程序员一起编程的实践活动。通常来说,一个人“驱动”(敲击键盘,输入代码),另一人(观察者或指引者)重审和分析代码,同时也要思考策略。这产生了一种实时的代码重审。通常程序员会定期地互换角色。 + +结对编程有很多好处,但最显著的是分享知识和防止阻塞。最佳传递信息的方式之一就是一起解决问题,我已经在很多次研讨会使用了结对编程,都取得了很好的效果(同时,研讨会上的众人可以通过这种方式互相了解对方)。而且两个人一起工作时,可以更容易地推进开发的进展,而只有一个程序员的话,可能被轻易地卡住。结对编程的程序员通常可以从工作中感到更高的满足感。有时很难向管理人员们推行结对编程,因为他们可能觉得两个程序员解决同一个问题的效率比他们分开解决不同问题的效率低。尽管短期内是这样,但是结对编程能带来更高的代码质量;除了结对编程的其他益处,如果你眼光长远的话,这会产生更高的生产力。 + +维基百科上这篇 [结对编程的文章](https://en.wikipedia.org/wiki/Pair_programming) 可以作为你深入了解结对编程的开始。 + ## 重构 +技术负债是指迭代发展的软件中为了应急而生的丑陋解决方案从而导致设计难以理解,代码难以阅读的部分。特别是当你必须修改和增加新特性的时候,这会造成麻烦。 + +重构可以矫正技术负债。重构的关键是它能改善代码设计,结构和可读性(因而减少代码负债),但是它不能改变代码的行为。 + +很难向管理人员推行重构:“我们将投入很多工作不是增加新的特性,当我们完成时,外界无感知变化。但是相信我们,事情会变得更加美好”。不幸的是,管理人员意识到重构的价值时都为时已晚了:当他们提出增加新的特性时,你不得不告诉他们做不到,因为代码基底已经埋藏了太多的问题,试图增加新特性可能会使软件崩溃,即使你能想出怎么做。 + +### 重构基石 + +在开始重构代码之前,你需要有以下三个系统的支撑: + +1. 测试(通常,JUnit 测试作为最小的根基),因此你能确保重构不会改变代码的行为。 +2. 自动构建,因而你能轻松地构建代码,运行所有的测试。通过这种方式做些小修改并确保修改不会破坏任何事物是毫不费力的。本书使用的是 Gradle 构建系统,你可以在 [代码示例](https://github.com/BruceEckel/OnJava8-Examples) 的 **build.gradle** 文件中查看示例。 +3. 版本控制,以便你能回退到可工作的代码版本,能够一直记录重构的每一步。 + +本书的代码托管在 [Github](https://github.com/BruceEckel/OnJava8-Examples) 上,使用的是 **git** 版本控制系统。 + +没有这三个系统的支持,重构几乎是不可能的。确实,没有这些系统,起初维护和增加代码是一个巨大的挑战。令人意外的是,有很多成功的公司竟然在没有这三个系统的情况下在相当长的时间里勉强过得去。然而,对于这样的公司来说,在他们遇到严重的问题之前,这只是个时间问题。 + +维基百科上的 [重构文章](https://en.wikipedia.org/wiki/Code_refactoring) 提供了更多的细节。 + ## 持续集成 +在软件开发的早期,人们只能一次处理一步,所以他们坚信他们总是在经历快乐之旅,每个开发阶段无缝进入下一个。这种错觉经常被称为软件开发中的“瀑布流模型”。很多人告诉我瀑布流是他们的选择方法,好像这是一个选择工具,而不仅是一厢情愿。 + +在这片童话的土地上,每一步都按照指定的预计时间准时完美结束,然后下一步开始。当最后一步结束时,所有的部件都可以无缝地滑在一起,瞧,一个装载产品诞生了! + +当然,现实中没有事能按计划或预计时间运作。相信它应该,然后当它不能时更加相信,只会使整件事变得更糟。否认证据不会产生好的结果。 + +除此之外,产品本身经常也不是对客户有价值的事物。有时一大堆的特性完全是浪费时间,因为创造出这些特性需求的人不是客户而是其他人。 + +因为受流水工作线的思路影响,所以每个开发阶段都有自己的团队。上游团队的延期传递到下游团队,当到了需要进行测试和集成的时候,这些团队被指望赶上预期时间,当他们必然做不到时,就认为他们是“差劲的团队成员”。不可能的时间安排和负相关的结合产生了自实现的预期:只有最绝望的开发者才会乐意做这些工作。 + +另外,商学院培养出的管理人员仍然被训练成只在已有的流程上做一些改动——这些流程都是基于工业时代制造业的想法上。注重培养创造力而不是墨守成规的商学院仍然很稀有。终于一些编程领域的人们再也忍受不了这种情况并开始进行实验。最初一些实验叫做“极限编程”,因为它们与工业时代的思想完全不同。随着实验展示的结果,这些思想开始看起来像是常识。这些实验逐渐形成了如今显而易见的观点——尽管非常小——即把生产可运作的产品交到客户手中,询问他们 (A) 是否想要它 (B) 是否喜欢它工作的方式 (C) 还希望有什么其他有用的功能特性。然后这些信息反馈给开发,从而继续产出一个新版本。版本不断迭代,项目最终演变成为客户带来真正价值的事物。 + +这完全颠倒了瀑布流开发的方式。你停止假设你要处理产品测试和把部署"作为最后一步"这类的事情。相反,每件事从开始到结束必须都在进行——即使一开始产品几乎没有任何特性。这么做对于在开发周期的早期发现更多问题有巨大的益处。此外,不是做大量宏大超前的计划和花费时间金钱在许多无用的特性上,而是一直都能从顾客那得到反馈。当客户不再需要其他特性时,你就完成了。这节省了大量的时间和金钱,并提高了顾客的满意度。 + +有许多不同的想法导向这种方式,但是目前首要的术语叫持续集成(CI)。CI 与导向 CI 的想法之间的不同之处在于 CI 是一种独特的机械式的过程,过程中涵盖了这些想法;它是一种定义好的做事方式。事实上,它定义得如此明确以至于整个过程是自动化的。 + +当前 CI 技术的高峰是持续集成服务器。这是一台独立的机器或虚拟机,通常是由第三方公司托管的完全独立的服务。这些公司通常免费提供基本服务,如果你需要额外的特性如更多的处理器或内存或者专门的工具或系统,你需要付费。CI 服务器起初是完全空白状态,即只是可用的操作系统的最小配置。这很重要因为你可能之前在你的开发机器上安装过一些程序,却没有在你的构建和部署系统中包含它。正如重构一样,持续集成需要分布式版本管理,自动构建和自动测试系统作为基础。通常来说,CI 服务器会绑定到你的版本控制仓库上。当 CI 服务器发现仓库中有改变时,就会拉取最新版本的代码,并按照 CI 脚本中的过程处理。这包括安装所有必要的工具和类库(记住,CI 服务器起初只有一个干净、基本的操作系统),所以如果过程中出现任何问题,你都可以发现它们。接着它会执行脚本中定义的构建和测试操作;通常脚本中使用的命令与人们在安装和测试中使用的命令完全相同。如果执行成功或失败,CI 服务器会有许多种方式汇报给你,包括在你的代码仓库上显示一个简单的标记。 + +使用持续集成,每次你合进仓库时,这些改变都会被从头到尾验证。通过这种方式,一旦出现问题你能立即发现。甚至当你准备交付一个产品的新版本时,都不会有延迟或其他必要的额外步骤(在任何时刻都可以交付叫做持续交付)。 + +本书的示例代码都是在 Travis-CI(基于 Linux 的系统) 和 AppVeyor(Windows) 上自动测试的。你可以在 [Gihub仓库](https://github.com/BruceEckel/OnJava8-Examples) 上的 Readme 看到通过/失败的标记。 + ## 本章小结 +"它在我的机器上正常工作了。" "我们不会运载你的机器!" + +代码校验不是单一的过程或技术。每种方法只能发现特定类型的 bug,作为程序员的你在开发过程中会明白每个额外的技术都能增加代码的可靠性和鲁棒性。校验不仅能在开发过程中,还能在为应用添加新功能的整个项目期间帮你发现更多的错误。现代化开发意味着比仅仅编写代码更多的内容,每种你在开发过程中融入的测试技术—— 包括而且尤其是你创建的能适应特定应用的自定义工具——都会带来更好、更快和更加愉悦的开发过程,同时也能为客户提供更高的价值和满意度体验。 + + From a6173f4b4ad6d02bba8a604cc24954ead0802c00 Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Mon, 4 Nov 2019 22:54:02 +0800 Subject: [PATCH 062/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E7=AB=A0=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20=E7=BF=BB=E8=AF=91=E6=9B=B4=E6=96=B0=20?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=A4=8D=E6=9D=82=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 139 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index c0e48e0f..da1a4d3d 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1086,8 +1086,145 @@ Serializable] 在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 -## 复杂模型构建 +## 构建复杂模型 +泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以地轻松创建一个元组列表: + +```java +// generics/TupleList.java +// Combining generic types to make complex generic types + +import onjava.Tuple4; + +import java.util.ArrayList; + +public class TupleList + extends ArrayList> { + public static void main(String[] args) { + TupleList tl = + new TupleList<>(); + tl.add(TupleTest2.h()); + tl.add(TupleTest2.h()); + tl.forEach(System.out::println); + } +} +/* Output: +(Vehicle@7cca494b, Amphibian@7ba4f24f, hi, 47) +(Vehicle@3b9a45b3, Amphibian@7699a589, hi, 47) +*/ +``` + +这将产生一个功能强大的数据结构,而无需太多代码。 + +下面是第二个例子。每个类都是组成块,总体包含很多个块。在这里,该模型是一个具有过道,货架和产品的零售商店: + +```java +// generics/Store.java +// Building a complex model using generic collections + +import onjava.Suppliers; + +import java.util.ArrayList; +import java.util.Random; +import java.util.function.Supplier; + +class Product { + private final int id; + private String description; + private double price; + + Product(int idNumber, String descr, double price) { + id = idNumber; + description = descr; + this.price = price; + System.out.println(toString()); + } + + @Override + public String toString() { + return id + ": " + description + + ", price: $" + price; + } + + public void priceChange(double change) { + price += change; + } + + public static Supplier generator = + new Supplier() { + private Random rand = new Random(47); + + @Override + public Product get() { + return new Product(rand.nextInt(1000), "Test", + Math.round( + rand.nextDouble() * 1000.0) + 0.99); + } + }; +} + +class Shelf extends ArrayList { + Shelf(int nProducts) { + Suppliers.fill(this, Product.generator, nProducts); + } +} + +class Aisle extends ArrayList { + Aisle(int nShelves, int nProducts) { + for (int i = 0; i < nShelves; i++) + add(new Shelf(nProducts)); + } +} + +class CheckoutStand { +} + +class Office { +} + +public class Store extends ArrayList { + private ArrayList checkouts = + new ArrayList<>(); + private Office office = new Office(); + + public Store( + int nAisles, int nShelves, int nProducts) { + for (int i = 0; i < nAisles; i++) + add(new Aisle(nShelves, nProducts)); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (Aisle a : this) + for (Shelf s : a) + for (Product p : s) { + result.append(p); + result.append("\n"); + } + return result.toString(); + } + + public static void main(String[] args) { + System.out.println(new Store(5, 4, 3)); + } +} +/* Output: (First 8 Lines) +258: Test, price: $400.99 +861: Test, price: $160.99 +868: Test, price: $417.99 +207: Test, price: $268.99 +551: Test, price: $114.99 +278: Test, price: $804.99 +520: Test, price: $554.99 +140: Test, price: $530.99 + ... +*/ +``` + +`Store.toString()` 显示了结果:尽管有复杂的层次结构,但多层的集合仍然是类型安全的和可管理的。令人印象深刻的是,组装这样的模型在理论上并不是禁止的。 + +**Shelf** 使用 `Suppliers.fill()` 这个实用程序,该实用程序接受 **Collection** (第一个参数),并使用 **Supplier** (第二个参数),以元素的数量为 **n** (第三个参数)来填充它。 **Suppliers** 类将会在本章末尾定义,其中的方法都是在执行某种填充操作,并在本章的其他示例中使用。 ## 泛型擦除 From ae4cc73054d6f4ece38622ab0c7fcccb88957a92 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Tue, 5 Nov 2019 09:17:02 +0800 Subject: [PATCH 063/371] =?UTF-8?q?revision[17]=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/17-Files.md | 260 +++++++++++++++++++++--------------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/docs/book/17-Files.md b/docs/book/17-Files.md index ae32b905..4106bcc0 100644 --- a/docs/book/17-Files.md +++ b/docs/book/17-Files.md @@ -1,13 +1,14 @@ [TOC] + # 第十七章 文件 ->在丑陋的Java I/O编程方式诞生多年以后,Java终于简化了文件读写的基本操作。这种"困难方式"的全部细节都在 +>在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。 -[Appendix: I/O Streams](.\Appendix-IO-Streams.md)。如果你读过这个部分,就会认同Java设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的,由于I/O糟糕的设计以至于 +这种"困难方式"的全部细节都在 [Appendix: I/O Streams](.\Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的,由于 I/O 糟糕的设计以至于 很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。 -好像Java设计者终于意识到了Java使用者多年来的痛苦,在Java7中对此引入了巨大的改进。这些新元素被放在**java.nio.file**包下面,过去人们通常把**nio**中的**n**理解为**new**即新的**io**,现在更应该当成是**non-blocking**非阻塞**io**(**io**就是*input/output输入/输出*)。**java.nio.file**库终于将Java文件操作带到与其他编程语言相同的水平。最重要的是Java8新增的streams与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件: +好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 **java.nio.file** 包下面,过去人们通常把 **nio** 中的 **n** 理解为 **new** 即新的 **io**,现在更应该当成是 **non-blocking** 非阻塞 **io**(**io**就是*input/output输入/输出*)。**java.nio.file** 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件: 1. 文件或者目录的路径; 2. 文件本身。 @@ -15,9 +16,7 @@ ## 文件和目录路径 -### `Paths` - -一个**Path**对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。**java.nio.file.Paths**类包含一个重载方法**static get()**,该方法方法接受一系列**Strings**字符串或一个*统一资源标识符*(URI)作为参数,并且进行转换返回一个**Path**对象: +一个 **Path** 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。**java.nio.file.Paths** 类包含一个重载方法 **static get()**,该方法接受一系列 **String** 字符串或一个*统一资源标识符*(URI)作为参数,并且进行转换返回一个 **Path** 对象: ```java // files/PathInfo.java @@ -122,17 +121,17 @@ true */ ``` -我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10上的程序输出。 +我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10 上的程序输出。 -当**toString()**方法生成完整形式的路径,你可以看到**getFileName()** 方法总是返回当前文件名。 -通过使用**Files**工具类(我们接下类将会更多的使用它),可以测试一个文件是否存在,测试是否是一个"真正"的文件还是一个目录等等。"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。"PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在"。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的**Path**,并且还会删除任何冗余元素。 +当 **toString()** 方法生成完整形式的路径,你可以看到 **getFileName()** 方法总是返回当前文件名。 +通过使用 **Files** 工具类(我们接下类将会更多地使用它),可以测试一个文件是否存在,测试是否是一个"普通"文件还是一个目录等等。"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。"PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在"。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的 **Path**,并且还会删除任何冗余元素。 -这里你会看到**URI**看起来只能用于描述文件,实际上**URI**可以用于描述更多的东西;通过[维基百科]()可以了解更多细节。现在我们成功地将**URI**转为一个**Path**对象。 +这里你会看到 **URI** 看起来只能用于描述文件,实际上 **URI** 可以用于描述更多的东西;通过 [维基百科](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) 可以了解更多细节。现在我们成功地将 **URI** 转为一个 **Path** 对象。 -最后,你会在**Path**中看到一些有点欺骗的东西,这就是调用**toFile()**方法会生成一个**File**对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为**File**),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为"路径",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于**java.nio.file**的存在我们可以安全的忽略它的存在。 +最后,你会在 **Path** 中看到一些有点欺骗的东西,这就是调用 **toFile()** 方法会生成一个 **File** 对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为 **File** ),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为"路径",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于 **java.nio.file** 的存在我们可以安全地忽略它的存在。 ### 选取路径部分片段 -**Path**对象可以非常容易的生成路径的某一部分: +**Path** 对象可以非常容易地生成路径的某一部分: ```java // files/PartsOfPaths.java @@ -178,11 +177,11 @@ Starts with C:\ true */ ``` -可以通过**getName()**来索引**Path**的各个部分,直到达到上限**getNameCount()**。**Path**也继承了**Iterable**接口,因此我们也可以通过增强的for循环进行遍历。请注意,即使路径以 **.java**结尾,使用**endsWith()** 方法也会返回**false**。这是因为使用**endsWith()** 比较的是整个路径部分,而不会包含文件路径的后缀。通过使用**startsWith()** 和**endsWith()**也可以完成路径的遍历。但是我们可以看到,遍历**Path**对象并不包含根路径,只有使用 -**startsWith()**检测根路径时才会返回**true**。 +可以通过 **getName()** 来索引 **Path** 的各个部分,直到达到上限 **getNameCount()**。**Path** 也实现了 **Iterable** 接口,因此我们也可以通过增强的 for-each 进行遍历。请注意,即使路径以 **.java** 结尾,使用 **endsWith()** 方法也会返回 **false**。这是因为使用 **endsWith()** 比较的是整个路径部分,而不会包含文件路径的后缀。通过使用 **startsWith()** 和 **endsWith()** 也可以完成路径的遍历。但是我们可以看到,遍历 **Path** 对象并不包含根路径,只有使用 **startsWith()** 检测根路径时才会返回 **true**。 ### 路径分析 -**Files**工具类包含一系列完整的方法用于获得**Path**相关的信息。 +**Files** 工具类包含一系列完整的方法用于获得 **Path** 相关的信息。 + ```java // files/PathAnalysis.java import java.nio.file.*; @@ -237,11 +236,13 @@ ContentType: null SymbolicLink: false */ ``` -在调用最后一个测试方法**getPosixFilePermissions()** 之前我们需要确认一下当前文件系统是否支持**Posix**接口,否则会抛出运行时异常。 +在调用最后一个测试方法 **getPosixFilePermissions()** 之前我们需要确认一下当前文件系统是否支持 **Posix** 接口,否则会抛出运行时异常。 ### **Paths**的增减修改 -我们必须能通过对**Path**对象增加或者删除一部分来构造一个新的**Path**对象。我们使用**relativize()**构造一个路径与给定路径的相对路径,使用**resolve()**解析为一个新的**Path**对象(不一定是“可发现”的名称)。对于下面代码中的示例,我使用**relativize()** 方法从所有的输出中移除根路径,部分原因是为了示范,部分原因是为了简化输出结果,这说明你可以使用该方法将绝对路径转为相对路径。 -这个版本的代码中包含**id**,以便于跟踪输出结果: +我们必须能通过对 **Path** 对象增加或者删除一部分来构造一个新的 **Path** 对象。我们使用 **relativize()** 移除 **Path** 的根路径,使用 **resolve()** 添加 **Path** 的尾路径(不一定是“可发现”的名称)。 + +对于下面代码中的示例,我使用 **relativize()** 方法从所有的输出中移除根路径,部分原因是为了示范,部分原因是为了简化输出结果,这说明你可以使用该方法将绝对路径转为相对路径。 +这个版本的代码中包含 **id**,以便于跟踪输出结果: ```java // files/AddAndSubtractPaths.java @@ -323,13 +324,13 @@ C:\Users\Bruce\Documents\GitHub\onjava\ ExtractedExamples\files\nonexistent */ ``` -我还为**toRealPath()** 添加了进一步的测试,这是为了扩展和规则化,防止路径不存在以免产生运行时异常。 - - +我还为 **toRealPath()** 添加了更多的测试,这是为了扩展和规则化,防止路径不存在时抛出运行时异常。 + ## 目录 -**Files**工具类类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,因此我们将实现并将其添加到**onjava**库中。 +**Files** 工具类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,因此我们将实现并将其添加到 **onjava** 库中。 + ```java // onjava/RmDir.java package onjava; @@ -356,16 +357,16 @@ public class RmDir { } } ``` -删除目录树的方法实现依赖于**Files.walkFileTree()**,"walking"意味着遍历整个子目录和文件。*Visitor*设计模式提供了一种标准机制来访问集合中的某个对象,然后你需要提供在每个对象上执行的操作。 -此操作的定义取决于实现的**FileVisitor**的四个抽象方法,包括: - 1. **preVisitDirectory()**:在访问目录中条目之前在目录上运行。 +删除目录树的方法实现依赖于 **Files.walkFileTree()**,"walking" 目录树意味着遍历每个子目录和文件。*Visitor* 设计模式提供了一种标准机制来访问集合中的每个对象,然后你需要提供在每个对象上执行的操作。 +此操作的定义取决于实现的 **FileVisitor** 的四个抽象方法,包括: + + 1. **preVisitDirectory()**:在访问目录中条目之前在目录上运行。 2. **visitFile()**:运行目录中的每一个文件。 3. **visitFileFailed()**:调用无法访问的文件。 4. **postVisitDirectory()**:在访问目录中条目之后在目录上运行,包括所有的子目录。 - -为了简化,**java.nio.file.SimpleFileVisitor** 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:**visitFile()** 和**postVisitDirectory()** 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。 - -作为探索目录操作的一部分,现在我们可以有条件地删除已存在的目录。在以下例子中,**makeVariant()** 接受基本目录测试,并通过旋转部件列表生成不同的子目录路径。这些旋转与路径分隔符粘**sep** 使用**String.join()**贴在一起,然后返回一个**Path**对象。 + +为了简化,**java.nio.file.SimpleFileVisitor** 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:**visitFile()** 和 **postVisitDirectory()** 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。 +作为探索目录操作的一部分,现在我们可以有条件地删除已存在的目录。在以下例子中,**makeVariant()** 接受基本目录测试,并通过旋转部件列表生成不同的子目录路径。这些旋转与路径分隔符 **sep** 使用 **String.join()** 贴在一起,然后返回一个 **Path** 对象。 ```java // files/Directories.java @@ -460,16 +461,18 @@ test\foo\bar\baz\bag\File.txt test\Hello.txt */ ``` -首先,**refreshTestDir()**用于检测**test**目录是否已经存在。果是这样,则使用我们新工具类**rmdir()** 删除其整个目录。检查是否**exists**是多余的,但我想说明一点,因为如果你对于已经存在的目录调用**createDirectory()** 将会抛出异常。**createFile()** 使用参数**Path** 创建一个空文件; **resolve()** 将文件名添加到测试路径的末尾。 +首先,**refreshTestDir()** 用于检测 **test** 目录是否已经存在。若存在,则使用我们新工具类 **rmdir()** 删除其整个目录。检查是否 **exists** 是多余的,但我想说明一点,因为如果你对于已经存在的目录调用 **createDirectory()** 将会抛出异常。**createFile()** 使用参数 **Path** 创建一个空文件; **resolve()** 将文件名添加到 **test Path** 的末尾。 + +我们尝试使用 **createDirectory()** 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路径。我已经将 **populateTestDir()** 作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量 **variant**,我们都能使用 **createDirectories()** 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用 **createTempFile()** 生成一个临时文件。 -我们尝试使用**createDirectory()** 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路劲。我已经将**populateTestDir()**作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量**variant**,我们都能使用**createDirectories()** 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用**createTempFile()** 生成一个临时文件。 +在调用 **populateTestDir()** 之后,我们在 **test** 目录下面下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与 **createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用".tmp"作为后缀。 -在调用**populateTestDir()** 之后,我们在**test**目录下面下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与**createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,将默认使用".tmp"作为后缀。 +为了展示结果,我们首次使用看起来很有希望的 **newDirectoryStream()**,但事实证明这个方法只是返回 **test** 目录内容的 Stream 流,并没有更多的内容。要获取目录树的全部内容的流,请使用 **Files.walk()**。 -为了展示结果,我们首先使用看起来很有可能的**newDirectoryStream()**,但事实证明这个方法只是返回**test** 目录内容的Stream流,并没有更多的内容。要获取目录树的全部内容的流,请使用**Files.walk()**。 + ## 文件系统 -为了完整起见,我们需要一种方法查找有关文件系统的其他信息。在这里,我们使用ileSystems工具类获取"默认"文件系统,但你同样也可以在**Path**对象上调用**getFileSystem()** 以获取创建该**Path** 的文件系统。你可以获得给定*URI*的文件系统,还可以构建新的文件系统(对于支持它的操作系统)。 +为了完整起见,我们需要一种方法查找文件系统相关的其他信息。在这里,我们使用静态的 **FileSystems** 工具类获取"默认"的文件系统,但你同样也可以在 **Path** 对象上调用 **getFileSystem()** 以获取创建该 **Path** 的文件系统。你可以获得给定 *URI* 的文件系统,还可以构建新的文件系统(对于支持它的操作系统)。 ```java // files/FileSystemDemo.java import java.nio.file.*; @@ -511,11 +514,12 @@ sun.nio.fs.WindowsFileSystemProvider@6d06d69c File Attribute Views: [owner, dos, acl, basic, user] */ ``` -一个**FileSystem**对象也能生成**WatchService** 和**WatchService** 对象,将会在接下来两章中详细讲解。 +一个**FileSystem** 对象也能生成 **WatchService** 和 **PathMatcher** 对象,将会在接下来两章中详细讲解。 + ## 路径监听 -通过**WatchService** 可以设置一个进程对目录中的更改做出响应。在这个例子中,**delTxtFiles()** 作为一个单独的任务执行,该任务将遍历整个目录并删除以 **.txt** 结尾的所有文件,**WatchService** 会对文件删除操作做出反应: +通过 **WatchService** 可以设置一个进程对目录中的更改做出响应。在这个例子中,**delTxtFiles()** 作为一个单独的任务执行,该任务将遍历整个目录并删除以 **.txt** 结尾的所有文件,**WatchService** 会对文件删除操作做出反应: ```java // files/PathWatcher.java @@ -577,15 +581,15 @@ evt.kind(): ENTRY_DELETE */ ``` -**delTxtFiles()** 中的**try** 代码块看起来有些多余,因为它们捕获的是同一种类型的异常,外部的**try** 语句似乎已经足够了。然而出于某种原因,Java要求两者都必须存在(这也可能是一个bug)。还要注意的是在**filter()** 中,我们必须显式地使用**f.toString()** 转为字符串,否则我们调用**endsWith()** 将会与整个**Path**对象进行比较,而不是路径名称字符串的一部分进行比较。 +**delTxtFiles()** 中的 **try** 代码块看起来有些多余,因为它们捕获的是同一种类型的异常,外部的 **try** 语句似乎已经足够了。然而出于某种原因,Java 要求两者都必须存在(这也可能是一个 bug)。还要注意的是在 **filter()** 中,我们必须显式地使用 **f.toString()** 转为字符串,否则我们调用 **endsWith()** 将会与整个 **Path** 对象进行比较,而不是路径名称字符串的一部分进行比较。 -一旦我们从**FileSystem** 中得到了**WatchService** 对象,我们将其注册到**tets** 路径以及我们感兴趣的项目的变量参数列表中,可以选择**ENTRY_CREATE**,**ENTRY_DELETE** 或**ENTRY_MODIFY**(其中创建和删除不属于修改)。 +一旦我们从 **FileSystem** 中得到了 **WatchService** 对象,我们将其注册到 **test** 路径以及我们感兴趣的项目的变量参数列表中,可以选择 **ENTRY_CREATE**,**ENTRY_DELETE** 或 **ENTRY_MODIFY**(其中创建和删除不属于修改)。 -因为接下来对**watcher.take()** 的调用会在发生某些事情之前停止所有操作,所以我们希望**deltxtfiles()** 能够并行运行以便生成我们感兴趣的事件。为了实现这个目的,我通过调用**Executors.newSingleThreadScheduledExecutor()**产生一个**ScheduledExecutorService** 对象,然后调用**schedule()** 方法传递所需函数的方法引用,设置以及在运行之前应该等待的时间。 +因为接下来对 **watcher.take()** 的调用会在发生某些事情之前停止所有操作,所以我们希望 **deltxtfiles()** 能够并行运行以便生成我们感兴趣的事件。为了实现这个目的,我通过调用 **Executors.newSingleThreadScheduledExecutor()** 产生一个 **ScheduledExecutorService** 对象,然后调用 **schedule()** 方法传递所需函数的方法引用,并且设置在运行之前应该等待的时间。 -此时,**watcher.take()** 将等待并阻塞在这里。当目标事件发生时,会返回一个包含**WatchEvent**的**Watchkey**对象。展示的这三种方法是对**WatchEvent** 执行的全部操作。 +此时,**watcher.take()** 将等待并阻塞在这里。当目标事件发生时,会返回一个包含 **WatchEvent** 的 **Watchkey** 对象。展示的这三种方法是能对 **WatchEvent** 执行的全部操作。 -查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在**hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说"监视这个目录",自然会包含整个目录和下面子目录,但实际上的:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个**Watchservice**。 +查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在 **Hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说"监视这个目录",自然会包含整个目录和下面子目录,但实际上的:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 **Watchservice**。 ```java // files/TreeWatcher.java @@ -640,9 +644,10 @@ evt.kind(): ENTRY_DELETE */ ``` -在**watchDir()** 方法中给**WatchSevice**提供参数**ENTRY_DELETE**,并启动一个独立的线程来监视该**atchserviceW**。这里我们没有使用**schedule()** 进行启动,而是使用**submit()** 启动线程。我们遍历整个目录树,并将**watchDir()** 应用于每个子目录。现在,当我们运行**deltxtfiles()** 时,其中一个**Watchservice**s 会检测到每一次文件删除。 +在 **watchDir()** 方法中给 **WatchSevice** 提供参数 **ENTRY_DELETE**,并启动一个独立的线程来监视该**Watchservice**。这里我们没有使用 **schedule()** 进行启动,而是使用 **submit()** 启动线程。我们遍历整个目录树,并将 **watchDir()** 应用于每个子目录。现在,当我们运行 **deltxtfiles()** 时,其中一个 **Watchservice** 会检测到每一次文件删除。 + ## 文件查找 到目前为止,为了找到文件,我们一直使用相当粗糙的方法,在 `path` 上调用 `toString()`,然后使用 `string` 操作查看结果。事实证明,`java.nio.file` 有更好的解决方案:通过在 `FileSystem` 对象上调用 `getPathMatcher()` 获得一个 `PathMatcher`,然后传入您感兴趣的模式。模式有两个选项:`glob` 和 `regex`。`glob` 比较简单,实际上功能非常强大,因此您可以使用 `glob` 解决许多问题。如果您的问题更复杂,可以使用 `regex`,这将在接下来的 `Strings` 一章中解释。 @@ -654,35 +659,34 @@ evt.kind(): ENTRY_DELETE import java.nio.file.*; public class Find { - public static void - main(String[] args) throws Exception { - Path test = Paths.get("test"); - Directories.refreshTestDir(); - Directories.populateTestDir(); - // Creating a *directory*, not a file: - Files.createDirectory(test.resolve("dir.tmp")); - - PathMatcher matcher = FileSystems.getDefault() - .getPathMatcher("glob:**/*.{tmp,txt}"); - Files.walk(test) - .filter(matcher::matches) - .forEach(System.out::println); - System.out.println("***************"); - - PathMatcher matcher2 = FileSystems.getDefault() - .getPathMatcher("glob:*.tmp"); - Files.walk(test) - .map(Path::getFileName) - .filter(matcher2::matches) - .forEach(System.out::println); - System.out.println("***************"); - - Files.walk(test) // Only look for files - .filter(Files::isRegularFile) - .map(Path::getFileName) - .filter(matcher2::matches) - .forEach(System.out::println); - } + public static void main(String[] args) throws Exception { + Path test = Paths.get("test"); + Directories.refreshTestDir(); + Directories.populateTestDir(); + // Creating a *directory*, not a file: + Files.createDirectory(test.resolve("dir.tmp")); + + PathMatcher matcher = FileSystems.getDefault() + .getPathMatcher("glob:**/*.{tmp,txt}"); + Files.walk(test) + .filter(matcher::matches) + .forEach(System.out::println); + System.out.println("***************"); + + PathMatcher matcher2 = FileSystems.getDefault() + .getPathMatcher("glob:*.tmp"); + Files.walk(test) + .map(Path::getFileName) + .filter(matcher2::matches) + .forEach(System.out::println); + System.out.println("***************"); + + Files.walk(test) // Only look for files + .filter(Files::isRegularFile) + .map(Path::getFileName) + .filter(matcher2::matches) + .forEach(System.out::println); + } } /* Output: test\bag\foo\bar\baz\5208762845883213974.tmp @@ -708,7 +712,7 @@ dir.tmp */ ``` -在 `matcher` 中,`glob` 表达式开头的 `**/` 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 `Path` 时非常有用。单 `*` 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性---我们正在寻找以`.tmp` 或 `.txt` 结尾的东西。您可以在 `getPathMatcher()` 文档中找到更多详细信息。 +在 `matcher` 中,`glob` 表达式开头的 `**/` 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 `Path` 时非常有用。单 `*` 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性---我们正在寻找以 `.tmp` 或 `.txt` 结尾的东西。您可以在 `getPathMatcher()` 文档中找到更多详细信息。 `matcher2` 只使用 `*.tmp`,通常不匹配任何内容,但是添加 `map()` 操作会将完整路径减少到末尾的名称。 @@ -728,16 +732,15 @@ import java.util.*; import java.nio.file.*; public class ListOfLines { - public static void - main(String[] args) throws Exception { - Files.readAllLines( - Paths.get("../streams/Cheese.dat")) - .stream() - .filter(line -> !line.startsWith("//")) - .map(line -> - line.substring(0, line.length()/2)) - .forEach(System.out::println); - } + public static void main(String[] args) throws Exception { + Files.readAllLines( + Paths.get("../streams/Cheese.dat")) + .stream() + .filter(line -> !line.startsWith("//")) + .map(line -> + line.substring(0, line.length()/2)) + .forEach(System.out::println); + } } /* Output: Not much of a cheese @@ -748,7 +751,7 @@ It's certainly uncon */ ``` -跳过注释行,其余的内容每行只打印一半。 这实现起来很简单:你只需将 `Path` 传递给 `readAllLines()` (以前的 java 实现这个功能很复杂)。 有一个 `readAllLines()` 的重载版本,它包含一个 `Charset` 参数来存储文件的Unicode编码。 +跳过注释行,其余的内容每行只打印一半。 这实现起来很简单:你只需将 `Path` 传递给 `readAllLines()` (以前的 java 实现这个功能很复杂)。`readAllLines()` 有一个重载版本,包含一个 `Charset` 参数来存储文件的 Unicode 编码。 `Files.write()` 被重载以写入 `byte` 数组或任何 `Iterable` 对象(它也有 `Charset` 选项): @@ -758,24 +761,22 @@ import java.util.*; import java.nio.file.*; public class Writing { - static Random rand = new Random(47); - static final int SIZE = 1000; - public static void - main(String[] args) throws Exception { - // Write bytes to a file: - byte[] bytes = new byte[SIZE]; - rand.nextBytes(bytes); - Files.write(Paths.get("bytes.dat"), bytes); - System.out.println("bytes.dat: " + - Files.size(Paths.get("bytes.dat"))); - - // Write an iterable to a file: - List lines = Files.readAllLines( - Paths.get("../streams/Cheese.dat")); - Files.write(Paths.get("Cheese.txt"), lines); - System.out.println("Cheese.txt: " + - Files.size(Paths.get("Cheese.txt"))); - } + static Random rand = new Random(47); + static final int SIZE = 1000; + + public static void main(String[] args) throws Exception { + // Write bytes to a file: + byte[] bytes = new byte[SIZE]; + rand.nextBytes(bytes); + Files.write(Paths.get("bytes.dat"), bytes); + System.out.println("bytes.dat: " + Files.size(Paths.get("bytes.dat"))); + + // Write an iterable to a file: + List lines = Files.readAllLines( + Paths.get("../streams/Cheese.dat")); + Files.write(Paths.get("Cheese.txt"), lines); + System.out.println("Cheese.txt: " + Files.size(Paths.get("Cheese.txt"))); + } } /* Output: bytes.dat: 1000 @@ -783,15 +784,15 @@ Cheese.txt: 199 */ ``` -我们使用 `Random` 来创建一个随机的 `byte` 数组; 你可以看到生成的文件大小是1000。 +我们使用 `Random` 来创建一个随机的 `byte` 数组; 你可以看到生成的文件大小是 1000。 一个 `List` 被写入文件,任何 `Iterable` 对象也可以这么做。 如果文件大小有问题怎么办? 比如说: -1.文件太大,如果你一次读完整个文件,你可能会耗尽内存。 +1. 文件太大,如果你一次性读完整个文件,你可能会耗尽内存。 -2.您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。 +2. 您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。 `Files.lines()` 方便地将文件转换为行的 `Stream`: @@ -800,22 +801,21 @@ Cheese.txt: 199 import java.nio.file.*; public class ReadLineStream { - public static void - main(String[] args) throws Exception { - Files.lines(Paths.get("PathInfo.java")) - .skip(13) - .findFirst() - .ifPresent(System.out::println); - } + public static void main(String[] args) throws Exception { + Files.lines(Paths.get("PathInfo.java")) + .skip(13) + .findFirst() + .ifPresent(System.out::println); + } } /* Output: show("RegularFile", Files.isRegularFile(p)); */ ``` -这本章中的第一个流式传输的示例,跳过13行,然后选择下一行并将其打印出来。 +这对本章中第一个示例代码做了流式处理,跳过 13 行,然后选择下一行并将其打印出来。 -`Files.lines()` 对于处理行作为 *incoming* `Stream` 非常有用,但是如果你想在 `Stream` 中读,操作或写怎么办?这就需要稍微复杂的代码: +`Files.lines()` 对于把文件处理行的传入流时非常有用,但是如果你想在 `Stream` 中读取,处理或写入怎么办?这就需要稍微复杂的代码: ```java // files/StreamInAndOut.java @@ -824,31 +824,31 @@ import java.nio.file.*; import java.util.stream.*; public class StreamInAndOut { - public static void main(String[] args) { - try( - Stream input = - Files.lines(Paths.get("StreamInAndOut.java")); - PrintWriter output = - new PrintWriter("StreamInAndOut.txt") - ) { - input - .map(String::toUpperCase) - .forEachOrdered(output::println); - } catch(Exception e) { - throw new RuntimeException(e); + public static void main(String[] args) { + try( + Stream input = + Files.lines(Paths.get("StreamInAndOut.java")); + PrintWriter output = + new PrintWriter("StreamInAndOut.txt") + ) { + input.map(String::toUpperCase) + .forEachOrdered(output::println); + } catch(Exception e) { + throw new RuntimeException(e); + } } - } } ``` -因为我们在同一个块中执行所有操作,所以这两个文件都可以在相同的try-with-resources语句中打开。`PrintWriter` 是一个旧式的`java.io` 类,允许你“打印”到一个文件,所以它是这个应用程序的理想选择。如果你看一下 `StreamInAndOut.txt`,你会发现它确实是大写的。 +因为我们在同一个块中执行所有操作,所以这两个文件都可以在相同的 try-with-resources 语句中打开。`PrintWriter` 是一个旧式的 `java.io` 类,允许你“打印”到一个文件,所以它是这个应用的理想选择。如果你看一下 `StreamInAndOut.txt`,你会发现它里面的内容确实是大写的。 -## 本章小结 -虽然这是对文件和目录操作的相当全面的介绍,但是库中仍然有没被介绍的功能 - 一定要研究 `java.nio.file` 的Javadocs,尤其是`java.nio.file.Files` 的。 -Java 7和8对于处理文件和目录的库中做了大量改进。如果您刚刚开始使用Java,那么您很幸运。在过去,它非常令人不愉快,我确信Java 设计者以前对于文件操作不够重视才没做简化。对于初学者来说这是一件很棒的事,对于教学者来说也一样。我不明白为什么花了这么长时间来解决这个明显的问题,但不过它被解决了,我很高兴。使用文件现在很简单,甚至很有趣,这是以前你永远想不到的。 +## 本章小结 +虽然本章对文件和目录操作做了相当全面的介绍,但是仍然有没被介绍的类库中的功能——一定要研究 `java.nio.file` 的 Javadocs,尤其是 `java.nio.file.Files` 这个类。 +Java 7 和 8 对于处理文件和目录的类库做了大量改进。如果您刚刚开始使用 Java,那么您很幸运。在过去,它令人非常不愉快,我确信 Java 设计者以前对于文件操作不够重视才没做简化。对于初学者来说这是一件很棒的事,对于教学者来说也一样。我不明白为什么花了这么长时间来解决这个明显的问题,但不管怎么说它被解决了,我很高兴。使用文件现在很简单,甚至很有趣,这是你以前永远想不到的。 +
\ No newline at end of file From 06b39f855b6dd1ec1f323651b363a8cbd4d47560 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Tue, 5 Nov 2019 19:20:40 +0800 Subject: [PATCH 064/371] =?UTF-8?q?revision[18]=20=E6=88=AA=E6=AD=A2?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BF=AE=E9=A5=B0=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/18-Strings.md | 99 +++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 1ffe5e4d..f8319026 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -1,15 +1,16 @@ [TOC] -# 第十八章 字符串 ->可以证明,字符串操作是计算机程序设计中最常见的行为之一。 -在Java大展拳脚的Web系统中更是如此。在本章中,我们将深入学习在Java语言中应用最广泛的`String`类,并研究与之相关的类及工具。 +# 第十八章 字符串 +>字符串操作毫无疑问是计算机程序设计中最常见的行为之一。 +在 Java 大展拳脚的 Web 系统中更是如此。在本章中,我们将深入学习在 Java 语言中应用最广泛的 `String` 类,并研究与之相关的类及工具。 + ## 字符串的不可变 -`String`对象是不可变的。查看JDK文档你就会发现,`String`类中每一个看起来会修改`String`值的方法,实际上都是创建了一个全新的`String`对象,以包含修改后的字符串内容。而最初的`String`对象则丝毫未动。 +`String` 对象是不可变的。查看 JDK 文档你就会发现,`String` 类中每一个看起来会修改 `String` 值的方法,实际上都是创建了一个全新的 `String` 对象,以包含修改后的字符串内容。而最初的`String` 对象则丝毫未动。 看看下面的代码: ```java @@ -32,25 +33,26 @@ HOWDY howdy */ ``` -当把`q`传递给`upcase()`方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。 +当把 `q` 传递给 `upcase()` 方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。 -回到`upcase()`的定义,传入其中的引用有了名字`s`,只有`upcase()`运行的时候,局部引用`s`才存在。一旦`upcase()`运行结束,`s`就消失了。当然了,`upcase()`的返回值,其实是最终结果的引用。这足以说明,`upcase()`返回的引用已经指向了一个新的对象,而`q`仍然在原来的位置。 +回到 `upcase()` 的定义,传入其中的引用有了名字 `s`,只有 `upcase()` 运行的时候,局部引用 `s` 才存在。一旦 `upcase()` 运行结束,`s` 就消失了。当然了,`upcase()` 的返回值,其实是最终结果的引用。这足以说明,`upcase()` 返回的引用已经指向了一个新的对象,而 `q` 仍然在原来的位置。 + +`String` 的这种行为正是我们想要的。例如: -`String`的这种行为正是我们想要的。例如: ```java String s = "asdf"; String x = Immutable.upcase(s); ``` -难道你真的希望`upcase()`方法改变其参数吗?对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。 +难道你真的希望 `upcase()` 方法改变其参数吗?对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。 -## `+`的重载与`StringBuilder` -`String`对象是不可变的,你可以给一个`String`对象添加任意多的别名。因为`String`是只读的,所以指向它的任何引用都不可能改它的值,因此,也就不会影响到其他引用。 +## `+` 的重载与 `StringBuilder` +`String` 对象是不可变的,你可以给一个 `String` 对象添加任意多的别名。因为 `String` 是只读的,所以指向它的任何引用都不可能修改它的值,因此,也就不会影响到其他引用。 -不可变性会带来一定的效率问题。为`String`对象重载的`+`操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于`String`的`+`与`+=`是Java中仅有的两个重载过的操作符,Java不允许程序员重载任何其他的操作符 [^1])。 +不可变性会带来一定的效率问题。为 `String` 对象重载的 `+` 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于 `String` 的 `+` 与 `+=` 是 Java 中仅有的两个重载过的操作符,Java 不允许程序员重载任何其他的操作符 [^1])。 -操作符`+`可以用来连接`String`: +操作符 `+` 可以用来连接 `String`: ```java // strings/Concatenation.java @@ -65,15 +67,15 @@ public class Concatenation { abcmangodef47 */ ``` -可以想象一下,这段代码可能是这样工作的:`String`可能有一个`append()`方法,它会生成一个新的`String`对象,以包含“abc”与`mango`连接后的字符串。该对象会再创建另一个新的`String`对象,然后与“def”相连,生成另一个新的对象,依此类推。 +可以想象一下,这段代码是这样工作的:`String` 可能有一个 `append()` 方法,它会生成一个新的 `String` 对象,以包含“abc”与 `mango` 连接后的字符串。该对象会再创建另一个新的 `String` 对象,然后与“def”相连,生成另一个新的对象,依此类推。 -这种方式当然是可行的,但是为了生成最终的`String`对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。 +这种方式当然是可行的,但是为了生成最终的 `String` 对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。 -想看看以上代码到底是如何工作的吗?可以用JDK自带的`javap`工具来反编译以上代码。命令如下: +想看看以上代码到底是如何工作的吗?可以用JDK自带的 `javap` 工具来反编译以上代码。命令如下: ```java javap -c Concatenation ``` -这里的`-c`标志表示将生成JVM字节码。我们剔除不感兴趣的部分,然后做细微的修改,于是有了一下的字节码: +这里的 `-c` 标志表示将生成 JVM 字节码。我们剔除不感兴趣的部分,然后做细微的修改,于是有了以下的字节码: ```x86asm public static void main(java.lang.String[]); Code: @@ -98,11 +100,11 @@ public static void main(java.lang.String[]); 37: invokevirtual #11; //PrintStream.println:(String) 40: return ``` -如果你有汇编语言的经验,以上代码应该很眼熟(其中的`dup`和`invokevirtual`语句相当于Java虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心)。需要重点注意的是:编译器自动引入了`java.lang.StringBuilder`类。虽然源代码中并没有使用`StringBuilder`类,但是编译器却自作主张地使用了它,就因为它更高效。 +如果你有汇编语言的经验,以上代码应该很眼熟(其中的 `dup` 和 `invokevirtual` 语句相当于Java虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心)。需要重点注意的是:编译器自动引入了 `java.lang.StringBuilder` 类。虽然源代码中并没有使用 `StringBuilder` 类,但是编译器却自作主张地使用了它,就因为它更高效。 -在这里,编译器创建了一个`StringBuilder`对象,用于构建最终的`String`,并对每个字符串调用了一次`append`()方法,共计4次。最后调用`toString()`生成结果,并存为`s`(使用的命令为`astore_2`)。 +在这里,编译器创建了一个 `StringBuilder` 对象,用于构建最终的 `String`,并对每个字符串调用了一次 `append()` 方法,共计 4 次。最后调用 `toString()` 生成结果,并存为 `s` (使用的命令为 `astore_2`)。 -现在,也许你会觉得可以随意使用`String`对象,反正编译器会自动为你做性能优化。可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。下面的例子采用两种方式生成一个`String`:方法一使用了多个`String`对象;方法二在代码中使用了`StringBuilder`。 +现在,也许你会觉得可以随意使用 `String` 对象,反正编译器会自动为你做性能优化。可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。下面的例子采用两种方式生成一个 `String`:方法一使用了多个 `String` 对象;方法二在代码中使用了 `StringBuilder`。 ```java // strings/WhitherStringBuilder.java @@ -123,11 +125,11 @@ public class WhitherStringBuilder { } } ``` -现在运行`javap -c WitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是`implicit`()方法: +现在运行 `javap -c WitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是 `implicit()` 方法: ```x86asm public java.lang.String implicit(java.lang.String[]); 0: ldc #2 // String -2: astore_2 +2: astore_2 3: aload_1 4: astore_3 5: aload_3 @@ -156,7 +158,7 @@ public java.lang.String implicit(java.lang.String[]); 51: aload_2 52: areturn ``` -注意从第16行到第48行构成了一个循环体。第16行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第51行。第48行:返回循环体的起始位置(第12行)。注意:`StringBuilder`是在循环内构造的,这意味着每进行一次循环,会创建一个新的`StringBuilder`对象。 +注意从第 16 行到第 48 行构成了一个循环体。第 16 行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第 51 行。第 48 行:重新回到循环体的起始位置(第 12 行)。注意:`StringBuilder` 是在循环内构造的,这意味着每进行一次循环,会创建一个新的`StringBuilder`对象。 下面是`explicit()`方法对应的字节码: ```x86asm @@ -189,9 +191,9 @@ public java.lang.String explicit(java.lang.String[]); 44: invokevirtual #6 // StringBuilder.toString:() 47: areturn ``` -可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个`StringBuilder`对象。显式地创建`StringBuilder`还允许你预先为其指定大小。如果你已经知道最终字符串的大概长度,那预先指定`StringBuilder`的大小可以避免频繁地重新分配缓冲。 +可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个 `StringBuilder` 对象。显式地创建 `StringBuilder` 还允许你预先为其指定大小。如果你已经知道最终字符串的大概长度,那预先指定 `StringBuilder` 的大小可以避免频繁地重新分配缓冲。 -因此,当你为一个类编写`toString()`方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在`toString()`方法中使用循环,且可能有性能问题,那么最好自己创建一个`StringBuilder`对象,用它来构建最终结果。请参考以下示例: +因此,当你为一个类编写 `toString()` 方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在 `toString()` 方法中使用循环,且可能有性能问题,那么最好自己创建一个 `StringBuilder` 对象,用它来构建最终结果。请参考以下示例: ```java // strings/UsingStringBuilder.java @@ -228,18 +230,18 @@ public class UsingStringBuilder { 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] */ ``` -在方法`string1()`中,最终结果是用`append()`语句拼接起来的。如果你想走捷径,例如:`append(a + ": " + c)`,编译器就会掉入陷阱,从而为你另外创建一个`StringBuilder`对象处理括号内的字符串操作。如果拿不准该用哪种方式,随时可以用`javap`来分析你的程序。 - -`StringBuilder`提供了丰富而全面的方法,包括`insert()`、`replace()`、`substring()`,甚至还有`reserve()`,但是最常用的还是`append()`和`toString()`。还有`delete()`,上面的例子中我们用它删除最后一个逗号和空格,以便添加右括号。 +在方法 `string1()` 中,最终结果是用 `append()` 语句拼接起来的。如果你想走捷径,例如:`append(a + ": " + c)`,编译器就会掉入陷阱,从而为你另外创建一个 `StringBuilder` 对象处理括号内的字符串操作。如果拿不准该用哪种方式,随时可以用 `javap` 来分析你的程序。 -`string2()`使用了`Stream`,这样代码更加简洁美观。可以证明,`Collectors.joining()`内部也是使用的`StringBuilder`,这种写法不会影响性能! +`StringBuilder` 提供了丰富而全面的方法,包括 `insert()`、`replace()`、`substring()`,甚至还有`reverse()`,但是最常用的还是 `append()` 和 `toString()`。还有 `delete()`,上面的例子中我们用它删除最后一个逗号和空格,以便添加右括号。 -`StringBuilder`是Java SE5引入的,在这之前用的是`StringBuffer`。后者是线程安全的(参见[并发编程](#)),因此开销也会大些。使用`StringBuilder`进行字符串操作更快一点。 +`string2()` 使用了 `Stream`,这样代码更加简洁美观。可以证明,`Collectors.joining()` 内部也是使用的 `StringBuilder`,这种写法不会影响性能! +`StringBuilder `是 Java SE5 引入的,在这之前用的是 `StringBuffer`。后者是线程安全的(参见[并发编程](./24-Concurrent-Programming.md)),因此开销也会大些。使用 `StringBuilder` 进行字符串操作更快一点。 + ## 意外递归 -Java中的每个类从根本上都是继承自`Object`,标准集合类也是如此,集合类都有`toString()`方法,并且覆盖了该方法,使得它生成的`String`结果能够表达集合自身,以及集合包含的对象。例如`ArrayList.toString()`,它会遍历`ArrayList`中包含的所有对象,调用每个元素上的`toString()`方法: +Java 中的每个类从根本上都是继承自 `Object`,标准集合类也是如此,它们都有 `toString()` 方法,并且覆盖了该方法,使得它生成的 `String` 结果能够表达集合自身,以及集合包含的对象。例如 `ArrayList.toString()`,它会遍历 `ArrayList` 中包含的所有对象,调用每个元素上的 `toString()` 方法: ```java // strings/ArrayListDisplay.java import java.util.*; @@ -259,7 +261,7 @@ public class ArrayListDisplay { Breve 5, Americano 6, Latte 7, Cappuccino 8, Cappuccino 9] */ ``` -如果你希望`toString()`打印出类的内存地址,也许你会考虑使用`this`关键字: +如果你希望 `toString()` 打印出类的内存地址,也许你会考虑使用 `this` 关键字: ```java // strings/InfiniteRecursion.java // Accidental recursion @@ -280,18 +282,18 @@ public class InfiniteRecursion { } } ``` -当你创建了`InfiniteRecursion`对象,并将其打印出来的时候,你会得到一串很长的异常信息。如果你将该`InfiniteRecursion`对象存入一个`ArrayList`中,然后打印该`ArrayList`,同样也会抛出异常。其实,当运行到如下代码时: +当你创建了 `InfiniteRecursion` 对象,并将其打印出来的时候,你会得到一串很长的异常信息。如果你将该 `InfiniteRecursion` 对象存入一个 `ArrayList` 中,然后打印该 `ArrayList`,同样也会抛出异常。其实,当运行到如下代码时: ```java "InfiniteRecursion address: " + this ``` -这里发生了自动类型转换,由`InfiniteRecursion`类型转换为`String`类型。因为编译器发现一个`String`对象后面跟着一个“+”,而“+”后面的对象不是`String`,于是编译器试着将`this`转换成一个`String`。它怎么转换呢?正是通过调用`this`上的`toString()`方法,于是就发生了递归调用。 +这里发生了自动类型转换,由 `InfiniteRecursion` 类型转换为 `String` 类型。因为编译器发现一个 `String` 对象后面跟着一个 “+”,而 “+” 后面的对象不是 `String`,于是编译器试着将 `this` 转换成一个 `String`。它怎么转换呢?正是通过调用 `this` 上的 `toString()` 方法,于是就发生了递归调用。 -如果你真的想要打印对象的内存地址,应该调用`Object.toString()`方法,这才是负责此任务的方法。所以,不要使用`this`,而是应该调用`super.toString()`方法。 +如果你真的想要打印对象的内存地址,应该调用 `Object.toString()` 方法,这才是负责此任务的方法。所以,不要使用 `this`,而是应该调用 `super.toString()` 方法。 ## 字符串操作 -以下是`String`对象具备的一些基本方法。重载的方法归纳在同一行中: +以下是 `String` 对象具备的一些基本方法。重载的方法归纳在同一行中: | 方法 | 参数,重载版本 | 作用 | | ---- | ---- | ---- | @@ -323,22 +325,22 @@ public class InfiniteRecursion { | `intern()` | | 为每个唯一的字符序列生成一个且仅生成一个`String`引用 | | `format()` | 要格式化的字符串,要替换到格式化字符串的参数 | 返回格式化结果`String` | -从这个表可以看出,当需要改变字符串的内容时,`String`类的方法都会返回一个新的`String`对象。同时,如果内容不改变,`String`方法只是返回原始对象的一个引用而已。这可以节约存储空间以及避免额外的开销。 - -本章稍后还将介绍正则表达式在`String`方法中的应用。 +从这个表可以看出,当需要改变字符串的内容时,`String` 类的方法都会返回一个新的 `String` 对象。同时,如果内容不改变,`String` 方法只是返回原始对象的一个引用而已。这可以节约存储空间以及避免额外的开销。 +本章稍后还将介绍正则表达式在 `String` 方法中的应用。 + ## 格式化输出 -在长久的等待之后,Java SE5终于推出了C语言中 `printf()` 风格的格式化输出这一功能。这不仅使得控制输出的代码更加简单,同时也给与Java开发者对于输出格式与排列更加大的控制能力。 +在长久的等待之后,Java SE5 终于推出了 C 语言中 `printf()` 风格的格式化输出这一功能。这不仅使得控制输出的代码更加简单,同时也给与Java开发者对于输出格式与排列更强大的控制能力。 ### `printf()` -C语言的 `printf()` 并不像Java那样连接字符串,它使用一个简单的格式化字符串,加上要插入其中的值,然后将其格式化输出。 `printf()` 并不使用重载的`+`操作符(C语言没有重载)来连接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如: +C 语言的 `printf()` 并不像 Java 那样连接字符串,它使用一个简单的格式化字符串,加上要插入其中的值,然后将其格式化输出。 `printf()` 并不使用重载的 `+` 操作符(C语言没有重载)来连接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如: ```c System.out.printf("Row 1: [%d %f]%n", x, y); ``` -这一行代码在运行的时候,首先将`x`的值插入到`%d_`的位置,然后将`y`的值插入到`%f`的位置。这些占位符叫做*格式修饰符*,它们不仅指明了插入数据的位置,同时还指明了将会插入什么类型的变量,以及如何格式化。在这个例子中`%d`表示`x`是一个整数,`%f`表示`y`是一个浮点数(`float`或者 `double`)。 +这一行代码在运行的时候,首先将 `x` 的值插入到 `%d_` 的位置,然后将 `y` 的值插入到 `%f` 的位置。这些占位符叫做*格式修饰符*,它们不仅指明了插入数据的位置,同时还指明了将会插入什么类型的变量,以及如何格式化。在这个例子中 `%d` 表示 `x` 是一个整数,`%f` 表示 `y` 是一个浮点数(`float` 或者 `double`)。 ### `System.out.format()` -Java SE5引入了`format()`方法,可用于`PrintStream`或者`PrintWriter`对象(你可以在 [`附录:流式I/O`](#)了解更多内容),其中也包括`System.out`对象。`format()`方法模仿了C语言的`printf()`。如果你比较怀旧的话,也可以使用 `printf()`。以下是一个简单的示例: +Java SE5 引入了 `format()` 方法,可用于 `PrintStream` 或者 `PrintWriter` 对象(你可以在 [附录:流式 I/O](./Appendix-IO-Streams.md) 了解更多内容),其中也包括 `System.out` 对象。`format()` 方法模仿了 C 语言的 `printf()`。如果你比较怀旧的话,也可以使用 `printf()`。以下是一个简单的示例: ```java // strings/SimpleFormat.java @@ -360,11 +362,12 @@ Row 1: [5 5.332542] Row 1: [5 5.332542] */ ``` -可以看到,`format()`和 `printf()`是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。 +可以看到,`format()` 和 `printf()` 是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。 + +`String` 类也有一个 `static format()` 方法,可以格式化字符串。 -`String`类也有一个`static format()`方法,可以格式化字符串。 -### `Formatter`类 -在Java中,所有的格式化功能都是由`java.util.Formatter`类处理的。可以将`Formatter`看做一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个`Formatter`对象时,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出: +### `Formatter` 类 +在 Java 中,所有的格式化功能都是由 `java.util.Formatter` 类处理的。可以将 `Formatter` 看做一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个 `Formatter` 对象时,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出: ```java // strings/Turtle.java import java.io.*; @@ -404,9 +407,9 @@ Tommy The Turtle is at (3,3) Terry The Turtle is at (3,3) */ ``` -格式化修饰符`%s`表明这里需要`String`参数。 +格式化修饰符 `%s` 表明这里需要 `String` 参数。 -所有的`tommy`都将输出到`System.out`,而所有的`terry`则都输出到`System.out`的一个别名中。`Formatter`的重载构造器支持输出到多个路径,不过最常用的还是`PrintStream()`(如上例)、`OutputStream`和`File`。你可以在[`附录:I/O Streams`](#)中了解更多信息。 +所有的 `tommy` 都将输出到 `System.out`,而所有的 `terry` 则都输出到 `System.out` 的一个别名中。`Formatter` 的重载构造器支持输出到多个路径,不过最常用的还是 `PrintStream()`(如上例)、`OutputStream` 和 `File`。你可以在 [附录:流式 I/O](././Appendix-IO-Streams.md) 中了解更多信息。 ### 格式化修饰符 在插入数据时,如果想要优化空格与对齐,你需要更精细复杂的格式修饰符。以下是其抽象语法: ```java From 18c67a6092fa4fa652376abe26c0d880090e69fb Mon Sep 17 00:00:00 2001 From: Moilk Date: Thu, 7 Nov 2019 17:47:36 +0800 Subject: [PATCH 065/371] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E7=AC=AC=E5=8D=81?= =?UTF-8?q?=E4=B9=9D=E7=AB=A0=20=E7=B1=BB=E5=9E=8B=E4=BF=A1=E6=81=AF=20-?= =?UTF-8?q?=20=E6=9C=AC=E7=AB=A0=E5=B0=8F=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index cd85f70f..81ddc02b 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1312,7 +1312,7 @@ x.getClass().equals(Derived.class)) true 通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](#ch040.xhtml#appendix-object-serialization))。但是,有时动态提取有关类的信息很有用。 -考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时。 +考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。 ```java // typeinfo/ShowMethods.java @@ -1386,7 +1386,7 @@ public ShowMethods() */ ``` -`Class` 方法 `getmethods()` 和'getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。 +`Class` 方法 `getmethods()` 和 `getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。 编译时无法知道 `Class.forName()` 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用(本书后面有这样的例子)。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。 @@ -1614,6 +1614,25 @@ boring3 ## 本章小结 +RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch` 语句,你可以用 RTTI 和 `switch` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能的使用多态机制,只在非用不可的时候才使用 RTTI。 + +然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 `switch` 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。 + +如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 `Instrument` 的类层次结构。假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 `Instrument` 中放入 `clearSpitValve()` 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 `Percussion`、弦乐器 `Stringed` 和电子乐器 `Electronic` 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 `clearSpitValve()` 放在某个合适的类中,在这个例子中是管乐器 `Wind`。不过,在这里你可能会发现还有更好的解决方法,就是将 `prepareInstrument()` 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。 + +最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早的关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)[^5]。 + +我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想的更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只有你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。 + +[^1]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。 + +[^2]: 极限编程(XP,Extreme Programming)的一条宗旨:“Try the simplest thing that could possibly work,实现尽最大可能的简单。” + +[^3]: 最著名的例子是 Windows 操作系统,Windows 为开发者提供了公开的 API,但是开发者还可以找到一些非公开但是可以调用的函数。为了解决问题,很多程序员使用了隐藏的 API 函数。这就迫使微软公司要像维护公开 API 一样维护这些隐藏的 API,消耗了巨大的成本和精力。 + +[^4]: 比如,Python 中在元素前面添加双下划线 `__`,就表示你想隐藏这个元素。如果你在类或者包外面调用了这个元素,运行环境就会报错。 + +[^5]: 译者注:Java Profiler 是一种 Java 性能分析工具,用于在 JVM 级别监视 Java 字节码的构造和执行。主流的 Profiler 有 JProfiler、YourKit 和 Java VisualVM 等。 From 4d8a895efecc17b8a5f270e036e244c8e340e394 Mon Sep 17 00:00:00 2001 From: Moilk Date: Fri, 8 Nov 2019 11:28:30 +0800 Subject: [PATCH 066/371] =?UTF-8?q?=E5=AE=8C=E6=88=90=20=E7=AC=AC19?= =?UTF-8?q?=E7=AB=A0=20=E7=B1=BB=E5=9E=8B=E4=BF=A1=E6=81=AF-=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=92=8C=E7=B1=BB=E5=9E=8B=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 317 +++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 81ddc02b..e977bb53 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1610,6 +1610,323 @@ boring3 ## 接口和类型 +`interface` 关键字的一个重要目标就是允许程序员隔离构件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口: + +```java +// typeinfo/interfacea/A.java +package typeinfo.interfacea; + +public interface A { + void f(); +} +``` + +然后实现这个接口,你可以看到其代码是怎么从实际类型开始顺藤摸瓜的: + +```java +// typeinfo/InterfaceViolation.java +// Sneaking around an interface +import typeinfo.interfacea.*; + +class B implements A { + public void f() {} + public void g() {} +} + +public class InterfaceViolation { + public static void main(String[] args) { + A a = new B(); + a.f(); + // a.g(); // Compile error + System.out.println(a.getClass().getName()); + if(a instanceof B) { + B b = (B)a; + b.g(); + } + } +} + +``` + +输出结果: + +``` +B +``` + +通过使用 RTTI,我们发现 `a` 是被当做 `B` 实现的。通过将其转型为 `B`,我们可以调用不在 `A` 中的方法。 + +这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 `interface` 关键字正在保护你,但其实并没有。另外,在本例中使用 `B` 来实现 `A` 这中情况是有公开案例可查的[^3]。 + +一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是可行的,但“可能”还不够,你或许希望能有一些更严格的控制方式。 + +最简单的方式是让实现类只具有包访问权限,这样在包外部的客户端就看不到它了: + +```java +// typeinfo/packageaccess/HiddenC.java +package typeinfo.packageaccess; +import typeinfo.interfacea.*; + +class C implements A { + @Override + public void f() { + System.out.println("public C.f()"); + } + public void g() { + System.out.println("public C.g()"); + } + void u() { + System.out.println("package C.u()"); + } + protected void v() { + System.out.println("protected C.v()"); + } + private void w() { + System.out.println("private C.w()"); + } +} + +public class HiddenC { + public static A makeA() { return new C(); } +} +``` + +在这个包中唯一 `public` 的部分就是 `HiddenC`,在被调用时将产生 `A`接口类型的对象。这里有趣之处在于:即使你从 `makeA()` 返回的是 `C` 类型,你在包的外部仍旧不能使用 `A` 之外的任何方法,因为你不能在包的外部命名 `C`。 + +现在如果你试着将其向下转型为 `C`,则将被禁止,因为在包的外部没有任何 `C` 类型可用: + +```java +// typeinfo/HiddenImplementation.java +// Sneaking around package hiding +import typeinfo.interfacea.*; +import typeinfo.packageaccess.*; +import java.lang.reflect.*; + +public class HiddenImplementation { + public static void main(String[] args) throws Exception { + A a = HiddenC.makeA(); + a.f(); + System.out.println(a.getClass().getName()); + // Compile error: cannot find symbol 'C': + /* if(a instanceof C) { + C c = (C)a; + c.g(); + } */ + // Oops! Reflection still allows us to call g(): + callHiddenMethod(a, "g"); + // And even less accessible methods! + callHiddenMethod(a, "u"); + callHiddenMethod(a, "v"); + callHiddenMethod(a, "w"); + } + static void callHiddenMethod(Object a, String methodName) throws Exception { + Method g = a.getClass().getDeclaredMethod(methodName); + g.setAccessible(true); + g.invoke(a); + } +} +``` + +输出结果: + +``` +public C.f() +typeinfo.packageaccess.C +public C.g() +package C.u() +protected C.v() +private C.w() +``` + +正如你所看到的,通过使用反射,仍然可以调用所有方法,甚至是 `private` 方法!如果知道方法名,你就可以在其 `Method` 对象上调用 `setAccessible(true)`,就像在 `callHiddenMethod()` 中看到的那样。 + +你可能觉得,可以通过只发布编译后的代码来阻止这种情况,但其实这并不能解决问题。因为只需要运行 `javap`(一个随 JDK 发布的反编译器)即可突破这一限制。下面是一个使用 `javap` 的命令行: + +```shell +javap -private C +``` + +`-private` 标志表示所有的成员都应该显示,甚至包括私有成员。下面是输出: + +``` +class typeinfo.packageaccess.C extends +java.lang.Object implements typeinfo.interfacea.A { + typeinfo.packageaccess.C(); + public void f(); + public void g(); + void u(); + protected void v(); + private void w(); +} +``` + +因此,任何人都可以获取你最私有的方法的名字和签名,然后调用它们。 + +那如果把接口实现为一个私有内部类,又会怎么样呢?下面展示了这种情况: + +```java +// typeinfo/InnerImplementation.java +// Private inner classes can't hide from reflection +import typeinfo.interfacea.*; +class InnerA { + private static class C implements A { + public void f() { + System.out.println("public C.f()"); + } + public void g() { + System.out.println("public C.g()"); + } + void u() { + System.out.println("package C.u()"); + } + protected void v() { + System.out.println("protected C.v()"); + } + private void w() { + System.out.println("private C.w()"); + } + } + public static A makeA() { + return new C(); + } +} +public class InnerImplementation { + public static void + main(String[] args) throws Exception { + A a = InnerA.makeA(); + a.f(); + System.out.println(a.getClass().getName()); + // Reflection still gets into the private class: + HiddenImplementation.callHiddenMethod(a, "g"); + HiddenImplementation.callHiddenMethod(a, "u"); + HiddenImplementation.callHiddenMethod(a, "v"); + HiddenImplementation.callHiddenMethod(a, "w"); + } +} +``` + +输出结果: + +``` +public C.f() +InnerA$C +public C.g() +package C.u() +protected C.v() +private C.w() +``` + +这里对反射仍然没有任何东西可以隐藏。那么如果是匿名类呢? + +```java +// typeinfo/AnonymousImplementation.java +// Anonymous inner classes can't hide from reflection +import typeinfo.interfacea.*; +class AnonymousA { + public static A makeA() { + return new A() { + public void f() { + System.out.println("public C.f()"); + } + public void g() { + System.out.println("public C.g()"); + } + void u() { + System.out.println("package C.u()"); + } + protected void v() { + System.out.println("protected C.v()"); + } + private void w() { + System.out.println("private C.w()"); + } + } + ; + } +} +public class AnonymousImplementation { + public static void + main(String[] args) throws Exception { + A a = AnonymousA.makeA(); + a.f(); + System.out.println(a.getClass().getName()); + // Reflection still gets into the anonymous class: + HiddenImplementation.callHiddenMethod(a, "g"); + HiddenImplementation.callHiddenMethod(a, "u"); + HiddenImplementation.callHiddenMethod(a, "v"); + HiddenImplementation.callHiddenMethod(a, "w"); + } +} +``` + +输出结果: + +``` +public C.f() +AnonymousA$1 +public C.g() +package C.u() +protected C.v() +private C.w() +``` + +看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于域来说也是这样,即便是 `private` 域: + +```java +// typeinfo/ModifyingPrivateFields.java +import java.lang.reflect.*; +class WithPrivateFinalField { + private int i = 1; + private final String s = "I'm totally safe"; + private String s2 = "Am I safe?"; + @Override + public String toString() { + return "i = " + i + ", " + s + ", " + s2; + } +} +public class ModifyingPrivateFields { + public static void + main(String[] args) throws Exception { + WithPrivateFinalField pf = + new WithPrivateFinalField(); + System.out.println(pf); + Field f = pf.getClass().getDeclaredField("i"); + f.setAccessible(true); + System.out.println( + "f.getInt(pf): " + f.getint(pf)); + f.setint(pf, 47); + System.out.println(pf); + f = pf.getClass().getDeclaredField("s"); + f.setAccessible(true); + System.out.println("f.get(pf): " + f.get(pf)); + f.set(pf, "No, you're not!"); + System.out.println(pf); + f = pf.getClass().getDeclaredField("s2"); + f.setAccessible(true); + System.out.println("f.get(pf): " + f.get(pf)); + f.set(pf, "No, you're not!"); + System.out.println(pf); + } +} +``` + +输出结果: + +``` +i = 1, I'm totally safe, Am I safe? +f.getInt(pf): 1 +i = 47, I'm totally safe, Am I safe? +f.get(pf): I'm totally safe +i = 47, I'm totally safe, Am I safe? +f.get(pf): Am I safe? +i = 47, I'm totally safe, No, you're not! +``` + +但实际上 `final` 域在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。 + +通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,发射给我们带来了很多好处。 + +程序员往往对编程语言提供的访问控制过于自信,甚至认为 Java 在安全性上比其它提供了(明显)更宽松的访问控制的语言要优越[^4]。然而,正如你所看到的,事实并不是这样。 ## 本章小结 From 7e7fb112038f62e5e5a2233cc967602cfbc5b372 Mon Sep 17 00:00:00 2001 From: Moilk Date: Fri, 8 Nov 2019 15:23:24 +0800 Subject: [PATCH 067/371] =?UTF-8?q?=E7=AC=AC19=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=A1=E6=81=AF=20-=20Optional=20=E5=AE=8C=E6=88=90?= =?UTF-8?q?1/2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index e977bb53..8909b21e 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1606,6 +1606,134 @@ boring3 ## Optional类 +如果你使用内置的 `null` 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 `null`,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 `null` 没什么自己的行为,只会在你想用它执行任何操作的时候产生 `NullPointException`。`java.util.Optional`(首次出现是在[函数式编程](docs/book/13-Functional-Programming.md)这章)为 `null` 值提供了一个轻量级代理,`Optional` 对象可以防止你的代码直接抛出 `NullPointException`。 + +虽然 `Optional` 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。 + +实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理的假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`: + +```java +// typeinfo/Person.java +// Using Optional with regular classes +import onjava.*; +import java.util.*; +class Person { + public final Optional first; + public final Optional last; + public final Optional address; + // etc. + public final Boolean empty; + Person(String first, String last, String address) { + this.first = Optional.ofNullable(first); + this.last = Optional.ofNullable(last); + this.address = Optional.ofNullable(address); + empty = !this.first.isPresent() + && !this.last.isPresent() + && !this.address.isPresent(); + } + Person(String first, String last) { + this(first, last, null); + } + Person(String last) { + this(null, last, null); + } + Person() { + this(null, null, null); + } + @Override + public String toString() { + if(empty) + return ""; + return (first.orElse("") + + " " + last.orElse("") + + " " + address.orElse("")).trim(); + } + public static void main(String[] args) { + System.out.println(new Person()); + System.out.println(new Person("Smith")); + System.out.println(new Person("Bob", "Smith")); + System.out.println(new Person("Bob", "Smith", + "11 Degree Lane, Frostbite Falls, MN")); + } +} +``` + +输出结果: + +``` + +Smith +Bob Smith +Bob Smith 11 Degree Lane, Frostbite Falls, MN +``` + +`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有域都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的域重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 域在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。 + +如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 域,这样就不会意外触发 `NullPointException` 了。 + +现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 `Person Optional` 对象放在每个 `Position` 上: + +```java +// typeinfo/Position.java +import java.util.*; +class EmptyTitleException extends RuntimeException { +} +class Position { + private String title; + private Person person; + Position(String jobTitle, Person employee) { + setTitle(jobTitle); + setPerson(employee); + } + Position(String jobTitle) { + this(jobTitle, null); + } + public String getTitle() { + return title; + } + public void setTitle(String newTitle) { + // Throws EmptyTitleException if newTitle is null: + title = Optional.ofNullable(newTitle) + .orElseThrow(EmptyTitleException::new); + } + public Person getPerson() { + return person; + } + public void setPerson(Person newPerson) { + // Uses empty Person if newPerson is null: + person = Optional.ofNullable(newPerson) + .orElse(new Person()); + } + @Override + public String toString() { + return "Position: " + title + + ", Employee: " + person; + } + public static void main(String[] args) { + System.out.println(new Position("CEO")); + System.out.println(new Position("Programmer", + new Person("Arthur", "Fonzarelli"))); + try { + new Position(null); + } + catch(Exception e) { + System.out.println("caught " + e); + } + } +} +``` + +输出结果: + +``` +Position: CEO, Employee: +Position: Programmer, Employee: Arthur Fonzarelli +caught EmptyTitleException +``` + +这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通域,不受 `Optional` 的保护。但是,修改这些域的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对域进行了严格的限制。 + + ## 接口和类型 From 18227d9557f50e0260ddb9e4087e3787f83c9b11 Mon Sep 17 00:00:00 2001 From: Moilk Date: Fri, 8 Nov 2019 21:57:18 +0800 Subject: [PATCH 068/371] backup --- docs/book/19-Type-Information.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 8909b21e..4564f619 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1733,6 +1733,8 @@ caught EmptyTitleException 这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通域,不受 `Optional` 的保护。但是,修改这些域的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对域进行了严格的限制。 +我们想保证 `title` 不会有 `null` 值, + From b0f5d060fa975b4d0793dc21b9b0d2061c71e2c7 Mon Sep 17 00:00:00 2001 From: Moilk Date: Sat, 9 Nov 2019 09:02:14 +0800 Subject: [PATCH 069/371] =?UTF-8?q?=E7=AC=AC19=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=A1=E6=81=AF=EF=BC=9A=E5=B0=86=E2=80=9C=E5=9F=9F?= =?UTF-8?q?=E2=80=9D=E4=BF=AE=E6=94=B9=E6=88=90=E2=80=9C=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 4564f619..6e171a44 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -317,11 +317,11 @@ Java还提供了另一种方法来生成类对象的引用:**类字面常量** 1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。 -2. **链接**。在链接阶段将验证类中的字节码,为 `static` 域分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。 +2. **链接**。在链接阶段将验证类中的字节码,为 `static` 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。 3. **初始化**。如果该类具有超类,则对其进行初始化,执行 `static` 初始化器和 `static` 初始化块。 -初始化被延迟到了对 `static` 方法(构造器隐式地是 `static` 的)或者非常数 `static` 域进行首次引用时才执行: +初始化被延迟到了对 `static` 方法(构造器隐式地是 `static` 的)或者非常数 `static` 字段进行首次引用时才执行: ```java // typeinfo/ClassInitialization.java @@ -385,9 +385,9 @@ After creating Initable3 ref 初始化有效地实现了尽可能的“惰性”,从对 `initable` 引用的创建中可以看到,仅使用 `.class` 语法来获得对类对象的引用不会引发初始化。但与此相反,使用 `Class.forName()` 来产生 `Class` 引用会立即就进行初始化,如 `initable3`。 -如果一个 `static final` 值是“编译期常量”(如 `Initable.staticFinal`),那么这个值不需要对 `Initable` 类进行初始化就可以被读取。但是,如果只是将一个域设置成为 `static` 和 `final`,还不足以确保这种行为。例如,对 `Initable.staticFinal2` 的访问将强制进行类的初始化,因为它不是一个编译期常量。 +如果一个 `static final` 值是“编译期常量”(如 `Initable.staticFinal`),那么这个值不需要对 `Initable` 类进行初始化就可以被读取。但是,如果只是将一个字段设置成为 `static` 和 `final`,还不足以确保这种行为。例如,对 `Initable.staticFinal2` 的访问将强制进行类的初始化,因为它不是一个编译期常量。 -如果一个 `static` 域不是 `final` 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间),就像在对 `Initable2.staticNonFinal` 的访问中所看到的那样。 +如果一个 `static` 字段不是 `final` 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间),就像在对 `Initable2.staticNonFinal` 的访问中所看到的那样。 ### 泛化的 `Class` 引用 @@ -1667,9 +1667,9 @@ Bob Smith Bob Smith 11 Degree Lane, Frostbite Falls, MN ``` -`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有域都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的域重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 域在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。 +`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 字段在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。 -如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 域,这样就不会意外触发 `NullPointException` 了。 +如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 字段,这样就不会意外触发 `NullPointException` 了。 现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 `Person Optional` 对象放在每个 `Position` 上: @@ -1731,7 +1731,7 @@ Position: Programmer, Employee: Arthur Fonzarelli caught EmptyTitleException ``` -这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通域,不受 `Optional` 的保护。但是,修改这些域的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对域进行了严格的限制。 +这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通字段,不受 `Optional` 的保护。但是,修改这些字段的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对字段进行了严格的限制。 我们想保证 `title` 不会有 `null` 值, @@ -2000,7 +2000,7 @@ protected C.v() private C.w() ``` -看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于域来说也是这样,即便是 `private` 域: +看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于字段来说也是这样,即便是 `private` 字段: ```java // typeinfo/ModifyingPrivateFields.java @@ -2052,7 +2052,7 @@ f.get(pf): Am I safe? i = 47, I'm totally safe, No, you're not! ``` -但实际上 `final` 域在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。 +但实际上 `final` 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。 通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,发射给我们带来了很多好处。 From 12b6a31ba845e18ad0b3ad3995553e372c2ef3a4 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 9 Nov 2019 16:56:28 +0800 Subject: [PATCH 070/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0022025f..667f37fc 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ - [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) -- [ ] [第十九章 类型信息](docs/book/19-Type-Information.md) +- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) - [ ] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) From 6ef070e69dbac456a5a155dfd0a0ff5ba3cd6bd2 Mon Sep 17 00:00:00 2001 From: Moilk Date: Sat, 9 Nov 2019 18:00:15 +0800 Subject: [PATCH 071/371] =?UTF-8?q?=E7=AC=AC19=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=A1=E6=81=AF=20=E7=BF=BB=E8=AF=91=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 269 ++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 3 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 6e171a44..83e75901 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1617,6 +1617,7 @@ boring3 // Using Optional with regular classes import onjava.*; import java.util.*; + class Person { public final Optional first; public final Optional last; @@ -1676,6 +1677,7 @@ Bob Smith 11 Degree Lane, Frostbite Falls, MN ```java // typeinfo/Position.java import java.util.*; + class EmptyTitleException extends RuntimeException { } class Position { @@ -1733,9 +1735,270 @@ caught EmptyTitleException 这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通字段,不受 `Optional` 的保护。但是,修改这些字段的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对字段进行了严格的限制。 -我们想保证 `title` 不会有 `null` 值, +同时,我们想保证 `title` 字段永远不会变成 `null` 值。为此,我们可以自己在 `setTitle()` 方法里边检查参数 `newTitle` 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 `ofNullable()` 把 `newTitle` 转换一个 `Optional`(如果传入的值为 `null`,`ofNullable()` 返回的将是 `Optional.empty()`)。紧接着我们调用了 `orElseThrow()` 方法,所以如果 `newTitle` 的值是 `null`,你将会得到一个异常。这里我们并没有把 `title` 保存成 `Optional`,但通过利 `Optional` 的功能,我们仍然如愿以偿的对这个字段施加了约束。 + +`EmptyTitleException` 是一个 `RuntimeException`,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 `setTitle()` 传 `null` 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 `EmptyTitleException` 还有助于定位 BUG。 + +`Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Option`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。 + +在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段如果是空 `Person` 对象就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显示的表示“空位”的方法,但是 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)。 + +请注意,虽然你清楚你使用了 `Optional`,可以免受 `NullPointerExceptions` 的困扰,但是 `Staff` 类却对此毫不知情。 + +```java +// typeinfo/Staff.java +import java.util.*; + +public class Staff extends ArrayList { + public void add(String title, Person person) { + add(new Position(title, person)); + } + public void add(String... titles) { + for (String title : titles) + add(new Position(title)); + } + public Staff(String... titles) { + add(titles); + } + public Boolean positionAvailable(String title) { + for (Position position : this) + if(position.getTitle().equals(title) && + position.getPerson().empty) + return true; + return false; + } + public void fillPosition(String title, Person hire) { + for (Position position : this) + if(position.getTitle().equals(title) && + position.getPerson().empty) { + position.setPerson(hire); + return; + } + throw new RuntimeException( + "Position " + title + " not available"); + } + public static void main(String[] args) { + Staff staff = new Staff("President", "CTO", + "Marketing Manager", "Product Manager", + "Project Lead", "Software Engineer", + "Software Engineer", "Software Engineer", + "Software Engineer", "Test Engineer", + "Technical Writer"); + staff.fillPosition("President", + new Person("Me", "Last", "The Top, Lonely At")); + staff.fillPosition("Project Lead", + new Person("Janet", "Planner", "The Burbs")); + if(staff.positionAvailable("Software Engineer")) + staff.fillPosition("Software Engineer", + new Person( + "Bob", "Coder", "Bright Light City")); + System.out.println(staff); + } +} +``` + +输出结果: + +``` +[Position: President, Employee: Me Last The Top, Lonely +At, Position: CTO, Employee: , Position: +Marketing Manager, Employee: , Position: Product +Manager, Employee: , Position: Project Lead, +Employee: Janet Planner The Burbs, Position: Software +Engineer, Employee: Bob Coder Bright Light City, +Position: Software Engineer, Employee: , +Position: Software Engineer, Employee: , +Position: Software Engineer, Employee: , +Position: Test Engineer, Employee: , Position: +Technical Writer, Employee: ] +``` + +注意,在有些地方你可能还是要测试引用是不是 `Optional`,这跟检查是否为 `null` 没什么不同。但是在其它地方(例如本例中的 `toString()` 转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。 + +### 标记接口 + +有时候使用一个**标记接口**来表示空值会更方便。标记接口里边什么都没有,你只要把它的名字当做标签来用就可以。 + +```java +// onjava/Null.java +package onjava; +public interface Null {} +``` + +如果你用接口取代具体类,那么就可以使用 `DynamicProxy` 来自动地创建 `Null` 对象。假设我们有一个 `Robot` 接口,它定义了一个名字、一个模型和一个描述 `Robot` 行为能力的 `List`: + +```java +// typeinfo/Robot.java +import onjava.*; +import java.util.*; + +public interface Robot { + String name(); + String model(); + List operations(); + static void test(Robot r) { + if(r instanceof Null) + System.out.println("[Null Robot]"); + System.out.println("Robot name: " + r.name()); + System.out.println("Robot model: " + r.model()); + for (Operation operation : r.operations()) { + System.out.println(operation.description.get()); + operation.command.run(); + } + } +} +``` + +你可以通过调用 `operations()` 来访问 `Robot` 的服务。`Robot` 里边还有一个 `static` 方法来执行测试。 + +`Operation` 包含一个描述和一个命令(这用到了**命令模式**)。它们被定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 `Operation` 的构造器: + +```java +// typeinfo/Operation.java +import java.util.function.*; + +public class Operation { + public final Supplier description; + public final Runnable command; + public + Operation(Supplier descr, Runnable cmd) { + description = descr; + command = cmd; + } +} +``` + +现在我们可以创建一个扫雪 `Robot`: + +```java +// typeinfo/SnowRemovalRobot.java +import java.util.*; + +public class SnowRemovalRobot implements Robot { + private String name; + public SnowRemovalRobot(String name) { + this.name = name; + } + @Override + public String name() { + return name; + } + @Override + public String model() { + return "SnowBot Series 11"; + } + private List ops = Arrays.asList( + new Operation( + () -> name + " can shovel snow", + () -> System.out.println( + name + " shoveling snow")), + new Operation( + () -> name + " can chip ice", + () -> System.out.println(name + " chipping ice")), + new Operation( + () -> name + " can clear the roof", + () -> System.out.println( + name + " clearing roof"))); + public List operations() { + return ops; + } + public static void main(String[] args) { + Robot.test(new SnowRemovalRobot("Slusher")); + } +} +``` + +输出结果: + +``` +Robot name: Slusher +Robot model: SnowBot Series 11 +Slusher can shovel snow +Slusher shoveling snow +Slusher can chip ice +Slusher chipping ice +Slusher can clear the roof +Slusher clearing roof +``` + +假设存在许多不同类型的 `Robot`,我们想让每种 `Robot` 都创建一个 `Null` 对象来执行一些特殊的操作——在本例中,即提供 `Null` 对象所代表 `Robot` 的确切类型信息。这些信息是通过动态代理捕获的: + +```java +// typeinfo/NullRobot.java +// Using a dynamic proxy to create an Optional +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.*; +import onjava.*; + +class NullRobotProxyHandler +implements InvocationHandler { + private String nullName; + private Robot proxied = new NRobot(); + NullRobotProxyHandler(Class type) { + nullName = type.getSimpleName() + " NullRobot"; + } + private class NRobot implements Null, Robot { + @Override + public String name() { + return nullName; + } + @Override + public String model() { + return nullName; + } + @Override + public List operations() { + return Collections.emptyList(); + } + } + @Override + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { + return method.invoke(proxied, args); + } +} +public class NullRobot { + public static Robot + newNullRobot(Class type) { + return (Robot)Proxy.newProxyInstance( + NullRobot.class.getClassLoader(), + new Class, + new NullRobotProxyHandler(type)); + } + public static void main(String[] args) { + Stream.of( + new SnowRemovalRobot("SnowBee"), + newNullRobot(SnowRemovalRobot.class) + ).forEach(Robot::test); + } +} +``` + +输出结果: + +``` +Robot name: SnowBee +Robot model: SnowBot Series 11 +SnowBee can shovel snow +SnowBee shoveling snow +SnowBee can chip ice +SnowBee chipping ice +SnowBee can clear the roof +SnowBee clearing roof +[Null Robot] +Robot name: SnowRemovalRobot NullRobot +Robot model: SnowRemovalRobot NullRobot +``` + +无论何时,如果你需要一个空 `Robot` 对象,只需要调用 `newNullRobot()`,并传递需要代理的 `Robot` 的类型。这个代理满足了 `Robot` 和 `Null` 接口的需要,并提供了它所代理的类型的确切名字。 + +### Mock 对象和桩 + +**Mock 对象**和 **桩(Stub)**在逻辑上都是 `Optional` 的变体。他们都是最终程序中所使用的“实际”对象的代理。不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 `Optional` 那样把包含潜在 `null` 值的对象隐藏。 - +Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。 ## 接口和类型 @@ -2073,7 +2336,7 @@ RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用 [^1]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。 -[^2]: 极限编程(XP,Extreme Programming)的一条宗旨:“Try the simplest thing that could possibly work,实现尽最大可能的简单。” +[^2]: 这是极限编程(XP,Extreme Programming)的原则之一:“Try the simplest thing that could possibly work,实现尽最大可能的简单。” [^3]: 最著名的例子是 Windows 操作系统,Windows 为开发者提供了公开的 API,但是开发者还可以找到一些非公开但是可以调用的函数。为了解决问题,很多程序员使用了隐藏的 API 函数。这就迫使微软公司要像维护公开 API 一样维护这些隐藏的 API,消耗了巨大的成本和精力。 From 5df4e604a9c8ce32cbb0fc1b0f985ae63a7979ba Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 9 Nov 2019 18:36:56 +0800 Subject: [PATCH 072/371] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=99=84=E5=BD=95?= =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...endix-Understanding-equals-and-hashCode.md | 615 +++++++++++++++++- 1 file changed, 613 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Understanding-equals-and-hashCode.md b/docs/book/Appendix-Understanding-equals-and-hashCode.md index e2bd8c32..71fa0c26 100644 --- a/docs/book/Appendix-Understanding-equals-and-hashCode.md +++ b/docs/book/Appendix-Understanding-equals-and-hashCode.md @@ -388,16 +388,627 @@ Pig2[1]: Ralph MEDIUM a752aeee 注意 **hashCode()** 是独一无二的,但是因为对象不再 **equals()** ,所以两个函数都出现在**HashSet**中。另外,**super.equals()** 意味着我们不需要访问基类的**private**字段。 -一种说法是Java从**equals()** 和**hashCode()** 的定义中分离了可替代性。我们仍然能够将**Dog**和**Pig**放置在 **Set** 中,无论 **equals()** 和 **hashCode()** 是如何定义的,但是对象不会在哈希数据结构中正常工作,除非这些函数能够被合理定义。不幸的是,**equals()** 不总是和 **hashCode()** 一起使用,这在你尝试为了某个特殊类型避免定义它的时候会让问题复杂化。并且这也是为什么遵循规范是有价值的。然而这会变得更加复杂,因为你不总是需要定义其中一个函数。 +一种说法是Java从**equals()** 和**hashCode()** 的定义中分离了可替代性。我们仍然能够将**Dog**和**Pig**放置在 **Set\** 中,无论 **equals()** 和 **hashCode()** 是如何定义的,但是对象不会在哈希数据结构中正常工作,除非这些函数能够被合理定义。不幸的是,**equals()** 不总是和 **hashCode()** 一起使用,这在你尝试为了某个特殊类型避免定义它的时候会让问题复杂化。并且这也是为什么遵循规范是有价值的。然而这会变得更加复杂,因为你不总是需要定义其中一个函数。 + ## 哈希和哈希码 +在 [集合]() 章节中,我们使用预先定义的类作为 HashMap 的键。这些示例之所以有用,是因为预定义的类具有所有必需的连线,以使它们正确地充当键。 + +当创建自己的类作为HashMap的键时,会发生一个常见的陷阱,从而忘记进行必要的接线。例如,考虑一个将Earthhog 对象与 Prediction 对象匹配的天气预报系统。这似乎很简单:使用Groundhog作为键,使用Prediction作为值: + +```java +// equalshashcode/Groundhog.java +// Looks plausible, but doesn't work as a HashMap key +public class Groundhog { + protected int number; + public Groundhog(int n) { number = n; } + @Override + public String toString() { + return "Groundhog #" + number; + } +} +``` + +```java +// equalshashcode/Prediction.java +// Predicting the weather +import java.util.*; +public class Prediction { + private static Random rand = new Random(47); + @Override + public String toString() { + return rand.nextBoolean() ? + "Six more weeks of Winter!" : "Early Spring!"; + } +} +``` + +```java +// equalshashcode/SpringDetector.java +// What will the weather be? +import java.util.*; +import java.util.stream.*; +import java.util.function.*; +import java.lang.reflect.*; +public class SpringDetector { + public static + void detectSpring(Class type) { + try { + Constructor ghog = + type.getConstructor(int.class); + Map map = + IntStream.range(0, 10) + .mapToObj(i -> { + try { + return ghog.newInstance(i); + } catch(Exception e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toMap( + Function.identity(), + gh -> new Prediction())); + map.forEach((k, v) -> + System.out.println(k + ": " + v)); + Groundhog gh = ghog.newInstance(3); + System.out.println( + "Looking up prediction for " + gh); + if(map.containsKey(gh)) + System.out.println(map.get(gh)); + else + System.out.println("Key not found: " + gh); + } catch(NoSuchMethodException | + IllegalAccessException | + InvocationTargetException | + InstantiationException e) { + throw new RuntimeException(e); + } + } + public static void main(String[] args) { + detectSpring(Groundhog.class); + } +} +/* Output: +Groundhog #3: Six more weeks of Winter! +Groundhog #0: Early Spring! +Groundhog #8: Six more weeks of Winter! +Groundhog #6: Early Spring! +Groundhog #4: Early Spring! +Groundhog #2: Six more weeks of Winter! +Groundhog #1: Early Spring! +Groundhog #9: Early Spring! +Groundhog #5: Six more weeks of Winter! +Groundhog #7: Six more weeks of Winter! +Looking up prediction for Groundhog #3 +Key not found: Groundhog #3 +*/ +``` + +每个 Groundhog 都被赋予了一个常数,因此你可以通过如下的方式在 HashMap 中寻找对应的 Prediction。“给我一个和 Groundhog#3 相关联的 Prediction”。而 Prediction 通过一个随机生成的 boolean 来选择天气。`detectSpring()` 方法通过反射来实例化 Groundhog 类,或者它的子类。稍后,当我们继承一种新型的“Groundhog ”以解决此处演示的问题时,这将派上用场。 + +这里的 HashMap 被 Groundhog 和其相关联的 Prediction 充满。并且上面展示了 HashMap 里面填充的内容。接下来我们使用填充了常数 3 的 Groundhog 作为 key 用于寻找对应的 Prediction 。(这个键值对肯定在 Map 中)。 + +这看起来十分简单,但是这样做并没有奏效 —— 它无法找到数字3这个键。问题出在Groundhog自动地继承自基类Object,所以这里使用Object的hashCode0方法生成散列码,而它默认是使用对象的地址计算散列码。因此,由Groundhog(3)生成的第一个实例的散列码与由Groundhog(3)生成的第二个实例的散列码是不同的,而我们正是使用后者进行查找的。 + +我们需要恰当的重写hashCode()方法。但是它仍然无法正常运行,除非你同时重写 equals()方法,它也是Object的一部分。HashMap使用equals()判断当前的键是否与表中存在的键相同。 + +这是因为默认的Object.equals()只是比较对象的地址,所以一个Groundhog(3)并不等于另一个Groundhog(3),因此,如果要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals(),如下所示: + +```java +// equalshashcode/Groundhog2.java +// A class that's used as a key in a HashMap +// must override hashCode() and equals() +import java.util.*; +public class Groundhog2 extends Groundhog { + public Groundhog2(int n) { super(n); } + @Override + public int hashCode() { return number; } + @Override + public boolean equals(Object o) { + return o instanceof Groundhog2 && + Objects.equals( + number, ((Groundhog2)o).number); + } +} +``` + +```java +// equalshashcode/SpringDetector2.java +// A working key +public class SpringDetector2 { + public static void main(String[] args) { + SpringDetector.detectSpring(Groundhog2.class); + } +} +/* Output: +Groundhog #0: Six more weeks of Winter! +Groundhog #1: Early Spring! +Groundhog #2: Six more weeks of Winter! +Groundhog #3: Early Spring! +Groundhog #4: Early Spring! +Groundhog #5: Six more weeks of Winter! +Groundhog #6: Early Spring! +Groundhog #7: Early Spring! +Groundhog #8: Six more weeks of Winter! +Groundhog #9: Six more weeks of Winter! +Looking up prediction for Groundhog #3 +Early Spring! +*/ +``` + +Groundhog2.hashCode0返回Groundhog的标识数字(编号)作为散列码。在此例中,程序员负责确保不同的Groundhog具有不同的编号。hashCode()并不需要总是能够返回唯一的标识码(稍后你会理解其原因),但是equals() 方法必须严格地判断两个对象是否相同。此处的equals()是判断Groundhog的号码,所以作为HashMap中的键,如果两个Groundhog2对象具有相同的Groundhog编号,程序就出错了。 + +如何定义 equals() 方法在上一节 [equals 规范]()中提到了。输出表明我们现在的输出是正确的。 + +### 理解 hashCode + +前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖hashCode() 和equals() ,那么使用散列的数据结构(HashSet,HashMap,LinkedHashst或LinkedHashMap)就无法正确处理你的键。然而,要很好地解决此问题,你必须了解这些数据结构的内部构造。 + +首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者你自己实现的Map也可以达到此目的。与散列实现相反,下面的示例用一对ArrayLists实现了一个Map,与AssociativeArray.java不同,这其中包含了Map接口的完整实现,因此提供了entrySet()方法: + +```java +// equalshashcode/SlowMap.java +// A Map implemented with ArrayLists +import java.util.*; +import onjava.*; +public class SlowMap extends AbstractMap { + private List keys = new ArrayList<>(); + private List values = new ArrayList<>(); + @Override + public V put(K key, V value) { + V oldValue = get(key); // The old value or null + if(!keys.contains(key)) { + keys.add(key); + values.add(value); + } else + values.set(keys.indexOf(key), value); + return oldValue; + } + @Override + public V get(Object key) { // key: type Object, not K + if(!keys.contains(key)) + return null; + return values.get(keys.indexOf(key)); + } + @Override + public Set> entrySet() { + Set> set= new HashSet<>(); + Iterator ki = keys.iterator(); + Iterator vi = values.iterator(); + while(ki.hasNext()) + set.add(new MapEntry<>(ki.next(), vi.next())); + return set; + } + public static void main(String[] args) { + SlowMap m= new SlowMap<>(); + m.putAll(Countries.capitals(8)); + m.forEach((k, v) -> + System.out.println(k + "=" + v)); + System.out.println(m.get("BENIN")); + m.entrySet().forEach(System.out::println); + } +} +/* Output: +CAMEROON=Yaounde +ANGOLA=Luanda +BURKINA FASO=Ouagadougou +BURUNDI=Bujumbura +ALGERIA=Algiers +BENIN=Porto-Novo +CAPE VERDE=Praia +BOTSWANA=Gaberone +Porto-Novo +CAMEROON=Yaounde +ANGOLA=Luanda +BURKINA FASO=Ouagadougou +BURUNDI=Bujumbura +ALGERIA=Algiers +BENIN=Porto-Novo +CAPE VERDE=Praia +BOTSWANA=Gaberone +*/ +``` + +put()方法只是将键与值放入相应的ArrayList。为了与Map接口保持一致,它必须返回旧的键,或者在没有任何旧键的情况下返回null。 + +同样遵循了Map规范,get()会在键不在SlowMap中的时候产生null。如果键存在,它将被用来查找表示它在keys列表中的位置的数值型索引,并且这个数字被用作索引来产生与values列表相关联的值。注意,在get()中key的类型是Object,而不是你所期望的参数化类型K(并且是在AssociativeArrayjava中真正使用的类型),这是将泛型注入到Java语言中的时刻如此之晚所导致的结果-如果泛型是Java语言最初就具备的属性,那么get()就可以执行其参数的类型。 + +Map.entrySet() 方法必须产生一个Map.Entry对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型,就必须同时定义Map.Entry的实现: + +```java +// equalshashcode/MapEntry.java +// A simple Map.Entry for sample Map implementations +import java.util.*; +public class MapEntry implements Map.Entry { + private K key; + private V value; + public MapEntry(K key, V value) { + this.key = key; + this.value = value; + } + @Override + public K getKey() { return key; } + @Override + public V getValue() { return value; } + @Override + public V setValue(V v) { + V result = value; + value = v; + return result; + } + @Override + public int hashCode() { + return Objects.hash(key, value); + } + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object rval) { + return rval instanceof MapEntry && + Objects.equals(key, + ((MapEntry)rval).getKey()) && + Objects.equals(value, + ((MapEntry)rval).getValue()); + } + @Override + public String toString() { + return key + "=" + value; + } +} +``` + +这里 equals 方法的实现遵循了[equals 规范]()。在 Objects 类中有一个非常熟悉的方法可以帮助创建 hashCode() 方法: Objects.hash()。当你定义含有超过一个属性的对象的 `hashCode()` 时,你可以使用这个方法。如果你的对象只有一个属性,可以直接使用 ` Objects.hashCode()`。 + +尽管这个解决方案非常简单,并且看起来在SlowMap.main() 的琐碎测试中可以正常工作,但是这并不是一个恰当的实现,因为它创建了键和值的副本。entrySet() 的恰当实现应该在Map中提供视图,而不是副本,并且这个视图允许对原始映射表进行修改(副本就不行)。 + +### 为了速度而散列 + +SlowMap.java 说明了创建一种新的Map并不困难。但是正如它的名称SlowMap所示,它不会很快,所以如果有更好的选择,就应该放弃它。它的问题在于对键的查询,键没有按照任何特定顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。 + +散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。 + +散列则更进一步,它将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(请小心留意,我是说键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有一个问题:我们希望在Map中保存数量不确定的值,但是如果键的数量被数组的容量限制了,该怎么办呢? + +答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(在计算机科学的术语中称为散列函数)生成。 + +于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。 + +理解了散列的原理,我们就能够实现一个简单的散列Map了: + +```java +// equalshashcode/SimpleHashMap.java +// A demonstration hashed Map +import java.util.*; +import onjava.*; +public +class SimpleHashMap extends AbstractMap { + // Choose a prime number for the hash table +// size, to achieve a uniform distribution: + static final int SIZE = 997; + // You can't have a physical array of generics, +// but you can upcast to one: + @SuppressWarnings("unchecked") + LinkedList>[] buckets = + new LinkedList[SIZE]; + @Override + public V put(K key, V value) { + V oldValue = null; + int index = Math.abs(key.hashCode()) % SIZE; + if(buckets[index] == null) + buckets[index] = new LinkedList<>(); + LinkedList> bucket = buckets[index]; + MapEntry pair = new MapEntry<>(key, value); + boolean found = false; + ListIterator> it = + bucket.listIterator(); + while(it.hasNext()) { + MapEntry iPair = it.next(); + if(iPair.getKey().equals(key)) { + oldValue = iPair.getValue(); + it.set(pair); // Replace old with new + found = true; + break; + } + } + if(!found) + buckets[index].add(pair); + return oldValue; + } + @Override + public V get(Object key) { + int index = Math.abs(key.hashCode()) % SIZE; + if(buckets[index] == null) return null; + for(MapEntry iPair : buckets[index]) + if(iPair.getKey().equals(key)) + return iPair.getValue(); + return null; + } + @Override + public Set> entrySet() { + Set> set= new HashSet<>(); + for(LinkedList> bucket : buckets) { + if(bucket == null) continue; + for(MapEntry mpair : bucket) + set.add(mpair); + } + return set; + } + public static void main(String[] args) { + SimpleHashMap m = + new SimpleHashMap<>(); + m.putAll(Countries.capitals(8)); + m.forEach((k, v) -> + System.out.println(k + "=" + v)); + System.out.println(m.get("BENIN")); + m.entrySet().forEach(System.out::println); + } +} +/* Output: +CAMEROON=Yaounde +ANGOLA=Luanda +BURKINA FASO=Ouagadougou +BURUNDI=Bujumbura +ALGERIA=Algiers +BENIN=Porto-Novo +CAPE VERDE=Praia +BOTSWANA=Gaberone +Porto-Novo +CAMEROON=Yaounde +ANGOLA=Luanda +BURKINA FASO=Ouagadougou +BURUNDI=Bujumbura +ALGERIA=Algiers +BENIN=Porto-Novo +CAPE VERDE=Praia +BOTSWANA=Gaberone +*/ +``` + +由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为bucket,为使散列分布均匀,桶的数量通常使用质数[^2]。注意,为了能够自动处理冲突,使用了一个LinkedList的数组;每一个新的元素只是直接添加到list尾的某个特定桶位中。即使Java不允许你创建泛型数组,那你也可以创建指向这种数组的引用。这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行额外的转型。 + +对于put() 方法,hashCode() 将针对键而被调用,并且其结果被强制转换为正数。为了使产生的数字适合bucket数组的大小,取模操作符将按照该数组的尺寸取模。如果数组的某个位置是 null,这表示还没有元素被散列至此,所以,为了保存刚散列到该定位的对象,需要创建一个新的LinkedList。一般的过程是,查看当前位置的ist中是否有相同的元素,如果有,则将旧的值赋给oldValue,然后用新的值取代旧的值。标记found用来跟踪是否找到(相同的)旧的键值对,如果没有,则将新的对添加到list的末尾。 + +get()方法按照与put()方法相同的方式计算在buckets数组中的索引(这很重要,因为这样可以保证两个方法可以计算出相同的位置)如果此位置有LinkedList存在,就对其进行查询。 + +注意,这个实现并不意味着对性能进行了调优,它只是想要展示散列映射表执行的各种操作。如果你浏览一下java.util.HashMap的源代码,你就会看到一个调过优的实现。同样,为了简单,SimpleHashMap使用了与SlowMap相同的方式来实现entrySet(),这个方法有些过于简单,不能用于通用的Map。 + +### 重写 hashCode() + +在明白了如何散列之后,编写自己的hashCode()就更有意义了。 + +首先,你无法控制bucket数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子(本章稍后会介绍这个术语)有关。hashCode()生成的结果,经过处理后成为桶位的下标(在SimpleHashMap中,只是对其取模,模数为bucket数组的大小)。 + +设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。 + +此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this值,这只能产生很糟糕的hashCode(),因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。这正是SpringDetector.java的问题所在,因为它默认的hashCode0使用的是对象的地址。所以,应该使用对象内有意义的识别信息。 + +下面以String类为例。String有个特点:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。所以new String("hello")生成的两个实例,虽然是相互独立的,但是对它们使用hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况: + +```java +// equalshashcode/StringHashCode.java +public class StringHashCode { + public static void main(String[] args) { + String[] hellos = "Hello Hello".split(" "); + System.out.println(hellos[0].hashCode()); + System.out.println(hellos[1].hashCode()); + } +} +/* Output: +69609650 +69609650 +*/ +``` + +对于String而言,hashCode() 明显是基于String的内容的。 + +因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯E),但是通过hashCode() 和equals() ,必须能够完全确定对象的身份。 + +因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int即可。 + +还有另一个影响因素:好的hashCode() 应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。 + +在Effective Java Programming Language Guide(Addison-Wesley 2001)这本书中,Joshua Bloch为怎样写出一份像样的hashCode()给出了基本的指导: + +1. 给int变量result赋予某个非零值常量,例如17。 +2. 为对象内每个有意义的字段(即每个可以做equals)操作的字段计算出一个int散列码c: + +| 字段类型 | 计算公式 | +| ------------------------------------------------------ | ------------------------------------------------------------ | +| boolean | c = (f ? 0 : 1) | +| byte , char , short , or int | c = (int)f | +| long | c = (int)(f ^ (f>>>32)) | +| float | c = Float.floatToIntBits(f); | +| double | long l =Double.doubleToLongBits(f);
c = (int)(l ^ (l >>> 32)) | +| Object , where equals() calls equals() for this field | c = f.hashCode() | +| Array | 应用以上规则到每一个元素中 | + +3. 合并计算得到的散列码: **result = 37 * result + c;​** +4. 返回 result。 +5. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。 + +下面便是遵循这些指导的一个例子。提示,你没有必要书写像如下的代码 —— 相反,使用 `Objects.hash()` 去用于散列多字段的对象(如同在本例中的那样),然后使用 `Objects.hashCode()` 如散列单字段的对象。 + +```java +// equalshashcode/CountedString.java +// Creating a good hashCode() +import java.util.*; +public class CountedString { + private static List created = + new ArrayList<>(); + private String s; + private int id = 0; + public CountedString(String str) { + s = str; + created.add(s); +// id is the total number of instances +// of this String used by CountedString: + for(String s2 : created) + if(s2.equals(s)) + id++; + } + @Override + public String toString() { + return "String: " + s + " id: " + id + + " hashCode(): " + hashCode(); + } + @Override + public int hashCode() { +// The very simple approach: +// return s.hashCode() * id; +// Using Joshua Bloch's recipe: + int result = 17; + result = 37 * result + s.hashCode(); + result = 37 * result + id; + return result; + } + @Override + public boolean equals(Object o) { + return o instanceof CountedString && + Objects.equals(s, ((CountedString)o).s) && + Objects.equals(id, ((CountedString)o).id); + } + public static void main(String[] args) { + Map map = new HashMap<>(); + CountedString[] cs = new CountedString[5]; + for(int i = 0; i < cs.length; i++) { + cs[i] = new CountedString("hi"); + map.put(cs[i], i); // Autobox int to Integer + } + System.out.println(map); + for(CountedString cstring : cs) { + System.out.println("Looking up " + cstring); + System.out.println(map.get(cstring)); + } + } +} +/* Output: +{String: hi id: 4 hashCode(): 146450=3, String: hi id: +5 hashCode(): 146451=4, String: hi id: 2 hashCode(): +146448=1, String: hi id: 3 hashCode(): 146449=2, +String: hi id: 1 hashCode(): 146447=0} +Looking up String: hi id: 1 hashCode(): 146447 +0 +Looking up String: hi id: 2 hashCode(): 146448 +1 +Looking up String: hi id: 3 hashCode(): 146449 +2 +Looking up String: hi id: 4 hashCode(): 146450 +3 +Looking up String: hi id: 5 hashCode(): 146451 +4 +*/ +``` + +CountedString由一个String和一个id组成,此id代表包含相同String的CountedString对象的编号。所有的String都被存储在static ArrayList中,在构造器中通过选代遍历此ArrayList完成对id的计算。 + +hashCode()和equals() 都基于CountedString的这两个字段来生成结果;如果它们只基于String或者只基于id,不同的对象就可能产生相同的值。 + +在main)中,使用相同的String创建了多个CountedString对象。这说明,虽然String相同,但是由于id不同,所以使得它们的散列码并不相同。在程序中,HashMap被打印了出来,因此可以看到它内部是如何存储元素的(以无法辨别的次序),然后单独查询每一个键,以此证明查询机制工作正常。 + +作为第二个示例,请考虑Individual类,它被用作[类型信息]()中所定义的typeinfo.pet类库的基类。Individual类在那一章中就用到了,而它的定义则放到了本章,因此你可以正确地理解其实现。 + +在这里替换了手工去计算 `hashCode()`,我们使用了更合适的方式 ` Objects.hash() `: + +```java +// typeinfo/pets/Individual.java +package typeinfo.pets; +import java.util.*; +public class +Individual implements Comparable { + private static long counter = 0; + private final long id = counter++; + private String name; + public Individual(String name) { this.name = name; } + // 'name' is optional: + public Individual() {} + @Override + public String toString() { + return getClass().getSimpleName() + + (name == null ? "" : " " + name); + } + public long id() { return id; } + @Override + public boolean equals(Object o) { + return o instanceof Individual && + Objects.equals(id, ((Individual)o).id); + } + @Override + public int hashCode() { + return Objects.hash(name, id); + } + @Override + public int compareTo(Individual arg) { + // Compare by class name first: + String first = getClass().getSimpleName(); + String argFirst = arg.getClass().getSimpleName(); + int firstCompare = first.compareTo(argFirst); + if(firstCompare != 0) + return firstCompare; + if(name != null && arg.name != null) { + int secondCompare = name.compareTo(arg.name); + if(secondCompare != 0) + return secondCompare; + } + return (arg.id < id ? -1 : (arg.id == id ? 0 : 1)); + } +} +``` + +compareTo() 方法有一个比较结构,因此它会产生一个排序序列,排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后按照创建的顺序排序。下面的示例说明了它是如何工作的: + +```java +// equalshashcode/IndividualTest.java +import collections.MapOfList; +import typeinfo.pets.*; +import java.util.*; +public class IndividualTest { + public static void main(String[] args) { + Set pets = new TreeSet<>(); + for(List lp : + MapOfList.petPeople.values()) + for(Pet p : lp) + pets.add(p); + pets.forEach(System.out::println); + } +} +/* Output: +Cat Elsie May +Cat Pinkola +Cat Shackleton +Cat Stanford +Cymric Molly +Dog Margrett +Mutt Spot +Pug Louie aka Louis Snorkelstein Dupree +Rat Fizzy +Rat Freckly +Rat Fuzzy +*/ +``` + +由于所有的宠物都有名字,因此它们首先按照类型排序,然后在同类型中按照名字排序。 -## 调整HashMap + +## 调优 HashMap + +我们有可能手动调优HashMap以提高其在特定应用程序中的性能。为了理解调整HashMap时的性能问题,一些术语是必要的: + +- 容量(Capacity):表中存储的桶数量。 +- 初试容量(Initial Capacity):当表被创建时,桶的初始个数。 HashMap 和 HashSet 有可以让你指定初始容量的构造器。 +- 个数(Size):目前存储在表中的键值对的个数。 +- 负载因子(Load factor):通常表现为 $\frac{size}{capacity}$。当负载因子大小为 0 的时候表示为一个空表。当负载因子大小为 0.5 表示为一个半满表(half-full table),以此类推。轻负载的表几乎没有冲突,因此是插入和查找的最佳选择(但会减慢使用迭代器进行遍历的过程)。 HashMap 和 HashSet 有可以让你指定负载因子的构造器。当表内容量达到了负载因子,集合就会自动扩充为原始容量(桶的数量)的两倍,并且会将原始的对象存储在新的桶集合中(也被称为 rehashing) + +HashMap 中负载因子的大小为 0.75(当表内容量大小不足四分之三的时候,不会发生 rehashing 现象)。这看起来是一个非常好的同时考虑到时间和空间消耗的平衡策略。更高的负载因子会减少空间的消耗,但是会增加查询的耗时。重要的是,查询操作是你使用的最频繁的一个操作(包括 `get()` 和 `put()` 方法)。 + +如果你知道存储在 HashMap 中确切的条目个数,直接创建一个足够容量大小的 HashMap,以避免自动发生的 rehashing 操作。 + +[^1]: +[^2]: 事实证明,质数实际上并不是散列桶的理想容量。近来,(经过广泛的测试)Java的散列函数都使用2的整数次方。对现代的处理器来说,除法与求余数是最慢的操作。使用2的整数次方长度的散列表,可用掩码代替除法。 +
From 99c1fee9efef78aa274dba39ca8992691e8a0725 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 9 Nov 2019 18:38:12 +0800 Subject: [PATCH 073/371] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 667f37fc..9b51600c 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ - [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) -- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) +- [] [第十九章 类型信息](docs/book/19-Type-Information.md) - [ ] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) @@ -52,7 +52,7 @@ - [ ] [附录:流式IO](docs/book/Appendix-IO-Streams.md) - [ ] [附录:标准IO](docs/book/Appendix-Standard-IO.md) - [x] [附录:新IO](docs/book/Appendix-New-IO.md) -- [ ] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) +- [x] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) - [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md) - [x] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md) - [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md) From dc7695dfa0d04340f2b4f05c086ad230c0899390 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 9 Nov 2019 18:38:27 +0800 Subject: [PATCH 074/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b51600c..8b99e573 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ - [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) -- [] [第十九章 类型信息](docs/book/19-Type-Information.md) +- [ ] [第十九章 类型信息](docs/book/19-Type-Information.md) - [ ] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) From 21ba10d296784435a677b6e18eb32349236341fb Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Sun, 10 Nov 2019 12:36:38 +0800 Subject: [PATCH 075/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E7=AB=A0=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20=20=E7=BF=BB=E8=AF=91=E6=9B=B4=E6=96=B0=20?= =?UTF-8?q?=E8=A1=A5=E5=81=BF=E6=93=A6=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 526 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index da1a4d3d..938b5f9f 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1233,6 +1233,532 @@ public class Store extends ArrayList { ## 补偿擦除 +因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型: + +```java +// generics/Erased.java +// {WillNotCompile} + +public class Erased { + private final int SIZE = 100; + + public void f(Object arg) { + + // error: illegal generic type for instanceof + if (arg instanceof T) { + } + + // error: unexpected type + T var = new T(); + + // error: generic array creation + T[] array = new T[SIZE]; + + // warning: [unchecked] unchecked cast + T[] array = (T[]) new Object[SIZE]; + + } +} +``` + +有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 **Class** 对象,以在类型表达式中使用它。 + +例如,由于删除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` : + +```java +// generics/ClassTypeCapture.java + +class Building { +} + +class House extends Building { +} + +public class ClassTypeCapture { + Class kind; + + public ClassTypeCapture(Class kind) { + this.kind = kind; + } + + public boolean f(Object arg) { + return kind.isInstance(arg); + } + + public static void main(String[] args) { + ClassTypeCapture ctt1 = + new ClassTypeCapture<>(Building.class); + System.out.println(ctt1.f(new Building())); + System.out.println(ctt1.f(new House())); + ClassTypeCapture ctt2 = + new ClassTypeCapture<>(House.class); + System.out.println(ctt2.f(new Building())); + System.out.println(ctt2.f(new House())); + } +} +/* Output: +true +true +false +true +*/ +``` + +编译器来保证类型标签与泛型参数相匹配。 + + +### 创建类型的实例 + +试图在 **Erased.java** 中 `new T()` 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 **T** 是否具有默认(无参)构造函数。但是在 C++ 中,此操作自然,直接且安全(在编译时检查): + +```C++ +// generics/InstantiateGenericType.cpp +// C++, not Java! + +template class Foo { + T x; // Create a field of type T + T* y; // Pointer to T +public: + // Initialize the pointer: + Foo() { y = new T(); } +}; + +class Bar {}; + +int main() { + Foo fb; + Foo fi; // ... and it works with primitives +} +``` + +Java 中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是 **Class** 对象,因此,如果使用类型标记,则可以使用 `newInstance()` 创建该类型的新对象: + +```java +// generics/InstantiateGenericType.java + +import java.util.function.Supplier; + +class ClassAsFactory implements Supplier { + Class kind; + + ClassAsFactory(Class kind) { + this.kind = kind; + } + + @Override + public T get() { + try { + return kind.newInstance(); + } catch (InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } +} + +class Employee { + @Override + public String toString() { + return "Employee"; + } +} + +public class InstantiateGenericType { + public static void main(String[] args) { + ClassAsFactory fe = + new ClassAsFactory<>(Employee.class); + System.out.println(fe.get()); + ClassAsFactory fi = + new ClassAsFactory<>(Integer.class); + try { + System.out.println(fi.get()); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} +/* Output: +Employee +java.lang.InstantiationException: java.lang.Integer +*/ +``` + +这样可以编译,但对于 `ClassAsFactory \` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法: + +```java +// generics/FactoryConstraint.java + +import onjava.Suppliers; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +class IntegerFactory implements Supplier { + private int i = 0; + + @Override + public Integer get() { + return ++i; + } +} + +class Widget { + private int id; + + Widget(int n) { + id = n; + } + + @Override + public String toString() { + return "Widget " + id; + } + + public static + class Factory implements Supplier { + private int i = 0; + + @Override + public Widget get() { + return new Widget(++i); + } + } +} + +class Fudge { + private static int count = 1; + private int n = count++; + + @Override + public String toString() { + return "Fudge " + n; + } +} + +class Foo2 { + private List x = new ArrayList<>(); + + Foo2(Supplier factory) { + Suppliers.fill(x, factory, 5); + } + + @Override + public String toString() { + return x.toString(); + } +} + +public class FactoryConstraint { + public static void main(String[] args) { + System.out.println( + new Foo2<>(new IntegerFactory())); + System.out.println( + new Foo2<>(new Widget.Factory())); + System.out.println( + new Foo2<>(Fudge::new)); + } +} +/* Output: +[1, 2, 3, 4, 5] +[Widget 1, Widget 2, Widget 3, Widget 4, Widget 5] +[Fudge 1, Fudge 2, Fudge 3, Fudge 4, Fudge 5] +*/ +``` + +**IntegerFactory** 本身就是通过实现 `Supplier\` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。 + +另一种方法是模板方法设计模式。在以下示例中,`create()` 是模板方法,在子类中被重写以生成该类型的对象: + +```java +// generics/CreatorGeneric.java + +abstract class GenericWithCreate { + final T element; + + GenericWithCreate() { + element = create(); + } + + abstract T create(); +} + +class X { +} + +class XCreator extends GenericWithCreate { + @Override + X create() { + return new X(); + } + + void f() { + System.out.println( + element.getClass().getSimpleName()); + } +} + +public class CreatorGeneric { + public static void main(String[] args) { + XCreator xc = new XCreator(); + xc.f(); + } +} +/* Output: +X +*/ +``` + +**GenericWithCreate** 包含 `element` 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 `create()` 方法。这种创建方式可以在子类中定义,同时建立 **T** 的类型。 + + +### 泛型数组 + +正如在 **Erased.java** 中所看到的,我们无法创建泛型数组。通用解决方案是在试图创建泛型数组的时候使用 **ArrayList** : + +```java +// generics/ListOfGenerics.java + +import java.util.ArrayList; +import java.util.List; + +public class ListOfGenerics { + private List array = new ArrayList<>(); + + public void add(T item) { + array.add(item); + } + + public T get(int index) { + return array.get(index); + } +} +``` + +这样做可以获得数组的行为,并且还具有泛型提供的编译时类型安全性。 + +有时,仍然会创建泛型类型的数组(例如, **ArrayList** 在内部使用数组)。可以通过使编译器满意的方式定义对数组的通用引用: + +```java +// generics/ArrayOfGenericReference.java + +class Generic { +} + +public class ArrayOfGenericReference { + static Generic[] gia; +} +``` + +编译器接受此操作而不产生警告。但是我们永远无法创建具有该确切类型(包括类型参数)的数组,因此有点令人困惑。由于所有数组,无论它们持有什么类型,都具有相同的结构(每个数组插槽的大小和数组布局),因此似乎可以创建一个 **Object** 数组并将其转换为所需的数组类型。实际上,这确实可以编译,但是会产生 **ClassCastException** : + +```java +// generics/ArrayOfGeneric.java + +public class ArrayOfGeneric { + static final int SIZE = 100; + static Generic[] gia; + + @SuppressWarnings("unchecked") + public static void main(String[] args) { + try { + gia = (Generic[]) new Object[SIZE]; + } catch (ClassCastException e) { + System.out.println(e.getMessage()); + } + // Runtime type is the raw (erased) type: + gia = (Generic[]) new Generic[SIZE]; + System.out.println(gia.getClass().getSimpleName()); + gia[0] = new Generic<>(); + //- gia[1] = new Object(); // Compile-time error + // Discovers type mismatch at compile time: + //- gia[2] = new Generic(); + } +} +/* Output: +[Ljava.lang.Object; cannot be cast to [LGeneric; +Generic[] +*/ +``` + +问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic\[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个对象数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。 + +让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器: + +```java +// generics/GenericArray.java + +public class GenericArray { + private T[] array; + + @SuppressWarnings("unchecked") + public GenericArray(int sz) { + array = (T[]) new Object[sz]; + } + + public void put(int index, T item) { + array[index] = item; + } + + public T get(int index) { + return array[index]; + } + + // Method that exposes the underlying representation: + public T[] rep() { + return array; + } + + public static void main(String[] args) { + GenericArray gai = new GenericArray<>(10); + try { + Integer[] ia = gai.rep(); + } catch (ClassCastException e) { + System.out.println(e.getMessage()); + } + // This is OK: + Object[] oa = gai.rep(); + } +} +/* Output: +[Ljava.lang.Object; cannot be cast to +[Ljava.lang.Integer; +*/ +``` + +和以前一样,我们不能说 `T[] array = new T[sz]` ,所以我们创建了一个 **Object** 数组并将其强制转换。 + +`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]` ,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。 + +如果再注释掉 **@SuppressWarnings** 注解后编译 **GenericArray.java** ,则编译器会产生警告: + +```java +GenericArray.java uses unchecked or unsafe operations. +Recompile with -Xlint:unchecked for details. +``` + +在这里,我们收到了一个警告,我们认为这是有关强制转换的。 + +但是要真正确定,请使用 `-Xlint:unchecked` 进行编译: + +```java +GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning +``` + +确实是在抱怨那个强制转换。由于警告会变成噪音,因此,一旦我们确认预期会出现特定警告,我们可以做的最好的办法就是使用 **@SuppressWarnings** 将其关闭。这样,当警告确实出现时,我们将进行实际调查。 + +由于擦除,数组的运行时类型只能是 `Object[]` 。 如果我们立即将其转换为 `T[]` ,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用 `Object[]` ,并在使用数组元素时向 **T** 添加强制类型转换。让我们来看看在 **GenericArray.java** 示例中会是怎么样的: + +```java +// generics/GenericArray2.java + +public class GenericArray2 { + private Object[] array; + + public GenericArray2(int sz) { + array = new Object[sz]; + } + + public void put(int index, T item) { + array[index] = item; + } + + @SuppressWarnings("unchecked") + public T get(int index) { + return (T) array[index]; + } + + @SuppressWarnings("unchecked") + public T[] rep() { + return (T[]) array; // Unchecked cast + } + + public static void main(String[] args) { + GenericArray2 gai = + new GenericArray2<>(10); + for (int i = 0; i < 10; i++) + gai.put(i, i); + for (int i = 0; i < 10; i++) + System.out.print(gai.get(i) + " "); + System.out.println(); + try { + Integer[] ia = gai.rep(); + } catch (Exception e) { + System.out.println(e); + } + } +} +/* Output: +0 1 2 3 4 5 6 7 8 9 +java.lang.ClassCastException: [Ljava.lang.Object; +cannot be cast to [Ljava.lang.Integer; +*/ +``` + +最初,看起来并没有太大不同,只是转换的位置移动了。没有 **@SuppressWarnings** 注解,仍然会收到“unchecked”警告。但是,内部表示现在是 `Object[]` 而不是 `T[]` 。 调用 `get()` 时,它将对象强制转换为 **T** ,实际上这是正确的类型,因此很安全。但是,如果调用 `rep()` ,它将再次尝试将 `Object[]` 强制转换为 `T[]` ,但仍然不正确,并在编译时生成警告,并在运行时生成异常。因此,无法破坏基础数组的类型,该基础数组只能是 `Object[]` 。在内部将数组视为 `Object[]` 而不是 `T[]` 的优点是,我们不太可能会忘记数组的运行时类型并意外地引入了bug,尽管大多数(也许是全部)此类错误会在运行时被迅速检测到。 + +对于新代码,请传入类型标记。在这种情况下,**GenericArray** 如下所示: + +```java +// generics/GenericArrayWithTypeToken.java + +import java.lang.reflect.Array; + +public class GenericArrayWithTypeToken { + private T[] array; + + @SuppressWarnings("unchecked") + public GenericArrayWithTypeToken(Class type, int sz) { + array = (T[]) Array.newInstance(type, sz); + } + + public void put(int index, T item) { + array[index] = item; + } + + public T get(int index) { + return array[index]; + } + + // Expose the underlying representation: + public T[] rep() { + return array; + } + + public static void main(String[] args) { + GenericArrayWithTypeToken gai = + new GenericArrayWithTypeToken<>( + Integer.class, 10); + // This now works: + Integer[] ia = gai.rep(); + } +} +``` + +类型标记 **Class\** 被传递到构造函数中以从擦除中恢复,因此尽管必须使用 **@SuppressWarnings** 关闭来自强制类型转换的警告,但我们仍可以创建所需的实际数组类型。一旦获得了实际的类型,就可以返回它并产生所需的结果,如在主方法中看到的那样。数组的运行时类型是确切的类型 `T[]` 。 + +不幸的是,如果查看 Java 标准库中的源代码,你会发现到处都有从 **Object** 数组到参数化类型的转换。例如,这是**ArrayList** 中,复制一个 **Collection** 的构造函数,这里为了简化,去除了源码中对此不重要的代码: + +```java +public ArrayList(Collection c) { + size = c.size(); + elementData = (E[])new Object[size]; + c.toArray(elementData); +} +``` + +如果你浏览 **ArrayList.java** 的代码,将会发现很多此类强制转换。当我们编译它时会发生什么? + +```java +Note: ArrayList.java uses unchecked or unsafe operations +Note: Recompile with -Xlint:unchecked for details. +``` + +果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。 + +Neal Gafter(Java 5的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时很懒惰,我们不应该做他所做的事情。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使某些习惯用法出现在 Java 库源代码中,也不一定是正确的做法。当查看库代码时,我们不能假设这是您在自己的代码中必须要遵循的示例。 + +请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。 ## 边界 From 97c31ddd30ebf4e56ad2bc6a354ac263572cca67 Mon Sep 17 00:00:00 2001 From: Moilk Date: Sun, 10 Nov 2019 14:40:43 +0800 Subject: [PATCH 076/371] =?UTF-8?q?=E7=AC=AC19=E7=AB=A0=20=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=BF=A1=E6=81=AF=20=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 1259 +++++++++++++++++------------- 1 file changed, 700 insertions(+), 559 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 83e75901..a2183e45 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -568,7 +568,7 @@ RTTI 在 Java 中还有第三种形式,那就是关键字 `instanceof`。它 ```java if(x instanceof Dog) - ((Dog)x).bark(); + ((Dog)x).bark(); ``` 在将 `x` 的类型转换为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。 @@ -582,7 +582,7 @@ if(x instanceof Dog) package typeinfo.pets; public class Person extends Individual { - public Person(String name) { super(name); } + public Person(String name) { super(name); } } ``` @@ -591,8 +591,8 @@ public class Person extends Individual { package typeinfo.pets; public class Pet extends Individual { - public Pet(String name) { super(name); } - public Pet() { super(); } + public Pet(String name) { super(name); } + public Pet() { super(); } } ``` @@ -601,8 +601,8 @@ public class Pet extends Individual { package typeinfo.pets; public class Dog extends Pet { - public Dog(String name) { super(name); } - public Dog() { super(); } + public Dog(String name) { super(name); } + public Dog() { super(); } } ``` @@ -611,8 +611,8 @@ public class Dog extends Pet { package typeinfo.pets; public class Mutt extends Dog { - public Mutt(String name) { super(name); } - public Mutt() { super(); } + public Mutt(String name) { super(name); } + public Mutt() { super(); } } ``` @@ -622,8 +622,8 @@ public class Mutt extends Dog { package typeinfo.pets; public class Pug extends Dog { - public Pug(String name) { super(name); } - public Pug() { super(); } + public Pug(String name) { super(name); } + public Pug() { super(); } } ``` @@ -632,8 +632,8 @@ public class Pug extends Dog { package typeinfo.pets; public class Cat extends Pet { - public Cat(String name) { super(name); } - public Cat() { super(); } + public Cat(String name) { super(name); } + public Cat() { super(); } } ``` @@ -642,8 +642,8 @@ public class Cat extends Pet { package typeinfo.pets; public class EgyptianMau extends Cat { - public EgyptianMau(String name) { super(name); } - public EgyptianMau() { super(); } + public EgyptianMau(String name) { super(name); } + public EgyptianMau() { super(); } } ``` @@ -652,8 +652,8 @@ public class EgyptianMau extends Cat { package typeinfo.pets; public class Manx extends Cat { - public Manx(String name) { super(name); } - public Manx() { super(); } + public Manx(String name) { super(name); } + public Manx() { super(); } } ``` @@ -662,8 +662,8 @@ public class Manx extends Cat { package typeinfo.pets; public class Cymric extends Manx { - public Cymric(String name) { super(name); } - public Cymric() { super(); } + public Cymric(String name) { super(name); } + public Cymric() { super(); } } ``` @@ -672,8 +672,8 @@ public class Cymric extends Manx { package typeinfo.pets; public class Rodent extends Pet { - public Rodent(String name) { super(name); } - public Rodent() { super(); } + public Rodent(String name) { super(name); } + public Rodent() { super(); } } ``` @@ -682,8 +682,8 @@ public class Rodent extends Pet { package typeinfo.pets; public class Rat extends Rodent { - public Rat(String name) { super(name); } - public Rat() { super(); } + public Rat(String name) { super(name); } + public Rat() { super(); } } ``` @@ -692,8 +692,8 @@ public class Rat extends Rodent { package typeinfo.pets; public class Mouse extends Rodent { - public Mouse(String name) { super(name); } - public Mouse() { super(); } + public Mouse(String name) { super(name); } + public Mouse() { super(); } } ``` @@ -702,8 +702,8 @@ public class Mouse extends Rodent { package typeinfo.pets; public class Hamster extends Rodent { - public Hamster(String name) { super(name); } - public Hamster() { super(); } + public Hamster(String name) { super(name); } + public Hamster() { super(); } } ``` @@ -718,20 +718,21 @@ package typeinfo.pets; import java.util.*; import java.util.function.*; -public abstract -class PetCreator implements Supplier { - private Random rand = new Random(47); - // The List of the different types of Pet to create: - public abstract List> types(); - public Pet get() { // Create one random Pet - int n = rand.nextInt(types().size()); - try { - return types().get(n).newInstance(); - } catch(InstantiationException | - IllegalAccessException e) { - throw new RuntimeException(e); +public abstract class PetCreator implements Supplier { + private Random rand = new Random(47); + + // The List of the different types of Pet to create: + public abstract List> types(); + + public Pet get() { // Create one random Pet + int n = rand.nextInt(types().size()); + try { + return types().get(n).newInstance(); + } catch (InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } } - } } ``` @@ -747,37 +748,39 @@ package typeinfo.pets; import java.util.*; public class ForNameCreator extends PetCreator { - private static List> types = - new ArrayList<>(); - - // 需要随机生成的类型名: - private static String[] typeNames = { - "typeinfo.pets.Mutt", - "typeinfo.pets.Pug", - "typeinfo.pets.EgyptianMau", - "typeinfo.pets.Manx", - "typeinfo.pets.Cymric", - "typeinfo.pets.Rat", - "typeinfo.pets.Mouse", - "typeinfo.pets.Hamster" - }; - - @SuppressWarnings("unchecked") - private static void loader() { - try { - for(String name : typeNames) - types.add( - (Class)Class.forName(name)); - } catch(ClassNotFoundException e) { - throw new RuntimeException(e); + private static List> types = + new ArrayList<>(); + // 需要随机生成的类型名: + private static String[] typeNames = { + "typeinfo.pets.Mutt", + "typeinfo.pets.Pug", + "typeinfo.pets.EgyptianMau", + "typeinfo.pets.Manx", + "typeinfo.pets.Cymric", + "typeinfo.pets.Rat", + "typeinfo.pets.Mouse", + "typeinfo.pets.Hamster" + }; + + @SuppressWarnings("unchecked") + private static void loader() { + try { + for (String name : typeNames) + types.add( + (Class) Class.forName(name)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } } - } - static { loader(); } - @Override - public List> types() { - return types; - } + static { + loader(); + } + + @Override + public List> types() { + return types; + } } ``` @@ -794,62 +797,67 @@ import typeinfo.pets.*; import java.util.*; public class PetCount { - static class Counter extends HashMap { - public void count(String type) { - Integer quantity = get(type); - if(quantity == null) - put(type, 1); - else - put(type, quantity + 1); + static class Counter extends HashMap { + public void count(String type) { + Integer quantity = get(type); + if (quantity == null) + put(type, 1); + else + put(type, quantity + 1); + } + } + + public static void + countPets(PetCreator creator) { + Counter counter = new Counter(); + for (Pet pet : Pets.array(20)) { + // List each individual pet: + System.out.print( + pet.getClass().getSimpleName() + " "); + if (pet instanceof Pet) + counter.count("Pet"); + if (pet instanceof Dog) + counter.count("Dog"); + if (pet instanceof Mutt) + counter.count("Mutt"); + if (pet instanceof Pug) + counter.count("Pug"); + if (pet instanceof Cat) + counter.count("Cat"); + if (pet instanceof EgyptianMau) + counter.count("EgyptianMau"); + if (pet instanceof Manx) + counter.count("Manx"); + if (pet instanceof Cymric) + counter.count("Cymric"); + if (pet instanceof Rodent) + counter.count("Rodent"); + if (pet instanceof Rat) + counter.count("Rat"); + if (pet instanceof Mouse) + counter.count("Mouse"); + if (pet instanceof Hamster) + counter.count("Hamster"); + } + // Show the counts: + System.out.println(); + System.out.println(counter); + } + + public static void main(String[] args) { + countPets(new ForNameCreator()); } - } - public static void - countPets(PetCreator creator) { - Counter counter = new Counter(); - for(Pet pet : Pets.array(20)) { - // List each individual pet: - System.out.print( - pet.getClass().getSimpleName() + " "); - if(pet instanceof Pet) - counter.count("Pet"); - if(pet instanceof Dog) - counter.count("Dog"); - if(pet instanceof Mutt) - counter.count("Mutt"); - if(pet instanceof Pug) - counter.count("Pug"); - if(pet instanceof Cat) - counter.count("Cat"); - if(pet instanceof EgyptianMau) - counter.count("EgyptianMau"); - if(pet instanceof Manx) - counter.count("Manx"); - if(pet instanceof Cymric) - counter.count("Cymric"); - if(pet instanceof Rodent) - counter.count("Rodent"); - if(pet instanceof Rat) - counter.count("Rat"); - if(pet instanceof Mouse) - counter.count("Mouse"); - if(pet instanceof Hamster) - counter.count("Hamster"); - } - // Show the counts: - System.out.println(); - System.out.println(counter); - } - public static void main(String[] args) { - countPets(new ForNameCreator()); - } } -/* Output: +``` + +输出结果: + +``` Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1} -*/ ``` 在 `countPets()` 中,一个简短的静态方法 `Pets.array()` 生产出了一个随机动物的集合。每个 `Pet` 都被 `instanceof` 检测到并数了一遍。 @@ -868,37 +876,41 @@ package typeinfo.pets; import java.util.*; public class LiteralPetCreator extends PetCreator { - // try 代码块不再需要 - @SuppressWarnings("unchecked") - public static - final List> ALL_TYPES = - Collections.unmodifiableList(Arrays.asList( - Pet.class, Dog.class, Cat.class, Rodent.class, - Mutt.class, Pug.class, EgyptianMau.class, - Manx.class, Cymric.class, Rat.class, - Mouse.class, Hamster.class)); - // 用于随机创建的类型: - private static final - List> TYPES = - ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class), - ALL_TYPES.size()); - @Override - public List> types() { - return TYPES; - } - public static void main(String[] args) { - System.out.println(TYPES); - } + // try 代码块不再需要 + @SuppressWarnings("unchecked") + public static final List> ALL_TYPES = + Collections.unmodifiableList(Arrays.asList( + Pet.class, Dog.class, Cat.class, Rodent.class, + Mutt.class, Pug.class, EgyptianMau.class, + Manx.class, Cymric.class, Rat.class, + Mouse.class, Hamster.class)); + // 用于随机创建的类型: + private static final List> TYPES = + ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class), + ALL_TYPES.size()); + + @Override + public List> types() { + return TYPES; + } + + public static void main(String[] args) { + System.out.println(TYPES); + } } -/* 输出: +``` + +输出结果: + +``` [class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse, class typeinfo.pets.Hamster] -*/ ``` + 在即将到来的 `PetCount3.java` 示例中,我们用所有 `Pet` 类型预先加载一个 `Map`(不仅仅是随机生成的),因此 `ALL_TYPES` 类型的列表是必要的。`types` 列表是 `ALL_TYPES` 类型(使用 `List.subList()` 创建)的一部分,它包含精确的宠物类型,因此用于随机生成 `Pet`。 这次,`types` 的创建没有被 `try` 块包围,因为它是在编译时计算的,因此不会引发任何异常,不像 `Class.forName()`。 @@ -909,33 +921,33 @@ typeinfo.pets.Hamster] // typeinfo/pets/Pets.java // Facade to produce a default PetCreator package typeinfo.pets; + import java.util.*; import java.util.stream.*; public class Pets { - public static final PetCreator CREATOR = - new LiteralPetCreator(); + public static final PetCreator CREATOR = new LiteralPetCreator(); - public static Pet get() { - return CREATOR.get(); - } + public static Pet get() { + return CREATOR.get(); + } - public static Pet[] array(int size) { - Pet[] result = new Pet[size]; - for(int i = 0; i < size; i++) - result[i] = CREATOR.get(); - return result; - } + public static Pet[] array(int size) { + Pet[] result = new Pet[size]; + for (int i = 0; i < size; i++) + result[i] = CREATOR.get(); + return result; + } - public static List list(int size) { - List result = new ArrayList<>(); - Collections.addAll(result, array(size)); - return result; - } + public static List list(int size) { + List result = new ArrayList<>(); + Collections.addAll(result, array(size)); + return result; + } - public static Stream stream() { - return Stream.generate(CREATOR); - } + public static Stream stream() { + return Stream.generate(CREATOR); + } } ``` @@ -948,17 +960,20 @@ public class Pets { import typeinfo.pets.*; public class PetCount2 { - public static void main(String[] args) { - PetCount.countPets(Pets.CREATOR); - } + public static void main(String[] args) { + PetCount.countPets(Pets.CREATOR); + } } -/* 输出: +``` + +输出结果: + +``` Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1} -*/ ``` 输出与 `PetCount.java` 的输出相同。 @@ -970,58 +985,62 @@ Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1} ```java // typeinfo/PetCount3.java // 使用 isInstance() 方法 + import java.util.*; import java.util.stream.*; + import onjava.*; import typeinfo.pets.*; public class PetCount3 { - static class Counter extends - LinkedHashMap, Integer> { + static class Counter extends + LinkedHashMap, Integer> { + Counter() { + super(LiteralPetCreator.ALL_TYPES.stream() + .map(lpc -> Pair.make(lpc, 0)) + .collect( + Collectors.toMap(Pair::key, Pair::value))); + } - Counter() { - super(LiteralPetCreator.ALL_TYPES.stream() - .map(lpc -> Pair.make(lpc, 0)) - .collect( - Collectors.toMap(Pair::key, Pair::value))); - } + public void count(Pet pet) { + // Class.isInstance() 替换 instanceof: + entrySet().stream() + .filter(pair -> pair.getKey().isInstance(pet)) + .forEach(pair -> + put(pair.getKey(), pair.getValue() + 1)); + } - public void count(Pet pet) { - // Class.isInstance() 替换 instanceof: - entrySet().stream() - .filter(pair -> pair.getKey().isInstance(pet)) - .forEach(pair -> - put(pair.getKey(), pair.getValue() + 1)); + @Override + public String toString() { + String result = entrySet().stream() + .map(pair -> String.format("%s=%s", + pair.getKey().getSimpleName(), + pair.getValue())) + .collect(Collectors.joining(", ")); + return "{" + result + "}"; + } } - @Override - public String toString() { - String result = entrySet().stream() - .map(pair -> String.format("%s=%s", - pair.getKey().getSimpleName(), - pair.getValue())) - .collect(Collectors.joining(", ")); - return "{" + result + "}"; + public static void main(String[] args) { + Counter petCount = new Counter(); + Pets.stream() + .limit(20) + .peek(petCount::count) + .forEach(p -> System.out.print( + p.getClass().getSimpleName() + " ")); + System.out.println("n" + petCount); } - } - - public static void main(String[] args) { - Counter petCount = new Counter(); - Pets.stream() - .limit(20) - .peek(petCount::count) - .forEach(p -> System.out.print( - p.getClass().getSimpleName() + " ")); - System.out.println("\n" + petCount); - } } -/* 输出: +``` + +输出结果: + +``` Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5, EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20} -*/ ``` 为了计算所有不同类型的 `Pet`,`Counter Map` 预先加载了来自 `LiteralPetCreator.ALL_TYPES` 的类型。如果不预先加载 `Map`,将只计数随机生成的类型,而不是像 `Pet` 和 `Cat` 这样的基本类型。 @@ -1041,41 +1060,40 @@ package onjava; import java.util.*; import java.util.stream.*; -public class -TypeCounter extends HashMap, Integer> { - private Class baseType; +public class TypeCounter extends HashMap, Integer> { + private Class baseType; - public TypeCounter(Class baseType) { - this.baseType = baseType; - } + public TypeCounter(Class baseType) { + this.baseType = baseType; + } - public void count(Object obj) { - Class type = obj.getClass(); - if(!baseType.isAssignableFrom(type)) - throw new RuntimeException( - obj + " incorrect type: " + type + - ", should be type or subtype of " + baseType); - countClass(type); - } + public void count(Object obj) { + Class type = obj.getClass(); + if(!baseType.isAssignableFrom(type)) + throw new RuntimeException( + obj + " incorrect type: " + type + + ", should be type or subtype of " + baseType); + countClass(type); + } - private void countClass(Class type) { - Integer quantity = get(type); - put(type, quantity == null ? 1 : quantity + 1); - Class superClass = type.getSuperclass(); - if(superClass != null && - baseType.isAssignableFrom(superClass)) - countClass(superClass); - } + private void countClass(Class type) { + Integer quantity = get(type); + put(type, quantity == null ? 1 : quantity + 1); + Class superClass = type.getSuperclass(); + if(superClass != null && + baseType.isAssignableFrom(superClass)) + countClass(superClass); + } - @Override - public String toString() { - String result = entrySet().stream() - .map(pair -> String.format("%s=%s", - pair.getKey().getSimpleName(), - pair.getValue())) - .collect(Collectors.joining(", ")); - return "{" + result + "}"; - } + @Override + public String toString() { + String result = entrySet().stream() + .map(pair -> String.format("%s=%s", + pair.getKey().getSimpleName(), + pair.getValue())) + .collect(Collectors.joining(", ")); + return "{" + result + "}"; + } } ``` @@ -1087,24 +1105,27 @@ import typeinfo.pets.*; import onjava.*; public class PetCount4 { - public static void main(String[] args) { - TypeCounter counter = new TypeCounter(Pet.class); - Pets.stream() - .limit(20) - .peek(counter::count) - .forEach(p -> System.out.print( - p.getClass().getSimpleName() + " ")); - System.out.println("\n" + counter); + public static void main(String[] args) { + TypeCounter counter = new TypeCounter(Pet.class); + Pets.stream() + .limit(20) + .peek(counter::count) + .forEach(p -> System.out.print( + p.getClass().getSimpleName() + " ")); + System.out.println("n" + counter); } } -/* 输出: +``` + +输出结果: + +``` Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Dog=6, Manx=7, Cat=9, Rodent=5, Hamster=1, Rat=2, Pug=3, Mutt=3, Cymric=5, EgyptianMau=2, Pet=20, Mouse=2} -*/ ``` 输出表明两个基类型以及精确类型都被计数了。 @@ -1130,83 +1151,94 @@ import java.util.function.*; import java.util.stream.*; class Part implements Supplier { - @Override - public String toString() { - return getClass().getSimpleName(); - } + @Override + public String toString() { + return getClass().getSimpleName(); + } - static List> prototypes = - Arrays.asList( - new FuelFilter(), - new AirFilter(), - new CabinAirFilter(), - new OilFilter(), - new FanBelt(), - new PowerSteeringBelt(), - new GeneratorBelt() - ); - - private static Random rand = new Random(47); - public Part get() { - int n = rand.nextInt(prototypes.size()); - return prototypes.get(n).get(); - } + static List> prototypes = + Arrays.asList( + new FuelFilter(), + new AirFilter(), + new CabinAirFilter(), + new OilFilter(), + new FanBelt(), + new PowerSteeringBelt(), + new GeneratorBelt() + ); + + private static Random rand = new Random(47); + public Part get() { + int n = rand.nextint(prototypes.size()); + return prototypes.get(n).get(); + } } class Filter extends Part {} class FuelFilter extends Filter { - @Override - public FuelFilter get() { return new FuelFilter(); } + @Override + public FuelFilter get() { + return new FuelFilter(); + } } class AirFilter extends Filter { - @Override - public AirFilter get() { return new AirFilter(); } + @Override + public AirFilter get() { + return new AirFilter(); + } } class CabinAirFilter extends Filter { - @Override - public CabinAirFilter get() { - return new CabinAirFilter(); - } + @Override + public CabinAirFilter get() { + return new CabinAirFilter(); + } } class OilFilter extends Filter { - @Override - public OilFilter get() { return new OilFilter(); } + @Override + public OilFilter get() { + return new OilFilter(); + } } class Belt extends Part {} class FanBelt extends Belt { - @Override - public FanBelt get() { return new FanBelt(); } + @Override + public FanBelt get() { + return new FanBelt(); + } } class GeneratorBelt extends Belt { - @Override - public GeneratorBelt get() { - return new GeneratorBelt(); - } + @Override + public GeneratorBelt get() { + return new GeneratorBelt(); + } } class PowerSteeringBelt extends Belt { - @Override - public PowerSteeringBelt get() { - return new PowerSteeringBelt(); - } + @Override + public PowerSteeringBelt get() { + return new PowerSteeringBelt(); + } } public class RegisteredFactories { - public static void main(String[] args) { - Stream.generate(new Part()) - .limit(10) - .forEach(System.out::println); - } + public static void main(String[] args) { + Stream.generate(new Part()) + .limit(10) + .forEach(System.out::println); + } } +``` + +输出结果: -/* 输出: +``` GeneratorBelt CabinAirFilter GeneratorBelt @@ -1217,7 +1249,6 @@ FuelFilter PowerSteeringBelt PowerSteeringBelt FuelFilter -*/ ``` 并非层次结构中的所有类都应实例化;这里的 `Filter` 和 `Belt` 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 `Part` 基类的行为)。 @@ -1239,37 +1270,42 @@ class Base {} class Derived extends Base {} public class FamilyVsExactType { - static void test(Object x) { - System.out.println( - "Testing x of type " + x.getClass()); - System.out.println( - "x instanceof Base " + (x instanceof Base)); - System.out.println( - "x instanceof Derived " + (x instanceof Derived)); - System.out.println( - "Base.isInstance(x) " + Base.class.isInstance(x)); - System.out.println( - "Derived.isInstance(x) " + - Derived.class.isInstance(x)); - System.out.println( - "x.getClass() == Base.class " + - (x.getClass() == Base.class)); - System.out.println( - "x.getClass() == Derived.class " + - (x.getClass() == Derived.class)); - System.out.println( - "x.getClass().equals(Base.class)) "+ - (x.getClass().equals(Base.class))); - System.out.println( - "x.getClass().equals(Derived.class)) " + - (x.getClass().equals(Derived.class))); - } - public static void main(String[] args) { - test(new Base()); - test(new Derived()); - } + static void test(Object x) { + System.out.println( + "Testing x of type " + x.getClass()); + System.out.println( + "x instanceof Base " + (x instanceof Base)); + System.out.println( + "x instanceof Derived " + (x instanceof Derived)); + System.out.println( + "Base.isInstance(x) " + Base.class.isInstance(x)); + System.out.println( + "Derived.isInstance(x) " + + Derived.class.isInstance(x)); + System.out.println( + "x.getClass() == Base.class " + + (x.getClass() == Base.class)); + System.out.println( + "x.getClass() == Derived.class " + + (x.getClass() == Derived.class)); + System.out.println( + "x.getClass().equals(Base.class)) "+ + (x.getClass().equals(Base.class))); + System.out.println( + "x.getClass().equals(Derived.class)) " + + (x.getClass().equals(Derived.class))); + } + + public static void main(String[] args) { + test(new Base()); + test(new Derived()); + } } -/* 输出: +``` + +输出结果: + +``` Testing x of type class typeinfo.Base x instanceof Base true x instanceof Derived false @@ -1288,7 +1324,6 @@ x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true -*/ ``` `test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果与 `equals()` 和 `==` 完全相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。另一方面,如果使用 `==` 比较实际的 `Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。 @@ -1322,54 +1357,57 @@ import java.lang.reflect.*; import java.util.regex.*; public class ShowMethods { - private static String usage = - "usage:\n" + - "ShowMethods qualified.class.name\n" + - "To show all methods in class or:\n" + - "ShowMethods qualified.class.name word\n" + - "To search for methods involving 'word'"; - - private static Pattern p = Pattern.compile("\\w+\\."); - public static void main(String[] args) { - - if(args.length < 1) { - System.out.println(usage); - System.exit(0); - } - int lines = 0; - try { - Class c = Class.forName(args[0]); - Method[] methods = c.getMethods(); - Constructor[] ctors = c.getConstructors(); - if(args.length == 1) { - for(Method method : methods) - System.out.println( - p.matcher( - method.toString()).replaceAll("")); - for(Constructor ctor : ctors) - System.out.println( - p.matcher(ctor.toString()).replaceAll("")); - lines = methods.length + ctors.length; - } else { - for(Method method : methods) - if(method.toString().contains(args[1])) { - System.out.println(p.matcher( - method.toString()).replaceAll("")); - lines++; - } - for(Constructor ctor : ctors) - if(ctor.toString().contains(args[1])) { - System.out.println(p.matcher( - ctor.toString()).replaceAll("")); - lines++; - } - } - } catch(ClassNotFoundException e) { - System.out.println("No such class: " + e); + private static String usage = + "usage:n" + + "ShowMethods qualified.class.namen" + + "To show all methods in class or:n" + + "ShowMethods qualified.class.name wordn" + + "To search for methods involving 'word'"; + private static Pattern p = Pattern.compile("\w+\."); + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println(usage); + System.exit(0); + } + int lines = 0; + try { + Class c = Class.forName(args[0]); + Method[] methods = c.getMethods(); + Constructor[] ctors = c.getConstructors(); + if (args.length == 1) { + for (Method method : methods) + System.out.println( + p.matcher( + method.toString()).replaceAll("")); + for (Constructor ctor : ctors) + System.out.println( + p.matcher(ctor.toString()).replaceAll("")); + lines = methods.length + ctors.length; + } else { + for (Method method : methods) + if (method.toString().contains(args[1])) { + System.out.println(p.matcher( + method.toString()).replaceAll("")); + lines++; + } + for (Constructor ctor : ctors) + if (ctor.toString().contains(args[1])) { + System.out.println(p.matcher( + ctor.toString()).replaceAll("")); + lines++; + } + } + } catch (ClassNotFoundException e) { + System.out.println("No such class: " + e); + } } - } } -/* 输出: +``` + +输出结果: + +``` public static void main(String[]) public final void wait() throws InterruptedException public final void wait(long,int) throws @@ -1383,7 +1421,6 @@ public final native Class getClass() public final native void notify() public final native void notifyAll() public ShowMethods() -*/ ``` `Class` 方法 `getmethods()` 和 `getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。 @@ -1411,57 +1448,66 @@ java ShowMethods ShowMethods // typeinfo/SimpleProxyDemo.java interface Interface { - void doSomething(); - void somethingElse(String arg); + void doSomething(); + + void somethingElse(String arg); } class RealObject implements Interface { - @Override - public void doSomething() { - System.out.println("doSomething"); - } - @Override - public void somethingElse(String arg) { - System.out.println("somethingElse " + arg); - } + @Override + public void doSomething() { + System.out.println("doSomething"); + } + + @Override + public void somethingElse(String arg) { + System.out.println("somethingElse " + arg); + } } class SimpleProxy implements Interface { - private Interface proxied; - SimpleProxy(Interface proxied) { - this.proxied = proxied; - } - @Override - public void doSomething() { - System.out.println("SimpleProxy doSomething"); - proxied.doSomething(); - } - @Override - public void somethingElse(String arg) { - System.out.println( - "SimpleProxy somethingElse " + arg); - proxied.somethingElse(arg); - } + private Interface proxied; + + SimpleProxy(Interface proxied) { + this.proxied = proxied; + } + + @Override + public void doSomething() { + System.out.println("SimpleProxy doSomething"); + proxied.doSomething(); + } + + @Override + public void somethingElse(String arg) { + System.out.println( + "SimpleProxy somethingElse " + arg); + proxied.somethingElse(arg); + } } class SimpleProxyDemo { - public static void consumer(Interface iface) { - iface.doSomething(); - iface.somethingElse("bonobo"); - } - public static void main(String[] args) { - consumer(new RealObject()); - consumer(new SimpleProxy(new RealObject())); - } + public static void consumer(Interface iface) { + iface.doSomething(); + iface.somethingElse("bonobo"); + } + + public static void main(String[] args) { + consumer(new RealObject()); + consumer(new SimpleProxy(new RealObject())); + } } -/* Output: +``` + +输出结果: + +``` doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo -*/ ``` 因为`consumer()`接受`Interface`,所以它不知道获得的是`RealObject`还是`SimpleProxy`,因为两者都实现了`Interface`。 @@ -1473,44 +1519,52 @@ Java的*动态代理*更进一步,不仅动态创建代理对象而且动态 ```java // typeinfo/SimpleDynamicProxy.java + import java.lang.reflect.*; class DynamicProxyHandler implements InvocationHandler { - private Object proxied; - DynamicProxyHandler(Object proxied) { - this.proxied = proxied; - } - @Override - public Object - invoke(Object proxy, Method method, Object[] args) - throws Throwable { - System.out.println( - "**** proxy: " + proxy.getClass() + - ", method: " + method + ", args: " + args); - if(args != null) - for(Object arg : args) - System.out.println(" " + arg); - return method.invoke(proxied, args); - } + private Object proxied; + + DynamicProxyHandler(Object proxied) { + this.proxied = proxied; + } + + @Override + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { + System.out.println( + "**** proxy: " + proxy.getClass() + + ", method: " + method + ", args: " + args); + if (args != null) + for (Object arg : args) + System.out.println(" " + arg); + return method.invoke(proxied, args); + } } class SimpleDynamicProxy { - public static void consumer(Interface iface) { - iface.doSomething(); - iface.somethingElse("bonobo"); - } - public static void main(String[] args) { - RealObject real = new RealObject(); - consumer(real); - // Insert a proxy and call again: - Interface proxy = (Interface)Proxy.newProxyInstance( - Interface.class.getClassLoader(), - new Class[]{ Interface.class }, - new DynamicProxyHandler(real)); - consumer(proxy); - } + public static void consumer(Interface iface) { + iface.doSomething(); + iface.somethingElse("bonobo"); + } + + public static void main(String[] args) { + RealObject real = new RealObject(); + consumer(real); + // Insert a proxy and call again: + Interface proxy = (Interface) Proxy.newProxyInstance( + Interface.class.getClassLoader(), + new Class[]{Interface.class}, + new DynamicProxyHandler(real)); + consumer(proxy); + } } -/* Output: +``` + +输出结果: + +``` doSomething somethingElse bonobo **** proxy: class $Proxy0, method: public abstract void @@ -1521,7 +1575,6 @@ Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6bc7c054 bonobo somethingElse bonobo -*/ ``` 可以通过调用静态方法`Proxy.newProxyInstance()`来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口`InvocationHandler`的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。 @@ -1533,70 +1586,82 @@ somethingElse bonobo ```java // typeinfo/SelectingMethods.java // Looking for particular methods in a dynamic proxy + import java.lang.reflect.*; class MethodSelector implements InvocationHandler { - private Object proxied; - MethodSelector(Object proxied) { - this.proxied = proxied; - } - @Override - public Object - invoke(Object proxy, Method method, Object[] args) - throws Throwable { - if(method.getName().equals("interesting")) - System.out.println( - "Proxy detected the interesting method"); - return method.invoke(proxied, args); - } + private Object proxied; + + MethodSelector(Object proxied) { + this.proxied = proxied; + } + + @Override + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { + if (method.getName().equals("interesting")) + System.out.println( + "Proxy detected the interesting method"); + return method.invoke(proxied, args); + } } interface SomeMethods { - void boring1(); - void boring2(); - void interesting(String arg); - void boring3(); + void boring1(); + + void boring2(); + + void interesting(String arg); + + void boring3(); } class Implementation implements SomeMethods { - @Override - public void boring1() { - System.out.println("boring1"); - } - @Override - public void boring2() { - System.out.println("boring2"); - } - @Override - public void interesting(String arg) { - System.out.println("interesting " + arg); - } - @Override - public void boring3() { - System.out.println("boring3"); - } + @Override + public void boring1() { + System.out.println("boring1"); + } + + @Override + public void boring2() { + System.out.println("boring2"); + } + + @Override + public void interesting(String arg) { + System.out.println("interesting " + arg); + } + + @Override + public void boring3() { + System.out.println("boring3"); + } } class SelectingMethods { - public static void main(String[] args) { - SomeMethods proxy = - (SomeMethods)Proxy.newProxyInstance( - SomeMethods.class.getClassLoader(), - new Class[]{ Interface.class }, - new MethodSelector(new Implementation())); - proxy.boring1(); - proxy.boring2(); - proxy.interesting("bonobo"); - proxy.boring3(); - } + public static void main(String[] args) { + SomeMethods proxy = + (SomeMethods) Proxy.newProxyInstance( + SomeMethods.class.getClassLoader(), + new Class[]{Interface.class}, + new MethodSelector(new Implementation())); + proxy.boring1(); + proxy.boring2(); + proxy.interesting("bonobo"); + proxy.boring3(); + } } -/* Output: +``` + +输出结果: + +``` boring1 boring2 Proxy detected the interesting method interesting bonobo boring3 -*/ ``` 在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。 @@ -1615,7 +1680,9 @@ boring3 ```java // typeinfo/Person.java // Using Optional with regular classes + import onjava.*; + import java.util.*; class Person { @@ -1624,37 +1691,43 @@ class Person { public final Optional address; // etc. public final Boolean empty; + Person(String first, String last, String address) { this.first = Optional.ofNullable(first); this.last = Optional.ofNullable(last); this.address = Optional.ofNullable(address); empty = !this.first.isPresent() - && !this.last.isPresent() - && !this.address.isPresent(); + && !this.last.isPresent() + && !this.address.isPresent(); } + Person(String first, String last) { this(first, last, null); } + Person(String last) { this(null, last, null); } + Person() { this(null, null, null); } + @Override - public String toString() { - if(empty) - return ""; + public String toString() { + if (empty) + return ""; return (first.orElse("") + - " " + last.orElse("") + - " " + address.orElse("")).trim(); + " " + last.orElse("") + + " " + address.orElse("")).trim(); } + public static void main(String[] args) { System.out.println(new Person()); System.out.println(new Person("Smith")); System.out.println(new Person("Bob", "Smith")); System.out.println(new Person("Bob", "Smith", - "11 Degree Lane, Frostbite Falls, MN")); + "11 Degree Lane, Frostbite Falls, MN")); } } ``` @@ -1676,49 +1749,58 @@ Bob Smith 11 Degree Lane, Frostbite Falls, MN ```java // typeinfo/Position.java + import java.util.*; class EmptyTitleException extends RuntimeException { } + class Position { private String title; private Person person; + Position(String jobTitle, Person employee) { setTitle(jobTitle); setPerson(employee); } + Position(String jobTitle) { this(jobTitle, null); } + public String getTitle() { return title; } + public void setTitle(String newTitle) { // Throws EmptyTitleException if newTitle is null: title = Optional.ofNullable(newTitle) - .orElseThrow(EmptyTitleException::new); + .orElseThrow(EmptyTitleException::new); } + public Person getPerson() { return person; } + public void setPerson(Person newPerson) { // Uses empty Person if newPerson is null: person = Optional.ofNullable(newPerson) - .orElse(new Person()); + .orElse(new Person()); } + @Override - public String toString() { + public String toString() { return "Position: " + title + - ", Employee: " + person; + ", Employee: " + person; } + public static void main(String[] args) { System.out.println(new Position("CEO")); System.out.println(new Position("Programmer", - new Person("Arthur", "Fonzarelli"))); + new Person("Arthur", "Fonzarelli"))); try { new Position(null); - } - catch(Exception e) { + } catch (Exception e) { System.out.println("caught " + e); } } @@ -1747,51 +1829,57 @@ caught EmptyTitleException ```java // typeinfo/Staff.java + import java.util.*; public class Staff extends ArrayList { public void add(String title, Person person) { add(new Position(title, person)); } + public void add(String... titles) { for (String title : titles) - add(new Position(title)); + add(new Position(title)); } + public Staff(String... titles) { add(titles); } + public Boolean positionAvailable(String title) { for (Position position : this) - if(position.getTitle().equals(title) && - position.getPerson().empty) + if (position.getTitle().equals(title) && + position.getPerson().empty) return true; return false; } + public void fillPosition(String title, Person hire) { for (Position position : this) - if(position.getTitle().equals(title) && - position.getPerson().empty) { - position.setPerson(hire); - return; - } + if (position.getTitle().equals(title) && + position.getPerson().empty) { + position.setPerson(hire); + return; + } throw new RuntimeException( - "Position " + title + " not available"); + "Position " + title + " not available"); } + public static void main(String[] args) { Staff staff = new Staff("President", "CTO", - "Marketing Manager", "Product Manager", - "Project Lead", "Software Engineer", - "Software Engineer", "Software Engineer", - "Software Engineer", "Test Engineer", - "Technical Writer"); + "Marketing Manager", "Product Manager", + "Project Lead", "Software Engineer", + "Software Engineer", "Software Engineer", + "Software Engineer", "Test Engineer", + "Technical Writer"); staff.fillPosition("President", - new Person("Me", "Last", "The Top, Lonely At")); + new Person("Me", "Last", "The Top, Lonely At")); staff.fillPosition("Project Lead", - new Person("Janet", "Planner", "The Burbs")); - if(staff.positionAvailable("Software Engineer")) - staff.fillPosition("Software Engineer", - new Person( - "Bob", "Coder", "Bright Light City")); + new Person("Janet", "Planner", "The Burbs")); + if (staff.positionAvailable("Software Engineer")) + staff.fillPosition("Software Engineer", + new Person( + "Bob", "Coder", "Bright Light City")); System.out.println(staff); } } @@ -1829,16 +1917,21 @@ public interface Null {} ```java // typeinfo/Robot.java + import onjava.*; + import java.util.*; public interface Robot { String name(); + String model(); + List operations(); + static void test(Robot r) { - if(r instanceof Null) - System.out.println("[Null Robot]"); + if (r instanceof Null) + System.out.println("[Null Robot]"); System.out.println("Robot name: " + r.name()); System.out.println("Robot model: " + r.model()); for (Operation operation : r.operations()) { @@ -1855,13 +1948,14 @@ public interface Robot { ```java // typeinfo/Operation.java + import java.util.function.*; public class Operation { public final Supplier description; public final Runnable command; - public - Operation(Supplier descr, Runnable cmd) { + + public Operation(Supplier descr, Runnable cmd) { description = descr; command = cmd; } @@ -1872,36 +1966,43 @@ public class Operation { ```java // typeinfo/SnowRemovalRobot.java + import java.util.*; public class SnowRemovalRobot implements Robot { private String name; + public SnowRemovalRobot(String name) { this.name = name; } + @Override - public String name() { + public String name() { return name; } + @Override - public String model() { + public String model() { return "SnowBot Series 11"; } + private List ops = Arrays.asList( - new Operation( - () -> name + " can shovel snow", - () -> System.out.println( - name + " shoveling snow")), - new Operation( - () -> name + " can chip ice", - () -> System.out.println(name + " chipping ice")), - new Operation( - () -> name + " can clear the roof", - () -> System.out.println( - name + " clearing roof"))); + new Operation( + () -> name + " can shovel snow", + () -> System.out.println( + name + " shoveling snow")), + new Operation( + () -> name + " can chip ice", + () -> System.out.println(name + " chipping ice")), + new Operation( + () -> name + " can clear the roof", + () -> System.out.println( + name + " clearing roof"))); + public List operations() { return ops; } + public static void main(String[] args) { Robot.test(new SnowRemovalRobot("Slusher")); } @@ -1926,52 +2027,61 @@ Slusher clearing roof ```java // typeinfo/NullRobot.java // Using a dynamic proxy to create an Optional + import java.lang.reflect.*; import java.util.*; import java.util.stream.*; + import onjava.*; class NullRobotProxyHandler -implements InvocationHandler { + implements InvocationHandler { private String nullName; private Robot proxied = new NRobot(); + NullRobotProxyHandler(Class type) { nullName = type.getSimpleName() + " NullRobot"; } + private class NRobot implements Null, Robot { @Override - public String name() { + public String name() { return nullName; } + @Override - public String model() { + public String model() { return nullName; } + @Override - public List operations() { + public List operations() { return Collections.emptyList(); } } + @Override - public Object - invoke(Object proxy, Method method, Object[] args) - throws Throwable { + public Object + invoke(Object proxy, Method method, Object[] args) + throws Throwable { return method.invoke(proxied, args); } } + public class NullRobot { public static Robot - newNullRobot(Class type) { - return (Robot)Proxy.newProxyInstance( - NullRobot.class.getClassLoader(), - new Class, - new NullRobotProxyHandler(type)); + newNullRobot(Class type) { + return (Robot) Proxy.newProxyInstance( + NullRobot.class.getClassLoader(), + new Class, + new NullRobotProxyHandler(type)); } + public static void main(String[] args) { Stream.of( - new SnowRemovalRobot("SnowBee"), - newNullRobot(SnowRemovalRobot.class) - ).forEach(Robot::test); + new SnowRemovalRobot("SnowBee"), + newNullRobot(SnowRemovalRobot.class) + ).forEach(Robot::test); } } ``` @@ -2019,11 +2129,15 @@ public interface A { ```java // typeinfo/InterfaceViolation.java // Sneaking around an interface + import typeinfo.interfacea.*; class B implements A { - public void f() {} - public void g() {} + public void f() { + } + + public void g() { + } } public class InterfaceViolation { @@ -2032,13 +2146,12 @@ public class InterfaceViolation { a.f(); // a.g(); // Compile error System.out.println(a.getClass().getName()); - if(a instanceof B) { - B b = (B)a; + if (a instanceof B) { + B b = (B) a; b.g(); } } } - ``` 输出结果: @@ -2058,6 +2171,7 @@ B ```java // typeinfo/packageaccess/HiddenC.java package typeinfo.packageaccess; + import typeinfo.interfacea.*; class C implements A { @@ -2065,22 +2179,28 @@ class C implements A { public void f() { System.out.println("public C.f()"); } + public void g() { System.out.println("public C.g()"); } + void u() { System.out.println("package C.u()"); } + protected void v() { System.out.println("protected C.v()"); } + private void w() { System.out.println("private C.w()"); } } public class HiddenC { - public static A makeA() { return new C(); } + public static A makeA() { + return new C(); + } } ``` @@ -2091,8 +2211,10 @@ public class HiddenC { ```java // typeinfo/HiddenImplementation.java // Sneaking around package hiding + import typeinfo.interfacea.*; import typeinfo.packageaccess.*; + import java.lang.reflect.*; public class HiddenImplementation { @@ -2112,6 +2234,7 @@ public class HiddenImplementation { callHiddenMethod(a, "v"); callHiddenMethod(a, "w"); } + static void callHiddenMethod(Object a, String methodName) throws Exception { Method g = a.getClass().getDeclaredMethod(methodName); g.setAccessible(true); @@ -2160,32 +2283,40 @@ java.lang.Object implements typeinfo.interfacea.A { ```java // typeinfo/InnerImplementation.java // Private inner classes can't hide from reflection + import typeinfo.interfacea.*; + class InnerA { private static class C implements A { public void f() { System.out.println("public C.f()"); } + public void g() { System.out.println("public C.g()"); } + void u() { System.out.println("package C.u()"); } + protected void v() { System.out.println("protected C.v()"); } + private void w() { System.out.println("private C.w()"); } } + public static A makeA() { return new C(); } } + public class InnerImplementation { public static void - main(String[] args) throws Exception { + main(String[] args) throws Exception { A a = InnerA.makeA(); a.f(); System.out.println(a.getClass().getName()); @@ -2214,32 +2345,39 @@ private C.w() ```java // typeinfo/AnonymousImplementation.java // Anonymous inner classes can't hide from reflection + import typeinfo.interfacea.*; + class AnonymousA { public static A makeA() { return new A() { public void f() { System.out.println("public C.f()"); } + public void g() { System.out.println("public C.g()"); } + void u() { System.out.println("package C.u()"); } + protected void v() { System.out.println("protected C.v()"); } + private void w() { System.out.println("private C.w()"); } } - ; + ; } } + public class AnonymousImplementation { public static void - main(String[] args) throws Exception { + main(String[] args) throws Exception { A a = AnonymousA.makeA(); a.f(); System.out.println(a.getClass().getName()); @@ -2267,26 +2405,29 @@ private C.w() ```java // typeinfo/ModifyingPrivateFields.java + import java.lang.reflect.*; + class WithPrivateFinalField { private int i = 1; private final String s = "I'm totally safe"; private String s2 = "Am I safe?"; + @Override - public String toString() { + public String toString() { return "i = " + i + ", " + s + ", " + s2; } } + public class ModifyingPrivateFields { - public static void - main(String[] args) throws Exception { + public static void main(String[] args) throws Exception { WithPrivateFinalField pf = - new WithPrivateFinalField(); + new WithPrivateFinalField(); System.out.println(pf); Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); System.out.println( - "f.getInt(pf): " + f.getint(pf)); + "f.getInt(pf): " + f.getint(pf)); f.setint(pf, 47); System.out.println(pf); f = pf.getClass().getDeclaredField("s"); From b36d9897de441f91beacb509d831405cbfcb6260 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Thu, 7 Nov 2019 21:23:51 +0800 Subject: [PATCH 077/371] =?UTF-8?q?revision[18]=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/16-Validating-Your-Code.md | 2 +- docs/book/17-Files.md | 4 +- docs/book/18-Strings.md | 775 ++++++++++++++------------- 3 files changed, 392 insertions(+), 389 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index f143721b..46306059 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -28,7 +28,7 @@ C 风格的语言,尤其是 C++,通常会认为性能比安全更重要。 #### JUnit -最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为[注解](./Annotations.md)一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本 +最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为 [注解](./Annotations.md) 一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本 在 JUnit 最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit 将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。 diff --git a/docs/book/17-Files.md b/docs/book/17-Files.md index 4106bcc0..c92cb291 100644 --- a/docs/book/17-Files.md +++ b/docs/book/17-Files.md @@ -5,7 +5,7 @@ # 第十七章 文件 >在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。 -这种"困难方式"的全部细节都在 [Appendix: I/O Streams](.\Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的,由于 I/O 糟糕的设计以至于 +这种"困难方式"的全部细节都在 [Appendix: I/O Streams](./Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的,由于 I/O 糟糕的设计以至于 很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。 好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 **java.nio.file** 包下面,过去人们通常把 **nio** 中的 **n** 理解为 **new** 即新的 **io**,现在更应该当成是 **non-blocking** 非阻塞 **io**(**io**就是*input/output输入/输出*)。**java.nio.file** 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件: @@ -514,7 +514,7 @@ sun.nio.fs.WindowsFileSystemProvider@6d06d69c File Attribute Views: [owner, dos, acl, basic, user] */ ``` -一个**FileSystem** 对象也能生成 **WatchService** 和 **PathMatcher** 对象,将会在接下来两章中详细讲解。 +一个 **FileSystem** 对象也能生成 **WatchService** 和 **PathMatcher** 对象,将会在接下来两章中详细讲解。 diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index f8319026..4282fc00 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -10,7 +10,7 @@ ## 字符串的不可变 -`String` 对象是不可变的。查看 JDK 文档你就会发现,`String` 类中每一个看起来会修改 `String` 值的方法,实际上都是创建了一个全新的 `String` 对象,以包含修改后的字符串内容。而最初的`String` 对象则丝毫未动。 +`String` 对象是不可变的。查看 JDK 文档你就会发现,`String` 类中每一个看起来会修改 `String` 值的方法,实际上都是创建了一个全新的 `String` 对象,以包含修改后的字符串内容。而最初的 `String` 对象则丝毫未动。 看看下面的代码: ```java @@ -57,10 +57,10 @@ String x = Immutable.upcase(s); // strings/Concatenation.java public class Concatenation { - public static void main(String[] args) { - String mango = "mango"; - String s = "abc" + mango + "def" + 47; - System.out.println(s); + public static void main(String[] args) { + String mango = "mango"; + String s = "abc" + mango + "def" + 47; + System.out.println(s); } } /* Output: @@ -71,7 +71,7 @@ abcmangodef47 这种方式当然是可行的,但是为了生成最终的 `String` 对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。 -想看看以上代码到底是如何工作的吗?可以用JDK自带的 `javap` 工具来反编译以上代码。命令如下: +想看看以上代码到底是如何工作的吗?可以用 JDK 自带的 `javap` 工具来反编译以上代码。命令如下: ```java javap -c Concatenation ``` @@ -158,9 +158,9 @@ public java.lang.String implicit(java.lang.String[]); 51: aload_2 52: areturn ``` -注意从第 16 行到第 48 行构成了一个循环体。第 16 行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第 51 行。第 48 行:重新回到循环体的起始位置(第 12 行)。注意:`StringBuilder` 是在循环内构造的,这意味着每进行一次循环,会创建一个新的`StringBuilder`对象。 +注意从第 16 行到第 48 行构成了一个循环体。第 16 行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第 51 行。第 48 行:重新回到循环体的起始位置(第 12 行)。注意:`StringBuilder` 是在循环内构造的,这意味着每进行一次循环,会创建一个新的 `StringBuilder` 对象。 -下面是`explicit()`方法对应的字节码: +下面是 `explicit()` 方法对应的字节码: ```x86asm public java.lang.String explicit(java.lang.String[]); 0: new #3 // StringBuilder @@ -411,15 +411,15 @@ Terry The Turtle is at (3,3) 所有的 `tommy` 都将输出到 `System.out`,而所有的 `terry` 则都输出到 `System.out` 的一个别名中。`Formatter` 的重载构造器支持输出到多个路径,不过最常用的还是 `PrintStream()`(如上例)、`OutputStream` 和 `File`。你可以在 [附录:流式 I/O](././Appendix-IO-Streams.md) 中了解更多信息。 ### 格式化修饰符 -在插入数据时,如果想要优化空格与对齐,你需要更精细复杂的格式修饰符。以下是其抽象语法: +在插入数据时,如果想要优化空格与对齐,你需要更精细复杂的格式修饰符。以下是其通用语法: ```java %[argument_index$][flags][width][.precision]conversion ``` -最常见的应用是控制一个字段的最小长度,这可以通过指定*width*来实现。`Formatter`对象通过在必要时添加空格,来确保一个字段至少达到设定长度。默认情况下,数据是右对齐的,不过可以通过使用`-`标志来改变对齐方向。 +最常见的应用是控制一个字段的最小长度,这可以通过指定 *width* 来实现。`Formatter `对象通过在必要时添加空格,来确保一个字段至少达到设定长度。默认情况下,数据是右对齐的,不过可以通过使用 `-` 标志来改变对齐方向。 -与*width*相对的是*precision*,用于指定最大长度。*width*可以应用于各种类型的数据转换,并且其行为方式都一样。*precision*则不然,不是所有的类型都能使用*precision*,而且,应用于不同类型的数据转换时,*precision*的意义也不同。在将*precision*应用于`String`时,它表示打印`string`时输出字符的最大数量。而在将*precision*应用于浮点数时,它表示小数部分要显示出来的位数(默认是6位小数),如果小数位数过多则舍入,太少则在尾部补零。由于整数没有小数部分,所以*precision*无法应用于整数,如果你对整数应用*precision*,则会触发异常。 +与 *width* 相对的是 *precision*,用于指定最大长度。*width* 可以应用于各种类型的数据转换,并且其行为方式都一样。*precision* 则不然,当应用于不同类型的数据转换时,*precision* 的意义也不同。在将 *precision* 应用于 `String` 时,它表示打印 `string` 时输出字符的最大数量。而在将 *precision* 应用于浮点数时,它表示小数部分要显示出来的位数(默认是 6 位小数),如果小数位数过多则舍入,太少则在尾部补零。由于整数没有小数部分,所以 *precision* 无法应用于整数,如果你对整数应用 *precision*,则会触发异常。 -下面的程序应用格式修饰符来打印一个购物收据。这是*Builder*设计模式的一个简单实现,即先创建一个初始对象,然后逐渐添加新东西,最后调用`build()`方法完成构建: +下面的程序应用格式修饰符来打印一个购物收据。这是 *Builder* 设计模式的一个简单实现,即先创建一个初始对象,然后逐渐添加新东西,最后调用 `build()` 方法完成构建: ```java // strings/ReceiptBuilder.java import java.util.*; @@ -467,10 +467,10 @@ Tax 2.80 Total 49.39 */ ``` -通过传入一个`StringBuilder`对象到`Formatter`的构造器,我们指定了一个容器来构建目标`String`。你也可以通过不同的构造器参数,把结果输出到标准输出,甚至是一个文件里。 +通过传入一个 `StringBuilder` 对象到 `Formatter` 的构造器,我们指定了一个容器来构建目标 `String`。你也可以通过不同的构造器参数,把结果输出到标准输出,甚至是一个文件里。 -正如你所见,通过相当简洁的语法,`Formatter`提供了对空格与对齐的强大控制能力。在该程序中,为了恰当地控制间隔,格式化字符串被重复利用了多遍。 -### `Formatter`转换 +正如你所见,通过相当简洁的语法,`Formatter ` 提供了对空格与对齐的强大控制能力。在该程序中,为了恰当地控制间隔,格式化字符串被重复利用了多遍。 +### `Formatter` 转换 下面的表格展示了最常用的类型转换: | 类型 | 含义 | @@ -602,11 +602,11 @@ h: 4d5 ``` 被注释的代码表示,针对相应类型的变量,这些转换是无效的。如果执行这些转换,则会触发异常。 -注意,程序中的每个变量都用到了`b`转换。虽然它对各种类型都是合法的,但其行为却不一定与你想象的一致。对于`boolean`基本类型或`Boolean`对象,其转换结果是对应的`true`或`false`。但是,对其他类型的参数,只要该参数不为`null`,其转换结果永远都是`true`。即使是数字0,转换结果依然为`true`,而这在其他语言中(包括C),往往转换为`false`。所以,将`b`应用于非布尔类型的对象时请格外小心。 +注意,程序中的每个变量都用到了 `b` 转换。虽然它对各种类型都是合法的,但其行为却不一定与你想象的一致。对于 `boolean` 基本类型或 `Boolean` 对象,其转换结果是对应的 `true` 或 `false`。但是,对其他类型的参数,只要该参数不为 `null`,其转换结果永远都是 `true`。即使是数字 0,转换结果依然为 `true`,而这在其他语言中(包括C),往往转换为 `false`。所以,将 `b` 应用于非布尔类型的对象时请格外小心。 -还有许多不常用的类型转换与格式修饰符选项,你可以在JDK文档中的`Formatter`类部分找到它们。 +还有许多不常用的类型转换与格式修饰符选项,你可以在 JDK 文档中的 `Formatter` 类部分找到它们。 ### `String.format()` -Java SE5也参考了C中的`sprintf()`方法,以生成格式化的`String`对象。`String.format()`是一个`static`方法,它接受与`Formatter.format()`方法一样的参数,但返回一个`String`对象。当你只需使用一次`format()`方法的时候,`String.format()`用起来很方便。例如: +Java SE5 也参考了 C 中的 `sprintf()` 方法,以生成格式化的 `String` 对象。`String.format()` 是一个 `static` 方法,它接受与 `Formatter.format()` 方法一样的参数,但返回一个 `String` 对象。当你只需使用一次 `format()` 方法的时候,`String.format()` 用起来很方便。例如: ```java // strings/DatabaseException.java @@ -629,9 +629,9 @@ Output: DatabaseException: (t3, q7) Write failed */ ``` -其实在`String.format()`内部,它也是创建了一个`Formatter`对象,然后将你传入的参数转给`Formatter`。不过,与其自己做这些事情,不如使用便捷的`String.format()`方法,何况这样的代码更清晰易读。 +其实在 `String.format()` 内部,它也是创建了一个 `Formatter` 对象,然后将你传入的参数转给 `Formatter`。不过,与其自己做这些事情,不如使用便捷的 `String.format()` 方法,何况这样的代码更清晰易读。 #### 一个十六进制转储(dump)工具 -在第二个例子中,我们把二进制文件转换为十六进制格式。下面的小工具使用了`String.format()`方法,以可读的十六进制格式将字节数组打印出来: +在第二个例子中,我们把二进制文件转换为十六进制格式。下面的小工具使用了 `String.format()` 方法,以可读的十六进制格式将字节数组打印出来: ```java // strings/Hex.java // {java onjava.Hex} @@ -641,27 +641,27 @@ import java.nio.file.*; public class Hex { public static String format(byte[] data) { - StringBuilder result = new StringBuilder(); - int n = 0; - for(byte b : data) { - if(n % 16 == 0) - result.append(String.format("%05X: ", n)); - result.append(String.format("%02X ", b)); - n++; - if(n % 16 == 0) result.append("\n"); - } - result.append("\n"); - return result.toString(); + StringBuilder result = new StringBuilder(); + int n = 0; + for(byte b : data) { + if(n % 16 == 0) + result.append(String.format("%05X: ", n)); + result.append(String.format("%02X ", b)); + n++; + if(n % 16 == 0) result.append("\n"); + } + result.append("\n"); + return result.toString(); } public static void main(String[] args) throws Exception { - if(args.length == 0) - // Test by displaying this class file: - System.out.println(format( - Files.readAllBytes(Paths.get( - "build/classes/main/onjava/Hex.class")))); - else - System.out.println(format( - Files.readAllBytes(Paths.get(args[0])))); + if(args.length == 0) + // Test by displaying this class file: + System.out.println(format( + Files.readAllBytes(Paths.get( + "build/classes/main/onjava/Hex.class")))); + else + System.out.println(format( + Files.readAllBytes(Paths.get(args[0])))); } } /* Output: (First 6 Lines) @@ -674,36 +674,36 @@ public class Hex { ... */ ``` -为了打开及读入二进制文件,我们用到了另一个工具`Files.readAllBytes()`,这已经在[Files章节](#)介绍过了。这里的`readAllBytes()`方法将整个文件以`byte`数组的形式返回。 - +为了打开及读入二进制文件,我们用到了另一个工具 `Files.readAllBytes()`,这已经在 [Files章节](./17-Files.md) 介绍过了。这里的 `readAllBytes()` 方法将整个文件以 `byte` 数组的形式返回。 + ## 正则表达式 -很久之前,*正则表达式*就已经整合到标准Unix工具集之中,例如sed、awk和程序语言之中了,如Python和Perl(有些人认为正是正则表达式促成了Perl的成功)。而在Java中,字符串操作还主要集中于`String`、`StringBuffer`和`StringTokenizer`类。与正则表达式相比较,它们只能提供相当简单的功能。 +很久之前,*正则表达式*就已经整合到标准 Unix 工具集之中,例如 sed、awk 和程序语言之中了,如 Python 和Perl(有些人认为正是正则表达式促成了 Perl 的成功)。而在 Java 中,字符串操作还主要集中于`String`、`StringBuffer` 和 `StringTokenizer` 类。与正则表达式相比较,它们只能提供相当简单的功能。 -正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入`String`进行搜索。一旦找到了匹配这些模式的部分,你就能随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。正则表达式提供了一种完全通用的方式,能够解决各种`String`处理相关的问题:匹配、选择、编辑以及验证。 +正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入 `String` 进行搜索。一旦找到了匹配这些模式的部分,你就能随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。正则表达式提供了一种完全通用的方式,能够解决各种 `String` 处理相关的问题:匹配、选择、编辑以及验证。 ### 基础 一般来说,正则表达式就是以某种方式来描述字符串,因此你可以说:“如果一个字符串含有这些东西,那么它就是我正在找的东西。”例如,要找一个数字,它可能有一个负号在最前面,那你就写一个负号加上一个问号,就像这样: ```java -? ``` -要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用`\d`表示一位数字。如果在其他语言中使用过正则表达式,那你可能就能发现Java对反斜线\的不同处理方式。在其他语言中,`\\`表示“我想要在正则表达式中插入一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”而在Java中,`\\`的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式因该是`\\d`。如果你想插入一个普通的反斜线,应该这样写`\\\`。不过换行符和制表符之类的东西只需要使用单反斜线:`\n\t`。 [^2] +要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用 `\d` 表示一位数字。如果在其他语言中使用过正则表达式,那你可能就能发现 Java 对反斜线 \ 的不同处理方式。在其他语言中,`\\` 表示“我想要在正则表达式中插入一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”而在Java中,`\\` 的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式应该是 `\\d`。如果你想插入一个普通的反斜线,应该这样写 `\\\`。不过换行符和制表符之类的东西只需要使用单反斜线:`\n\t`。 [^2] -要表示“一个或多个之前的表达式”,应该使用`+`。所以,如果要表示“可能有一个负号,后面跟着一位或多位数字”,可以这样: +要表示“一个或多个之前的表达式”,应该使用 `+`。所以,如果要表示“可能有一个负号,后面跟着一位或多位数字”,可以这样: ```java -?\\d+ ``` -应用正则表达式最简单的途径,就是利用`String`类内建的功能。例如,你可以检查一个`String`是否匹配如上所述的正则表达式: +应用正则表达式最简单的途径,就是利用 `String` 类内建的功能。例如,你可以检查一个 `String` 是否匹配如上所述的正则表达式: ```java // strings/IntegerMatch.java public class IntegerMatch { - public static void main(String[] args) { - System.out.println("-1234".matches("-?\\d+")); - System.out.println("5678".matches("-?\\d+")); - System.out.println("+911".matches("-?\\d+")); - System.out.println("+911".matches("(|-\\+)?\\d+")); - } + public static void main(String[] args) { + System.out.println("-1234".matches("-?\\d+")); + System.out.println("5678".matches("-?\\d+")); + System.out.println("+911".matches("-?\\d+")); + System.out.println("+911".matches("(-|\\+)?\\d+")); + } } /* Output: true @@ -712,30 +712,31 @@ false true */ ``` -前两个字符串都满足对应的正则表达式,匹配成功。第三个字符串以`+`开头,这也是一个合法的符号,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,用括号将表达式进行分组,用竖线` | `表示或操作。也就是: +前两个字符串都满足对应的正则表达式,匹配成功。第三个字符串以 `+` 开头,这也是一个合法的符号,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,用括号将表达式进行分组,用竖线 ` | ` 表示或操作。也就是: ```java (-|\\+)? ``` -这个正则表达式表示字符串的起始字符可能是一个`-`或`+`,或者二者都没有(因为后面跟着`?`修饰符)。因为字符`+`在正则表达式中有特殊的意义,所以必须使用`\\`将其转义,使之成为表达式中的一个普通字符。 +这个正则表达式表示字符串的起始字符可能是一个 `-` 或 `+`,或者二者都没有(因为后面跟着 `?` 修饰符)。因为字符 `+` 在正则表达式中有特殊的意义,所以必须使用 `\\` 将其转义,使之成为表达式中的一个普通字符。 + +`String`类还自带了一个非常有用的正则表达式工具——`split()` 方法,其功能是“将字符串从正则表达式匹配的地方切开。” -`String`类还自带了一个非常有用的正则表达式工具——`split()`方法,其功能是“将字符串从正则表达式匹配的地方切开。” ```java // strings/Splitting.java import java.util.*; public class Splitting { - public static String knights = - "Then, when you have found the shrubbery, " + - "you must cut down the mightiest tree in the " + - "forest...with... a herring!"; - public static void split(String regex) { - System.out.println( - Arrays.toString(knights.split(regex))); + public static String knights = + "Then, when you have found the shrubbery, " + + "you must cut down the mightiest tree in the " + + "forest...with... a herring!"; + public static void split(String regex) { + System.out.println( + Arrays.toString(knights.split(regex))); + } + public static void main(String[] args) { + split(" "); // Doesn't have to contain regex chars + split("\\W+"); // Non-word characters + split("n\\W+"); // 'n' followed by non-words } - public static void main(String[] args) { - split(" "); // Doesn't have to contain regex chars - split("\\W+"); // Non-word characters - split("n\\W+"); // 'n' followed by non-words - } } /* Output: [Then,, when, you, have, found, the, shrubbery,, you, @@ -749,24 +750,24 @@ dow, the mightiest tree i, the forest...with... a herring!] */ ``` -首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中并不包含任何特殊字符。因此第一个`split()`只是按空格来划分字符串。 +首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中并不包含任何特殊字符。因此第一个 `split()` 只是按空格来划分字符串。 -第二个和第三个`split()`都用到了`\\W`,它的意思是一个非单词字符(如果W小写,`\\w`,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个`split()`表示“字母`n`后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。 +第二个和第三个 `split()` 都用到了 `\\W`,它的意思是一个非单词字符(如果 W 小写,`\\w`,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个 `split()` 表示“字母 `n` 后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。 -`String.split()`还有一个重载的版本,它允许你限制字符串分割的次数。 +`String.split()` 还有一个重载的版本,它允许你限制字符串分割的次数。 用正则表达式进行替换操作时,你可以只替换第一处匹配,也可以替换所有的匹配: ```java // strings/Replacing.java public class Replacing { - static String s = Splitting.knights; - public static void main(String[] args) { - System.out.println( - s.replaceFirst("f\\w+", "located")); - System.out.println( - s.replaceAll("shrubbery|tree|herring","banana")); - } + static String s = Splitting.knights; + public static void main(String[] args) { + System.out.println( + s.replaceFirst("f\\w+", "located")); + System.out.println( + s.replaceAll("shrubbery|tree|herring","banana")); + } } /* Output: Then, when you have located the shrubbery, you must cut @@ -776,13 +777,13 @@ Then, when you have found the banana, you must cut down the mightiest banana in the forest...with... a banana! */ ``` -第一个表达式要匹配的是,以字母`f`开头,后面跟一个或多个字母(注意这里的`w`是小写的)。并且只替换掉第一个匹配的部分,所以“found”被替换成“located”。 +第一个表达式要匹配的是,以字母 `f` 开头,后面跟一个或多个字母(注意这里的 `w` 是小写的)。并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。 第二个表达式要匹配的是三个单词中的任意一个,因为它们以竖线分割表示“或”,并且替换所有匹配的部分。 -稍后你会看到,`String`之外的正则表达式还有更强大的替换工具,例如,可以通过方法调用执行替换。而且,如果正则表达式不是只使用一次的话,非`String`对象的正则表达式明显具备更佳的性能。 +稍后你会看到,`String` 之外的正则表达式还有更强大的替换工具,例如,可以通过方法调用执行替换。而且,如果正则表达式不是只使用一次的话,非 `String` 对象的正则表达式明显具备更佳的性能。 ### 创建正则表达式 -我们首先从正则表达式可能存在的构造集中选取一个很有用的子集,以此开始学习正则表达式。正则表达式的完整构造子列表,请参考JDK文档`java.util.regex`包中的`Pattern`类。 +我们首先从正则表达式可能存在的构造集中选取一个很有用的子集,以此开始学习正则表达式。正则表达式的完整构造子列表,请参考JDK文档 `java.util.regex` 包中的 `Pattern`类。 | 表达式 | 含义 | | :---- | :---- | @@ -812,12 +813,12 @@ the mightiest banana in the forest...with... a banana! | `\w` | 词字符(`[a-zA-Z_0-9]`) | | `\W` | 非词字符(`[^\w]`) | -这里只列出了部分常用的表达式,你应该将JDK文档中`java.util.regex.Pattern`那一页加入浏览器书签中,以便在需要的时候方便查询。 +这里只列出了部分常用的表达式,你应该将JDK文档中 `java.util.regex.Pattern` 那一页加入浏览器书签中,以便在需要的时候方便查询。 | 逻辑操作符 | 含义 | | :----: | :---- | | `XY` | `Y`跟在`X`后面 | -| `X\|Y` | `X`或`Y` | +| `X|Y` | `X`或`Y` | | `(X)` | 捕获组(capturing group)。可以在表达式中用`\i`引用第i个捕获组 | 下面是不同的边界匹配符: @@ -835,14 +836,14 @@ the mightiest banana in the forest...with... a banana! // strings/Rudolph.java public class Rudolph { - public static void main(String[] args) { - for(String pattern : new String[]{ - "Rudolph", - "[rR]udolph", - "[rR][aeiou][a-z]ol.*", - "R.*" }) - System.out.println("Rudolph".matches(pattern)); - } + public static void main(String[] args) { + for(String pattern : new String[]{ + "Rudolph", + "[rR]udolph", + "[rR][aeiou][a-z]ol.*", + "R.*" }) + System.out.println("Rudolph".matches(pattern)); + } } /* Output: true @@ -862,7 +863,7 @@ true 用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也被称作懒惰的、最少匹配的、非贪婪的或不贪婪的。 + **占有型**: -目前,这种类型的量词只有在Java语言中才可用(在其他语言中不可用),并且也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于`String`时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更高效。 +目前,这种类型的量词只有在 Java 语言中才可用(在其他语言中不可用),并且也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于 `String` 时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更高效。 | 贪婪型 | 勉强型 | 占有型 | 如何匹配 | | ---- | ---- | ---- | ---- | @@ -873,7 +874,7 @@ true | `X{n,}` | `X{n,}?` | `X{n,}+` | 至少`n`次`X` | | `X{n,m}` | `X{n,m}?` | `X{n,m}+` | `X`至少`n`次,但不超过`m`次 | -应该非常清楚地意识到,表达式`X`通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。例如: +应该非常清楚地意识到,表达式 `X` 通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。例如: ```java abc+ ``` @@ -881,20 +882,20 @@ abc+ ```java (abc)+ ``` -你会发现,在使用正则表达式时很容易混淆,因为它是一种在Java之上的新语言。 +你会发现,在使用正则表达式时很容易混淆,因为它是一种在 Java 之上的新语言。 ### `CharSequence` -接口`CharSequence`从`CharBuffer`、`String`、`StringBuffer`、`StringBuilder`类中抽象出了字符序列的一般化定义: +接口 `CharSequence` 从 `CharBuffer`、`String`、`StringBuffer`、`StringBuilder` 类中抽象出了字符序列的一般化定义: ```java interface CharSequence { - char charAt(int i); - int length(); - CharSequence subSequence(int start, int end); - String toString(); + char charAt(int i); + int length(); + CharSequence subSequence(int start, int end); + String toString(); } ``` -因此,这些类都实现了该接口。多数正则表达式操作都接受`CharSequence`类型参数。 -### `Pattern`和`Matcher` -通常,比起功能有限的`String`类,我们更愿意构造功能强大的正则表达式对象。只需导入`java.util.regex`包,然后用`static Pattern.compile()`方法来编译你的正则表达式即可。它会根据你的`String`类型的正则表达式生成一个`Pattern`对象。接下来,把你想要检索的字符串传入`Pattern`对象的`matcher()`方法。`matcher()`方法会生成一个`Matcher`对象,它有很多功能可用(可以参考`java.util.regext.Matcher`的JDK文档)。例如,它的`replaceAll()`方法能将所有匹配的部分都替换成你传入的参数。 +因此,这些类都实现了该接口。多数正则表达式操作都接受 `CharSequence` 类型参数。 +### `Pattern` 和 `Matcher` +通常,比起功能有限的 `String` 类,我们更愿意构造功能强大的正则表达式对象。只需导入 `java.util.regex`包,然后用 `static Pattern.compile()` 方法来编译你的正则表达式即可。它会根据你的 `String` 类型的正则表达式生成一个 `Pattern` 对象。接下来,把你想要检索的字符串传入 `Pattern` 对象的 `matcher()` 方法。`matcher()` 方法会生成一个 `Matcher` 对象,它有很多功能可用(可以参考 `java.util.regext.Matcher` 的 JDK 文档)。例如,它的 `replaceAll()` 方法能将所有匹配的部分都替换成你传入的参数。 作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串。第一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输入的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须用引号括起来。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。[^3] ```java @@ -905,26 +906,26 @@ interface CharSequence { import java.util.regex.*; public class TestRegularExpression { - public static void main(String[] args) { - if(args.length < 2) { - System.out.println( - "Usage:\njava TestRegularExpression " + - "characterSequence regularExpression+"); - System.exit(0); + public static void main(String[] args) { + if(args.length < 2) { + System.out.println( + "Usage:\njava TestRegularExpression " + + "characterSequence regularExpression+"); + System.exit(0); + } + System.out.println("Input: \"" + args[0] + "\""); + for(String arg : args) { + System.out.println( + "Regular expression: \"" + arg + "\""); + Pattern p = Pattern.compile(arg); + Matcher m = p.matcher(args[0]); + while(m.find()) { + System.out.println( + "Match \"" + m.group() + "\" at positions " + + m.start() + "-" + (m.end() - 1)); + } + } } - System.out.println("Input: \"" + args[0] + "\""); - for(String arg : args) { - System.out.println( - "Regular expression: \"" + arg + "\""); - Pattern p = Pattern.compile(arg); - Matcher m = p.matcher(args[0]); - while(m.find()) { - System.out.println( - "Match \"" + m.group() + "\" at positions " + - m.start() + "-" + (m.end() - 1)); - } - } - } } /* Output: Input: "abcabcabcdefabc" @@ -942,40 +943,42 @@ Match "abc" at positions 12-14 ``` 还可以在控制台参数中加入`“(abc){2,}”`,看看执行结果。 -`Pattern`对象表示编译后的正则表达式。从这个例子可以看到,我们使用已编译的`Pattern`对象上的`matcher()`方法,加上一个输入字符串,从而共同构造了一个`Matcher`对象。同时,`Pattern`类还提供了一个`static`方法: +`Pattern` 对象表示编译后的正则表达式。从这个例子可以看到,我们使用已编译的 `Pattern` 对象上的 `matcher()` 方法,加上一个输入字符串,从而共同构造了一个 `Matcher` 对象。同时,`Pattern` 类还提供了一个`static`方法: + ```java static boolean matches(String regex, CharSequence input) ``` -该方法用以检查`regex`是否匹配整个`CharSequence`类型的`input`参数。编译后的`Pattern`对象还提供了`split()`方法,它从匹配了`regex`的地方分割输入字符串,返回分割后的子字符串`String`数组。 +该方法用以检查 `regex` 是否匹配整个 `CharSequence` 类型的 `input` 参数。编译后的 `Pattern` 对象还提供了 `split()` 方法,它从匹配了 `regex` 的地方分割输入字符串,返回分割后的子字符串 `String` 数组。 -通过调用`Pattern.matcher()`方法,并传入一个字符串参数,我们得到了一个`Matcher`对象。使用`Matcher`上的方法,我们将能够判断各种不同类型的匹配是否成功: +通过调用 `Pattern.matcher()` 方法,并传入一个字符串参数,我们得到了一个 `Matcher` 对象。使用 `Matcher` 上的方法,我们将能够判断各种不同类型的匹配是否成功: ```java boolean matches() boolean lookingAt() boolean find() boolean find(int start) ``` -其中的`matches()`方法用来判断整个输入字符串是否匹配正则表达式模式,而`lookingAt()`则用来判断该字符串(不必是整个字符串)的起始部分是否能够匹配模式。 +其中的 `matches()` 方法用来判断整个输入字符串是否匹配正则表达式模式,而 `lookingAt()` 则用来判断该字符串(不必是整个字符串)的起始部分是否能够匹配模式。 ### `find()` -`Matcher.find()`方法可用来在`CharSequence`中查找多个匹配。例如: +`Matcher.find()` 方法可用来在 `CharSequence` 中查找多个匹配。例如: + ```java // strings/Finding.java import java.util.regex.*; public class Finding { - public static void main(String[] args) { - Matcher m = Pattern.compile("\\w+") - .matcher( - "Evening is full of the linnet's wings"); - while(m.find()) - System.out.print(m.group() + " "); - System.out.println(); - int i = 0; - while(m.find(i)) { - System.out.print(m.group() + " "); - i++; - } - } + public static void main(String[] args) { + Matcher m = Pattern.compile("\\w+") + .matcher( + "Evening is full of the linnet's wings"); + while(m.find()) + System.out.print(m.group() + " "); + System.out.println(); + int i = 0; + while(m.find(i)) { + System.out.print(m.group() + " "); + i++; + } + } } /* Output: Evening is full of the linnet s wings @@ -984,18 +987,19 @@ ull ll l of of f the the he e linnet linnet innet nnet net et t s s wings wings ings ngs gs s */ ``` -模式`\\w+`将字符串划分为词。`find()`方法像迭代器那样向前遍历输入字符串。而第二个重载的`find()`接收一个整型参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果可以看出,后一个版本的`find()`方法能够根据其参数的值,不断重新设定搜索的起始位置。 +模式 `\\w+` 将字符串划分为词。`find()` 方法像迭代器那样向前遍历输入字符串。而第二个重载的 `find()` 接收一个整型参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果可以看出,后一个版本的 `find()` 方法能够根据其参数的值,不断重新设定搜索的起始位置。 ### 组(Groups) -组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个表达式,组号1表示被第一对括号括起来的组,以此类推。因此,下面这个表达式, +组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示整个表达式,组号 1 表示被第一对括号括起来的组,以此类推。因此,下面这个表达式, ```java A(B(C))D ``` -中有三个组:组0是`ABCD`,组1是`BC`,组2是`C`。 +中有三个组:组 0 是 `ABCD`,组 1 是 `BC`,组 2 是 `C`。 + +`Matcher` 对象提供了一系列方法,用以获取与组相关的信息: -`Matcher`对象提供了一系列方法,用以获取与组相关的信息: -+ `public int groupCount()` 返回该匹配器的模式中的分组数目,组0不包括在内。 -+ `public String group()` 返回前一次匹配操作(例如`find()`)的第0组(整个匹配)。 -+ `public String group(int i)` 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回`null`。 ++ `public int groupCount()` 返回该匹配器的模式中的分组数目,组 0 不包括在内。 ++ `public String group()` 返回前一次匹配操作(例如 `find()`)的第 0 组(整个匹配)。 ++ `public String group(int i)` 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回 `null`。 + `public int start(int group)` 返回在前一次匹配操作中寻找到的组的起始索引。 + `public int end(int group)` 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。 @@ -1005,25 +1009,25 @@ A(B(C))D import java.util.regex.*; public class Groups { - public static final String POEM = - "Twas brillig, and the slithy toves\n" + - "Did gyre and gimble in the wabe.\n" + - "All mimsy were the borogoves,\n" + - "And the mome raths outgrabe.\n\n" + - "Beware the Jabberwock, my son,\n" + - "The jaws that bite, the claws that catch.\n" + - "Beware the Jubjub bird, and shun\n" + - "The frumious Bandersnatch."; - public static void main(String[] args) { - Matcher m = Pattern.compile( - "(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$") - .matcher(POEM); - while(m.find()) { - for(int j = 0; j <= m.groupCount(); j++) - System.out.print("[" + m.group(j) + "]"); - System.out.println(); - } - } + public static final String POEM = + "Twas brillig, and the slithy toves\n" + + "Did gyre and gimble in the wabe.\n" + + "All mimsy were the borogoves,\n" + + "And the mome raths outgrabe.\n\n" + + "Beware the Jabberwock, my son,\n" + + "The jaws that bite, the claws that catch.\n" + + "Beware the Jubjub bird, and shun\n" + + "The frumious Bandersnatch."; + public static void main(String[] args) { + Matcher m = Pattern.compile( + "(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$") + .matcher(POEM); + while(m.find()) { + for(int j = 0; j <= m.groupCount(); j++) + System.out.print("[" + m.group(j) + "]"); + System.out.println(); + } + } } /* Output: [the slithy toves][the][slithy toves][slithy][toves] @@ -1039,55 +1043,58 @@ outgrabe.][raths][outgrabe.] Bandersnatch.][frumious][Bandersnatch.] */ ``` -这首诗来自于Lewis Carroll所写的*Through the Looking Glass*中的“Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(`\\S+`)及随后的任意数目的空白符(`\\s+`)所组成。目的是捕获每行的最后3个词,每行最后以`\$`结束。不过,在正常情况下是将`\$`与整个输入序列的末端相匹配。所以我们一定要显示地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记`(?m)`来完成(模式标记马上就会介绍)。 -### `start()`和`end()` -在匹配操作成功之后,`start()`返回先前匹配的起始位置的索引,而`end()`返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用`start()`或`end()`将会产生`IllegalStateException`。下面的示例还同时展示了`matches()`和`lookingAt()`的用法 [^4]: +这首诗来自于 Lewis Carroll 所写的 *Through the Looking Glass* 中的 “Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(`\\S+`)及随后的任意数目的空白符(`\\s+`)所组成。目的是捕获每行的最后3个词,每行最后以 `\$` 结束。不过,在正常情况下是将 `\$` 与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记 `(?m)` 来完成(模式标记马上就会介绍)。 +### `start()` 和 `end()` +在匹配操作成功之后,`start()` 返回先前匹配的起始位置的索引,而 `end()` 返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用 `start()` 或 `end()` 将会产生 `IllegalStateException`。下面的示例还同时展示了 `matches()` 和 `lookingAt()` 的用法 [^4]: ```java // strings/StartEnd.java import java.util.regex.*; public class StartEnd { - public static String input = - "As long as there is injustice, whenever a\n" + - "Targathian baby cries out, wherever a distress\n" + - "signal sounds among the stars " + - "... We'll be there.\n"+ - "This fine ship, and this fine crew ...\n" + - "Never give up! Never surrender!"; - private static class Display { - private boolean regexPrinted = false; - private String regex; - Display(String regex) { this.regex = regex; } - void display(String message) { - if(!regexPrinted) { - System.out.println(regex); - regexPrinted = true; - } - System.out.println(message); - } - } - static void examine(String s, String regex) { - Display d = new Display(regex); - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(s); - while(m.find()) - d.display("find() '" + m.group() + - "' start = "+ m.start() + " end = " + m.end()); - if(m.lookingAt()) // No reset() necessary - d.display("lookingAt() start = " - + m.start() + " end = " + m.end()); - if(m.matches()) // No reset() necessary - d.display("matches() start = " - + m.start() + " end = " + m.end()); - } - public static void main(String[] args) { - for(String in : input.split("\n")) { - System.out.println("input : " + in); - for(String regex : new String[]{"\\w*ere\\w*", - "\\w*ever", "T\\w+", "Never.*?!"}) - examine(in, regex); + public static String input = + "As long as there is injustice, whenever a\n" + + "Targathian baby cries out, wherever a distress\n" + + "signal sounds among the stars " + + "... We'll be there.\n"+ + "This fine ship, and this fine crew ...\n" + + "Never give up! Never surrender!"; + private static class Display { + private boolean regexPrinted = false; + private String regex; + Display(String regex) { this.regex = regex; } + + void display(String message) { + if(!regexPrinted) { + System.out.println(regex); + regexPrinted = true; + } + System.out.println(message); + } } - } + + static void examine(String s, String regex) { + Display d = new Display(regex); + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(s); + while(m.find()) + d.display("find() '" + m.group() + + "' start = "+ m.start() + " end = " + m.end()); + if(m.lookingAt()) // No reset() necessary + d.display("lookingAt() start = " + + m.start() + " end = " + m.end()); + if(m.matches()) // No reset() necessary + d.display("matches() start = " + + m.start() + " end = " + m.end()); + } + + public static void main(String[] args) { + for(String in : input.split("\n")) { + System.out.println("input : " + in); + for(String regex : new String[]{"\\w*ere\\w*", + "\\w*ever", "T\\w+", "Never.*?!"}) + examine(in, regex); + } + } } /* Output: input : As long as there is injustice, whenever a @@ -1121,13 +1128,14 @@ lookingAt() start = 0 end = 14 matches() start = 0 end = 31 */ ``` -注意,`find()`可以在输入的任意位置定位正则表达式,而`lookingAt()`和`matches()`只有在正则表达式与输入的最开始处就开始匹配时才会成功。`matches()`只有在整个输入都匹配正则表达式时才会成功,而`lookingAt()` [^5]只要输入的第一部分匹配就会成功。 -### `Pattern`标记 -`Pattern`类的`compile()`方法还有另一个版本,它接受一个标记参数,以调整匹配行为: +注意,`find()` 可以在输入的任意位置定位正则表达式,而 `lookingAt()` 和 `matches()` 只有在正则表达式与输入的最开始处就开始匹配时才会成功。`matches()` 只有在整个输入都匹配正则表达式时才会成功,而 `lookingAt()` [^5] 只要输入的第一部分匹配就会成功。 +### `Pattern` 标记 +`Pattern` 类的 `compile()` 方法还有另一个版本,它接受一个标记参数,以调整匹配行为: + ```java Pattern Pattern.compile(String regex, int flag) ``` -其中的`flag`来自以下`Pattern`类中的常量 +其中的 `flag` 来自以下 `Pattern` 类中的常量 | 编译标记 | 效果 | | ---- |---- | @@ -1139,7 +1147,7 @@ Pattern Pattern.compile(String regex, int flag) | `Pattern.UNICODE_CASE(?u)` | 当指定这个标记,并且开启`CASE_INSENSITIVE`时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行 | | `Pattern.UNIX_LINES(?d)` | 在这种模式下,在`.`、`^`和`$`的行为中,只识别行终止符`\n` | -在这些标记中,`Pattern.CASE_INSENSITIVE`、`Pattern.MULTILINE`以及`Pattern.COMMENTS`(对声明或文档有用)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。 +在这些标记中,`Pattern.CASE_INSENSITIVE`、`Pattern.MULTILINE` 以及 `Pattern.COMMENTS`(对声明或文档有用)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。 你还可以通过“或”(`|`)操作符组合多个标记的功能: ```java @@ -1147,16 +1155,16 @@ Pattern Pattern.compile(String regex, int flag) import java.util.regex.*; public class ReFlags { - public static void main(String[] args) { - Pattern p = Pattern.compile("^java", - Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); - Matcher m = p.matcher( - "java has regex\nJava has regex\n" + - "JAVA has pretty good regular expressions\n" + - "Regular expressions are in Java"); - while(m.find()) - System.out.println(m.group()); - } + public static void main(String[] args) { + Pattern p = Pattern.compile("^java", + Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + Matcher m = p.matcher( + "java has regex\nJava has regex\n" + + "JAVA has pretty good regular expressions\n" + + "Regular expressions are in Java"); + while(m.find()) + System.out.println(m.group()); + } } /* Output: java @@ -1164,9 +1172,10 @@ Java JAVA */ ``` -在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一行(从字符序列的第一个字符开始,至每一个行终止符)都进行匹配。注意,`group()`方法只返回已匹配的部分。 +在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一行(从字符序列的第一个字符开始,至每一个行终止符)都进行匹配。注意,`group()` 方法只返回已匹配的部分。 ### `split()` -`split()`方法将输入`String`断开成`String`对象数组,断开边界由正则表达式确定: +`split()`方法将输入 `String` 断开成 `String` 对象数组,断开边界由正则表达式确定: + ```java String[] split(CharSequence input) String[] split(CharSequence input, int limit) @@ -1178,28 +1187,28 @@ import java.util.regex.*; import java.util.*; public class SplitDemo { - public static void main(String[] args) { - String input = - "This!!unusual use!!of exclamation!!points"; - System.out.println(Arrays.toString( - Pattern.compile("!!").split(input))); - // Only do the first three: - System.out.println(Arrays.toString( - Pattern.compile("!!").split(input, 3))); - } + public static void main(String[] args) { + String input = + "This!!unusual use!!of exclamation!!points"; + System.out.println(Arrays.toString( + Pattern.compile("!!").split(input))); + // Only do the first three: + System.out.println(Arrays.toString( + Pattern.compile("!!").split(input, 3))); + } } /* Output: [This, unusual use, of exclamation, points] [This, unusual use, of exclamation!!points] */ ``` -第二种形式的`split()`方法可以限制将输入分割成字符串的数量。 +第二种形式的 `split()` 方法可以限制将输入分割成字符串的数量。 ### 替换操作 正则表达式在进行文本替换时特别方便,它提供了许多方法: -+ `replaceFirst(String replacement)` 以参数字符串`replacement`替换掉第一个匹配成功的部分。 -+ `replaceAll(String replacement)` 以参数字符串`replacement`替换所有匹配成功的部分。 -+ `appendReplacement(StringBuffer sbuf, String replacement)` 执行渐进式的替换,而不是像`replaceFirst()`和`replaceAll()`那样只替换第一个匹配或全部匹配。这是一个非常重要的方法。它允许你调用其他方法来生成或处理`replacement`(`replaceFirst()`和`replaceAll()`则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。 -+ `appendTail(StringBuffer sbuf)` 在执行了一次或多次`appendReplacement()`之后,调用此方法可以将输入字符串余下的部分复制到`sbuf`中。 ++ `replaceFirst(String replacement)` 以参数字符串 `replacement` 替换掉第一个匹配成功的部分。 ++ `replaceAll(String replacement)` 以参数字符串 `replacement` 替换所有匹配成功的部分。 ++ `appendReplacement(StringBuffer sbuf, String replacement)` 执行渐进式的替换,而不是像 `replaceFirst()` 和 `replaceAll()` 那样只替换第一个匹配或全部匹配。这是一个非常重要的方法。它允许你调用其他方法来生成或处理 `replacement`(`replaceFirst()` 和 `replaceAll()` 则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。 ++ `appendTail(StringBuffer sbuf)` 在执行了一次或多次 `appendReplacement()` 之后,调用此方法可以将输入字符串余下的部分复制到 `sbuf` 中。 下面的程序演示了如何使用这些替换方法。开头部分注释掉的文本,就是正则表达式要处理的输入字符串: ```java @@ -1215,34 +1224,33 @@ import java.util.stream.*; extracted block. !*/ public class TheReplacements { - public static void main(String[] args) throws Exception { - String s = Files.lines( - Paths.get("TheReplacements.java")) - .collect(Collectors.joining("\n")); - // Match specially commented block of text above: - Matcher mInput = Pattern.compile( - "/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s); - if(mInput.find()) - s = mInput.group(1); // Captured by parentheses - // Replace two or more spaces with a single space: - s = s.replaceAll(" {2,}", " "); - // Replace 1+ spaces at the beginning of each - // line with no spaces. Must enable MULTILINE mode: - s = s.replaceAll("(?m)^ +", ""); - System.out.println(s); - s = s.replaceFirst("[aeiou]", "(VOWEL1)"); - StringBuffer sbuf = new StringBuffer(); - Pattern p = Pattern.compile("[aeiou]"); - Matcher m = p.matcher(s); - // Process the find information as you - // perform the replacements: - while(m.find()) - m.appendReplacement( - sbuf, m.group().toUpperCase()); - // Put in the remainder of the text: - m.appendTail(sbuf); - System.out.println(sbuf); - } + public static void main(String[] args) throws Exception { + String s = Files.lines( + Paths.get("TheReplacements.java")) + .collect(Collectors.joining("\n")); + // Match specially commented block of text above: + Matcher mInput = Pattern.compile( + "/\\*!(.*)!\\*/", Pattern.DOTALL).matcher(s); + if(mInput.find()) + s = mInput.group(1); // Captured by parentheses + // Replace two or more spaces with a single space: + s = s.replaceAll(" {2,}", " "); + // Replace 1+ spaces at the beginning of each + // line with no spaces. Must enable MULTILINE mode: + s = s.replaceAll("(?m)^ +", ""); + System.out.println(s); + s = s.replaceFirst("[aeiou]", "(VOWEL1)"); + StringBuffer sbuf = new StringBuffer(); + Pattern p = Pattern.compile("[aeiou]"); + Matcher m = p.matcher(s); + // Process the find information as you + // perform the replacements: + while(m.find()) + m.appendReplacement(sbuf, m.group().toUpperCase()); + // Put in the remainder of the text: + m.appendTail(sbuf); + System.out.println(sbuf); + } } /* Output: Here's a block of text to use as input to @@ -1257,39 +1265,39 @@ thE spEcIAl dElImItErs, thEn prOcEss thE ExtrActEd blOck. */ ``` -此处使用上一章介绍过的[`Files`](#)类打开并读入文件。`Files.lines()`返回一个`Stream`对象,包含读入的所有行,`Collectors.joining()`在每一行的结尾追加参数字符序列,最终拼接成一个`String`对象。 +此处使用上一章介绍过的 [`Files`](./17-Files.md) 类打开并读入文件。`Files.lines()` 返回一个 `Stream` 对象,包含读入的所有行,`Collectors.joining()` 在每一行的结尾追加参数字符序列,最终拼接成一个 `String` 对象。 -`mInput`匹配`/*!`和`!*/`之间的所有文字(注意分组的括号)。接下来,将存在两个或两个以上空格的地方,缩减为一个空格,并且删除每行开头部分的所有空格(为了使每一行都达到这个效果,而不仅仅是删除文本开头部分的空格,这里特意开启了多行模式)。这两个替换操作所使用的的`replaceAll()`是`String`对象自带的方法,在这里,使用此方法更方便。注意,因为这两个替换操作都只使用了一次`replaceAll()`,所以,与其编译为`Pattern`,不如直接使用`String`的`replaceAll()`方法,而且开销也更小些。 +`mInput` 匹配 `/*!` 和 `!*/` 之间的所有文字(注意分组的括号)。接下来,将存在两个或两个以上空格的地方,缩减为一个空格,并且删除每行开头部分的所有空格(为了使每一行都达到这个效果,而不仅仅是删除文本开头部分的空格,这里特意开启了多行模式)。这两个替换操作所使用的的 `replaceAll()` 是 `String` 对象自带的方法,在这里,使用此方法更方便。注意,因为这两个替换操作都只使用了一次 `replaceAll()`,所以,与其编译为 `Pattern`,不如直接使用 `String` 的 `replaceAll()` 方法,而且开销也更小些。 -`replaceFirst()`只对找到的第一个匹配进行替换。此外,`replaceFirst()`和`replaceAll()`方法用来替换的只是普通字符串,所以,如果想对这些替换字符串进行某些特殊处理,这两个方法时无法胜任的。如果你想要那么做,就应该使用`appendReplacement()`方法。该方法允许你在执行替换的过程中,操作用来替换的字符串。在这个例子中,先构造了`sbuf`用来保存最终结果,然后用`group()`选择一个组,并对其进行处理,将正则表达式找到的元音字母替换成大些字母。一般情况下,你应该遍历执行所有的替换操作,然后再调用`appendTail()`方法,但是,如果你想模拟`replaceFirst()`(或替换n次)的行为,那就只需要执行一次替换,然后调用`appendTail()`方法,将剩余未处理的部分存入`sbuf`即可。 +`replaceFirst()` 只对找到的第一个匹配进行替换。此外,`replaceFirst()` 和 `replaceAll()` 方法用来替换的只是普通字符串,所以,如果想对这些替换字符串进行某些特殊处理,这两个方法时无法胜任的。如果你想要那么做,就应该使用 `appendReplacement()` 方法。该方法允许你在执行替换的过程中,操作用来替换的字符串。在这个例子中,先构造了 `sbuf` 用来保存最终结果,然后用 `group()` 选择一个组,并对其进行处理,将正则表达式找到的元音字母替换成大些字母。一般情况下,你应该遍历执行所有的替换操作,然后再调用 `appendTail()` 方法,但是,如果你想模拟 `replaceFirst()`(或替换n次)的行为,那就只需要执行一次替换,然后调用 `appendTail()` 方法,将剩余未处理的部分存入 `sbuf` 即可。 -同时,`appendReplacement()`方法还允许你通过`\$g`直接找到匹配的某个组,这里的`g`就是组号。然而,它只能应付一些简单的处理,无法实现类似前面这个例子中的功能。 +同时,`appendReplacement()` 方法还允许你通过 `\$g` 直接找到匹配的某个组,这里的 `g` 就是组号。然而,它只能应付一些简单的处理,无法实现类似前面这个例子中的功能。 ### `reset()` -通过`reset()`方法,可以将现有的`Matcher`对象应用于一个新的字符序列: +通过 `reset()` 方法,可以将现有的 `Matcher` 对象应用于一个新的字符序列: ```java // strings/Resetting.java import java.util.regex.*; public class Resetting { - public static void main(String[] args) throws Exception { - Matcher m = Pattern.compile("[frb][aiu][gx]") - .matcher("fix the rug with bags"); - while(m.find()) - System.out.print(m.group() + " "); - System.out.println(); - m.reset("fix the rig with rags"); - while(m.find()) - System.out.print(m.group() + " "); - } + public static void main(String[] args) throws Exception { + Matcher m = Pattern.compile("[frb][aiu][gx]") + .matcher("fix the rug with bags"); + while(m.find()) + System.out.print(m.group() + " "); + System.out.println(); + m.reset("fix the rig with rags"); + while(m.find()) + System.out.print(m.group() + " "); + } } /* Output: fix rug bag fix rig rag */ ``` -使用不带参数的`reset()`方法,可以将`Matcher`对象重新设置到当前字符序列的起始位置。 -### 正则表达式与Java I/O -到目前为止,我们看到的例子都是将正则表达式用于静态的字符串。下面的例子将向你演示,如何应用正则表达式在一个文件中进行搜索匹配操作。`JGrep.java`的灵感源自于Unix上的*grep*。它有两个参数:文件名以及要匹配的正则表达式。输出的是每行有匹配的部分以及匹配部分在行中的位置。 +使用不带参数的 `reset()` 方法,可以将 `Matcher` 对象重新设置到当前字符序列的起始位置。 +### 正则表达式与 Java I/O +到目前为止,我们看到的例子都是将正则表达式用于静态的字符串。下面的例子将向你演示,如何应用正则表达式在一个文件中进行搜索匹配操作。`JGrep.java` 的灵感源自于 Unix 上的 *grep*。它有两个参数:文件名以及要匹配的正则表达式。输出的是每行有匹配的部分以及匹配部分在行中的位置。 ```java // strings/JGrep.java // A very simple version of the "grep" program @@ -1300,70 +1308,67 @@ import java.nio.file.*; import java.util.stream.*; public class JGrep {   - public static void main(String[] args) throws Exception {     - if(args.length < 2) {       - System.out.println(         - "Usage: java JGrep file regex");       - System.exit(0);   - }     - Pattern p = Pattern.compile(args[1]);     - // Iterate through the lines of the input file:     - int index = 0;     - Matcher m = p.matcher("");     - for(String line :         - Files.readAllLines(Paths.get(args[0]))) {       - m.reset(line);       - while(m.find())         - System.out.println(index++ + ": " +           - m.group() + ": " + m.start());   - } - } + public static void main(String[] args) throws Exception {     + if(args.length < 2) {       + System.out.println(         + "Usage: java JGrep file regex");       + System.exit(0);   + }     + Pattern p = Pattern.compile(args[1]);     + // Iterate through the lines of the input file:     + int index = 0;     + Matcher m = p.matcher("");     + for(String line: Files.readAllLines(Paths.get(args[0]))) {       + m.reset(line);       + while(m.find())         + System.out.println(index++ + ": " +           + m.group() + ": " + m.start());   + } + } } /* Output: 0: for: 4 1: for: 4 */ ``` -`Files.readAllLines()`返回一个`List`对象,这意味着可以用*for-in*进行遍历。虽然可以在`for`循环内部创建一个新的`Matcher`对象,但是,在循环体外创建一个空的`Matcher`对象,然后用`reset()`方法每次为`Matcher`加载一行输入,这种处理会有一定的性能优化。最后用`find()`搜索结果。 +`Files.readAllLines()` 返回一个 `List` 对象,这意味着可以用 *for-in* 进行遍历。虽然可以在 `for` 循环内部创建一个新的 `Matcher` 对象,但是,在循环体外创建一个空的 `Matcher` 对象,然后用 `reset()` 方法每次为 `Matcher` 加载一行输入,这种处理会有一定的性能优化。最后用 `find()` 搜索结果。 -这里读入的测试参数是`JGrep.java`文件,然后搜索一`[Ssct]`开头的单词。 +这里读入的测试参数是 `JGrep.java` 文件,然后搜索以 `[Ssct]` 开头的单词。 -如果想要更深入地学习正则表达式,你可以阅读Jeffrey E. F. Friedl的《精通正则表达式(第2版)》。网络上也有很多正则表达式的介绍,你还可以从Perl和Python等其他语言的文档中找到有用的信息。 +如果想要更深入地学习正则表达式,你可以阅读 Jeffrey E. F. Friedl 的《精通正则表达式(第2版)》。网络上也有很多正则表达式的介绍,你还可以从 Perl 和 Python 等其他语言的文档中找到有用的信息。 ## 扫描输入 -到目前为止,从文件或标准输入读取数据还是一件相当痛苦的事情。一般的解决办法就是读入一行文本,对其进行分词,然后使用`Integer`、`Double`等类的各种解析方法来解析数据: +到目前为止,从文件或标准输入读取数据还是一件相当痛苦的事情。一般的解决办法就是读入一行文本,对其进行分词,然后使用 `Integer`、`Double` 等类的各种解析方法来解析数据: ```java // strings/SimpleRead.java import java.io.*; public class SimpleRead {   - public static BufferedReader input =     - new BufferedReader(new StringReader(     - "Sir Robin of Camelot\n22 1.61803"));   - public static void main(String[] args) {     - try {       - System.out.println("What is your name?");       - String name = input.readLine();       - System.out.println(name);       - System.out.println("How old are you? " +         - "What is your favorite double?");       - System.out.println("(input: )");       - String numbers = input.readLine();       - System.out.println(numbers);       - String[] numArray = numbers.split(" ");       - int age = Integer.parseInt(numArray[0]);       - double favorite = Double.parseDouble(numArray[1]);       - System.out.format("Hi %s.%n", name);       - System.out.format("In 5 years you will be %d.%n",         - age + 5);       - System.out.format("My favorite double is %f.",         - favorite / 2);   - } catch(IOException e) {       - System.err.println("I/O exception");   - } - } + public static BufferedReader input =     + new BufferedReader(new StringReader(     + "Sir Robin of Camelot\n22 1.61803"));   + public static void main(String[] args) {     + try {       + System.out.println("What is your name?");       + String name = input.readLine();       + System.out.println(name);       + System.out.println("How old are you? " +         + "What is your favorite double?");       + System.out.println("(input: )");       + String numbers = input.readLine();       + System.out.println(numbers);       + String[] numArray = numbers.split(" ");       + int age = Integer.parseInt(numArray[0]);       + double favorite = Double.parseDouble(numArray[1]);       + System.out.format("Hi %s.%n", name);       + System.out.format("In 5 years you will be %d.%n", age + 5);       + System.out.format("My favorite double is %f.", favorite / 2);   + } catch(IOException e) {       + System.err.println("I/O exception");   + } + } } /* Output: What is your name? @@ -1376,11 +1381,11 @@ In 5 years you will be 27. My favorite double is 0.809015. */ ``` -`input`字段使用的类来自`java.io`,[`附录:I/O流`](#)详细介绍了相关内容。`StringReader`将`String`转化为可读的流对象,然后用这个对象来构造`BufferedReader`对象,因为我们要使用`BufferedReader`的`readLine()`方法。最终,我们可以使用`input`对象一次读取一行文本,就像从控制台读入标准输入一样。 +`input` 字段使用的类来自 `java.io`,[附录:流式 I/O](./Appendix-IO-Streams.md) 详细介绍了相关内容。`StringReader` 将 `String` 转化为可读的流对象,然后用这个对象来构造 `BufferedReader` 对象,因为我们要使用 `BufferedReader` 的 `readLine()` 方法。最终,我们可以使用 `input` 对象一次读取一行文本,就像从控制台读入标准输入一样。 -`readLine()`方法将一行输入转为`String`对象。如果每一行数据正好对应一个输入值,那这个方法也还可行。但是,如果两个输入值在同一行中,事情就不好办了,我们必须分解这个行,才能分别解析所需的输入值。在这个例子中,分解的操作发生在创建`numArray`时。 +`readLine()` 方法将一行输入转为 `String` 对象。如果每一行数据正好对应一个输入值,那这个方法也还可行。但是,如果两个输入值在同一行中,事情就不好办了,我们必须分解这个行,才能分别解析所需的输入值。在这个例子中,分解的操作发生在创建 `numArray`时。 -终于,Java SE5新增了`Scanner`类,它可以大大减轻扫描输入的工作负担: +终于,Java SE5 新增了 `Scanner` 类,它可以大大减轻扫描输入的工作负担: ```java // strings/BetterRead.java import java.util.*; @@ -1417,24 +1422,24 @@ In 5 years you will be 27. My favorite double is 0.809015. */ ``` -`Scanner`的构造器可以接收任意类型的输入对象,包括`File`对象、`InputStream`、`String`或者像此例中的`Readable`实现类。`Readable`是Java SE5中新加入的一个接口,表示“具有`read()`方法的某种东西”。上一个例子中的`BufferedReader`也归于这一类。 +`Scanner` 的构造器可以接收任意类型的输入对象,包括 `File`、`InputStream`、`String` 或者像此例中的`Readable` 实现类。`Readable` 是 Java SE5 中新加入的一个接口,表示“具有 `read()` 方法的某种东西”。上一个例子中的 `BufferedReader` 也归于这一类。 -有了`Scanner`,所有的输入、分词、以及解析的操作都隐藏在不同类型的`next`方法中。普通的`next()`方法返回下一个`String`。所有的基本类型(除`char`之外)都有对应的`next`方法,包括`BigDecimal`和`BigInteger`。所有的next方法,只有在找到一个完整的分词之后才会返回。`Scanner`还有相应的`hasNext`方法,用以判断下一个输入分词是否是所需的类型,如果是则返回`true`。 +有了 `Scanner`,所有的输入、分词、以及解析的操作都隐藏在不同类型的 `next` 方法中。普通的 `next()` 方法返回下一个 `String`。所有的基本类型(除 `char` 之外)都有对应的 `next` 方法,包括 `BigDecimal` 和 `BigInteger`。所有的 next 方法,只有在找到一个完整的分词之后才会返回。`Scanner` 还有相应的 `hasNext` 方法,用以判断下一个输入分词是否是所需的类型,如果是则返回 `true`。 -在`BetterRead.java`中没有用`try`区块捕获`IOException`。因为,`Scanner`有一个设假设,在输入结束时会抛出`IOException`,所以`Scanner`会把`IOException`吞掉。不过,通过`ioException()`方法,你可以找到最近发生的异常,因此,你可以在必要时检查它。 -### `Scanner`分隔符 -默认情况下,`Scanner`根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的分隔符: +在 `BetterRead.java` 中没有用 `try` 区块捕获`IOException`。因为,`Scanner` 有一个假设,在输入结束时会抛出 `IOException`,所以 `Scanner` 会把 `IOException` 吞掉。不过,通过 `ioException()` 方法,你可以找到最近发生的异常,因此,你可以在必要时检查它。 +### `Scanner` 分隔符 +默认情况下,`Scanner` 根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的分隔符: ```java // strings/ScannerDelimiter.java import java.util.*; public class ScannerDelimiter {   - public static void main(String[] args) {     - Scanner scanner = new Scanner("12, 42, 78, 99, 42");     - scanner.useDelimiter("\\s*,\\s*");     - while(scanner.hasNextInt())     - System.out.println(scanner.nextInt()); - } -} + public static void main(String[] args) {     + Scanner scanner = new Scanner("12, 42, 78, 99, 42");     + scanner.useDelimiter("\\s*,\\s*");     + while(scanner.hasNextInt())     + System.out.println(scanner.nextInt()); + } +} /* Output: 12 42 @@ -1443,7 +1448,7 @@ public class ScannerDelimiter {   42 */ ``` -这个例子使用逗号(包括逗号前后任意的空白字符)作为分隔符,同样的技术也可以用来读取逗号分隔的文件。我们可以用`useDelimiter()`来设置分隔符,同时,还有一个`delimiter()`方法,用来返回当前正在作为分隔符使用的`Pattern`对象。 +这个例子使用逗号(包括逗号前后任意的空白字符)作为分隔符,同样的技术也可以用来读取逗号分隔的文件。我们可以用 `useDelimiter()` 来设置分隔符,同时,还有一个 `delimiter()` 方法,用来返回当前正在作为分隔符使用的 `Pattern` 对象。 ### 用正则表达式扫描 除了能够扫描基本类型之外,你还可以使用自定义的正则表达式进行扫描,这在扫描复杂数据时非常有用。下面的例子将扫描一个防火墙日志文件中的威胁数据: ```java @@ -1451,26 +1456,26 @@ public class ScannerDelimiter {   import java.util.regex.*; import java.util.*; public class ThreatAnalyzer {  - static String threatData =     - "58.27.82.161@08/10/2015\n" +    - "204.45.234.40@08/11/2015\n" +     - "58.27.82.161@08/11/2015\n" +     - "58.27.82.161@08/12/2015\n" +     - "58.27.82.161@08/12/2015\n" + -    "[Next log section with different data format]";   - public static void main(String[] args) {  - Scanner scanner = new Scanner(threatData);     - String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" +       - "(\\d{2}/\\d{2}/\\d{4})";     - while(scanner.hasNext(pattern)) {       - scanner.next(pattern);       - MatchResult match = scanner.match();       - String ip = match.group(1);       - String date = match.group(2);       - System.out.format(         - "Threat on %s from %s%n", date,ip);   - } - } + static String threatData =     + "58.27.82.161@08/10/2015\n" +    + "204.45.234.40@08/11/2015\n" +     + "58.27.82.161@08/11/2015\n" +     + "58.27.82.161@08/12/2015\n" +     + "58.27.82.161@08/12/2015\n" + +     "[Next log section with different data format]";   + public static void main(String[] args) {  + Scanner scanner = new Scanner(threatData);     + String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@" +       + "(\\d{2}/\\d{2}/\\d{4})";     + while(scanner.hasNext(pattern)) {       + scanner.next(pattern);       + MatchResult match = scanner.match();       + String ip = match.group(1);       + String date = match.group(2);       + System.out.format(         + "Threat on %s from %s%n", date,ip);   + } + } } /* Output: Threat on 08/10/2015 from 58.27.82.161 @@ -1480,32 +1485,31 @@ Threat on 08/12/2015 from 58.27.82.161 Threat on 08/12/2015 from 58.27.82.161 */ ``` -当`next()`方法配合指定的正则表达式使用时,将找到下一个匹配该模式的输入部分,调用`match()`方法就可以获得匹配的结果。如上所示,它的工作方式与之前看到的正则表达式匹配相似。 +当 `next()` 方法配合指定的正则表达式使用时,将找到下一个匹配该模式的输入部分,调用 `match()` 方法就可以获得匹配的结果。如上所示,它的工作方式与之前看到的正则表达式匹配相似。 在配合正则表达式使用扫描时,有一点需要注意:它仅仅针对下一个输入分词进行匹配,如果你的正则表达式中含有分隔符,那永远不可能匹配成功。 - + ## StringTokenizer类 -在Java引入正则表达式(J2SE1.4)和`Scanner`类(Java SE5)之前,分割字符串的唯一方法是使用`StringTokenizer`来分词。不过,现在有了正则表达式和`Scanner`,我们可以使用更加简单、更加简洁的方式来完成同样的工作了。下面的例子中,我们将`StringTokenizer`与另外两种技术简单地做了一个比较: +在 Java 引入正则表达式(J2SE1.4)和 `Scanner` 类(Java SE5)之前,分割字符串的唯一方法是使用 `StringTokenizer` 来分词。不过,现在有了正则表达式和 `Scanner`,我们可以使用更加简单、更加简洁的方式来完成同样的工作了。下面的例子中,我们将 `StringTokenizer` 与另外两种技术简单地做了一个比较: ```java // strings/ReplacingStringTokenizer.java import java.util.*; public class ReplacingStringTokenizer { - public static void main(String[] args) { - String input = - "But I'm not dead yet! I feel happy!"; - StringTokenizer stoke = new StringTokenizer(input); - while(stoke.hasMoreElements()) - System.out.print(stoke.nextToken() + " "); - System.out.println(); - System.out.println( - Arrays.toString(input.split(" "))); - Scanner scanner = new Scanner(input); - while(scanner.hasNext()) - System.out.print(scanner.next() + " "); - } + public static void main(String[] args) { + String input = + "But I'm not dead yet! I feel happy!"; + StringTokenizer stoke = new StringTokenizer(input); + while(stoke.hasMoreElements()) + System.out.print(stoke.nextToken() + " "); + System.out.println(); + System.out.println(Arrays.toString(input.split(" "))); + Scanner scanner = new Scanner(input); + while(scanner.hasNext()) + System.out.print(scanner.next() + " "); + } } /* Output: But I'm not dead yet! I feel happy! @@ -1513,12 +1517,12 @@ But I'm not dead yet! I feel happy! But I'm not dead yet! I feel happy! */ ``` -使用正则表达式或`Scanner`对象,我们能够以更加复杂的模式来分割一个字符串,而这对于`StringTokenizer`来说就很困难了。基本上,我们可以放心地说,`StringTokenizer`已经可以废弃不用了。 +使用正则表达式或 `Scanner` 对象,我们能够以更加复杂的模式来分割一个字符串,而这对于 `StringTokenizer` 来说就很困难了。基本上,我们可以放心地说,`StringTokenizer` 已经可以废弃不用了。 ## 本章小结 -过去,Java对于字符串操作的技术相当不完善。不过随着近几个版本的升级,我们可以看到,Java已经从其他语言中吸取了许多成熟的经验。到目前为止,它对字符串操作的支持已经很完善了。不过,有时你还要在细节上注意效率问题,例如恰当地使用`StringBuilder`等。 +过去,Java 对于字符串操作的技术相当不完善。不过随着近几个版本的升级,我们可以看到,Java 已经从其他语言中吸取了许多成熟的经验。到目前为止,它对字符串操作的支持已经很完善了。不过,有时你还要在细节上注意效率问题,例如恰当地使用 `StringBuilder` 等。 [^1]: C++允许编程人员任意重载操作符。这通常是很复杂的过程(参见Prentice Hall于2000年编写的《Thinking in C++(第2版)》第10章),因此Java设计者认为这是很糟糕的功能,不应该纳入到Java中。起始重载操作符并没有糟糕到只能自己去重载的地步,但具有讽刺意味的是,与C++相比,在Java中使用操作符重载要容易得多。这一点可以在Python(参见[www.Python.org](http://www.python.org))和C#中看到,它们都有垃圾回收机制,操作符重载也简单易懂。 @@ -1535,7 +1539,6 @@ But I'm not dead yet! I feel happy! [^5]: 我不知道他们是如何想出这个方法名的,或者它到底指的什么。这只是代码审查很重要的原因之一。 -
From 4050274cf4436e7c6ab006a44ec99e50a6d6bd46 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Mon, 11 Nov 2019 10:23:50 +0800 Subject: [PATCH 078/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b99e573..a06018a2 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ - [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) -- [ ] [第十九章 类型信息](docs/book/19-Type-Information.md) +- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) - [ ] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) From fa0dafbaacda92a521ac12594950a74287cb7e08 Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Mon, 11 Nov 2019 17:48:47 +0800 Subject: [PATCH 079/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E7=AC=AC=E5=8D=81?= =?UTF-8?q?=E4=BA=8C=E7=AB=A0=20=E9=9B=86=E5=90=88=E6=8F=92=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/images/collection.png | Bin 58326 -> 51285 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/collection.png b/docs/images/collection.png index ab24df4466e7b844ae90f45cfba61a346e564802..a34dd0617a488690fdfc9cee1265ab97585dc1be 100644 GIT binary patch literal 51285 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 Date: Wed, 13 Nov 2019 12:57:22 +0800 Subject: [PATCH 080/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E7=AB=A0=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=20=E6=A0=A1=E8=AE=A2+=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 315 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 6 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 938b5f9f..daf2969d 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -17,7 +17,7 @@ 然而,如果你了解其他语言(例如 C++ )的参数化机制,你会发现,Java 泛型并不能满足所有的预期。使用别人创建好的泛型相对容易,但是创建自己的泛型时,就会遇到很多意料之外的麻烦。 -这并不是说 Java 泛型毫无用处。在很多情况下,它可以使代码更直接更优雅。不过,如果你见识过那种实现了更纯粹的泛型的编程语言,那么,Java 可能会令你失望。本章会介绍 Java 泛型的优点与局限。我会解释 Java 的泛型是如何发展成现在这样的,希望能够帮助你更有效地使用这个特性。 +这并不是说 Java 泛型毫无用处。在很多情况下,它可以使代码更直接更优雅。不过,如果你见识过那种实现了更纯粹的泛型的编程语言,那么,Java 可能会令你失望。本章会介绍 Java 泛型的优点与局限。我会解释 Java 的泛型是如何发展成现在这样的,希望能够帮助你更有效地使用这个特性。[^1] ### 与 C++ 的比较 @@ -1756,13 +1756,317 @@ Note: Recompile with -Xlint:unchecked for details. 果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。 -Neal Gafter(Java 5的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时很懒惰,我们不应该做他所做的事情。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使某些习惯用法出现在 Java 库源代码中,也不一定是正确的做法。当查看库代码时,我们不能假设这是您在自己的代码中必须要遵循的示例。 +Neal Gafter(Java 5的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。 请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。 ## 边界 +*边界*(bounds)在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在绑定的类型中调用方法。 + +由于擦除会删除类型信息,因此唯一可用于无限制泛型参数的方法是那些 **Object** 可用的方法。但是,如果将该参数限制为某类型的子集,则可以调用该子集中的方法。为了应用约束,Java 泛型使用了 `extends` 关键字。 + +重要的是要理解,当用于限定泛型类型时,`extends` 的含义与通常的意义截然不同。此示例展示边界的基础应用: + +```java +// generics/BasicBounds.java + +interface HasColor { + java.awt.Color getColor(); +} + +class WithColor { + T item; + + WithColor(T item) { + this.item = item; + } + + T getItem() { + return item; + } + + // The bound allows you to call a method: + java.awt.Color color() { + return item.getColor(); + } +} + +class Coord { + public int x, y, z; +} + +// This fails. Class must be first, then interfaces: +// class WithColorCoord { + +// Multiple bounds: +class WithColorCoord { + T item; + + WithColorCoord(T item) { + this.item = item; + } + + T getItem() { + return item; + } + + java.awt.Color color() { + return item.getColor(); + } + + int getX() { + return item.x; + } + + int getY() { + return item.y; + } + + int getZ() { + return item.z; + } +} + +interface Weight { + int weight(); +} + +// As with inheritance, you can have only one +// concrete class but multiple interfaces: +class Solid { + T item; + + Solid(T item) { + this.item = item; + } + + T getItem() { + return item; + } + + java.awt.Color color() { + return item.getColor(); + } + + int getX() { + return item.x; + } + + int getY() { + return item.y; + } + + int getZ() { + return item.z; + } + + int weight() { + return item.weight(); + } +} + +class Bounded + extends Coord implements HasColor, Weight { + @Override + public java.awt.Color getColor() { + return null; + } + + @Override + public int weight() { + return 0; + } +} + +public class BasicBounds { + public static void main(String[] args) { + Solid solid = + new Solid<>(new Bounded()); + solid.color(); + solid.getY(); + solid.weight(); + } +} +``` + +你可能会观察到 **BasicBounds.java** 中似乎包含一些冗余,它们可以通过继承来消除。在这里,每个继承级别还添加了边界约束: + +```java +// generics/InheritBounds.java + +class HoldItem { + T item; + + HoldItem(T item) { + this.item = item; + } + + T getItem() { + return item; + } +} + +class WithColor2 + extends HoldItem { + WithColor2(T item) { + super(item); + } + + java.awt.Color color() { + return item.getColor(); + } +} + +class WithColorCoord2 + extends WithColor2 { + WithColorCoord2(T item) { + super(item); + } + + int getX() { + return item.x; + } + + int getY() { + return item.y; + } + + int getZ() { + return item.z; + } +} + +class Solid2 + extends WithColorCoord2 { + Solid2(T item) { + super(item); + } + + int weight() { + return item.weight(); + } +} + +public class InheritBounds { + public static void main(String[] args) { + Solid2 solid2 = + new Solid2<>(new Bounded()); + solid2.color(); + solid2.getY(); + solid2.weight(); + } +} +``` + +**HoldItem** 拥有一个对象,因此此行为将继承到 **WithColor2** 中,这也需要其参数符合 **HasColor**。 **WithColorCoord2** 和 **Solid2** 进一步扩展了层次结构,并在每个级别添加了边界。现在,这些方法已被继承,并且在每个类中不再重复。 + +这是一个具有更多层次的示例: + +```java +// generics/EpicBattle.java +// Bounds in Java generics + +import java.util.List; + +interface SuperPower { +} + +interface XRayVision extends SuperPower { + void seeThroughWalls(); +} + +interface SuperHearing extends SuperPower { + void hearSubtleNoises(); +} + +interface SuperSmell extends SuperPower { + void trackBySmell(); +} + +class SuperHero { + POWER power; + + SuperHero(POWER power) { + this.power = power; + } + + POWER getPower() { + return power; + } +} + +class SuperSleuth + extends SuperHero { + SuperSleuth(POWER power) { + super(power); + } + + void see() { + power.seeThroughWalls(); + } +} + +class +CanineHero + extends SuperHero { + CanineHero(POWER power) { + super(power); + } + + void hear() { + power.hearSubtleNoises(); + } + + void smell() { + power.trackBySmell(); + } +} + +class SuperHearSmell + implements SuperHearing, SuperSmell { + @Override + public void hearSubtleNoises() { + } + + @Override + public void trackBySmell() { + } +} + +class DogPerson extends CanineHero { + DogPerson() { + super(new SuperHearSmell()); + } +} + +public class EpicBattle { + // Bounds in generic methods: + static + void useSuperHearing(SuperHero hero) { + hero.getPower().hearSubtleNoises(); + } + + static + void superFind(SuperHero hero) { + hero.getPower().hearSubtleNoises(); + hero.getPower().trackBySmell(); + } + + public static void main(String[] args) { + DogPerson dogPerson = new DogPerson(); + useSuperHearing(dogPerson); + superFind(dogPerson); + // You can do this: + List audioPeople; + // But you can't do this: + // List dogPs; + } +} +``` + +接下来将要研究的通配符将会把范围限制在单个类型。 ## 通配符 @@ -1805,10 +2109,9 @@ Neal Gafter(Java 5的主要开发人员之一)在他的博客中[^2]指出 - - - - +[^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。 +[^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html) +[^3]: 参见本章章末引文。 From f22896ffbc7dad4aeb4bf984ba592a22b5d58865 Mon Sep 17 00:00:00 2001 From: Moilk Date: Wed, 13 Nov 2019 16:54:30 +0800 Subject: [PATCH 081/371] =?UTF-8?q?=E2=98=81=20=20Backup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 56c9342e..c0cffe04 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -3,6 +3,9 @@ # 附录:流式IO +> Java 7 引入了一种简单明了的方式来读写文件和操作目录。大多情况下,[文件](./17-Files.md)这一章所介绍的那些库和技术就足够你用了。但是,如果你必须面对一些特殊的需求和比较底层的操作,或者处理一些老版本的代码,那么你就必须了解本附录中的内容。 + +对于编程语言的设计者来说,搭建良好的输入/输出(I/O)系统是一项比较困难的任务。 ## 输入流类型 From 5f95829bb6ec5df04c2311435ad4a054f5a8c95c Mon Sep 17 00:00:00 2001 From: xiangflight Date: Mon, 11 Nov 2019 09:07:55 +0800 Subject: [PATCH 082/371] =?UTF-8?q?revision[19]=20=E6=88=AA=E6=AD=A2?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 62 +++++++++++++++++--------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index a2183e45..eef85d32 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -13,13 +13,14 @@ RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了 2. “反射”机制:允许我们在运行时发现和使用类的信息。 + ## 为什么需要 RTTI 下面看一下我们已经很熟悉的一个例子,它使用了多态的类层次结构。基类 `Shape` 是泛化的类型,从它派生出了三个具体类: `Circle` 、`Square` 和 `Triangle`(见下图所示)。 ![多态例子Shape的类层次结构图](../images/image-20190409114913825-4781754.png) -这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从 `Shape` 派生出 `Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape`接口中动态绑定了 `draw()` 方法,这样做的目的就是让客户端程序员可以使用泛化的 `Shape` 引用来调用 `draw()`。`draw()` 方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以它可以使用 `Shape` 引用来调用,这就是多态。 +这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从 `Shape` 派生出 `Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape` 接口中动态绑定了 `draw()` 方法,这样做的目的就是让客户端程序员可以使用泛化的 `Shape` 引用来调用 `draw()`。`draw()` 方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以即使通过 `Shape` 引用来调用它,也能产生恰当的行为,这就是多态。 因此,我们通常会创建一个具体的对象(`Circle`、`Square` 或者 `Triangle`),把它向上转型成 `Shape` (忽略对象的具体类型),并且在后面的程序中使用 `Shape` 引用来调用在具体对象中被重载的方法(如 `draw()`)。 @@ -67,7 +68,7 @@ Square.draw() Triangle.draw() ``` -基类中包含 `draw()` 方法,它通过传递 `this` 参数传递给 `System.out.println()`,间接地使用 `toString()` 打印类标识符(注意:这里将 `toString()` 声明为了 `abstract`,以此强制继承者覆盖改方法,并防止对 `Shape` 的实例化)。如果某个对象出现在字符串表达式中(涉及"+"和字符串对象的表达式),`toString()` 方法就会被自动调用,以生成表示该对象的 `String`。每个派生类都要覆盖(从 `Object` 继承来的)`toString()` 方法,这样 `draw()` 在不同情况下就打印出不同的消息(多态)。 +基类中包含 `draw()` 方法,它通过传递 `this` 参数传递给 `System.out.println()`,间接地使用 `toString()` 打印类标识符(注意:这里将 `toString()` 声明为 `abstract`,以此强制继承者覆盖该方法,并防止对 `Shape` 的实例化)。如果某个对象出现在字符串表达式中(涉及"+"和字符串对象的表达式),`toString()` 方法就会被自动调用,以生成表示该对象的 `String`。每个派生类都要覆盖(从 `Object` 继承来的)`toString()` 方法,这样 `draw()` 在不同情况下就打印出不同的消息(多态)。 这个例子中,在把 `Shape` 对象放入 `Stream` 中时就会进行向上转型(隐式),但在向上转型的时候也丢失了这些对象的具体类型。对 `stream` 而言,它们只是 `Shape` 对象。 @@ -89,7 +90,7 @@ Triangle.draw() 类是程序的一部分,每个类都有一个 `Class` 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 `Class` 对象(更恰当的说,是被保存在一个同名的 `.class` 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用"类加载器"子系统把这个类加载到内存中。 -类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是JVM实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。 +类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是 JVM 实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。 所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。 @@ -145,7 +146,7 @@ Loading Cookie After creating Cookie ``` -上面的代码中,`Candy`、`Gum` 和 `Cookie` 这几个类都有一个 `static{...}` 静态初始化块,这些静态初始化块在类第一次被加载的时候就会执行。也就是说,静态初始化块会打印出相应的信息,告诉我们这些类分别是什么时候被加载了。而在主方法里边,创建对象 的代码都放在了 `print()` 语句之间,以帮助我们判断类加载的时间点。 +上面的代码中,`Candy`、`Gum` 和 `Cookie` 这几个类都有一个 `static{...}` 静态初始化块,这些静态初始化块在类第一次被加载的时候就会执行。也就是说,静态初始化块会打印出相应的信息,告诉我们这些类分别是什么时候被加载了。而在主方法里边,创建对象的代码都放在了 `print()` 语句之间,以帮助我们判断类加载的时间点。 从输出中可以看到,`Class` 对象仅在需要的时候才会被加载,`static` 初始化是在类加载时进行的。 @@ -159,7 +160,7 @@ Class.forName("Gum"); 还需要注意的是,如果 `Class.forName()` 找不到要加载的类,它就会抛出异常 `ClassNotFoundException`。上面的例子中我们只是简单地报告了问题,但在更严密的程序里,就要考虑在异常处理程序中把问题解决掉(具体例子详见[设计模式](./25-Patterns)章节)。 -无论何时,只要你想在运行时使用类型信息,就必须先得到那个 `Class` 对象的引用。`Class.forName()` 就是实现这个功能的一个便捷途径,因为使用该方法你不需要先持有这个类型 的对象。但是,如果你已经拥有了目标类的对象,那就可以通过调用 `getClass()` 方法来获取 `Class` 引用了,这个方法来自根类 `Object`,它将返回表示该对象实际类型的 `Class`对象的引用。`Class` 包含很多有用的方法,下面代码展示了其中的一部分: +无论何时,只要你想在运行时使用类型信息,就必须先得到那个 `Class` 对象的引用。`Class.forName()` 就是实现这个功能的一个便捷途径,因为使用该方法你不需要先持有这个类型 的对象。但是,如果你已经拥有了目标类的对象,那就可以通过调用 `getClass()` 方法来获取 `Class` 引用了,这个方法来自根类 `Object`,它将返回表示该对象实际类型的 `Class` 对象的引用。`Class` 包含很多有用的方法,下面代码展示了其中的一部分: ```java // typeinfo/toys/ToyTest.java @@ -251,17 +252,17 @@ Canonical name : typeinfo.toys.Toy `printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。 -在主方法中调用的 `Class.getInterface()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 +在主方法中调用的 `Class.getInterface()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。 -`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态的创建类的对象。 +`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态地创建类的对象。 ### 类字面常量 -Java还提供了另一种方法来生成类对象的引用:**类字面常量**。对上述程序来说,就像这样:`FancyToy.class;`。这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不必放在`try`语句块中)。并且它根除了对 `forName()` 方法的调用,所以效率更高。 +Java 还提供了另一种方法来生成类对象的引用:**类字面常量**。对上述程序来说,就像这样:`FancyToy.class;`。这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不必放在 `try` 语句块中)。并且它根除了对 `forName()` 方法的调用,所以效率更高。 -类字面常量不仅不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段 `TYPE`。`TYPE`字段是一个引用,指向对应的基本数据类型的 `Class` 对象,如下所示: +类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段 `TYPE`。`TYPE` 字段是一个引用,指向对应的基本数据类型的 `Class` 对象,如下所示:
@@ -313,15 +314,15 @@ Java还提供了另一种方法来生成类对象的引用:**类字面常量** 我的建议是使用 `.class` 的形式,以保持与普通类的一致性。 -注意,有一点很有趣:当使用 `.class` 来创建对 `Class` 对象的引用时,不会自动地初始化该`Class` 对象。为了使用类而做的准备工作实际包含三个步骤: +注意,有一点很有趣:当使用 `.class` 来创建对 `Class` 对象的引用时,不会自动地初始化该 `Class` 对象。为了使用类而做的准备工作实际包含三个步骤: 1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。 2. **链接**。在链接阶段将验证类中的字节码,为 `static` 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。 -3. **初始化**。如果该类具有超类,则对其进行初始化,执行 `static` 初始化器和 `static` 初始化块。 +3. **初始化**。如果该类具有超类,则先初始化超类,执行 `static` 初始化器和 `static` 初始化块。 -初始化被延迟到了对 `static` 方法(构造器隐式地是 `static` 的)或者非常数 `static` 字段进行首次引用时才执行: +直到第一次引用一个 `static` 方法(构造器隐式地是 `static`)或者非常量的 `static` 字段,才会进行类初始化。 ```java // typeinfo/ClassInitialization.java @@ -391,7 +392,7 @@ After creating Initable3 ref ### 泛化的 `Class` 引用 -`Class`引用总是指向某个 `Class` 对象,而 `Class` 对象可以用于产生类的实例,并且包含可作用于这些实例的所有方法代码。它还包含该类的 `static` 成员,因此 `Class` 引用表明了它所指向对象的确切类型,而该对象便是 `Class` 类的一个对象。 +`Class` 引用总是指向某个 `Class` 对象,而 `Class` 对象可以用于产生类的实例,并且包含可作用于这些实例的所有方法代码。它还包含该类的 `static` 成员,因此 `Class` 引用表明了它所指向对象的确切类型,而该对象便是 `Class` 类的一个对象。 @@ -451,7 +452,7 @@ public class BoundedClassReferences { } ``` -向 `Class` 引用添加泛型语法的原因只是为了提供编译期类型检查,因此如果你操作有误,稍后就会发现这点。使用普通的 `Class` 引用你要确保自己不会犯错,因为一旦你犯了错误,就要等到运行时你才能发现它,这并不是很方便。 +向 `Class` 引用添加泛型语法的原因只是为了提供编译期类型检查,因此如果你操作有误,稍后就会发现这点。使用普通的 `Class` 引用你要确保自己不会犯错,因为一旦你犯了错误,就要等到运行时才能发现它,很不方便。 下面的示例使用了泛型语法,它保存了一个类引用,稍后又用 `newInstance()` 方法产生类的对象: @@ -500,7 +501,7 @@ public class DynamicSupplier implements Supplier { 14 ``` -注意,这个类必须假设与它与它一起工作的任何类型都有一个无参构造器,否者运行时会抛出异常。编译期对该程序不会产生任何警告信息。 +注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否者运行时会抛出异常。编译期对该程序不会产生任何警告信息。 当你将泛型语法用于 `Class` 对象时,`newInstance()` 将返回该对象的确切类型,而不仅仅只是在 `ToyTest.java` 中看到的基类 `Object`。然而,这在某种程度上有些受限: @@ -526,7 +527,7 @@ public class GenericToyTest { } ``` -如果你手头的是超类,那编译期将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是“某个类,它是 `FancyToy` 的超类”。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 +如果你手头的是超类,那编译期将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 ### `cast()` 方法 @@ -556,13 +557,13 @@ Java 类库中另一个没有任何用处的特性就是 `Class.asSubclass()`, ## 类型转换检测 -直到现在,我们已知的RTTI类型包括: +直到现在,我们已知的 RTTI 类型包括: 1. 传统的类型转换,如 “`(Shape)`”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 `ClassCastException` 异常。 2. 代表对象类型的 `Class` 对象. 通过查询 `Class` 对象可以获取运行时所需的信息. -在 C++ 中,经典的类型转换 “`(Shape)`” 并不使用 RTTI. 它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显示的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式的类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类型的子类的类型上)。 +在 C++ 中,经典的类型转换 “`(Shape)`” 并不使用 RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显式的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式地进行类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际不是待转型类型的子类类型上)。 RTTI 在 Java 中还有第三种形式,那就是关键字 `instanceof`。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子: @@ -573,7 +574,7 @@ if(x instanceof Dog) 在将 `x` 的类型转换为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。 -一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 `instanceof` 来计数所有对象。举个例子,假如你有一个类的继承体系,描述了 `Pet`(以及它们的主人,在后面一个例子中会用到这个特性)。在这个继承体系中的每个 `Individual` 都有一个 `id` 和一个可选的名字。尽管下面的类都继承自 `Individual`,但是 `Individual` 类复杂性较高,因此其代码将放在[附录:容器](./Appendix-Collection-Topics)中进行解释说明。正如你所看到的,此处并不需要去了解 `Individual` 的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 `Individual` 都有一个 `id()` 方法,如果你没有为 `Individual` 提供名字,`toString()` 方法只产生类型名。 +一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 `instanceof` 来度量所有对象。举个例子,假如你有一个类的继承体系,描述了 `Pet`(以及它们的主人,在后面一个例子中会用到这个特性)。在这个继承体系中的每个 `Individual` 都有一个 `id` 和一个可选的名字。尽管下面的类都继承自 `Individual`,但是 `Individual` 类复杂性较高,因此其代码将放在[附录:容器](./Appendix-Collection-Topics)中进行解释说明。正如你所看到的,此处并不需要去了解 `Individual` 的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 `Individual` 都有一个 `id()` 方法,如果你没有为 `Individual` 提供名字,`toString()` 方法只产生类型名。 下面是继承自 `Individual` 的类的继承体系: @@ -709,7 +710,7 @@ public class Hamster extends Rodent { 我们必须显式地为每一个子类编写无参构造器。因为我们有一个带一个参数的构造器,所以编译器不会自动地为我们加上无参构造器。 -接下来,我们需要一个类,它可以随机地创建不同类型的宠物,同时,它还可以创建宠物数组和持有宠物的 `List`。为了这个类更加普遍适用,我们将其定义为抽象类: +接下来,我们需要一个类,它可以随机地创建不同类型的宠物,同时,它还可以创建宠物数组和持有宠物的 `List`。为了使这个类更加普遍适用,我们将其定义为抽象类: ```java // typeinfo/pets/PetCreator.java @@ -740,7 +741,7 @@ public abstract class PetCreator implements Supplier { 在调用 `newInstance()` 时,可能会出现两种异常。在紧跟 `try` 语句块后面的 `catch` 子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(`IllegalAccessException` 违反了 Java 安全机制,在本例中,表示默认构造器为 `private` 的情况)。 -当你导出 `PetCreator` 的子类时,你需要为 `get()` 方法提供 `Pet` 类型的 `List`。`types()` 方法会简单地返回一个静态 `List` 的引用。下面是使用 `forName()` 的一个具体实现: +当你创建 `PetCreator` 的子类时,你需要为 `get()` 方法提供 `Pet` 类型的 `List`。`types()` 方法会简单地返回一个静态 `List` 的引用。下面是使用 `forName()` 的一个具体实现: ```java // typeinfo/pets/ForNameCreator.java @@ -860,7 +861,7 @@ Pug Mouse Cymric Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1} ``` -在 `countPets()` 中,一个简短的静态方法 `Pets.array()` 生产出了一个随机动物的集合。每个 `Pet` 都被 `instanceof` 检测到并数了一遍。 +在 `countPets()` 中,一个简短的静态方法 `Pets.array()` 生产出了一个随机动物的集合。每个 `Pet` 都被 `instanceof` 检测到并计算了一遍。 `instanceof` 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 `Class` 对象作比较。在前面的例子中,你可能会觉得写出一大堆 `instanceof` 表达式很乏味,事实也是如此。但是,也没有办法让 `instanceof` 聪明起来,让它能够自动地创建一个 `Class` 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 `instanceof`,那就说明你的设计可能存在瑕疵。 @@ -1131,9 +1132,10 @@ Mouse=2} 输出表明两个基类型以及精确类型都被计数了。 + ## 注册工厂 -从 `Pet` 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 `Pet` 时,必须记住将其添加到 `LiteralPetCreator.java` 中的条目中。在一个定期添加更多类的系统中,这可能会成为问题。 +从 `Pet` 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 `Pet` 时,必须记住将其添加到 `LiteralPetCreator.java` 的条目中。在一个定期添加更多类的系统中,这可能会成为问题。 你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。 @@ -1256,9 +1258,10 @@ FuelFilter 因为 `Part implements Supplier`,`Part` 通过其 `get()` 方法供应其他 `Part`。如果为基类 `Part` 调用 `get()`(或者如果 `generate()` 调用 `get()`),它将创建随机特定的 `Part` 子类型,每个子类型最终都从 `Part` 继承,并重写相应的 `get()` 以生成它们中的一个。 + ## 类的等价比较 -When you are querying for type information, there's an important difference between either form of `instanceof` (that is, `instanceof` or `isInstance()`, which produce equivalent results) and the direct comparison of the `Class` objects. Here's an example that demonstrates the difference: +当你查询类型信息时,需要注意:instanceof 的形式(即 `instanceof` 或 `isInstance()` ,这两者产生的结果相同) 和 与 Class 对象直接比较 这两者间存在重要区别。下面的例子展示了这种区别: ```java // typeinfo/FamilyVsExactType.java @@ -1326,20 +1329,20 @@ x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true ``` -`test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果与 `equals()` 和 `==` 完全相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。另一方面,如果使用 `==` 比较实际的 `Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。 +`test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果相同, `equals()` 和 `==` 产生的结果也相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 `==` 比较实际的`Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。 ## 反射:运行时类信息 如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。 -起初,这看起来并没有那么大的限制,但是假设你被赋予了一个对不在程序空间中的对象的引用。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类? +起初,这看起来并没有那么大的限制,但是假设你引用了一个对不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类? -在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在程序时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆盖这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。 +在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在编程时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆写这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。 在运行时发现类信息的另一个令人信服的动机是提供跨网络在远程平台上创建和执行对象的能力。这称为*远程方法调用*(RMI),它使 Java 程序的对象分布在许多机器上。这种分布有多种原因。如果你想加速一个计算密集型的任务,你可以把它分解成小块放到空闲的机器上。或者你可以将处理特定类型任务的代码(例如,多层次客户机/服务器体系结构中的“业务规则”)放在特定的机器上,这样机器就成为描述这些操作的公共存储库,并且可以很容易地更改它以影响系统中的每个人。分布式计算还支持专门的硬件,这些硬件可能擅长于某个特定的任务——例如矩阵转换——但对于通用编程来说不合适或过于昂贵。 -类 `Class` 支持*反射*的概念,以及 `java.lang.reflect` 库,其中包含类 `Field`、`Method` 和 `Constructor`(每一个都实现了 `Member` 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 `Constructor` 创建新对象,`get()` 和 `set()` 方法读取和修改与 `Field` 对象关联的字段,`invoke()` 方法调用与 `Method` 对象关联的方法。此外,还可以调用便利方法 `getFields()`、`getMethods()`、`getConstructors()` 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 `Class` 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。 +类 `Class` 支持*反射*的概念, `java.lang.reflect` 库中包含类 `Field`、`Method` 和 `Constructor`(每一个都实现了 `Member` 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 `Constructor` 创建新对象,`get()` 和 `set()` 方法读取和修改与 `Field` 对象关联的字段,`invoke()` 方法调用与 `Method` 对象关联的方法。此外,还可以调用便利方法 `getFields()`、`getMethods()`、`getConstructors()` 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 `Class` 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。 重要的是要意识到反射没有什么魔力。当你使用反射与未知类型的对象交互时,JVM 将查看该对象,并看到它属于特定的类(就像普通的 RTTI)。在对其执行任何操作之前,必须加载 `Class` 对象。因此,该特定类型的 `.class` 文件必须在本地计算机上或通过网络对 JVM 仍然可用。因此,RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 `.class` 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,`.class` 文件在编译时不可用;它由运行时环境打开并检查。 @@ -1347,7 +1350,7 @@ x.getClass().equals(Derived.class)) true 通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](#ch040.xhtml#appendix-object-serialization))。但是,有时动态提取有关类的信息很有用。 -考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。 +考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口: ```java // typeinfo/ShowMethods.java @@ -1440,6 +1443,7 @@ java ShowMethods ShowMethods 编程时,当你不记得某个类是否有特定的方法,并且不想在 JDK 文档中搜索索引或类层次结构时,或者如果你不知道该类是否可以对 `Color` 对象执行任何操作时,该工具能节省不少时间。 + ## 动态代理 *代理*是基本的设计模式之一。它是你插入的对象,代替“真实”对象以提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构: From e8e2374f660f1e758598e258e1f0c11572d58476 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Thu, 14 Nov 2019 20:06:49 +0800 Subject: [PATCH 083/371] =?UTF-8?q?revision[19]=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 39 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index eef85d32..75dfa68e 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1446,7 +1446,7 @@ java ShowMethods ShowMethods ## 动态代理 -*代理*是基本的设计模式之一。它是你插入的对象,代替“真实”对象以提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构: +*代理*是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构: ```java // typeinfo/SimpleProxyDemo.java @@ -1514,12 +1514,12 @@ SimpleProxy somethingElse bonobo somethingElse bonobo ``` -因为`consumer()`接受`Interface`,所以它不知道获得的是`RealObject`还是`SimpleProxy`,因为两者都实现了`Interface`。 -但是,在客户端和`RealObject`之间插入的`SimpleProxy`执行操作,然后在`RealObject`上调用相同的方法。 +因为 `consumer()` 接受 `Interface`,所以它不知道获得的是 `RealObject` 还是 `SimpleProxy`,因为两者都实现了 `Interface`。 +但是,在客户端和 `RealObject` 之间插入的 `SimpleProxy` 执行操作,然后在 `RealObject` 上调用相同的方法。 -当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对`RealObject`中方法的调用,或衡量此类调用的开销,该怎么办?这不是你要写入到程序中的代码,而且代理使你可以很轻松地添加或删除它。 +当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对 `RealObject` 中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。 -Java的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是`SimpleProxyDemo.java`使用动态代理重写的例子: +Java 的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是 `SimpleProxyDemo.java` 使用动态代理重写的例子: ```java // typeinfo/SimpleDynamicProxy.java @@ -1581,11 +1581,11 @@ Interface.somethingElse(java.lang.String), args: somethingElse bonobo ``` -可以通过调用静态方法`Proxy.newProxyInstance()`来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口`InvocationHandler`的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。 +可以通过调用静态方法 `Proxy.newProxyInstance()` 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 `InvocationHandler` 的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。 -`invoke()`方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在`invoke()`内的代理上调用方法时要小心,因为通过接口的调用是通过代理重定向的。 +`invoke()` 方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在 `invoke()` 内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。 -通常执行代理操作,然后使用`Method.invoke()`传递必要的参数将请求转发给代理对象。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用: +通常执行代理操作,然后使用 `Method.invoke()` 将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用: ```java // typeinfo/SelectingMethods.java @@ -1670,16 +1670,17 @@ boring3 在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。 -动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在Erich Gamma等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](./25-Patterns.md)一章。 +动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](./25-Patterns.md)一章。 + ## Optional类 如果你使用内置的 `null` 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 `null`,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 `null` 没什么自己的行为,只会在你想用它执行任何操作的时候产生 `NullPointException`。`java.util.Optional`(首次出现是在[函数式编程](docs/book/13-Functional-Programming.md)这章)为 `null` 值提供了一个轻量级代理,`Optional` 对象可以防止你的代码直接抛出 `NullPointException`。 虽然 `Optional` 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。 -实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理的假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`: +实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理地假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`: ```java // typeinfo/Person.java @@ -1821,13 +1822,13 @@ caught EmptyTitleException 这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通字段,不受 `Optional` 的保护。但是,修改这些字段的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对字段进行了严格的限制。 -同时,我们想保证 `title` 字段永远不会变成 `null` 值。为此,我们可以自己在 `setTitle()` 方法里边检查参数 `newTitle` 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 `ofNullable()` 把 `newTitle` 转换一个 `Optional`(如果传入的值为 `null`,`ofNullable()` 返回的将是 `Optional.empty()`)。紧接着我们调用了 `orElseThrow()` 方法,所以如果 `newTitle` 的值是 `null`,你将会得到一个异常。这里我们并没有把 `title` 保存成 `Optional`,但通过利 `Optional` 的功能,我们仍然如愿以偿的对这个字段施加了约束。 +同时,我们想保证 `title` 字段永远不会变成 `null` 值。为此,我们可以自己在 `setTitle()` 方法里边检查参数 `newTitle` 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 `ofNullable()` 把 `newTitle` 转换一个 `Optional`(如果传入的值为 `null`,`ofNullable()` 返回的将是 `Optional.empty()`)。紧接着我们调用了 `orElseThrow()` 方法,所以如果 `newTitle` 的值是 `null`,你将会得到一个异常。这里我们并没有把 `title` 保存成 `Optional`,但通过应用 `Optional` 的功能,我们仍然如愿以偿地对这个字段施加了约束。 `EmptyTitleException` 是一个 `RuntimeException`,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 `setTitle()` 传 `null` 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 `EmptyTitleException` 还有助于定位 BUG。 `Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Option`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。 -在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段如果是空 `Person` 对象就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显示的表示“空位”的方法,但是 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)。 +在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段的 `Person` 对象为空,就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。 请注意,虽然你清楚你使用了 `Optional`,可以免受 `NullPointerExceptions` 的困扰,但是 `Staff` 类却对此毫不知情。 @@ -2117,7 +2118,7 @@ Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻 ## 接口和类型 -`interface` 关键字的一个重要目标就是允许程序员隔离构件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口: +`interface` 关键字的一个重要目标就是允许程序员隔离组件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口: ```java // typeinfo/interfacea/A.java @@ -2164,9 +2165,9 @@ public class InterfaceViolation { B ``` -通过使用 RTTI,我们发现 `a` 是被当做 `B` 实现的。通过将其转型为 `B`,我们可以调用不在 `A` 中的方法。 +通过使用 RTTI,我们发现 `a` 是用 `B` 实现的。通过将其转型为 `B`,我们可以调用不在 `A` 中的方法。 -这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 `interface` 关键字正在保护你,但其实并没有。另外,在本例中使用 `B` 来实现 `A` 这中情况是有公开案例可查的[^3]。 +这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 `interface` 关键字正在保护你,但其实并没有。另外,在本例中使用 `B` 来实现 `A` 这种情况是有公开案例可查的[^3]。 一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是可行的,但“可能”还不够,你或许希望能有一些更严格的控制方式。 @@ -2462,22 +2463,22 @@ i = 47, I'm totally safe, No, you're not! 但实际上 `final` 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。 -通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,发射给我们带来了很多好处。 +通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,反射给我们带来了很多好处。 程序员往往对编程语言提供的访问控制过于自信,甚至认为 Java 在安全性上比其它提供了(明显)更宽松的访问控制的语言要优越[^4]。然而,正如你所看到的,事实并不是这样。 ## 本章小结 -RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch` 语句,你可以用 RTTI 和 `switch` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能的使用多态机制,只在非用不可的时候才使用 RTTI。 +RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch` 语句,你可以用 RTTI 和 `switch` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能地使用多态机制,只在非用不可的时候才使用 RTTI。 然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 `switch` 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。 如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 `Instrument` 的类层次结构。假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 `Instrument` 中放入 `clearSpitValve()` 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 `Percussion`、弦乐器 `Stringed` 和电子乐器 `Electronic` 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 `clearSpitValve()` 放在某个合适的类中,在这个例子中是管乐器 `Wind`。不过,在这里你可能会发现还有更好的解决方法,就是将 `prepareInstrument()` 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。 -最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早的关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)[^5]。 +最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早地关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)[^5]。 -我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想的更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只有你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。 +我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想得更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只要你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。 [^1]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。 From e539642cf1d6d3d37c821c31b73dd8147f1737dc Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Fri, 15 Nov 2019 10:26:41 +0800 Subject: [PATCH 084/371] Update 06-Housekeeping.md --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 4721d296..00c636aa 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1363,7 +1363,7 @@ public class ArrayInit { 3, // Autoboxing }; System.out.println(Arrays.toString(a)); - System.out.println(Arrays.toString(a)); + System.out.println(Arrays.toString(b)); } } From 542e50db93267f0d03ce10fc8b2c067658ed4fbd Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Fri, 15 Nov 2019 10:29:56 +0800 Subject: [PATCH 085/371] Update 00-Introduction.md --- docs/book/00-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index 083b5a1b..ed3cf59a 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -43,7 +43,7 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中, ## C编程思想 -*Thinking in C* 已经可以在 www.OnJava8.com 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。 +*Thinking in C* 已经可以在 [www.OnJava8.com](https://archive.org/details/ThinkingInC) 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。 ## 源码下载 From 740b59eb95fe881f6aa510f7931fbd5b7c408796 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Sun, 17 Nov 2019 16:36:06 +0800 Subject: [PATCH 086/371] =?UTF-8?q?[revision=2020](=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E6=96=B9=E6=B3=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index daf2969d..2e6623bb 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1,11 +1,12 @@ [TOC] + # 第二十章 泛型 > 普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。 -多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了`final`类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。 +多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了 `final` 类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。 拘泥于单一的继承体系太过局限,因为只有继承体系中的对象才能适用基类作为参数的方法中。如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。这给予调用方一种选项,通过调整现有的类来实现接口,满足方法参数要求。接口可以突破继承体系的限制。 @@ -23,16 +24,17 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。 -但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好的做到所能做的(部分原因是,不必浪费时间在死胡同里)。 +但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里)。 第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。 因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。 + ## 简单泛型 -促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 +促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型: @@ -48,7 +50,7 @@ public class Holder1 { } ``` -这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到每个类型都编写一个新的类。 +这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类。 在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象: @@ -116,15 +118,15 @@ public class Diamond { 注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。 -一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定类型参数即可。 +一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定它们的名称和类型参数列表即可。 ### 一个元组类库 -有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个容器对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。 +有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。 -这个概念称为*元组*,它是将一组对象直接打包存储于其中的一个单一对象。可以从容器对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象* 或 *信使* )。 +这个概念称为*元组*,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象* 或 *信使* )。 -通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明其类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组: +通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组: ```java // onjava/Tuple2.java @@ -359,6 +361,7 @@ brown over fox quick quick dog brown The brown lazy brown `RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。 + ## 泛型接口 泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。 @@ -520,7 +523,7 @@ public class Fibonacci implements Supplier { 虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。 -如果还想更进一步,编写一个实现了 `Iterator` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterator` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。 +如果还想更进一步,编写一个实现了 `Iterable` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterable` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。 有多种方法可以实现适配器。例如,可以通过继承来创建适配器类: @@ -564,7 +567,7 @@ extends Fibonacci implements Iterable { 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 ``` -在循环语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。 +在 *for-in* 语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。 From 9e886db3fc0ae976e204a8c32478cf7f208a111b Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 11:04:03 +0800 Subject: [PATCH 087/371] =?UTF-8?q?=E2=88=9A=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AB=A0=E9=A6=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index c0cffe04..d70734cd 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -5,7 +5,27 @@ > Java 7 引入了一种简单明了的方式来读写文件和操作目录。大多情况下,[文件](./17-Files.md)这一章所介绍的那些库和技术就足够你用了。但是,如果你必须面对一些特殊的需求和比较底层的操作,或者处理一些老版本的代码,那么你就必须了解本附录中的内容。 -对于编程语言的设计者来说,搭建良好的输入/输出(I/O)系统是一项比较困难的任务。 +对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、带缓冲、二进制、字符、按行和按字等)。 + +Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的变改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。 + +因此,要想充分理解 Java I/O 系统以便正确运用它,我们需要学习一定数量的类。另外,理解 I/O 类库的演化过程也很有必要,因为如果缺乏历史的眼光,很快我们就会对什么时候该使用哪些类,以及什么时候不该使用它们而感到困惑。 + +编程语言的 I/O 类库经常使用**流**这个抽象概念,它将所有数据源或者数据接收器表示为能够产生或者接收数据片的对象。 + +> **注意:**Java 8 函数式编程中的 `Stream` 类和这里的 I/O stream 没有任何关系。这又是另一个例子,如果再给设计者一次重来的机会,他们将使用不同的术语。 + +I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: + +1. 字节流对应原生的二进制数据; +2. 字符流对应字符数据,它会自动处理与本地字符集之间的转换; +3. 缓冲流可以提高性能,通过减少底层 API 的调用次数来优化 I/O。 + +从 JDK 文档中的类层次结构中可以看到,Java 类库中的 I/O 类分成了输入和输出两部分。在设计 Java 1.0 时,类库的设计者们就决定让所有与输入有关系的类都继承自 `InputStream`,所有与输出有关系的类都继承自 `OutputStream`。所有从 `InputStream` 或 `Reader` 派生而来的类都含有名为 `read()` 的基本方法,用于读取单个字节或者字节数组。同样,所有从 `OutputStream` 或 `Writer` 派生而来的类都含有名为 `write()` 的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。 + +我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是**装饰器设计模式**)。为了创建一个流,你却要创建多个对象,这也是 Java I/O 类库让人困惑的主要原因。 + +这里我只会提供这些类的概述,并假定你会使用 JDK 文档来获取它们的详细信息(比如某个类的所以方法的详细列表)。 ## 输入流类型 From d0f6cc1586f9e08f0ce9ba3ba7760a5866b0b3e7 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 11:05:33 +0800 Subject: [PATCH 088/371] =?UTF-8?q?=E2=88=9A=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AB=A0=E9=A6=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index d70734cd..f70a40be 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -5,7 +5,7 @@ > Java 7 引入了一种简单明了的方式来读写文件和操作目录。大多情况下,[文件](./17-Files.md)这一章所介绍的那些库和技术就足够你用了。但是,如果你必须面对一些特殊的需求和比较底层的操作,或者处理一些老版本的代码,那么你就必须了解本附录中的内容。 -对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、带缓冲、二进制、字符、按行和按字等)。 +对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、缓冲、二进制、字符、按行和按字等)。 Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的变改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。 From b8d3c171b4ec061191911f83c1263fa80f57a51e Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 11:10:08 +0800 Subject: [PATCH 089/371] =?UTF-8?q?=E2=86=BB=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AB=A0=E9=A6=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index f70a40be..11a0c8d3 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -7,7 +7,7 @@ 对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、缓冲、二进制、字符、按行和按字等)。 -Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的变改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。 +Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。 因此,要想充分理解 Java I/O 系统以便正确运用它,我们需要学习一定数量的类。另外,理解 I/O 类库的演化过程也很有必要,因为如果缺乏历史的眼光,很快我们就会对什么时候该使用哪些类,以及什么时候不该使用它们而感到困惑。 @@ -21,7 +21,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 2. 字符流对应字符数据,它会自动处理与本地字符集之间的转换; 3. 缓冲流可以提高性能,通过减少底层 API 的调用次数来优化 I/O。 -从 JDK 文档中的类层次结构中可以看到,Java 类库中的 I/O 类分成了输入和输出两部分。在设计 Java 1.0 时,类库的设计者们就决定让所有与输入有关系的类都继承自 `InputStream`,所有与输出有关系的类都继承自 `OutputStream`。所有从 `InputStream` 或 `Reader` 派生而来的类都含有名为 `read()` 的基本方法,用于读取单个字节或者字节数组。同样,所有从 `OutputStream` 或 `Writer` 派生而来的类都含有名为 `write()` 的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。 +从 JDK 文档的类层次结构中可以看到,Java 类库中的 I/O 类分成了输入和输出两部分。在设计 Java 1.0 时,类库的设计者们就决定让所有与输入有关系的类都继承自 `InputStream`,所有与输出有关系的类都继承自 `OutputStream`。所有从 `InputStream` 或 `Reader` 派生而来的类都含有名为 `read()` 的基本方法,用于读取单个字节或者字节数组。同样,所有从 `OutputStream` 或 `Writer` 派生而来的类都含有名为 `write()` 的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。 我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是**装饰器设计模式**)。为了创建一个流,你却要创建多个对象,这也是 Java I/O 类库让人困惑的主要原因。 From a8df1af72e92db2193f01bcbbb5ad9ea77294fde Mon Sep 17 00:00:00 2001 From: zhen Date: Mon, 18 Nov 2019 12:01:40 +0800 Subject: [PATCH 090/371] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BF=BB=E8=AF=91=20?= =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E6=A0=87=E5=87=86IO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-Standard-IO.md | 189 ++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/docs/book/Appendix-Standard-IO.md b/docs/book/Appendix-Standard-IO.md index eab20ad3..0c61b233 100644 --- a/docs/book/Appendix-Standard-IO.md +++ b/docs/book/Appendix-Standard-IO.md @@ -3,9 +3,198 @@ # 附录:标准IO +>*标准 I/O*这个术语参考Unix中的概念,指程序所使用的单一信息流(这种思想在大多数操作系统中,也有相似形式的实现)。 + +程序的所有输入都可以来自于*标准输入*,其所有输出都可以流向*标准输出*,并且其所有错误信息均可以发送到*标准错误*。*标准 I/O* 的意义在于程序之间可以很容易地连接起来,一个程序的标准输出可以作为另一个程序的标准输入。这是一个非常强大的工具。 + +## 从标准输入中读取 + +遵循标准 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` 之前必须先对其进行包装。 + +我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader` 把 `System.in` 转换[^2]成 `Reader` 。下面这个例子将键入的每一行显示出来: + +```java +// standardio/Echo.java +// How to read from standard input +import java.io.*; +import onjava.TimedAbort; + +public class Echo { + public static void main(String[] args) { + TimedAbort abort = new TimedAbort(2); + new BufferedReader( + new InputStreamReader(System.in)) + .lines() + .peek(ln -> abort.restart()) + .forEach(System.out::println); + // Ctrl-Z or two seconds inactivity + // terminates the program + } +} +``` + +`BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。 + +## 将`System.out` 转换成 `PrintWriter` + +`System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。 + +```java +// standardio/ChangeSystemOut.java +// Turn System.out into a PrintWriter + +import java.io.*; + +public class ChangeSystemOut { + public static void main(String[] args) { + PrintWriter out = + new PrintWriter(System.out, true); + out.println("Hello, world"); + } +} +``` + +输出结果: + +``` +Hello, world +``` + +要使用 `PrintWriter` 带有两个参数的构造器,并设置第二个参数为 `true`,从而使能自动刷新到输出缓冲区的功能;否则,可能无法看到打印输出。 + +## 重定向标准 I/O + +Java的 `System` 类提供了简单的 `static` 方法调用,从而能够重定向标准输入流、标准输出流和标准错误流: +- setIn(InputStream) +- setOut(PrintStream) +- setErr(PrintStream) + +如果我们突然需要在显示器上创建大量的输出,而这些输出滚动的速度太快以至于无法阅读时,重定向输出就显得格外有用,可把输出内容重定向到文件中供后续查看。对于我们想重复测试特定的用户输入序列的命令行程序来说,重定向输入就很有价值。下例简单演示了这些方法的使用: + +```java +// standardio/Redirecting.java +// Demonstrates standard I/O redirection +import java.io.*; + +public class Redirecting { + public static void main(String[] args) { + PrintStream console = System.out; + try ( + BufferedInputStream in = new BufferedInputStream( + new FileInputStream("Redirecting.java")); + PrintStream out = new PrintStream( + new BufferedOutputStream( + new FileOutputStream("Redirecting.txt"))) + ) { + System.setIn(in); + System.setOut(out); + System.setErr(out); + new BufferedReader( + new InputStreamReader(System.in)) + .lines() + .forEach(System.out::println); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + System.setOut(console); + } + } +} +``` + +该程序将文件中内容载入到标准输入,并把标准输出和标准错误重定向到另一个文件。它在程序的开始保存了最初对 `System.out` 对象的引用,并且在程序结束时将系统输出恢复到了该对象上。 + +I/O重定向操作的是字节流而不是字符流,因此使用 `InputStream` 和 `OutputStream`,而不是 `Reader` 和 `Writer`。 + ## 执行控制 +你经常需要在Java内部直接执行操作系统的程序,并控制这些程序的输入输出,Java类库提供了执行这些操作的类。 + +一项常见的任务是运行程序并将输出结果发送到控制台。本节包含了一个可以简化此任务的实用工具。 + +在使用这个工具时可能会产生两种类型的错误:导致异常的普通错误——对于这些错误我们只需要重新抛出一个 `RuntimeException` 即可,以及进程自身的执行过程中导致的错误——我们需要用单独的异常来报告这些错误: + +```java +// onjava/OSExecuteException.java +package onjava; + +public class OSExecuteException extends RuntimeException { + public OSExecuteException(String why) { + super(why); + } +} +``` + +为了运行程序,我们需要传递给 `OSExecute.command()` 一个 `String command`,我们可以在控制台键入同样的指令运行程序。该命令传递给 `java.lang.ProcessBuilder` 的构造器(需要将其作为 `String` 对象的序列),然后启动生成的 `ProcessBuilder` 对象。 + +```java +// onjava/OSExecute.java +// Run an operating system command +// and send the output to the console +package onjava; +import java.io.*; + +public class OSExecute { + public static void command(String command) { + boolean err = false; + try { + Process process = new ProcessBuilder( + command.split(" ")).start(); + try ( + BufferedReader results = new BufferedReader( + new InputStreamReader( + process.getInputStream())); + BufferedReader errors = new BufferedReader( + new InputStreamReader( + process.getErrorStream())) + ) { + results.lines() + .forEach(System.out::println); + err = errors.lines() + .peek(System.err::println) + .count() > 0; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + if (err) + throw new OSExecuteException( + "Errors executing " + command); + } +} +``` + +为了捕获在程序执行时产生的标准输出流,我们可以调用 `getInputStream()`。这是因为 `InputStream` 是我们可以从中读取信息的流。 + +这里这些行只是被打印了出来,但是你也可以从 `command()` 捕获和返回它们。 + +该程序的错误被发送到了标准错误流,可以调用 `getErrorStream()` 捕获。如果存在任何错误,它们都会被打印并且抛出 `OSExcuteException` ,以便调用程序处理这个问题。 + +下面是展示如何使用 `OSExecute` 的示例: + +```java +// standardio/OSExecuteDemo.java +// Demonstrates standard I/O redirection +// {javap -cp build/classes/main OSExecuteDemo} +import onjava.*; + +public class OSExecuteDemo {} +``` + +这里使用 `javap` 反编译器(随JDK发布)来反编译程序,编译结果: + +``` +Compiled from "OSExecuteDemo.java" +public class OSExecuteDemo { + public OSExecuteDemo(); +} +``` + +[^1]: 译者注:这里用到了**装饰器模式**。 + +[^2]: 译者注:这里用到了**适配器模式**。 +
\ No newline at end of file From b6ca074471e2318a205172a0baafd0104ca2f7ff Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Mon, 18 Nov 2019 15:27:24 +0800 Subject: [PATCH 091/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a06018a2..d9672bf0 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ - [ ] [附录:文档注释](docs/book/Appendix-Javadoc.md) - [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) - [ ] [附录:流式IO](docs/book/Appendix-IO-Streams.md) -- [ ] [附录:标准IO](docs/book/Appendix-Standard-IO.md) +- [x] [附录:标准IO](docs/book/Appendix-Standard-IO.md) - [x] [附录:新IO](docs/book/Appendix-New-IO.md) - [x] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) - [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md) From 2a4d48da134946b715ff4f0a03120355c905c02f Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:12:30 +0800 Subject: [PATCH 092/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 59 ++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 11a0c8d3..625df110 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -13,7 +13,7 @@ Java 类库的设计者通过创建大量的类来解决这一难题。一开始 编程语言的 I/O 类库经常使用**流**这个抽象概念,它将所有数据源或者数据接收器表示为能够产生或者接收数据片的对象。 -> **注意:**Java 8 函数式编程中的 `Stream` 类和这里的 I/O stream 没有任何关系。这又是另一个例子,如果再给设计者一次重来的机会,他们将使用不同的术语。 +> **注意**:Java 8 函数式编程中的 `Stream` 类和这里的 I/O stream 没有任何关系。这又是另一个例子,如果再给设计者一次重来的机会,他们将使用不同的术语。 I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: @@ -30,19 +30,73 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输入流类型 +`InputStream` 表示那些从不同数据源产生输入的类,这些数据源包括: + +1. 字节数组; +2. `String` 对象; +3. 文件; +4. “管道”,工作方式与实际生活中的管道类似:从一端输入,从另一端输出; +5. 一个由其它种类的流组成的序列,然后我们可以把它们汇聚成一个流; +6. 其它数据源,如 `Internet` 连接。 + +每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 + +**表 I/O-1:`InputStream` 类型** + +| 类 | 功能 | 构造器参数 | 如何使用 | +| :--: | :-- | :-------- | :----- | +| `ByteArrayInputStream` | 允许将内存的缓冲区当做 `InputStream` 使用 | 缓冲区,字节将从中取出 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 | +| `StringBufferInputStream` | 将 `String` 转换成 `InputStream` | 字符串。底层实现实际使用 `StringBuffer` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 | +| `FileInputStream` | 用于从文件中读取信息 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 | +| `PipedInputStream` | 产生用于写入相关 `PipedOutputStream` 的数据。实现“管道化”概念 | `PipedOutputSteam` | 作为多线程中的数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 | +| `SequenceInputStream` | 将两个或多个 `InputStream` 对象转换成一个 `InputStream` | 两个 `InputStream` 对象或一个容纳 `InputStream` 对象的容器 `Enumeration` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 | +| `FilterInputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它的 `InputStream` 类提供有用的功能。见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) | ## 输出流类型 +**表 I/O-2:`OutputStream` 类型** + +| 类 | 功能 | 构造器参数 | 如何使用 | +| :--: | :-- | :-------- | :----- | +| `ByteArrayOutputStream` | 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区 | 缓冲区初始大小(可选) | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | +| `FileOutputStream` | 用于将信息接入文件 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | +| `PipedOutputStream` | 任何写入其中的信息都会自动作为相关 `PipedInputStream` 的输出。实现“管道化”概念 | `PipedInputStream` | 指定用于多线程的数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | +| `FilterOutputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它 `OutputStream` 提供有用功能。见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | + ## 添加属性和有用的接口 +### 通过 `FilterInputStream` 从 `InputStream` 读取 + +**表 I/O-3:`FilterInputStream` 类型** + + + +| 类 | 功能 | 构造器参数 | 如何使用 | +| :--: | :-- | :-------- | :----- | +| `DataInputStream` | | | | +| `BufferedInputStream` | | | | +| `LineNumberInputStream` | | | | +| `PushbackInputStream` | | | | + +### 通过 `FilterOutputStream` 向 `OutputStream` 写入 + +**表 I/O-4:`FilterOutputStream` 类型** + +| 类 | 功能 | 构造器参数 | 如何使用 | +| :--: | :-- | :-------- | :----- | +| `DataOutputStream` | | | | +| `PrintStream` | | | | +| `BufferedOutputStream` | | | | + ## Reader和Writer + ## RandomAccessFile类 @@ -57,4 +111,5 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: -
\ No newline at end of file +
+[^表 IO-3]: \ No newline at end of file From ae4256ad7387ee5eead5ba55fc1f4afb468d0208 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:15:11 +0800 Subject: [PATCH 093/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 625df110..4ab0f075 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -
**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,9 +71,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** - - +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -84,7 +82,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | From d2c936b549ec4099125d873b56e5a38f16fa51d2 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:16:16 +0800 Subject: [PATCH 094/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 4ab0f075..d2dc2f28 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,7 +71,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -82,7 +82,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | From 1908ac876c40e87c62a69bd48086210571927068 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:18:49 +0800 Subject: [PATCH 095/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index d2dc2f28..b837eec0 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,7 +71,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -82,7 +82,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | From 73707ab1ac33ebbc41e01b7a5cda2e4a3fe1ee72 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:19:46 +0800 Subject: [PATCH 096/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index b837eec0..d2dc2f28 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,7 +71,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -82,7 +82,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | From eb201a897fca675027a635ddf1f35e4a6fd8567b Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 16:23:10 +0800 Subject: [PATCH 097/371] =?UTF-8?q?=E2=98=81=20BACKUP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index d2dc2f28..b662009f 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,7 +71,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -82,7 +82,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | From 85fcae1d9e6ddbf48d52544ed3ed7f424824a226 Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 17:04:49 +0800 Subject: [PATCH 098/371] =?UTF-8?q?=E2=9C=93=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E5=89=8D=E4=B8=A4=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index b662009f..d6e00cfb 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -30,7 +30,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输入流类型 -`InputStream` 表示那些从不同数据源产生输入的类,这些数据源包括: +`InputStream` 表示那些从不同数据源产生输入的类,如[表 I/O-1](#table-io-1) 所示,这些数据源包括: 1. 字节数组; 2. `String` 对象; @@ -41,7 +41,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1:`InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,11 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -**表 I/O-2:`OutputStream` 类型** +如[表 I/O-2](#table-io-2) 所示,该类别的类决定了输出所要去往的目标:字节数组(但不是 `String`,当然,你可以用字节数组自己创建)、文件或管道。 + +另外,`FilterOutputStream` 为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口与输出流连接了起来,这些稍后会讨论。 + +**表 I/O-2:`OutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -71,26 +75,27 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ### 通过 `FilterInputStream` 从 `InputStream` 读取 -**表 I/O-3:`FilterInputStream` 类型** +**表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | -| `DataInputStream` | | | | -| `BufferedInputStream` | | | | -| `LineNumberInputStream` | | | | -| `PushbackInputStream` | | | | +| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int`、`char`、`long` 等) | `InputStream` | 包含用于读取基本类型数据的全部接口 | +| `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 | +| `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()` 和 `setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 | +| `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 | ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 -**表 I/O-4:`FilterOutputStream` 类型** +**表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | -| `DataOutputStream` | | | | -| `PrintStream` | | | | -| `BufferedOutputStream` | | | | +| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照一直方式向流中写入基本类型数据(`int`、`char`、`long` 等) | `OutputStream` | | +| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintOutputStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是都每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 | +| `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 | + ## Reader和Writer @@ -106,8 +111,10 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 本章小结 +[^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。 + +[^2]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。
-[^表 IO-3]: \ No newline at end of file From ca53609ef441961964a186f65ce835738e3d611e Mon Sep 17 00:00:00 2001 From: Moilk Date: Mon, 18 Nov 2019 17:31:34 +0800 Subject: [PATCH 099/371] =?UTF-8?q?=E2=86=BB=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E5=89=8D=E4=B8=A4=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index d6e00cfb..4e4c2b79 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -37,11 +37,11 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: 3. 文件; 4. “管道”,工作方式与实际生活中的管道类似:从一端输入,从另一端输出; 5. 一个由其它种类的流组成的序列,然后我们可以把它们汇聚成一个流; -6. 其它数据源,如 `Internet` 连接。 +6. 其它数据源,如 Internet 连接。 -每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后在讨论。 +每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后再讨论。 -**表 I/O-1:`InputStream` 类型** +**表 I/O-1 `InputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | @@ -55,7 +55,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 输出流类型 -如[表 I/O-2](#table-io-2) 所示,该类别的类决定了输出所要去往的目标:字节数组(但不是 `String`,当然,你可以用字节数组自己创建)、文件或管道。 +如[表 I/O-2](#table-io-2) 所示,该类别的类决定了输出所要去往的目标:字节数组(但不是 `String`,当然,你也可以用字节数组自己创建)、文件或管道。 另外,`FilterOutputStream` 为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口与输出流连接了起来,这些稍后会讨论。 @@ -64,7 +64,7 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | | `ByteArrayOutputStream` | 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区 | 缓冲区初始大小(可选) | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | -| `FileOutputStream` | 用于将信息接入文件 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | +| `FileOutputStream` | 用于将信息写入文件 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | | `PipedOutputStream` | 任何写入其中的信息都会自动作为相关 `PipedInputStream` 的输出。实现“管道化”概念 | `PipedInputStream` | 指定用于多线程的数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 | | `FilterOutputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它 `OutputStream` 提供有用功能。见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | @@ -90,8 +90,8 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | -| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照一直方式向流中写入基本类型数据(`int`、`char`、`long` 等) | `OutputStream` | | -| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintOutputStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是都每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 | +| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本类型数据(`int`、`char`、`long` 等) | `OutputStream` | 包含用于写入基本类型数据的全部接口 | +| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 | | `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 | From 29ee6242021d6770b8bc29499e5161f3342d4e4a Mon Sep 17 00:00:00 2001 From: Moilk Date: Tue, 19 Nov 2019 15:38:12 +0800 Subject: [PATCH 100/371] =?UTF-8?q?=E2=9C=93=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AC=AC=E4=B8=89=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 4e4c2b79..34a21cd4 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -72,25 +72,47 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## 添加属性和有用的接口 +装饰器在[泛型](./20-Generics.md)这一章引入。Java I/O 类库需要多种不同功能的组合,这正是使用装饰器模式的原因所在[^1]。而之所以存在 **filter**(过滤器)类,是因为让抽象类 **filter** 作为所有装饰器类的基类。装饰器必须具有和它所装饰对象相同的接口,但它也可以扩展接口,不过这种情况只发生在个别 **filter** 类中。 + +但是,装饰器模式也有一个缺点:在编写程序的时候,它给我们带来了相当多的灵活性(因为我们可以很容易地对属性进行混搭),但它同时也增加了代码的复杂性。Java I/O 类库操作不便的原因在于:我们必须创建许多类(“核心” I/O 类型加上所有的装饰器)才能得到我们所希望的单个 I/O 对象。 + +`FilterInputStream` 和 `FilterOutputStream` 是用来提供装饰器类接口以控制特定输入流 `InputStream` 和 输出流 `OutputStream` 的两个类,但它们的名字并不是很直观。`FilterInputStream` 和 `FilterOutputStream` 分别从 I/O 类库中的基类 `InputStream` 和 `OutputStream` 派生而来,这两个类是创建装饰器的必要条件(这样它们才能为所有被装饰的对象提供统一接口)。 ### 通过 `FilterInputStream` 从 `InputStream` 读取 +`FilterInputStream` 类能够完成两件截然不同的事情。其中,`DataInputStream` 允许我们读取不同的基本数据类型和 `String` 类型的对象(所有方法都以 “read” 开头,例如 `readByte()`、`readFloat()`等等)。搭配其对应的 `DataOutputStream`,我们就可以通过数据“流”将基本数据类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由[表 I/O-1](#table-io-1) 中的那些类决定的。 + +其它 `FilterInputStream` 类则在内部修改 `InputStream` 的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否允许把单个字符推回输入流等等。最后两个类看起来就像是为了创建编译器提供的(它们被添加进来可能是为了对“用 Java 构建编译器”实现提供支持),因此我们在一般编程中不会用到它们。 + +在实际应用中,不管连接的是什么 I/O 设备,我们基本上都会对输入进行缓冲。所以当初 I/O 类库如果能默认都让输入进行缓冲,同时将无缓冲输入作为一种特殊情况(或者只是简单地提供一个方法调用),这样会更加合理,而不是像现在这样迫使我们基本上每次都得手动添加缓冲。 + + **表 I/O-3:`FilterInputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | -| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int`、`char`、`long` 等) | `InputStream` | 包含用于读取基本类型数据的全部接口 | +| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int`、`char`、`long` 等) | `InputStream` | 包含用于读取基本数据类型的全部接口 | | `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 | | `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()` 和 `setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 | | `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 | ### 通过 `FilterOutputStream` 向 `OutputStream` 写入 +与 `DataInputStream` 对应的是 `DataOutputStream`,它可以将各种基本数据类型和 `String` 类型的对象格式化输出到“流”中,。这样一来,任何机器上的任何 `DataInputStream` 都可以读出它们。所有方法都以 “write” 开头,例如 `writeByte()`、`writeFloat()` 等等。 + +`PrintStream` 最初的目的就是为了以可视化格式打印所有基本数据类型和 `String` 类型的对象。这和 `DataOutputStream` 不同,后者的目的是将数据元素置入“流”中,使 `DataInputStream` 能够可移植地重构它们。 + +`PrintStream` 内有两个重要方法:`print()` 和 `println()`。它们都被重载了,可以打印各种各种数据类型。`print()` 和 `println()` 之间的差异是,后者在操作完毕后会添加一个换行符。 + +`PrintStream` 可能会造成一些问题,因为它捕获了所有 `IOException`(因此,我们必须使用 `checkError()` 自行测试错误状态,如果出现错误它会返回 `true`)。另外,`PrintStream` 没有处理好国际化问题。这些问题都在 `PrintWriter` 中得到了解决,这在后面会讲到。 + +`BufferedOutputStream` 是一个修饰符,表明这个“流”使用了缓冲技术,因此每次向流写入的时候,不是每次都会执行物理写操作。我们在进行输出操作的时候可能会经常用到它。 + **表 I/O-4:`FilterOutputStream` 类型** | 类 | 功能 | 构造器参数 | 如何使用 | | :--: | :-- | :-------- | :----- | -| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本类型数据(`int`、`char`、`long` 等) | `OutputStream` | 包含用于写入基本类型数据的全部接口 | +| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本数据类型(`int`、`char`、`long` 等) | `OutputStream` | 包含用于写入基本数据类型的全部接口 | | `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 | | `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 | From a12aa7cd260d8bac783e168a90f59d15ebd3a765 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Thu, 21 Nov 2019 10:02:36 +0800 Subject: [PATCH 101/371] Update 06-Housekeeping.md --- docs/book/06-Housekeeping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 00c636aa..e1d1f0c1 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -422,11 +422,11 @@ class Banana { } } public class BananaPeel { - public static void main(String[] args) [ + public static void main(String[] args) { Banana a = new Banana(), b = new Banana(); a.peel(1); b.peel(2); - ] + } } ``` From 3bf13da9d743a77b350ba2ce13eeaa8d880662b7 Mon Sep 17 00:00:00 2001 From: StormZhao Date: Sat, 23 Nov 2019 09:35:28 +0800 Subject: [PATCH 102/371] fix mistakes --- docs/book/05-Control-Flow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md index 2d630af9..e2699007 100644 --- a/docs/book/05-Control-Flow.md +++ b/docs/book/05-Control-Flow.md @@ -767,7 +767,7 @@ RED 一旦理解了 **switch**,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。 -作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、 [0,1]、(0,1) 、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”) +作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、 [0,1)、(0,1] 、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”) 下面是一个可能提供答案的测试程序。 所有命令行参数都作为 **String** 对象传递,因此我们可以 **switch** 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 `args` 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用**空字符串** `""` 替代;否则,选择 `args` 数组中的第一个元素: From 9d10fdb77b8b2ce188219d17d58b69ace2a607dc Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 23 Nov 2019 11:58:27 +0800 Subject: [PATCH 103/371] Update Appendix-Understanding-equals-and-hashCode.md --- docs/book/Appendix-Understanding-equals-and-hashCode.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Understanding-equals-and-hashCode.md b/docs/book/Appendix-Understanding-equals-and-hashCode.md index 71fa0c26..9cfeb1d5 100644 --- a/docs/book/Appendix-Understanding-equals-and-hashCode.md +++ b/docs/book/Appendix-Understanding-equals-and-hashCode.md @@ -804,7 +804,7 @@ public class StringHashCode { 对于String而言,hashCode() 明显是基于String的内容的。 -因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯E),但是通过hashCode() 和equals() ,必须能够完全确定对象的身份。 +因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过 hashCode() 和 equals() ,必须能够完全确定对象的身份。 因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int即可。 @@ -998,7 +998,7 @@ Rat Fuzzy 我们有可能手动调优HashMap以提高其在特定应用程序中的性能。为了理解调整HashMap时的性能问题,一些术语是必要的: - 容量(Capacity):表中存储的桶数量。 -- 初试容量(Initial Capacity):当表被创建时,桶的初始个数。 HashMap 和 HashSet 有可以让你指定初始容量的构造器。 +- 初始容量(Initial Capacity):当表被创建时,桶的初始个数。 HashMap 和 HashSet 有可以让你指定初始容量的构造器。 - 个数(Size):目前存储在表中的键值对的个数。 - 负载因子(Load factor):通常表现为 $\frac{size}{capacity}$。当负载因子大小为 0 的时候表示为一个空表。当负载因子大小为 0.5 表示为一个半满表(half-full table),以此类推。轻负载的表几乎没有冲突,因此是插入和查找的最佳选择(但会减慢使用迭代器进行遍历的过程)。 HashMap 和 HashSet 有可以让你指定负载因子的构造器。当表内容量达到了负载因子,集合就会自动扩充为原始容量(桶的数量)的两倍,并且会将原始的对象存储在新的桶集合中(也被称为 rehashing) From 0f0e11a115dc6cca5149c8b1fe016064d96195ec Mon Sep 17 00:00:00 2001 From: Moilk Date: Sat, 23 Nov 2019 22:47:24 +0800 Subject: [PATCH 104/371] =?UTF-8?q?=E2=9C=93=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AC=AC=E5=9B=9B=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 102 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 34a21cd4..2163b7ae 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -120,22 +120,122 @@ I/O 流屏蔽了实际的 I/O 设备中处理数据的细节: ## Reader和Writer +Java 1.1 对基本的 I/O 流类库做了重大的修改。你初次遇到 `Reader` 和 `Writer` 时,可能会以为这两个类是用来替代 `InputStream` 和 `OutputStream` 的,但实际上并不是这样。尽管一些原始的“流”类库已经过时了(如果使用它们,编译器会发出警告),但是 `InputStream` 和 `OutputStream` 在面向字节 I/O 这方面仍然发挥着极其重要的作用,而 `Reader` 和 `Writer` 则提供兼容 Unicode 和面向字符 I/O 的功能。另外: +1. Java 1.1 往 `InputStream` 和 `OutputStream` 的继承体系中又添加了一些新类,所以这两个类显然是不会被取代的; + +2. 有时我们必须把来自“字节”层级结构中的类和来自“字符”层次结构中的类结合起来使用。为了达到这个目的,需要用到“适配器(adapter)类”:`InputStreamReader` 可以把 `InputStream` 转换为 `Reader`,而 `OutputStreamWriter` 可以把 `OutputStream` 转换为 `Writer`。 + +设计 `Reader` 和 `Writer` 继承体系主要是为了国际化。老的 I/O 流继承体系仅支持 8 比特的字节流,并且不能很好地处理 16 比特的 Unicode 字符。由于 Unicode 用于字符国际化(Java 本身的 `char` 也是 16 比特的 Unicode),所以添加 `Reader` 和 `Writer` 继承体系就是为了让所有的 I/O 操作都支持 Unicode。另外,新类库的设计使得它的操作比旧类库要快。 + +### 数据的来源和去处 + +几乎所有原始的 Java I/O 流类都有相应的 `Reader` 和 `Writer` 类来提供原生的 Unicode 操作。但是在某些场合,面向字节的 `InputStream` 和 `OutputStream` 才是正确的解决方案。特别是 `java.util.zip` 类库就是面向字节而不是面向字符的。因此,最明智的做法是尽量**尝试**使用 `Reader` 和 `Writer`,一旦代码没法成功编译,你就会发现此时应该使用面向字节的类库了。 + +下表展示了在两个继承体系中,信息的来源和去处(即数据物理上来自哪里又去向哪里)之间的对应关系: + +| 来源与去处:Java 1.0 类 | 相应的 Java 1.1 类 | +| :-------------------: | :--------------: | +| `InputStream` | `Reader`
适配器:`InputStreamReader` | +| `OutputStream` | `Writer`
适配器:`OutputStreamWriter` | +| `FileInputStream` | `FileReader` | +| `FileOutputStream` | `FileWriter` | +| `StringBufferInputStream`(已弃用) | `StringReader` | +| (无相应的类) | `StringWriter` | +| `ByteArrayInputStream` | `CharArrayReader` | +| `ByteArrayOutputStream` | `CharArrayWriter` | +| `PipedInputStream` | `PipedReader` | +| `PipedOutputStream` | `PipedWriter` | + +总的来说,这两个不同的继承体系中的接口即便不能说完全相同,但也是非常相似的。 + +### 更改流的行为 + +对于 `InputStream` 和 `OutputStream` 来说,我们会使用 `FilterInputStream` 和 `FilterOutputStream` 的装饰器子类来修改“流”以满足特殊需要。`Reader` 和 `Writer` 的类继承体系沿用了相同的思想——但是并不完全相同。 + +在下表中,左右之间对应关系的近似程度现比上一个表格更加粗略一些。造成这种差别的原因是类的组织形式不同,`BufferedOutputStream` 是 `FilterOutputStream` 的子类,但 `BufferedWriter` 却不是 `FilterWriter` 的子类(尽管 `FilterWriter` 是抽象类,但却没有任何子类,把它放在表格里只是占个位置,不然你可能奇怪 `FilterWriter` 上哪去了)。然而,这些类的接口却又十分相似。 + +| 过滤器:Java 1.0 类 | 相应 Java 1.1 类 | +| :--------------- | :-------------- | +| `FilterInputStream` | `FilterReader` | +| `FilterOutputStream` | `FilterWriter` (抽象类,没有子类) | +| `BufferedInputStream` | `BufferedReader`(也有 `readLine()`) | +| `BufferedOutputStream` | `BufferedWriter` | +| `DataInputStream` | 使用 `DataInputStream`( 如果必须用到 `readLine()`,那你就得使用 `BufferedReader`。否则,一般情况下就用 `DataInputStream` | +| `PrintStream` | `PrintWriter` | +| `LineNumberInputStream` | `LineNumberReader` | +| `StreamTokenizer` | `StreamTokenizer`(使用具有 `Reader` 参数的构造器) | +| `PushbackInputStream` | `PushbackReader` | + +有一条限制需要明确:一旦要使用 `readLine()`,我们就不应该用 `DataInputStream`(否则,编译时会得到使用了过时方法的警告),而应该使用 `BufferedReader`。除了这种情况之外的情形中,`DataInputStream` 仍是 I/O 类库的首选成员。 + +为了使用时更容易过渡到 `PrintWriter`,它提供了一个既能接受 `Writer` 对象又能接受任何 `OutputStream` 对象的构造器。`PrintWriter` 的格式化接口实际上与 `PrintStream` 相同。 + +Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简化文件的创建过程,你马上就会见到它们。 + +其中一种 `PrintWriter` 构造器还有一个执行**自动 flush**[^2] 的选项。如果构造器设置了该选项,就会在每个 `println()` 调用之后,自动执行 flush。 + +### 未发生改变的类 + +有一些类在 Java 1.0 和 Java 1.1 之间未做改变。 + +| 以下这些 Java 1.0 类在 Java 1.1 中没有相应类 | +| --- | +| `DataOutputStream` | +| `File` | +| `RandomAccessFile` | +| `SequenceInputStream` | + +特别是 `DataOutputStream`,在使用时没有任何变化;因此如果想以可传输的格式存储和检索数据,请用 `InputStream` 和 `OutputStream` 继承体系。 ## RandomAccessFile类 + ## IO流典型用途 + +### 缓冲输入文件 + + + +### 从内存输入 + + + +### 格式化内存输入 + + + +### 基本文件的输出 + + + +### 文本文件输出快捷方式 + + + +### 存储和恢复数据 + + + +### 读写随机访问文件 + + + ## 本章小结 + + [^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。 -[^2]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。 +[^2]: 译者注:“flush” 直译是“清空”,意思是把缓冲中的数据清空,输送到对应的目的地(如文件和屏幕)。 + +[^3]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。 From 2db1dbc82daa2c264245143694c57150cae1330b Mon Sep 17 00:00:00 2001 From: tangsw Date: Sun, 24 Nov 2019 13:33:49 +0800 Subject: [PATCH 105/371] =?UTF-8?q?=E7=BA=A0=E6=AD=A3=E6=8B=BC=E5=86=99?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 5e56e294..142079a3 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -54,7 +54,7 @@ public class ImperativeRandoms { Random rand = new Random(47); SortedSet rints = new TreeSet<>(); while(rints.size() < 7) { - int r = rand.nextint(20); + int r = rand.nextInt(20); if(r < 5) continue; rints.add(r); } From 83a3bb89b95191951aa08203943c9c7324e9550c Mon Sep 17 00:00:00 2001 From: Moilk Date: Sun, 24 Nov 2019 20:22:22 +0800 Subject: [PATCH 106/371] =?UTF-8?q?=E2=9C=93=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81=20-=20=E7=AC=AC=E4=BA=94=E5=B0=8F=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index 2163b7ae..f17c3ddb 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -191,7 +191,13 @@ Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简 ## RandomAccessFile类 +`RandomAccessFile` 适用于由大小已知的记录组成的文件,所以我们可以使用 `seek()` 将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改。文件中记录的大小不一定都相同,只要我们能确定那些记录有多大以及它们在文件中的位置即可。 +最初,我们可能难以相信 `RandomAccessFile` 不是 `InputStream` 或者 `OutputStream` 继承体系中的一部分。除了实现了 `DataInput` 和 `DataOutput` 接口(`DataInputStream` 和 `DataOutputStream` 也实现了这两个接口)之外,它和这两个继承体系没有任何关系。它甚至都不使用 `InputStream` 和 `OutputStream` 类中已有的任何功能。它是一个完全独立的类,其所有的方法(大多数都是 `native` 方法)都是从头开始编写的。这么做是因为 `RandomAccessFile` 拥有和别的 I/O 类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接继承自 `Object`。 + +从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream` 和 `DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度,另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。 + +在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件(mmap)**取代,详见[附录:新 I/O](./Appendix-New-IO.md)。 ## IO流典型用途 From 475518cc7d1cd44e3148d2c53b52052cbc4ee720 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Mon, 25 Nov 2019 09:49:44 +0800 Subject: [PATCH 107/371] =?UTF-8?q?[revision=2020](=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E6=95=B0=E7=BB=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 527 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 511 insertions(+), 16 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 2e6623bb..3228a45a 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -611,12 +611,12 @@ GenericMethods 尽管可以同时对类及其方法进行参数化,但这里未将 **GenericMethods** 类参数化。只有方法 `f()` 具有类型参数,该参数由方法返回类型之前的参数列表指示。 -对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对`f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。 +对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对 `f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。 如果使用基本类型调用 `f()` ,自动装箱就开始起作用,自动将基本类型包装在它们对应的包装类型中。 -### 变量和泛型方法 +### 变长参数和泛型方法 泛型方法和变长参数列表可以很好地共存: @@ -658,7 +658,7 @@ S, T, U, V, W, X, Y, Z] `@SafeVarargs` 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。 -### 一个泛型的Supplier +### 一个泛型的 Supplier 这是一个为任意具有无参构造方法的类生成 **Supplier** 的类。为了减少键入,它还包括一个用于生成 **BasicSupplier** 的泛型方法: @@ -834,10 +834,10 @@ public class TupleTest2 { */ ``` -请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。 +请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。 -### 一个Set工具 +### 一个 Set 工具 对于泛型方法的另一个示例,请考虑由 **Set** 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法: @@ -898,7 +898,7 @@ public enum Watercolors { } ``` -为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](https://github.com/LingCoder/OnJava8/blob/master/docs/book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: +为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](./22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: ```java // generics/WatercolorSets.java @@ -1089,9 +1089,10 @@ Serializable] 在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 + ## 构建复杂模型 -泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以地轻松创建一个元组列表: +泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以轻松地创建一个元组列表: ```java // generics/TupleList.java @@ -1232,8 +1233,505 @@ public class Store extends ArrayList { ## 泛型擦除 +当你开始更深入地钻研泛型时,会发现有大量的东西初看起来是没有意义的。例如,尽管可以说 `ArrayList.class`,但不能说成 `ArrayList.class`。考虑下面的情况: + +```java +// generics/ErasedTypeEquivalence.java + +import java.util.*; + +public class ErasedTypeEquivalence { + + public static void main(String[] args) { + Class c1 = new ArrayList().getClass(); + Class c2 = new ArrayList().getClass(); + System.out.println(c1 == c2); + } + +} +/* Output: +true +*/ +``` + +`ArrayList` 和 `ArrayList` 应该是不同的类型。不同的类型会有不同的行为。例如,如果尝试向 `ArrayList` 中放入一个 `Integer`,所得到的行为(失败)和向 `ArrayList` 中放入一个 `Integer` 所得到的行为(成功)完全不同。然而上面的程序认为它们是相同的类型。 + +下面的例子是对该谜题的补充: + +```java +// generics/LostInformation.java + +import java.util.*; + +class Frob {} +class Fnorkle {} +class Quark {} + +class Particle {} + +public class LostInformation { + + public static void main(String[] args) { + List list = new ArrayList<>(); + Map map = new HashMap<>(); + Quark quark = new Quark<>(); + Particle p = new Particle<>(); + System.out.println(Arrays.toString(list.getClass().getTypeParameters())); + System.out.println(Arrays.toString(map.getClass().getTypeParameters())); + System.out.println(Arrays.toString(quark.getClass().getTypeParameters())); + System.out.println(Arrays.toString(p.getClass().getTypeParameters())); + } + +} +/* Output: +[E] +[K,V] +[Q] +[POSITION,MOMENTUM] +``` + +根据 JDK 文档,**Class.getTypeParameters()** “返回一个 **TypeVariable** 对象数组,表示泛型声明中声明的类型参数...” 这暗示你可以发现这些参数类型。但是正如上例中输出所示,你只能看到用作参数占位符的标识符,这并非有用的信息。 + +残酷的现实是: + +在泛型代码内部,无法获取任何有关泛型参数类型的信息。 + +因此,你可以知道如类型参数标识符和泛型边界这些信息,但无法得知实际的类型参数从而用来创建特定的实例。如果你曾是 C++ 程序员,那么这个事实会让你很沮丧,在使用 Java 泛型工作时,它是必须处理的最基本的问题。 + +Java 泛型是使用擦除实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此,`List` 和 `List` 在运行时实际上是相同的类型。它们都被擦除成原生类型 `List`。 + +理解擦除并知道如何处理它,是你在学习 Java 泛型时面临的最大障碍之一。这也是本节将要探讨的内容。 + +### C++ 的方式 + +下面是使用模版的 C++ 示例。你会看到类型参数的语法十分相似,因为 Java 是受 C++ 启发的: + +```c++ +// generics/Templates.cpp + +#include +using namespace std; + +template class Manipulator { + T obj; +public: + Manipulator(T x) { obj = x; } + void manipulate() { obj.f(); } +}; + +class HasF { +public: + void f() { cout << "HasF::f()" << endl; } +}; + +int main() { + HasF hf; + Manipulator manipulator(hf); + manipulator.manipulate(); +} +/* Output: +HasF::f() +*/ +``` + +**Manipulator** 类存储了一个 **T** 类型的对象。`manipulate()` 方法会调用 **obj** 上的 `f()` 方法。它是如何知道类型参数 **T** 中存在 `f()` 方法的呢?C++ 编译器会在你实例化模版时进行检查,所以在 `Manipulator` 实例化的那一刻,它看到 **HasF** 中含有一个方法 `f()`。如果情况并非如此,你就会得到一个编译期错误,保持类型安全。 + +用 C++ 编写这种代码很简单,因为当模版被实例化时,模版代码就知道模版参数的类型。Java 泛型就不同了。下面是 **HasF** 的 Java 版本: + +```java +// generics/HasF.java + +public class HasF { + public void f() { + System.out.println("HasF.f()"); + } +} +``` + +如果我们将示例的其余代码用 Java 实现,就不会通过编译: + +```java +// generics/Manipulation.java +// {WillNotCompile} + +class Manipulator { + private T obj; + + Manipulator(T x) { + obj = x; + } + + // Error: cannot find symbol: method f(): + public void manipulate() { + obj.f(); + } +} + +public class Manipulation { + public static void main(String[] args) { + HasF hf = new HasF(); + Manipulator manipulator = new Manipulator<>(hf); + manipulator.manipulate(); + } +} +``` + +因为擦除,Java 编译器无法将 `manipulate()` 方法必须能调用 **obj** 的 `f()` 方法这一需求映射到 HasF 具有 `f()` 方法这个事实上。为了调用 `f()`,我们必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了 **extends** 关键字。由于有了边界,下面的代码就能通过编译: + +```java +public class Manipulator2 { + private T obj; + + Manipulator2(T x) { + obj = x; + } + + public void manipulate() { + obj.f(); + } +} +``` + +边界 `` 声明 T 必须是 HasF 类型或其子类。如果情况确实如此,就可以安全地在 **obj** 上调用 `f()` 方法。 + +我们说泛型类型参数会擦除到它的第一个边界(可能有多个边界,稍后你将看到)。我们还提到了类型参数的擦除。编译器实际上会把类型参数替换为它的擦除,就像上面的示例,**T** 擦除到了 **HasF**,就像在类的声明中用 **HasF** 替换了 **T** 一样。 + +你可能正确地观察到了泛型在 **Manipulator2.java** 中没有贡献任何事。你可以很轻松地自己去执行擦除,生成没有泛型的类: + +```java +// generics/Manipulator3.java + +class Manipulator3 { + private HasF obj; + + Manipulator3(HasF x) { + ojb = x; + } + + public void manipulate() { + obj.f(); + } +} +``` + +这提出了很重要的一点:泛型只有在类型参数比某个具体类型(以及其子类)更加“泛化”——代码能跨多个类工作时才有用。因此,类型参数和它们在有用的泛型代码中的应用,通常比简单的类替换更加复杂。但是,不能因此认为使用 `` 形式就是有缺陷的。例如,如果某个类有一个返回 **T** 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型: + +```java +// generics/ReturnGenericType.java + +public class ReturnGenericType { + private T obj; + + ReturnGenericType(T x) { + obj = x; + } + + public T get() { + return obj; + } +} +``` + +你必须查看所有的代码,从而确定代码是否复杂到必须使用泛型的程度。 + +我们将在本章稍后看到有关边界的更多细节。 + +### 迁移兼容性 + +为了减少潜在的关于擦除的困惑,你必须清楚地认识到这不是一个语言特性。它是 Java 实现泛型的一种妥协,因为泛型不是 Java 语言出现时就有的,所以就有了这种妥协。它会使你痛苦,因此你需要尽早习惯它并了解为什么它会这样。 + +如果 Java 1.0 就含有泛型的话,那么这个特性就不会使用擦除来实现——它会使用具体化,保持参数类型为第一类实体,因此你就能在类型参数上执行基于类型的语言操作和反射操作。本章稍后你会看到,擦除减少了泛型的泛化性。泛型在 Java 中仍然是有用的,只是不如它们本来设想的那么有用,而原因就是擦除。 + +在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文使用泛型类型。泛型类型只有在静态类型检测期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如, `List` 这样的类型注解会被擦除为 **List**,普通的类型变量在未指定边界的情况下会被擦除为 **Object**。 + +擦除的核心动机是你可以在泛化的客户端上使用非泛型的类库,反之亦然。这经常被称为“迁移兼容性”。在理想情况下,所有事物将在指定的某天被泛化。在现实中,即使程序员只编写泛型代码,他们也必须处理 Java 5 之前编写的非泛型类库。这些类库的作者可能从没想过要泛化他们的代码,或许他们可能刚刚开始接触泛型。 + +因此 Java 泛型不仅必须支持向后兼容性——现有的代码和类文件仍然合法,继续保持之前的含义——而且还必须支持迁移兼容性,使得类库能按照它们自己的步调变为泛型,当某个类库变为泛型时,不会破坏依赖于它的代码和应用。在确定了这个目标后,Java 设计者们和从事此问题相关工作的各个团队决策认为擦除是唯一可行的解决方案。擦除使得这种向泛型的迁移成为可能,允许非泛型的代码和泛型代码共存。 + +例如,假设一个应用使用了两个类库 **X** 和 **Y**,**Y** 使用了类库 **Z**。随着 Java 5 的出现,这个应用和这些类库的创建者最终可能希望迁移到泛型上。但是当进行迁移时,它们有着不同的动机和限制。为了实现迁移兼容性,每个类库与应用必须与其他所有的部分是否使用泛型无关。因此,它们不能探测其他类库是否使用了泛型。因此,某个特定的类库使用了泛型这样的证据必须被”擦除“。 + +如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到 Java 泛型上的开发者们说再见了。类库毫无争议是编程语言的一部分,对生产效率有着极大的影响,所以这种代码无法接受。擦除是否是最佳的活唯一的迁移途径,还待时间来证明。 + +### 擦除的问题 + +因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下将泛型融入到语言中。擦除允许你继续使用现有的非泛型客户端代码,直至客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为它不会骤然破坏所有现有的代码。 + +擦除的代码是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、**instanceof** 操作和 **new** 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。 + +考虑如下的代码段: + +```java +class Foo { + T var; +} +``` + +看上去当你创建一个 **Foo** 实例时: + +```java +Foo f = new Foo<>(); +``` + +**class** **Foo** 中的代码应该知道现在工作于 **Cat** 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 **Object**“。 + +另外,擦除和迁移兼容性意味着,使用泛型并不是强制的,尽管你可能希望这样: + +```java +// generics/ErasureAndInheritance.java + +class GenericBase { + private T element; + + public void set(T arg) { + element = arg; + } + + public T get() { + return element; + } +} + +class Derived1 extends GenericBase {} + +class Derived2 extends GenericBase {} // No warning + +// class Derived3 extends GenericBase {} +// Strange error: +// unexpected type +// required: class or interface without bounds +public class ErasureAndInteritance { + @SuppressWarnings("unchecked") + public static void main(String[] args) { + Derived2 d2 = new Derived2(); + Object obj = d2.get(); + d2.set(obj); // Warning here! + } +} +``` + +**Derived2** 继承自 **GenericBase**,但是没有任何类型参数,编译器没有发出任何警告。直到调用 `set()` 方法时才出现警告。 + +为了关闭警告,Java 提供了一个注解,我们可以在列表中看到它: + +```java +@SuppressWarnings("unchecked") +``` + +这个注解放置在产生警告的方法上,而不是整个类上。当你要关闭警告时,最好尽可能地“聚焦”,这样就不会因为过于宽泛地关闭警告,而导致意外地遮蔽掉真正的问题。 + +可以推断,**Derived3** 产生的错误意味着编译器期望得到一个原生基类。 + +当你希望将类型参数不仅仅当作 Object 处理时,就需要付出额外努力来管理边界,并且与在 C++、Ada 和 Eiffel 这样的语言中获得参数化类型相比,你需要付出多得多的努力来获得少得多的回报。这并不是说,对于大多数的编程问题而言,这些语言通常都会比 Java 更得心应手,只是说它们的参数化类型机制相比 Java 更灵活、更强大。 + +### 边界处的动作 + +因为擦除,我发现了泛型最令人困惑的方面是可以表示没有任何意义的事物。例如: + +```java +// generics/ArrayMaker.java + +import java.lang.reflect.*; +import java.util.*; + +public class ArrayMaker { + private Class kind; + + public ArrayMaker(Class kind) { + this.kind = kind; + } + + @SuppressWarnings("unchecked") + T[] create(int size) { + return (T[]) Array.newInstance(kind, size); + } + + public static void main(String[] args) { + ArrayMaker stringMaker = new ArrayMaker<>(String.class); + String[] stringArray = stringMaker.create(9); + System.out.println(Arrays.toString(stringArray)); + } +} +/* Output +[null,null,null,null,null,null,null,null,null] +*/ +``` + +即使 **kind** 被存储为 `Class`,擦除也意味着它实际被存储为没有任何参数的 **Class**。因此,当你在使用它时,例如创建数组,`Array.newInstance()` 实际上并未拥有 **kind** 所蕴含的类型信息。所以它不会产生具体的结果,因而必须转型,这会产生一条令你无法满意的警告。 + +注意,对于在泛型中创建数组,使用 `Array.newInstance()` 是推荐的方式。 + +如果我们创建一个集合而不是数组,情况就不同了: + +```java +// generics/ListMaker.java + +import java.util.*; + +public class ListMaker { + List create() { + return new ArrayList<>(); + } + + public static void main(String[] args) { + ListMaker stringMaker = new ListMaker<>(); + List stringList = stringMaker.create(); + } +} +``` + +编译器不会给出任何警告,尽管我们知道(从擦除中)在 `create()` 内部的 `new ArrayList<>()` 中的 `` 被移除了——在运行时,类内部没有任何 ``,因此这看起来毫无意义。但是如果你遵从这种思路,并将这个表达式改为 `new ArrayList()`,编译器就会发出警告。 + +本例中这么做真的毫无意义吗?如果在创建 **List** 的同时向其中放入一些对象呢,像这样: + +```java +// generics/FilledList.java + +import java.util.*; +import java.util.function.*; +import onjava.*; + +public class FilledList extends ArrayList { + FilledList gen, int size) { + Suppliers.fill(this, gen, size); + } + + public FilledList(T t, int size) { + for (int i = 0; i < size; i++) { + this.add(t); + } + } + + public static void main(String[] args) { + List list = new FilledList<>("Hello", 4); + System.out.println(list); + // Supplier version: + List ilist = new FilledList<>(() -> 47, 4); + System.out.println(ilist); + } +} +/* Output: +[Hello,Hello,Hello,Hello] +[47,47,47,47] +``` + +即使编译器无法得知 `add()` 中的 **T** 的任何信息,但它仍可以在编译期确保你放入 **FilledList** 中的对象是 **T** 类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以确保方法或类中使用的类型的内部一致性。 + +因为擦除移除了方法体中的类型信息,所以在运行时的问题就是*边界*:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。 + +考虑如下这段非泛型示例: + +```java +// generics/SimpleHolder.java + +public class SimpleHolder { + private Object obj; + + public void set(Object obj) { + this.obj = obj; + } + + public Object get() { + return obj; + } + + public static void main(String[] args) { + SimpleHolder holder = new SimpleHolder(); + holder.set("Item"); + String s = (String) holder.get(); + } +} +``` + +如果用 **javap -c SimpleHolder** 反编译这个类,会得到如下内容(经过编辑): + +```java +public void set(java.lang.Object); + 0: aload_0 + 1: aload_1 + 2: putfield #2; // Field obj:Object; + 5: return + +public java.lang.Object get(); + 0: aload_0 + 1: getfield #2; // Field obj:Object; + 4: areturn + +public static void main(java.lang.String[]); + 0: new #3; // class SimpleHolder + 3: dup + 4: invokespecial #4; // Method "":()V + 7: astore_1 + 8: aload_1 + 9: ldc #5; // String Item + 11: invokevirtual #6; // Method set:(Object;)V + 14: aload_1 + 15: invokevirtual #7; // Method get:()Object; + 18: checkcast #8; // class java/lang/String + 21: astore_2 + 22: return +``` + +`set()` 和 `get()` 方法存储和产生值,转型在调用 `get()` 时接受检查。 + +现在将泛型融入上例代码中: + +```java +// generics/GenericHolder2.java + +public class GenericHolder2 { + private T obj; + + public void set(T obj) { + this.obj = obj; + } + + public T get() { + return obj; + } + + public static void main(String[] args) { + GenericHolder2 holder = new GenericHolder2<>(); + holder.set("Item"); + String s = holder.get(); + } +} +``` + +从 `get()` 返回后的转型消失了,但是我们还知道传递给 `set()` 的值在编译期会被检查。下面是相关的字节码: + +```java +public void set(java.lang.Object); + 0: aload_0 + 1: aload_1 + 2: putfield #2; // Field obj:Object; + 5: return + +public java.lang.Object get(); + 0: aload_0 + 1: getfield #2; // Field obj:Object; + 4: areturn + +public static void main(java.lang.String[]); + 0: new #3; // class GenericHolder2 + 3: dup + 4: invokespecial #4; // Method "":()V + 7: astore_1 + 8: aload_1 + 9: ldc #5; // String Item + 11: invokevirtual #6; // Method set:(Object;)V + 14: aload_1 + 15: invokevirtual #7; // Method get:()Object; + 18: checkcast #8; // class java/lang/String + 21: astore_2 + 22: return +``` + +所产生的字节码是相同的。对进入 `set()` 的类型进行检查是不需要的,因为这将由编译器执行。而对 `get()` 返回的值进行转型仍然是需要的,只不过不需要你来操作,它由编译器自动插入,这样你就不用编写(阅读)杂乱的代码。 + +`get()` 和 `set()` 产生了相同的字节码,这就告诉我们泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。 + ## 补偿擦除 因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型: @@ -1246,17 +1744,13 @@ public class Erased { private final int SIZE = 100; public void f(Object arg) { - // error: illegal generic type for instanceof if (arg instanceof T) { } - // error: unexpected type T var = new T(); - // error: generic array creation T[] array = new T[SIZE]; - // warning: [unchecked] unchecked cast T[] array = (T[]) new Object[SIZE]; @@ -1266,7 +1760,7 @@ public class Erased { 有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 **Class** 对象,以在类型表达式中使用它。 -例如,由于删除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` : +例如,由于擦除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` : ```java // generics/ClassTypeCapture.java @@ -1386,7 +1880,7 @@ java.lang.InstantiationException: java.lang.Integer */ ``` -这样可以编译,但对于 `ClassAsFactory \` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法: +这样可以编译,但对于 `ClassAsFactory` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法: ```java // generics/FactoryConstraint.java @@ -1469,7 +1963,7 @@ public class FactoryConstraint { */ ``` -**IntegerFactory** 本身就是通过实现 `Supplier\` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。 +**IntegerFactory** 本身就是通过实现 `Supplier` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。 另一种方法是模板方法设计模式。在以下示例中,`create()` 是模板方法,在子类中被重写以生成该类型的对象: @@ -1515,6 +2009,7 @@ X **GenericWithCreate** 包含 `element` 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 `create()` 方法。这种创建方式可以在子类中定义,同时建立 **T** 的类型。 + ### 泛型数组 正如在 **Erased.java** 中所看到的,我们无法创建泛型数组。通用解决方案是在试图创建泛型数组的时候使用 **ArrayList** : @@ -1584,7 +2079,7 @@ Generic[] */ ``` -问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic\[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个对象数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。 +问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个 **Object** 数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。 让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器: @@ -1631,7 +2126,7 @@ public class GenericArray { 和以前一样,我们不能说 `T[] array = new T[sz]` ,所以我们创建了一个 **Object** 数组并将其强制转换。 -`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]` ,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。 +`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]`,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。 如果再注释掉 **@SuppressWarnings** 注解后编译 **GenericArray.java** ,则编译器会产生警告: From e567f4ba0c4ade52897666c8e8f1b54ba9fc52f7 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Fri, 29 Nov 2019 21:50:14 +0800 Subject: [PATCH 108/371] =?UTF-8?q?revision[20]=20=E6=88=AA=E6=AD=A2=20?= =?UTF-8?q?=E9=80=9A=E9=85=8D=E7=AC=A6::=E9=80=86=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 193 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 3228a45a..249b4e5e 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2254,11 +2254,12 @@ Note: Recompile with -Xlint:unchecked for details. 果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。 -Neal Gafter(Java 5的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。 +Neal Gafter(Java 5 的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。 请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。 + ## 边界 *边界*(bounds)在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在绑定的类型中调用方法。 @@ -2567,10 +2568,200 @@ public class EpicBattle { 接下来将要研究的通配符将会把范围限制在单个类型。 + ## 通配符 +你已经在 [集合](./12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](./19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。 + +我们的起始示例要展示数组的一种特殊行为:你可以将派生类的数组赋值给基类的引用: + +```java +// generics/CovariantArrays.java + +class Fruit {} + +class Apple extends Fruit {} + +class Jonathan extends Apple {} + +class Orange extends Fruit {} + +public class CovariantArrays { + + public static void main(String[] args) { + Fruit[] fruit = new Apple[10]; + fruit[0] = new Apple(); // OK + fruit[1] = new Jonathan(); // OK + // Runtime type is Apple[], not Fruit[] or Orange[]: + try { + // Compiler allows you to add Fruit: + fruit[0] = new Fruit(); // ArrayStoreException + } catch (Exception e) { + System.out.println(e); + } + try { + // Compiler allows you to add Oranges: + fruit[0] = new Orange(); // ArrayStoreException + } catch (Exception e) { + System.out.println(e); + } + } +} +/* Output: +java.lang.ArrayStoreException: Fruit +java.lang.ArrayStoreException: Orange +``` + +`main()` 中的第一行创建了 **Apple** 数组,并赋值给一个 **Fruit** 数组引用。这是有意义的,因为 **Apple** 也是一种 **Fruit**,因此 **Apple** 数组应该也是一个 **Fruit** 数组。 + +但是,如果实际的数组类型是 **Apple[]**,你可以在其中放置 **Apple** 或 **Apple** 的子类型,这在编译期和运行时都可以工作。但是你也可以在数组中放置 **Fruit** 对象。这对编译器来说是有意义的,因为它有一个 **Fruit[]** 引用——它有什么理由不允许将 **Fruit** 对象或任何从 **Fruit** 继承出来的对象(比如 **Orange**),放置到这个数组中呢?因此在编译期,这是允许的。然而,运行时的数组机制知道它处理的是 **Apple[]**,因此会在向数组中放置异构类型时抛出异常。 + +向上转型用在这里不合适。你真正在做的是将一个数组赋值给另一个数组。数组的行为是持有其他对象,这里只是因为我们能够向上转型而已,所以很明显,数组对象可以保留有关它们包含的对象类型的规则。看起来就像数组对它们持有的对象是有意识的,因此在编译期检查和运行时检查之间,你不能滥用它们。 + +数组的这种赋值并不是那么可怕,因为在运行时你可以发现插入了错误的类型。但是泛型的主要目标之一是将这种错误检测移到编译期。所以当我们试图使用泛型集合代替数组时,会发生什么呢? + +```java +// generics/NonCovariantGenerics.java +// {WillNotCompile} + +import java.util.*; + +public class NonCovariantGenerics { + // Compile Error: incompatible types: + List flist = new ArrayList(); +} +``` + +尽管你在首次阅读这段代码时会认为“不能将一个 **Apple** 集合赋值给一个 **Fruit** 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 **Apple** 的泛型赋值给一个涉及 **Fruit** 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 将持有 **Apple** 和 **Apple** 的子类型,**Fruit** 的 **List** 将持有任何类型的 **Fruit**。是的,这包括 **Apple**,但是它不是一个 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在类型上不等价于 **Fruit** 的 **List**,即使 **Apple** 是一种 **Fruit** 类型。 + +真正的问题是我们在讨论的集合类型,而不是集合持有对象的类型。与数组不同,泛型没有内建的协变类型。这是因为数组是完全在语言中定义的,因此可以具有编译期和运行时的内建检查,但是在使用泛型时,编译器和运行时系统不知道你想用类型做什么,以及应该采用什么规则。 + +但是,有时你想在两个类型间建立某种向上转型关系。通配符可以产生这种关系。 + +```java +// generics/GenericsAndCovariance.java + +import java.util.*; + +public class GenericsAndCovariance { + + public static void main(String[] args) { + // Wildcards allow covariance: + List flist = new ArrayList<>(); + // Compile Error: can't add any type of object: + // flist.add(new Apple()); + // flist.add(new Fruit()); + // flist.add(new Object()); + flist.add(null); // Legal but uninteresting + // We know it returns at least Fruit: + Fruit f = flist.get(0); + } + +} +``` + +**flist** 的类型现在是 `List`,你可以读作“一个具有任何从 **Fruit** 继承的类型的列表”。然而,这实际上并不意味着这个 **List** 将持有任何类型的 **Fruit**。通配符引用的是明确的类型,因此它意味着“某种 **flist** 引用没有指定的具体类型”。因此这个被赋值的 **List** 必须持有诸如 **Fruit** 或 **Apple** 这样的指定类型,但是为了向上转型为 **Fruit**,这个类型是什么没人在意。 + +**List** 必须持有一种具体的 **Fruit** 或 **Fruit** 的子类型,但是如果你不关心具体的类型是什么,那么你能对这样的 **List** 做什么呢?如果不知道 **List** 中持有的对象是什么类型,你怎能保证安全地向其中添加对象呢?就像在 **CovariantArrays.java** 中向上转型一样,你不能,除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。 + +你可能认为事情开始变得有点走极端了,因为现在你甚至不能向刚刚声明过将持有 **Apple** 对象的 **List** 中放入一个 **Apple** 对象。是的,但编译器并不知道这一点。`List` 可能合法地指向一个 `List`。一旦执行这种类型的向上转型,你就丢失了向其中传递任何对象的能力,甚至传递 **Object** 也不行。 + +另一方面,如果你调用了一个返回 **Fruit** 的方法,则是安全的,因为你知道这个 **List** 中的任何对象至少具有 **Fruit** 类型,因此编译器允许这么做。 + +### 编译器有多聪明 + +现在你可能会猜想自己不能去调用任何接受参数的方法,但是考虑下面的代码: + +```java +// generics/CompilerIntelligence.java + +import java.util.*; + +public class CompilerIntelligence { + + public static void main(String[] args) { + List flist = Arrays.asList(new Apple()); + Apple a = (Apple) flist.get(0); // No warning + flist.contains(new Apple()); // Argument is 'Object' + flist.indexOf(new Apple()); // Argument is 'Object' + } + +} +``` + +这里对 `contains()` 和 `indexOf()` 的调用接受 **Apple** 对象作为参数,执行没问题。这是否意味着编译器实际上会检查代码,以查看是否有某个特定的方法修改了它的对象? + +通过查看 **ArrayList** 的文档,我们发现编译器没有那么聪明。尽管 `add()` 接受一个泛型参数类型的参数,但 `contains()` 和 `indexOf()` 接受的参数类型是 **Object**。因此当你指定一个 `ArrayList` 时,`add()` 的参数就变成了"**? extends Fruit**"。从这个描述中,编译器无法得知这里需要 **Fruit** 的哪个具体子类型,因此它不会接受任何类型的 **Fruit**。如果你先把 **Apple** 向上转型为 **Fruit**,也没有关系——编译器仅仅会拒绝调用像 `add()` 这样参数列表中涉及通配符的方法。 + +`contains()` 和 `indexOf()` 的参数类型是 **Object**,不涉及通配符,所以编译器允许调用它们。这意味着将由泛型类的设计者来决定哪些调用是“安全的”,并使用 **Object** 类作为它们的参数类型。为了禁止对类型中使用了通配符的方法调用,需要在参数列表中使用类型参数。 + +下面展示一个简单的 **Holder** 类: + +```java +public class Holder { + + private T value; + + public Holder() {} + + public Holder(T val) { + value = val; + } + + public void set(T val) { + value = val; + } + + public T get() { + return value; + } + + @Override + public boolean equals(Object o) { + return o instanceof Holder && Objects.equals(value, ((Holder) o).value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + public static void main(String[] args) { + Holder apple = new Holder<>(new Apple()); + Apple d = apple.get(); + apple.set(d); +// Holder fruit = apple; // Cannot upcast + Holder fruit = apple; // OK + Fruit p = fruit.get(); + d = (Apple) fruit.get(); + try { + Orange c = (Orange) fruit.get(); // No warning + } catch (Exception e) { + System.out.println(e); + } +// fruit.set(new Apple()); // Cannot call set() +// fruit.set(new Fruit()); // Cannot call set() + System.out.println(fruit.equals(d)); // OK + } +} +/* Output +java.lang.ClassCastException: Apple cannot be cast to Orange +false +*/ +``` + +**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何;额扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是"**? extends Fruit**",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。 + +但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。 + +Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](./Appendix-Understanding-equals-and-hashCode) 一章。 + +### 逆变 + + + ## 问题 From c98657df794f21c8992d2dad54bc347eb54a3597 Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Sat, 30 Nov 2019 16:51:28 +0800 Subject: [PATCH 109/371] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E6=95=B4=E4=B8=AA?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 249b4e5e..822d48f2 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2764,16 +2764,26 @@ Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode( ## 问题 - -## 自我约束类型 +## 自我约束类型 -## 动态类型安全 +Java泛型中会定期出现一种让人费解的习惯用法。 看起来是这样的: + +``` +class SelfBounded> { // ... +``` + +这具有两个指向彼此的镜子的眩晕效果,这是一种无限的反射。 **SelfBounded**类采用通用参数`T`,`T`受一个界限约束,该界限为**SelfBounded**,其中`T`为参数。 + +## 动态类型安全 + + + ## 泛型异常 From 1ed6c5f73ddc72e277825ad70b77f8c410bdcfff Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Sun, 1 Dec 2019 00:10:02 +0800 Subject: [PATCH 110/371] =?UTF-8?q?=E7=BF=BB=E8=AF=9120=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E5=88=B0=E9=80=9A=E9=85=8D=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 555 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 552 insertions(+), 3 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 822d48f2..9398967f 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2758,25 +2758,574 @@ Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode( ### 逆变 +还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,甚至或者使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了: +```java +// generics/SuperTypeWildcards.java +import java.util.*; +public class SuperTypeWildcards { + static void writeTo(List apples) { + apples.add(new Apple()); + apples.add(new Jonathan()); + // apples.add(new Fruit()); // Error + } +} +``` + +参数 **Apple** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是,既然 **Apple** 是下界,那么你可以知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。 +因此你可能会根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。 +超类型边界放松了在可以向方法传递的参数上所作的限制,此示例提供了逆变和通配符的概述:: + +```java +// generics/GenericReading.java +import java.util.*; + +public class GenericReading { + static List apples = + Arrays.asList(new Apple()); + static List fruit = Arrays.asList(new Fruit()); + static T readExact(List list) { + return list.get(0); + } + // A static method adapts to each call: + static void f1() { + Apple a = readExact(apples); + Fruit f = readExact(fruit); + f = readExact(apples); + } + // A class type is established + // when the class is instantiated: + static class Reader { + T readExact(List list) { return list.get(0); } + } + static void f2() { + Reader fruitReader = new Reader<>(); + Fruit f = fruitReader.readExact(fruit); + //- Fruit a = fruitReader.readExact(apples); + // error: incompatible types: List + // cannot be converted to List + } + static class CovariantReader { + T readCovariant(List list) { + return list.get(0); + } + } + static void f3() { + CovariantReader fruitReader = + new CovariantReader<>(); + Fruit f = fruitReader.readCovariant(fruit); + Fruit a = fruitReader.readCovariant(apples); + } + public static void main(String[] args) { + f1(); f2(); f3(); + } +} +``` + +第一个方法 `readExact()` 使用了精确的类型。因此如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么当只是读取时,就不需要协变类型了。 +但是,如果有一个泛型类,那么当你创建这个类的实例时,要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 还应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。 +为了修正这个问题,`CovariantReader.readCovcariant()` 方法将接受 `List<?extendsT>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从T导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。 + +### 无界通配符 + +无界通配符<?>看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的: + +```java +// generics/UnboundedWildcards1.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. +import java.util.*; + +public class UnboundedWildcards1 { + static List list1; + static List list2; + static List list3; + static void assign1(List list) { + list1 = list; + list2 = list; + //- list3 = list; + // warning: [unchecked] unchecked conversion + // list3 = list; + // ^ + // required: List + // found: List + } + static void assign2(List list) { + list1 = list; + list2 = list; + list3 = list; + } + static void assign3(List list) { + list1 = list; + list2 = list; + list3 = list; + } + public static void main(String[] args) { + assign1(new ArrayList()); + assign2(new ArrayList()); + //- assign3(new ArrayList()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards1 + // is applied to given types + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // warning: [unchecked] unchecked conversion + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // 2 warnings + assign1(new ArrayList<>()); + assign2(new ArrayList<>()); + assign3(new ArrayList<>()); + // Both forms are acceptable as List: + List wildList = new ArrayList(); + wildList = new ArrayList<>(); + assign1(wildList); + assign2(wildList); + assign3(wildList); + } +} +``` + +有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上,它是在声明:“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。” +第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要: + +```java +// generics/UnboundedWildcards2.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. +import java.util.*; + +public class UnboundedWildcards2 { + static Map map1; + static Map map2; + static Map map3; + static void assign1(Map map) { map1 = map; } + static void assign2(Map map) { map2 = map; } + static void assign3(Map map) { map3 = map; } + public static void main(String[] args) { + assign1(new HashMap()); + assign2(new HashMap()); + //- assign3(new HashMap()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards2 + // is applied to given types + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // warning: [unchecked] unchecked conversion + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // 2 warnings + assign1(new HashMap<>()); + assign2(new HashMap<>()); + assign3(new HashMap<>()); + } +} +``` + +但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards.java** 展示了编译器处理 `List` 和 `List` 时是不同的。 +令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。因为,事实上,由于泛型参数将擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List<?>` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道那种类型是什么。” +编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式: +作为原生类型,具有具体的类型参数以及具有无界通配符参数: + +```java +// generics/Wildcards.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. +// Exploring the meaning of wildcards + +public class Wildcards { + // Raw argument: + static void rawArgs(Holder holder, Object arg) { + //- holder.set(arg); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // holder.set(arg); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning + + // Can't do this; don't have any 'T': + // T t = holder.get(); + + // OK, but type information is lost: + Object obj = holder.get(); + } + // Like rawArgs(), but errors instead of warnings: + static void + unboundedArg(Holder holder, Object arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: Object + // reason: argument mismatch; + // Object cannot be converted to CAP#1 + // where T is a type-variable: + // T extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + // Can't do this; don't have any 'T': + // T t = holder.get(); + + // OK, but type information is lost: + Object obj = holder.get(); + } + static T exact1(Holder holder) { + return holder.get(); + } + static T exact2(Holder holder, T arg) { + holder.set(arg); + return holder.get(); + } + static + T wildSubtype(Holder holder, T arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: T#1 + // reason: argument mismatch; + // T#1 cannot be converted to CAP#1 + // where T#1,T#2 are type-variables: + // T#1 extends Object declared in method + // wildSubtype(Holder,T#1) + // T#2 extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends T#1 from + // capture of ? extends T#1 + // 1 error + + return holder.get(); + } + static + void wildSupertype(Holder holder, T arg) { + holder.set(arg); + //- T t = holder.get(); + // error: incompatible types: + // CAP#1 cannot be converted to T + // T t = holder.get(); + // ^ + // where T is a type-variable: + // T extends Object declared in method + // wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object super: + // T from capture of ? super T + // 1 error + + // OK, but type information is lost: + Object obj = holder.get(); + } + public static void main(String[] args) { + Holder raw = new Holder<>(); + // Or: + raw = new Holder(); + Holder qualified = new Holder<>(); + Holder unbounded = new Holder<>(); + Holder bounded = new Holder<>(); + Long lng = 1L; + + rawArgs(raw, lng); + rawArgs(qualified, lng); + rawArgs(unbounded, lng); + rawArgs(bounded, lng); + + unboundedArg(raw, lng); + unboundedArg(qualified, lng); + unboundedArg(unbounded, lng); + unboundedArg(bounded, lng); + + //- Object r1 = exact1(raw); + // warning: [unchecked] unchecked method invocation: + // method exact1 in class Wildcards is applied + // to given types + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // warning: [unchecked] unchecked conversion + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // 2 warnings + + Long r2 = exact1(qualified); + Object r3 = exact1(unbounded); // Must return Object + Long r4 = exact1(bounded); + + //- Long r5 = exact2(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method exact2 in class Wildcards is + // applied to given types + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // 2 warnings + + Long r6 = exact2(qualified, lng); + + //- Long r7 = exact2(unbounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r7 = exact2(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T has + // incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + //- Long r8 = exact2(bounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r8 = exact2(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T + // has incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from + // capture of ? extends Long + // 1 error + + //- Long r9 = wildSubtype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSubtype in class Wildcards + // is applied to given types + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // 2 warnings + + Long r10 = wildSubtype(qualified, lng); + // OK, but can only return Object: + Object r11 = wildSubtype(unbounded, lng); + Long r12 = wildSubtype(bounded, lng); + + //- wildSupertype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSupertype in class Wildcards + // is applied to given types + // wildSupertype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // warning: [unchecked] unchecked conversion + // wildSupertype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // 2 warnings + + wildSupertype(qualified, lng); + + //- wildSupertype(unbounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + //- wildSupertype(bounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from capture of + // ? extends Long + // 1 error + } +} +``` +在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此,无论何时;只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object** 。 +人们很自然地会开始考虑原生 `Holder` 与 `Holder` 是大致相同的事物。但是 `unboundedArg()` 强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生 **Holder** 将持有任何类型的组合,而 `Holder` 将持有具有某种具体类型的同构集合,因此不能只是向其中传递 **Object** 。 +在 `exact1()` 和 `exact2()` 中,你可以看到使用了确切的泛型参数——没有任何通配符。你将看到,`exact2()`与 `exact1()` 具有不同的限制,因为它有额外的参数。 +在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果T是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。 +`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何T的基类型的容器。因此, `set()` 可以接受**T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。 +这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制。对于迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。 + +如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生 **Holder** 引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向 `exact1()` 传递一个无界引用,就不会有任何可以确定返回类型的类型信息。 +可以看到,`exact2()` 具有最多的限制,因为它希望精确地得到一个 `Holder` ,以及一个具有类型 **T** 的参数,正由于此,它将产生错误或警告,除非提供确切的参数。有时,这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 `wildSubtype()` 中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在 `wildSupertype()` 中看到的那样)。 +因此,使用确切类型来替代通配符类型的好处是,可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。 + +### 捕获转换 + +有一种情况特别需要使用 ``而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用: + +```java +// generics/CaptureConversion.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. + +public class CaptureConversion { + static void f1(Holder holder) { + T t = holder.get(); + System.out.println(t.getClass().getSimpleName()); + } + static void f2(Holder holder) { + f1(holder); // Call with captured type + } + @SuppressWarnings("unchecked") + public static void main(String[] args) { + Holder raw = new Holder<>(1); + + f1(raw); + // warning: [unchecked] unchecked method invocation: + // method f1 in class CaptureConversion + // is applied to given types + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // warning: [unchecked] unchecked conversion + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // 2 warnings + + f2(raw); // No warnings + Holder rawBasic = new Holder(); + + rawBasic.set(new Object()); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // rawBasic.set(new Object()); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning + + f2(rawBasic); // No warnings + // Upcast to Holder, still figures it out: + Holder wildcarded = new Holder<>(1.0); + f2(wildcarded); + } +} +/* Output: +Integer +Integer +Object +Double +*/ +``` + +`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中,`f1()` 被调用,而 `f1()` 需要一个已知参数。这里所发生的是:参数类型在调用 `f2()` 的过程中被捕获,因此它可以在对 `f1()` 的调用中被使用。 +你可能想知道,这项技术是否可以用于写入,但是这要求要在传递 `Holder`时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()`中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 + ## 问题 -## 自我约束类型 +## 自限定的类型 -Java泛型中会定期出现一种让人费解的习惯用法。 看起来是这样的: +在Java泛型中,有一个好像是经常性出现的惯用法,它相当令人费解: ``` class SelfBounded> { // ... ``` -这具有两个指向彼此的镜子的眩晕效果,这是一种无限的反射。 **SelfBounded**类采用通用参数`T`,`T`受一个界限约束,该界限为**SelfBounded**,其中`T`为参数。 +这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而T由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 +当你首次看到它时,很难去解析它,它强调的是当 `extends` 关键字用于边界与用来创建子类明显是不同的。 ## 动态类型安全 From 4d47d0adb1a18cd5e9900db7c84a420ce3ba0ce2 Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Sun, 1 Dec 2019 10:10:33 +0800 Subject: [PATCH 111/371] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=BF=BB=E8=AF=91=20?= =?UTF-8?q?=E7=AC=AC20=E7=AB=A0=E5=88=B0=E9=97=AE=E9=A2=98=E7=AB=A0?= =?UTF-8?q?=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: siyuanzhou --- docs/book/20-Generics.md | 268 +++++++++++++++++- .../Appendix-Passing-and-Returning-Objects.md | 30 +- 2 files changed, 284 insertions(+), 14 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 9398967f..703eda09 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2832,9 +2832,6 @@ public class GenericReading { ```java // generics/UnboundedWildcards1.java -// (c)2017 MindView LLC: see Copyright.txt -// We make no guarantees that this code is fit for any purpose. -// Visit http://OnJava8.com for more book information. import java.util.*; public class UnboundedWildcards1 { @@ -2896,9 +2893,6 @@ public class UnboundedWildcards1 { ```java // generics/UnboundedWildcards2.java -// (c)2017 MindView LLC: see Copyright.txt -// We make no guarantees that this code is fit for any purpose. -// Visit http://OnJava8.com for more book information. import java.util.*; public class UnboundedWildcards2 { @@ -2939,9 +2933,6 @@ public class UnboundedWildcards2 { ```java // generics/Wildcards.java -// (c)2017 MindView LLC: see Copyright.txt -// We make no guarantees that this code is fit for any purpose. -// Visit http://OnJava8.com for more book information. // Exploring the meaning of wildcards public class Wildcards { @@ -3244,9 +3235,6 @@ public class Wildcards { ```java // generics/CaptureConversion.java -// (c)2017 MindView LLC: see Copyright.txt -// We make no guarantees that this code is fit for any purpose. -// Visit http://OnJava8.com for more book information. public class CaptureConversion { static void f1(Holder holder) { @@ -3312,6 +3300,255 @@ Double ## 问题 +本节将阐述在使用Java泛型时会出现的各类问题。 + +### 任何基本类型都不能作为类型参数 + +正如本章早先提到过的,你将在Java泛型中发现的限制之一是,不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。 +解决之道是使用基本类型的包装器类以及JavaSE5的自动包装机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个容器,那么你将发现自动包装机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList`一样: + +```java +// generics/ListOfInt.java +// Autoboxing compensates for the inability +// to use primitives in generics +import java.util.*; +import java.util.stream.*; + +public class ListOfInt { + public static void main(String[] args) { + List li = IntStream.range(38, 48) + .boxed() // Converts ints to Integers + .collect(Collectors.toList()); + System.out.println(li); + } +} +/* Output: +[38, 39, 40, 41, 42, 43, 44, 45, 46, 47] +*/ +``` + +注意,自动包装机制甚至允许用foreach语法来产生 **int** 。 +通常,这种解决方案工作得很好——能够成功地存储和读取 **int** ,有一些转换碰巧在发生的同时会对你屏蔽掉。但是,如果性能成为了问题,就需要使用专门适配基本类型的容器版。**Org.apache.commons.collections.primitives** 就是一种开源的这类版本。 +下面是另外一种方式,它可以创建持有 **Byte** 的 **Set** : + +```java +// generics/ByteSet.java +import java.util.*; + +public class ByteSet { + Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; + Set mySet = + new HashSet<>(Arrays.asList(possibles)); + // But you can't do this: + // Set mySet2 = new HashSet<>( + // Arrays.asList(1,2,3,4,5,6,7,8,9)); +} +``` + +注意,自动包装机制解决了一些问题,但并不是解决了所有问题。 + +在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的) **Supplier** 实现来自“数组”一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用它在数组中填充对象: + +```java +// generics/PrimitiveGenericTest.java +import onjava.*; +import java.util.*; +import java.util.function.*; + +// Fill an array using a generator: +interface FillArray { + static T[] fill(T[] a, Supplier gen) { + Arrays.setAll(a, n -> gen.get()); + return a; + } + static int[] fill(int[] a, IntSupplier gen) { + Arrays.setAll(a, n -> gen.getAsInt()); + return a; + } + static long[] fill(long[] a, LongSupplier gen) { + Arrays.setAll(a, n -> gen.getAsLong()); + return a; + } + static double[] fill(double[] a, DoubleSupplier gen) { + Arrays.setAll(a, n -> gen.getAsDouble()); + return a; + } +} + +public class PrimitiveGenericTest { + public static void main(String[] args) { + String[] strings = FillArray.fill( + new String[5], new Rand.String(9)); + System.out.println(Arrays.toString(strings)); + int[] integers = FillArray.fill( + new int[9], new Rand.Pint()); + System.out.println(Arrays.toString(integers)); + } +} +/* Output: +[btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl] +[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768, 4948] +*/ +``` + +自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用,因为它返回填充的数组。 + +### 实现参数化接口 + +一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况: + +```java +// generics/MultipleInterfaceVariants.java +// {WillNotCompile} +package generics; + +interface Payable {} + +class Employee implements Payable {} + +class Hourly extends Employee +implements Payable {} +``` + +**Hourly** 不能编译,因为擦除会将 `Payable` 和 `Payable` 简化为相同的类 **Payable**,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 **Payable** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。 + +在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后就会看到的那样。 + +### 转型和警告 + +使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的容器在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**: + +```java +// generics/GenericCast.java +import java.util.*; +import java.util.stream.*; + +class FixedSizeStack { + private final int size; + private Object[] storage; + private int index = 0; + FixedSizeStack(int size) { + this.size = size; + storage = new Object[size]; + } + public void push(T item) { + if(index < size) + storage[index++] = item; + } + @SuppressWarnings("unchecked") + public T pop() { + return index == 0 ? null : (T)storage[--index]; + } + @SuppressWarnings("unchecked") + Stream stream() { + return (Stream)Arrays.stream(storage); + } +} + +public class GenericCast { + static String[] letters = + "ABCDEFGHIJKLMNOPQRS".split(""); + public static void main(String[] args) { + FixedSizeStack strings = + new FixedSizeStack<>(letters.length); + Arrays.stream("ABCDEFGHIJKLMNOPQRS".split("")) + .forEach(strings::push); + System.out.println(strings.pop()); + strings.stream() + .map(s -> s + " ") + .forEach(System.out::print); + } +} +/* Output: +S +A B C D E F G H I J K L M N O P Q R S +*/ +``` + +如果没有 **@SuppressWarnings** 注解,编译器将对 `pop()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop()` 方法实际上并没有执行任何转型。 +这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。 +有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如: + +```java +// generics/NeedCasting.java +import java.io.*; +import java.util.*; + +public class NeedCasting { + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + List shapes = (List)in.readObject(); + } +} +``` + +正如你将在附件:对象序列化( Appendix: Object Serialization)中学到的那样,`readObject() `无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解,并编译这个程序时,就会得到下面的警告。 + +``` +NeedCasting.java uses unchecked or unsafe operations. +Recompile with -Xlint:unchecked for details. + +And if you follow the instructions and recompile with - +Xlint:unchecked :(如果遵循这条指示,使用-Xlint:unchecked来重新编译:) + +NeedCasting.java:10: warning: [unchecked] unchecked cast + List shapes = (List)in.readObject(); + required: List + found: Object +1 warning +``` + +你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用在 Java SE5 中引入的新的转型形式,既通过泛型类来转型: + +```java +// generics/ClassCasting.java +import java.io.*; +import java.util.*; + +public class ClassCasting { + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + // Won't Compile: +// List lw1 = +// List<>.class.cast(in.readObject()); + List lw2 = List.class.cast(in.readObject()); + } +} +``` + +但是,不能转型到实际类型( `List` )。也就是说,不能声明: + +``` +List.class.cast(in.readobject()) +``` + +甚至当你添加一个像下面这样的另一个转型时: + +``` +(List)List.class.cast(in.readobject()) +``` + +仍旧会得到一个警告。 + +### 重载 + +下面的程序是不能编译的,即使编译它是一种合理的尝试: + +```java +// generics/UseList.java +// {WillNotCompile} +import java.util.*; + +public class UseList { + void f(List v) {} + void f(List v) {} +} +``` + ## 自限定的类型 @@ -3325,7 +3562,12 @@ class SelfBounded> { // ... ``` 这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而T由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 -当你首次看到它时,很难去解析它,它强调的是当 `extends` 关键字用于边界与用来创建子类明显是不同的。 +当你首次看到它时,很难去解析它,它强调的是当 **extends** 关键字用于边界与用来创建子类明显是不同的。 + +### 古怪的循环泛型 + +为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。 +不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明: ## 动态类型安全 diff --git a/docs/book/Appendix-Passing-and-Returning-Objects.md b/docs/book/Appendix-Passing-and-Returning-Objects.md index de6d712e..6f12f044 100644 --- a/docs/book/Appendix-Passing-and-Returning-Objects.md +++ b/docs/book/Appendix-Passing-and-Returning-Objects.md @@ -3,12 +3,40 @@ # 附录:对象传递和返回 +> 到现在为止,你已经对“传递”对象实际上是传递引用这一想法想法感到满意。 + +在许多编程语言中,你可以使用该语言的“常规”方式来传递对象,并且大多数情况下一切正常。 但是通常会出现这种情况,你必须做一些不平常的事情,突然事情变得更加复杂。 Java也不例外,当您传递对象并对其进行操作时,准确了解正在发生的事情很重要。 本附录提供了这种见解。 + +提出本附录问题的另一种方法是,如果你之前使用类似C++的编程语言,则是“ Java是否有指针?” Java中的每个对象标识符(除原语外)都是这些指针之一,但它们的用法是不仅受编译器的约束,而且受运行时系统的约束。 换一种说法,Java有指针,但没有指针算法。 这些就是我一直所说的“引用”,您可以将它们视为“安全指针”,与小学的安全剪刀不同-它们不敏锐,因此您不费吹灰之力就无法伤害自己,但是它们有时可能很乏味。 -## 传递引用 +## 传递引用 + +当你将引用传递给方法时,它仍指向同一对象。 一个简单的实验演示了这一点: + +```java +// references/PassReferences.java +public class PassReferences { +public static void f(PassReferences h) { + System.out.println("h inside f(): " + h); + } + public static void main(String[] args) { + PassReferences p = new PassReferences(); + System.out.println("p inside main(): " + p); + f(p); + } +} +/* Output: +p inside main(): PassReferences@15db9742 +h inside f(): PassReferences@15db9742 +*/ +``` + +方法 `toString() ` 在打印语句中自动调用,并且 `PassReferences` 直接从 `Object` 继承而无需重新定义 `toString()` 。 因此,使用的是 `Object` 的 `toString()` 版本,它打印出对象的类,然后打印出该对象所在的地址(不是引用,而是实际的对象存储)。 + ## 本地拷贝 From 8266eeb44a1cd6fd5d3514883a9f0df9dcb7ff0d Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Sun, 1 Dec 2019 11:37:00 +0800 Subject: [PATCH 112/371] =?UTF-8?q?=E7=BF=BB=E8=AF=9120=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E5=88=B0=E8=87=AA=E9=99=90=E5=AE=9A=E7=B1=BB=E5=9E=8B=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 329 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 703eda09..4319d63c 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3569,6 +3569,335 @@ class SelfBounded> { // ... 为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。 不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明: +```java +// generics/CuriouslyRecurringGeneric.java + +class GenericType {} + +public class CuriouslyRecurringGeneric + extends GenericType {} +``` + +这可以按照 Jim Coplien 在 C++ 中的*古怪的循环模版模式*的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。 +为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,甚至那些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类: + +```java +// generics/BasicHolder.java + +public class BasicHolder { + T element; + void set(T arg) { element = arg; } + T get() { return element; } + void f() { + System.out.println( + element.getClass().getSimpleName()); + } +} +``` + +这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法将在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。 +我们可以在一个古怪的循环泛型中使用 **BasicHolder**: + +```java +// generics/CRGWithBasicHolder.java + +class Subtype extends BasicHolder {} + +public class CRGWithBasicHolder { + public static void main(String[] args) { + Subtype + st1 = new Subtype(), + st2 = new Subtype(); + st1.set(st2); + Subtype st3 = st1.get(); + st1.f(); + } +} +/* Output: +Subtype +*/ +``` + +注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype** 。 + +### 自限定 + +BasicHolder可以使用任何类型作为其泛型参数,就像下面看到的那样: + +```java +// generics/Unconstrained.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. + +class Other {} +class BasicOther extends BasicHolder {} + +public class Unconstrained { + public static void main(String[] args) { + BasicOther b = new BasicOther(); + BasicOther b2 = new BasicOther(); + b.set(new Other()); + Other other = b.get(); + b.f(); + } +} +/* Output: +Other +*/ +``` + +限定将采取额外的步骤,强制泛型当作其自己的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用: + +```java +// generics/SelfBounding.java + +class SelfBounded> { + T element; + SelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } +} + +class A extends SelfBounded {} +class B extends SelfBounded {} // Also OK + +class C extends SelfBounded { + C setAndGet(C arg) { set(arg); return get(); } +} + +class D {} +// Can't do this: +// class E extends SelfBounded {} +// Compile error: +// Type parameter D is not within its bound + +// Alas, you can do this, so you cannot force the idiom: +class F extends SelfBounded {} + +public class SelfBounding { + public static void main(String[] args) { + A a = new A(); + a.set(new A()); + a = a.set(new A()).get(); + a = a.get(); + C c = new C(); + c = c.setAndGet(new C()); + } +} +``` + +自限定所做的,就是要求在继承关系中,像下面这样使用这个类: + +```java +class A extends SelfBounded{} +``` + +这会强制要求将正在定义的类当作参数传递给基类。 +自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在B类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的**SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。 +遗憾的是, **F** 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。 +注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 **E** 也会因此而变得可编译: + +```java +// generics/NotSelfBounded.java + +public class NotSelfBounded { + T element; + NotSelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } +} + +class A2 extends NotSelfBounded {} +class B2 extends NotSelfBounded {} + +class C2 extends NotSelfBounded { + C2 setAndGet(C2 arg) { set(arg); return get(); } +} + +class D2 {} +// Now this is OK: +class E2 extends NotSelfBounded {} +``` + +因此很明显,自限定限制只能强制作用于继承关系。如果使用自限定,就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基类型。这会强制要求使用这个类的每个人都要遵循这种形式。 +还可以将自限定用于泛型方法: + +```java +// generics/SelfBoundingMethods.java +// (c)2017 MindView LLC: see Copyright.txt +// We make no guarantees that this code is fit for any purpose. +// Visit http://OnJava8.com for more book information. + +public class SelfBoundingMethods { + static > T f(T arg) { + return arg.set(arg).get(); + } + public static void main(String[] args) { + A a = f(new A()); + } +} +``` + +这可以防止这个方法被应用于除上述形式的自限定参数之外的任何事物上。 + +### 参数协变 + +自限定类型的价值在于它们可以产生*协变参数类型*——方法参数类型会随子类而变化。 + +尽管自限定类型还可以产生于子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java SE5 中引入的: + +```java +// generics/CovariantReturnTypes.java + +class Base {} +class Derived extends Base {} + +interface OrdinaryGetter { + Base get(); +} + +interface DerivedGetter extends OrdinaryGetter { + // Overridden method return type can vary: + @Override + Derived get(); +} + +public class CovariantReturnTypes { + void test(DerivedGetter d) { + Derived d2 = d.get(); + } +} +``` + +**DerivedGetter** 中的 `get()` 方法覆盖了 **OrdinaryGetter** 中的 `get()` ,并返回了一个从 `OrdinaryGetter.get()` 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情(导出类方法应该能够返回比它覆盖的基类方法更具体的类型)但是这在早先的 Java 版本中是不合法的。 +自限定泛型事实上将产生确切的导出类型作为其返回值,就像在 `get()` 中所看到的一样: + +```java +// generics/GenericsAndReturnTypes.java + +interface GenericGetter> { + T get(); +} + +interface Getter extends GenericGetter {} + +public class GenericsAndReturnTypes { + void test(Getter g) { + Getter result = g.get(); + GenericGetter gg = g.get(); // Also the base type + } +} +``` + +注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java SE5 。 + +然而,在非泛型代码中,参数类型不能随子类型发生变化: + +```java +// generics/OrdinaryArguments.java + +class OrdinarySetter { + void set(Base base) { + System.out.println("OrdinarySetter.set(Base)"); + } +} + +class DerivedSetter extends OrdinarySetter { + void set(Derived derived) { + System.out.println("DerivedSetter.set(Derived)"); + } +} + +public class OrdinaryArguments { + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedSetter ds = new DerivedSetter(); + ds.set(derived); + // Compiles--overloaded, not overridden!: + ds.set(base); + } +} +/* Output: +DerivedSetter.set(Derived) +OrdinarySetter.set(Base) +*/ +``` + +`set(derived)` 和 `set(base)` 都是合法的,因此 `DerivedSetter.set()` 没有覆盖 `OrdinarySetter.set()` ,而是重载了这个方法。从输出中可以看到,在 **DerivedSetter** 中有两个方法,因此基类版本仍旧是可用的,因此可以证明它被重载过。 +但是,在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型为参数: + +```java +// generics/SelfBoundingAndCovariantArguments.java + +interface +SelfBoundSetter> { + void set(T arg); +} + +interface Setter extends SelfBoundSetter {} + +public class SelfBoundingAndCovariantArguments { + void + testA(Setter s1, Setter s2, SelfBoundSetter sbs) { + s1.set(s2); + //- s1.set(sbs); + // error: method set in interface SelfBoundSetter + // cannot be applied to given types; + // s1.set(sbs); + // ^ + // required: Setter + // found: SelfBoundSetter + // reason: argument mismatch; + // SelfBoundSetter cannot be converted to Setter + // where T is a type-variable: + // T extends SelfBoundSetter declared in + // interface SelfBoundSetter + // 1 error + } +} +``` + +编译器不能识别将基类型当作参数传递给 `set()` 的尝试,因为没有任何方法具有这样的签名。实际上,这个参数已经被覆盖。 +如果不使用自限定类型,普通的继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样: + +``` +// generics/PlainGenericInheritance.java + +class GenericSetter { // Not self-bounded + void set(T arg) { + System.out.println("GenericSetter.set(Base)"); + } +} + +class DerivedGS extends GenericSetter { + void set(Derived derived) { + System.out.println("DerivedGS.set(Derived)"); + } +} + +public class PlainGenericInheritance { + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedGS dgs = new DerivedGS(); + dgs.set(derived); + dgs.set(base); // Overloaded, not overridden! + } +} +/* Output: +DerivedGS.set(Derived) +GenericSetter.set(Base) +*/ +``` + +这段代码在模仿 **OrdinaryArgument.java** ,在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArgument.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得某个方法的一个版本,它将接受确切的参数类型。 + ## 动态类型安全 From 9a3fe121848d38552a08f1f60854352d985824ff Mon Sep 17 00:00:00 2001 From: Moilk Date: Sun, 1 Dec 2019 21:25:21 +0800 Subject: [PATCH 113/371] =?UTF-8?q?=E2=9C=93=20=E9=99=84=E5=BD=95=EF=BC=9A?= =?UTF-8?q?IO=20=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-IO-Streams.md | 315 ++++++++++++++++++++++++++++++- 1 file changed, 307 insertions(+), 8 deletions(-) diff --git a/docs/book/Appendix-IO-Streams.md b/docs/book/Appendix-IO-Streams.md index f17c3ddb..cf7b7cbd 100644 --- a/docs/book/Appendix-IO-Streams.md +++ b/docs/book/Appendix-IO-Streams.md @@ -195,47 +195,346 @@ Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简 最初,我们可能难以相信 `RandomAccessFile` 不是 `InputStream` 或者 `OutputStream` 继承体系中的一部分。除了实现了 `DataInput` 和 `DataOutput` 接口(`DataInputStream` 和 `DataOutputStream` 也实现了这两个接口)之外,它和这两个继承体系没有任何关系。它甚至都不使用 `InputStream` 和 `OutputStream` 类中已有的任何功能。它是一个完全独立的类,其所有的方法(大多数都是 `native` 方法)都是从头开始编写的。这么做是因为 `RandomAccessFile` 拥有和别的 I/O 类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接继承自 `Object`。 -从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream` 和 `DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度,另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。 +从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream` 和 `DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度。另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。 -在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件(mmap)**取代,详见[附录:新 I/O](./Appendix-New-IO.md)。 +在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件**(mmap)取代,详见[附录:新 I/O](./Appendix-New-IO.md)。 + ## IO流典型用途 +尽管我们可以用不同的方式来组合 I/O 流类,但常用的也就其中几种。你可以下面的例子可以作为 I/O 典型用法的基本参照(在你确定无法使用[文件](./17-Files.md)这一章所述的库之后)。 +在这些示例中,异常处理都被简化为将异常传递给控制台,但是这样做只适用于小型的示例和工具。在你自己的代码中,你需要考虑更加复杂的错误处理方式。 ### 缓冲输入文件 - +如果想要打开一个文件进行字符输入,我们可以使用一个 `FileInputReader` 对象,然后传入一个 `String` 或者 `File` 对象作为文件名。为了提高速度,我们希望对那个文件进行缓冲,那么我们可以将所产生的引用传递给一个 `BufferedReader` 构造器。`BufferedReader` 提供了 `line()` 方法,它会产生一个 `Stream` 对象: + +```java +// iostreams/BufferedInputFile.java +// {VisuallyInspectOutput} +import java.io.*; +import java.util.stream.*; + +public class BufferedInputFile { + public static String read(String filename) { + try (BufferedReader in = new BufferedReader( + new FileReader(filename))) { + return in.lines() + .collect(Collectors.joining("\n")); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + System.out.print( + read("BufferedInputFile.java")); + } +} +``` + +`Collectors.joining()` 在其内部使用了一个 `StringBuilder` 来累加其运行结果。该文件会通过 `try-with-resources` 子句自动关闭。 ### 从内存输入 +下面示例中,从 `BufferedInputFile.read()` 读入的 `String` 被用来创建一个 `StringReader` 对象。然后调用其 `read()` 方法,每次读取一个字符,并把它显示在控制台上: +```java +// iostreams/MemoryInput.java +// {VisuallyInspectOutput} +import java.io.*; -### 格式化内存输入 +public class MemoryInput { + public static void + main(String[] args) throws IOException { + StringReader in = new StringReader( + BufferedInputFile.read("MemoryInput.java")); + int c; + while ((c = in.read()) != -1) + System.out.print((char) c); + } +} +``` +注意 `read()` 是以 `int` 形式返回下一个字节,所以必须类型转换为 `char` 才能正确打印。 +### 格式化内存输入 -### 基本文件的输出 +要读取格式化数据,我们可以使用 `DataInputStream`,它是一个面向字节的 I/O 类(不是面向字符的)。这样我们就必须使用 `InputStream` 类而不是 `Reader` 类。我们可以使用 `InputStream` 以字节形式读取任何数据(比如一个文件),但这里使用的是字符串。 + +```java +// iostreams/FormattedMemoryInput.java +// {VisuallyInspectOutput} +import java.io.*; + +public class FormattedMemoryInput { + public static void main(String[] args) { + try ( + DataInputStream in = new DataInputStream( + new ByteArrayInputStream( + BufferedInputFile.read( + "FormattedMemoryInput.java") + .getBytes())) + ) { + while (true) + System.out.write((char) in.readByte()); + } catch (EOFException e) { + System.out.println("\nEnd of stream"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +`ByteArrayInputStream` 必须接收一个字节数组,所以这里我们调用了 `String.getBytes()` 方法。所产生的的 `ByteArrayInputStream` 是一个适合传递给 `DataInputStream` 的 `InputStream`。 + +如果我们用 `readByte()` 从 `DataInputStream` 一次一个字节地读取字符,那么任何字节的值都是合法结果,因此返回值不能用来检测输入是否结束。取而代之的是,我们可以使用 `available()` 方法得到剩余可用字符的数量。下面例子演示了怎么一次一个字节地读取文件: + +```java +// iostreams/TestEOF.java +// Testing for end of file +// {VisuallyInspectOutput} +import java.io.*; + +public class TestEOF { + public static void main(String[] args) { + try ( + DataInputStream in = new DataInputStream( + new BufferedInputStream( + new FileInputStream("TestEOF.java"))) + ) { + while (in.available() != 0) + System.out.write(in.readByte()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +注意,`available()` 的工作方式会随着所读取媒介类型的不同而有所差异,它的字面意思就是“在没有阻塞的情况下所能读取的字节数”。对于文件,能够读取的是整个文件;但是对于其它类型的“流”,可能就不是这样,所以要谨慎使用。 + +我们也可以通过捕获异常来检测输入的末尾。但是,用异常作为控制流是对异常的一种错误使用方式。 +### 基本文件的输出 +`FileWriter` 对象用于向文件写入数据。实际使用时,我们通常会用 `BufferedWriter` 将其包装起来以增加缓冲的功能(可以试试移除此包装来感受一下它对性能的影响——缓冲往往能显著地增加 I/O 操作的性能)。在本例中,为了提供格式化功能,它又被装饰成了 `PrintWriter`。按照这种方式创建的数据文件可作为普通文本文件来读取。 + +```java +// iostreams/BasicFileOutput.java +// {VisuallyInspectOutput} +import java.io.*; + +public class BasicFileOutput { + static String file = "BasicFileOutput.dat"; + + public static void main(String[] args) { + try ( + BufferedReader in = new BufferedReader( + new StringReader( + BufferedInputFile.read( + "BasicFileOutput.java"))); + PrintWriter out = new PrintWriter( + new BufferedWriter(new FileWriter(file))) + ) { + in.lines().forEach(out::println); + } catch (IOException e) { + throw new RuntimeException(e); + } + // Show the stored file: + System.out.println(BufferedInputFile.read(file)); + } +} +``` + +`try-with-resources` 语句会自动 flush 并关闭文件。 ### 文本文件输出快捷方式 - +Java 5 在 `PrintWriter` 中添加了一个辅助构造器,有了它,你在创建并写入文件时,就不必每次都手动执行一些装饰的工作。下面的代码使用这种快捷方式重写了 `BasicFileOutput.java`: + +```java +// iostreams/FileOutputShortcut.java +// {VisuallyInspectOutput} +import java.io.*; + +public class FileOutputShortcut { + static String file = "FileOutputShortcut.dat"; + + public static void main(String[] args) { + try ( + BufferedReader in = new BufferedReader( + new StringReader(BufferedInputFile.read( + "FileOutputShortcut.java"))); + // Here's the shortcut: + PrintWriter out = new PrintWriter(file) + ) { + in.lines().forEach(out::println); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println(BufferedInputFile.read(file)); + } +} +``` + +使用这种方式仍具备了缓冲的功能,只是现在不必自己手动添加缓冲了。但遗憾的是,其它常见的写入任务都没有快捷方式,因此典型的 I/O 流依旧涉及大量冗余的代码。本书[文件](./17-Files.md)一章中介绍的另一种方式,对此类任务进行了极大的简化。 ### 存储和恢复数据 - +`PrintWriter` 是用来对可读的数据进行格式化。但如果要输出可供另一个“流”恢复的数据,我们可以用 `DataOutputStream` 写入数据,然后用 `DataInputStream` 恢复数据。当然,这些流可能是任何形式,在下面的示例中使用的是一个文件,并且对读写都进行了缓冲。注意 `DataOutputStream` 和 `DataInputStream` 是面向字节的,因此要使用 `InputStream` 和 `OutputStream` 体系的类。 + +```java +// iostreams/StoringAndRecoveringData.java +import java.io.*; + +public class StoringAndRecoveringData { + public static void main(String[] args) { + try ( + DataOutputStream out = new DataOutputStream( + new BufferedOutputStream( + new FileOutputStream("Data.txt"))) + ) { + out.writeDouble(3.14159); + out.writeUTF("That was pi"); + out.writeDouble(1.41413); + out.writeUTF("Square root of 2"); + } catch (IOException e) { + throw new RuntimeException(e); + } + try ( + DataInputStream in = new DataInputStream( + new BufferedInputStream( + new FileInputStream("Data.txt"))) + ) { + System.out.println(in.readDouble()); + // Only readUTF() will recover the + // Java-UTF String properly: + System.out.println(in.readUTF()); + System.out.println(in.readDouble()); + System.out.println(in.readUTF()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +输出结果: + +``` +3.14159 +That was pi +1.41413 +Square root of 2 +``` + +如果我们使用 `DataOutputStream` 进行数据写入,那么 Java 就保证了即便读和写数据的平台多么不同,我们仍可以使用 `DataInputStream` 准确地读取数据。这一点很有价值,众所周知,人们曾把大量精力耗费在数据的平台相关性问题上。但现在,只要两个平台上都有 Java,就不会存在这样的问题[^3]。 + +当我们使用 `DastaOutputStream` 时,写字符串并且让 `DataInputStream` 能够恢复它的唯一可靠方式就是使用 UTF-8 编码,在这个示例中是用 `writeUTF()` 和 `readUTF()` 来实现的。UTF-8 是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。如果我们使用的只是 ASCII 或者几乎都是 ASCII 字符(只占 7 比特),那么就显得及其浪费空间和带宽,所以 UTF-8 将 ASCII 字符编码成一个字节的形式,而非 ASCII 字符则编码成两到三个字节的形式。另外,字符串的长度保存在 UTF-8 字符串的前两个字节中。但是,`writeUTF()` 和 `readUTF()` 使用的是一种适用于 Java 的 UTF-8 变体(JDK 文档中有这些方法的详尽描述),因此如果我们用一个非 Java 程序读取用 `writeUTF()` 所写的字符串时,必须编写一些特殊的代码才能正确读取。 + +有了 `writeUTF()` 和 `readUTF()`,我们就可以在 `DataOutputStream` 中把字符串和其它数据类型混合使用。因为字符串完全可以作为 Unicode 格式存储,并且可以很容易地使用 `DataInputStream` 来恢复它。 + +`writeDouble()` 将 `double` 类型的数字存储在流中,并用相应的 `readDouble()` 恢复它(对于其它的书类型,也有类似的方法用于读写)。但是为了保证所有的读方法都能够正常工作,我们必须知道流中数据项所在的确切位置,因为极有可能将保存的 `double` 数据作为一个简单的字节序列、`char` 或其它类型读入。因此,我们必须:要么为文件中的数据采用固定的格式;要么将额外的信息保存到文件中,通过解析额外信息来确定数据的存放位置。注意,对象序列化和 XML (二者都在[附录:对象序列化](Appendix-Object-Serialization.md)中介绍)是存储和读取复杂数据结构的更简单的方式。 ### 读写随机访问文件 - +使用 `RandomAccessFile` 就像是使用了一个 `DataInputStream` 和 `DataOutputStream` 的结合体(因为它实现了相同的接口:`DataInput` 和 `DataOutput`)。另外,我们还可以使用 `seek()` 方法移动文件指针并修改对应位置的值。 + +在使用 `RandomAccessFile` 时,你必须清楚文件的结构,否则没法正确使用它。`RandomAccessFile` 有一套专门的方法来读写基本数据类型的数据和 UTF-8 编码的字符串: + +```java +// iostreams/UsingRandomAccessFile.java +import java.io.*; + +public class UsingRandomAccessFile { + static String file = "rtest.dat"; + + public static void display() { + try ( + RandomAccessFile rf = + new RandomAccessFile(file, "r") + ) { + for (int i = 0; i < 7; i++) + System.out.println( + "Value " + i + ": " + rf.readDouble()); + System.out.println(rf.readUTF()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + try ( + RandomAccessFile rf = + new RandomAccessFile(file, "rw") + ) { + for (int i = 0; i < 7; i++) + rf.writeDouble(i * 1.414); + rf.writeUTF("The end of the file"); + rf.close(); + display(); + } catch (IOException e) { + throw new RuntimeException(e); + } + try ( + RandomAccessFile rf = + new RandomAccessFile(file, "rw") + ) { + rf.seek(5 * 8); + rf.writeDouble(47.0001); + rf.close(); + display(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} +``` + +输出结果: + +``` +Value 0: 0.0 +Value 1: 1.414 +Value 2: 2.828 +Value 3: 4.242 +Value 4: 5.656 +Value 5: 7.069999999999999 +Value 6: 8.484 +The end of the file +Value 0: 0.0 +Value 1: 1.414 +Value 2: 2.828 +Value 3: 4.242 +Value 4: 5.656 +Value 5: 47.0001 +Value 6: 8.484 +The end of the file +``` + +`display()` 方法打开了一个文件,并以 `double` 值的形式显示了其中的七个元素。在 `main()` 中,首先创建了文件,然后打开并修改了它。因为 `double` 总是 8 字节长,所以如果要用 `seek()` 定位到第 5 个(从 0 开始计数) `double` 值,则要传入的地址值应该为 `5*8`。 + +正如前面所诉,虽然 `RandomAccess` 实现了 `DataInput` 和 `DataOutput` 接口,但实际上它和 I/O 继承体系中的其它部分是分离的。它不支持装饰,故而不能将其与 `InputStream` 及 `OutputStream` 子类中的任何一个组合起来,所以我们也没法给它添加缓冲的功能。 + +该类的构造器还有第二个必选参数:我们可以指定让 `RandomAccessFile` 以“只读”(r)方式或“读写” +(rw)方式打开文件。 + +除此之外,还可以使用 `nio` 中的“内存映射文件”代替 `RandomAccessFile`,这在[附录:新 I/O](Appendix-New-IO.md)中有介绍。 ## 本章小结 +Java 的 I/O 流类库的确能够满足我们的基本需求:我们可以通过控制台、文件、内存块,甚至因特网进行读写。通过继承,我们可以创建新类型的输入和输出对象。并且我们甚至可以通过重新定义“流”所接受对象类型的 `toString()` 方法,进行简单的扩展。当我们向一个期望收到字符串的方法传送一个非字符串对象时,会自动调用对象的 `toString()` 方法(这是 Java 中有限的“自动类型转换”功能之一)。 + +在 I/O 流类库的文档和设计中,仍留有一些没有解决的问题。例如,我们打开一个文件用于输出,如果在我们试图覆盖这个文件时能抛出一个异常,这样会比较好(有的编程系统只有当该文件不存在时,才允许你将其作为输出文件打开)。在 Java 中,我们应该使用一个 `File` 对象来判断文件是否存在,因为如果我们用 `FileOutputStream` 或者 `FileWriter` 打开,那么这个文件肯定会被覆盖。 + +I/O 流类库让我们喜忧参半。它确实挺有用的,而且还具有可移植性。但是如果我们没有理解“装饰器”模式,那么这种设计就会显得不是很直观。所以,它的学习成本相对较高。而且它并不完善,比如说在过去,我不得不编写相当数量的代码去实现一个读取文本文件的工具——所幸的是,Java 7 中的 nio 消除了此类需求。 +一旦你理解了装饰器模式,并且开始在某些需要这种灵活性的场景中使用该类库,那么你就开始能从这种设计中受益了。到那时候,为此额外多写几行代码的开销应该不至于让人觉得太麻烦。但还是请务必检查一下,确保使用[文件](./17-Files.md)一章中的库和技术没法解决问题后,再考虑使用本章的 I/O 流库。 [^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。 From 2e16e4ec49d383b4fbb75e5de127e8c0e8c6185f Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 21:56:34 +0800 Subject: [PATCH 114/371] Update 06-Housekeeping.md --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index e1d1f0c1..8086aa2e 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -82,7 +82,7 @@ Tree t = new Tree(12); // 12-foot 树 构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,你看不到对 `initialize()` 方法的显式调用,而从概念上来看,`initialize()` 方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。 -构造器是一种特殊的方法,因为它没有返回值。这与返回 **void** 值的方法不同,在返回 **void** 值的方法中,方法返回空值,但是你还是有选择返回一些其他值。构造器返回空值,你没有选择(**new** 表达式的确返回了新创建对象的引用,但是构造器自身并没有返回值 )。假如有返回值,而且你可以自由选择,那么编译器得知道如何去处理这个返回值。 +构造器是一种特殊的方法,因为它没有返回值。这与返回 **void** 值的方法不同,在返回 **void** 值的方法中,方法返回空值,但是你还是有选择将该方法改造返回一些其他类型的值。构造器不返回任何值,并且你没有选择使其有返回值(**new** 表达式的确返回了新创建对象的引用,但是构造器自身并没有返回值 )。假如你可以自由选择有返回值,那么编译器得知道如何去处理这个返回值。 From c1b97a6a8ed7372a1218aeb73fca0f27e52ffd33 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 21:58:37 +0800 Subject: [PATCH 115/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9672bf0..6caf550d 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ - [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) - [ ] [附录:文档注释](docs/book/Appendix-Javadoc.md) - [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) -- [ ] [附录:流式IO](docs/book/Appendix-IO-Streams.md) +- [x] [附录:流式IO](docs/book/Appendix-IO-Streams.md) - [x] [附录:标准IO](docs/book/Appendix-Standard-IO.md) - [x] [附录:新IO](docs/book/Appendix-New-IO.md) - [x] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) From 968207979cdbd0c90edb3e0ff2db8cd14560c6f5 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 22:22:56 +0800 Subject: [PATCH 116/371] Update 06-Housekeeping.md --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 8086aa2e..b6451d97 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -82,7 +82,7 @@ Tree t = new Tree(12); // 12-foot 树 构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,你看不到对 `initialize()` 方法的显式调用,而从概念上来看,`initialize()` 方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。 -构造器是一种特殊的方法,因为它没有返回值。这与返回 **void** 值的方法不同,在返回 **void** 值的方法中,方法返回空值,但是你还是有选择将该方法改造返回一些其他类型的值。构造器不返回任何值,并且你没有选择使其有返回值(**new** 表达式的确返回了新创建对象的引用,但是构造器自身并没有返回值 )。假如你可以自由选择有返回值,那么编译器得知道如何去处理这个返回值。 +构造器是一种特殊的方法,因为它没有返回值。这与返回 **void** 值的方法不同,在返回 **void** 值的方法中,方法返回空值,但是你还是有选择将该方法改造返回一些其他类型的值。构造器不返回任何值,并且你无法使其拥有返回值(**new** 表达式的确返回了新创建对象的引用,但是构造器自身并没有返回值 )。假如你可以自由选择返回值,那么编译器需要知道如何去处理这个返回值。 From ddc4f01992670e087090c2956e8feac5ef3a64c1 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 22:23:10 +0800 Subject: [PATCH 117/371] Update 06-Housekeeping.md From b40a92ef57b5a6b69c719675d087a9f0b8cc6ec3 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 22:27:56 +0800 Subject: [PATCH 118/371] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=8B=BC?= =?UTF-8?q?=E5=86=99=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #328 --- docs/book/10-Interfaces.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 10441b5e..65e9e38f 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -1132,7 +1132,7 @@ public class Adventure { 类 **Hero** 结合了具体类 **ActionCharacter** 和接口 **CanFight**、**CanSwim** 和 **CanFly**。当通过这种方式结合具体类和接口时,需要将具体类放在前面,后面跟着接口(否则编译器会报错)。 -接口 **CanFight** 和类 **ActionCharacter** 中的 `flight()` 方法签名相同,而在类 Hero 中也没有提供 `fight()` 的定义。可以扩展一个接口,但是得到的是另一个接口。当想创建一个对象时,所有的定义必须首先都存在。类 **Hero** 中没有显式地提供 `fight()` 的定义,是由于该方法在类 **ActionCharacter** 中已经定义过,这样才使得创建 **Hero** 对象成为可能。 +接口 **CanFight** 和类 **ActionCharacter** 中的 `fight()` 方法签名相同,而在类 Hero 中也没有提供 `fight()` 的定义。可以扩展一个接口,但是得到的是另一个接口。当想创建一个对象时,所有的定义必须首先都存在。类 **Hero** 中没有显式地提供 `fight()` 的定义,是由于该方法在类 **ActionCharacter** 中已经定义过,这样才使得创建 **Hero** 对象成为可能。 在类 **Adventure** 中可以看到四个方法,它们把不同的接口和具体类作为参数。当创建一个 **Hero** 对象时,它可以被传入这些方法中的任意一个,意味着它可以依次向上转型为每个接口。Java 中这种接口的设计方式,使得程序员不需要付出特别的努力。 @@ -1217,7 +1217,7 @@ public class HorrorShow { ### 结合接口时的命名冲突 -当实现多个接口时可能会存在一个小陷阱。在前面的例子中,**CanFlight** 和 **ActionCharacter** 具有完全相同的 `flight()` 方法。完全相同的方法没有问题,但是如果它们的签名或返回类型不同会怎么样呢?这里有一个例子: +当实现多个接口时可能会存在一个小陷阱。在前面的例子中,**CanFight** 和 **ActionCharacter** 具有完全相同的 `fight()` 方法。完全相同的方法没有问题,但是如果它们的签名或返回类型不同会怎么样呢?这里有一个例子: ```java // interfaces/InterfaceCollision.java From 027efe76edb34a9f7e3e6676a224cf48269e855c Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 1 Dec 2019 23:21:55 +0800 Subject: [PATCH 119/371] Update 06-Housekeeping.md --- docs/book/06-Housekeeping.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index b6451d97..8d126fd2 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -82,7 +82,8 @@ Tree t = new Tree(12); // 12-foot 树 构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,你看不到对 `initialize()` 方法的显式调用,而从概念上来看,`initialize()` 方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。 -构造器是一种特殊的方法,因为它没有返回值。这与返回 **void** 值的方法不同,在返回 **void** 值的方法中,方法返回空值,但是你还是有选择将该方法改造返回一些其他类型的值。构造器不返回任何值,并且你无法使其拥有返回值(**new** 表达式的确返回了新创建对象的引用,但是构造器自身并没有返回值 )。假如你可以自由选择返回值,那么编译器需要知道如何去处理这个返回值。 +构造器没有返回值,它是一种特殊的方法。但它和返回类型为 `void` 的普通方法不同,普通方法可以返回空值,你还能选择让它返回别的类型;而构造器没有返回值,却同时也没有给你选择的余地(`new` 表达式虽然返回了刚创建的对象的引用,但构造器本身却没有返回任何值)。如果它有返回值,并且你也可以自己选择让它返回什么,那么编译器就还得知道接下来该怎么处理那个返回值(这个返回值没有接收者)。 + From 8e08794374b3b1c666bce540442c87c39a114e2f Mon Sep 17 00:00:00 2001 From: syzhou Date: Mon, 2 Dec 2019 16:48:24 +0800 Subject: [PATCH 120/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E8=8C=83=E5=9E=8B?= =?UTF-8?q?=E5=88=B0=E5=BC=82=E5=B8=B8=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 4319d63c..7a015696 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3902,7 +3902,47 @@ GenericSetter.set(Base) +因为可以向 Java SE5 之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器,Java SE5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种强况下的类型检查问题,它们是:静态方法`checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。 +受检查的容器在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)容器形成了对比,对于后者来说,当你将对象从容器中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的容器,就可以发现谁在试图插入不良对象。 +让我们用受检查的容器来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的: + +```java +// generics/CheckedList.java +// Using Collection.checkedList() +import typeinfo.pets.*; +import java.util.*; + +public class CheckedList { + @SuppressWarnings("unchecked") + static void oldStyleMethod(List probablyDogs) { + probablyDogs.add(new Cat()); + } + public static void main(String[] args) { + List dogs1 = new ArrayList<>(); + oldStyleMethod(dogs1); // Quietly accepts a Cat + List dogs2 = Collections.checkedList( + new ArrayList<>(), Dog.class); + try { + oldStyleMethod(dogs2); // Throws an exception + } catch(Exception e) { + System.out.println("Expected: " + e); + } + // Derived types work fine: + List pets = Collections.checkedList( + new ArrayList<>(), Pet.class); + pets.add(new Dog()); + pets.add(new Cat()); + } +} +/* Output: +Expected: java.lang.ClassCastException: Attempt to +insert class typeinfo.pets.Cat element into collection +with element type class typeinfo.pets.Dog +*/ +``` + +运行这个程序时,你会发现插入一个 **Cat** 对于 **dogs1** 来说没有任何问题,而 **dogs2** 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。 ## 泛型异常 From e846a602ae9dd03ef60975075ddcc294e6aa8b00 Mon Sep 17 00:00:00 2001 From: syzhou Date: Mon, 2 Dec 2019 17:15:37 +0800 Subject: [PATCH 121/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E8=8C=83=E5=9E=8B?= =?UTF-8?q?=E5=88=B0=E6=BD=9C=E5=9C=A8=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 374 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 372 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 7a015696..c86be638 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3946,12 +3946,382 @@ with element type class typeinfo.pets.Dog ## 泛型异常 - -## 混入 +由于擦除的原因,将泛型应用于异常是非常受限的。**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。 +但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常的类型而发生变化的泛型代码: + +```java +// generics/ThrowGenericException.java + +import java.util.*; + +interface Processor { + void process(List resultCollector) throws E; +} + +class ProcessRunner +extends ArrayList> { + List processAll() throws E { + List resultCollector = new ArrayList<>(); + for(Processor processor : this) + processor.process(resultCollector); + return resultCollector; + } +} + +class Failure1 extends Exception {} + +class Processor1 +implements Processor { + static int count = 3; + @Override + public void process(List resultCollector) + throws Failure1 { + if(count-- > 1) + resultCollector.add("Hep!"); + else + resultCollector.add("Ho!"); + if(count < 0) + throw new Failure1(); + } +} + +class Failure2 extends Exception {} + +class Processor2 +implements Processor { + static int count = 2; + @Override + public void process(List resultCollector) + throws Failure2 { + if(count-- == 0) + resultCollector.add(47); + else { + resultCollector.add(11); + } + if(count < 0) + throw new Failure2(); + } +} + +public class ThrowGenericException { + public static void main(String[] args) { + ProcessRunner runner = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner.add(new Processor1()); + try { + System.out.println(runner.processAll()); + } catch(Failure1 e) { + System.out.println(e); + } + + ProcessRunner runner2 = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner2.add(new Processor2()); + try { + System.out.println(runner2.processAll()); + } catch(Failure2 e) { + System.out.println(e); + } + } +} +/* Output: +[Hep!, Hep!, Ho!] +Failure2 +*/ +``` + +**Processor** 执行 `process()`,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它将执行所持有的每个 **Process** 对象,并返回 **resultCollector** 。 +如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。 + +## 混型 + +术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。 +混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向方面编程* (AOP) 的味道,而方面经常被建议用来解决混型问题。 + +### C++中的混型 + +在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易的创建混型,因为 C++ 能够记住其模版参数的类型。 +下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。 + +```cpp +// generics/Mixins.cpp + +#include +#include +#include +using namespace std; + +template class TimeStamped : public T { + long timeStamp; +public: + TimeStamped() { timeStamp = time(0); } + long getStamp() { return timeStamp; } +}; + +template class SerialNumbered : public T { + long serialNumber; + static long counter; +public: + SerialNumbered() { serialNumber = counter++; } + long getSerialNumber() { return serialNumber; } +}; + +// Define and initialize the static storage: +template long SerialNumbered::counter = 1; + +class Basic { + string value; +public: + void set(string val) { value = val; } + string get() { return value; } +}; + +int main() { + TimeStamped> mixin1, mixin2; + mixin1.set("test string 1"); + mixin2.set("test string 2"); + cout << mixin1.get() << " " << mixin1.getStamp() << + " " << mixin1.getSerialNumber() << endl; + cout << mixin2.get() << " " << mixin2.getStamp() << + " " << mixin2.getSerialNumber() << endl; +} +/* Output: +test string 1 1452987605 1 +test string 2 1452987605 2 +*/ +``` + +在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么地轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了: + +``` +TimeStamped> mixin1,mixin2; +``` + +遗憾的是,Java泛型不允许这样。擦除会忘记基类类型,因此 + +> 泛型类不能直接继承自一个泛型参数 + +这突显了许多我在Java语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您发现自己做不到。 + +### 与接口混合 + +一种更常见的推荐解决方案是使用接口来产生混型效果,就像下面这样: + +```java +// generics/Mixins.java + +import java.util.*; + +interface TimeStamped { long getStamp(); } + +class TimeStampedImp implements TimeStamped { + private final long timeStamp; + TimeStampedImp() { + timeStamp = new Date().getTime(); + } + @Override + public long getStamp() { return timeStamp; } +} + +interface SerialNumbered { long getSerialNumber(); } + +class SerialNumberedImp implements SerialNumbered { + private static long counter = 1; + private final long serialNumber = counter++; + @Override + public long getSerialNumber() { return serialNumber; } +} + +interface Basic { + void set(String val); + String get(); +} + +class BasicImp implements Basic { + private String value; + @Override + public void set(String val) { value = val; } + @Override + public String get() { return value; } +} + +class Mixin extends BasicImp +implements TimeStamped, SerialNumbered { + private TimeStamped timeStamp = new TimeStampedImp(); + private SerialNumbered serialNumber = + new SerialNumberedImp(); + @Override + public long getStamp() { + return timeStamp.getStamp(); + } + @Override + public long getSerialNumber() { + return serialNumber.getSerialNumber(); + } +} + +public class Mixins { + public static void main(String[] args) { + Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); + mixin1.set("test string 1"); + mixin2.set("test string 2"); + System.out.println(mixin1.get() + " " + + mixin1.getStamp() + " " + + mixin1.getSerialNumber()); + System.out.println(mixin2.get() + " " + + mixin2.getStamp() + " " + + mixin2.getSerialNumber()); + } +} +/* Output: +test string 1 1494331663026 1 +test string 2 1494331663027 2 +*/ +``` + +**Mixin** 类基本上是在使用*代理*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 + +### 使用装饰器模式 + +当你观察混型的使用方式时,就会发现混型概念好像与*装饰器*设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。 +装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的一—无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。 +装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。 +前面的示例可以被改写为使用装饰器: + +```java +// generics/decorator/Decoration.java + +// {java generics.decorator.Decoration} +package generics.decorator; +import java.util.*; + +class Basic { + private String value; + public void set(String val) { value = val; } + public String get() { return value; } +} + +class Decorator extends Basic { + protected Basic basic; + Decorator(Basic basic) { this.basic = basic; } + @Override + public void set(String val) { basic.set(val); } + @Override + public String get() { return basic.get(); } +} + +class TimeStamped extends Decorator { + private final long timeStamp; + TimeStamped(Basic basic) { + super(basic); + timeStamp = new Date().getTime(); + } + public long getStamp() { return timeStamp; } +} + +class SerialNumbered extends Decorator { + private static long counter = 1; + private final long serialNumber = counter++; + SerialNumbered(Basic basic) { super(basic); } + public long getSerialNumber() { return serialNumber; } +} + +public class Decoration { + public static void main(String[] args) { + TimeStamped t = new TimeStamped(new Basic()); + TimeStamped t2 = new TimeStamped( + new SerialNumbered(new Basic())); + //- t2.getSerialNumber(); // Not available + SerialNumbered s = new SerialNumbered(new Basic()); + SerialNumbered s2 = new SerialNumbered( + new TimeStamped(new Basic())); + //- s2.getStamp(); // Not available + } +} +``` + +产生自泛型的类包含所有感兴趣的方法,但是由使用装饰器所产生的对象类型是最后被装饰的类型。也就是说,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,而混型的类型是所有被混合到一起的类型。因此对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。 + +### 与动态代理混合 + +可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看类型信息(Type Information) 章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。 +由于动态代理的限制,每个被混入的类都必须是某个接口的实现: + +```java +// generics/DynamicProxyMixin.java + +import java.lang.reflect.*; +import java.util.*; +import onjava.*; +import static onjava.Tuple.*; + +class MixinProxy implements InvocationHandler { + Map delegatesByMethod; + @SuppressWarnings("unchecked") + MixinProxy(Tuple2>... pairs) { + delegatesByMethod = new HashMap<>(); + for(Tuple2> pair : pairs) { + for(Method method : pair.a2.getMethods()) { + String methodName = method.getName(); + // The first interface in the map + // implements the method. + if(!delegatesByMethod.containsKey(methodName)) + delegatesByMethod.put(methodName, pair.a1); + } + } + } + @Override + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { + String methodName = method.getName(); + Object delegate = delegatesByMethod.get(methodName); + return method.invoke(delegate, args); + } + @SuppressWarnings("unchecked") + public static Object newInstance(Tuple2... pairs) { + Class[] interfaces = new Class[pairs.length]; + for(int i = 0; i < pairs.length; i++) { + interfaces[i] = (Class)pairs[i].a2; + } + ClassLoader cl = + pairs[0].a1.getClass().getClassLoader(); + return Proxy.newProxyInstance( + cl, interfaces, new MixinProxy(pairs)); + } +} + +public class DynamicProxyMixin { + public static void main(String[] args) { + Object mixin = MixinProxy.newInstance( + tuple(new BasicImp(), Basic.class), + tuple(new TimeStampedImp(), TimeStamped.class), + tuple(new SerialNumberedImp(), + SerialNumbered.class)); + Basic b = (Basic)mixin; + TimeStamped t = (TimeStamped)mixin; + SerialNumbered s = (SerialNumbered)mixin; + b.set("Hello"); + System.out.println(b.get()); + System.out.println(t.getStamp()); + System.out.println(s.getSerialNumber()); + } +} +/* Output: +Hello +1494331653339 +1 +*/ +``` + +因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 +为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。 + ## 潜在类型 From 494690c14d9aa4f2f2e82f6300e6661b7a7fa0db Mon Sep 17 00:00:00 2001 From: syzhou Date: Mon, 2 Dec 2019 20:46:27 +0800 Subject: [PATCH 122/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E5=88=B0=E8=BE=85=E5=8A=A9=E6=BD=9C=E5=9C=A8=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 524 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 520 insertions(+), 4 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index c86be638..16df165d 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4322,19 +4322,535 @@ Hello -## 潜在类型 +## 潜在类型机制 + +在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。 +Java泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动包装机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。 + +还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之前的操作)时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。 +某些编程语言提供的一种解决方案称为潜在类型机制或结构化类型机制,而更古怪的术语称为鸭子类型机制,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。 +泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。 +潜在类型机制是一种代码组织和复用机制。有了它编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。 +两种支持潜在类型机制的语言实例是 Python (可以从www.Python.org免费下载)和 C++。Python是动态类型语言(事实上所有的类型检查都发生在运行时),而C++是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。 + +### pyhton 中的潜在类型 + +如果我们将上面的描述用 Python 来表示,如下所示: + +```python +# generics/DogsAndRobots.py + +class Dog: + def speak(self): + print("Arf!") + def sit(self): + print("Sitting") + def reproduce(self): + pass + +class Robot: + def speak(self): + print("Click!") + def sit(self): + print("Clank!") + def oilChange(self): + pass + +def perform(anything): + anything.speak() + anything.sit() + +a = Dog() +b = Robot() +perform(a) +perform(b) + +output = """ +Arf! +Sitting +Click! +Clank! +""" +``` + +Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许正则(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)`中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()`方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。 + +输出规定使用三重引号创建带有嵌入式换行符的字符串。 + +### C++ 中的潜在类型 + +我们可以用C++产生相同的效果: + +```cpp +// generics/DogsAndRobots.cpp + +#include +using namespace std; + +class Dog { +public: + void speak() { cout << "Arf!" << endl; } + void sit() { cout << "Sitting" << endl; } + void reproduce() {} +}; + +class Robot { +public: + void speak() { cout << "Click!" << endl; } + void sit() { cout << "Clank!" << endl; } + void oilChange() {} +}; + +template void perform(T anything) { + anything.speak(); + anything.sit(); +} + +int main() { + Dog d; + Robot r; + perform(d); + perform(r); +} +/* Output: +Arf! +Sitting +Click! +Clank! +*/ +``` + +在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。 +C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,而主要原因是因为 C++ 的模版名声欠佳)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 + +### Go 中的潜在类型 + +这是用Go语言编写的相同程序: + +```java +// generics/dogsandrobots.go + +package main +import "fmt" + +type Dog struct {} +func (this Dog) speak() { fmt.Printf("Arf!\n")} +func (this Dog) sit() { fmt.Printf("Sitting\n")} +func (this Dog) reproduce() {} + +type Robot struct {} +func (this Robot) speak() { fmt.Printf("Click!\n") } +func (this Robot) sit() { fmt.Printf("Clank!\n") } +func (this Robot) oilChange() {} + +func perform(speaker interface { speak(); sit() }) { + speaker.speak(); + speaker.sit(); +} + +func main() { + perform(Dog{}) + perform(Robot{}) +} +/* Output: +Arf! +Sitting +Click! +Clank! +*/ +``` + +Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是将其定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。 + +Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要的原因。 但是,Go 的组成很简单。 + +`perform()` 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 `speak()` 和 `sit()` 方法即可。 该接口在此处匿名定义,内联,如 `perform()` 的参数列表所示。 + +`main()` 证明 `perform()` 确实对其参数的确切类型无关紧要,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。 + +语法 **Dog {}** 和 **Robot {}** 创建匿名的 **Dog** 和 **Robot** 结构。 + +### java中的直接潜在类型 + +因为泛型是在这场竞赛的后期才添加到 Java 中的,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用删除来实现Java泛型的实现有时称为第二类泛型类型)例如,在 Java8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它: + +```java +// generics/Performs.java + +public interface Performs { + void speak(); + void sit(); +} +``` +```java +// generics/DogsAndRobots.java +// No (direct) latent typing in Java +import typeinfo.pets.*; + +class PerformingDog extends Dog implements Performs { + @Override + public void speak() { System.out.println("Woof!"); } + @Override + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} +} + +class Robot implements Performs { + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} +} + +class Communicate { + public static + void perform(T performer) { + performer.speak(); + performer.sit(); + } +} + +public class DogsAndRobots { + public static void main(String[] args) { + Communicate.perform(new PerformingDog()); + Communicate.perform(new Robot()); + } +} +/* Output: +Woof! +Sitting +Click! +Clank! +*/ +``` + +但是要注意,`perform()` 不需要使用泛型来工作,它可以被简单地指定为接受一个 **Performs** 对象: + +```java +// generics/SimpleDogsAndRobots.java +// Removing the generic; code still works + +class CommunicateSimply { + static void perform(Performs performer) { + performer.speak(); + performer.sit(); + } +} + +public class SimpleDogsAndRobots { + public static void main(String[] args) { + CommunicateSimply.perform(new PerformingDog()); + CommunicateSimply.perform(new Robot()); + } +} +/* Output: +Woof! +Sitting +Click! +Clank! +*/ +``` + +在本例中,泛型不是必需的,因为这些类已经被强制要求实现 **Performs** 接口。 -## 补偿不足 + +## 对缺乏潜在类型机制的补偿 + +尽管 Java 不支持潜在类型机制,但是这并不意味着有界泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 + +### 反射 + +可以使用的一种方式是反射,下面的 `perform()` 方法就是用了潜在类型机制: + +```java +// generics/LatentReflection.java +// Using reflection for latent typing +import java.lang.reflect.*; + +// Does not implement Performs: +class Mime { + public void walkAgainstTheWind() {} + public void sit() { + System.out.println("Pretending to sit"); + } + public void pushInvisibleWalls() {} + @Override + public String toString() { return "Mime"; } +} + +// Does not implement Performs: +class SmartDog { + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} +} + +class CommunicateReflectively { + public static void perform(Object speaker) { + Class spkr = speaker.getClass(); + try { + try { + Method speak = spkr.getMethod("speak"); + speak.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot speak"); + } + try { + Method sit = spkr.getMethod("sit"); + sit.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot sit"); + } + } catch(SecurityException | + IllegalAccessException | + IllegalArgumentException | + InvocationTargetException e) { + throw new RuntimeException(speaker.toString(), e); + } + } +} + +public class LatentReflection { + public static void main(String[] args) { + CommunicateReflectively.perform(new SmartDog()); + CommunicateReflectively.perform(new Robot()); + CommunicateReflectively.perform(new Mime()); + } +} +/* Output: +Woof! +Sitting +Click! +Clank! +Mime cannot speak +Pretending to sit +*/ +``` + +上例中,这些类完全是彼此分离的,没有任何公共基类(除了 **Object** )或接口。通过反射, `CommunicateReflectively.perform()` 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 **Mime** 只具有一个必需的方法这一事实,并能够部分实现其目标。 + +### 将一个方法应用于序列 + +反射提供了一些有趣的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? + +让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这是接口看起来并不适合的情况,因为你想要将任何方法应用于一个对象集合,而接口对于描述“任何方法”存在过多的限制。如何用Java来实现这个需求呢? + +最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的: + +```java +// generics/Apply.java + +import java.lang.reflect.*; +import java.util.*; + +public class Apply { + public static > + void apply(S seq, Method f, Object... args) { + try { + for(T t: seq) + f.invoke(t, args); + } catch(IllegalAccessException | + IllegalArgumentException | + InvocationTargetException e) { + // Failures are programmer errors + throw new RuntimeException(e); + } + } +} +``` + +在 **Apply.java** 中,异常被转换为 **RuntimeException** ,因为没有多少办法可以从这种异常中恢复——在这种情况下,它们实际上代表着程序员的错误。 + +为什么我们不只使用 Java 8 方法参考(稍后显示)而不是反射方法 **f** ? 注意,`invoke()` 和 `apply()` 的优点是它们可以接受任意数量的参数。 在某些情况下,灵活性可能至关重要。 + +为了测试 **Apply** ,我们首先创建一个 **Shape** 类: + +```java +// generics/Shape.java + +public class Shape { + private static long counter = 0; + private final long id = counter++; + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } + public void rotate() { + System.out.println(this + " rotate"); + } + public void resize(int newSize) { + System.out.println(this + " resize " + newSize); + } +} +``` + +被一个子类 **Square** 继承: + +```java +// generics/Square.java + +public class Square extends Shape {} +``` + +通过这些,我们可以测试 **Apply**: + +```java +// generics/ApplyTest.java + +import java.util.*; +import java.util.function.*; +import onjava.*; + +public class ApplyTest { + public static + void main(String[] args) throws Exception { + List shapes = + Suppliers.create(ArrayList::new, Shape::new, 3); + Apply.apply(shapes, + Shape.class.getMethod("rotate")); + Apply.apply(shapes, + Shape.class.getMethod("resize", int.class), 7); + + List squares = + Suppliers.create(ArrayList::new, Square::new, 3); + Apply.apply(squares, + Shape.class.getMethod("rotate")); + Apply.apply(squares, + Shape.class.getMethod("resize", int.class), 7); + + Apply.apply(new FilledList<>(Shape::new, 3), + Shape.class.getMethod("rotate")); + Apply.apply(new FilledList<>(Square::new, 3), + Shape.class.getMethod("rotate")); + + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 3); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 3); + Apply.apply(shapeQ, + Shape.class.getMethod("rotate")); + } +} +/* Output: +Shape 0 rotate +Shape 1 rotate +Shape 2 rotate +Shape 0 resize 7 +Shape 1 resize 7 +Shape 2 resize 7 +Square 3 rotate +Square 4 rotate +Square 5 rotate +Square 3 resize 7 +Square 4 resize 7 +Square 5 resize 7 +Shape 6 rotate +Shape 7 rotate +Shape 8 rotate +Square 9 rotate +Square 10 rotate +Square 11 rotate +Shape 12 rotate +Shape 13 rotate +Shape 14 rotate +Square 15 rotate +Square 16 rotate +Square 17 rotate +*/ +``` + +在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 容器类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的一例如,在 `main()` 中使用的下面定义的 **SimpleQueue** 类: + +```java +// generics/SimpleQueue.java + +// A different kind of Iterable collection +import java.util.*; + +public class SimpleQueue implements Iterable { + private LinkedList storage = new LinkedList<>(); + public void add(T t) { storage.offer(t); } + public T get() { return storage.poll(); } + @Override + public Iterator iterator() { + return storage.iterator(); + } +} +``` + +正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在Java的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 + +几乎可以肯定,您会首先使用 Java 8 功能方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: + +```java +// generics/ApplyFunctional.java + +import java.util.*; +import java.util.stream.*; +import java.util.function.*; +import onjava.*; + +public class ApplyFunctional { + public static void main(String[] args) { + Stream.of( + Stream.generate(Shape::new).limit(2), + Stream.generate(Square::new).limit(2)) + .flatMap(c -> c) // flatten into one stream + .peek(Shape::rotate) + .forEach(s -> s.resize(7)); + + new FilledList<>(Shape::new, 2) + .forEach(Shape::rotate); + new FilledList<>(Square::new, 2) + .forEach(Shape::rotate); + + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 2); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 2); + shapeQ.forEach(Shape::rotate); + } +} +/* Output: +Shape 0 rotate +Shape 0 resize 7 +Shape 1 rotate +Shape 1 resize 7 +Square 2 rotate +Square 2 resize 7 +Square 3 rotate +Square 3 resize 7 +Shape 4 rotate +Shape 5 rotate +Square 6 rotate +Square 7 rotate +Shape 8 rotate +Shape 9 rotate +Square 10 rotate +Square 11 rotate +*/ +``` + +<未完待续> + + + -## 辅助潜在类型 + +## Java8 中的辅助潜在类型 -## 泛型的优劣 +## 总结:转型真的如此之糟吗? + + + + From 23b03251df80ba474fd9573803544075d1d275e4 Mon Sep 17 00:00:00 2001 From: syzhou Date: Tue, 3 Dec 2019 18:29:11 +0800 Subject: [PATCH 123/371] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 16df165d..5f8ffe8f 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4040,7 +4040,7 @@ Failure2 ## 混型 术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。 -混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向方面编程* (AOP) 的味道,而方面经常被建议用来解决混型问题。 +混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而方面经常被建议用来解决混型问题。 ### C++中的混型 @@ -4782,7 +4782,7 @@ public class SimpleQueue implements Iterable { 正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在Java的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 -几乎可以肯定,您会首先使用 Java 8 功能方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: +几乎可以肯定,你会首先使用 Java 8 功能方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: ```java // generics/ApplyFunctional.java @@ -4846,7 +4846,7 @@ Square 11 rotate -## 总结:转型真的如此之糟吗? +## 总结:类型转换真的如此之糟吗? From c25d20a9cd4a57605bff657ff6cae1a4db0069d6 Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Fri, 6 Dec 2019 11:42:57 +0800 Subject: [PATCH 124/371] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=88=B0=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 189 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 5f8ffe8f..a2d8dc0c 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4834,18 +4834,203 @@ Square 11 rotate */ ``` -<未完待续> - +由于使用 Java 8,因此不需要 `Apply.apply()` 。 +我们首先生成两个 **Stream** : 一个是 **Shape** ,一个是 **Square** ,并将它们展平为单个流。 尽管 Java 缺少功能语言中经常出现的 `flatten()` ,但是我们可以使用 `flatMap(c-> c)` 产生相同的结果,后者使用身份映射将操作简化为“ **flatten** ”。 +我们使用 `peek()` 当做对 `rotate()` 的调用,因为 `peek()` 执行一个操作(此处是出于副作用),并在未更改的情况下传递对象。 +注意,使用 **FilledList** 和 **shapeQ** 调用 `forEach()` 比 `Apply.apply()` 代码整洁得多。 在代码简单性和可读性方面,结果比以前的方法好得多。 并且,现在也不可能从 `main()` 引发异常。 ## Java8 中的辅助潜在类型 +先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,该形式可以满足创建可在不相关类型上工作的单段代码的要求。 由于 Java 最初并不是设计用于执行此操作的,因此,正如现在可能期望的那样,其结果比其他语言要尴尬得多。 + +我没有在其他地方遇到过这种技术,因此我将其称为辅助潜在类型。 + +我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A** : + +```java +// generics/DogsAndRobotMethodReferences.java + +// "Assisted Latent Typing" +import typeinfo.pets.*; +import java.util.function.*; + +class PerformingDogA extends Dog { + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} +} + +class RobotA { + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} +} + +class CommunicateA { + public static

void perform(P performer, + Consumer

action1, Consumer

action2) { + action1.accept(performer); + action2.accept(performer); + } +} + +public class DogsAndRobotMethodReferences { + public static void main(String[] args) { + CommunicateA.perform(new PerformingDogA(), + PerformingDogA::speak, PerformingDogA::sit); + CommunicateA.perform(new RobotA(), + RobotA::speak, RobotA::sit); + CommunicateA.perform(new Mime(), + Mime::walkAgainstTheWind, + Mime::pushInvisibleWalls); + } +} +/* Output: +Woof! +Sitting +Click! +Clank! +*/ +``` + +**PerformingDogA** 和 **RobotA** 与 **DogsAndRobots.java** 中的相同,不同之处在于它们不继承通用接口 **Performs** ,因此它们没有通用性。 + +`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。 当您调用消费者的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于“函数式编程”一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。 + +之所以称其为“辅助”,是因为您必须显式地为 `perform()` 提供要使用的方法引用。 它不能只按名称调用方法。 + +尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 `CommunicateA.perform()` ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。 + +为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime** 。 + +使用**Suppliers**类的通用方法 + +通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的实用程序方法。 “通用化”这些操作很有意义: + +```java +// onjava/Suppliers.java + +// A utility to use with Suppliers +package onjava; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +public class Suppliers { + // Create a collection and fill it: + public static > C + create(Supplier factory, Supplier gen, int n) { + return Stream.generate(gen) + .limit(n) + .collect(factory, C::add, C::addAll); + } + // Fill an existing collection: + public static > + C fill(C coll, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(coll::add); + return coll; + } + // Use an unbound method reference to + // produce a more general method: + public static H fill(H holder, + BiConsumer adder, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(a -> adder.accept(holder, a)); + return holder; + } +} +``` + +`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。 + +前两种方法一般都受约束以与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数a调用对象 **holder** 上的未绑定方法 **holder**。 + +在一个稍作模拟的测试中对 **Suppliers** 实用程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** : + +```java +// generics/BankTeller.java + +// A very simple bank teller simulation +import java.util.*; +import onjava.*; + +class Customer { + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Customer " + id; + } +} + +class Teller { + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Teller " + id; + } +} + +class Bank { + private List tellers = + new ArrayList<>(); + public void put(BankTeller bt) { + tellers.add(bt); + } +} + +public class BankTeller { + public static void serve(Teller t, Customer c) { + System.out.println(t + " serves " + c); + } + public static void main(String[] args) { + // Demonstrate create(): + RandomList tellers = + Suppliers.create( + RandomList::new, Teller::new, 4); + // Demonstrate fill(): + List customers = Suppliers.fill( + new ArrayList<>(), Customer::new, 12); + customers.forEach(c -> + serve(tellers.select(), c)); + // Demonstrate assisted latent typing: + Bank bank = Suppliers.fill( + new Bank(), Bank::put, BankTeller::new, 3); + // Can also use second version of fill(): + List customers2 = Suppliers.fill( + new ArrayList<>(), + List::add, Customer::new, 12); + } +} +/* Output: +Teller 3 serves Customer 1 +Teller 2 serves Customer 2 +Teller 3 serves Customer 3 +Teller 1 serves Customer 4 +Teller 1 serves Customer 5 +Teller 3 serves Customer 6 +Teller 1 serves Customer 7 +Teller 2 serves Customer 8 +Teller 3 serves Customer 9 +Teller 3 serves Customer 10 +Teller 2 serves Customer 11 +Teller 4 serves Customer 12 +*/ +``` + +可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与新的和无关的类型 **Bank** 一起使用,还与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。 + ## 总结:类型转换真的如此之糟吗? From 186de68ae2c3d55a124b3c34224b21782c37a872 Mon Sep 17 00:00:00 2001 From: siyuanzhou Date: Fri, 6 Dec 2019 12:57:44 +0800 Subject: [PATCH 125/371] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index a2d8dc0c..d14c2ccd 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4908,7 +4908,7 @@ Clank! 为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime** 。 -使用**Suppliers**类的通用方法 +### 使用**Suppliers**类的通用方法 通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的实用程序方法。 “通用化”这些操作很有意义: @@ -5033,11 +5033,28 @@ Teller 4 serves Customer 12 ## 总结:类型转换真的如此之糟吗? +自从C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。 +这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用容器类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在集合章节和附件:集合主题章节中看到的各种类。在 Java SE 5 之前,当你将一个对象放置到容器中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从容器中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在集合章节的开头展示过)。如果没有 Java SE 5 的泛型版本的容器,你放到容器里的和从容器中取回的,都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。 +但是,泛型出现之前的 Java 并不会让你误用放入到容器中的对象。如果将一个 **Dog** 扔到 **Cat** 的容器中,并且试图将这个容器中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 容器中取回那个 **Dog** 引用,并试图将其转型为**Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。 +在本书以前的版本中,我曾经说过: +> 这不止是令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向容器中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了容器中,那么必须发现这个不良插入到底是在何处发生的。 +> +但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个示例,它是一个包含 **String** 对象的被称为 **files** 的列表在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的容器,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。 + +有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在第多线程编程章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗? +我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用代码这一能力所带来的副作用。 +因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解适配器设计模式的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。 + +还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 **Map** ,在特定的方法 `containsKey(Object key) `和 `get(Object key)` 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 **Object** ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 **map** 中,键的类型总是在编译期检查的。 +有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。 +某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语句将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。 + +## 进阶阅读 [^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。 [^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html) @@ -5045,7 +5062,6 @@ Teller 4 serves Customer 12 -

\ No newline at end of file From 367882a04a0a35378b9b9a45511f180db15c99c1 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Fri, 6 Dec 2019 17:57:53 +0800 Subject: [PATCH 126/371] =?UTF-8?q?=E6=B3=9B=E5=9E=8B=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6caf550d..fd1983c4 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) - [x] [第十九章 类型信息](docs/book/19-Type-Information.md) -- [ ] [第二十章 泛型](docs/book/20-Generics.md) +- [x] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) - [x] [第二十三章 注解](docs/book/23-Annotations.md) From a4d4491dc2f470f7762a92b3b48c9a42d15411fd Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 7 Dec 2019 16:00:33 +0800 Subject: [PATCH 127/371] =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-Javadoc.md | 161 +++++++++++++++++++++++++++++++++- 1 file changed, 157 insertions(+), 4 deletions(-) diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md index 02a5e059..56d62c40 100644 --- a/docs/book/Appendix-Javadoc.md +++ b/docs/book/Appendix-Javadoc.md @@ -26,7 +26,6 @@ Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被 有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子: ```java - // javadoc/Documentation1.java /** 一个类注释 */ public class Documentation1 { @@ -46,10 +45,164 @@ Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 要通过Javadoc处理前面的代码,命令是: -**javadoc Documentation1.java** +```cmd +javadoc Documentation1.java +``` + +这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览你的类。 + +## 内嵌 HTML + +Javadoc传递未修改的HTML代码,用以生成的HTML文档。这使你可以充分利用HTML。但是,这样做的主要目的是让你格式化代码,例如: + +```java +// javadoc/Documentation2.java +/**
+* System.out.println(new Date());
+* 
+*/ +public class Documentation2 {} +``` + +您你也可以像在其他任何Web文档中一样使用HTML来格式化说明中的文字: + +```java +// javadoc/Documentation3.java +/** You can even insert a list: +*
    +*
  1. Item one +*
  2. Item two +*
  3. Item three +*
+*/ +public class Documentation3 {} +``` + +请注意,在文档注释中,Javadoc删除了行首的星号以及前导空格。 Javadoc重新格式化了所有内容,使其符合标准文档的外观。不要将诸如 \或 \之类的标题用作嵌入式HTML,因为Javadoc会插入自己的标题,后插入的标题将对其生成的文档产生干扰。 + +所有类型的注释文档(类,字段和方法)都可以支持嵌入式HTML。 + +## 示例标签 + +以下是一些可用于代码文档的Javadoc标记。在尝试使用Javadoc进行任何认真的操作之前,请查阅JDK文档中的Javadoc参考,以了解使用Javadoc的所有不同方法。 + +### @see + +这个标签可以将其他的类连接到文档中,Javadoc 将使用 @see 标记超链接到其他文档中,形式为: + +```java +@see classname +@see fully-qualified-classname +@see fully-qualified-classname#method-name +``` + +每个都向生成的文档中添加超链接的“另请参阅”条目。 Javadoc 不会检查超链接的有效性。 + +### {@link package.class#member label} + +和 @see 非常相似,不同之处在于它可以内联使用,并使用标签作为超链接文本,而不是“另请参阅”。 + +### {@docRoot} + +生成文档根目录的相对路径。对于显式超链接到文档树中的页面很有用。 + +### {@inheritDoc} + +将文档从此类的最近基类继承到当前文档注释中。 + +### @version + +其形式为: + +```java +@version version-information +``` + +其中 version-information 是你认为适合包含的任何重要信息。当在Javadoc命令行上放置 -version 标志时,特别在生成的HTML文档中用于生成version信息。 + +### @author + +其形式为: + +``` +@author author-information +``` + + author-information 大概率是你的名字,但是一样可以包含你的 email 地址或者其他合适的信息。当在 Javadoc 命令行上放置 -author 标志的时候,在生成的HTML文档中特别注明了作者信息。 + +你可以对作者列表使用多个作者标签,但是必须连续放置它们。所有作者信息都集中在生成的HTML中的单个段落中。 + +### @since + +此标记指示此代码的版本开始使用特定功能。例如,它出现在HTML Java文档中,以指示功能首次出现的JDK版本。 + +### @param + +这将生成有关方法参数的文档: + +```java +@param parameter-name description +``` + +其中parameter-name是方法参数列表中的标识符,description 是可以在后续行中继续的文本。当遇到新的文档标签时,说明被视为完成。@param 标签的可以任意使用,大概每个参数一个。 + +### @return + +这记录了返回值: + +```java +@return description +``` + +其中description给出了返回值的含义。它可延续到后面的行内。 + +### @throws + +一个方法可以产生许多不同类型的异常,所有这些异常都需要描述。异常标记的形式为: + +```java +@throws fully-qualified-class-name description +``` + + fully-qualified-class-name 给出明确的异常分类名称,并且 description (可延续到后面的行内)告诉你为什么这特定类型的异常会在方法调用后出现。 + +### @deprecated + +这表示已被改进的功能取代的功能。deprecated 标记表明你不再使用此特定功能,因为将来有可能将其删除。标记为@不赞成使用的方法会导致编译器在使用时发出警告。在Java 5中,@deprecated Javadoc 标记已被 @Deprecated 注解取代(在[注解]()一章中进行了描述)。 + +## 文档示例 + +**objects/HelloDate.java** 是带有文档注释的例子。 + +```java +// javadoc/HelloDateDoc.java +import java.util.*; +/** The first On Java 8 example program. + * Displays a String and today's date. + * @author Bruce Eckel + * @author www.MindviewInc.com + * @version 5.0 + */ +public class HelloDateDoc { + /** Entry point to class & application. + * @param args array of String arguments + * @throws exceptions No exceptions thrown + */ + public static void main(String[] args) { + System.out.println("Hello, it's: "); + System.out.println(new Date()); + } +} +/* Output: +Hello, it's: +Tue May 09 06:07:27 MDT 2017 +*/ +``` + +你可以在Java标准库的源代码中找到许多Javadoc注释文档的示例。 + -这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览您的类。 -
\ No newline at end of file +
From f5db574ce4609a18b49839f275be570f0c651748 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 7 Dec 2019 16:05:51 +0800 Subject: [PATCH 128/371] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fd1983c4..6a247bf8 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ - [ ] [第二十五章 设计模式](docs/book/25-Patterns.md) - [x] [附录:补充](docs/book/Appendix-Supplements.md) - [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) -- [ ] [附录:文档注释](docs/book/Appendix-Javadoc.md) +- [x] [附录:文档注释](docs/book/Appendix-Javadoc.md) - [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) - [x] [附录:流式IO](docs/book/Appendix-IO-Streams.md) - [x] [附录:标准IO](docs/book/Appendix-Standard-IO.md) From 93578121b3501268d1f56bdf6eadd3311f7ff3aa Mon Sep 17 00:00:00 2001 From: xiangflight Date: Wed, 4 Dec 2019 10:06:29 +0800 Subject: [PATCH 129/371] =?UTF-8?q?revision[20]=20=E7=BB=93=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2930 ++++++++++++++++++++------------------ 1 file changed, 1525 insertions(+), 1405 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index d14c2ccd..2c089a3b 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -34,7 +34,7 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 ## 简单泛型 -促成泛型出现的最主要的动机之一是为了创建*集合类*,参见[集合](./12-Collections.md)章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 +促成泛型出现的最主要的动机之一是为了创建*集合类*,参见 [集合](book/12-Collections.md) 章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。 我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型: @@ -44,9 +44,9 @@ Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如 class Automobile {} public class Holder1 { - private Automobile a; - public Holder1(Automobile a) { this.a = a; } - Automobile get() { return a; } + private Automobile a; + public Holder1(Automobile a) { this.a = a; } + Automobile get() { return a; } } ``` @@ -58,18 +58,19 @@ public class Holder1 { // generics/ObjectHolder.java public class ObjectHolder { - private Object a; - public ObjectHolder(Object a) { this.a = a; } - public void set(Object a) { this.a = a; } - public Object get() { return a; } - public static void main(String[] args) { - ObjectHolder h2 = new ObjectHolder(new Automobile()); - Automobile a = (Automobile)h2.get(); - h2.set("Not an Automobile"); - String s = (String)h2.get(); - h2.set(1); // 自动装箱为 Integer - Integer x = (Integer)h2.get(); - } + private Object a; + public ObjectHolder(Object a) { this.a = a; } + public void set(Object a) { this.a = a; } + public Object get() { return a; } + + public static void main(String[] args) { + ObjectHolder h2 = new ObjectHolder(new Automobile()); + Automobile a = (Automobile)h2.get(); + h2.set("Not an Automobile"); + String s = (String)h2.get(); + h2.set(1); // 自动装箱为 Integer + Integer x = (Integer)h2.get(); + } } ``` @@ -83,17 +84,18 @@ public class ObjectHolder { // generics/GenericHolder.java public class GenericHolder { - private T a; - public GenericHolder() {} - public void set(T a) { this.a = a; } - public T get() { return a; } - public static void main(String[] args) { - GenericHolder h3 = new GenericHolder(); - h3.set(new Automobile()); // 此处有类型校验 - Automobile a = h3.get(); // 无需类型转换 - //- h3.set("Not an Automobile"); // 报错 - //- h3.set(1); // 报错 - } + private T a; + public GenericHolder() {} + public void set(T a) { this.a = a; } + public T get() { return a; } + + public static void main(String[] args) { + GenericHolder h3 = new GenericHolder(); + h3.set(new Automobile()); // 此处有类型校验 + Automobile a = h3.get(); // 无需类型转换 + //- h3.set("Not an Automobile"); // 报错 + //- h3.set(1); // 报错 + } } ``` @@ -109,10 +111,10 @@ public class GenericHolder { class Bob {} public class Diamond { - public static void main(String[] args) { - GenericHolder h3 = new GenericHolder<>(); - h3.set(new Bob()); - } + public static void main(String[] args) { + GenericHolder h3 = new GenericHolder<>(); + h3.set(new Bob()); + } } ``` @@ -133,14 +135,15 @@ public class Diamond { package onjava; public class Tuple2 { - public final A a1; - public final B a2; - public Tuple2(A a, B b) { a1 = a; a2 = b; } - public String rep() { return a1 + ", " + a2; } - @Override - public String toString() { - return "(" + rep() + ")"; - } + public final A a1; + public final B a2; + public Tuple2(A a, B b) { a1 = a; a2 = b; } + public String rep() { return a1 + ", " + a2; } + + @Override + public String toString() { + return "(" + rep() + ")"; + } } ``` @@ -157,49 +160,50 @@ public class Tuple2 { package onjava; public class Tuple3 extends Tuple2 { - public final C a3; - public Tuple3(A a, B b, C c) { - super(a, b); - a3 = c; - } - @Override - public String rep() { - return super.rep() + ", " + a3; - } + public final C a3; + public Tuple3(A a, B b, C c) { + super(a, b); + a3 = c; + } + + @Override + public String rep() { + return super.rep() + ", " + a3; + } } - // onjava/Tuple4.java package onjava; public class Tuple4 extends Tuple3 { - public final D a4; - public Tuple4(A a, B b, C c, D d) { - super(a, b, c); - a4 = d; - } - @Override - public String rep() { - return super.rep() + ", " + a4; - } + public final D a4; + public Tuple4(A a, B b, C c, D d) { + super(a, b, c); + a4 = d; + } + + @Override + public String rep() { + return super.rep() + ", " + a4; + } } - // onjava/Tuple5.java package onjava; public class Tuple5 extends Tuple4 { - public final E a5; - public Tuple5(A a, B b, C c, D d, E e) { - super(a, b, c, d); - a5 = e; - } - @Override - public String rep() { - return super.rep() + ", " + a5; - } + public final E a5; + public Tuple5(A a, B b, C c, D d, E e) { + super(a, b, c, d); + a5 = e; + } + + @Override + public String rep() { + return super.rep() + ", " + a5; + } } ``` @@ -220,31 +224,31 @@ public class Vehicle {} import onjava.*; public class TupleTest { - static Tuple2 f() { - // 47 自动装箱为 Integer - return new Tuple2<>("hi", 47); - } + static Tuple2 f() { + // 47 自动装箱为 Integer + return new Tuple2<>("hi", 47); + } - static Tuple3 g() { - return new Tuple3<>(new Amphibian(), "hi", 47); - } + static Tuple3 g() { + return new Tuple3<>(new Amphibian(), "hi", 47); + } - static Tuple4 h() { - return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47); - } + static Tuple4 h() { + return new Tuple4<>(new Vehicle(), new Amphibian(), "hi", 47); + } - static Tuple5 k() { - return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1); - } + static Tuple5 k() { + return new Tuple5<>(new Vehicle(), new Amphibian(), "hi", 47, 11.1); + } - public static void main(String[] args) { - Tuple2 ttsi = f(); - System.out.println(ttsi); - // ttsi.a1 = "there"; // 编译错误,因为 final 不能重新赋值 - System.out.println(g()); - System.out.println(h()); - System.out.println(k()); - } + public static void main(String[] args) { + Tuple2 ttsi = f(); + System.out.println(ttsi); + // ttsi.a1 = "there"; // 编译错误,因为 final 不能重新赋值 + System.out.println(g()); + System.out.println(h()); + System.out.println(k()); + } } /* 输出: @@ -263,7 +267,7 @@ public class TupleTest { ### 一个堆栈类 -接下来我们看一个稍微复杂一点的例子:堆栈。在[集合](./12-Collections.md)一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。 +接下来我们看一个稍微复杂一点的例子:堆栈。在 [集合](book/12-Collections.md) 一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。 这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。 @@ -272,45 +276,46 @@ public class TupleTest { // 用链式结构实现的堆栈 public class LinkedStack { - private static class Node { - U item; - Node next; + private static class Node { + U item; + Node next; - Node() { item = null; next = null; } - Node(U item, Node next) { - this.item = item; - this.next = next; - } + Node() { item = null; next = null; } + + Node(U item, Node next) { + this.item = item; + this.next = next; + } - boolean end() { - return item == null && next == null; + boolean end() { + return item == null && next == null; + } } - } - - private Node top = new Node<>(); // 栈顶 - public void push(T item) { - top = new Node<>(item, top); - } + private Node top = new Node<>(); // 栈顶 - public T pop() { - T result = top.item; - if (!top.end()) { - top = top.next; + public void push(T item) { + top = new Node<>(item, top); } - return result; - } - public static void main(String[] args) { - LinkedStack lss = new LinkedStack<>(); - for (String s : "Phasers on stun!".split(" ")) { - lss.push(s); + public T pop() { + T result = top.item; + if (!top.end()) { + top = top.next; + } + return result; } - String s; - while ((s = lss.pop()) != null) { - System.out.println(s); + + public static void main(String[] args) { + LinkedStack lss = new LinkedStack<>(); + for (String s : "Phasers on stun!".split(" ")) { + lss.push(s); + } + String s; + while ((s = lss.pop()) != null) { + System.out.println(s); + } } - } } ``` @@ -336,19 +341,18 @@ import java.util.*; import java.util.stream.*; public class RandomList extends ArrayList { - private Random rand = new Random(47); + private Random rand = new Random(47); - public T select() { - return get(rand.nextInt(size())); - } + public T select() { + return get(rand.nextInt(size())); + } - public static void main(String[] args) { - RandomList rs = new RandomList<>(); - Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); - IntStream.range(0, 11).forEach(i -> - System.out.print(rs.select() + " ")); - ); - } + public static void main(String[] args) { + RandomList rs = new RandomList<>(); + Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); + IntStream.range(0, 11).forEach(i -> + System.out.print(rs.select() + " ")); + } } ``` @@ -375,13 +379,13 @@ brown over fox quick quick dog brown The brown lazy brown package generics.coffee; public class Coffee { - private static long counter = 0; - private final long id = counter++; + private static long counter = 0; + private final long id = counter++; - @Override - public String toString() { - return getClass().getSimpleName() + " " + id; - } + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } } @@ -422,52 +426,52 @@ import java.util.stream.*; public class CoffeeSupplier implements Supplier, Iterable { - private Class[] types = { Latte.class, Mocha.class, - Cappuccino.class, Americano.class, Breve.class }; - private static Random rand = new Random(47); + private Class[] types = { Latte.class, Mocha.class, + Cappuccino.class, Americano.class, Breve.class }; + private static Random rand = new Random(47); - public CoffeeSupplier() {} - // For iteration: - private int size = 0; - public CoffeeSupplier(int sz) { size = sz; } + public CoffeeSupplier() {} + // For iteration: + private int size = 0; + public CoffeeSupplier(int sz) { size = sz; } - @Override - public Coffee get() { - try { - return (Coffee) types[rand.nextInt(types.length)].newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); + @Override + public Coffee get() { + try { + return (Coffee) types[rand.nextInt(types.length)].newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } } - } - class CoffeeIterator implements Iterator { - int count = size; - @Override - public boolean hasNext() { return count > 0; } - @Override - public Coffee next() { - count--; - return CoffeeSupplier.this.get(); + class CoffeeIterator implements Iterator { + int count = size; + @Override + public boolean hasNext() { return count > 0; } + @Override + public Coffee next() { + count--; + return CoffeeSupplier.this.get(); + } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } } + @Override - public void remove() { - throw new UnsupportedOperationException(); + public Iterator iterator() { + return new CoffeeIterator(); } - } - - @Override - public Iterator iterator() { - return new CoffeeIterator(); - } - public static void main(String[] args) { - Stream.generate(new CoffeeSupplier()) - .limit(5) - .forEach(System.out::println); - for (Coffee c : new CoffeeSupplier(5)) { - System.out.println(c); + public static void main(String[] args) { + Stream.generate(new CoffeeSupplier()) + .limit(5) + .forEach(System.out::println); + for (Coffee c : new CoffeeSupplier(5)) { + System.out.println(c); + } } - } } ``` @@ -497,21 +501,21 @@ import java.util.function.*; import java.util.stream.*; public class Fibonacci implements Supplier { - private int count = 0; - @Override - public Integer get() { return fib(count++); } + private int count = 0; + @Override + public Integer get() { return fib(count++); } - private int fib(int n) { - if(n < 2) return 1; - return fib(n-2) + fib(n-1); - } + private int fib(int n) { + if(n < 2) return 1; + return fib(n-2) + fib(n-1); + } - public static void main(String[] args) { - Stream.generate(new Fibonacci()) - .limit(18) - .map(n -> n + " ") - .forEach(System.out::print); - } + public static void main(String[] args) { + Stream.generate(new Fibonacci()) + .limit(18) + .map(n -> n + " ") + .forEach(System.out::print); + } } ``` @@ -534,30 +538,30 @@ import java.util.*; public class IterableFibonacci extends Fibonacci implements Iterable { - private int n; - public IterableFibonacci(int count) { n = count; } + private int n; + public IterableFibonacci(int count) { n = count; } - @Override - public Iterator iterator() { - return new Iterator() { - @Override - public boolean hasNext() { return n > 0; } - @Override - public Integer next() { - n--; - return IterableFibonacci.this.get(); - } - @Override - public void remove() { // Not implemented - throw new UnsupportedOperationException(); - } - }; - } + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { return n > 0; } + @Override + public Integer next() { + n--; + return IterableFibonacci.this.get(); + } + @Override + public void remove() { // Not implemented + throw new UnsupportedOperationException(); + } + }; + } - public static void main(String[] args) { - for(int i : new IterableFibonacci(18)) - System.out.print(i + " "); - } + public static void main(String[] args) { + for(int i : new IterableFibonacci(18)) + System.out.print(i + " "); + } } ``` @@ -898,7 +902,7 @@ public enum Watercolors { } ``` -为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](./22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: +为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素: ```java // generics/WatercolorSets.java @@ -1086,7 +1090,7 @@ Serializable] */ ``` -在[第十二章 集合](./12-Collections.md)的[本章小结](./12-Collections.md#本章小结)部分将会用到这里的输出结果。 +在第十二章 [集合的本章小节](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。 @@ -2571,7 +2575,7 @@ public class EpicBattle { ## 通配符 -你已经在 [集合](./12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](./19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。 +你已经在 [集合](book/12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](book/19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。 我们的起始示例要展示数组的一种特殊行为:你可以将派生类的数组赋值给基类的引用: @@ -2754,11 +2758,11 @@ false 但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。 -Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](./Appendix-Understanding-equals-and-hashCode) 一章。 +Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](book/Appendix-Understanding-equals-and-hashCode) 一章。 ### 逆变 -还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,甚至或者使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了: +还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,或者甚至使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了: ```java // generics/SuperTypeWildcards.java @@ -2772,123 +2776,135 @@ public class SuperTypeWildcards { } ``` -参数 **Apple** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是,既然 **Apple** 是下界,那么你可以知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。 -因此你可能会根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。 -超类型边界放松了在可以向方法传递的参数上所作的限制,此示例提供了逆变和通配符的概述:: +参数 **apples** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是因为 **Apple** 是下界,所以你知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。 +下面的示例复习了一下逆变和通配符的的使用: ```java // generics/GenericReading.java import java.util.*; public class GenericReading { - static List apples = - Arrays.asList(new Apple()); - static List fruit = Arrays.asList(new Fruit()); - static T readExact(List list) { - return list.get(0); - } - // A static method adapts to each call: - static void f1() { - Apple a = readExact(apples); - Fruit f = readExact(fruit); - f = readExact(apples); - } - // A class type is established - // when the class is instantiated: - static class Reader { - T readExact(List list) { return list.get(0); } - } - static void f2() { - Reader fruitReader = new Reader<>(); - Fruit f = fruitReader.readExact(fruit); - //- Fruit a = fruitReader.readExact(apples); - // error: incompatible types: List - // cannot be converted to List - } - static class CovariantReader { - T readCovariant(List list) { - return list.get(0); + static List apples = Arrays.asList(new Apple()); + static List fruit = Arrays.asList(new Fruit()); + + static T readExact(List list) { + return list.get(0); + } + + // A static method adapts to each call: + static void f1() { + Apple a = readExact(apples); + Fruit f = readExact(fruit); + f = readExact(apples); + } + + // A class type is established + // when the class is instantiated: + static class Reader { + T readExact(List list) { + return list.get(0); + } + } + + static void f2() { + Reader fruitReader = new Reader<>(); + Fruit f = fruitReader.readExact(fruit); + //- Fruit a = fruitReader.readExact(apples); + // error: incompatible types: List + // cannot be converted to List + } + + static class CovariantReader { + T readCovariant(List list) { + return list.get(0); + } + } + + static void f3() { + CovariantReader fruitReader = new CovariantReader<>(); + Fruit f = fruitReader.readCovariant(fruit); + Fruit a = fruitReader.readCovariant(apples); + } + + public static void main(String[] args) { + f1(); + f2(); + f3(); } - } - static void f3() { - CovariantReader fruitReader = - new CovariantReader<>(); - Fruit f = fruitReader.readCovariant(fruit); - Fruit a = fruitReader.readCovariant(apples); - } - public static void main(String[] args) { - f1(); f2(); f3(); - } } ``` -第一个方法 `readExact()` 使用了精确的类型。因此如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么当只是读取时,就不需要协变类型了。 -但是,如果有一个泛型类,那么当你创建这个类的实例时,要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 还应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。 -为了修正这个问题,`CovariantReader.readCovcariant()` 方法将接受 `List<?extendsT>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从T导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。 +`readExact()` 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么在读取时就不需要协变类型了。 +然而对于泛型类来说,当你创建这个类的实例时,就要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 也应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。 +为了修正这个问题,`CovariantReader.readCovariant()` 方法将接受 `List<?extends T>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从 T 导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。 ### 无界通配符 -无界通配符<?>看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的: +无界通配符 `` 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的: ```java // generics/UnboundedWildcards1.java import java.util.*; public class UnboundedWildcards1 { - static List list1; - static List list2; - static List list3; - static void assign1(List list) { - list1 = list; - list2 = list; - //- list3 = list; - // warning: [unchecked] unchecked conversion - // list3 = list; - // ^ - // required: List - // found: List - } - static void assign2(List list) { - list1 = list; - list2 = list; - list3 = list; - } - static void assign3(List list) { - list1 = list; - list2 = list; - list3 = list; - } - public static void main(String[] args) { - assign1(new ArrayList()); - assign2(new ArrayList()); - //- assign3(new ArrayList()); - // warning: [unchecked] unchecked method invocation: - // method assign3 in class UnboundedWildcards1 - // is applied to given types - // assign3(new ArrayList()); - // ^ - // required: List - // found: ArrayList - // warning: [unchecked] unchecked conversion - // assign3(new ArrayList()); - // ^ - // required: List - // found: ArrayList - // 2 warnings - assign1(new ArrayList<>()); - assign2(new ArrayList<>()); - assign3(new ArrayList<>()); - // Both forms are acceptable as List: - List wildList = new ArrayList(); - wildList = new ArrayList<>(); - assign1(wildList); - assign2(wildList); - assign3(wildList); - } -} -``` - -有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上,它是在声明:“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。” + static List list1; + static List list2; + static List list3; + + static void assign1(List list) { + list1 = list; + list2 = list; + //- list3 = list; + // warning: [unchecked] unchecked conversion + // list3 = list; + // ^ + // required: List + // found: List + } + + static void assign2(List list) { + list1 = list; + list2 = list; + list3 = list; + } + + static void assign3(List list) { + list1 = list; + list2 = list; + list3 = list; + } + + public static void main(String[] args) { + assign1(new ArrayList()); + assign2(new ArrayList()); + //- assign3(new ArrayList()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards1 + // is applied to given types + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // warning: [unchecked] unchecked conversion + // assign3(new ArrayList()); + // ^ + // required: List + // found: ArrayList + // 2 warnings + assign1(new ArrayList<>()); + assign2(new ArrayList<>()); + assign3(new ArrayList<>()); + // Both forms are acceptable as List: + List wildList = new ArrayList(); + wildList = new ArrayList<>(); + assign1(wildList); + assign2(wildList); + assign3(wildList); + } +} +``` + +有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上它是在声明:“我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。” 第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要: ```java @@ -2896,334 +2912,345 @@ public class UnboundedWildcards1 { import java.util.*; public class UnboundedWildcards2 { - static Map map1; - static Map map2; - static Map map3; - static void assign1(Map map) { map1 = map; } - static void assign2(Map map) { map2 = map; } - static void assign3(Map map) { map3 = map; } - public static void main(String[] args) { - assign1(new HashMap()); - assign2(new HashMap()); - //- assign3(new HashMap()); - // warning: [unchecked] unchecked method invocation: - // method assign3 in class UnboundedWildcards2 - // is applied to given types - // assign3(new HashMap()); - // ^ - // required: Map - // found: HashMap - // warning: [unchecked] unchecked conversion - // assign3(new HashMap()); - // ^ - // required: Map - // found: HashMap - // 2 warnings - assign1(new HashMap<>()); - assign2(new HashMap<>()); - assign3(new HashMap<>()); - } -} -``` - -但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards.java** 展示了编译器处理 `List` 和 `List` 时是不同的。 -令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。因为,事实上,由于泛型参数将擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List<?>` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道那种类型是什么。” -编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式: -作为原生类型,具有具体的类型参数以及具有无界通配符参数: + static Map map1; + static Map map2; + static Map map3; + + static void assign1(Map map) { + map1 = map; + } + + static void assign2(Map map) { + map2 = map; + } + + static void assign3(Map map) { + map3 = map; + } + + public static void main(String[] args) { + assign1(new HashMap()); + assign2(new HashMap()); + //- assign3(new HashMap()); + // warning: [unchecked] unchecked method invocation: + // method assign3 in class UnboundedWildcards2 + // is applied to given types + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // warning: [unchecked] unchecked conversion + // assign3(new HashMap()); + // ^ + // required: Map + // found: HashMap + // 2 warnings + assign1(new HashMap<>()); + assign2(new HashMap<>()); + assign3(new HashMap<>()); + } +} +``` + +但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards1.java** 展示了编译器处理 `List` 和 `List` 是不同的。 +令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。” +编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数: ```java // generics/Wildcards.java // Exploring the meaning of wildcards public class Wildcards { - // Raw argument: - static void rawArgs(Holder holder, Object arg) { - //- holder.set(arg); - // warning: [unchecked] unchecked call to set(T) - // as a member of the raw type Holder - // holder.set(arg); - // ^ - // where T is a type-variable: - // T extends Object declared in class Holder - // 1 warning - - // Can't do this; don't have any 'T': - // T t = holder.get(); - - // OK, but type information is lost: - Object obj = holder.get(); - } - // Like rawArgs(), but errors instead of warnings: - static void - unboundedArg(Holder holder, Object arg) { - //- holder.set(arg); - // error: method set in class Holder - // cannot be applied to given types; - // holder.set(arg); - // ^ - // required: CAP#1 - // found: Object - // reason: argument mismatch; - // Object cannot be converted to CAP#1 - // where T is a type-variable: - // T extends Object declared in class Holder - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error - - // Can't do this; don't have any 'T': - // T t = holder.get(); - - // OK, but type information is lost: - Object obj = holder.get(); - } - static T exact1(Holder holder) { - return holder.get(); - } - static T exact2(Holder holder, T arg) { - holder.set(arg); - return holder.get(); - } - static - T wildSubtype(Holder holder, T arg) { - //- holder.set(arg); - // error: method set in class Holder - // cannot be applied to given types; - // holder.set(arg); - // ^ - // required: CAP#1 - // found: T#1 - // reason: argument mismatch; - // T#1 cannot be converted to CAP#1 - // where T#1,T#2 are type-variables: - // T#1 extends Object declared in method - // wildSubtype(Holder,T#1) - // T#2 extends Object declared in class Holder - // where CAP#1 is a fresh type-variable: - // CAP#1 extends T#1 from - // capture of ? extends T#1 - // 1 error - - return holder.get(); - } - static - void wildSupertype(Holder holder, T arg) { - holder.set(arg); - //- T t = holder.get(); - // error: incompatible types: - // CAP#1 cannot be converted to T - // T t = holder.get(); - // ^ - // where T is a type-variable: - // T extends Object declared in method - // wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object super: - // T from capture of ? super T - // 1 error - - // OK, but type information is lost: - Object obj = holder.get(); - } - public static void main(String[] args) { - Holder raw = new Holder<>(); - // Or: - raw = new Holder(); - Holder qualified = new Holder<>(); - Holder unbounded = new Holder<>(); - Holder bounded = new Holder<>(); - Long lng = 1L; - - rawArgs(raw, lng); - rawArgs(qualified, lng); - rawArgs(unbounded, lng); - rawArgs(bounded, lng); - - unboundedArg(raw, lng); - unboundedArg(qualified, lng); - unboundedArg(unbounded, lng); - unboundedArg(bounded, lng); - - //- Object r1 = exact1(raw); - // warning: [unchecked] unchecked method invocation: - // method exact1 in class Wildcards is applied - // to given types - // Object r1 = exact1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact1(Holder) - // warning: [unchecked] unchecked conversion - // Object r1 = exact1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact1(Holder) - // 2 warnings - - Long r2 = exact1(qualified); - Object r3 = exact1(unbounded); // Must return Object - Long r4 = exact1(bounded); - - //- Long r5 = exact2(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method exact2 in class Wildcards is - // applied to given types - // Long r5 = exact2(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // warning: [unchecked] unchecked conversion - // Long r5 = exact2(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // 2 warnings - - Long r6 = exact2(qualified, lng); - - //- Long r7 = exact2(unbounded, lng); - // error: method exact2 in class Wildcards - // cannot be applied to given types; - // Long r7 = exact2(unbounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: inference variable T has - // incompatible bounds - // equality constraints: CAP#1 - // lower bounds: Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error - - //- Long r8 = exact2(bounded, lng); - // error: method exact2 in class Wildcards - // cannot be applied to given types; - // Long r8 = exact2(bounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: inference variable T - // has incompatible bounds - // equality constraints: CAP#1 - // lower bounds: Long - // where T is a type-variable: - // T extends Object declared in - // method exact2(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Long from - // capture of ? extends Long - // 1 error - - //- Long r9 = wildSubtype(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method wildSubtype in class Wildcards - // is applied to given types - // Long r9 = wildSubtype(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method wildSubtype(Holder,T) - // warning: [unchecked] unchecked conversion - // Long r9 = wildSubtype(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method wildSubtype(Holder,T) - // 2 warnings - - Long r10 = wildSubtype(qualified, lng); - // OK, but can only return Object: - Object r11 = wildSubtype(unbounded, lng); - Long r12 = wildSubtype(bounded, lng); - - //- wildSupertype(raw, lng); - // warning: [unchecked] unchecked method invocation: - // method wildSupertype in class Wildcards - // is applied to given types - // wildSupertype(raw, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // warning: [unchecked] unchecked conversion - // wildSupertype(raw, lng); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // 2 warnings - - wildSupertype(qualified, lng); - - //- wildSupertype(unbounded, lng); - // error: method wildSupertype in class Wildcards - // cannot be applied to given types; - // wildSupertype(unbounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: cannot infer type-variable(s) T - // (argument mismatch; Holder - // cannot be converted to Holder) - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Object from capture of ? - // 1 error - - //- wildSupertype(bounded, lng); - // error: method wildSupertype in class Wildcards - // cannot be applied to given types; - // wildSupertype(bounded, lng); - // ^ - // required: Holder,T - // found: Holder,Long - // reason: cannot infer type-variable(s) T - // (argument mismatch; Holder - // cannot be converted to Holder) - // where T is a type-variable: - // T extends Object declared in - // method wildSupertype(Holder,T) - // where CAP#1 is a fresh type-variable: - // CAP#1 extends Long from capture of - // ? extends Long - // 1 error - } -} -``` - - - -在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此,无论何时;只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object** 。 + // Raw argument: + static void rawArgs(Holder holder, Object arg) { + //- holder.set(arg); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // holder.set(arg); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning + + // Can't do this; don't have any 'T': + // T t = holder.get(); + + // OK, but type information is lost: + Object obj = holder.get(); + } + + // Like rawArgs(), but errors instead of warnings: + static void unboundedArg(Holder holder, Object arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: Object + // reason: argument mismatch; + // Object cannot be converted to CAP#1 + // where T is a type-variable: + // T extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + // Can't do this; don't have any 'T': + // T t = holder.get(); + + // OK, but type information is lost: + Object obj = holder.get(); + } + + static T exact1(Holder holder) { + return holder.get(); + } + + static T exact2(Holder holder, T arg) { + holder.set(arg); + return holder.get(); + } + + static T wildSubtype(Holder holder, T arg) { + //- holder.set(arg); + // error: method set in class Holder + // cannot be applied to given types; + // holder.set(arg); + // ^ + // required: CAP#1 + // found: T#1 + // reason: argument mismatch; + // T#1 cannot be converted to CAP#1 + // where T#1,T#2 are type-variables: + // T#1 extends Object declared in method + // wildSubtype(Holder,T#1) + // T#2 extends Object declared in class Holder + // where CAP#1 is a fresh type-variable: + // CAP#1 extends T#1 from + // capture of ? extends T#1 + // 1 error + return holder.get(); + } + + static void wildSupertype(Holder holder, T arg) { + holder.set(arg); + //- T t = holder.get(); + // error: incompatible types: + // CAP#1 cannot be converted to T + // T t = holder.get(); + // ^ + // where T is a type-variable: + // T extends Object declared in method + // wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object super: + // T from capture of ? super T + // 1 error + + // OK, but type information is lost: + Object obj = holder.get(); + } + + public static void main(String[] args) { + Holder raw = new Holder<>(); + // Or: + raw = new Holder(); + Holder qualified = new Holder<>(); + Holder unbounded = new Holder<>(); + Holder bounded = new Holder<>(); + Long lng = 1L; + + rawArgs(raw, lng); + rawArgs(qualified, lng); + rawArgs(unbounded, lng); + rawArgs(bounded, lng); + + unboundedArg(raw, lng); + unboundedArg(qualified, lng); + unboundedArg(unbounded, lng); + unboundedArg(bounded, lng); + + //- Object r1 = exact1(raw); + // warning: [unchecked] unchecked method invocation: + // method exact1 in class Wildcards is applied + // to given types + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // warning: [unchecked] unchecked conversion + // Object r1 = exact1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact1(Holder) + // 2 warnings + + Long r2 = exact1(qualified); + Object r3 = exact1(unbounded); // Must return Object + Long r4 = exact1(bounded); + + //- Long r5 = exact2(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method exact2 in class Wildcards is + // applied to given types + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r5 = exact2(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // 2 warnings + + Long r6 = exact2(qualified, lng); + + //- Long r7 = exact2(unbounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r7 = exact2(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T has + // incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + //- Long r8 = exact2(bounded, lng); + // error: method exact2 in class Wildcards + // cannot be applied to given types; + // Long r8 = exact2(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: inference variable T + // has incompatible bounds + // equality constraints: CAP#1 + // lower bounds: Long + // where T is a type-variable: + // T extends Object declared in + // method exact2(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from + // capture of ? extends Long + // 1 error + + //- Long r9 = wildSubtype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSubtype in class Wildcards + // is applied to given types + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // warning: [unchecked] unchecked conversion + // Long r9 = wildSubtype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSubtype(Holder,T) + // 2 warnings + + Long r10 = wildSubtype(qualified, lng); + // OK, but can only return Object: + Object r11 = wildSubtype(unbounded, lng); + Long r12 = wildSubtype(bounded, lng); + + //- wildSupertype(raw, lng); + // warning: [unchecked] unchecked method invocation: + // method wildSupertype in class Wildcards + // is applied to given types + // wildSupertype(raw, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // warning: [unchecked] unchecked conversion + // wildSupertype(raw, lng); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // 2 warnings + + wildSupertype(qualified, lng); + + //- wildSupertype(unbounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(unbounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Object from capture of ? + // 1 error + + //- wildSupertype(bounded, lng); + // error: method wildSupertype in class Wildcards + // cannot be applied to given types; + // wildSupertype(bounded, lng); + // ^ + // required: Holder,T + // found: Holder,Long + // reason: cannot infer type-variable(s) T + // (argument mismatch; Holder + // cannot be converted to Holder) + // where T is a type-variable: + // T extends Object declared in + // method wildSupertype(Holder,T) + // where CAP#1 is a fresh type-variable: + // CAP#1 extends Long from capture of + // ? extends Long + // 1 error + } +} +``` + +在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此无论何时,只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object**。 人们很自然地会开始考虑原生 `Holder` 与 `Holder` 是大致相同的事物。但是 `unboundedArg()` 强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生 **Holder** 将持有任何类型的组合,而 `Holder` 将持有具有某种具体类型的同构集合,因此不能只是向其中传递 **Object** 。 在 `exact1()` 和 `exact2()` 中,你可以看到使用了确切的泛型参数——没有任何通配符。你将看到,`exact2()`与 `exact1()` 具有不同的限制,因为它有额外的参数。 -在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果T是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。 -`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何T的基类型的容器。因此, `set()` 可以接受**T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。 -这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制。对于迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。 +在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果 T 是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。 +`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何 T 的基类型的容器。因此, `set()` 可以接受 **T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。 +这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制:因为你没有 **T**,所以你不能将 `set()` 或 `get()` 作用于 **T** 上。 + +在 `main()` 方法中你看到了某些方法在接受某些类型的参数时没有报错和警告。为了迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。 如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生 **Holder** 引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向 `exact1()` 传递一个无界引用,就不会有任何可以确定返回类型的类型信息。 可以看到,`exact2()` 具有最多的限制,因为它希望精确地得到一个 `Holder` ,以及一个具有类型 **T** 的参数,正由于此,它将产生错误或警告,除非提供确切的参数。有时,这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 `wildSubtype()` 中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在 `wildSupertype()` 中看到的那样)。 @@ -3231,61 +3258,61 @@ public class Wildcards { ### 捕获转换 -有一种情况特别需要使用 ``而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用: +有一种特殊情况需要使用 `` 而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用: ```java // generics/CaptureConversion.java public class CaptureConversion { - static void f1(Holder holder) { - T t = holder.get(); - System.out.println(t.getClass().getSimpleName()); - } - static void f2(Holder holder) { - f1(holder); // Call with captured type - } - @SuppressWarnings("unchecked") - public static void main(String[] args) { - Holder raw = new Holder<>(1); - - f1(raw); - // warning: [unchecked] unchecked method invocation: - // method f1 in class CaptureConversion - // is applied to given types - // f1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method f1(Holder) - // warning: [unchecked] unchecked conversion - // f1(raw); - // ^ - // required: Holder - // found: Holder - // where T is a type-variable: - // T extends Object declared in - // method f1(Holder) - // 2 warnings - - f2(raw); // No warnings - Holder rawBasic = new Holder(); - - rawBasic.set(new Object()); - // warning: [unchecked] unchecked call to set(T) - // as a member of the raw type Holder - // rawBasic.set(new Object()); - // ^ - // where T is a type-variable: - // T extends Object declared in class Holder - // 1 warning - - f2(rawBasic); // No warnings - // Upcast to Holder, still figures it out: - Holder wildcarded = new Holder<>(1.0); - f2(wildcarded); - } + static void f1(Holder holder) { + T t = holder.get(); + System.out.println(t.getClass().getSimpleName()); + } + + static void f2(Holder holder) { + f1(holder); // Call with captured type + } + + @SuppressWarnings("unchecked") + public static void main(String[] args) { + Holder raw = new Holder<>(1); + f1(raw); + // warning: [unchecked] unchecked method invocation: + // method f1 in class CaptureConversion + // is applied to given types + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // warning: [unchecked] unchecked conversion + // f1(raw); + // ^ + // required: Holder + // found: Holder + // where T is a type-variable: + // T extends Object declared in + // method f1(Holder) + // 2 warnings + f2(raw); // No warnings + + Holder rawBasic = new Holder(); + rawBasic.set(new Object()); + // warning: [unchecked] unchecked call to set(T) + // as a member of the raw type Holder + // rawBasic.set(new Object()); + // ^ + // where T is a type-variable: + // T extends Object declared in class Holder + // 1 warning + f2(rawBasic); // No warnings + + // Upcast to Holder, still figures it out: + Holder wildcarded = new Holder<>(1.0); + f2(wildcarded); + } } /* Output: Integer @@ -3295,17 +3322,19 @@ Double */ ``` -`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中,`f1()` 被调用,而 `f1()` 需要一个已知参数。这里所发生的是:参数类型在调用 `f2()` 的过程中被捕获,因此它可以在对 `f1()` 的调用中被使用。 -你可能想知道,这项技术是否可以用于写入,但是这要求要在传递 `Holder`时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()`中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 +`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中调用了 `f1()`,而 `f1()` 需要一个已知参数。这里所发生的是:在调用 `f2()` 的过程中捕获了参数类型,并在调用 `f1()` 时使用了这种类型。 +你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 + + ## 问题 -本节将阐述在使用Java泛型时会出现的各类问题。 +本节将阐述在使用 Java 泛型时会出现的各类问题。 ### 任何基本类型都不能作为类型参数 -正如本章早先提到过的,你将在Java泛型中发现的限制之一是,不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。 -解决之道是使用基本类型的包装器类以及JavaSE5的自动包装机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个容器,那么你将发现自动包装机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList`一样: +正如本章早先提到的,Java 泛型的限制之一是不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。 +解决方法是使用基本类型的包装器类以及自动装箱机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个集合,那么你将发现自动装箱机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList` 一样: ```java // generics/ListOfInt.java @@ -3315,39 +3344,37 @@ import java.util.*; import java.util.stream.*; public class ListOfInt { - public static void main(String[] args) { - List li = IntStream.range(38, 48) - .boxed() // Converts ints to Integers - .collect(Collectors.toList()); - System.out.println(li); - } + public static void main(String[] args) { + List li = IntStream.range(38, 48) + .boxed() // Converts ints to Integers + .collect(Collectors.toList()); + System.out.println(li); + } } /* Output: [38, 39, 40, 41, 42, 43, 44, 45, 46, 47] */ ``` -注意,自动包装机制甚至允许用foreach语法来产生 **int** 。 -通常,这种解决方案工作得很好——能够成功地存储和读取 **int** ,有一些转换碰巧在发生的同时会对你屏蔽掉。但是,如果性能成为了问题,就需要使用专门适配基本类型的容器版。**Org.apache.commons.collections.primitives** 就是一种开源的这类版本。 -下面是另外一种方式,它可以创建持有 **Byte** 的 **Set** : +通常,这种解决方案工作得很好——能够成功地存储和读取 **int**,自动装箱隐藏了转换的过程。但是如果性能成为问题的话,就需要使用专门为基本类型适配的特殊版本的集合;一个开源版本的实现是 **org.apache.commons.collections.primitives**。 +下面是另外一种方式,它可以创建持有 **Byte** 的 **Set**: ```java // generics/ByteSet.java import java.util.*; public class ByteSet { - Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; - Set mySet = - new HashSet<>(Arrays.asList(possibles)); - // But you can't do this: - // Set mySet2 = new HashSet<>( - // Arrays.asList(1,2,3,4,5,6,7,8,9)); + Byte[] possibles = { 1,2,3,4,5,6,7,8,9 }; + Set mySet = new HashSet<>(Arrays.asList(possibles)); + // But you can't do this: + // Set mySet2 = new HashSet<>( + // Arrays.asList(1,2,3,4,5,6,7,8,9)); } ``` -注意,自动包装机制解决了一些问题,但并不是解决了所有问题。 +自动装箱机制解决了一些问题,但并没有解决所有问题。 -在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的) **Supplier** 实现来自“数组”一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用它在数组中填充对象: +在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的)。**Supplier** 实现来自 [数组](book/21-Arrays.md) 一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用对象填充了数组: ```java // generics/PrimitiveGenericTest.java @@ -3357,33 +3384,36 @@ import java.util.function.*; // Fill an array using a generator: interface FillArray { - static T[] fill(T[] a, Supplier gen) { - Arrays.setAll(a, n -> gen.get()); - return a; - } - static int[] fill(int[] a, IntSupplier gen) { - Arrays.setAll(a, n -> gen.getAsInt()); - return a; - } - static long[] fill(long[] a, LongSupplier gen) { - Arrays.setAll(a, n -> gen.getAsLong()); - return a; - } - static double[] fill(double[] a, DoubleSupplier gen) { - Arrays.setAll(a, n -> gen.getAsDouble()); - return a; - } + static T[] fill(T[] a, Supplier gen) { + Arrays.setAll(a, n -> gen.get()); + return a; + } + + static int[] fill(int[] a, IntSupplier gen) { + Arrays.setAll(a, n -> gen.getAsInt()); + return a; + } + + static long[] fill(long[] a, LongSupplier gen) { + Arrays.setAll(a, n -> gen.getAsLong()); + return a; + } + + static double[] fill(double[] a, DoubleSupplier gen) { + Arrays.setAll(a, n -> gen.getAsDouble()); + return a; + } } public class PrimitiveGenericTest { - public static void main(String[] args) { - String[] strings = FillArray.fill( - new String[5], new Rand.String(9)); - System.out.println(Arrays.toString(strings)); - int[] integers = FillArray.fill( - new int[9], new Rand.Pint()); - System.out.println(Arrays.toString(integers)); - } + public static void main(String[] args) { + String[] strings = FillArray.fill( + new String[5], new Rand.String(9)); + System.out.println(Arrays.toString(strings)); + int[] integers = FillArray.fill( + new int[9], new Rand.Pint()); + System.out.println(Arrays.toString(integers)); + } } /* Output: [btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl] @@ -3391,7 +3421,7 @@ public class PrimitiveGenericTest { */ ``` -自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用,因为它返回填充的数组。 +自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用一点,因为它返回填充的数组。 ### 实现参数化接口 @@ -3406,17 +3436,16 @@ interface Payable {} class Employee implements Payable {} -class Hourly extends Employee -implements Payable {} +class Hourly extends Employee implements Payable {} ``` **Hourly** 不能编译,因为擦除会将 `Payable` 和 `Payable` 简化为相同的类 **Payable**,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 **Payable** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。 -在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后就会看到的那样。 +在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后看到的那样。 ### 转型和警告 -使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的容器在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**: +使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的集合在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**: ```java // generics/GenericCast.java @@ -3424,40 +3453,44 @@ import java.util.*; import java.util.stream.*; class FixedSizeStack { - private final int size; - private Object[] storage; - private int index = 0; - FixedSizeStack(int size) { - this.size = size; - storage = new Object[size]; - } - public void push(T item) { - if(index < size) - storage[index++] = item; - } - @SuppressWarnings("unchecked") - public T pop() { - return index == 0 ? null : (T)storage[--index]; - } - @SuppressWarnings("unchecked") - Stream stream() { - return (Stream)Arrays.stream(storage); - } + private final int size; + private Object[] storage; + private int index = 0; + + FixedSizeStack(int size) { + this.size = size; + storage = new Object[size]; + } + + public void push(T item) { + if(index < size) + storage[index++] = item; + } + + @SuppressWarnings("unchecked") + public T pop() { + return index == 0 ? null : (T)storage[--index]; + } + + @SuppressWarnings("unchecked") + Stream stream() { + return (Stream)Arrays.stream(storage); + } } public class GenericCast { - static String[] letters = - "ABCDEFGHIJKLMNOPQRS".split(""); - public static void main(String[] args) { - FixedSizeStack strings = - new FixedSizeStack<>(letters.length); - Arrays.stream("ABCDEFGHIJKLMNOPQRS".split("")) - .forEach(strings::push); - System.out.println(strings.pop()); - strings.stream() - .map(s -> s + " ") - .forEach(System.out::print); - } + static String[] letters = "ABCDEFGHIJKLMNOPQRS".split(""); + + public static void main(String[] args) { + FixedSizeStack strings = + new FixedSizeStack<>(letters.length); + Arrays.stream("ABCDEFGHIJKLMNOPQRS".split("")) + .forEach(strings::push); + System.out.println(strings.pop()); + strings.stream() + .map(s -> s + " ") + .forEach(System.out::print); + } } /* Output: S @@ -3466,7 +3499,7 @@ A B C D E F G H I J K L M N O P Q R S ``` 如果没有 **@SuppressWarnings** 注解,编译器将对 `pop()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop()` 方法实际上并没有执行任何转型。 -这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。 +这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。 有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如: ```java @@ -3475,16 +3508,16 @@ import java.io.*; import java.util.*; public class NeedCasting { - @SuppressWarnings("unchecked") - public void f(String[] args) throws Exception { - ObjectInputStream in = new ObjectInputStream( - new FileInputStream(args[0])); - List shapes = (List)in.readObject(); - } + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + List shapes = (List)in.readObject(); + } } ``` -正如你将在附件:对象序列化( Appendix: Object Serialization)中学到的那样,`readObject() `无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解,并编译这个程序时,就会得到下面的警告。 +正如你将在 [附录:对象序列化](book/Appendix-Object-Serialization.md) 中学到的那样,`readObject()` 无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解并编译这个程序时,就会得到下面的警告。 ``` NeedCasting.java uses unchecked or unsafe operations. @@ -3500,7 +3533,7 @@ NeedCasting.java:10: warning: [unchecked] unchecked cast 1 warning ``` -你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用在 Java SE5 中引入的新的转型形式,既通过泛型类来转型: +你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,既通过泛型类来转型: ```java // generics/ClassCasting.java @@ -3508,15 +3541,15 @@ import java.io.*; import java.util.*; public class ClassCasting { - @SuppressWarnings("unchecked") - public void f(String[] args) throws Exception { - ObjectInputStream in = new ObjectInputStream( - new FileInputStream(args[0])); - // Won't Compile: -// List lw1 = -// List<>.class.cast(in.readObject()); - List lw2 = List.class.cast(in.readObject()); - } + @SuppressWarnings("unchecked") + public void f(String[] args) throws Exception { + ObjectInputStream in = new ObjectInputStream( + new FileInputStream(args[0])); + // Won't Compile: + // List lw1 = + // List<>.class.cast(in.readObject()); + List lw2 = List.class.cast(in.readObject()); + } } ``` @@ -3536,7 +3569,7 @@ List.class.cast(in.readobject()) ### 重载 -下面的程序是不能编译的,即使编译它是一种合理的尝试: +下面的程序是不能编译的,即使它看起来是合理的: ```java // generics/UseList.java @@ -3544,29 +3577,101 @@ List.class.cast(in.readobject()) import java.util.*; public class UseList { - void f(List v) {} - void f(List v) {} + void f(List v) {} + void f(List v) {} } ``` - +因为擦除,所以重载方法产生了的类型签名。 -## 自限定的类型 +因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名: - +```java +// generics/UseList2.java + +import java.util.*; + +public class UseList2 { + void f1(List v) {} + void f2(List v) {} +} +``` + +幸运的是,编译器可以检测到这类问题。 + +### 基类劫持接口 + +假设你有一个实现了 **Comparable** 接口的 **Pet** 类: + +```java +// generics/ComparablePet.java + +public class ComparablePet implements Comparable { + @Override + public int compareTo(ComparablePet o) { + return 0; + } +} +``` + +尝试缩小 **ComparablePet** 子类的比较类型是有意义的。例如,**Cat** 类可以与其他的 **Cat** 比较: -在Java泛型中,有一个好像是经常性出现的惯用法,它相当令人费解: +```java +// generics/HijackedInterface.java +// {WillNotCompile} +class Cat extends ComparablePet implements Comparable { + // error: Comparable cannot be inherited with + // different arguments: and + // class Cat + // ^ + // 1 error + public int compareTo(Cat arg) { + return 0; + } +} +``` + +不幸的是,这不能工作。一旦 **Comparable** 的类型参数设置为 **ComparablePet**,其他的实现类只能比较 **ComparablePet**: + +```java +// generics/RestrictedComparablePets.java + +public class Hamster extends ComparablePet implements Comparable { + + @Override + public int compareTo(ComparablePet arg) { + return 0; + } +} +// Or just: +class Gecko extends ComparablePet { + public int compareTo(ComparablePet arg) { + return 0; + } +} ``` + +**Hamster** 显示了重新实现 **ComparableSet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。 + + + +## 自限定的类型 + +在 Java 泛型中,有一个似乎经常性出现的惯用法,它相当令人费解: + +```java class SelfBounded> { // ... ``` -这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而T由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 +这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而 **T** 由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。 + 当你首次看到它时,很难去解析它,它强调的是当 **extends** 关键字用于边界与用来创建子类明显是不同的。 ### 古怪的循环泛型 为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。 + 不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明: ```java @@ -3579,23 +3684,22 @@ public class CuriouslyRecurringGeneric ``` 这可以按照 Jim Coplien 在 C++ 中的*古怪的循环模版模式*的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。 -为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,甚至那些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类: +为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java 中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,尽管这些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类: ```java // generics/BasicHolder.java public class BasicHolder { - T element; - void set(T arg) { element = arg; } - T get() { return element; } - void f() { - System.out.println( - element.getClass().getSimpleName()); - } + T element; + void set(T arg) { element = arg; } + T get() { return element; } + void f() { + System.out.println(element.getClass().getSimpleName()); + } } ``` -这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法将在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。 +这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。 我们可以在一个古怪的循环泛型中使用 **BasicHolder**: ```java @@ -3604,25 +3708,23 @@ public class BasicHolder { class Subtype extends BasicHolder {} public class CRGWithBasicHolder { - public static void main(String[] args) { - Subtype - st1 = new Subtype(), - st2 = new Subtype(); - st1.set(st2); - Subtype st3 = st1.get(); - st1.f(); - } + public static void main(String[] args) { + Subtype st1 = new Subtype(), st2 = new Subtype(); + st1.set(st2); + Subtype st3 = st1.get(); + st1.f(); + } } /* Output: Subtype */ ``` -注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype** 。 +注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype**。 ### 自限定 -BasicHolder可以使用任何类型作为其泛型参数,就像下面看到的那样: +**BasicHolder** 可以使用任何类型作为其泛型参数,就像下面看到的那样: ```java // generics/Unconstrained.java @@ -3634,38 +3736,41 @@ class Other {} class BasicOther extends BasicHolder {} public class Unconstrained { - public static void main(String[] args) { - BasicOther b = new BasicOther(); - BasicOther b2 = new BasicOther(); - b.set(new Other()); - Other other = b.get(); - b.f(); - } + public static void main(String[] args) { + BasicOther b = new BasicOther(); + BasicOther b2 = new BasicOther(); + b.set(new Other()); + Other other = b.get(); + b.f(); + } } /* Output: Other */ ``` -限定将采取额外的步骤,强制泛型当作其自己的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用: +限定将采取额外的步骤,强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用: ```java // generics/SelfBounding.java class SelfBounded> { - T element; - SelfBounded set(T arg) { - element = arg; - return this; - } - T get() { return element; } + T element; + SelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } } class A extends SelfBounded {} class B extends SelfBounded {} // Also OK class C extends SelfBounded { - C setAndGet(C arg) { set(arg); return get(); } + C setAndGet(C arg) { + set(arg); + return get(); + } } class D {} @@ -3678,14 +3783,14 @@ class D {} class F extends SelfBounded {} public class SelfBounding { - public static void main(String[] args) { - A a = new A(); - a.set(new A()); - a = a.set(new A()).get(); - a = a.get(); - C c = new C(); - c = c.setAndGet(new C()); - } + public static void main(String[] args) { + A a = new A(); + a.set(new A()); + a = a.set(new A()).get(); + a = a.get(); + C c = new C(); + c = c.setAndGet(new C()); + } } ``` @@ -3696,7 +3801,8 @@ class A extends SelfBounded{} ``` 这会强制要求将正在定义的类当作参数传递给基类。 -自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在B类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的**SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。 + +自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的 **SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。 遗憾的是, **F** 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。 注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 **E** 也会因此而变得可编译: @@ -3704,19 +3810,22 @@ class A extends SelfBounded{} // generics/NotSelfBounded.java public class NotSelfBounded { - T element; - NotSelfBounded set(T arg) { - element = arg; - return this; - } - T get() { return element; } -} + T element; + NotSelfBounded set(T arg) { + element = arg; + return this; + } + T get() { return element; } +} class A2 extends NotSelfBounded {} class B2 extends NotSelfBounded {} class C2 extends NotSelfBounded { - C2 setAndGet(C2 arg) { set(arg); return get(); } + C2 setAndGet(C2 arg) { + set(arg); + return get(); + } } class D2 {} @@ -3734,12 +3843,13 @@ class E2 extends NotSelfBounded {} // Visit http://OnJava8.com for more book information. public class SelfBoundingMethods { - static > T f(T arg) { - return arg.set(arg).get(); - } - public static void main(String[] args) { - A a = f(new A()); - } + static > T f(T arg) { + return arg.set(arg).get(); + } + + public static void main(String[] args) { + A a = f(new A()); + } } ``` @@ -3749,7 +3859,7 @@ public class SelfBoundingMethods { 自限定类型的价值在于它们可以产生*协变参数类型*——方法参数类型会随子类而变化。 -尽管自限定类型还可以产生于子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java SE5 中引入的: +尽管自限定类型还可以产生与子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java 5 引入: ```java // generics/CovariantReturnTypes.java @@ -3758,43 +3868,44 @@ class Base {} class Derived extends Base {} interface OrdinaryGetter { - Base get(); + Base get(); } interface DerivedGetter extends OrdinaryGetter { - // Overridden method return type can vary: - @Override - Derived get(); + // Overridden method return type can vary: + @Override + Derived get(); } public class CovariantReturnTypes { - void test(DerivedGetter d) { - Derived d2 = d.get(); - } + void test(DerivedGetter d) { + Derived d2 = d.get(); + } } ``` **DerivedGetter** 中的 `get()` 方法覆盖了 **OrdinaryGetter** 中的 `get()` ,并返回了一个从 `OrdinaryGetter.get()` 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情(导出类方法应该能够返回比它覆盖的基类方法更具体的类型)但是这在早先的 Java 版本中是不合法的。 + 自限定泛型事实上将产生确切的导出类型作为其返回值,就像在 `get()` 中所看到的一样: ```java // generics/GenericsAndReturnTypes.java interface GenericGetter> { - T get(); + T get(); } interface Getter extends GenericGetter {} public class GenericsAndReturnTypes { - void test(Getter g) { - Getter result = g.get(); - GenericGetter gg = g.get(); // Also the base type - } + void test(Getter g) { + Getter result = g.get(); + GenericGetter gg = g.get(); // Also the base type + } } ``` -注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java SE5 。 +注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java 5。 然而,在非泛型代码中,参数类型不能随子类型发生变化: @@ -3802,26 +3913,26 @@ public class GenericsAndReturnTypes { // generics/OrdinaryArguments.java class OrdinarySetter { - void set(Base base) { - System.out.println("OrdinarySetter.set(Base)"); - } + void set(Base base) { + System.out.println("OrdinarySetter.set(Base)"); + } } class DerivedSetter extends OrdinarySetter { - void set(Derived derived) { - System.out.println("DerivedSetter.set(Derived)"); - } + void set(Derived derived) { + System.out.println("DerivedSetter.set(Derived)"); + } } public class OrdinaryArguments { - public static void main(String[] args) { - Base base = new Base(); - Derived derived = new Derived(); - DerivedSetter ds = new DerivedSetter(); - ds.set(derived); - // Compiles--overloaded, not overridden!: - ds.set(base); - } + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedSetter ds = new DerivedSetter(); + ds.set(derived); + // Compiles--overloaded, not overridden!: + ds.set(base); + } } /* Output: DerivedSetter.set(Derived) @@ -3835,60 +3946,59 @@ OrdinarySetter.set(Base) ```java // generics/SelfBoundingAndCovariantArguments.java -interface -SelfBoundSetter> { - void set(T arg); +interface SelfBoundSetter> { + void set(T arg); } interface Setter extends SelfBoundSetter {} public class SelfBoundingAndCovariantArguments { - void - testA(Setter s1, Setter s2, SelfBoundSetter sbs) { - s1.set(s2); - //- s1.set(sbs); - // error: method set in interface SelfBoundSetter - // cannot be applied to given types; - // s1.set(sbs); - // ^ - // required: Setter - // found: SelfBoundSetter - // reason: argument mismatch; - // SelfBoundSetter cannot be converted to Setter - // where T is a type-variable: - // T extends SelfBoundSetter declared in - // interface SelfBoundSetter - // 1 error - } + void + testA(Setter s1, Setter s2, SelfBoundSetter sbs) { + s1.set(s2); + //- s1.set(sbs); + // error: method set in interface SelfBoundSetter + // cannot be applied to given types; + // s1.set(sbs); + // ^ + // required: Setter + // found: SelfBoundSetter + // reason: argument mismatch; + // SelfBoundSetter cannot be converted to Setter + // where T is a type-variable: + // T extends SelfBoundSetter declared in + // interface SelfBoundSetter + // 1 error + } } ``` 编译器不能识别将基类型当作参数传递给 `set()` 的尝试,因为没有任何方法具有这样的签名。实际上,这个参数已经被覆盖。 如果不使用自限定类型,普通的继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样: -``` +```java // generics/PlainGenericInheritance.java class GenericSetter { // Not self-bounded - void set(T arg) { - System.out.println("GenericSetter.set(Base)"); - } + void set(T arg) { + System.out.println("GenericSetter.set(Base)"); + } } class DerivedGS extends GenericSetter { - void set(Derived derived) { - System.out.println("DerivedGS.set(Derived)"); - } + void set(Derived derived) { + System.out.println("DerivedGS.set(Derived)"); + } } public class PlainGenericInheritance { - public static void main(String[] args) { - Base base = new Base(); - Derived derived = new Derived(); - DerivedGS dgs = new DerivedGS(); - dgs.set(derived); - dgs.set(base); // Overloaded, not overridden! - } + public static void main(String[] args) { + Base base = new Base(); + Derived derived = new Derived(); + DerivedGS dgs = new DerivedGS(); + dgs.set(derived); + dgs.set(base); // Overloaded, not overridden! + } } /* Output: DerivedGS.set(Derived) @@ -3896,16 +4006,16 @@ GenericSetter.set(Base) */ ``` -这段代码在模仿 **OrdinaryArgument.java** ,在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArgument.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得某个方法的一个版本,它将接受确切的参数类型。 +这段代码在模仿 **OrdinaryArguments.java**;在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArguments.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得方法的一个版本,它将接受确切的参数类型。 -## 动态类型安全 + - +## 动态类型安全 -因为可以向 Java SE5 之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器,Java SE5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种强况下的类型检查问题,它们是:静态方法`checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的容器当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。 +因为可以向 Java 5 之前的代码传递泛型集合,所以旧式代码仍旧有可能会破坏你的集合。Java 5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法 `checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的集合当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。 -受检查的容器在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)容器形成了对比,对于后者来说,当你将对象从容器中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的容器,就可以发现谁在试图插入不良对象。 -让我们用受检查的容器来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的: +受检查的集合在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的集合,就可以发现谁在试图插入不良对象。 +让我们用受检查的集合来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的: ```java // generics/CheckedList.java @@ -3914,26 +4024,27 @@ import typeinfo.pets.*; import java.util.*; public class CheckedList { - @SuppressWarnings("unchecked") - static void oldStyleMethod(List probablyDogs) { - probablyDogs.add(new Cat()); - } - public static void main(String[] args) { - List dogs1 = new ArrayList<>(); - oldStyleMethod(dogs1); // Quietly accepts a Cat - List dogs2 = Collections.checkedList( - new ArrayList<>(), Dog.class); - try { - oldStyleMethod(dogs2); // Throws an exception - } catch(Exception e) { - System.out.println("Expected: " + e); - } - // Derived types work fine: - List pets = Collections.checkedList( - new ArrayList<>(), Pet.class); - pets.add(new Dog()); - pets.add(new Cat()); - } + @SuppressWarnings("unchecked") + static void oldStyleMethod(List probablyDogs) { + probablyDogs.add(new Cat()); + } + + public static void main(String[] args) { + List dogs1 = new ArrayList<>(); + oldStyleMethod(dogs1); // Quietly accepts a Cat + List dogs2 = Collections.checkedList( + new ArrayList<>(), Dog.class); + try { + oldStyleMethod(dogs2); // Throws an exception + } catch(Exception e) { + System.out.println("Expected: " + e); + } + // Derived types work fine: + List pets = Collections.checkedList( + new ArrayList<>(), Pet.class); + pets.add(new Dog()); + pets.add(new Cat()); + } } /* Output: Expected: java.lang.ClassCastException: Attempt to @@ -3944,12 +4055,12 @@ with element type class typeinfo.pets.Dog 运行这个程序时,你会发现插入一个 **Cat** 对于 **dogs1** 来说没有任何问题,而 **dogs2** 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。 -## 泛型异常 + - +## 泛型异常 -由于擦除的原因,将泛型应用于异常是非常受限的。**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。 -但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常的类型而发生变化的泛型代码: +由于擦除的原因,**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。 +但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常类型变化的泛型代码: ```java // generics/ThrowGenericException.java @@ -3957,76 +4068,76 @@ with element type class typeinfo.pets.Dog import java.util.*; interface Processor { - void process(List resultCollector) throws E; + void process(List resultCollector) throws E; } class ProcessRunner extends ArrayList> { - List processAll() throws E { - List resultCollector = new ArrayList<>(); - for(Processor processor : this) - processor.process(resultCollector); - return resultCollector; - } + List processAll() throws E { + List resultCollector = new ArrayList<>(); + for(Processor processor : this) + processor.process(resultCollector); + return resultCollector; + } } class Failure1 extends Exception {} class Processor1 implements Processor { - static int count = 3; - @Override - public void process(List resultCollector) - throws Failure1 { - if(count-- > 1) - resultCollector.add("Hep!"); - else - resultCollector.add("Ho!"); - if(count < 0) - throw new Failure1(); - } + static int count = 3; + @Override + public void process(List resultCollector) + throws Failure1 { + if(count-- > 1) + resultCollector.add("Hep!"); + else + resultCollector.add("Ho!"); + if(count < 0) + throw new Failure1(); + } } class Failure2 extends Exception {} class Processor2 implements Processor { - static int count = 2; - @Override - public void process(List resultCollector) - throws Failure2 { - if(count-- == 0) - resultCollector.add(47); - else { - resultCollector.add(11); - } - if(count < 0) - throw new Failure2(); - } + static int count = 2; + @Override + public void process(List resultCollector) + throws Failure2 { + if(count-- == 0) + resultCollector.add(47); + else { + resultCollector.add(11); + } + if(count < 0) + throw new Failure2(); + } } public class ThrowGenericException { - public static void main(String[] args) { - ProcessRunner runner = - new ProcessRunner<>(); - for(int i = 0; i < 3; i++) - runner.add(new Processor1()); - try { - System.out.println(runner.processAll()); - } catch(Failure1 e) { - System.out.println(e); - } - - ProcessRunner runner2 = - new ProcessRunner<>(); - for(int i = 0; i < 3; i++) - runner2.add(new Processor2()); - try { - System.out.println(runner2.processAll()); - } catch(Failure2 e) { - System.out.println(e); + public static void main(String[] args) { + ProcessRunner runner = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner.add(new Processor1()); + try { + System.out.println(runner.processAll()); + } catch(Failure1 e) { + System.out.println(e); + } + + ProcessRunner runner2 = + new ProcessRunner<>(); + for(int i = 0; i < 3; i++) + runner2.add(new Processor2()); + try { + System.out.println(runner2.processAll()); + } catch(Failure2 e) { + System.out.println(e); + } } - } } /* Output: [Hep!, Hep!, Ho!] @@ -4034,20 +4145,22 @@ Failure2 */ ``` -**Processor** 执行 `process()`,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它将执行所持有的每个 **Process** 对象,并返回 **resultCollector** 。 +**Processor** 执行 `process()` 方法,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它会在所持有的每个 **Process** 对象执行,并返回 **resultCollector** 。 如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。 + + ## 混型 术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。 -混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而方面经常被建议用来解决混型问题。 +混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而切面经常被建议用来解决混型问题。 -### C++中的混型 +### C++ 中的混型 -在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易的创建混型,因为 C++ 能够记住其模版参数的类型。 +在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。 下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。 -```cpp +```c++ // generics/Mixins.cpp #include @@ -4056,38 +4169,38 @@ Failure2 using namespace std; template class TimeStamped : public T { - long timeStamp; + long timeStamp; public: - TimeStamped() { timeStamp = time(0); } - long getStamp() { return timeStamp; } + TimeStamped() { timeStamp = time(0); } + long getStamp() { return timeStamp; } }; template class SerialNumbered : public T { - long serialNumber; - static long counter; + long serialNumber; + static long counter; public: - SerialNumbered() { serialNumber = counter++; } - long getSerialNumber() { return serialNumber; } + SerialNumbered() { serialNumber = counter++; } + long getSerialNumber() { return serialNumber; } }; // Define and initialize the static storage: template long SerialNumbered::counter = 1; class Basic { - string value; + string value; public: - void set(string val) { value = val; } - string get() { return value; } + void set(string val) { value = val; } + string get() { return value; } }; int main() { - TimeStamped> mixin1, mixin2; - mixin1.set("test string 1"); - mixin2.set("test string 2"); - cout << mixin1.get() << " " << mixin1.getStamp() << - " " << mixin1.getSerialNumber() << endl; - cout << mixin2.get() << " " << mixin2.getStamp() << - " " << mixin2.getSerialNumber() << endl; + TimeStamped> mixin1, mixin2; + mixin1.set("test string 1"); + mixin2.set("test string 2"); + cout << mixin1.get() << " " << mixin1.getStamp() << + " " << mixin1.getSerialNumber() << endl; + cout << mixin2.get() << " " << mixin2.getStamp() << + " " << mixin2.getSerialNumber() << endl; } /* Output: test string 1 1452987605 1 @@ -4095,17 +4208,17 @@ test string 2 1452987605 2 */ ``` -在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么地轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了: +在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么的轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了: -``` +```c++ TimeStamped> mixin1,mixin2; ``` -遗憾的是,Java泛型不允许这样。擦除会忘记基类类型,因此 +遗憾的是,Java 泛型不允许这样。擦除会忘记基类类型,因此 > 泛型类不能直接继承自一个泛型参数 -这突显了许多我在Java语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您发现自己做不到。 +这突显了许多我在 Java 语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您会发现自己做不到。 ### 与接口混合 @@ -4119,63 +4232,61 @@ import java.util.*; interface TimeStamped { long getStamp(); } class TimeStampedImp implements TimeStamped { - private final long timeStamp; - TimeStampedImp() { - timeStamp = new Date().getTime(); - } - @Override - public long getStamp() { return timeStamp; } + private final long timeStamp; + TimeStampedImp() { + timeStamp = new Date().getTime(); + } + @Override + public long getStamp() { return timeStamp; } } interface SerialNumbered { long getSerialNumber(); } class SerialNumberedImp implements SerialNumbered { - private static long counter = 1; - private final long serialNumber = counter++; - @Override - public long getSerialNumber() { return serialNumber; } + private static long counter = 1; + private final long serialNumber = counter++; + @Override + public long getSerialNumber() { return serialNumber; } } interface Basic { - void set(String val); - String get(); + void set(String val); + String get(); } class BasicImp implements Basic { - private String value; - @Override - public void set(String val) { value = val; } - @Override - public String get() { return value; } + private String value; + @Override + public void set(String val) { value = val; } + @Override + public String get() { return value; } } class Mixin extends BasicImp implements TimeStamped, SerialNumbered { - private TimeStamped timeStamp = new TimeStampedImp(); - private SerialNumbered serialNumber = - new SerialNumberedImp(); - @Override - public long getStamp() { - return timeStamp.getStamp(); - } - @Override - public long getSerialNumber() { - return serialNumber.getSerialNumber(); - } + private TimeStamped timeStamp = new TimeStampedImp(); + private SerialNumbered serialNumber = + new SerialNumberedImp(); + @Override + public long getStamp() { + return timeStamp.getStamp(); + } + @Override + public long getSerialNumber() { + return serialNumber.getSerialNumber(); + } } public class Mixins { - public static void main(String[] args) { - Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); - mixin1.set("test string 1"); - mixin2.set("test string 2"); - System.out.println(mixin1.get() + " " + - mixin1.getStamp() + " " + - mixin1.getSerialNumber()); - System.out.println(mixin2.get() + " " + - mixin2.getStamp() + " " + - mixin2.getSerialNumber()); - } + public static void main(String[] args) { + Mixin mixin1 = new Mixin(), mixin2 = new Mixin(); + mixin1.set("test string 1"); + mixin2.set("test string 2"); + System.out.println(mixin1.get() + " " + + mixin1.getStamp() + " " + mixin1.getSerialNumber()); + System.out.println(mixin2.get() + " " + + mixin2.getStamp() + " " + mixin2.getSerialNumber()); + } } /* Output: test string 1 1494331663026 1 @@ -4183,12 +4294,12 @@ test string 2 1494331663027 2 */ ``` -**Mixin** 类基本上是在使用*代理*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 +**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 ### 使用装饰器模式 当你观察混型的使用方式时,就会发现混型概念好像与*装饰器*设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。 -装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的一—无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。 +装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。 装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。 前面的示例可以被改写为使用装饰器: @@ -4200,46 +4311,46 @@ package generics.decorator; import java.util.*; class Basic { - private String value; - public void set(String val) { value = val; } - public String get() { return value; } + private String value; + public void set(String val) { value = val; } + public String get() { return value; } } class Decorator extends Basic { - protected Basic basic; - Decorator(Basic basic) { this.basic = basic; } - @Override - public void set(String val) { basic.set(val); } - @Override - public String get() { return basic.get(); } + protected Basic basic; + Decorator(Basic basic) { this.basic = basic; } + @Override + public void set(String val) { basic.set(val); } + @Override + public String get() { return basic.get(); } } class TimeStamped extends Decorator { - private final long timeStamp; - TimeStamped(Basic basic) { - super(basic); - timeStamp = new Date().getTime(); - } - public long getStamp() { return timeStamp; } + private final long timeStamp; + TimeStamped(Basic basic) { + super(basic); + timeStamp = new Date().getTime(); + } + public long getStamp() { return timeStamp; } } class SerialNumbered extends Decorator { - private static long counter = 1; - private final long serialNumber = counter++; - SerialNumbered(Basic basic) { super(basic); } - public long getSerialNumber() { return serialNumber; } + private static long counter = 1; + private final long serialNumber = counter++; + SerialNumbered(Basic basic) { super(basic); } + public long getSerialNumber() { return serialNumber; } } public class Decoration { - public static void main(String[] args) { - TimeStamped t = new TimeStamped(new Basic()); - TimeStamped t2 = new TimeStamped( - new SerialNumbered(new Basic())); - //- t2.getSerialNumber(); // Not available - SerialNumbered s = new SerialNumbered(new Basic()); - SerialNumbered s2 = new SerialNumbered( - new TimeStamped(new Basic())); - //- s2.getStamp(); // Not available + public static void main(String[] args) { + TimeStamped t = new TimeStamped(new Basic()); + TimeStamped t2 = new TimeStamped( + new SerialNumbered(new Basic())); + //- t2.getSerialNumber(); // Not available + SerialNumbered s = new SerialNumbered(new Basic()); + SerialNumbered s2 = new SerialNumbered( + new TimeStamped(new Basic())); + //- s2.getStamp(); // Not available } } ``` @@ -4248,7 +4359,7 @@ public class Decoration { ### 与动态代理混合 -可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看类型信息(Type Information) 章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。 +可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看 [类型信息](book/19-Type-Information.md) 一章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。 由于动态代理的限制,每个被混入的类都必须是某个接口的实现: ```java @@ -4260,55 +4371,53 @@ import onjava.*; import static onjava.Tuple.*; class MixinProxy implements InvocationHandler { - Map delegatesByMethod; - @SuppressWarnings("unchecked") - MixinProxy(Tuple2>... pairs) { - delegatesByMethod = new HashMap<>(); - for(Tuple2> pair : pairs) { - for(Method method : pair.a2.getMethods()) { + Map delegatesByMethod; + @SuppressWarnings("unchecked") + MixinProxy(Tuple2>... pairs) { + delegatesByMethod = new HashMap<>(); + for(Tuple2> pair : pairs) { + for(Method method : pair.a2.getMethods()) { + String methodName = method.getName(); + // The first interface in the map + // implements the method. + if(!delegatesByMethod.containsKey(methodName)) + delegatesByMethod.put(methodName, pair.a1); + } + } + } + @Override + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { String methodName = method.getName(); - // The first interface in the map - // implements the method. - if(!delegatesByMethod.containsKey(methodName)) - delegatesByMethod.put(methodName, pair.a1); - } + Object delegate = delegatesByMethod.get(methodName); + return method.invoke(delegate, args); + } + + @SuppressWarnings("unchecked") + public static Object newInstance(Tuple2... pairs) { + Class[] interfaces = new Class[pairs.length]; + for(int i = 0; i < pairs.length; i++) { + interfaces[i] = (Class)pairs[i].a2; + } + ClassLoader cl = pairs[0].a1.getClass().getClassLoader(); + return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs)); } - } - @Override - public Object invoke(Object proxy, Method method, - Object[] args) throws Throwable { - String methodName = method.getName(); - Object delegate = delegatesByMethod.get(methodName); - return method.invoke(delegate, args); - } - @SuppressWarnings("unchecked") - public static Object newInstance(Tuple2... pairs) { - Class[] interfaces = new Class[pairs.length]; - for(int i = 0; i < pairs.length; i++) { - interfaces[i] = (Class)pairs[i].a2; - } - ClassLoader cl = - pairs[0].a1.getClass().getClassLoader(); - return Proxy.newProxyInstance( - cl, interfaces, new MixinProxy(pairs)); - } } public class DynamicProxyMixin { - public static void main(String[] args) { - Object mixin = MixinProxy.newInstance( - tuple(new BasicImp(), Basic.class), - tuple(new TimeStampedImp(), TimeStamped.class), - tuple(new SerialNumberedImp(), - SerialNumbered.class)); - Basic b = (Basic)mixin; - TimeStamped t = (TimeStamped)mixin; - SerialNumbered s = (SerialNumbered)mixin; - b.set("Hello"); - System.out.println(b.get()); - System.out.println(t.getStamp()); - System.out.println(s.getSerialNumber()); - } + public static void main(String[] args) { + Object mixin = MixinProxy.newInstance( + tuple(new BasicImp(), Basic.class), + tuple(new TimeStampedImp(), TimeStamped.class), + tuple(new SerialNumberedImp(), SerialNumbered.class)); + Basic b = (Basic)mixin; + TimeStamped t = (TimeStamped)mixin; + SerialNumbered s = (SerialNumbered)mixin; + b.set("Hello"); + System.out.println(b.get()); + System.out.println(t.getStamp()); + System.out.println(s.getSerialNumber()); + } } /* Output: Hello @@ -4317,7 +4426,7 @@ Hello */ ``` -因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 +因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。 @@ -4325,13 +4434,18 @@ Hello ## 潜在类型机制 在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。 -Java泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动包装机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。 -还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之前的操作)时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。 -某些编程语言提供的一种解决方案称为潜在类型机制或结构化类型机制,而更古怪的术语称为鸭子类型机制,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。 -泛型代码典型地将在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。 -潜在类型机制是一种代码组织和复用机制。有了它编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。 -两种支持潜在类型机制的语言实例是 Python (可以从www.Python.org免费下载)和 C++。Python是动态类型语言(事实上所有的类型检查都发生在运行时),而C++是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。 +Java 泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动装箱机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。 + +还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。 + +某些编程语言提供的一种解决方案称为*潜在类型机制*或*结构化类型机制*,而更古怪的术语称为*鸭子类型机制*,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。 + +泛型代码典型地只能在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。 + +潜在类型机制是一种代码组织和复用机制。有了它,编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。 + +支持潜在类型机制的语言包括 Python(可以从 www.Python.org 免费下载)、C++、Ruby、SmallTalk 和 Go。Python 是动态类型语言(几乎所有的类型检查都发生在运行时),而 C++ 和 Go 是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。 ### pyhton 中的潜在类型 @@ -4373,15 +4487,15 @@ Clank! """ ``` -Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许正则(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)`中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()`方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。 +Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许普通(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)` 中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()` 方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。 -输出规定使用三重引号创建带有嵌入式换行符的字符串。 +输出规定使用三重引号创建带有内嵌换行符的字符串。 ### C++ 中的潜在类型 -我们可以用C++产生相同的效果: +我们可以用 C++ 产生相同的效果: -```cpp +```c++ // generics/DogsAndRobots.cpp #include @@ -4389,28 +4503,28 @@ using namespace std; class Dog { public: - void speak() { cout << "Arf!" << endl; } - void sit() { cout << "Sitting" << endl; } - void reproduce() {} + void speak() { cout << "Arf!" << endl; } + void sit() { cout << "Sitting" << endl; } + void reproduce() {} }; class Robot { public: - void speak() { cout << "Click!" << endl; } - void sit() { cout << "Clank!" << endl; } - void oilChange() {} + void speak() { cout << "Click!" << endl; } + void sit() { cout << "Clank!" << endl; } + void oilChange() {} }; template void perform(T anything) { - anything.speak(); - anything.sit(); + anything.speak(); + anything.sit(); } int main() { - Dog d; - Robot r; - perform(d); - perform(r); + Dog d; + Robot r; + perform(d); + perform(r); } /* Output: Arf! @@ -4421,13 +4535,13 @@ Clank! ``` 在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。 -C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,而主要原因是因为 C++ 的模版名声欠佳)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 +C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 ### Go 中的潜在类型 -这是用Go语言编写的相同程序: +这里用 Go 语言编写相同的程序: -```java +```go // generics/dogsandrobots.go package main @@ -4460,26 +4574,26 @@ Clank! */ ``` -Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是将其定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。 +Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。 -Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要的原因。 但是,Go 的组成很简单。 +Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要原因。 但是,Go 的组成很简单。 `perform()` 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 `speak()` 和 `sit()` 方法即可。 该接口在此处匿名定义,内联,如 `perform()` 的参数列表所示。 -`main()` 证明 `perform()` 确实对其参数的确切类型无关紧要,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。 +`main()` 证明 `perform()` 确实对其参数的确切类型不在乎,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。 语法 **Dog {}** 和 **Robot {}** 创建匿名的 **Dog** 和 **Robot** 结构。 ### java中的直接潜在类型 -因为泛型是在这场竞赛的后期才添加到 Java 中的,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用删除来实现Java泛型的实现有时称为第二类泛型类型)例如,在 Java8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它: +因为泛型是在这场竞赛的后期才添加到 Java 中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用擦除来实现 Java 泛型的实现有时称为第二类泛型类型)例如,在 Java 8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它: ```java // generics/Performs.java public interface Performs { - void speak(); - void sit(); + void speak(); + void sit(); } ``` @@ -4489,32 +4603,32 @@ public interface Performs { import typeinfo.pets.*; class PerformingDog extends Dog implements Performs { - @Override - public void speak() { System.out.println("Woof!"); } - @Override - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + @Override + public void speak() { System.out.println("Woof!"); } + @Override + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class Robot implements Performs { - public void speak() { System.out.println("Click!"); } - public void sit() { System.out.println("Clank!"); } - public void oilChange() {} + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} } class Communicate { - public static - void perform(T performer) { - performer.speak(); - performer.sit(); - } + public static + void perform(T performer) { + performer.speak(); + performer.sit(); + } } public class DogsAndRobots { - public static void main(String[] args) { - Communicate.perform(new PerformingDog()); - Communicate.perform(new Robot()); - } + public static void main(String[] args) { + Communicate.perform(new PerformingDog()); + Communicate.perform(new Robot()); + } } /* Output: Woof! @@ -4531,17 +4645,17 @@ Clank! // Removing the generic; code still works class CommunicateSimply { - static void perform(Performs performer) { - performer.speak(); - performer.sit(); - } + static void perform(Performs performer) { + performer.speak(); + performer.sit(); + } } public class SimpleDogsAndRobots { - public static void main(String[] args) { - CommunicateSimply.perform(new PerformingDog()); - CommunicateSimply.perform(new Robot()); - } + public static void main(String[] args) { + CommunicateSimply.perform(new PerformingDog()); + CommunicateSimply.perform(new Robot()); + } } /* Output: Woof! @@ -4557,7 +4671,7 @@ Clank! ## 对缺乏潜在类型机制的补偿 -尽管 Java 不支持潜在类型机制,但是这并不意味着有界泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 +尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。 ### 反射 @@ -4570,53 +4684,53 @@ import java.lang.reflect.*; // Does not implement Performs: class Mime { - public void walkAgainstTheWind() {} - public void sit() { - System.out.println("Pretending to sit"); - } - public void pushInvisibleWalls() {} - @Override - public String toString() { return "Mime"; } + public void walkAgainstTheWind() {} + public void sit() { + System.out.println("Pretending to sit"); + } + public void pushInvisibleWalls() {} + @Override + public String toString() { return "Mime"; } } // Does not implement Performs: class SmartDog { - public void speak() { System.out.println("Woof!"); } - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class CommunicateReflectively { - public static void perform(Object speaker) { - Class spkr = speaker.getClass(); - try { - try { - Method speak = spkr.getMethod("speak"); - speak.invoke(speaker); - } catch(NoSuchMethodException e) { - System.out.println(speaker + " cannot speak"); - } - try { - Method sit = spkr.getMethod("sit"); - sit.invoke(speaker); - } catch(NoSuchMethodException e) { - System.out.println(speaker + " cannot sit"); - } - } catch(SecurityException | + public static void perform(Object speaker) { + Class spkr = speaker.getClass(); + try { + try { + Method speak = spkr.getMethod("speak"); + speak.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot speak"); + } + try { + Method sit = spkr.getMethod("sit"); + sit.invoke(speaker); + } catch(NoSuchMethodException e) { + System.out.println(speaker + " cannot sit"); + } + } catch(SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(speaker.toString(), e); + throw new RuntimeException(speaker.toString(), e); + } } - } } public class LatentReflection { - public static void main(String[] args) { - CommunicateReflectively.perform(new SmartDog()); - CommunicateReflectively.perform(new Robot()); - CommunicateReflectively.perform(new Mime()); - } + public static void main(String[] args) { + CommunicateReflectively.perform(new SmartDog()); + CommunicateReflectively.perform(new Robot()); + CommunicateReflectively.perform(new Mime()); + } } /* Output: Woof! @@ -4632,9 +4746,9 @@ Pretending to sit ### 将一个方法应用于序列 -反射提供了一些有趣的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? +反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗? -让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这是接口看起来并不适合的情况,因为你想要将任何方法应用于一个对象集合,而接口对于描述“任何方法”存在过多的限制。如何用Java来实现这个需求呢? +让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢? 最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的: @@ -4645,18 +4759,18 @@ import java.lang.reflect.*; import java.util.*; public class Apply { - public static > - void apply(S seq, Method f, Object... args) { - try { - for(T t: seq) - f.invoke(t, args); - } catch(IllegalAccessException | + public static > + void apply(S seq, Method f, Object... args) { + try { + for(T t: seq) + f.invoke(t, args); + } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - // Failures are programmer errors - throw new RuntimeException(e); + // Failures are programmer errors + throw new RuntimeException(e); + } } - } } ``` @@ -4670,18 +4784,18 @@ public class Apply { // generics/Shape.java public class Shape { - private static long counter = 0; - private final long id = counter++; - @Override - public String toString() { - return getClass().getSimpleName() + " " + id; - } - public void rotate() { - System.out.println(this + " rotate"); - } - public void resize(int newSize) { - System.out.println(this + " resize " + newSize); - } + private static long counter = 0; + private final long id = counter++; + @Override + public String toString() { + return getClass().getSimpleName() + " " + id; + } + public void rotate() { + System.out.println(this + " rotate"); + } + public void resize(int newSize) { + System.out.println(this + " resize " + newSize); + } } ``` @@ -4703,35 +4817,30 @@ import java.util.function.*; import onjava.*; public class ApplyTest { - public static - void main(String[] args) throws Exception { - List shapes = - Suppliers.create(ArrayList::new, Shape::new, 3); - Apply.apply(shapes, - Shape.class.getMethod("rotate")); - Apply.apply(shapes, - Shape.class.getMethod("resize", int.class), 7); - - List squares = - Suppliers.create(ArrayList::new, Square::new, 3); - Apply.apply(squares, - Shape.class.getMethod("rotate")); - Apply.apply(squares, - Shape.class.getMethod("resize", int.class), 7); - - Apply.apply(new FilledList<>(Shape::new, 3), - Shape.class.getMethod("rotate")); - Apply.apply(new FilledList<>(Square::new, 3), - Shape.class.getMethod("rotate")); - - SimpleQueue shapeQ = Suppliers.fill( - new SimpleQueue<>(), SimpleQueue::add, - Shape::new, 3); - Suppliers.fill(shapeQ, SimpleQueue::add, - Square::new, 3); - Apply.apply(shapeQ, - Shape.class.getMethod("rotate")); - } + public static + void main(String[] args) throws Exception { + List shapes = + Suppliers.create(ArrayList::new, Shape::new, 3); + Apply.apply(shapes, Shape.class.getMethod("rotate")); + Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 7); + + List squares = + Suppliers.create(ArrayList::new, Square::new, 3); + Apply.apply(squares, Shape.class.getMethod("rotate")); + Apply.apply(squares, Shape.class.getMethod("resize", int.class), 7); + + Apply.apply(new FilledList<>(Shape::new, 3), + Shape.class.getMethod("rotate")); + Apply.apply(new FilledList<>(Square::new, 3), + Shape.class.getMethod("rotate")); + + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 3); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 3); + Apply.apply(shapeQ, Shape.class.getMethod("rotate")); + } } /* Output: Shape 0 rotate @@ -4761,7 +4870,7 @@ Square 17 rotate */ ``` -在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 容器类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的一例如,在 `main()` 中使用的下面定义的 **SimpleQueue** 类: +在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的——例如,在 `main()` 中使用下面定义的 **SimpleQueue** 类: ```java // generics/SimpleQueue.java @@ -4770,19 +4879,19 @@ Square 17 rotate import java.util.*; public class SimpleQueue implements Iterable { - private LinkedList storage = new LinkedList<>(); - public void add(T t) { storage.offer(t); } - public T get() { return storage.poll(); } - @Override - public Iterator iterator() { - return storage.iterator(); - } + private LinkedList storage = new LinkedList<>(); + public void add(T t) { storage.offer(t); } + public T get() { return storage.poll(); } + @Override + public Iterator iterator() { + return storage.iterator(); + } } ``` -正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在Java的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 +正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。 -几乎可以肯定,你会首先使用 Java 8 功能方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: +几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具: ```java // generics/ApplyFunctional.java @@ -4793,26 +4902,26 @@ import java.util.function.*; import onjava.*; public class ApplyFunctional { - public static void main(String[] args) { - Stream.of( - Stream.generate(Shape::new).limit(2), - Stream.generate(Square::new).limit(2)) - .flatMap(c -> c) // flatten into one stream - .peek(Shape::rotate) - .forEach(s -> s.resize(7)); - - new FilledList<>(Shape::new, 2) - .forEach(Shape::rotate); - new FilledList<>(Square::new, 2) - .forEach(Shape::rotate); - - SimpleQueue shapeQ = Suppliers.fill( - new SimpleQueue<>(), SimpleQueue::add, - Shape::new, 2); - Suppliers.fill(shapeQ, SimpleQueue::add, - Square::new, 2); - shapeQ.forEach(Shape::rotate); - } + public static void main(String[] args) { + Stream.of( + Stream.generate(Shape::new).limit(2), + Stream.generate(Square::new).limit(2)) + .flatMap(c -> c) // flatten into one stream + .peek(Shape::rotate) + .forEach(s -> s.resize(7)); + + new FilledList<>(Shape::new, 2) + .forEach(Shape::rotate); + new FilledList<>(Square::new, 2) + .forEach(Shape::rotate); + + SimpleQueue shapeQ = Suppliers.fill( + new SimpleQueue<>(), SimpleQueue::add, + Shape::new, 2); + Suppliers.fill(shapeQ, SimpleQueue::add, + Square::new, 2); + shapeQ.forEach(Shape::rotate); + } } /* Output: Shape 0 rotate @@ -4846,11 +4955,11 @@ Square 11 rotate ## Java8 中的辅助潜在类型 -先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,该形式可以满足创建可在不相关类型上工作的单段代码的要求。 由于 Java 最初并不是设计用于执行此操作的,因此,正如现在可能期望的那样,其结果比其他语言要尴尬得多。 +先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为 Java 最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。 -我没有在其他地方遇到过这种技术,因此我将其称为辅助潜在类型。 +我在其他地方从没遇过这种技术,因此我将其称为辅助潜在类型。 -我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A** : +我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A**: ```java // generics/DogsAndRobotMethodReferences.java @@ -4860,35 +4969,35 @@ import typeinfo.pets.*; import java.util.function.*; class PerformingDogA extends Dog { - public void speak() { System.out.println("Woof!"); } - public void sit() { System.out.println("Sitting"); } - public void reproduce() {} + public void speak() { System.out.println("Woof!"); } + public void sit() { System.out.println("Sitting"); } + public void reproduce() {} } class RobotA { - public void speak() { System.out.println("Click!"); } - public void sit() { System.out.println("Clank!"); } - public void oilChange() {} + public void speak() { System.out.println("Click!"); } + public void sit() { System.out.println("Clank!"); } + public void oilChange() {} } class CommunicateA { - public static

void perform(P performer, - Consumer

action1, Consumer

action2) { - action1.accept(performer); - action2.accept(performer); - } + public static

void perform(P performer, + Consumer

action1, Consumer

action2) { + action1.accept(performer); + action2.accept(performer); + } } public class DogsAndRobotMethodReferences { - public static void main(String[] args) { - CommunicateA.perform(new PerformingDogA(), - PerformingDogA::speak, PerformingDogA::sit); - CommunicateA.perform(new RobotA(), - RobotA::speak, RobotA::sit); - CommunicateA.perform(new Mime(), - Mime::walkAgainstTheWind, - Mime::pushInvisibleWalls); - } + public static void main(String[] args) { + CommunicateA.perform(new PerformingDogA(), + PerformingDogA::speak, PerformingDogA::sit); + CommunicateA.perform(new RobotA(), + RobotA::speak, RobotA::sit); + CommunicateA.perform(new Mime(), + Mime::walkAgainstTheWind, + Mime::pushInvisibleWalls); + } } /* Output: Woof! @@ -4900,17 +5009,17 @@ Clank! **PerformingDogA** 和 **RobotA** 与 **DogsAndRobots.java** 中的相同,不同之处在于它们不继承通用接口 **Performs** ,因此它们没有通用性。 -`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。 当您调用消费者的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于“函数式编程”一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。 +`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。当您调用 **Consumer** 的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于 [函数式编程](book/13-Functional-Programming.md) 一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。 之所以称其为“辅助”,是因为您必须显式地为 `perform()` 提供要使用的方法引用。 它不能只按名称调用方法。 尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 `CommunicateA.perform()` ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。 -为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime** 。 +为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime**。 ### 使用**Suppliers**类的通用方法 -通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的实用程序方法。 “通用化”这些操作很有意义: +通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的工具方法。 泛化这些操作很有意义: ```java // onjava/Suppliers.java @@ -4922,38 +5031,40 @@ import java.util.function.*; import java.util.stream.*; public class Suppliers { - // Create a collection and fill it: - public static > C - create(Supplier factory, Supplier gen, int n) { - return Stream.generate(gen) - .limit(n) - .collect(factory, C::add, C::addAll); - } - // Fill an existing collection: - public static > - C fill(C coll, Supplier gen, int n) { - Stream.generate(gen) - .limit(n) - .forEach(coll::add); - return coll; - } - // Use an unbound method reference to - // produce a more general method: - public static H fill(H holder, - BiConsumer adder, Supplier gen, int n) { - Stream.generate(gen) - .limit(n) - .forEach(a -> adder.accept(holder, a)); - return holder; - } + // Create a collection and fill it: + public static > C + create(Supplier factory, Supplier gen, int n) { + return Stream.generate(gen) + .limit(n) + .collect(factory, C::add, C::addAll); + } + + // Fill an existing collection: + public static > + C fill(C coll, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(coll::add); + return coll; + } + + // Use an unbound method reference to + // produce a more general method: + public static H fill(H holder, + BiConsumer adder, Supplier gen, int n) { + Stream.generate(gen) + .limit(n) + .forEach(a -> adder.accept(holder, a)); + return holder; + } } ``` `create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。 -前两种方法一般都受约束以与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数a调用对象 **holder** 上的未绑定方法 **holder**。 +前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。 -在一个稍作模拟的测试中对 **Suppliers** 实用程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** : +在一个稍作模拟的测试中对 **Suppliers** 工具程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** : ```java // generics/BankTeller.java @@ -4963,53 +5074,53 @@ import java.util.*; import onjava.*; class Customer { - private static long counter = 1; - private final long id = counter++; - @Override - public String toString() { - return "Customer " + id; - } + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Customer " + id; + } } class Teller { - private static long counter = 1; - private final long id = counter++; - @Override - public String toString() { - return "Teller " + id; - } + private static long counter = 1; + private final long id = counter++; + @Override + public String toString() { + return "Teller " + id; + } } class Bank { - private List tellers = - new ArrayList<>(); - public void put(BankTeller bt) { - tellers.add(bt); - } + private List tellers = + new ArrayList<>(); + public void put(BankTeller bt) { + tellers.add(bt); + } } public class BankTeller { - public static void serve(Teller t, Customer c) { - System.out.println(t + " serves " + c); - } - public static void main(String[] args) { - // Demonstrate create(): - RandomList tellers = - Suppliers.create( - RandomList::new, Teller::new, 4); - // Demonstrate fill(): - List customers = Suppliers.fill( - new ArrayList<>(), Customer::new, 12); - customers.forEach(c -> - serve(tellers.select(), c)); - // Demonstrate assisted latent typing: - Bank bank = Suppliers.fill( - new Bank(), Bank::put, BankTeller::new, 3); - // Can also use second version of fill(): - List customers2 = Suppliers.fill( - new ArrayList<>(), - List::add, Customer::new, 12); - } + public static void serve(Teller t, Customer c) { + System.out.println(t + " serves " + c); + } + public static void main(String[] args) { + // Demonstrate create(): + RandomList tellers = + Suppliers.create( + RandomList::new, Teller::new, 4); + // Demonstrate fill(): + List customers = Suppliers.fill( + new ArrayList<>(), Customer::new, 12); + customers.forEach(c -> + serve(tellers.select(), c)); + // Demonstrate assisted latent typing: + Bank bank = Suppliers.fill( + new Bank(), Bank::put, BankTeller::new, 3); + // Can also use second version of fill(): + List customers2 = Suppliers.fill( + new ArrayList<>(), + List::add, Customer::new, 12); + } } /* Output: Teller 3 serves Customer 1 @@ -5027,41 +5138,50 @@ Teller 4 serves Customer 12 */ ``` -可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与新的和无关的类型 **Bank** 一起使用,还与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。 +可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与无关的新类型 **Bank** 一起使用,还能与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。 ## 总结:类型转换真的如此之糟吗? -自从C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。 +自从 C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。 -这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用容器类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在集合章节和附件:集合主题章节中看到的各种类。在 Java SE 5 之前,当你将一个对象放置到容器中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从容器中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在集合章节的开头展示过)。如果没有 Java SE 5 的泛型版本的容器,你放到容器里的和从容器中取回的,都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。 +这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用集合类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在 [集合](book/12-Collections.md) 和 [附录:集合主题](book/Appendix-Collection-Topics.md) 这两章所见。在 Java 5 之前,当你将一个对象放置到集合中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从集合中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在 [集合](book/12-Collections.md) 章节的开头展示过)。如果没有 Java 5 泛型版本的集合,你放到容集里和从集合中取回的都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。 -但是,泛型出现之前的 Java 并不会让你误用放入到容器中的对象。如果将一个 **Dog** 扔到 **Cat** 的容器中,并且试图将这个容器中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 容器中取回那个 **Dog** 引用,并试图将其转型为**Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。 +但是,泛型出现之前的 Java 并不会让你误用放入到集合中的对象。如果将一个 **Dog** 扔到 **Cat** 的集合中,并且试图将这个集合中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 集合中取回那个 **Dog** 引用,并试图将其转型为 **Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。 在本书以前的版本中,我曾经说过: -> 这不止是令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向容器中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了容器中,那么必须发现这个不良插入到底是在何处发生的。 +> 这不止令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向集合中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了集合中,那么必须发现这个不良插入到底是在何处发生的。 > -但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个示例,它是一个包含 **String** 对象的被称为 **files** 的列表在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的容器,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。 +但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个称为 **files** 的 list 示例,它包含 **String** 对象。在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的集合,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。 -有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在第多线程编程章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗? -我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的容器。类型安全的容器是能够创建更通用代码这一能力所带来的副作用。 -因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解适配器设计模式的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。 +有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在 [多线程编程](book/24-Concurrent-Programming.md) 章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗? +我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的集合。类型安全的集合是能够创建更通用代码这一能力所带来的副作用。 +因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解这些代码的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。 还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 **Map** ,在特定的方法 `containsKey(Object key) `和 `get(Object key)` 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 **Object** ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 **map** 中,键的类型总是在编译期检查的。 + 有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。 -某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语句将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。 +某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语言将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。 ## 进阶阅读 -[^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。 -[^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html) -[^3]: 参见本章章末引文。 +泛型的入门文档是 《Generics in the Java Programming Language》,作者是 Gilad Bracha,可以从 http://java.oracle.com 获取。 +Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html。 +你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。 +Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到:http://www.infoq.com/articles/neal-gafter-on-java。 + +[^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。 +[^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html) +[^3]: 参见本章章末引文。 +[^4]: 注意,一些编程环境,如 Eclipse 和 IntelliJ IDEA,将会自动生成委托代码。 +[^5]: 因为可以使用转型,有效地禁止了类型系统,一些人就认为 C++ 是弱类型,但这太极端了。一种可能更好的说法是 C++ 是有一道暗门的强类型语言。 +[^6]: 我再次从 Brian Goetz 那获得帮助。

\ No newline at end of file From 335be52438f430b4f1b8d4523d876866c9513e58 Mon Sep 17 00:00:00 2001 From: 1326670425 <1326670425@qq.com> Date: Mon, 9 Dec 2019 10:29:10 +0800 Subject: [PATCH 130/371] =?UTF-8?q?=E9=99=84=E5=BD=95=EF=BC=9A=E6=88=90?= =?UTF-8?q?=E4=B8=BA=E4=B8=80=E5=90=8D=E7=A8=8B=E5=BA=8F=E5=91=98=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-Becoming-a-Programmer.md | 47 ++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/book/Appendix-Becoming-a-Programmer.md b/docs/book/Appendix-Becoming-a-Programmer.md index 56524862..edbf4c8b 100644 --- a/docs/book/Appendix-Becoming-a-Programmer.md +++ b/docs/book/Appendix-Becoming-a-Programmer.md @@ -10,7 +10,7 @@ 这是一条相当漫长和曲折的道路。我在高一学代数时(1971年),有个非常古怪的老师有一台计算机,还弄到了一台配有一个300波特的音频电话耦合器的ASR-33电传打字机,我学会了如何执行命令并得到响应,以及一个可以在高中区使用的HP-1000计算机上的帐户。我们能够创建和运行BASIC程序并将它们保存在打孔磁带上。我对此非常着迷,所以尽可能地把它带回家后在晚上写程序。我写了一个赛马模拟游戏--HOSRAC.BAS,用星号来代表马的移动,由于是在纸上打印输出,所以需要一点想象力。 -我的朋友丹尼尔(和设计我的书的封面是同一个人)有一个兄弟,他有段时间通过向酒吧和餐馆提供弹球机来赚钱。他有一台投币式街机(老虎机),最早的《乓》游戏之一,我对此全然不知,到现在我还忍受不了这东西(现在我几乎不玩电脑游戏,这样看来我可能是个没有幽默的人,但似乎编程比玩电脑游戏更有趣、更具挑战性。) +我的朋友丹尼尔(就是设计我的书封面的人)有一个兄弟,他有段时间通过向酒吧和餐馆提供弹球机来赚钱。他有一台投币式街机(老虎机),最早的《乓》游戏之一,我对此全然不知,到现在我还忍受不了这东西(现在我几乎不玩电脑游戏,这样看来我可能是个没有幽默的人,但似乎编程比玩电脑游戏更有趣、更具挑战性。) 后来我在高中参与了摄影和新闻工作,在大学的第一年就主修新闻学。我觉得自己已经从学校学到了足够多的东西,又转修了物理学。后来我在加州大学欧文分校完成了物理学位,如果我当时选择了一个特定的工程领域,修了足够的工程课就能拿到双专业,但我试图走得更远一些,所以最后我获得的本科学位是 "应用物理"。作为一名本科生,我多多少少学习了一些可以自娱自乐,但又没有任何深度的计算机编程课程。我个人认为在这些课程细细熏陶下,帮我打下了一定的基础,但事实我理解的这些东西没有任何深度。我不知道计算机、编译器或解释器有什么区别(只是对编译器和解释器一点点的理解)。对我来说计算机是绝对可靠的,而且我从来没有想过在程序语言和操作系统中会有出现错误的可能。 @@ -37,6 +37,51 @@ ## 码农生涯 +我会定期收到有关职业建议的请求,所以我尝试在这里回答一下这个问题。 + +人们提出的问题通常是错误的问题:“我应该学习 C++ 还是 Java ?”在本文中,我将尝试阐述我对选择计算机职业所涉及的真正问题的看法。 + +请注意,我在这里并不是和那些已经知道自己使命的人聊(译者注:指计划成为程序员或者已经从业的程序员,暗指这里是讲给外行的小白的)。因为无论别人怎么说,你都要去做,因为它已经渗入你的血液,并且你将无法摆脱它。你已经知道答案了:你当然会学到 C++ ,Java ,shell 脚本,Python 和许多其他语言和技术。即使你只有14岁,你也已经知道其中几种语言。 + +问我这个问题的人可能来自另一职业。也许他们来自 Web 开发等领域,他们已经发现 HTML 只是一种类似编程,他们想尝试构建更实质的内容。但是,我特别希望,如果你提出这个问题,你就已经意识到,要在计算机领域取得成功,你必须教自己如何学习,并且永不停止学习。 + +随着我做的越来越多,在我看来,软件越发比其他任何东西都更像写作。而且我们还没有弄清怎样成为一个好的作家,我们只知道何时我们喜欢别人写的东西。这不是像一些工程那样,我们要做的只是将某些东西放到一端,然后转动曲柄。诱人的是将软件视为确定性的,这就是我们想要的,这就是我们不断推出工具来帮助我们实现所需行为的原因。但是我的经验不断表明事实是相反的:它更多地是关于人而不是过程,并且它在确定性机器上运行的事实变得越来越没有影响力(指运行环境受机器影响,与机器相关这个事实),就像海森堡原理(不确定性原理:不可能同时知道一个粒子的位置和它的速度)不会在人类规模上影响事物一样。 + +在我青年时期,父亲是建造民居的,我偶尔会为他工作,大部分时间都从事艰苦的工作,有时还得悬挂石膏板。他和他的木匠会告诉我说,他们是为了我才把这些工作交给了我 —— 为了不让我从事这项工作。这确实是有效的。 + +因此,我也可以用比喻说,建造软件就像盖房子一样。我们并不是指每个在房屋上工作的人都一样。有混凝土泥瓦匠,屋顶工,水管工,电工,石膏板工人,抹灰工,瓷砖铺砌工,普通劳工,粗木匠,精整木匠,当然还有总承包商。这些中的每一个都需要一套不同的技能,这需要花费不同的时间和精力 房屋建造也受制于繁荣和萧条的周期,例如编程。为了快速起步,你可能需要当普通劳工或石膏板工人工作,在那里你可以在没有太多学习曲线的情况下开始获得报酬。只要需求旺盛,你就可以稳定工作,而且如果没有足够的人来工作,你的薪水甚至可能会上涨。但是一旦经济低迷,木匠甚至总承包商就可以自己将石膏板挂起来。 + +当 Internet 刚兴起时,你所要做的就是花一些时间学习 HTML ,就可以找到一份工作并赚到很多钱。但是,当情况恶化时,你很快就会发现需要的技能层次结构很深,HTML 程序员(例如劳工和石膏板工)排在第一位,而高技能的码农和木匠则被保留。 + +我想在这里说的是:除非你准备致力于终身学习,否则请不要从事这项业务。有时,编程似乎是一份报酬丰厚,值得信赖的工作,但确保这一点的唯一方法是,始终使自己变得更有价值。 + +当然,也可以找到例外。总会有一些人只学习一种语言,并且足够精通,甚至足够聪明,那么可以在不用多学很多其他知识的情况下继续工作。但是他们靠运气生存,最终很脆弱。为了减少自身的脆弱性,必须通过阅读,参加用户组,会议和研讨会来不断提高自己的能力。你在该领域的走得越深,你的价值就越大,这意味着你的工作前景更稳定,并且可以获得更高的薪水。 + +另一种方法是从总体上看待该领域,并找到一个你能成为专家的点。例如,我的兄弟对软件感兴趣,并且涉足软件,但是他的业务是安装计算机,维修计算机和升级计算机。他一直都很细致,因此,当他安装或修理计算机时,你会知道计算机状态良好。不仅是软件,而且一直到电缆,电缆都整齐地捆扎在一起,并且不成束。他的工作多到做不完,而且他从不关心网络泡沫破灭。毋庸置疑,他是不可能失业的。 + +我在大学待了很长时间,并以各种方式设法度过了难关。我甚至开始在加州大学洛杉矶分校攻读博士学位。这里的课程很短,我欣慰地说是因为我不再爱上大学了,而我在大学待了这么长时间的原因是因为我非常喜欢。但是我喜欢的通常是跑偏的东西。例如艺术,舞蹈课程,在大学报社工作,以及我参加的少数计算机编程课程(由于我是物理本科生和计算机工程专业的研究生,所以也算跑偏)。尽管我在学业上还算是出色的(具有讽刺意味的是,当时许多不接受我作为学生的大学现在都在课程中使用我的书),但我确实很享受大学生的生活,并且完成了博士学位。我可能会走上简单的道路,最终成为一名教授。 + +但是事实证明,我从大学获得的最大价值一部分来自那些跑偏的课程,这些课程使我的思维超出了“我们已经知道的东西”。我认为在计算机领域尤其如此,因为你总是通过编程来实现其他目标,而你对该目标越了解,你的表现就会越好(我学习了一些欧洲研究生课程,这些课程要求结合其他一些专业研究计算,通过解决这个领域相关的问题,你就会形成一种新的理论体系并可以将它用在别处)。 + +我还认为,不仅编程,多了解一些其它的知识,还可以大大提高你的解决问题的能力(就像了解一种以上的编程语言可以极大地提高你的编程能力一样)。在很多情况下,我遇到过仅接受过计算机科学训练的人,他们的思维似乎比其他背景(例如数学或物理学)的人更受限制,但其实这些人(数学或物理学领域的人)才更需要严格的思维。 + +在我组织的一次会议上,主题之一是为理想的求职者提供一系列功能: + +- 将学习作为一种生活方式。例如,学习一种以上的语言;没有什么比学习另一种语言更能吸引你的眼球。 +- 知道在哪里以及如何获得新知识。 +- 研究现有技术。 +- 我们是工具使用者,即要善于利用工具。 +- 学习做最简单的事情。 +- 了解业务(阅读杂志。从 *fast company*(国外一家商业杂志)开始,该公司的文章非常简短有趣。然后你就会知道是否要阅读其他的) +- 应对错误负责。 “我用着没事”是不可接受的策略。查找自己的错误。 +- 成为领导者:那些沟通和鼓舞别人的人。 +- 你在为谁服务? +- 没有正确的答案……但总是更好的方法。展示和讨论你的代码,不要有情感上的依恋。你不是你的代码。 +- 这是通往完美的渐进旅程。 + +承担一切可能的风险,最好的风险是那些可怕的风险,但是在尝试时你会比想象中的更加活跃。最好不要刻意去预测某个特定的结果,因为如果你过于重视某个结果,就会经常错过真正的可能性。应该“让我们做一点实验,看看会把我们带到哪里”。这些实验是我最好的冒险。 + +有些人对这个答案感到失望,然后回答“是的,这都是非常有趣和有用的。但是实际上,我应该学习什么? C++ 还是 Java ?”,以防这些问题,我将在这里重复一遍:我知道似乎所有的 1 和 0 都应该使一切具有确定性因此此类问题应该有一个简单的答案,但事实并非如此。这与做出选择并完成选择无关,这是有关持续学习和有时需要大胆的选择。相信我,这样你的生活会更加令人兴奋。 ### 延伸阅读 * [Teach Yourself Programming In Ten Years](http://norvig.com/21-days.html), by Peter Norvig. From 536797a58cc9ce0071759079e27959e0119cf5b3 Mon Sep 17 00:00:00 2001 From: xiangflight Date: Mon, 9 Dec 2019 09:39:00 +0800 Subject: [PATCH 131/371] =?UTF-8?q?revision[21]=20=E6=B3=9B=E5=9E=8B?= =?UTF-8?q?=E6=95=B0=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 3 +- docs/book/21-Arrays.md | 71 +++++++++++++++++++--------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 2c089a3b..2c02e10a 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -5170,7 +5170,7 @@ Teller 4 serves Customer 12 泛型的入门文档是 《Generics in the Java Programming Language》,作者是 Gilad Bracha,可以从 http://java.oracle.com 获取。 -Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html。 +Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html 获取。 你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。 @@ -5182,6 +5182,7 @@ Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到 [^4]: 注意,一些编程环境,如 Eclipse 和 IntelliJ IDEA,将会自动生成委托代码。 [^5]: 因为可以使用转型,有效地禁止了类型系统,一些人就认为 C++ 是弱类型,但这太极端了。一种可能更好的说法是 C++ 是有一道暗门的强类型语言。 [^6]: 我再次从 Brian Goetz 那获得帮助。 +
\ No newline at end of file diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 74c5e677..bda4af74 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -1,31 +1,31 @@ [TOC] + # 第二十一章 数组 -> 在[初始化和清理](/docs/book/06-Housekeeping.md)一章的最后,你已经学过如何定义和初始化一个数组。 +> 在 [初始化和清理](book/06-Housekeeping.md) 一章的最后,你已经学过如何定义和初始化一个数组。 简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 **集合** (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。 -**注意:** 随着Java Collection 和 Stream 类中高级功能的不断增加,日常编程中使用数组的需求也在变少,所以你暂且可以放心的略读甚至跳过这一章。但是,即使你自己避免使用数组,也总会有需要阅读别人数组代码的那一天。那时候,本章依然在这里等着你来翻阅。 - - +**注意:** 随着 Java Collection 和 Stream 类中高级功能的不断增加,日常编程中使用数组的需求也在变少,所以你暂且可以放心地略读甚至跳过这一章。但是,即使你自己避免使用数组,也总会有需要阅读别人数组代码的那一天。那时候,本章依然在这里等着你来翻阅。 + ## 数组特性 明明还有很多其他的办法来保存对象,那么是什么令数组如此特别? -将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据的能力。在Java中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。 +将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。 -速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](/docs/book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个**ArrayList**的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。 +速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个 **ArrayList** 的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。 数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 **RuntimeException** 的异常提醒,这表明你的程序中存在错误。 -在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 **Object** ,也就Java中所有类的基类。而数组是优于 **预泛型** (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。 +在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 **Object**,也就是 Java 中所有类的基类。而数组是优于 **预泛型** (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。 当然,不管在编译时还是运行时,Java都会阻止你犯向对象发送不正确消息的错误。然而不管怎样,使用数组都不会有更大的风险。比较好的地方在于,如果编译器报错,最终的用户更容易理解抛出异常的含义。 @@ -91,12 +91,12 @@ Sphere 9 */ ``` -**Suppliers.create()** 方法在[泛型](/docs/book/20-Generics.md)中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用[ ] 来随机存取元素,而一个List 使用诸如add()和get()等方法。数组和ArrayList之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](/docs/book/12-Collections.md)中看到的,集合的功能明显多于数组。随着Java自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。 +**Suppliers.create()** 方法在[泛型](book/20-Generics.md)一章中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用 **[ ]** 来随机存取元素,而一个 **List** 使用诸如 `add()` 和 `get()` 等方法。数组和 **ArrayList** 之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](book/12-Collections.md)中看到的,集合的功能明显多于数组。随着 Java 自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。 ### 用于显示数组的实用程序 -在本章中,我们处处都要显示数组。Java提供了 **array.toString()** 来将数组转换为可读字符串,然后可以在控制台上显示。然而这种方式视觉上噪音太大,所以我们创建一个小的库来完成这项工作。 +在本章中,我们处处都要显示数组。Java 提供了 **Arrays.toString()** 来将数组转换为可读字符串,然后可以在控制台上显示。然而这种方式视觉上噪音太大,所以我们创建一个小的库来完成这项工作。 ```java // onjava/ArrayShow.java @@ -172,20 +172,19 @@ public interface ArrayShow { show(a); } } - ``` 第一个方法适用于对象数组,包括那些包装基本数据类型的数组。所有的方法重载对于不同的数据类型是必要的。 第二组重载方法可以让你显示带有信息 **字符串** 前缀的数组。 -为了简单起见,你通常可以静态的导入它们。 - +为了简单起见,你通常可以静态地导入它们。 + ## 一等对象 -不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 **new** 关键字。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 **length** 成员函数,它能告诉你数组对象中可以存储多少元素。**[ ]** 语法是你访问数组对象的唯一方式。 +不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 **new** 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 **length** 成员函数,它能告诉你数组对象中可以存储多少元素。**[ ]** 语法是你访问数组对象的唯一方式。 下面的例子总结了初始化数组的多种方式,并且展示了如何给不同的数组对象分配数组引用。同时也可以看出对象数组和基元数组在使用上是完全相同的。唯一的不同之处就是对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。 @@ -266,22 +265,19 @@ h.length = 3 e.length = 3 e.length = 2 */ - ``` +数组 **a** 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。数组 **b** 被初始化成一系列指向 **BerylliumSphere** 对象的引用,但是并没有真正的 **BerylliumSphere** 对象被存储在数组中。尽管你仍然可以获得这个数组的大小,因为 **b** 指向合法对象。这带来了一个小问题:你无法找出到底有多少元素存储在数组中,因为 **length** 只能告诉你数组可以存储多少元素;这就是说,数组对象的大小并不是真正存储在数组中对象的个数。然而,当你创建一个数组对象,其引用将自动初始化为 **null**,因此你可以通过检查特定数组元素中的引用是否为 **null** 来判断其中是否有对象。基元数组也有类似的机制,比如自动将数值类型初始化为 **0**,char 型初始化为 **(char)0**,布尔类型初始化为 **false**。 +数组 **c** 展示了创建数组对象后给数组中各元素分配 **BerylliumSphere** 对象。数组 **d** 展示了创建数组对象的聚合初始化语法(隐式地使用 **new** 在堆中创建对象,就像 **c** 一样)并且初始化成 **BeryliumSphere** 对象,这一切都在一条语句中完成。 -数组 **a** 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。数组 **b** 被初始化成一系列指向 **BerylliumSphere** 对象的引用,但是并没有真正的 **BerylliumSphere** 对象被存储在数组中。尽管你仍然可以获得这个数组的大小,因为 **b** 指向合法对象。这带来了一个小问题:你无法找出到底有多少元素存储在数组中,因为 **length** 只能告诉你数组可以存储多少元素;这就是说,数组对象的大小并不是真正存储在数组中对象的个数。然而,当你创建一个对象数组,其引用将自动初始化为 **null** ,因此你可以通过检查特定数组元素中的引用是否为 **null** 来判断其中是否有对象。基元数组也有类似的机制,比如自动将数值类型初始化为 **0** ,char型初始化为 **(char)0** ,布尔类型初始化为 **false** 。 - -在给数组中各元素分配 **BerylliumSphere** 对象后,数组 **c** 展示数组对象的创建。数组 **d** 展示了创建数组对象的聚合初始化语法(隐式地使用new在堆中创建对象,就像 **c** 一样)并且初始化成 **BeryliumSphere** 对象,这一切都在一条语句中完成。 - -下一个数组初始化可以被看做是一个“动态聚合初始化”。 **d** 使用的聚合初始化必须在 **d** 定义的点使用,但是使用第二种语法,你可以在任何地方创建和初始化数组对象。例如,假设 **hide()** 是一个需要使用一系列的 **BeryliumSphere**对象。你可以这样调用它: +下一个数组初始化可以被看做是一个“动态聚合初始化”。 **d** 使用的聚合初始化必须在 **d** 定义处使用,但是使用第二种语法,你可以在任何地方创建和初始化数组对象。例如,假设 **hide()** 是一个需要使用一系列的 **BeryliumSphere**对象。你可以这样调用它: ```Java hide(d); ``` -你也可以动态的创建你用作参数传递的数组: +你也可以动态地创建你用作参数传递的数组: ```Java hide(new BerylliumSphere[]{ @@ -302,13 +298,13 @@ a = d; **ArrayOptions.java** 的第二部分展示了基元数组的语法就像对象数组一样,除了基元数组直接保存基本数据类型的值。 - + ## 返回数组 -假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对C++/C这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。 +假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对 C++/C 这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。 -而在Java中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。 +而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。 下面,我们返回一个 **字符串** 数组: @@ -357,19 +353,18 @@ public class IceCreamFlavors { */ ``` +**flavorset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后从数组 **FLAVORS** 中随机选择 flavors 并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者是在其他什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。 +如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/20-Generics.md) 中介绍的 **元组** 。 -**flaverset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后选择从数组 **FLAVORS** 中随机选择flavors并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何的对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者在其他的什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。 +注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项没被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串 -如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](/docs/book/20-Generics.md) 中介绍的 **元组** 。 +比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flavorSet()** 每一次都是按照随机顺序选择 flavors。 -注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串 - -比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flvorSet()** 每一次都是按照随机顺序选择 flavors。 - -直到书中的这个点,随机数通过 **java.util.Random** 类生成的,这个类从Java 1.0就有,甚至被更新以提供Java 8 流。现在我们可以介绍Java 8中的 **SplittableRandom** ,它不只是以线性操作工作(你最终会学到),还提供了一个高质量的随机数。我们将在这本书的后面部分使用 **SplittableRandom** 。 +一直到现在,随机数都是通过 **java.util.Random** 类生成的,这个类从 Java 1.0 就有,甚至更新过以提供 Java 8 流。现在我们可以介绍 Java 8 中的 **SplittableRandom** ,它不仅能在并行操作使用(你最终会学到),而且提供了一个高质量的随机数。这本书的剩余部分都使用 **SplittableRandom** 。 + ## 多维数组 要创建多维的基元数组,你要用大括号来界定数组中的向量: @@ -394,9 +389,9 @@ public class MultidimensionalPrimitiveArray { 每个嵌套的大括号都代表了数组的一个维度。 -这个例子使用 **array.deepToString()** 方法,这将多维数组转换成 **String** 类型,就像在输出中显示的那样。 +这个例子使用 **Arrays.deepToString()** 方法,将多维数组转换成 **String** 类型,就像输出中显示的那样。 -你也可以使用 **new** 分配数组。 这是一个使用 **new** 表达式分配的三维数组: +你也可以使用 **new** 分配数组。这是一个使用 **new** 表达式分配的三维数组: ```Java // arrays/ThreeDWithNew.java @@ -445,9 +440,9 @@ public class RaggedArray { */ ``` -第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个在for循环中的 **new** 给数组填充了元素,第三个 **new** 为数组的最后一个索引填充元素。 +第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个 **new** 在 for 循环中给数组填充了第二个元素,第三个 **new** 为数组的最后一个索引填充元素。 -* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数接口 **IntunaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的lambda表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。 +* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数式接口 **IntUnaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的 lambda 表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。 非基元的对象数组也可以定义为不规则数组。这里,我们收集了许多使用大括号的 **new** 表达式: @@ -524,9 +519,9 @@ public class AssemblingMultidimensionalArrays { */ ``` -**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。 +**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。 -**Arrays.deepToString()** 方法同时适用于基元数组和对象数组: +**Arrays.deepToString()** 方法同时适用于基元数组和对象数组: ```JAVA // arrays/MultiDimWrapperArray.java @@ -569,7 +564,7 @@ Lazy, Brown, Dog, &, friend]] */ ``` -同样的,在 **Integer** 和 **Double** 数组中,自动装箱为可为你创建包装器对象。 +同样的,在 **Integer** 和 **Double** 数组中,自动装箱可为你创建包装器对象。 ## 泛型数组 @@ -2204,7 +2199,7 @@ a1w == a2w: false gmeinne], [btpenpc, #$#$#$#, gmeinne, eloztdv]] deepEquals(md1, md2): false */ - ``` +``` 最初,a1和a2是完全相等的,所以输出是true,但是之后其中一个元素改变了,这使得结果为false。a1w和a2w是对一个封装类型数组重复该练习。 From 47fbbfbbd3085311552deb26f11c6e4df86f4f5a Mon Sep 17 00:00:00 2001 From: buqieryu <2680643943@qq.com> Date: Wed, 18 Dec 2019 11:28:23 +0800 Subject: [PATCH 132/371] Update 11-Inner-Classes.md --- docs/book/11-Inner-Classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 680e924d..fbd857be 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -225,7 +225,7 @@ public class DotNew { } ``` -要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 **DotNew**,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Innero。 +要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 **DotNew**,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Inner。 下面你可以看到将 **.new** 应用于 Parcel 的示例: From 775ac4449b32178f226c4f0d6b524baa63b648b1 Mon Sep 17 00:00:00 2001 From: zhangzw Date: Wed, 18 Dec 2019 15:49:22 +0800 Subject: [PATCH 133/371] =?UTF-8?q?update:=20=E8=A1=A5=E5=85=85=E7=BC=BA?= =?UTF-8?q?=E5=B0=91=E7=9A=84=E5=88=86=E5=8F=B7=E5=8F=8A=E5=85=B6=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=A9=BA=E8=A1=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index d129610e..27e1220a 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -415,7 +415,7 @@ public class Summing2 { Summing.timeTest("Basic Sum", CHECK, () -> basicSum(la));// Destructive summation: Summing.timeTest("parallelPrefix", CHECK, () -> { - Arrays.parallelPrefix(la, Long::sum) + Arrays.parallelPrefix(la, Long::sum); return la[la.length - 1]; }); } @@ -543,7 +543,7 @@ public class CollectionIntoStream { String result = strings.stream() .map(String::toUpperCase) .map(s -> s.substring(2)) - .reduce(":", (s1, s2) -> s1 + s2) + .reduce(":", (s1, s2) -> s1 + s2); System.out.println(result); } } @@ -777,7 +777,7 @@ public class Nap { 要执行任务,我们将从最简单的方法--SingleThreadExecutor开始: ```java -/ concurrent/SingleThreadExecutor.java +//concurrent/SingleThreadExecutor.java import java.util.concurrent.*; import java.util.stream.*; import onjava.*; @@ -1448,7 +1448,7 @@ public class CompletableApplyAsync { .thenApplyAsync(Machina::work); System.out.println(timer.duration()); System.out.println(cf.join()); - System.out.println(timer.duration()) + System.out.println(timer.duration()); } } /* Output: @@ -1589,9 +1589,7 @@ main()包含一系列可由其int值引用的测试。cfi(1)演示了showr()正 ## 本章小结 [^1]:例如,Eric-Raymond在“VIIX编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 - [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 - [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 [^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 From d436d089e42c7d447ec88417278b748560f1ae5f Mon Sep 17 00:00:00 2001 From: zhangzw Date: Tue, 24 Dec 2019 09:25:20 +0800 Subject: [PATCH 134/371] =?UTF-8?q?CompletableFutures=20=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E6=89=80=E6=9C=89=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 540 ++++++++++++++++++++++++- 1 file changed, 532 insertions(+), 8 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 27e1220a..e35e2c09 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -1366,7 +1366,7 @@ public class CompletedMachina { 通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。 -当我们将Machina包装在CompletableFuture中时,我们发现我们可以在CompletableFuture上添加操作来处理所包含的对象,事情变得更加有趣: +当我们将**handle()**包装在**CompletableFuture**中时,我们发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,事情变得更加有趣: ```java // concurrent/CompletableApply.java @@ -1397,7 +1397,7 @@ Machina0: complete **thenApply()**应用一个接受输入并产生输出的函数。在这种情况下,**work()**函数产生与它相同的类型,因此每个得到的**CompletableFuture**仍然被输入为**Machina**,但是(类似于**Streams**中的**map()**)**Function**也可以返回不同的类型,这将反映在返回类型 -您可以在此处看到有关CompletableFutures的重要信息:它们会在您执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。 +您可以在此处看到有关**CompletableFutures**的重要信息:它们会在您执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。 我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样: @@ -1464,14 +1464,14 @@ Machina0: complete 同步调用(我们通常使用得那种)意味着“当你完成工作时,返回”,而异步调用以意味着“立刻返回但是继续后台工作。”正如你所看到的,**cf**的创建现在发生得跟快。每次调用 **thenApplyAsync()** 都会立刻返回,因此可以进行下一次调用,整个链接序列的完成速度比以前快得快。 -事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了main()进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。 +事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了**main()**进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。 这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将您需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。 -幸运的是,您需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,CompletableFuture为您管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 +幸运的是,您需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,**CompletableFuture**为您管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 - 其他操作 -当您查看CompletableFuture的Javadoc时,您会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有thenApply(),thenApplyAsync()和thenApplyAsync()的第二种形式,它接受运行任务的Executor(在本书中我们忽略了Executor选项)。 +当您查看**CompletableFuture**的Javadoc时,您会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有**thenApply()**,**thenApplyAsync()**和**thenApplyAsync()**的第二种形式,它接受运行任务的**Executor**(在本书中我们忽略了**Executor**选项)。 这是一个显示所有“基本”操作的示例,它们不涉及组合两个CompletableFutures或异常(我们将在稍后查看)。首先,我们将重复使用两个实用程序以提供简洁和方便: @@ -1500,9 +1500,9 @@ public class CompletableUtilities { } ``` -showr()在CompletableFuture 上调用get()并显示结果,捕获两个可能的异常。voidr()是CompletableFuture 的showr()版本,即CompletableFutures,仅在任务完成或失败时显示。 +**showr()**在**CompletableFuture **上调用**get()**并显示结果,捕获两个可能的异常。**voidr()**是**CompletableFuture **的**showr()**版本,即**CompletableFutures**,仅在任务完成或失败时显示。 -为简单起见,以下CompletableFutures只包装整数。cfi()是一个方便的方法,它在完成的CompletableFuture 中包装一个int: +为简单起见,以下**CompletableFutures**只包装整数。**cfi()**是一个方便的方法,它在完成的**CompletableFuture **中包装一个**int**: ```java // concurrent/CompletableOperations.java @@ -1571,7 +1571,531 @@ dependents: 2 */ ``` -main()包含一系列可由其int值引用的测试。cfi(1)演示了showr()正常工作。cfi(2)是调用runAsync()的示例。由于Runnable不产生返回值,因此结果是CompletableFuture ,因此使用voidr()。 +**main()**包含一系列可由其**int**值引用的测试。**cfi(1)**演示了**showr()**正常工作。**cfi(2)**是调用**runAsync()**的示例。由于**Runnable**不产生返回值,因此结果是**CompletableFuture **,因此使用**voidr()**。 +注意使用**cfi(3)**,**thenRunAsync()**似乎与**runAsync()**一致,差异显示在后续的测试中: +**runAsync()**是一个静态方法,所以你不会像**cfi(2)**一样调用它。相反你可以在**QuittingCompletable.java**中使用它。后续测试中**supplyAsync()**也是静态方法,但是需要一个**Supplier**而不是**Runnable**并产生一个**CompletableFuture**来代替**CompletableFuture**。 +含有“then”的方法将进一步的操作应用于现有的**CompletableFuture **。与**thenRunAsync()**不同的是,将**cfi(4)**,**cfi(5)**和**cfi(6)**的“ then”方法作为未包装的**Integer**的参数。如您通过使用**voidr()**所见,然后**AcceptAsync()**接受了一个**Consumer**,因此不会产生结果。**thenApplyAsync()**接受一个**Function**并因此产生一个结果(该结果的类型可以不同于其参数)。**thenComposeAsync()**与**thenApplyAsync()**非常相似,不同之处在于其Function必须产生已经包装在**CompletableFuture**中的结果。 +**cfi(7)**示例演示了**obtrudeValue()**,它强制将值作为结果。**cfi(8)**使用**toCompletableFuture()**从**CompletionStage**生成**CompletableFuture**。**c.complete(9)**显示了如何通过给它一个结果来完成一个任务(**future**)(与**obtrudeValue()**相对,后者可能会迫使其结果替换该结果)。 +如果你调用**CompletableFuture**中的**cancel()**方法,它也会完成并且是非常好的完成。 +如果任务(**future**)未完成,则**getNow()**方法返回**CompletableFuture**的完成值,或者返回**getNow()**的替换参数。 +最后,我们看一下依赖(dependents)的概念。如果我们将两个**thenApplyAsync()**调用链接到**CompletableFuture**上,则依赖项的数量仍为1。但是,如果我们将另一个**thenApplyAsync()**直接附加到**c**,则现在有两个依赖项:两个链和另一个链。这表明您可以拥有一个**CompletionStage**,当它完成时,可以根据其结果派生多个新任务。 + +### 结合CompletableFutures + +第二类**CompletableFuture**方法采用两个**CompletableFuture**并以各种方式将它们组合在一起。一个**CompletableFuture**通常会先于另一个完成,就好像两者都在比赛中一样。这些方法使您可以以不同的方式处理结果。 +为了对此进行测试,我们将创建一个任务,该任务将完成的时间作为其参数之一,因此我们可以控制。 +**CompletableFuture**首先完成: + +```java +// concurrent/Workable.java +import java.util.concurrent.*; +import onjava.Nap; +public class Workable { + String id; + final double duration; + public Workable(String id, double duration) { + this.id = id; + this.duration = duration; + } + @Override + public String toString() { + return "Workable[" + id + "]"; + } + public static Workable work(Workable tt) { + new Nap(tt.duration); // Seconds + tt.id = tt.id + "W"; + System.out.println(tt); + return tt; + } + public static CompletableFuture make(String id, double duration) { + return CompletableFuture.completedFuture( new Workable(id, duration)) .thenApplyAsync(Workable::work); + } +} +``` + +在**make()**中,**work()**方法应用于**CompletableFuture.work()**需要持续时间才能完成,然后将字母W附加到id上以指示工作已完成。 +现在,我们可以创建多个竞争的**CompletableFuture**,并使用**CompletableFuture**库: + +```java +// concurrent/DualCompletableOperations.java +import java.util.concurrent.*; +import static onjava.CompletableUtilities.*; +public class DualCompletableOperations { + static CompletableFuture cfA, cfB; + static void init() { + cfA = Workable.make("A", 0.15); + cfB = Workable.make("B", 0.10);// Always wins + } + static void join() { + cfA.join(); + cfB.join(); + System.out.println("*****************"); + } + public static void main(String[] args) { + init(); + voidr(cfA.runAfterEitherAsync(cfB, () -> System.out.println("runAfterEither"))); + join(); + init(); + voidr(cfA.runAfterBothAsync(cfB, () -> System.out.println("runAfterBoth"))); + join(); + init(); + showr(cfA.applyToEitherAsync(cfB, w -> { + System.out.println("applyToEither: " + w); + return w; + })); + join(); + init(); + voidr(cfA.acceptEitherAsync(cfB, w -> { + System.out.println("acceptEither: " + w); + })); + join(); + init(); + voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> { System.out.println("thenAcceptBoth: " + w1 + ", " + w2); + })); + join(); + init(); + showr(cfA.thenCombineAsync(cfB, (w1, w2) -> { + System.out.println("thenCombine: " + w1 + ", " + w2); + return w1; + })); + join(); + init(); + CompletableFuture + cfC = Workable.make("C", 0.08), + cfD = Workable.make("D", 0.09); + CompletableFuture.anyOf(cfA, cfB, cfC, cfD) + .thenRunAsync(() -> System.out.println("anyOf")); + join(); + init(); + cfC = Workable.make("C", 0.08); + cfD = Workable.make("D", 0.09); + CompletableFuture.allOf(cfA, cfB, cfC, cfD) + .thenRunAsync(() -> System.out.println("allOf")); + join(); + } +} +/* Output: +Workable[BW] +runAfterEither +Workable[AW] +***************** +Workable[BW] +Workable[AW] +runAfterBoth +***************** +Workable[BW] +applyToEither: Workable[BW] +Workable[BW] +Workable[AW] +***************** +Workable[BW] +acceptEither: Workable[BW] +Workable[AW] +***************** +Workable[BW] +Workable[AW] +thenAcceptBoth: Workable[AW], Workable[BW] +**************** + Workable[BW] + Workable[AW] + thenCombine: Workable[AW], Workable[BW] + Workable[AW] + ***************** + Workable[CW] + anyOf + Workable[DW] + Workable[BW] + Workable[AW] + ***************** + Workable[CW] + Workable[DW] + Workable[BW] + Workable[AW] + ***************** + allOf + */ +``` + +为了便于访问,**cfA**和**cfB**是静态的。**init()**总是使用较短的延迟(因此总是“获胜”)使用“ B”初始化两者。**join()**是在这两种方法上调用**join()**并显示边框的另一种便捷方法。 +所有这些“双重”方法都以一个**CompletableFuture**作为调用该方法的对象,第二个**CompletableFuture**作为第一个参数,然后是要执行的操作。 +通过使用**Shower()**和**void()**,您可以看到“运行”和“接受”是终端操作,而“应用”和“组合”产生了新的承载载荷的**CompletableFutures**。 + +方法的名称是不言自明的,您可以通过查看输出来验证这一点。一个特别有趣的方法是CombineAsync(),它等待两个**CompletableFuture**完成,然后将它们都交给BiFunction,然后BiFunction可以将结果加入到所得**CompletableFuture**的有效负载中。 + +### 模拟 + +作为一个示例,说明如何使用**CompletableFutures**将一系列操作组合在一起,让我们模拟制作蛋糕的过程。在第一个阶段中,我们准备并将成分混合成面糊: + +```java +// concurrent/Batter.java +import java.util.concurrent.*; +import onjava.Nap; +public class Batter { + static class Eggs {} + static class Milk {} + static class Sugar {} + static class Flour {} + static T prepare(T ingredient) { + new Nap(0.1); + return ingredient; + } + static CompletableFuture prep(T ingredient) { + return CompletableFuture + .completedFuture(ingredient) + .thenApplyAsync(Batter::prepare); + } + public static CompletableFuture mix() { + CompletableFuture eggs = prep(new Eggs()); CompletableFuture milk = prep(new Milk()); CompletableFuture sugar = prep(new Sugar()); CompletableFuture flour = prep(new Flour()); CompletableFuture.allOf(eggs, milk, sugar, flour) + .join(); + new Nap(0.1); // Mixing time + return CompletableFuture.completedFuture(new Batter()); + } +} + +``` + +每种成分都需要一些时间来准备。**allOf()**等待所有配料准备就绪,然后需要更多时间将其混合到面糊中。 + +接下来,我们将单批面糊放入四个锅中进行烘烤。产品作为**CompletableFutures**流返回: + +```java +// concurrent/Baked.java +import java.util.concurrent.*; +import java.util.stream.*; +import onjava.Nap; +public class Baked { + static class Pan {} + static Pan pan(Batter b) { + new Nap(0.1); + return new Pan(); + } + static Baked heat(Pan p) { + new Nap(0.1); + return new Baked(); + } + static CompletableFuture bake(CompletableFuture cfb){ + return cfb.thenApplyAsync(Baked::pan) + .thenApplyAsync(Baked::heat); + } + public static Stream> batch() { + CompletableFuture batter = Batter.mix(); + return Stream.of(bake(batter), bake(batter), bake(batter), bake(batter)); + } +} +``` + +最后,我们创建了一批糖,并用它对蛋糕进行糖化: + +```java +// concurrent/FrostedCake.java +import java.util.concurrent.*; +import java.util.stream.*; +import onjava.Nap; +final class Frosting { + private Frosting() {} + static CompletableFuture make() { + new Nap(0.1); + return CompletableFuture.completedFuture(new Frosting()); + } +} +public class FrostedCake { + public FrostedCake(Baked baked, Frosting frosting) { + new Nap(0.1); + } + @Override + public String toString() { + return "FrostedCake"; + } + public static void main(String[] args) { + Baked.batch() + .forEach(baked -> baked.thenCombineAsync(Frosting.make(), (cake, frosting) -> new FrostedCake(cake, frosting)) .thenAcceptAsync(System.out::println) + .join()); + } +} +``` + +一旦您对背后的想法感到满意。**CompletableFutures**它们相对易于使用。 + +### 例外情况 + +与**CompletableFutur**e在处理链中包装对象的方式相同,它还可以缓冲异常。这些不会在处理过程中显示给调用者,而只会在您尝试提取结果时显示。为了展示它们是如何工作的,我们将从创建一个在某些情况下引发异常的类开始: + +```java +// concurrent/Breakable.java +import java.util.concurrent.*; +public class Breakable { + String id; + private int failcount; + public Breakable(String id, int failcount) { + this.id = id; + this.failcount = failcount; + } + @Override + public String toString() { + return "Breakable_" + id + " [" + failcount + "]"; + } + public static Breakable work(Breakable b) { + if(--b.failcount == 0) { + System.out.println( "Throwing Exception for " + b.id + ""); + throw new RuntimeException( "Breakable_" + b.id + " failed"); + } + System.out.println(b); + return b; + } +} + +``` + +**failcount**为正时,每次将对象传递给**work()**方法可减少**failcount**。当它为零时,**work()**会引发异常。如果您给它的**failcount**为零,则它永远不会引发异常。 +请注意,它报告在抛出异常时抛出异常。 +在下面的**test()**方法中,**work()**多次应用于**Breakable**,因此,如果**failcount**在范围内,则会引发异常。但是,在测试**A**到**E**中,您可以从输出中看到抛出了异常,但是它们从未出现: + +```java +// concurrent/CompletableExceptions.java +import java.util.concurrent.*; +public class CompletableExceptions { + static CompletableFuture test(String id, int failcount) { + return + CompletableFuture.completedFuture( + new Breakable(id, failcount)) + .thenApply(Breakable::work) + .thenApply(Breakable::work) + .thenApply(Breakable::work) + .thenApply(Breakable::work); + } + public static void main(String[] args) { + // Exceptions don't appear ... + test("A", 1); + test("B", 2); + test("C", 3); + test("D", 4); + test("E", 5); + // ... until you try to fetch the value: + try { + test("F", 2).get(); // or join() + } catch(Exception e) { + System.out.println(e.getMessage()); + } + // Test for exceptions: + System.out.println( + test("G", 2).isCompletedExceptionally()); + // Counts as "done": + System.out.println(test("H", 2).isDone()); + // Force an exception: + CompletableFuture cfi = + new CompletableFuture<>(); + System.out.println("done? " + cfi.isDone()); + cfi.completeExceptionally( new RuntimeException("forced")); + try { + cfi.get(); + } catch(Exception e) { + System.out.println(e.getMessage()); + } + } +} + +/* Output: +Throwing Exception for A +Breakable_B [1] +Throwing Exception for B +Breakable_C [2] +Breakable_C [1] +Throwing Exception for C +Breakable_D [3] +Breakable_D [2] +Breakable_D [1] +Throwing Exception for D +Breakable_E [4] +Breakable_E [3] +Breakable_E [2] +Breakable_E [1] +Breakable_F [1] +Throwing Exception for F +java.lang.RuntimeException: Breakable_F failed +Breakable_G [1] +Throwing Exception for G +true +Breakable_H [1] +Throwing Exception for H +true +done? false +java.lang.RuntimeException: forced +*/ +``` + +测试**A**到**E**运行到抛出异常的地步,然后……什么都没有。只有在测试**F**中调用**get()**时,我们才能看到抛出的异常。 +测试**G**显示,您可以首先检查在处理过程中是否引发了异常,而没有引发该异常。但是,测试H告诉我们,无论异常成功与否,异常仍然可以被视为“完成” +代码的最后一部分显示了如何在**CompletableFuture**中插入异常,而不管是否存在任何故障。 +加入或获取结果时,我们不会使用粗略的try-catch,而是使用**CompletableFuture**提供的更复杂的机制来自动响应异常。您可以使用与所有**CompletableFuture**相同的表格来执行此操作:在链中插入**CompletableFuture**调用。有三个选项:**exclusively(**),**handle()**和**whenComplete()**: + +```java +// concurrent/CatchCompletableExceptions.java +import java.util.concurrent.*; +public class CatchCompletableExceptions { + static void handleException(int failcount) { + // Call the Function only if there's an + // exception, must produce same type as came in: + CompletableExceptions + .test("exceptionally", failcount) + .exceptionally((ex) -> { // Function + if(ex == null) + System.out.println("I don't get it yet"); + return new Breakable(ex.getMessage(), 0); + }) + .thenAccept(str -> + System.out.println("result: " + str)); + // Create a new result (recover): + CompletableExceptions + .test("handle", failcount) + .handle((result, fail) -> { // BiFunction + if(fail != null) + return "Failure recovery object"; + else + return result + " is good"; }) + .thenAccept(str -> + System.out.println("result: " + str)); + // Do something but pass the same result through: + CompletableExceptions + .test("whenComplete", failcount) + .whenComplete((result, fail) -> {// BiConsumer + if(fail != null) + System.out.println("It failed"); + else + System.out.println(result + " OK"); + }) + .thenAccept(r -> + System.out.println("result: " + r)); + } + public static void main(String[] args) { + System.out.println("**** Failure Mode ****"); + handleException(2); + System.out.println("**** Success Mode ****"); + handleException(0); + } +} +/* Output: +**** Failure Mode **** +Breakable_exceptionally [1] +Throwing Exception for exceptionally +result: Breakable_java.lang.RuntimeException: +Breakable_exceptionally failed [0] +Breakable_handle [1] +Throwing Exception for handle +result: Failure recovery object +Breakable_whenComplete [1] +Throwing Exception for whenComplete +It failed +**** Success Mode **** +Breakable_exceptionally [-1] +Breakable_exceptionally [-2] +Breakable_exceptionally [-3] +Breakable_exceptionally [-4] +result: Breakable_exceptionally [-4] +Breakable_handle [-1] +Breakable_handle [-2] +Breakable_handle [-3] +Breakable_handle [-4] +result: Breakable_handle [-4] is good +Breakable_whenComplete [-1] +Breakable_whenComplete [-2] +Breakable_whenComplete [-3] +Breakable_whenComplete [-4] +Breakable_whenComplete [-4] OK +result: Breakable_whenComplete [-4] +*/ +``` + +只有在有异常的情况下,**exclusively()**参数才会运行。**Exclusively()**的局限性在于,该函数只能返回输入的相同类型的值。**exclusively()**通过将一个好的对象重新插入流中而恢复到可行状态。 +**handle()**始终被调用,您必须检查一下**fail**是否为**true**才能查看是否发生了异常。但是**handle()**可以产生任何新类型,因此它使您可以执行处理,而不仅可以像**exception()**那样进行恢复。 +**whenComplete()**就像**handle()**一样,您必须测试是否失败,但是该参数是使用者,并且不会修改正在传递的结果对象。 + +### 流异常 + +通过修改**CompletableExceptions.java**,看看**CompletableFuture**异常与**Streams**异常有何不同: + +```java +// concurrent/StreamExceptions.java +import java.util.concurrent.*; +import java.util.stream.*; +public class StreamExceptions { + static Stream test(String id, int failcount) { + return Stream.of(new Breakable(id, failcount)). + map(Breakable::work) + .map(Breakable::work + .map(Breakable::work) + .map(Breakable::work); + } + public static void main(String[] args) { + // No operations are even applied ... + test("A", 1); + test("B", 2); + Stream c = test("C", 3); + test("D", 4); + test("E", 5); + // ... until there's a terminal operation: + System.out.println("Entering try"); + try { + c.forEach(System.out::println);// [1] + } catch(Exception e) { + System.out.println(e.getMessage()); + } + } +} +/* Output: +Entering try +Breakable_C [2] +Breakable_C [1] +Throwing Exception for C +Breakable_C failed +*/ +``` + +使用**CompletableFutures**,我们看到了测试**A**到**E**的进展,但是使用**Streams**,直到您应用了终端操作(如[1]的**forEach()**),一切都没有开始。**CompletableFuture**执行工作并捕获任何异常以供以后检索。比较这两者并不是一件容易的事,因为**Stream**没有终端操作根本无法执行任何操作,但是**Stream**绝对不会存储其异常。 + +### 检查异常 + +CompletableFutures和并行Streams都不支持包含已检查异常的操作。相反,您必须在调用操作时处理检查到的异常,这会产生不太优雅的代码: + +```java +// concurrent/ThrowsChecked.java +import java.util.stream.*; +import java.util.concurrent.*; +public class ThrowsChecked { + class Checked extends Exception {} + static ThrowsChecked nochecked(ThrowsChecked tc) { + return tc; + } + static ThrowsChecked withchecked(ThrowsChecked tc) throws Checked { + return tc; + } + static void testStream() { + Stream.of(new ThrowsChecked()) + .map(ThrowsChecked::nochecked) + // .map(ThrowsChecked::withchecked); // [1] + .map(tc -> { + try { + return withchecked(tc); + } catch(Checked e) { + throw new RuntimeException(e); + } + }); + } + static void testCompletableFuture() { + CompletableFuture .completedFuture(new ThrowsChecked()) + .thenApply(ThrowsChecked::nochecked) + // .thenApply(ThrowsChecked::withchecked); // [2] + .thenApply(tc -> { + try { + return withchecked(tc); + } catch(Checked e) { + throw new RuntimeException(e); + } + }); + } +} +``` + +如果您尝试像对 **nochecked()** 一样对 **withchecked()** 使用方法引用,则编译器会抱怨[1]和[2]。相反,您必须写出lambda表达式(或编写一个不会引发异常的包装器方法)。 ## 死锁 From 7b9a888b5deea6c70b9c2358d0122db77f9c81e1 Mon Sep 17 00:00:00 2001 From: zzw Date: Tue, 24 Dec 2019 13:42:46 +0800 Subject: [PATCH 135/371] Update 24-Concurrent-Programming.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 死锁章节所有内容 --- docs/book/24-Concurrent-Programming.md | 125 ++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index e35e2c09..a2631f1e 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -2099,6 +2099,128 @@ public class ThrowsChecked { ## 死锁 +由于任务可能会被阻塞,因此一个任务有可能卡在等待另一个任务上,而任务又在等待另一个任务,依此类推,直到链回到第一个任务上。您会遇到一个不断循环的任务,彼此等待,没有人能动。这称为死锁[^6] +如果您尝试运行某个程序并立即陷入死锁,则可以立即查找该错误。真正的问题是,当您的程序看起来运行良好,但具有隐藏潜力死锁。在这里,您可能没有任何迹象表明可能发生死锁,因此该缺陷在您的程序中是潜在的,直到它意外发生为止(通常是对客户而言(几乎肯定很难复制))。因此,通过仔细的程序设计防止死锁是开发并发系统的关键部分。 +埃德斯·迪克斯特拉(Essger Dijkstra)发明的"哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数字)。这些哲学家将一部分时间花在思考上,一部分时间在吃饭上。他们在思考的时候并不需要任何共享资源,但是他们使用的餐具数量有限。在最初的问题描述中,器物是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子。显然,每个哲学家都需要两个筷子才能吃饭。 +引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更普遍地说,筷子的数量与哲学家相同)。它们之间围绕桌子隔开。当一个哲学家想要吃饭时,该哲学家必须拿起左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到必要的筷子可用为止。 +**StickHolder**类通过将单个筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果您调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: + +```java +// concurrent/StickHolder.java +import java.util.concurrent.*; +public class StickHolder { + private static class Chopstick {} + private Chopstick stick = new Chopstick(); + private BlockingQueue holder = new ArrayBlockingQueue<>(1); + public StickHolder() { + putDown(); + } + public void pickUp() { + try { + holder.take();// Blocks if unavailable + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + } + public void putDown() { + try { + holder.put(stick); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + } +} +``` + +为简单起见,**StickHolder**从未真正制作过**Chopstick**,而是在类中将其保密。如果调用**pickUp()**而该筷子不可用,则**pickUp()**会阻塞,直到另一位调用**putDown()**的哲学家返回了该摇杆。请注意,此类中的所有线程安全性都是通过**BlockingQueue**实现的。 + +每个哲学家都是一个任务,尝试将左右两把筷子都拿起,使其可以进食,然后使用**putDown()**释放这些筷子: + +```java +// concurrent/Philosopher.java +public class Philosopher implements Runnable { + private final int seat; + private final StickHolder left, right; + public Philosopher(int seat, StickHolder left, StickHolder right) { + this.seat = seat; + this.left = left; + this.right = right; + } + @Override + public String toString() { + return "P" + seat; + } + @Override + public void run() { + while(true) { + // System.out.println("Thinking"); + // [1] right.pickUp(); + left.pickUp(); + System.out.println(this + " eating"); + right.putDown(); + left.putDown(); + } + } +} +``` + +没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 +结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为结果语法更简洁: + +```java +// concurrent/DiningPhilosophers.java +// Hidden deadlock +// {ExcludeFromGradle} Gradle has trouble +import java.util.*; +import java.util.concurrent.*; +import onjava.Nap; +public class DiningPhilosophers { + private StickHolder[] sticks; + private Philosopher[] philosophers; + public DiningPhilosophers(int n) { + sticks = new StickHolder[n]; + Arrays.setAll(sticks, i -> new StickHolder()); + philosophers = new Philosopher[n]; + Arrays.setAll(philosophers, + i -> new Philosopher(i, sticks[i], sticks[(i + 1) % n]));// [1] + // Fix by reversing stick order for this one: + // philosophers[1] = // [2] + // new Philosopher(0, sticks[0], sticks[1]); + Arrays.stream(philosophers) + .forEach(CompletableFuture::runAsync);// [3] + } + public static void main(String[] args) { + // Returns right away: + new DiningPhilosophers(5);// [4] + // Keeps main() from exiting: + new Nap(3, "Shutdown"); + } +} +``` + +当您停止查看输出时,该程序将死锁。但是,根据您的计算机配置,您可能不会看到死锁。看来这取决于计算机上的内核数7。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为您可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为您不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 +在DiningPhilosophers构造函数中,每个哲学家都获得一个左右StickHolder的引用。除最后一个哲学家外,每个哲学家都通过以下方式初始化: +哲学家之间的下一双筷子。最后一位哲学家右手的筷子为零,因此圆桌会议完成了。那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右摇杆,将最后一个哲学家缠绕在第一个哲学家的旁边。 +现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 +要开始在[3]上运行的每个Philosopher,我调用runAsync(),这意味着DiningPhilosophers构造函数立即在[4]处返回。没有任何东西可以阻止main()完成,该程序只是退出而无济于事。Nap对象阻止main()退出,然后在三秒钟后强制退出(可能是)死锁的程序。 +在给定的配置中,哲学家几乎没有时间思考。因此,他们都在尝试吃饭时争夺筷子,而且僵局往往很快发生。您可以更改此: + +1. 通过增加[4]的值来添加更多哲学家。 +2. 在Philosopher.java中取消注释行[1]。 + +任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。您可以轻松地说服自己该程序没有死锁,即使它不是。这个例子很有趣,因为它演示了程序似乎可以正确运行,同时仍然容易出现死锁。 +为了解决该问题,我们观察到当四个同时满足条件: + +1. 互斥。任务使用的至少一种资源必须不可共享。在这里,筷子一次只能由一位哲学家使用。 +2. 至少一个任务必须拥有资源,并等待获取当前由另一任务拥有的资源。也就是说,要使僵局发生,哲学家必须握住一根筷子,等待另一根筷子。 +3. 不能抢先从任务中夺走资源。任务仅作为正常事件释放资源。我们的哲学家很有礼貌,他们不会抓住其他哲学家的筷子。 +4. 可能发生循环等待,即一个任务等待另一个任务持有的资源,而该任务又等待另一个任务持有的资源,依此类推,直到一个任务正在等待另一个任务持有的资源。第一项任务,从而使一切陷入僵局。在**DiningPhilosophers.java**中,发生循环等待是因为每个哲学家都先尝试获取右筷子,然后再获取左筷子。 + +因为必须满足所有这些条件才能导致死锁,所以您只能阻止其中一个解除死锁。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 +在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 +这只是解决问题的一种方法。您也可以通过防止其他情况之一来解决它。 +没有语言支持可以帮助防止死锁;您有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 + @@ -2117,6 +2239,7 @@ public class ThrowsChecked { [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 [^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 +[^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,您也可以使用活动锁。 -
\ No newline at end of file +
From f5bdfbcab9052145e94e0ffdf32d186264453e44 Mon Sep 17 00:00:00 2001 From: Zed Zhao Date: Tue, 31 Dec 2019 19:04:41 +0800 Subject: [PATCH 136/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E7=AC=AC?= =?UTF-8?q?=E4=B8=83=E7=AB=A0=E9=83=A8=E5=88=86=E4=B8=8D=E6=98=93=E7=90=86?= =?UTF-8?q?=E8=A7=A3=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/07-Implementation-Hiding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index e8224d61..a331bd5b 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -298,7 +298,7 @@ Java 访问权限修饰符 **public**,**protected** 和 **private** 位于定 本章之前的所有示例要么使用 **public** 访问修饰符,要么就没使用修饰符(默认访问)。默认访问权限没有关键字,通常被称为包访问权限(有时也称为 friendly)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 **private** 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。 -包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类给它们的包访问权限的成员赋予了相互访问的权限,所以你"拥有”了包内的程序代码。只有你拥有的代码才能访问你拥有的其他代码是有意义的。包访问为把类聚在一个包中提供了理由。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可以把不能访问当前包里的类的其他类排除在外。 +包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类给它们的包访问权限的成员赋予了相互访问的权限,所以你"拥有”了包内的程序代码。只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。 类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说"嗨,我是 **Bob** 的朋友!",然后想看到 **Bob** 的 **protected**,包访问权限和 **private** 成员。取得对成员的访问权的唯一方式是: From d5380153c06eef1f3b62a4dac4cbfcf727749b99 Mon Sep 17 00:00:00 2001 From: diguage Date: Fri, 3 Jan 2020 14:54:11 +0800 Subject: [PATCH 137/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E5=88=AB?= =?UTF-8?q?=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index a2631f1e..8faec42b 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -15,7 +15,7 @@ 我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能没有。很可能它会在某些条件下有效,而不是在其他条件下,你必须知道和了解这些情况以确定哪些有效。 -作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并移动它们的动量。电线具有阻力,过线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。 +作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并移动它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。 而不是单一的意识流叙事,我们在同时多条故事线进行的间谍小说里。一个间谍在一个特殊的岩石下李璐下微缩胶片,当第二个间谍来取回包裹时,它可能已经被第三个间谍带走了。但是这部特别的小说并没有把事情搞得一团糟;你可以轻松地走到尽头,永远不会弄明白什么。 From 27d5027ab4f5a9ff1173843067a902947605b345 Mon Sep 17 00:00:00 2001 From: xus Date: Fri, 3 Jan 2020 17:14:01 +0800 Subject: [PATCH 138/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=99=BE=E5=88=86?= =?UTF-8?q?=E5=8F=B7=E4=B8=BA=E8=8B=B1=E6=96=87=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index 8c949961..1cfeb312 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -588,7 +588,7 @@ public class Underscores { 3. `F`、`D` 和 `L`的前后禁止出现 `_`。 4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。 -[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\r\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。 +[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\r\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。 ### 指数计数法 From 0ad485ba48e248fd7bd5dce0000c16eef3001d6b Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 5 Jan 2020 13:01:35 +0800 Subject: [PATCH 139/371] Update 08-Reuse.md close #344 --- docs/book/08-Reuse.md | 487 +----------------------------------------- 1 file changed, 2 insertions(+), 485 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index b422e115..5c81f346 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -736,7 +736,7 @@ class Wheel { class Window { public void rollup() {} - pubilc void rolldown() {} + public void rolldown() {} } class Door { @@ -777,487 +777,4 @@ public class Car { 关键字 **protected** 就起这个作用。它表示“就类的用户而言,这是 **private** 的。但对于任何继承它的子类或在同一包中的类,它是可访问的。”(**protected** 也提供了包访问权限) -尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。然后通过 **protected** 控制类的继承者的访问权限。 - -```java -// reuse/Orc.java -// The protected keyword -class Villain { - private String name; - - protected void set(String nm) { - name = nm; - } - - Villain(String name) { - this.name = name; - } - - @Override - public String toString() { - return "I'm a Villain and my name is " + name; - } -} - -public class Orc extends Villain { - private int orcNumber; - - public Orc(String name, int orcNumber) { - super(name); - this.orcNumber = orcNumber; - } - - public void change(String name, int orcNumber) { - set(name); // Available because it's protected - this.orcNumber = orcNumber; - } - - @Override - public String toString() { - return "Orc " + orcNumber + ": " + super.toString(); - } - - public static void main(String[] args) { - Orc orc = new Orc("Limburger", 12); - System.out.println(orc); - orc.change("Bob", 19); - System.out.println(orc); - } -} -``` - -输出: - -``` -Orc 12: I'm a Villain and my name is Limburger -Orc 19: I'm a Villain and my name is Bob -``` - -`change()` 方法可以访问 `set()` 方法,因为 `set()` 方法是 **protected**。注意到,类 **Orc** 的 `toString()` 方法也使用了基类的版本。 - - - -## 向上转型 - -继承最重要的方面不是为新类提供方法。它是新类与基类的一种关系。简而言之,这种关系可以表述为“新类是已有类的一种类型”。 - -这种描述并非是解释继承的一种花哨方式,这是直接由语言支持的。例如,假设有一个基类 **Instrument** 代表音乐乐器和一个派生类 **Wind**。 因为继承保证了基类的所有方法在派生类中也是可用的,所以任意发送给该基类的消息也能发送给派生类。如果 **Instrument** 有一个 `play()` 方法,那么 **Wind** 也有该方法。这意味着你可以准确地说 **Wind** 对象也是一种类型的 **Instrument**。下面例子展示了编译器是如何支持这一概念的: - -```java -// reuse/Wind.java -// Inheritance & upcasting -class Instrument { - public void play() {} - - static void tune(Instrument i) { - // ... - i.play(); - } -} - -// Wind objects are instruments -// because they have the same interface: -public class Wind extends Instrument { - public static void main(String[] args) { - Wind flute = new Wind(); - Instrument.tune(flute); // Upcasting - } -} -``` - -`tune()` 方法接受了一个 **Instrument** 类型的引用。但是,在 **Wind** 的 `main()` 方法里,`tune()` 方法却传入了一个 **Wind** 引用。鉴于 Java 对类型检查十分严格,一个接收一种类型的方法接受了另一种类型看起来很奇怪,除非你意识到 **Wind** 对象同时也是一个 **Instrument** 对象,而且 **Instrument** 的 `tune` 方法一定会存在于 **Wind** 中。在 `tune()` 中,代码对 **Instrument** 和 所有 **Instrument** 的派生类起作用,这种把 **Wind** 引用转换为 **Instrument** 引用的行为称作*向上转型*。 - -该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是: - -![Wind 类图](../images/1561774164644.png) - -继承图中派生类转型为基类是向上的,所以通常称作*向上转型*。因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的。也就是说,派生类是基类的一个超集。它可能比基类包含更多的方法,但它必须至少具有与基类一样的方法。在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因。 - -也可以执行与向上转型相反的向下转型,但是会有问题,对于该问题会放在下一章和“类型信息”一章进行更深入的探讨。 - -### 再论组合和继承 - -在面向对象编程中,创建和使用代码最有可能的方法是将数据和方法一起打包到类中,然后使用该类的对象。也可以使用已有的类通过组合来创建新类。继承其实不太常用。因此尽管在教授 OOP 的过程中我们多次强调继承,但这并不意味着要尽可能使用它。恰恰相反,尽量少使用它,除非确实使用继承是有帮助的。一种判断使用组合还是继承的最清晰的方法是问一问自己是否需要把新类向上转型为基类。如果必须向上转型,那么继承就是必要的,但如果不需要,则要进一步考虑是否该采用继承。“多态”一章提出了一个使用向上转型的最有力的理由,但是只要记住问一问“我需要向上转型吗?”,就能在这两者中作出较好的选择。 - - - -## final关键字 - -根据上下文环境,Java 的关键字 **final** 的含义有些微的不同,但通常它指的是“这是不能被改变的”。防止改变有两个原因:设计或效率。因为这两个原因相差很远,所以有可能误用关键字 **final**。 - -以下几节讨论了可能使用 **final** 的三个地方:数据、方法和类。 - -### final 数据 - -许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如: - -1. 一个永不改变的编译时常量。 -2. 一个在运行时初始化就不会改变的值。 - -对于编译时常量这种情况,编译器可以把常量带入计算中;也就是说,可以在编译时计算,减少了一些运行时的负担。在 Java 中,这类常量必须是基本类型,而且用关键字 **final** 修饰。你必须在定义常量的时候进行赋值。 - -一个被 **static** 和 **final** 同时修饰的属性只会占用一段不能改变的存储空间。 - -当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。 - -下面例子展示了 **final** 属性的使用: - -```java -// reuse/FinalData.java -// The effect of final on fields -import java.util.*; - -class Value { - int i; // package access - - Value(int i) { - this.i = i; - } -} - -public class FinalData { - private static Random rand = new Random(47); - private String id; - - public FinalData(String id) { - this.id = id; - } - // Can be compile-time constants: - private final int valueOne = 9; - private static final int VALUE_TWO = 99; - // Typical public constant: - public static final int VALUE_THREE = 39; - // Cannot be compile-time constants: - private final int i4 = rand.nextInt(20); - static final int INT_5 = rand.nextInt(20); - private Value v1 = new Value(11); - private final Value v2 = new Value(22); - private static final Value VAL_3 = new Value(33); - // Arrays: - private final int[] a = {1, 2, 3, 4, 5, 6}; - - @Override - public String toString() { - return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5; - } - - public static void main(String[] args) { - FinalData fd1 = new FinalData("fd1"); - //- fd1.valueOne++; // Error: can't change value - fd1.v2.i++; // Object isn't constant - fd1.v1 = new Value(9); // OK -- not final - for (int i = 0; i < fd1.a.length; i++) { - fd1.a[i]++; // Object isn't constant - } - //- fd1.v2 = new Value(0); // Error: Can't - //- fd1.VAL_3 = new Value(1); // change reference - //- fd1.a = new int[3]; - System.out.println(fd1); - System.out.println("Creating new FinalData"); - FinalData fd2 = new FinalData("fd2"); - System.out.println(fd1); - System.out.println(fd2); - } -} -``` - -输出: - -``` -fd1: i4 = 15, INT_5 = 18 -Creating new FinalData -fd1: i4 = 15, INT_5 = 18 -fd2: i4 = 13, INT_5 = 18 -``` - -因为 **valueOne** 和 **VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。**VALUE_THREE** 是一种更加典型的常量定义的方式:**public** 意味着可以在包外访问,**static** 强调只有一个,**final** 说明是一个常量。 - -按照惯例,带有恒定初始值的 **final** **static** 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。(源于 C 语言中定义常量的方式。) - -我们不能因为某数据被 **final** 修饰就认为在编译时可以知道它的值。由上例中的 **i4** 和 **INT_5** 可以看出,它们在运行时才会赋值随机数。示例部分也展示了将 **final** 值定义为 **static** 和非 **static** 的区别。此区别只有当值在运行时被初始化时才会显现,因为编译器对编译时数值一视同仁。(而且编译时数值可能因优化而消失。)当运行程序时就能看到这个区别。注意到 **fd1** 和 **fd2** 的 **i4** 值不同,但 **INT_5** 的值并没有因为创建了第二个 **FinalData** 对象而改变,这是因为它是 **static** 的,在加载时已经被初始化,并不是每次创建新对象时都初始化。 - -**v1** 到 **VAL_3** 变量说明了 **final** 引用的意义。正如你在 `main()` 中所见,**v2** 是 **final** 的并不意味着你不能修改它的值。因为它是引用,所以只是说明它不能指向一个新的对象。这对于数组具有同样的意义,数组只不过是另一种引用。(我不知道有什么方法能使数组引用本身成为 **final**。)看起来,声明引用为 **final** 没有声明基本类型 **final** 有用。 - -### 空白 final - -空白 final 指的是没有初始化值的 **final** 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 **final** 属性值不同,也能保持它的不变性。 - -```java -// reuse/BlankFinal.java -// "Blank" final fields -class Poppet { - private int i; - - Poppet(int ii) { - i = ii; - } -} - -public class BlankFinal { - private final int i = 0; // Initialized final - private final int j; // Blank final - private final Poppet p; // Blank final reference - // Blank finals MUST be initialized in constructor - public BlankFinal() { - j = 1; // Initialize blank final - p = new Poppet(1); // Init blank final reference - } - - public BlankFinal(int x) { - j = x; // Initialize blank final - p = new Poppet(x); // Init blank final reference - } - - public static void main(String[] args) { - new BlankFinal(); - new BlankFinal(47); - } -} -``` - -你必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。 - -### final 参数 - -在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量: - -```java -// reuse/FinalArguments.java -// Using "final" with method arguments -class Gizmo { - public void spin() { - - } -} - -public class FinalArguments { - void with(final Gizmo g) { - //-g = new Gizmo(); // Illegal -- g is final - } - - void without(Gizmo g) { - g = new Gizmo(); // OK -- g is not final - g.spin(); - } - - //void f(final int i) { i++; } // Can't change - // You can only read from a final primitive - int g(final int i) { - return i + 1; - } - - public static void main(String[] args) { - FinalArguments bf = new FinalArguments(); - bf.without(null); - bf.with(null); - } -} -``` - -方法 `f()` 和 `g()` 展示了 **final** 基本类型参数的使用情况。你只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类。这将在”内部类“章节中详解。 - -### final 方法 - -使用 **final** 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。 - -过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。 - -在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 **final**。 - -### final 和 private - -类中所有的 **private** 方法都隐式地指定为 **final**。因为不能访问 **private** 方法,所以不能覆写它。可以给 **private** 方法添加 **final** 修饰,但是并不能给方法带来额外的含义。 - -以下情况会令人困惑,当你试图覆写一个 **private** 方法(隐式是 **final** 的)时,看上去奏效,而且编译器不会给出错误信息: - -```java -// reuse/FinalOverridingIllusion.java -// It only looks like you can override -// a private or private final method -class WithFinals { - // Identical to "private" alone: - private final void f() { - System.out.println("WithFinals.f()"); - } - // Also automatically "final": - private void g() { - System.out.println("WithFinals.g()"); - } -} - -class OverridingPrivate extends WithFinals { - private final void f() { - System.out.println("OverridingPrivate.f()"); - } - - private void g() { - System.out.println("OverridingPrivate.g()"); - } -} - -class OverridingPrivate2 extends OverridingPrivate { - public final void f() { - System.out.println("OverridingPrivate2.f()"); - } - - public void g() { - System.out.println("OverridingPrivate2.g()"); - } -} - -public class FinalOverridingIllusion { - public static void main(String[] args) { - OverridingPrivate2 op2 = new OverridingPrivate2(); - op2.f(); - op2.g(); - // You can upcast: - OverridingPrivate op = op2; - // But you can't call the methods: - //- op.f(); - //- op.g(); - // Same here: - WithFinals wf = op2; - //- wf.f(); - //- wf.g(); - } -} -``` - -输出: - -``` -OverridingPrivate2.f() -OverridingPrivate2.g() -``` - -"覆写"只发生在方法是基类的接口时。也就是说,必须能将一个对象向上转型为基类并调用相同的方法(这一点在下一章阐明)。如果一个方法是 **private** 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 **public**,**protected** 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。由于 **private** 方法无法触及且能有效隐藏,除了把它看作类中的一部分,其他任何事物都不需要考虑到它。 - -### final 类 - -当说一个类是 **final** (**final** 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。 - -```java -// reuse/Jurassic.java -// Making an entire class final -class SmallBrain {} - -final class Dinosaur { - int i = 7; - int j = 1; - SmallBrain x = new SmallBrain(); - - void f() {} -} - -//- class Further extends Dinosaur {} -// error: Cannot extend final class 'Dinosaur' -public class Jurassic { - public static void main(String[] args) { - Dinosaur n = new Dinosaur(); - n.f(); - n.i = 40; - n.j++; - } -} -``` - -**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。 - -### final 忠告 - -在设计类时将一个方法指明为 final 看上去是明智的。你可能会觉得没人会覆写那个方法。有时这是对的。 - -但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。 - -Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,而且从效率考虑(这近乎是个幻想),如果它的所有方法没有被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 - -第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。 - -Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 - - - -## 类初始化和加载 - -在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 **statics** 初始化的顺序不会造成麻烦。在 C++ 中,如果一个 **static** 期望使用另一个 **static**,而另一个 **static** 还没有初始化,就会出现问题。 - -Java 中不存在这样的问题,因为它采用了一种不同的方式加载。因为 Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 **static** 属性或方法。构造器也是一个 **static** 方法尽管它的 **static** 关键字是隐式的。因此,准确地说,一个类当它任意一个 **static** 成员被访问时,就会被加载。 - -首次使用时就是 **static** 初始化发生时。所有的 **static** 对象和 **static** 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。**static** 变量只被初始化一次。 - -### 继承和初始化 - -了解包括继承在内的整个初始化过程是有帮助的,这样可以对所发生的一切有全局性的把握。考虑下面的例子: - -```java -// reuse/Beetle.java -// The full process of initialization -class Insect { - private int i = 9; - protected int j; - - Insect() { - System.out.println("i = " + i + ", j = " + j); - j = 39; - } - - private static int x1 = printInit("static Insect.x1 initialized"); - - static int printInit(String s) { - System.out.println(s); - return 47; - } -} - -public class Beetle extends Insect { - private int k = printInit("Beetle.k.initialized"); - - public Beetle() { - System.out.println("k = " + k); - System.out.println("j = " + j); - } - - private static int x2 = printInit("static Beetle.x2 initialized"); - - public static void main(String[] args) { - System.out.println("Beetle constructor"); - Beetle b = new Beetle(); - } -} -``` - -输出: - -``` -static Insect.x1 initialized -static Beetle.x2 initialized -Beetle constructor -i = 9, j = 0 -Beetle.k initialized -k = 47 -j = 39 -``` - -当执行 **java Beetle**,首先会试图访问 **Beetle** 类的 `main()` 方法(一个静态方法),加载器启动并找出 **Beetle** 类的编译代码(在名为 **Beetle.class** 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。) - -如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 **Insect**)的 **static** 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 **static** 的初始化可能依赖基类成员是否被正确地初始化。 - -至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。 - - - -## 本章小结 - -继承和组合都是从已有类型创建新类型。组合将已有类型作为新类型底层实现的一部分,继承复用的是接口。 - -使用继承时,派生类具有基类接口,因此可以向上转型为基类,这对于多态至关重要,在下一章你将看到。 - -尽管在面向对象编程时极力强调继承,但在开始设计时,优先使用组合(或委托),只有当确实需要时再使用继承。组合更具灵活性。另外,通过对成员类型使用继承的技巧,可以在运行时改变成员的类型和行为。因此,可以在运行时改变组合对象的行为。 - -在设计一个系统时,目标是发现或创建一系列类,每个类有特定的用途,而且既不应太大(包括太多功能难以复用),也不应太小(不添加其他功能就无法使用)。如果设计变得过于复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常是有帮助的。 - -当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。 - - - -
+尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。 From 5d6635a2629f3ff16082a00d3c12b09760027537 Mon Sep 17 00:00:00 2001 From: chenguohui Date: Mon, 6 Jan 2020 16:50:38 +0800 Subject: [PATCH 140/371] Update 23-Annotations.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改翻译错误 --- docs/book/23-Annotations.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index d80001c3..9d0454ae 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -4,7 +4,7 @@ # 第二十三章 注解 -注解(也被成为元数据)为我们在代码中添加信息提供了一种形式化的方式,使我们可以在稍后的某个时刻更容易的使用这些数据。 +注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方式,使我们可以在稍后的某个时刻更容易的使用这些数据。 注解在一定程度上是把元数据和源代码文件结合在一起的趋势所激发的,而不是保存在外部文档。这同样是对像 C# 语言对于 Java 语言特性压力的一种回应。 @@ -20,7 +20,7 @@ 还有 5 种额外的注解类型用于创造新的注解。你将会在这一章学习它们。 -每当创建涉及重复工作的类或接口时,你通常可以使用注释来自动化和简化流程。例如在 Enterprise JavaBean(EJB)中的许多额外工作就是通过注解来消除的。 +每当创建涉及重复工作的类或接口时,你通常可以使用注解来自动化和简化流程。例如在 Enterprise JavaBean(EJB)中的许多额外工作就是通过注解来消除的。 注解的出现可以替代一些现有的系统,例如 XDoclet,它是一种独立的文档化工具,专门设计用来生成注解风格的文档。与之相比,注解是真正语言层级的概念,以前构造出来就享有编译器的类型检查保护。注解在源代码级别保存所有信息而不是通过注释文字,这使得代码更加整洁和便于维护。通过使用拓展的 annotation API 或稍后在本章节可以看到的外部的字节码工具类库,你会拥有对源代码及字节码强大的检查与操作能力。 @@ -120,7 +120,7 @@ Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元 | @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明
**FIELD**:字段声明(包括 enum 实例)
**LOCAL_VARIABLE**:局部变量声明
**METHOD**:方法声明
**PACKAGE**:包声明
**PARAMETER**:参数声明
**TYPE**:类、接口(包括注解类型)或者 enum 声明 | | @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 | | @Documented | 将此注解保存在 Javadoc 中 | -| @Interited | 允许子类继承父类的注解 | +| @Inherited | 允许子类继承父类的注解 | | @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 | 大多数时候,程序员定义自己的注解,并编写自己的处理器来处理他们。 @@ -271,7 +271,7 @@ public @interface SQLInteger { 另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过为为示例,两个元素足够了。 -这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值时 **@Constraints**。由于在 **@Constraints** 注解类型之后,没有在括号中指明 **@Constraints** 元素的值,因此,**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的 **@Constraints** 注解中的 `unique()` 元素为 true,并作为 `constraints()` 元素的默认值,你可以像如下定义: +这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值是 **@Constraints**。由于在 **@Constraints** 注解类型之后,没有在括号中指明 **@Constraints** 元素的值,因此,**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的 **@Constraints** 注解中的 `unique()` 元素为 true,并作为 `constraints()` 元素的默认值,你可以像如下定义: ```java // annotations/database/Uniqueness.java From 70b6f990f1ddf411b1f099db0748e925ece76b4e Mon Sep 17 00:00:00 2001 From: heyahui <369425422@qq.com> Date: Wed, 8 Jan 2020 13:33:50 +0800 Subject: [PATCH 141/371] Update 09-Polymorphism.md --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index ffd7369e..dd2debf1 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -503,7 +503,7 @@ public Derived extends PrivateOverride { private f() ``` -你可能期望输出是 **public f()**,然而 **private** 方法也是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。 +你可能期望输出是 **public f()**,然而 **private** 方法可以当作是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。 结论是只有非 **private** 方法才能被重写,但是得小心重写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。 From 8cf319e9285225bc4a33f640315b61ccfab31c12 Mon Sep 17 00:00:00 2001 From: heyahui <369425422@qq.com> Date: Wed, 8 Jan 2020 14:19:26 +0800 Subject: [PATCH 142/371] Update 09-Polymorphism.md --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index dd2debf1..dbf369de 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -640,7 +640,7 @@ Derived dynamicGet() ## 构造器和多态 -通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(它们实际上是静态方法,但是隐式声明的),但是理解构造器在复杂层次结构中运作多态还是非常重要的。这个理解可以帮助你避免一些不愉快的困扰。 +通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。这个理解可以帮助你避免一些不愉快的困扰。 ### 构造器调用顺序 From 110584cd8e8cccaa574e6c8715a4b95bc2715505 Mon Sep 17 00:00:00 2001 From: heyahui <369425422@qq.com> Date: Wed, 8 Jan 2020 21:50:20 +0800 Subject: [PATCH 143/371] Update 09-Polymorphism.md --- docs/book/09-Polymorphism.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index dbf369de..35939a5b 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -723,9 +723,9 @@ Sandwich() 从创建 **Sandwich** 对象的输出中可以看出对象的构造器调用顺序如下: -1. 基类构造器被调用。这个步骤重复递归,直到根基类的构造器被调用,然后是它的派生类,以此类推,直到最底层的派生类构造器被调用。 +1. 基类构造器被调用。这个步骤被递归地重复,这样一来类层次的顶级父类会被最先构造,然后是它的派生类,以此类推,直到最底层的派生类。 2. 按声明顺序初始化成员。 -3. 最终调用派生类的构造器。 +3. 调用派生类构造器的方法体。 构造器的调用顺序很重要。当使用继承时,就已经知道了基类的一切,并可以访问基类中任意 **public** 和 **protected** 的成员。这意味着在派生类中可以假定所有的基类成员都是有效的。在一个标准方法中,构造动作已经发生过,对象其他部分的所有成员都已经创建好。 From d4f6d7e13284e1101f8ff12490f925e8faef0ae3 Mon Sep 17 00:00:00 2001 From: heyahui <369425422@qq.com> Date: Wed, 8 Jan 2020 22:49:30 +0800 Subject: [PATCH 144/371] Update 09-Polymorphism.md --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 35939a5b..351a9ea9 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -1037,7 +1037,7 @@ RoundGlyph.RoundGlyph(), radius = 5 另一方面,应该震惊于输出结果。逻辑方面我们已经做得非常完美,然而行为仍不可思议的错了,编译器也没有报错(C++ 在这种情况下会产生更加合理的行为)。像这样的 bug 很容易被忽略,需要花很长时间才能发现。 -因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在构造器中唯一能安全调用的只有基类的 **final** 方法(包括 **private** 方法,它们自动属于 **final**)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。 +因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 **final** 方法(这也适用于可被看作是 **final** 的 **private** 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。 From a629f2ef8d0561211704714b3b9fd78d13d53ba4 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Thu, 9 Jan 2020 10:40:02 +0800 Subject: [PATCH 145/371] fix #347 --- docs/book/11-Inner-Classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index fbd857be..b4157f75 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1218,7 +1218,7 @@ Terminating ## 继承内部类 -因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在干,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: +因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: ```java // innerclasses/InheritInner.java From 2913b9bc6c0deaae400432ebf001009f4c12e741 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Thu, 9 Jan 2020 10:46:31 +0800 Subject: [PATCH 146/371] fix #348 --- docs/book/10-Interfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 65e9e38f..7625e07b 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -1112,7 +1112,7 @@ public class Adventure { x.swim(); } - public staic void v(CanFly x) { + public static void v(CanFly x) { x.fly(); } From 30986939c390dd4f15a0087fb3c17590bfefac4a Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sat, 11 Jan 2020 17:57:08 +0800 Subject: [PATCH 147/371] =?UTF-8?q?[ISSUE=20#40]=E6=A0=A1=E8=AE=A2?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E7=BC=96=E7=A8=8B=20close=20#40?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/13-Functional-Programming.md | 2 +- docs/book/14-Streams.md | 64 +++++++++++++------------ docs/book/Appendix-Collection-Topics.md | 2 +- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index ab7ec49b..4ede66bf 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -646,7 +646,7 @@ public class FunctionalAnnotation { 4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。 -5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。 +5. 如果接收两个参数且返回值为布尔值,则是一个断言(Predicate)。 6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。 diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 142079a3..b75adc65 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1683,15 +1683,15 @@ Signal(dash) ## 终端操作 -这些操作接收一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事情。 +以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中所做的最后一件事。 -### 转化为数组 +### 数组 - `toArray()`:将流转换成适当类型的数组。 -- `toArray(generator)`:在特殊情况下,生成器用于分配自定义的数组存储。 +- `toArray(generator)`:在特殊情况下,生成自定义类型的数组。 -这组方法在流操作产生的结果必须是数组形式时很有用。假如我们想在流里复用获取的随机数,可以将他们保存到数组中。代码示例: +当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假设我们需要复用流产生的随机数时,就可以这么使用。代码示例: ```java // streams/RandInts.java @@ -1706,19 +1706,19 @@ public class RandInts { } ``` -上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中,在每次调用 `rands()` 的时候可以重复获取相同的流。 +上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中。这样一来,每次调用 `rands()` 的时候可以重复获取相同的整数流。 -### 应用最终操作 +### 循环 -- `forEach(Consumer)`:你已经看到过很多次 `System.out::println` 作为 **Consumer** 函数。 +- `forEach(Consumer)`常见如 `System.out::println` 作为 **Consumer** 函数。 - `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。 -第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。 +第一种形式:无序操作,仅在引入并行流时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。 `parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。 -下例引入了 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例: +下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例: ```java // streams/ForEach.java @@ -1756,7 +1756,7 @@ public class ForEach { -### 收集 +### 集合 - `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。 - `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。 @@ -1798,7 +1798,7 @@ stream, streams, throws, toCollection, trim, util, void, words2] ``` -**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果扁平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 +**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果展平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 -### 组合所有流元素 +### 组合 - `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。 - `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。 -- `reduce(identity, BiFunction, BinaryOperator)`:这个形式更为复杂(所以我们不会介绍它),在这里被提到是因为它使用起来会更有效。通常,你可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。 +- `reduce(identity, BiFunction, BinaryOperator)`:更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。 -如下是一个用于演示 `reduce()` 的示例: +下面来看下 `reduce` 的代码示例: ```java // streams/Reduce.java @@ -1946,20 +1946,20 @@ Frobnitz(7) Frobnitz(29) ``` -**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。我们使用没有给“起始值”的 `reduce()`方法,这意味着它的返回值是 **Optional** 类型的。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。 +**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。 Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。 -`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其 size 小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。因此你会取得第一个 size 小于 50 的 `Frobnitz`,只要找到了就这个结果就会紧紧地攥住它,即使有其他候选者出现。虽然这是一个非常奇怪的约束,但是它确实让你对 `reduce()` 有了更多的了解。 +`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。当取得第一个长度小于 50 的 `Frobnitz`,只要得到结果就会忽略其他。这是个非常奇怪的约束, 也确实让我们对 `reduce()` 有了更多的了解。 ### 匹配 -- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。 -- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。 -- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。 +- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。 +- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。 +- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。 -你已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()` 和 `anyMatch()` 的用法基本上是等同的。让我们探究短路行为。为了创建消除冗余代码的 ` show()` 方法,我们必须首先发现如何统一地描述所有三个匹配器操作,然后将其转换为称作 **Matcher** 的接口: +我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须治到如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: ```java // streams/Matching.java @@ -2001,15 +2001,17 @@ public class Matching { 1 2 3 4 5 6 7 8 9 true ``` -**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数并且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。 +**BiPredicate** 是一个二元断言,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个断言 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。 -`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生。 +`show()` 获取两个参数,**Matcher** 匹配器和用于表示断言测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。 -### 元素查找 +### 查找 -- `findFirst()`:返回一个含有第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**。 +- `findFirst()`:返回第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**。 - `findAny(`:返回含有任意流元素的 **Optional**,如果流为空返回 **Optional.empty**。 +代码示例: + ```java // streams/SelectElement.java import java.util.*; @@ -2038,7 +2040,7 @@ public class SelectElement { `findFirst()` 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。 -如果必须选择流中最后一个元素,那就使用 ` reduce()`: +如果必须选择流中最后一个元素,那就使用 `reduce()`。代码示例: ```java // streams/LastElement.java @@ -2076,7 +2078,7 @@ three - `max(Comparator)`:根据所传入的 **Comparator** 所决定的“最大”元素。 - `min(Comparator)`:根据所传入的 **Comparator** 所决定的“最小”元素。 -字符串类型有预先定义好的 **Comparator**,这简化了我们的示例: +**String** 类型有预设的 **Comparator** 实现。代码示例: ```java // streams/Informational.java @@ -2113,9 +2115,9 @@ you ### 数字流信息 - `average()` :求取流元素平均值。 -- `max()` 和 `min()`:因为这些操作在数字流上面,所以不需要 **Comparator**。 +- `max()` 和 `min()`:数值流操作无需 **Comparator**。 - `sum()`:对所有流元素进行求和。 -- `summaryStatistics()`:生成可能有用的数据。目前还不太清楚他们为什么觉得有必要这样做,因为你可以使用直接的方法产生所有的数据。 +- `summaryStatistics()`:生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。 ```java // streams/NumericStreamInfo.java @@ -2142,11 +2144,11 @@ public class NumericStreamInfo { IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998} ``` -这些操作对于 **LongStream** 和 **DoubleStream** 也同样适用。 +上例操作对于 **LongStream** 和 **DoubleStream** 同样适用。 ## 本章小结 -流改变并极大地提升了 Java 编程的性质,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流动。在本书的剩余部分,我们将尽可能地使用流。 +流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。在本书的剩余部分,我们将尽可能地使用流。 diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md index 22ce7a6a..511de489 100644 --- a/docs/book/Appendix-Collection-Topics.md +++ b/docs/book/Appendix-Collection-Topics.md @@ -1613,7 +1613,7 @@ Brasilia | **Iterator\ iterator() Spliterator\ spliterator()** | 返回一个迭代器来遍历集合中的元素。 **Spliterators** 更复杂一些,它用在并发场景 | | **boolean remove(Object)** | 如果目标集合包含该参数,则在集合中删除该参数,如果成功删除则返回 **true** 。(“可选的”) | | **boolean removeAll(Collection\)** | 删除目标集合中,参数集合所包含的全部元素。如果有元素被成功删除则返回 **true** 。 (“可选的”) | -| **boolean removeIf(Predicate\)** | 删除此集合中,满足给定谓词(predicate)的所有元素 | +| **boolean removeIf(Predicate\)** | 删除此集合中,满足给定断言(predicate)的所有元素 | | **Stream\ stream() Stream\ parallelStream()** | 返回由该 **Collection** 中元素所组成的一个 **Stream** | | **int size()** | 返回集合中所包含元素的个数 | | **Object[] toArrat()** | 返回包含该集合所有元素的一个数组 | From 2633b672b0f2839e0d774bda6f2bab73f9aa4557 Mon Sep 17 00:00:00 2001 From: dingpeilong <77676182@qq.com> Date: Mon, 13 Jan 2020 18:50:33 +0800 Subject: [PATCH 148/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AC=AC=E5=8D=81?= =?UTF-8?q?=E4=B9=9D=E7=AB=A0=E7=9A=84=E4=B8=80=E4=BA=9B=E6=8B=BC=E5=86=99?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 75dfa68e..dd6514ce 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1361,12 +1361,12 @@ import java.util.regex.*; public class ShowMethods { private static String usage = - "usage:n" + - "ShowMethods qualified.class.namen" + - "To show all methods in class or:n" + - "ShowMethods qualified.class.name wordn" + - "To search for methods involving 'word'"; - private static Pattern p = Pattern.compile("\w+\."); + "usage:\n" + + "ShowMethods qualified.class.name\n" + + "To show all methods in class or:\n" + + "ShowMethods qualified.class.name word\n" + + "To search for methods involving 'word'"; + private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { @@ -1648,7 +1648,7 @@ class SelectingMethods { SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance( SomeMethods.class.getClassLoader(), - new Class[]{Interface.class}, + new Class[]{ SomeMethods.class }, new MethodSelector(new Implementation())); proxy.boring1(); proxy.boring2(); @@ -2078,7 +2078,7 @@ public class NullRobot { newNullRobot(Class type) { return (Robot) Proxy.newProxyInstance( NullRobot.class.getClassLoader(), - new Class, + new Class[] { Null.class, Robot.class }, new NullRobotProxyHandler(type)); } @@ -2375,8 +2375,7 @@ class AnonymousA { private void w() { System.out.println("private C.w()"); } - } - ; + }; } } @@ -2432,8 +2431,8 @@ public class ModifyingPrivateFields { Field f = pf.getClass().getDeclaredField("i"); f.setAccessible(true); System.out.println( - "f.getInt(pf): " + f.getint(pf)); - f.setint(pf, 47); + "f.getInt(pf): " + f.getInt(pf)); + f.setInt(pf, 47); System.out.println(pf); f = pf.getClass().getDeclaredField("s"); f.setAccessible(true); From 6362f0a6bc84c96a037125bbb4e6de8c7186a22b Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Tue, 14 Jan 2020 21:51:54 +0800 Subject: [PATCH 149/371] =?UTF-8?q?=E8=A1=A5=E5=9B=9E=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/08-Reuse.md | 486 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 485 insertions(+), 1 deletion(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index 5c81f346..baad9ac2 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -777,4 +777,488 @@ public class Car { 关键字 **protected** 就起这个作用。它表示“就类的用户而言,这是 **private** 的。但对于任何继承它的子类或在同一包中的类,它是可访问的。”(**protected** 也提供了包访问权限) -尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。 +尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。然后通过 **protected** 控制类的继承者的访问权限。 + +```java +// reuse/Orc.java +// The protected keyword +class Villain { + private String name; + + protected void set(String nm) { + name = nm; + } + + Villain(String name) { + this.name = name; + } + + @Override + public String toString() { + return "I'm a Villain and my name is " + name; + } +} + +public class Orc extends Villain { + private int orcNumber; + + public Orc(String name, int orcNumber) { + super(name); + this.orcNumber = orcNumber; + } + + public void change(String name, int orcNumber) { + set(name); // Available because it's protected + this.orcNumber = orcNumber; + } + + @Override + public String toString() { + return "Orc " + orcNumber + ": " + super.toString(); + } + + public static void main(String[] args) { + Orc orc = new Orc("Limburger", 12); + System.out.println(orc); + orc.change("Bob", 19); + System.out.println(orc); + } +} +``` + +输出: + +``` +Orc 12: I'm a Villain and my name is Limburger +Orc 19: I'm a Villain and my name is Bob +``` + +`change()` 方法可以访问 `set()` 方法,因为 `set()` 方法是 **protected**。注意到,类 **Orc** 的 `toString()` 方法也使用了基类的版本。 + + + +## 向上转型 + +继承最重要的方面不是为新类提供方法。它是新类与基类的一种关系。简而言之,这种关系可以表述为“新类是已有类的一种类型”。 + +这种描述并非是解释继承的一种花哨方式,这是直接由语言支持的。例如,假设有一个基类 **Instrument** 代表音乐乐器和一个派生类 **Wind**。 因为继承保证了基类的所有方法在派生类中也是可用的,所以任意发送给该基类的消息也能发送给派生类。如果 **Instrument** 有一个 `play()` 方法,那么 **Wind** 也有该方法。这意味着你可以准确地说 **Wind** 对象也是一种类型的 **Instrument**。下面例子展示了编译器是如何支持这一概念的: + +```java +// reuse/Wind.java +// Inheritance & upcasting +class Instrument { + public void play() {} + + static void tune(Instrument i) { + // ... + i.play(); + } +} + +// Wind objects are instruments +// because they have the same interface: +public class Wind extends Instrument { + public static void main(String[] args) { + Wind flute = new Wind(); + Instrument.tune(flute); // Upcasting + } +} +``` + +`tune()` 方法接受了一个 **Instrument** 类型的引用。但是,在 **Wind** 的 `main()` 方法里,`tune()` 方法却传入了一个 **Wind** 引用。鉴于 Java 对类型检查十分严格,一个接收一种类型的方法接受了另一种类型看起来很奇怪,除非你意识到 **Wind** 对象同时也是一个 **Instrument** 对象,而且 **Instrument** 的 `tune` 方法一定会存在于 **Wind** 中。在 `tune()` 中,代码对 **Instrument** 和 所有 **Instrument** 的派生类起作用,这种把 **Wind** 引用转换为 **Instrument** 引用的行为称作*向上转型*。 + +该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是: + +![Wind 类图](../images/1561774164644.png) + +继承图中派生类转型为基类是向上的,所以通常称作*向上转型*。因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的。也就是说,派生类是基类的一个超集。它可能比基类包含更多的方法,但它必须至少具有与基类一样的方法。在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因。 + +也可以执行与向上转型相反的向下转型,但是会有问题,对于该问题会放在下一章和“类型信息”一章进行更深入的探讨。 + +### 再论组合和继承 + +在面向对象编程中,创建和使用代码最有可能的方法是将数据和方法一起打包到类中,然后使用该类的对象。也可以使用已有的类通过组合来创建新类。继承其实不太常用。因此尽管在教授 OOP 的过程中我们多次强调继承,但这并不意味着要尽可能使用它。恰恰相反,尽量少使用它,除非确实使用继承是有帮助的。一种判断使用组合还是继承的最清晰的方法是问一问自己是否需要把新类向上转型为基类。如果必须向上转型,那么继承就是必要的,但如果不需要,则要进一步考虑是否该采用继承。“多态”一章提出了一个使用向上转型的最有力的理由,但是只要记住问一问“我需要向上转型吗?”,就能在这两者中作出较好的选择。 + + + +## final关键字 + +根据上下文环境,Java 的关键字 **final** 的含义有些微的不同,但通常它指的是“这是不能被改变的”。防止改变有两个原因:设计或效率。因为这两个原因相差很远,所以有可能误用关键字 **final**。 + +以下几节讨论了可能使用 **final** 的三个地方:数据、方法和类。 + +### final 数据 + +许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如: + +1. 一个永不改变的编译时常量。 +2. 一个在运行时初始化就不会改变的值。 + +对于编译时常量这种情况,编译器可以把常量带入计算中;也就是说,可以在编译时计算,减少了一些运行时的负担。在 Java 中,这类常量必须是基本类型,而且用关键字 **final** 修饰。你必须在定义常量的时候进行赋值。 + +一个被 **static** 和 **final** 同时修饰的属性只会占用一段不能改变的存储空间。 + +当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。 + +下面例子展示了 **final** 属性的使用: + +```java +// reuse/FinalData.java +// The effect of final on fields +import java.util.*; + +class Value { + int i; // package access + + Value(int i) { + this.i = i; + } +} + +public class FinalData { + private static Random rand = new Random(47); + private String id; + + public FinalData(String id) { + this.id = id; + } + // Can be compile-time constants: + private final int valueOne = 9; + private static final int VALUE_TWO = 99; + // Typical public constant: + public static final int VALUE_THREE = 39; + // Cannot be compile-time constants: + private final int i4 = rand.nextInt(20); + static final int INT_5 = rand.nextInt(20); + private Value v1 = new Value(11); + private final Value v2 = new Value(22); + private static final Value VAL_3 = new Value(33); + // Arrays: + private final int[] a = {1, 2, 3, 4, 5, 6}; + + @Override + public String toString() { + return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5; + } + + public static void main(String[] args) { + FinalData fd1 = new FinalData("fd1"); + //- fd1.valueOne++; // Error: can't change value + fd1.v2.i++; // Object isn't constant + fd1.v1 = new Value(9); // OK -- not final + for (int i = 0; i < fd1.a.length; i++) { + fd1.a[i]++; // Object isn't constant + } + //- fd1.v2 = new Value(0); // Error: Can't + //- fd1.VAL_3 = new Value(1); // change reference + //- fd1.a = new int[3]; + System.out.println(fd1); + System.out.println("Creating new FinalData"); + FinalData fd2 = new FinalData("fd2"); + System.out.println(fd1); + System.out.println(fd2); + } +} +``` + +输出: + +``` +fd1: i4 = 15, INT_5 = 18 +Creating new FinalData +fd1: i4 = 15, INT_5 = 18 +fd2: i4 = 13, INT_5 = 18 +``` + +因为 **valueOne** 和 **VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。**VALUE_THREE** 是一种更加典型的常量定义的方式:**public** 意味着可以在包外访问,**static** 强调只有一个,**final** 说明是一个常量。 + +按照惯例,带有恒定初始值的 **final** **static** 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。(源于 C 语言中定义常量的方式。) + +我们不能因为某数据被 **final** 修饰就认为在编译时可以知道它的值。由上例中的 **i4** 和 **INT_5** 可以看出,它们在运行时才会赋值随机数。示例部分也展示了将 **final** 值定义为 **static** 和非 **static** 的区别。此区别只有当值在运行时被初始化时才会显现,因为编译器对编译时数值一视同仁。(而且编译时数值可能因优化而消失。)当运行程序时就能看到这个区别。注意到 **fd1** 和 **fd2** 的 **i4** 值不同,但 **INT_5** 的值并没有因为创建了第二个 **FinalData** 对象而改变,这是因为它是 **static** 的,在加载时已经被初始化,并不是每次创建新对象时都初始化。 + +**v1** 到 **VAL_3** 变量说明了 **final** 引用的意义。正如你在 `main()` 中所见,**v2** 是 **final** 的并不意味着你不能修改它的值。因为它是引用,所以只是说明它不能指向一个新的对象。这对于数组具有同样的意义,数组只不过是另一种引用。(我不知道有什么方法能使数组引用本身成为 **final**。)看起来,声明引用为 **final** 没有声明基本类型 **final** 有用。 + +### 空白 final + +空白 final 指的是没有初始化值的 **final** 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 **final** 属性值不同,也能保持它的不变性。 + +```java +// reuse/BlankFinal.java +// "Blank" final fields +class Poppet { + private int i; + + Poppet(int ii) { + i = ii; + } +} + +public class BlankFinal { + private final int i = 0; // Initialized final + private final int j; // Blank final + private final Poppet p; // Blank final reference + // Blank finals MUST be initialized in constructor + public BlankFinal() { + j = 1; // Initialize blank final + p = new Poppet(1); // Init blank final reference + } + + public BlankFinal(int x) { + j = x; // Initialize blank final + p = new Poppet(x); // Init blank final reference + } + + public static void main(String[] args) { + new BlankFinal(); + new BlankFinal(47); + } +} +``` + +你必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。 + +### final 参数 + +在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量: + +```java +// reuse/FinalArguments.java +// Using "final" with method arguments +class Gizmo { + public void spin() { + + } +} + +public class FinalArguments { + void with(final Gizmo g) { + //-g = new Gizmo(); // Illegal -- g is final + } + + void without(Gizmo g) { + g = new Gizmo(); // OK -- g is not final + g.spin(); + } + + //void f(final int i) { i++; } // Can't change + // You can only read from a final primitive + int g(final int i) { + return i + 1; + } + + public static void main(String[] args) { + FinalArguments bf = new FinalArguments(); + bf.without(null); + bf.with(null); + } +} +``` + +方法 `f()` 和 `g()` 展示了 **final** 基本类型参数的使用情况。你只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类。这将在”内部类“章节中详解。 + +### final 方法 + +使用 **final** 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。 + +过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。 + +在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 **final**。 + +### final 和 private + +类中所有的 **private** 方法都隐式地指定为 **final**。因为不能访问 **private** 方法,所以不能覆写它。可以给 **private** 方法添加 **final** 修饰,但是并不能给方法带来额外的含义。 + +以下情况会令人困惑,当你试图覆写一个 **private** 方法(隐式是 **final** 的)时,看上去奏效,而且编译器不会给出错误信息: + +```java +// reuse/FinalOverridingIllusion.java +// It only looks like you can override +// a private or private final method +class WithFinals { + // Identical to "private" alone: + private final void f() { + System.out.println("WithFinals.f()"); + } + // Also automatically "final": + private void g() { + System.out.println("WithFinals.g()"); + } +} + +class OverridingPrivate extends WithFinals { + private final void f() { + System.out.println("OverridingPrivate.f()"); + } + + private void g() { + System.out.println("OverridingPrivate.g()"); + } +} + +class OverridingPrivate2 extends OverridingPrivate { + public final void f() { + System.out.println("OverridingPrivate2.f()"); + } + + public void g() { + System.out.println("OverridingPrivate2.g()"); + } +} + +public class FinalOverridingIllusion { + public static void main(String[] args) { + OverridingPrivate2 op2 = new OverridingPrivate2(); + op2.f(); + op2.g(); + // You can upcast: + OverridingPrivate op = op2; + // But you can't call the methods: + //- op.f(); + //- op.g(); + // Same here: + WithFinals wf = op2; + //- wf.f(); + //- wf.g(); + } +} +``` + +输出: + +``` +OverridingPrivate2.f() +OverridingPrivate2.g() +``` + +"覆写"只发生在方法是基类的接口时。也就是说,必须能将一个对象向上转型为基类并调用相同的方法(这一点在下一章阐明)。如果一个方法是 **private** 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 **public**,**protected** 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。由于 **private** 方法无法触及且能有效隐藏,除了把它看作类中的一部分,其他任何事物都不需要考虑到它。 + +### final 类 + +当说一个类是 **final** (**final** 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。 + +```java +// reuse/Jurassic.java +// Making an entire class final +class SmallBrain {} + +final class Dinosaur { + int i = 7; + int j = 1; + SmallBrain x = new SmallBrain(); + + void f() {} +} + +//- class Further extends Dinosaur {} +// error: Cannot extend final class 'Dinosaur' +public class Jurassic { + public static void main(String[] args) { + Dinosaur n = new Dinosaur(); + n.f(); + n.i = 40; + n.j++; + } +} +``` + +**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。 + +### final 忠告 + +在设计类时将一个方法指明为 final 看上去是明智的。你可能会觉得没人会覆写那个方法。有时这是对的。 + +但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。 + +Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,而且从效率考虑(这近乎是个幻想),如果它的所有方法没有被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 + +第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。 + +Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 + + + +## 类初始化和加载 + +在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 **statics** 初始化的顺序不会造成麻烦。在 C++ 中,如果一个 **static** 期望使用另一个 **static**,而另一个 **static** 还没有初始化,就会出现问题。 + +Java 中不存在这样的问题,因为它采用了一种不同的方式加载。因为 Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 **static** 属性或方法。构造器也是一个 **static** 方法尽管它的 **static** 关键字是隐式的。因此,准确地说,一个类当它任意一个 **static** 成员被访问时,就会被加载。 + +首次使用时就是 **static** 初始化发生时。所有的 **static** 对象和 **static** 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。**static** 变量只被初始化一次。 + +### 继承和初始化 + +了解包括继承在内的整个初始化过程是有帮助的,这样可以对所发生的一切有全局性的把握。考虑下面的例子: + +```java +// reuse/Beetle.java +// The full process of initialization +class Insect { + private int i = 9; + protected int j; + + Insect() { + System.out.println("i = " + i + ", j = " + j); + j = 39; + } + + private static int x1 = printInit("static Insect.x1 initialized"); + + static int printInit(String s) { + System.out.println(s); + return 47; + } +} + +public class Beetle extends Insect { + private int k = printInit("Beetle.k.initialized"); + + public Beetle() { + System.out.println("k = " + k); + System.out.println("j = " + j); + } + + private static int x2 = printInit("static Beetle.x2 initialized"); + + public static void main(String[] args) { + System.out.println("Beetle constructor"); + Beetle b = new Beetle(); + } +} +``` + +输出: + +``` +static Insect.x1 initialized +static Beetle.x2 initialized +Beetle constructor +i = 9, j = 0 +Beetle.k initialized +k = 47 +j = 39 +``` + +当执行 **java Beetle**,首先会试图访问 **Beetle** 类的 `main()` 方法(一个静态方法),加载器启动并找出 **Beetle** 类的编译代码(在名为 **Beetle.class** 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。) + +如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 **Insect**)的 **static** 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 **static** 的初始化可能依赖基类成员是否被正确地初始化。 + +至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。 + + + +## 本章小结 + +继承和组合都是从已有类型创建新类型。组合将已有类型作为新类型底层实现的一部分,继承复用的是接口。 + +使用继承时,派生类具有基类接口,因此可以向上转型为基类,这对于多态至关重要,在下一章你将看到。 + +尽管在面向对象编程时极力强调继承,但在开始设计时,优先使用组合(或委托),只有当确实需要时再使用继承。组合更具灵活性。另外,通过对成员类型使用继承的技巧,可以在运行时改变成员的类型和行为。因此,可以在运行时改变组合对象的行为。 + +在设计一个系统时,目标是发现或创建一系列类,每个类有特定的用途,而且既不应太大(包括太多功能难以复用),也不应太小(不添加其他功能就无法使用)。如果设计变得过于复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常是有帮助的。 + +当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。 + + + +
+ From da805f405455be426db209450007ffb9a06c9d5e Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Tue, 14 Jan 2020 21:52:14 +0800 Subject: [PATCH 150/371] =?UTF-8?q?=E8=A1=A5=E5=9B=9E=E8=AF=AF=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E7=9A=84=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 15c089fc5adb59d17598c1dfa604a052d90ea689 Mon Sep 17 00:00:00 2001 From: Pic Date: Fri, 17 Jan 2020 10:34:51 +0800 Subject: [PATCH 151/371] Update 23-Annotations.md (#353) Modify word errors --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 9d0454ae..2e38bd9e 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -306,7 +306,7 @@ public class Member { } ``` -类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **firstName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: +类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: ```java @SQLString(30) From 9fd1aa7079c32426870a6747c4a4ad5aa60cd9bd Mon Sep 17 00:00:00 2001 From: LeonTheProfessional Date: Fri, 17 Jan 2020 14:31:49 +0800 Subject: [PATCH 152/371] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BA=86=E7=A7=BB?= =?UTF-8?q?=E4=BD=8D=E8=BF=90=E7=AE=97=E7=AC=A6=E4=B8=AD=E8=BE=83=E6=9C=89?= =?UTF-8?q?=E8=AF=AF=E5=AF=BC=E6=80=A7=E7=9A=84=E6=8F=8F=E8=BF=B0=20(#354)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 更改了移位运算符中较有误导性的描述 “仅使用右侧的5 个低阶位” 中的“右侧”之前主语不明确,容易引起歧义。改为“仅使用右值的5 个低阶位” 则明确表示为等号右侧值的5个低阶位,使语义更清晰。 * 翻译勘误 ”数字的二进制表示称为有符号的两个补数。“这句的原文为“The binary representation of the numbers is referred to as signed twos complement”。这里的“2's complement” 为计算机术语“补码”,所以这句话应翻译为“数字的二进制表示形式是带符号的补码”。 --- docs/book/04-Operators.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index 1cfeb312..d9f83652 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -654,7 +654,7 @@ float f4 = 1e-43f; //10 的幂数 移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。 -如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右侧的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。 +如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。 移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例: @@ -805,7 +805,7 @@ i >>> 5, int: 97591828, binary: ... ``` -结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。数字的二进制表示称为有符号的两个补数。 +结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。 ## 三元运算符 From d3fea25992534d1f44c37e7517efcae0cf189c7b Mon Sep 17 00:00:00 2001 From: crimson <1291463831@qq.com> Date: Sat, 18 Jan 2020 07:31:39 +0800 Subject: [PATCH 153/371] =?UTF-8?q?Fix=20issue=20#112:=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=B0=8F=E8=8A=82=EF=BC=9A=E6=9E=84=E9=80=A0=E5=99=A8=E9=9D=9E?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 231 +++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 8faec42b..9b2b4ca1 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -2226,7 +2226,238 @@ public class DiningPhilosophers { ## 构造函数非线程安全 +当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前没人能见到这个对象,所以又怎么会产生对于这个对象的争议呢?确实,java语言规范(JLS)自信满满地陈述道:“没有必要使构造器同步,因为它会锁定正在构造的对象,而这通常会使得该对象直到其所有构造器完成所有工作后,才对其他线程可见。” +不幸的是,对象构造过程像其他任何事物一样容易受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 +考虑使用静态字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始: +```java +//concurrent/HasID.java +public interface HasID { + int getID(); +} +``` + +然后StaticIDField类以显式方式实现该接口: + +```java +// concurrent/StaticIDField.java +public class StaticIDField implements HasID { + private static int counter = 0; + private int id = counter++; + public int getID() { return id; } +} +``` + +正如您所想的,此类是一个简单无害的类,它甚至没有一个显式的构造器来引发问题。当我们运行多个用于创建此类对象的线程时,究竟会发生什么,为了搞清楚这点,我们做了以下测试: + +```java +// concurrent/IDChecker.java +import java.util.*; +import java.util.function.*; +import java.util.stream.*; +import java.util.concurrent.*; +import com.google.common.collect.Sets; +public class IDChecker { + public static final int SIZE = 100_000; + + static class MakeObjects implements + Supplier> { + private Supplier gen; + + MakeObjects(Supplier gen) { + this.gen = gen; + } + + @Override public List get() { + return Stream.generate(gen) + .limit(SIZE) + .map(HasID::getID) + .collect(Collectors.toList()); + } + } + + public static void test(Supplier gen) { + CompletableFuture> + groupA = CompletableFuture.supplyAsync(new + MakeObjects(gen)), + groupB = CompletableFuture.supplyAsync(new + MakeObjects(gen)); + + groupA.thenAcceptBoth(groupB, (a, b) -> { + System.out.println( + Sets.intersection( + Sets.newHashSet(a), + Sets.newHashSet(b)).size()); + }).join(); + } +} +``` + +MakeObjects类是一个供应者类,包含一个能够产生List\类型的列表对象的get()方法。通过从每个HasID对象提取ID并放入列表中来生成这个列表对象,而test()方法则创建了两个并行的CompletableFuture对象,用于运行MakeObjects供应者类,然后获取运行结果。 +使用Guava库中的Sets.intersection()方法,计算出这两个返回的List\对象中有多少相同的ID(使用谷歌Guava库里的方法比使用官方的retainAll()方法速度快得多)。 + +现在我们可以测试上面的StaticIDField类了: + +```java +// concurrent/TestStaticIDField.java +public class TestStaticIDField { + + public static void main(String[] args) { + IDChecker.test(StaticIDField::new); + } +} +/* Output: + 13287 +*/ +``` + +结果中出现了很多重复项。很显然,纯静态int用于构造过程并不是线程安全的。让我们使用AtomicInteger来使其变为线程安全的: + +```java +// concurrent/GuardedIDField.java +import java.util.concurrent.atomic.*; +public class GuardedIDField implements HasID { + private static AtomicInteger counter = new + AtomicInteger(); + + private int id = counter.getAndIncrement(); + + public int getID() { return id; } + + public static void main(String[] args) { IDChecker.test(GuardedIDField::new); + } +} +/* Output: + 0 +*/ +``` + +构造器有一种更微妙的状态共享方式:通过构造器参数: + +```java +// concurrent/SharedConstructorArgument.java +import java.util.concurrent.atomic.*; +interface SharedArg{ + int get(); +} + +class Unsafe implements SharedArg{ + private int i = 0; + + public int get(){ + return i++; + } +} + +class Safe implements SharedArg{ + private static AtomicInteger counter = new AtomicInteger(); + + public int get(){ + return counter.getAndIncrement(); + } +} + +class SharedUser implements HasID{ + private final int id; + + SharedUser(SharedArg sa){ + id = sa.get(); + } + + @Override + public int getID(){ + return id; + } +} + +public class SharedConstructorArgument{ + public static void main(String[] args){ + Unsafe unsafe = new Unsafe(); + IDChecker.test(() -> new SharedUser(unsafe)); + + Safe safe = new Safe(); + IDChecker.test(() -> new SharedUser(safe)); + } +} +/* Output: + 24838 + 0 +*/ +``` + +在这里,SharedUser构造器实际上共享了相同的参数。即使SharedUser以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。SharedUser甚至不知道它是以这种方式调用的,更不必说控制它了。 +同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:Low-Level Concurrency,来进一步了解同步关键字——synchronized)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: + +```java +// concurrent/SynchronizedConstructor.java + +import java.util.concurrent.atomic.*; + +class SyncConstructor implements HasID{ + private final int id; + private static Object constructorLock = + new Object(); + + SyncConstructor(SharedArg sa){ + synchronized (constructorLock){ + id = sa.get(); + } + } + + @Override + public int getID(){ + return id; + } +} + +public class SynchronizedConstructor{ + public static void main(String[] args){ + Unsafe unsafe = new Unsafe(); + IDChecker.test(() -> new SyncConstructor(unsafe)); + } +} +/* Output: + 0 +*/ + +``` + +Unsafe类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: + +```java +// concurrent/SynchronizedFactory.java +import java.util.concurrent.atomic.*; + +final class SyncFactory implements HasID{ + private final int id; + + private SyncFactory(SharedArg sa){ + id = sa.get(); + } + + @Override + public int getID(){ + return id; + } + + public static synchronized SyncFactory factory(SharedArg sa){ + return new SyncFactory(sa); + } +} + +public class SynchronizedFactory{ + public static void main(String[] args){ + Unsafe unsafe = new Unsafe(); + IDChecker.test(() -> SyncFactory.factory(unsafe)); + } +} +/* Output: + 0 +*/ +``` + +通过同步静态工厂方法,可以在构造过程中锁定Class对象。 +这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使您采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 ## 复杂性和代价 From fb9581dec28ef9065e3d20444e80944865724e11 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sat, 18 Jan 2020 07:43:34 +0800 Subject: [PATCH 154/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20Predicate=20?= =?UTF-8?q?=E7=9A=84=E7=BF=BB=E8=AF=91=E4=B8=BA=20=E8=B0=93=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/13-Functional-Programming.md | 12 ++++++------ docs/book/14-Streams.md | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 4ede66bf..3ddd4493 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -646,7 +646,7 @@ public class FunctionalAnnotation { 4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。 -5. 如果接收两个参数且返回值为布尔值,则是一个断言(Predicate)。 +5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。 6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。 @@ -1306,9 +1306,9 @@ public class AnonymousClosure { | :----- | :----- | | `andThen(argument)`
根据参数执行原始操作 | **Function
BiFunction
Consumer
BiConsumer
IntConsumer
LongConsumer
DoubleConsumer
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator** | | `compose(argument)`
根据参数执行原始操作 | **Function
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator** | -| `and(argument)`
短路**逻辑与**原始断言和参数断言 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | -| `or(argument)`
短路**逻辑或**原始断言和参数断言 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | -| `negate()`
该断言的**逻辑否**断言| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `and(argument)`
短路**逻辑与**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `or(argument)`
短路**逻辑或**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `negate()`
该谓词的**逻辑否**谓词| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | 下例使用了 `Function` 里的 `compose()`和 `andThen()`。代码示例: @@ -1374,9 +1374,9 @@ foobar foobaz ``` -`p4` 获取到了所有断言并组合成一个更复杂的断言。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。 +`p4` 获取到了所有谓词并组合成一个更复杂的谓词。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。 -正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的断言来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。 +正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。 从输出结果我们可以看到 `p4` 的工作流程:任何带有 `foo` 的东西都会留下,即使它的长度大于 5。 `fongopuckey` 因长度超出和不包含 `bar` 而被丢弃。 diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index b75adc65..764d3ac9 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1959,7 +1959,7 @@ Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结 - `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。 - `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。 -我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须治到如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: +我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: ```java // streams/Matching.java @@ -2001,9 +2001,9 @@ public class Matching { 1 2 3 4 5 6 7 8 9 true ``` -**BiPredicate** 是一个二元断言,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个断言 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。 +**BiPredicate** 是一个二元谓词,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。 -`show()` 获取两个参数,**Matcher** 匹配器和用于表示断言测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。 +`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。 ### 查找 From 86bf1010e41bbc30f6fe4ee62230118f43b7d662 Mon Sep 17 00:00:00 2001 From: crimson <1291463831@qq.com> Date: Sat, 18 Jan 2020 09:48:19 +0800 Subject: [PATCH 155/371] =?UTF-8?q?Fix=20issue#112:=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=B0=8F=E8=8A=82=E5=A4=8D=E6=9D=82=E6=80=A7=E5=92=8C=E4=BB=A3?= =?UTF-8?q?=E4=BB=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 350 +++++++++++++++++++++++++ 1 file changed, 350 insertions(+) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 9b2b4ca1..15cdfef3 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -2461,8 +2461,358 @@ public class SynchronizedFactory{ ## 复杂性和代价 +假设您正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分: + + + +```java +// concurrent/Pizza.java import java.util.function.*; + +import onjava.Nap; +public class Pizza{ + public enum Step{ + DOUGH(4), ROLLED(1), SAUCED(1), CHEESED(2), + TOPPED(5), BAKED(2), SLICED(1), BOXED(0); + int effort;// Needed to get to the next step + + Step(int effort){ + this.effort = effort; + } + + Step forward(){ + if (equals(BOXED)) return BOXED; + new Nap(effort * 0.1); + return values()[ordinal() + 1]; + } + } + + private Step step = Step.DOUGH; + private final int id; + + public Pizza(int id){ + this.id = id; + } + + public Pizza next(){ + step = step.forward(); + System.out.println("Pizza " + id + ": " + step); + return this; + } + + public Pizza next(Step previousStep){ + if (!step.equals(previousStep)) + throw new IllegalStateException("Expected " + + previousStep + " but found " + step); + return next(); + } + + public Pizza roll(){ + return next(Step.DOUGH); + } + + public Pizza sauce(){ + return next(Step.ROLLED); + } + + public Pizza cheese(){ + return next(Step.SAUCED); + } + + public Pizza toppings(){ + return next(Step.CHEESED); + } + + public Pizza bake(){ + return next(Step.TOPPED); + } + + public Pizza slice(){ + return next(Step.BAKED); + } + + public Pizza box(){ + return next(Step.SLICED); + } + + public boolean complete(){ + return step.equals(Step.BOXED); + } + + @Override + public String toString(){ + return "Pizza" + id + ": " + (step.equals(Step.BOXED) ? "complete" : step); + } +} +``` + +这只算得上是一个简单的状态机,就像Machina类一样。 + +制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行: + +```java +// concurrent/OnePizza.java + +import onjava.Timer; + +public class OnePizza{ + public static void main(String[] args){ + Pizza za = new Pizza(0); + System.out.println(Timer.duration(() -> { + while (!za.complete()) za.next(); + })); + } +} +/* Output: +Pizza 0: ROLLED +Pizza 0: SAUCED +Pizza 0: CHEESED +Pizza 0: TOPPED +Pizza 0: BAKED +Pizza 0: SLICED +Pizza 0: BOXED + 1622 +*/ +``` + +时间以毫秒为单位,加总所有步骤的工作量,会得出与我们的期望值相符的数字。 如果您以这种方式制作了五个披萨,那么您会认为它花费的时间是原来的五倍。 但是,如果这还不够快怎么办? 我们可以从尝试并行流方法开始: + +```java +// concurrent/PizzaStreams.java +// import java.util.*; import java.util.stream.*; + +import onjava.Timer; + +public class PizzaStreams{ + static final int QUANTITY = 5; + + public static void main(String[] args){ + Timer timer = new Timer(); + IntStream.range(0, QUANTITY) + .mapToObj(Pizza::new) + .parallel()//[1] + .forEach(za -> { while(!za.complete()) za.next(); }); System.out.println(timer.duration()); + } +} +/* Output: +Pizza 2: ROLLED +Pizza 0: ROLLED +Pizza 1: ROLLED +Pizza 4: ROLLED +Pizza 3:ROLLED +Pizza 2:SAUCED +Pizza 1:SAUCED +Pizza 0:SAUCED +Pizza 4:SAUCED +Pizza 3:SAUCED +Pizza 2:CHEESED +Pizza 1:CHEESED +Pizza 0:CHEESED +Pizza 4:CHEESED +Pizza 3:CHEESED +Pizza 2:TOPPED +Pizza 1:TOPPED +Pizza 0:TOPPED +Pizza 4:TOPPED +Pizza 3:TOPPED +Pizza 2:BAKED +Pizza 1:BAKED +Pizza 0:BAKED +Pizza 4:BAKED +Pizza 3:BAKED +Pizza 2:SLICED +Pizza 1:SLICED +Pizza 0:SLICED +Pizza 4:SLICED +Pizza 3:SLICED +Pizza 2:BOXED +Pizza 1:BOXED +Pizza 0:BOXED +Pizza 4:BOXED +Pizza 3:BOXED +1739 +*/ +``` + +现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将QUANTITY更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 + +PizzaStreams类产生的每个并行流在它的forEach()内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? + +```java +// concurrent/PizzaParallelSteps.java + +import java.util.*; +import java.util.stream.*; +import onjava.Timer; + +public class PizzaParallelSteps{ + static final int QUANTITY = 5; + + public static void main(String[] args){ + Timer timer = new Timer(); + IntStream.range(0, QUANTITY) + .mapToObj(Pizza::new) + .parallel() + .map(Pizza::roll) + .map(Pizza::sauce) + .map(Pizza::cheese) + .map(Pizza::toppings) + .map(Pizza::bake) + .map(Pizza::slice) + .map(Pizza::box) + .forEach(za -> System.out.println(za)); + System.out.println(timer.duration()); + } +} +/* Output: +Pizza 2: ROLLED +Pizza 0: ROLLED +Pizza 1: ROLLED +Pizza 4: ROLLED +Pizza 3: ROLLED +Pizza 1: SAUCED +Pizza 0: SAUCED +Pizza 2: SAUCED +Pizza 3: SAUCED +Pizza 4: SAUCED +Pizza 1: CHEESED +Pizza 0: CHEESED +Pizza 2: CHEESED +Pizza 3: CHEESED +Pizza 4: CHEESED +Pizza 0: TOPPED +Pizza 2: TOPPED +Pizza 1: TOPPED +Pizza 3: TOPPED +Pizza 4: TOPPED +Pizza 1: BAKED +Pizza 2: BAKED +Pizza 0: BAKED +Pizza 4: BAKED +Pizza 3: BAKED +Pizza 0: SLICED +Pizza 2: SLICED +Pizza 1: SLICED +Pizza 3: SLICED +Pizza 4: SLICED +Pizza 1: BOXED +Pizza1: complete +Pizza 2: BOXED +Pizza 0: BOXED +Pizza2: complete +Pizza0: complete +Pizza 3: BOXED +Pizza 4: BOXED +Pizza4: complete +Pizza3: complete +1738 +*/ +``` + +答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的PizzaParallelSteps.java里面展示的一样。 + +我们可以使用CompletableFutures重写这个例子: + +```java +// concurrent/CompletablePizza.java + +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.*; +import onjava.Timer; + +public class CompletablePizza{ + static final int QUANTITY = 5; + + public static CompletableFuture makeCF(Pizza za){ + return CompletableFuture + .completedFuture(za) + .thenApplyAsync(Pizza::roll) + .thenApplyAsync(Pizza::sauce) + .thenApplyAsync(Pizza::cheese) + .thenApplyAsync(Pizza::toppings) + .thenApplyAsync(Pizza::bake) + .thenApplyAsync(Pizza::slice) + .thenApplyAsync(Pizza::box); + } + + public static void show(CompletableFuture cf){ + try{ + System.out.println(cf.get()); + } catch (Exception e){ + throw new RuntimeException(e); + } + } + + public static void main(String[] args){ + Timer timer = new Timer(); + List> pizzas = + IntStream.range(0, QUANTITY) + .mapToObj(Pizza::new) + .map(CompletablePizza::makeCF) + .collect(Collectors.toList()); + System.out.println(timer.duration()); + pizzas.forEach(CompletablePizza::show); + System.out.println(timer.duration()); + } +} +/* Output: +169 +Pizza 0: ROLLED +Pizza 1: ROLLED +Pizza 2: ROLLED +Pizza 4: ROLLED +Pizza 3: ROLLED +Pizza 1: SAUCED +Pizza 0: SAUCED +Pizza 2: SAUCED +Pizza 4: SAUCED +Pizza 3: SAUCED +Pizza 0: CHEESED +Pizza 4: CHEESED +Pizza 1: CHEESED +Pizza 2: CHEESED +Pizza 3: CHEESED +Pizza 0: TOPPED +Pizza 4: TOPPED +Pizza 1: TOPPED +Pizza 2: TOPPED +Pizza 3: TOPPED +Pizza 0: BAKED +Pizza 4: BAKED +Pizza 1: BAKED +Pizza 3: BAKED +Pizza 2: BAKED +Pizza 0: SLICED +Pizza 4: SLICED +Pizza 1: SLICED +Pizza 3: SLICED +Pizza 2: SLICED +Pizza 4: BOXED +Pizza 0: BOXED +Pizza0: complete +Pizza 1: BOXED +Pizza1: complete +Pizza 3: BOXED +Pizza 2: BOXED +Pizza2: complete +Pizza3: complete +Pizza4: complete +1797 +*/ +``` + +并行流和CompletableFutures是Java并发工具箱中最先进发达的技术。 您应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果您决定不借助它而由自己完成,您就必须撸起袖子,深入研究Spliterator的文档)。 + +而当工作的各个部分内容各不相同时,使用CompletableFutures是最好的选择。比起面向数据,CompletableFutures更像是面向任务的。 + +对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。 + +由于制作披萨总需要一定的时间,无论您使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当您处理更复杂的问题时,您就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止您因为采取无用的加快运行速度的举措而忙得团团转。 + +使用CompletableFutures或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 + ## 本章小结 [^1]:例如,Eric-Raymond在“VIIX编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 From 4b09a31a4ec8e4f489104e75af891338cd8f412a Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sat, 18 Jan 2020 13:09:32 +0800 Subject: [PATCH 156/371] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E6=A0=A1=E8=AE=A2=20?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E7=BC=96=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 491 +++++++++++++++---------- 1 file changed, 300 insertions(+), 191 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 15cdfef3..5a82b1e6 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -19,7 +19,7 @@ 而不是单一的意识流叙事,我们在同时多条故事线进行的间谍小说里。一个间谍在一个特殊的岩石下李璐下微缩胶片,当第二个间谍来取回包裹时,它可能已经被第三个间谍带走了。但是这部特别的小说并没有把事情搞得一团糟;你可以轻松地走到尽头,永远不会弄明白什么。 -构建并发应用程序非常类似于游戏[Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。您从构建系统中学到的东西可能不适用于下一个系统。 +构建并发应用程序非常类似于游戏[Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。 本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的Java 8工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够解决问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。 @@ -64,7 +64,7 @@ _并行_ 我开始怀疑是否真的有高度抽象。当编写这些类型的程序时,你永远不会被底层系统和工具屏蔽,甚至关于CPU缓存如何工作的细节。最后,如果你非常小心,你创作的东西在特定的情况下起作用,但它在其他情况下不起作用。有时,区别在于两台机器的配置方式,或者程序的估计负载。这不是Java特有的-它是并发和并行编程的本质。 -您可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果它没有正确调整并且输入速率要么没有被正确估计或被限制(并且限制意味着,在不同情况下不同的东西具有不同的影响),该队列将填满并阻塞或溢出。最后,您必须了解所有细节,任何问题都可能会破坏您的系统。这是一种非常不同的编程方式 +你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果它没有正确调整并且输入速率要么没有被正确估计或被限制(并且限制意味着,在不同情况下不同的东西具有不同的影响),该队列将填满并阻塞或溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式 ### 并发的新定义 @@ -75,14 +75,14 @@ _并行_ 这实际上是一个相当多的声明,所以我将其分解: - 这是一个集合:有许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大 -- 这些是性能技术:就是这样。并发的关键点在于让您的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 -- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果您发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果您在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 +- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 +- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 值得强调的是,这个定义的有效性取决于等待这个词。如果没有什么可以等待,那就没有机会了。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 ## 并发的超能力 -想象一下,你是一部科幻电影。您必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。您进入建筑物并移动走廊。走廊分开了。 +想象一下,你是一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并移动走廊。走廊分开了。 你自己完成这项任务需要一百个生命周期。 @@ -94,36 +94,36 @@ _并行_ 我很想能够说,“你在科幻小说中的超级大国?这就是并发性。“每当你有更多的任务要解决时,它就像分裂两个一样简单。问题是我们用来描述这种现象的任何模型最终都是抽象的 -以下是其中一个漏洞:在理想的世界中,每次克隆自己时,您还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 您的机器上可能有四个或八个处理器(通常在写入时)。您可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配您的决策 +以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,那么只是因为搜索者恰好是因为处理器闲置了被锁,等待一扇门被接听。相反,我们希望将处理器应用于搜索,在那里它可以做一些真正的工作,因此需要将处理器从一个任务切换到另一个任务的机制。 -许多型号能够有效地隐藏处理器的数量,并允许您假装您的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 +许多型号能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 -其中一个最大的影响取决于您是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。 +其中一个最大的影响取决于你是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。 -这可能会让您决定,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上值得让它运行得更慢以实现。 +这可能会让你决定,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上值得让它运行得更慢以实现。 在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本会降低一切,在这种情况下,如果你有多个进程,并发通常只会有意义。 在接听电话的客户服务部门,你只有一定数量的人,但是你可以拨打很多电话。那些人(处理器)必须一次拨打一个电话,直到完成电话和额外的电话必须排队。 -在“鞋匠和精灵”的童话故事中,鞋匠做了很多工作,当他睡着时,一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时会产生限制 - 例如,如果鞋底需要制作鞋子,这会限制制鞋的速度并改变您设计解决方案的方式。 +在“鞋匠和精灵”的童话故事中,鞋匠做了很多工作,当他睡着时,一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时会产生限制 - 例如,如果鞋底需要制作鞋子,这会限制制鞋的速度并改变你设计解决方案的方式。 -因此,您尝试解决的问题驱动解决方案的设计。打破一个“独立运行”问题的高级[原文:lovely ]抽象,然后就是实际发生的现实。物理现实不断侵入和震撼,这种抽象。 +因此,你尝试解决的问题驱动解决方案的设计。打破一个“独立运行”问题的高级[原文:lovely ]抽象,然后就是实际发生的现实。物理现实不断侵入和震撼,这种抽象。 这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备收到蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先在框中获取蛋糕(通常使用锁定机制来解决问题,因此一个工作人员可以先抓住框并防止蛋糕砸)。 当“同时”执行的任务相互干扰时,会出现问题。他可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,编写仅看起来可行的并发程序更为常见,但是在适当的条件下,将会失败。这些情况可能会发生,或者很少发生,你在测试期间从未看到它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 这是推动并发的最强有力的论据之一:如果你忽略它,你可能会被咬。 -因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证或检查异常那样的安全网来告诉您何时出现错误。通过并发,您可以自己动手,只有知识渊博,可疑和积极,才能用Java编写可靠的并发代码。 +因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证或检查异常那样的安全网来告诉你何时出现错误。通过并发,你可以自己动手,只有知识渊博,可疑和积极,才能用Java编写可靠的并发代码。 ## 并发为速度而生 -在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定它没有之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行您的程序,或者如果您可以对其进行分析并发现瓶颈并在该位置交换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在孤立的地方。 +在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定它没有之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置交换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在孤立的地方。 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学习利用那些超级处理器,这是并发性给你的一个建议。 @@ -139,11 +139,11 @@ _并行_ 有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,以防止它们在并发频谱中的适用性(最终你习惯了标准的并发性克制,“这种方法适用于一些情况但不适用于其他情况”) -一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果您发现程序的一部分必须大量使用并发性并且您在尝试构建该部分时遇到了过多的问题,那么您可能会考虑使用专用并发语言创建程序的那一部分。 +一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 -并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使您能够创建更加松散耦合的设计;否则,您的代码部分将被迫明确标注通常由并发处理的操作。 +并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;否则,你的代码部分将被迫明确标注通常由并发处理的操作。 ## 四句格言 @@ -167,7 +167,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 首先应用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。 -如果您被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。 +如果你被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。 ### 2.没有什么是真的,一切可能都有问题 @@ -175,41 +175,41 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。我已经很熟悉的东西,认为它显然有效但实际上并没有。 -在非并发程序中你可以忽略的各种事情突然变得非常重要。例如,您必须知道处理器缓存以及保持本地缓存与主内存一致的问题。您必须了解对象构造的深度复杂性,以便您的构造对象不会意外地将数据暴露给其他线程进行更改。问题还有很多。 +在非并发程序中你可以忽略的各种事情突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须了解对象构造的深度复杂性,以便你的构造对象不会意外地将数据暴露给其他线程进行更改。问题还有很多。 -虽然这些主题太复杂,无法为您提供本章的专业知识(再次参见Java Concurrency in Practice),但您必须了解它们。 +虽然这些主题太复杂,无法为你提供本章的专业知识(再次参见Java Concurrency in Practice),但你必须了解它们。 ### 3.它起作用,并不意味着它没有问题 -您可以轻松编写一个似乎可以工作,但实际上是有问题的并发程序,并且该问题仅在最极限的条件下显示出来 - 在您部署程序后不可避免地会出现用户问题。 +我们很容易编写出一个看似完美实则有问题的并发程序,并且往往问题直在极端情况下才暴露出来 - 在程序部署后不可避免地会出现用户问题。 - 你不能证明并发程序是正确的,你只能(有时)证明它是不正确的。 - 大多数情况下你甚至不能这样做:如果它有问题,你可能无法检测到它。 -- 您通常不能编写有用的测试,因此您必须依靠代码检查结合深入的并发知识来发现错误。 +- 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。 - 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。 在其他Java主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“你知道的越多,你认为你知道得越多。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 -我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉您。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。 +我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。 在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。你可以在这个情况下做的最糟糕的事情是“自信”。 ### 4.你必须仍然理解 -在格言1-3之后,您可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续保留它。 +在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续保留它。 -这是一种理性的反应。您可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢? +这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢? 唉,你不能轻易逃脱: - 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** clas那样简单的东西。 -- 这是最糟糕的事情:当您创建组件时,您必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? +- 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? 人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。 -唉,如果只是这样的话,那就太好了。但不幸的是,您无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 +唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 -Java是一种多线程语言,如果您了解它们是否存在并发问题。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使您意识到正确的程序可能会表现出错误的行为。 +Java是一种多线程语言,如果你了解它们是否存在并发问题。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 ## 残酷的真相 @@ -220,13 +220,13 @@ Java是一种多线程语言,如果您了解它们是否存在并发问题。 有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件 -Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易就像语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气体相位期间,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用您的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 +Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易就像语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气体相位期间,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 [Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 -热情使原始Java设计师因为看起来有必要而投入功能。信心(以及原始语言的气味)让他们认为任何问题都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 - 这是非常有信心,相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,您可以告诉人们不要使用Vector,但保留了对之前版本的支持。 +热情使原始Java设计师因为看起来有必要而投入功能。信心(以及原始语言的气味)让他们认为任何问题都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 - 这是非常有信心,相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但保留了对之前版本的支持。 线程包含在Java 1.0中。当然,并发性是影响语言远角的基本语言设计决策,很难想象以后添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。 @@ -240,36 +240,36 @@ Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入 尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它还有多远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 -这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化您对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然容易受到攻击,您仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 +这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然容易受到攻击,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 ## 本章其余部分 -这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得您的生活比旧的替代品更加轻松。但是,您仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 +这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得你的生活比旧的替代品更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 - Parallel Streams(并发流) -到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在您对该语法(作为一个粉丝,我希望)感到满意,您可以获得额外的好处:您可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 +到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在你对该语法(作为一个粉丝,我希望)感到满意,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 - 创建和运行任务 -任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。您创建一个任务,然后将其交给Executorto运行。 +任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executorto运行。 有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 - 终止长时间运行的任务 任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。 - Completable Futures -当您将衣服带到干洗店时,他们会给您一张收据。你继续完成其他任务,最终你的衣服很干净,你可以拿起它。收据是您与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 +当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,最终你的衣服很干净,你可以拿起它。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,等待任务没有完成。对于一系列操作,Futures并没有真正帮助那么多。 -Java 8 CompletableFuture是一个更好的解决方案:它允许您将操作链接在一起,因此您不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,清理菜肴,储存菜肴”等一系列链式操作。 +Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,清理菜肴,储存菜肴”等一系列链式操作。 - 死锁 某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,等待另一个被阻止的任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入僵局。 -如果在运行程序时没有立即出现死锁,则会出现最大的问题。您的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如您的开发机器,但是当您将其部署到不同的硬件时会开始死锁。 +如果在运行程序时没有立即出现死锁,则会出现最大的问题。你的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如你的开发机器,但是当你将其部署到不同的硬件时会开始死锁。 死锁通常源于细微的编程错误;一系列无辜的决定,最终意外地创建了一个依赖循环。本节包含一个经典示例,演示了死锁的特性。 @@ -279,7 +279,7 @@ Java 8 CompletableFuture是一个更好的解决方案:它允许您将操作 ## 并行流 -Java 8流的一个显着优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。这产生了相当神奇的结果,即能够简单用parallel()然后流中的所有内容都作为一组并行任务运行。如果您的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 +Java 8流的一个显着优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。这产生了相当神奇的结果,即能够简单用parallel()然后流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: @@ -311,17 +311,20 @@ public class ParallelPrime { Files.write(Paths.get("primes.txt"), primes, StandardOpenOption.CREATE); } } - /* +``` + +输出结果: + +``` Output: 1224 - */ ``` 请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且消除了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。 当我注释掉[1] parallel()行时,我的结果大约是parallel()的三倍。 -并行流似乎是一个甜蜜的交易。您所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 +并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 - parallel()不是灵丹妙药 @@ -367,11 +370,15 @@ public class Summing { // .limit(SZ+1).sum()); } } -/* Output:5000000050000000 +``` + +输出结果: + +``` +5000000050000000 Sum Stream: 167ms Sum Stream Parallel: 46ms Sum Iterated: 284ms -*/ ``` **CHECK**值是使用Carl Friedrich Gauss在1700年代后期仍在小学时创建的公式计算出来的. @@ -420,13 +427,17 @@ public class Summing2 { }); } } -/* Output:200000010000000 +``` + +输出结果: + +``` +200000010000000 Array Stream Sum: 104ms Parallel: 81ms Basic Sum: 106ms parallelPrefix: 265ms -*/ ``` 第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。” @@ -464,13 +475,17 @@ public class Summing3 { }); } } -/* Output:50000005000000 +``` + +输出结果: + +``` +50000005000000 Long Array Stream Reduce: 1038ms Long Basic Sum: 21ms Long parallelPrefix: 3616ms -*/ ``` 现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了**basicSum()**,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。 @@ -493,9 +508,13 @@ public class Summing4 { .reduce(0L,Long::sum)); } } -/* Output:50000005000000 +``` + +输出结果: + +``` +50000005000000 Long Parallel: 1014ms -*/ ``` 它比非parallel()版本略快,但并不显着。 @@ -524,7 +543,7 @@ Long Parallel: 1008ms** - parallel()/limit()交点 -使用parallel()时会有更复杂的问题。从其他语言中吸取的流是围绕无限流模型设计的。如果您拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果您使用无限流,则使用针对流优化的算法。 +使用parallel()时会有更复杂的问题。从其他语言中吸取的流是围绕无限流模型设计的。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在Collection和Map中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将Collection转换为存在这些操作的Stream: @@ -547,7 +566,11 @@ public class CollectionIntoStream { System.out.println(result); } } -/* Output:btpen +``` + +输出结果: + +``` pccux szgvg meinn @@ -558,7 +581,6 @@ ygpoa lkljl bynxt :PENCUXGVGINNLOZVEWPPCPOALJLNXT -*/ ``` **Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作.**ConcurrentHashMap**对**forEachand**和**reduce**操作有特别广泛的支持。 @@ -628,15 +650,17 @@ public class ParallelStreamPuzzle2 { Files.write(Paths.get("PSP2.txt"), trace); } } -/* -Output: +``` + +输出结果: + +``` [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -*/ ``` -current是使用线程安全的**AtomicInteger**类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。 +current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。 -在查看**PSP2.txt**。**IntGenerator.get()**被调用1024次时,您可能会感到惊讶。 +在查看 **PSP2.txt**.**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。 **0: main 1: ForkJoinPool.commonPool-worker-1 @@ -685,7 +709,11 @@ public class ParallelStreamPuzzle3 { System.out.println(x); } } -/* Output: +``` + +输出结果: + +``` 8: main 6: ForkJoinPool.commonPool-worker-5 3: ForkJoinPool.commonPool-worker-7 @@ -697,12 +725,11 @@ public class ParallelStreamPuzzle3 { 7: ForkJoinPool.commonPool-worker-1 9: ForkJoinPool.commonPool-worker-2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -*/ ``` 为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -您还可以看到boxed()的添加,它接受int流并将其转换为Integer流。 +你还可以看到boxed()的添加,它接受int流并将其转换为Integer流。 现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 @@ -710,20 +737,20 @@ public class ParallelStreamPuzzle3 { - 并行流只看起来很容易 -实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如您所见,只需将**parallel()**打到您的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,您必须了解并行性如何帮助或损害您的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着您不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现您的目标。 +实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,只需将**parallel()**打到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现你的目标。 ## 创建和运行任务 -如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后您将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。 +如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。 Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。 - Tasks and Executors -在Java的早期版本中,您通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建您自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 +在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 -创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为您处理线程池。您可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为您管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 +创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: @@ -770,7 +797,7 @@ public class Nap { ``` 为了消除异常处理的视觉噪声,这被定义为实用程序。第二个构造函数在超时时显示一条消息 -对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在您的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 +对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 你可以看到**sleep()**抛出一个已检查的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 @@ -798,7 +825,11 @@ public class SingleThreadExecutor { } } } -/* Output: +``` + +输出结果: + +``` All tasks submitted main awaiting termination main awaiting termination @@ -821,7 +852,6 @@ main awaiting termination NapTask[8] pool-1-thread-1 main awaiting termination NapTask[9] pool-1-thread-1 -*/ ``` 首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4] @@ -846,7 +876,11 @@ public class SingleThreadExecutor2 { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` NapTask[0] pool-1-thread-1 NapTask[1] pool-1-thread-1 NapTask[2] pool-1-thread-1 @@ -857,7 +891,6 @@ NapTask[6] pool-1-thread-1 NapTask[7] pool-1-thread-1 NapTask[8] pool-1-thread-1 NapTask[9] pool-1-thread-1 -*/ ``` 一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。 @@ -878,17 +911,19 @@ public class MoreTasksAfterShutdown { } } } -/* Output: -java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1 -*/ +``` + +输出结果: +``` +java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1 ``` **exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 - 使用更多线程 -使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,您将看到更多选项。例如CachedThreadPool: +使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,你将看到更多选项。例如CachedThreadPool: ```java // concurrent/CachedThreadPool.java @@ -904,7 +939,11 @@ public class CachedThreadPool { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` NapTask[7] pool-1-thread-8 NapTask[4] pool-1-thread-5 NapTask[1] pool-1-thread-2 @@ -915,8 +954,6 @@ NapTask[2] pool-1-thread-3 NapTask[9] pool-1-thread-10 NapTask[6] pool-1-thread-7 NapTask[5] pool-1-thread-6 -*/ - ``` 当你运行这个程序时,你会发现它完成得更快。这是有道理的,而不是使用相同的线程来顺序运行每个任务,每个任务都有自己的线程,所以它们都并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。 @@ -958,7 +995,11 @@ public class CachedThreadPool2 { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` 0 pool-1-thread-1 200 1 pool-1-thread-2 200 4 pool-1-thread-5 300 @@ -969,8 +1010,6 @@ public class CachedThreadPool2 { 7 pool-1-thread-8 800 3 pool-1-thread-4 900 6 pool-1-thread-7 1000 -*/ - ``` 输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们说这样的类不是线程安全的。让我们看看SingleThreadExecutor会发生什么: @@ -989,7 +1028,11 @@ public class SingleThreadExecutor3 { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` 0 pool-1-thread-1 100 1 pool-1-thread-1 200 2 pool-1-thread-1 300 @@ -1000,8 +1043,6 @@ public class SingleThreadExecutor3 { 7 pool-1-thread-1 800 8 pool-1-thread-1 900 9 pool-1-thread-1 1000 -*/ - ``` 现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。线程限制限制了加速,但可以节省很多困难的调试和重写。 @@ -1035,7 +1076,7 @@ public class CountingTask implements Callable { **call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态 -**ExecutorService**允许您使用**invokeAll()**启动集合中的每个Callable: +**ExecutorService**允许你使用**invokeAll()**启动集合中的每个Callable: ```java // concurrent/CachedThreadPool3.java @@ -1066,7 +1107,11 @@ public class CachedThreadPool3 { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` 1 pool-1-thread-2 100 0 pool-1-thread-1 100 4 pool-1-thread-5 100 @@ -1078,11 +1123,10 @@ public class CachedThreadPool3 { 6 pool-1-thread-7 100 7 pool-1-thread-8 100 sum = 1000 -*/ ``` -只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许您提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**: +只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**: ```java // concurrent/Futures.java @@ -1099,10 +1143,13 @@ public class Futures { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` 99 pool-1-thread-1 100 100 -*/ ``` - [1] 当你的任务尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。 @@ -1111,7 +1158,7 @@ public class Futures { 还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。 -因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,您仍会在遗留库中遇到Futures +因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,你仍会在遗留库中遇到Futures 我们可以使用并行Stream以更简单,更优雅的方式解决这个问题: @@ -1131,7 +1178,11 @@ public class CountingStream { .reduce(0, Integer::sum)); } } -/* Output: +``` + +输出结果: + +``` 1 ForkJoinPool.commonPool-worker-3 100 8 ForkJoinPool.commonPool-worker-2 100 0 ForkJoinPool.commonPool-worker-6 100 @@ -1143,16 +1194,15 @@ public class CountingStream { 5 ForkJoinPool.commonPool-worker-2 100 3 ForkJoinPool.commonPool-worker-3 100 1000 -*/ ``` 这不仅更容易理解,我们需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。 - Lambda和方法引用作为任务 -使用lambdas和方法引用,您不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将notRunnables或Callables的参数传递给ExecutorService: +使用lambdas和方法引用,你不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将notRunnables或Callables的参数传递给ExecutorService: -使用lambdas和方法引用,您不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将不是**Runnables**或**Callables**的参数传递给**ExecutorService**: +使用lambdas和方法引用,你不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将不是**Runnables**或**Callables**的参数传递给**ExecutorService**: ```java // concurrent/LambdasAndMethodReferences.java @@ -1182,23 +1232,26 @@ public class LambdasAndMethodReferences { exec.shutdown(); } } -/* Output: +``` + +输出结果: + +``` Lambda1 NotCallable NotRunnable Lambda2 -*/ ``` -这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,您可以在后两次调用的情况下提取结果。 +这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,你可以在后两次调用的情况下提取结果。 ## 终止耗时任务 -并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。您经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当您关闭程序时。 +并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当你关闭程序时。 -最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为您必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 +最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 InterruptedException,因为设计的向后兼容性残留。 @@ -1238,7 +1291,7 @@ public class QuittableTask implements Runnable { - [1]:只要运行标志为true,此任务的run()方法将继续。 - [2]: 显示仅在任务退出时发生。 -需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,您没有任何反馈来告诉您已经做错了。通常,您编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 +需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值 @@ -1262,9 +1315,13 @@ public class QuittingTasks { tasks.forEach(QuittableTask::quit); es.shutdown(); } } -/* Output:24 27 31 8 11 7 19 12 16 4 23 3 28 32 15 20 63 60 68 6764 39 47 52 51 55 40 43 48 59 44 56 36 35 71 72 83 10396 92 88 99 100 87 91 79 75 84 76 115 108 112 104 107111 95 80 147 120 127 119 123 144 143 116 132 124 128 +``` + +输出结果: + +``` +24 27 31 8 11 7 19 12 16 4 23 3 28 32 15 20 63 60 68 6764 39 47 52 51 55 40 43 48 59 44 56 36 35 71 72 83 10396 92 88 99 100 87 91 79 75 84 76 115 108 112 104 107111 95 80 147 120 127 119 123 144 143 116 132 124 128 136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97 -*/ ``` 我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 @@ -1295,7 +1352,12 @@ public class QuittingCompletable { cfutures.forEach(CompletableFuture::join); } } -/* Output:7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3*/ +``` + +输出结果: + +``` +7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3 ``` 任务是一个**List **,就像在**QuittingTasks.java**中一样,但是在这个例子中,没有**peek()**将每个**QuittableTask**提交给**ExecutorService**。相反,在创建cfutures期间,每个任务都交给**CompletableFuture::runAsync**。这执行**VerifyTask.run(**)并返回**CompletableFuture **。因为**run()**不返回任何内容,所以在这种情况下我只使用**CompletableFuture**调用**join()**来等待它完成。 @@ -1386,18 +1448,20 @@ public class CompletableApply { cf4.thenApply(Machina::work); } } -/* Output: +``` + +输出结果: + +``` Machina0: ONE Machina0: TWO Machina0: THREE Machina0: complete -*/ - ``` **thenApply()**应用一个接受输入并产生输出的函数。在这种情况下,**work()**函数产生与它相同的类型,因此每个得到的**CompletableFuture**仍然被输入为**Machina**,但是(类似于**Streams**中的**map()**)**Function**也可以返回不同的类型,这将反映在返回类型 -您可以在此处看到有关**CompletableFutures**的重要信息:它们会在您执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。 +你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。 我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样: @@ -1417,18 +1481,20 @@ public class CompletableApplyChained { System.out.println(timer.duration()); } } -/* Output: +``` + +输出结果: + +``` Machina0: ONE Machina0: TWO Machina0: THREE Machina0: complete 514 -*/ - ``` 在这里,我们还添加了一个**Timer**,它向我们展示每一步增加100毫秒,还有一些额外的开销。 -**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效[^5]。并行流和ComplempleFutures旨在支持这些原则。只要您不决定共享数据(共享非常容易,甚至意外)您可以编写相对安全的并发程序。 +**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效[^5]。并行流和ComplempleFutures旨在支持这些原则。只要你不决定共享数据(共享非常容易,甚至意外)你可以编写相对安全的并发程序。 回调**thenApply()**开始一个操作,在这种情况下,在完成所有任务之前,不会完成**e CompletableFuture**的创建。虽然这有时很有用,但是启动所有任务通常更有价值,这样就可以运行时继续前进并执行其他操作。我们通过在操作结束时添加Async来实现此目的: @@ -1451,7 +1517,11 @@ public class CompletableApplyAsync { System.out.println(timer.duration()); } } -/* Output: +``` + +输出结果: + +``` 116 Machina0: ONE Machina0: TWO @@ -1459,19 +1529,18 @@ Machina0:THREE Machina0: complete Machina0: complete 552 -*/ ``` 同步调用(我们通常使用得那种)意味着“当你完成工作时,返回”,而异步调用以意味着“立刻返回但是继续后台工作。”正如你所看到的,**cf**的创建现在发生得跟快。每次调用 **thenApplyAsync()** 都会立刻返回,因此可以进行下一次调用,整个链接序列的完成速度比以前快得快。 事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了**main()**进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。 -这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将您需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。 +这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将你需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。 -幸运的是,您需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,**CompletableFuture**为您管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 +幸运的是,你需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,**CompletableFuture**为你管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 - 其他操作 -当您查看**CompletableFuture**的Javadoc时,您会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有**thenApply()**,**thenApplyAsync()**和**thenApplyAsync()**的第二种形式,它接受运行任务的**Executor**(在本书中我们忽略了**Executor**选项)。 +当你查看**CompletableFuture**的Javadoc时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有**thenApply()**,**thenApplyAsync()**和**thenApplyAsync()**的第二种形式,它接受运行任务的**Executor**(在本书中我们忽略了**Executor**选项)。 这是一个显示所有“基本”操作的示例,它们不涉及组合两个CompletableFutures或异常(我们将在稍后查看)。首先,我们将重复使用两个实用程序以提供简洁和方便: @@ -1549,7 +1618,11 @@ public class CompletableOperations { System.out.println("dependents: " + c.getNumberOfDependents()); } } -/* Output: +``` + +输出结果: + +``` 1 runAsync thenRunAsync @@ -1568,21 +1641,20 @@ java.util.concurrent.CompletableFuture@6d311334[Complet ed exceptionally] 777 dependents: 1 dependents: 2 -*/ ``` **main()**包含一系列可由其**int**值引用的测试。**cfi(1)**演示了**showr()**正常工作。**cfi(2)**是调用**runAsync()**的示例。由于**Runnable**不产生返回值,因此结果是**CompletableFuture **,因此使用**voidr()**。 注意使用**cfi(3)**,**thenRunAsync()**似乎与**runAsync()**一致,差异显示在后续的测试中: **runAsync()**是一个静态方法,所以你不会像**cfi(2)**一样调用它。相反你可以在**QuittingCompletable.java**中使用它。后续测试中**supplyAsync()**也是静态方法,但是需要一个**Supplier**而不是**Runnable**并产生一个**CompletableFuture**来代替**CompletableFuture**。 -含有“then”的方法将进一步的操作应用于现有的**CompletableFuture **。与**thenRunAsync()**不同的是,将**cfi(4)**,**cfi(5)**和**cfi(6)**的“ then”方法作为未包装的**Integer**的参数。如您通过使用**voidr()**所见,然后**AcceptAsync()**接受了一个**Consumer**,因此不会产生结果。**thenApplyAsync()**接受一个**Function**并因此产生一个结果(该结果的类型可以不同于其参数)。**thenComposeAsync()**与**thenApplyAsync()**非常相似,不同之处在于其Function必须产生已经包装在**CompletableFuture**中的结果。 +含有“then”的方法将进一步的操作应用于现有的**CompletableFuture **。与**thenRunAsync()**不同的是,将**cfi(4)**,**cfi(5)**和**cfi(6)**的“ then”方法作为未包装的**Integer**的参数。如你通过使用**voidr()**所见,然后**AcceptAsync()**接受了一个**Consumer**,因此不会产生结果。**thenApplyAsync()**接受一个**Function**并因此产生一个结果(该结果的类型可以不同于其参数)。**thenComposeAsync()**与**thenApplyAsync()**非常相似,不同之处在于其Function必须产生已经包装在**CompletableFuture**中的结果。 **cfi(7)**示例演示了**obtrudeValue()**,它强制将值作为结果。**cfi(8)**使用**toCompletableFuture()**从**CompletionStage**生成**CompletableFuture**。**c.complete(9)**显示了如何通过给它一个结果来完成一个任务(**future**)(与**obtrudeValue()**相对,后者可能会迫使其结果替换该结果)。 如果你调用**CompletableFuture**中的**cancel()**方法,它也会完成并且是非常好的完成。 如果任务(**future**)未完成,则**getNow()**方法返回**CompletableFuture**的完成值,或者返回**getNow()**的替换参数。 -最后,我们看一下依赖(dependents)的概念。如果我们将两个**thenApplyAsync()**调用链接到**CompletableFuture**上,则依赖项的数量仍为1。但是,如果我们将另一个**thenApplyAsync()**直接附加到**c**,则现在有两个依赖项:两个链和另一个链。这表明您可以拥有一个**CompletionStage**,当它完成时,可以根据其结果派生多个新任务。 +最后,我们看一下依赖(dependents)的概念。如果我们将两个**thenApplyAsync()**调用链接到**CompletableFuture**上,则依赖项的数量仍为1。但是,如果我们将另一个**thenApplyAsync()**直接附加到**c**,则现在有两个依赖项:两个链和另一个链。这表明你可以拥有一个**CompletionStage**,当它完成时,可以根据其结果派生多个新任务。 ### 结合CompletableFutures -第二类**CompletableFuture**方法采用两个**CompletableFuture**并以各种方式将它们组合在一起。一个**CompletableFuture**通常会先于另一个完成,就好像两者都在比赛中一样。这些方法使您可以以不同的方式处理结果。 +第二类**CompletableFuture**方法采用两个**CompletableFuture**并以各种方式将它们组合在一起。一个**CompletableFuture**通常会先于另一个完成,就好像两者都在比赛中一样。这些方法使你可以以不同的方式处理结果。 为了对此进行测试,我们将创建一个任务,该任务将完成的时间作为其参数之一,因此我们可以控制。 **CompletableFuture**首先完成: @@ -1674,7 +1746,11 @@ public class DualCompletableOperations { join(); } } -/* Output: +``` + +输出结果: + +``` Workable[BW] runAfterEither Workable[AW] @@ -1713,14 +1789,13 @@ thenAcceptBoth: Workable[AW], Workable[BW] Workable[AW] ***************** allOf - */ ``` 为了便于访问,**cfA**和**cfB**是静态的。**init()**总是使用较短的延迟(因此总是“获胜”)使用“ B”初始化两者。**join()**是在这两种方法上调用**join()**并显示边框的另一种便捷方法。 所有这些“双重”方法都以一个**CompletableFuture**作为调用该方法的对象,第二个**CompletableFuture**作为第一个参数,然后是要执行的操作。 -通过使用**Shower()**和**void()**,您可以看到“运行”和“接受”是终端操作,而“应用”和“组合”产生了新的承载载荷的**CompletableFutures**。 +通过使用**Shower()**和**void()**,你可以看到“运行”和“接受”是终端操作,而“应用”和“组合”产生了新的承载载荷的**CompletableFutures**。 -方法的名称是不言自明的,您可以通过查看输出来验证这一点。一个特别有趣的方法是CombineAsync(),它等待两个**CompletableFuture**完成,然后将它们都交给BiFunction,然后BiFunction可以将结果加入到所得**CompletableFuture**的有效负载中。 +方法的名称是不言自明的,你可以通过查看输出来验证这一点。一个特别有趣的方法是CombineAsync(),它等待两个**CompletableFuture**完成,然后将它们都交给BiFunction,然后BiFunction可以将结果加入到所得**CompletableFuture**的有效负载中。 ### 模拟 @@ -1814,11 +1889,11 @@ public class FrostedCake { } ``` -一旦您对背后的想法感到满意。**CompletableFutures**它们相对易于使用。 +一旦你对背后的想法感到满意。**CompletableFutures**它们相对易于使用。 ### 例外情况 -与**CompletableFutur**e在处理链中包装对象的方式相同,它还可以缓冲异常。这些不会在处理过程中显示给调用者,而只会在您尝试提取结果时显示。为了展示它们是如何工作的,我们将从创建一个在某些情况下引发异常的类开始: +与**CompletableFutur**e在处理链中包装对象的方式相同,它还可以缓冲异常。这些不会在处理过程中显示给调用者,而只会在你尝试提取结果时显示。为了展示它们是如何工作的,我们将从创建一个在某些情况下引发异常的类开始: ```java // concurrent/Breakable.java @@ -1843,12 +1918,11 @@ public class Breakable { return b; } } - ``` -**failcount**为正时,每次将对象传递给**work()**方法可减少**failcount**。当它为零时,**work()**会引发异常。如果您给它的**failcount**为零,则它永远不会引发异常。 +**failcount**为正时,每次将对象传递给**work()**方法可减少**failcount**。当它为零时,**work()**会引发异常。如果你给它的**failcount**为零,则它永远不会引发异常。 请注意,它报告在抛出异常时抛出异常。 -在下面的**test()**方法中,**work()**多次应用于**Breakable**,因此,如果**failcount**在范围内,则会引发异常。但是,在测试**A**到**E**中,您可以从输出中看到抛出了异常,但是它们从未出现: +在下面的**test()**方法中,**work()**多次应用于**Breakable**,因此,如果**failcount**在范围内,则会引发异常。但是,在测试**A**到**E**中,你可以从输出中看到抛出了异常,但是它们从未出现: ```java // concurrent/CompletableExceptions.java @@ -1893,8 +1967,11 @@ public class CompletableExceptions { } } } +``` -/* Output: +输出结果: + +``` Throwing Exception for A Breakable_B [1] Throwing Exception for B @@ -1920,13 +1997,12 @@ Throwing Exception for H true done? false java.lang.RuntimeException: forced -*/ ``` 测试**A**到**E**运行到抛出异常的地步,然后……什么都没有。只有在测试**F**中调用**get()**时,我们才能看到抛出的异常。 -测试**G**显示,您可以首先检查在处理过程中是否引发了异常,而没有引发该异常。但是,测试H告诉我们,无论异常成功与否,异常仍然可以被视为“完成” +测试**G**显示,你可以首先检查在处理过程中是否引发了异常,而没有引发该异常。但是,测试H告诉我们,无论异常成功与否,异常仍然可以被视为“完成” 代码的最后一部分显示了如何在**CompletableFuture**中插入异常,而不管是否存在任何故障。 -加入或获取结果时,我们不会使用粗略的try-catch,而是使用**CompletableFuture**提供的更复杂的机制来自动响应异常。您可以使用与所有**CompletableFuture**相同的表格来执行此操作:在链中插入**CompletableFuture**调用。有三个选项:**exclusively(**),**handle()**和**whenComplete()**: +加入或获取结果时,我们不会使用粗略的try-catch,而是使用**CompletableFuture**提供的更复杂的机制来自动响应异常。你可以使用与所有**CompletableFuture**相同的表格来执行此操作:在链中插入**CompletableFuture**调用。有三个选项:**exclusively(**),**handle()**和**whenComplete()**: ```java // concurrent/CatchCompletableExceptions.java @@ -1973,7 +2049,11 @@ public class CatchCompletableExceptions { handleException(0); } } -/* Output: +``` + +输出结果: + +``` **** Failure Mode **** Breakable_exceptionally [1] Throwing Exception for exceptionally @@ -2002,12 +2082,11 @@ Breakable_whenComplete [-3] Breakable_whenComplete [-4] Breakable_whenComplete [-4] OK result: Breakable_whenComplete [-4] -*/ ``` 只有在有异常的情况下,**exclusively()**参数才会运行。**Exclusively()**的局限性在于,该函数只能返回输入的相同类型的值。**exclusively()**通过将一个好的对象重新插入流中而恢复到可行状态。 -**handle()**始终被调用,您必须检查一下**fail**是否为**true**才能查看是否发生了异常。但是**handle()**可以产生任何新类型,因此它使您可以执行处理,而不仅可以像**exception()**那样进行恢复。 -**whenComplete()**就像**handle()**一样,您必须测试是否失败,但是该参数是使用者,并且不会修改正在传递的结果对象。 +**handle()**始终被调用,你必须检查一下**fail**是否为**true**才能查看是否发生了异常。但是**handle()**可以产生任何新类型,因此它使你可以执行处理,而不仅可以像**exception()**那样进行恢复。 +**whenComplete()**就像**handle()**一样,你必须测试是否失败,但是该参数是使用者,并且不会修改正在传递的结果对象。 ### 流异常 @@ -2041,20 +2120,23 @@ public class StreamExceptions { } } } -/* Output: +``` + +输出结果: + +``` Entering try Breakable_C [2] Breakable_C [1] Throwing Exception for C Breakable_C failed -*/ ``` -使用**CompletableFutures**,我们看到了测试**A**到**E**的进展,但是使用**Streams**,直到您应用了终端操作(如[1]的**forEach()**),一切都没有开始。**CompletableFuture**执行工作并捕获任何异常以供以后检索。比较这两者并不是一件容易的事,因为**Stream**没有终端操作根本无法执行任何操作,但是**Stream**绝对不会存储其异常。 +使用**CompletableFutures**,我们看到了测试**A**到**E**的进展,但是使用**Streams**,直到你应用了终端操作(如[1]的**forEach()**),一切都没有开始。**CompletableFuture**执行工作并捕获任何异常以供以后检索。比较这两者并不是一件容易的事,因为**Stream**没有终端操作根本无法执行任何操作,但是**Stream**绝对不会存储其异常。 ### 检查异常 -CompletableFutures和并行Streams都不支持包含已检查异常的操作。相反,您必须在调用操作时处理检查到的异常,这会产生不太优雅的代码: +CompletableFutures和并行Streams都不支持包含已检查异常的操作。相反,你必须在调用操作时处理检查到的异常,这会产生不太优雅的代码: ```java // concurrent/ThrowsChecked.java @@ -2095,15 +2177,15 @@ public class ThrowsChecked { } ``` -如果您尝试像对 **nochecked()** 一样对 **withchecked()** 使用方法引用,则编译器会抱怨[1]和[2]。相反,您必须写出lambda表达式(或编写一个不会引发异常的包装器方法)。 +如果你尝试像对 **nochecked()** 一样对 **withchecked()** 使用方法引用,则编译器会抱怨[1]和[2]。相反,你必须写出lambda表达式(或编写一个不会引发异常的包装器方法)。 ## 死锁 -由于任务可能会被阻塞,因此一个任务有可能卡在等待另一个任务上,而任务又在等待另一个任务,依此类推,直到链回到第一个任务上。您会遇到一个不断循环的任务,彼此等待,没有人能动。这称为死锁[^6] -如果您尝试运行某个程序并立即陷入死锁,则可以立即查找该错误。真正的问题是,当您的程序看起来运行良好,但具有隐藏潜力死锁。在这里,您可能没有任何迹象表明可能发生死锁,因此该缺陷在您的程序中是潜在的,直到它意外发生为止(通常是对客户而言(几乎肯定很难复制))。因此,通过仔细的程序设计防止死锁是开发并发系统的关键部分。 +由于任务可能会被阻塞,因此一个任务有可能卡在等待另一个任务上,而任务又在等待另一个任务,依此类推,直到链回到第一个任务上。你会遇到一个不断循环的任务,彼此等待,没有人能动。这称为死锁[^6] +如果你尝试运行某个程序并立即陷入死锁,则可以立即查找该错误。真正的问题是,当你的程序看起来运行良好,但具有隐藏潜力死锁。在这里,你可能没有任何迹象表明可能发生死锁,因此该缺陷在你的程序中是潜在的,直到它意外发生为止(通常是对客户而言(几乎肯定很难复制))。因此,通过仔细的程序设计防止死锁是开发并发系统的关键部分。 埃德斯·迪克斯特拉(Essger Dijkstra)发明的"哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数字)。这些哲学家将一部分时间花在思考上,一部分时间在吃饭上。他们在思考的时候并不需要任何共享资源,但是他们使用的餐具数量有限。在最初的问题描述中,器物是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子。显然,每个哲学家都需要两个筷子才能吃饭。 引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更普遍地说,筷子的数量与哲学家相同)。它们之间围绕桌子隔开。当一个哲学家想要吃饭时,该哲学家必须拿起左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到必要的筷子可用为止。 -**StickHolder**类通过将单个筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果您调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: +**StickHolder**类通过将单个筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: ```java // concurrent/StickHolder.java @@ -2198,17 +2280,17 @@ public class DiningPhilosophers { } ``` -当您停止查看输出时,该程序将死锁。但是,根据您的计算机配置,您可能不会看到死锁。看来这取决于计算机上的内核数7。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为您可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为您不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 +当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数7。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为你可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为你不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 在DiningPhilosophers构造函数中,每个哲学家都获得一个左右StickHolder的引用。除最后一个哲学家外,每个哲学家都通过以下方式初始化: 哲学家之间的下一双筷子。最后一位哲学家右手的筷子为零,因此圆桌会议完成了。那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右摇杆,将最后一个哲学家缠绕在第一个哲学家的旁边。 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 要开始在[3]上运行的每个Philosopher,我调用runAsync(),这意味着DiningPhilosophers构造函数立即在[4]处返回。没有任何东西可以阻止main()完成,该程序只是退出而无济于事。Nap对象阻止main()退出,然后在三秒钟后强制退出(可能是)死锁的程序。 -在给定的配置中,哲学家几乎没有时间思考。因此,他们都在尝试吃饭时争夺筷子,而且僵局往往很快发生。您可以更改此: +在给定的配置中,哲学家几乎没有时间思考。因此,他们都在尝试吃饭时争夺筷子,而且僵局往往很快发生。你可以更改此: 1. 通过增加[4]的值来添加更多哲学家。 2. 在Philosopher.java中取消注释行[1]。 -任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。您可以轻松地说服自己该程序没有死锁,即使它不是。这个例子很有趣,因为它演示了程序似乎可以正确运行,同时仍然容易出现死锁。 +任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个例子很有趣,因为它演示了程序似乎可以正确运行,同时仍然容易出现死锁。 为了解决该问题,我们观察到当四个同时满足条件: 1. 互斥。任务使用的至少一种资源必须不可共享。在这里,筷子一次只能由一位哲学家使用。 @@ -2216,19 +2298,20 @@ public class DiningPhilosophers { 3. 不能抢先从任务中夺走资源。任务仅作为正常事件释放资源。我们的哲学家很有礼貌,他们不会抓住其他哲学家的筷子。 4. 可能发生循环等待,即一个任务等待另一个任务持有的资源,而该任务又等待另一个任务持有的资源,依此类推,直到一个任务正在等待另一个任务持有的资源。第一项任务,从而使一切陷入僵局。在**DiningPhilosophers.java**中,发生循环等待是因为每个哲学家都先尝试获取右筷子,然后再获取左筷子。 -因为必须满足所有这些条件才能导致死锁,所以您只能阻止其中一个解除死锁。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 +因为必须满足所有这些条件才能导致死锁,所以你只能阻止其中一个解除死锁。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 -这只是解决问题的一种方法。您也可以通过防止其他情况之一来解决它。 -没有语言支持可以帮助防止死锁;您有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 +这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。 +没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 ## 构造函数非线程安全 -当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前没人能见到这个对象,所以又怎么会产生对于这个对象的争议呢?确实,java语言规范(JLS)自信满满地陈述道:“没有必要使构造器同步,因为它会锁定正在构造的对象,而这通常会使得该对象直到其所有构造器完成所有工作后,才对其他线程可见。” -不幸的是,对象构造过程像其他任何事物一样容易受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 -考虑使用静态字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始: +当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,Java 语言规范(JLS)自信满满地陈述道:“没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。” +不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 + +设想下使用一个静态字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: ```java //concurrent/HasID.java @@ -2237,7 +2320,7 @@ public interface HasID { } ``` -然后StaticIDField类以显式方式实现该接口: +然后 **StaticIDField** 类显式地实现该接口。代码示例: ```java // concurrent/StaticIDField.java @@ -2248,7 +2331,7 @@ public class StaticIDField implements HasID { } ``` -正如您所想的,此类是一个简单无害的类,它甚至没有一个显式的构造器来引发问题。当我们运行多个用于创建此类对象的线程时,究竟会发生什么,为了搞清楚这点,我们做了以下测试: +如你所想,该类是个简单无害的类,它甚至都没一个显式的构造器来引发问题。当我们运行多个用于创建此类对象的线程时,究竟会发生什么?为了搞清楚这点,我们做了以下测试。代码示例: ```java // concurrent/IDChecker.java @@ -2293,10 +2376,10 @@ public class IDChecker { } ``` -MakeObjects类是一个供应者类,包含一个能够产生List\类型的列表对象的get()方法。通过从每个HasID对象提取ID并放入列表中来生成这个列表对象,而test()方法则创建了两个并行的CompletableFuture对象,用于运行MakeObjects供应者类,然后获取运行结果。 -使用Guava库中的Sets.intersection()方法,计算出这两个返回的List\对象中有多少相同的ID(使用谷歌Guava库里的方法比使用官方的retainAll()方法速度快得多)。 +**MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 +使用 Guava 库中的 **Sets.**`intersection()` 方法,计算出这两个返回的 List\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。 -现在我们可以测试上面的StaticIDField类了: +现在我们可以测试上面的 **StaticIDField** 类了。代码示例: ```java // concurrent/TestStaticIDField.java @@ -2306,12 +2389,15 @@ public class TestStaticIDField { IDChecker.test(StaticIDField::new); } } -/* Output: +``` + +输出结果: + +``` 13287 -*/ ``` -结果中出现了很多重复项。很显然,纯静态int用于构造过程并不是线程安全的。让我们使用AtomicInteger来使其变为线程安全的: +结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例: ```java // concurrent/GuardedIDField.java @@ -2327,9 +2413,12 @@ public class GuardedIDField implements HasID { public static void main(String[] args) { IDChecker.test(GuardedIDField::new); } } -/* Output: +``` + +输出结果: + +``` 0 -*/ ``` 构造器有一种更微妙的状态共享方式:通过构造器参数: @@ -2379,14 +2468,17 @@ public class SharedConstructorArgument{ IDChecker.test(() -> new SharedUser(safe)); } } -/* Output: +``` + +输出结果: + +``` 24838 0 -*/ ``` -在这里,SharedUser构造器实际上共享了相同的参数。即使SharedUser以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。SharedUser甚至不知道它是以这种方式调用的,更不必说控制它了。 -同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:Low-Level Concurrency,来进一步了解同步关键字——synchronized)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: +在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 +同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: ```java // concurrent/SynchronizedConstructor.java @@ -2416,10 +2508,12 @@ public class SynchronizedConstructor{ IDChecker.test(() -> new SyncConstructor(unsafe)); } } -/* Output: - 0 -*/ +``` + +输出结果: +``` + 0 ``` Unsafe类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: @@ -2451,17 +2545,20 @@ public class SynchronizedFactory{ IDChecker.test(() -> SyncFactory.factory(unsafe)); } } -/* Output: +``` + +输出结果: + +``` 0 -*/ ``` -通过同步静态工厂方法,可以在构造过程中锁定Class对象。 -这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使您采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 +通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 +这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 ## 复杂性和代价 -假设您正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分: +假设你正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分: @@ -2562,7 +2659,11 @@ public class OnePizza{ })); } } -/* Output: +``` + +输出结果: + +``` Pizza 0: ROLLED Pizza 0: SAUCED Pizza 0: CHEESED @@ -2571,10 +2672,9 @@ Pizza 0: BAKED Pizza 0: SLICED Pizza 0: BOXED 1622 -*/ ``` -时间以毫秒为单位,加总所有步骤的工作量,会得出与我们的期望值相符的数字。 如果您以这种方式制作了五个披萨,那么您会认为它花费的时间是原来的五倍。 但是,如果这还不够快怎么办? 我们可以从尝试并行流方法开始: +时间以毫秒为单位,加总所有步骤的工作量,会得出与我们的期望值相符的数字。 如果你以这种方式制作了五个披萨,那么你会认为它花费的时间是原来的五倍。 但是,如果这还不够快怎么办? 我们可以从尝试并行流方法开始: ```java // concurrent/PizzaStreams.java @@ -2593,7 +2693,11 @@ public class PizzaStreams{ .forEach(za -> { while(!za.complete()) za.next(); }); System.out.println(timer.duration()); } } -/* Output: +``` + +输出结果: + +``` Pizza 2: ROLLED Pizza 0: ROLLED Pizza 1: ROLLED @@ -2630,12 +2734,11 @@ Pizza 0:BOXED Pizza 4:BOXED Pizza 3:BOXED 1739 -*/ ``` 现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将QUANTITY更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 -PizzaStreams类产生的每个并行流在它的forEach()内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? +**PizzaStreams** 类产生的每个并行流在它的forEach()内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? ```java // concurrent/PizzaParallelSteps.java @@ -2663,7 +2766,11 @@ public class PizzaParallelSteps{ System.out.println(timer.duration()); } } -/* Output: +``` + +输出结果: + +``` Pizza 2: ROLLED Pizza 0: ROLLED Pizza 1: ROLLED @@ -2705,12 +2812,11 @@ Pizza 4: BOXED Pizza4: complete Pizza3: complete 1738 -*/ ``` -答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的PizzaParallelSteps.java里面展示的一样。 +答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的 `PizzaParallelSteps.java` 里面展示的一样。 -我们可以使用CompletableFutures重写这个例子: +我们可以使用 **CompletableFutures** 重写这个例子: ```java // concurrent/CompletablePizza.java @@ -2755,7 +2861,11 @@ public class CompletablePizza{ System.out.println(timer.duration()); } } -/* Output: +``` + +输出结果: + +``` 169 Pizza 0: ROLLED Pizza 1: ROLLED @@ -2798,18 +2908,17 @@ Pizza2: complete Pizza3: complete Pizza4: complete 1797 -*/ ``` -并行流和CompletableFutures是Java并发工具箱中最先进发达的技术。 您应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果您决定不借助它而由自己完成,您就必须撸起袖子,深入研究Spliterator的文档)。 +并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究Spliterator的文档)。 -而当工作的各个部分内容各不相同时,使用CompletableFutures是最好的选择。比起面向数据,CompletableFutures更像是面向任务的。 +而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,CompletableFutures** 更像是面向任务的。 对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。 -由于制作披萨总需要一定的时间,无论您使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当您处理更复杂的问题时,您就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止您因为采取无用的加快运行速度的举措而忙得团团转。 +由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。 -使用CompletableFutures或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 +使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 @@ -2820,7 +2929,7 @@ Pizza4: complete [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 [^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 -[^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,您也可以使用活动锁。 +[^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。
From 3e9902373c973a06e4cbb0744568cfa811db2691 Mon Sep 17 00:00:00 2001 From: LeonTheProfessional Date: Sun, 19 Jan 2020 19:40:03 +0800 Subject: [PATCH 157/371] =?UTF-8?q?=E4=B8=80=E4=BA=9B=E5=8B=98=E8=AF=AF=20?= =?UTF-8?q?(#358)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 更改了移位运算符中较有误导性的描述 “仅使用右侧的5 个低阶位” 中的“右侧”之前主语不明确,容易引起歧义。改为“仅使用右值的5 个低阶位” 则明确表示为等号右侧值的5个低阶位,使语义更清晰。 * 翻译勘误 ”数字的二进制表示称为有符号的两个补数。“这句的原文为“The binary representation of the numbers is referred to as signed twos complement”。这里的“2's complement” 为计算机术语“补码”,所以这句话应翻译为“数字的二进制表示形式是带符号的补码”。 * 内部类勘误 row:913 原文为Calleel,这里可能将1写成了字母l row:1429 原文为LocalInnerClass$1LocalCounter.class,根据我的理解应该为LocalInnerClass$LocalCounter.class,请主编校对 --- docs/book/11-Inner-Classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index b4157f75..ce3e0043 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -910,7 +910,7 @@ Other operation 3 ``` -这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Calleel** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。 +这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。 注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。 内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。 @@ -1426,7 +1426,7 @@ Anonymous inner 9 ```java Counter.class LocalInnerClass$1.class -LocalInnerClass$1LocalCounter.class +LocalInnerClass$LocalCounter.class LocalInnerClass.class ``` From 74fd7f7104b00d502c953fce69da62d9fcee7cdb Mon Sep 17 00:00:00 2001 From: Qimiao Chen Date: Mon, 20 Jan 2020 16:13:34 +0800 Subject: [PATCH 158/371] Fix typo in Streams (#359) --- docs/book/14-Streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 764d3ac9..3633550e 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -42,7 +42,7 @@ public class Randoms { 首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了数值产生的边界。这将生成一个整数流。我们可以使用中间流操作(intermediate stream operation) `distinct()` 来获取它们的非重复值,然后使用 `limit()` 方法获取前 7 个元素。接下来,我们使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对每个流对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用。`System.out::println` 。 -注意 `Randoms.java` 中没有声明任何变量。流流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。 +注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。 声明式编程(Declarative programming)是一种:声明要做什么,而非怎么做的编程风格。正如我们在函数式编程中所看到的。**注意**,命令式编程的形式更难以理解。代码示例: From ab08db16aa9152d412a57fd653c3ca7ecb927380 Mon Sep 17 00:00:00 2001 From: Crimson_Loves_Code <39024757+OrientationJump@users.noreply.github.com> Date: Tue, 21 Jan 2020 14:05:37 +0800 Subject: [PATCH 159/371] =?UTF-8?q?Fix=20issue:#112=20=E7=BF=BB=E8=AF=91Su?= =?UTF-8?q?mmary=20(#360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: crimson <1291463831@qq.com> --- docs/book/24-Concurrent-Programming.md | 97 +++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 5a82b1e6..9ecea280 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -247,7 +247,7 @@ Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入 这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得你的生活比旧的替代品更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 -- Parallel Streams(并发流) +- Parallel Streams(并行流) 到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在你对该语法(作为一个粉丝,我希望)感到满意,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 @@ -2280,7 +2280,7 @@ public class DiningPhilosophers { } ``` -当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数7。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为你可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为你不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 +当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为你可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为你不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 在DiningPhilosophers构造函数中,每个哲学家都获得一个左右StickHolder的引用。除最后一个哲学家外,每个哲学家都通过以下方式初始化: 哲学家之间的下一双筷子。最后一位哲学家右手的筷子为零,因此圆桌会议完成了。那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右摇杆,将最后一个哲学家缠绕在第一个哲学家的旁边。 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 @@ -2308,10 +2308,10 @@ public class DiningPhilosophers { ## 构造函数非线程安全 -当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,Java 语言规范(JLS)自信满满地陈述道:“没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。” +当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” 不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 -设想下使用一个静态字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: +设想下使用一个**静态**字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: ```java //concurrent/HasID.java @@ -2516,7 +2516,7 @@ public class SynchronizedConstructor{ 0 ``` -Unsafe类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: +**Unsafe**类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: ```java // concurrent/SynchronizedFactory.java @@ -2642,7 +2642,7 @@ public class Pizza{ } ``` -这只算得上是一个简单的状态机,就像Machina类一样。 +这只算得上是一个平凡的状态机,就像**Machina**类一样。 制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行: @@ -2736,9 +2736,9 @@ Pizza 3:BOXED 1739 ``` -现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将QUANTITY更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 +现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将**QUANTITY**更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 -**PizzaStreams** 类产生的每个并行流在它的forEach()内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? +**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? ```java // concurrent/PizzaParallelSteps.java @@ -2910,9 +2910,9 @@ Pizza4: complete 1797 ``` -并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究Spliterator的文档)。 +并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究**Spliterator**的文档)。 -而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,CompletableFutures** 更像是面向任务的。 +而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。 对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。 @@ -2924,12 +2924,87 @@ Pizza4: complete ## 本章小结 -[^1]:例如,Eric-Raymond在“VIIX编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 +需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“您的程序运行速度还不够快”。 + +如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以您应该仔细考虑是否值得为此付出努力,并考虑您能否以其他方式提升速度。 + +例如,迁移到更快的硬件(这可能比消耗程序员的时间要便宜得多)或者将程序分解成多个部分,然后在不同的机器上运行这些部分。 + +奥卡姆剃刀是一个经常被误解的原则。 我看过至少一部电影,他们将其定义为”最简单的解决方案是正确的解决方案“,就好像这是某种毋庸置疑的法律。实际上,这是一个准则:面对多种方法时,请先尝试需要最少假设的方法。 在编程世界中,这已演变为“尝试可能可行的最简单的方法”。当您了解了特定工具的知识时——就像你现在了解了有关并发性的知识一样,你可能会很想使用它,或者提前规定你的解决方案必须能够“速度飞快”,从而来证明从一开始就进行并发设计是合理的。但是,我们的奥卡姆剃刀编程版本表示您应该首先尝试最简单的方法(这种方法开发起来也更便宜),然后看看它是否足够好。 + +由于我出身于底层学术背景(物理学和计算机工程),所以我很容易想到所有小轮子转动的成本。我确定使用最简单的方法不够快的场景出现的次数已经数不过来了,但是尝试后却发现它实际上绰绰有余。 + +### 缺点 + +并发编程的主要缺点是: + +1. 在线程等待共享资源时会降低速度。 + +2. 线程管理产生额外CPU开销。 + +3. 糟糕的设计决策带来无法弥补的复杂性。 + +4. 诸如饥饿,竞速,死锁和活锁(多线程各自处理单个任务而整体却无法完成)之类的问题。 + +5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果您在后者上开发程序,则在分发程序时可能会感到非常惊讶。 + + + +另外,并发的应用是一门艺术。 Java旨在允许您创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,您只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使您使用完全不同的方案。 + +### 共享内存陷阱 + +并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且您必须确保多个任务不会同时读取和更改该资源。 + +我花了多年的时间研究并发并发。 我了解到您永远无法相信使用共享内存并发的程序可以正常工作。 您可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] + +我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上您可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 + +再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给您一种一切都棒极了的印象。当涉及到共享内存并发编程时,您永远不应该对自己的编程能力变得过于自信。 + + + +### This Albatross is Big + +如果您对Java并发感到不知所措,那说明您身处在一家出色的公司里。您 可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 + +事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当您使用旧代码时,仍然会遇到旧的解决方案。 + +在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,您很容易迷失于旧的设计中。 + +### 其他类库 + +本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免您不知所措,我没有介绍您可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是您还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 + +### 考虑为并发设计的语言 + +通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设您不尝试共享内存)使您摆脱麻烦(在Java的世界范围内)。 + +如果您的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使您可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与您的主要Java代码进行交互。 或者,您可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11] + +你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许您通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许您在Scala中完成代码)。 + +心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 + +无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在您最不期望出现的时候咬您。 + +### 拓展阅读 + +《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 + + + +[^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 [^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 [^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。 +[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的Java版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩ +[^8]: 库就在那里用于调用,而语言本身就被设计用于此目的,但实际上它很少发生,以至于可以说”没有“。↩ +[^9]: 举例来说,如果没有Flyweight设计模式,在工程中创建数百万个对象用于有限元分析可能在Java中不可行。↩ +[^10]: 在科学中,虽然从来没有一种理论被证实过,但是一种理论必须是可证伪的才有意义。而对于并发性,我们大部分时间甚至都无法得到这种可证伪性。↩ +[^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。
From d6e00fcbb41b30c9574911d95150fb2e0ab55543 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Tue, 21 Jan 2020 16:41:33 +0800 Subject: [PATCH 160/371] =?UTF-8?q?chapter=2024=20=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=BC=96=E7=A8=8B=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a247bf8..7d5c6774 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) - [x] [第二十三章 注解](docs/book/23-Annotations.md) -- [ ] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) +- [x] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) - [ ] [第二十五章 设计模式](docs/book/25-Patterns.md) - [x] [附录:补充](docs/book/Appendix-Supplements.md) - [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) From ee50c09b55b7ead39cb0809f68e21e1dab3e7415 Mon Sep 17 00:00:00 2001 From: dellenovo Date: Tue, 21 Jan 2020 18:04:01 +0800 Subject: [PATCH 161/371] =?UTF-8?q?Fix=20issue=20#362:=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E7=BF=BB=E8=AF=91What=E2=80=99s=20impressive=20is=20it=20is=20?= =?UTF-8?q?not=20intellectually=20prohibitive=20to=20assemble=20such=20a?= =?UTF-8?q?=20model=20(#363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: lifei.zhang --- docs/book/20-Generics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 2c02e10a..09b0364c 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1230,7 +1230,7 @@ public class Store extends ArrayList { */ ``` -`Store.toString()` 显示了结果:尽管有复杂的层次结构,但多层的集合仍然是类型安全的和可管理的。令人印象深刻的是,组装这样的模型在理论上并不是禁止的。 +`Store.toString()` 显示了结果:尽管有复杂的层次结构,但多层的集合仍然是类型安全的和可管理的。令人印象深刻的是,组装这样的模型并不需要耗费过多精力。 **Shelf** 使用 `Suppliers.fill()` 这个实用程序,该实用程序接受 **Collection** (第一个参数),并使用 **Supplier** (第二个参数),以元素的数量为 **n** (第三个参数)来填充它。 **Suppliers** 类将会在本章末尾定义,其中的方法都是在执行某种填充操作,并在本章的其他示例中使用。 @@ -5185,4 +5185,4 @@ Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到 -
\ No newline at end of file +
From 2010769b2403cd49223d888e8e1f4674318cc7b1 Mon Sep 17 00:00:00 2001 From: Crimson_Loves_Code <39024757+OrientationJump@users.noreply.github.com> Date: Wed, 22 Jan 2020 09:31:31 +0800 Subject: [PATCH 162/371] fix issue:#161 (#364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix issue:#112 翻译Summary Signed-off-by: crimson <1291463831@qq.com> * fix issue:#161 翻译工厂模式 Signed-off-by: crimson <1291463831@qq.com> * fix issue:#161 Signed-off-by: crimson <1291463831@qq.com> --- docs/book/25-Patterns.md | 360 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 360 insertions(+) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index 9bd8e81c..22ce3cfb 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -425,8 +425,368 @@ Spinning ## 工厂模式 +当你发现必须将新类型添加到系统中时,合理的第一步是使用多态性为这些新类型创建一个通用接口。这会将你系统中的其余代码与要添加的特定类型的信息分开,使得可以在不改变现有代码的情况下添加新类型……或者看起来如此。起初,在这种设计中,似乎你必须更改代码的唯一地方就是你继承新类型的地方,但这并不是完全正确的。 你仍然必须创建新类型的对象,并且在创建时必须指定要使用的确切构造器。因此,如果创建对象的代码分布在整个应用程序中,那么在添加新类型时,你将遇到相同的问题——你仍然必须追查你代码中新类型碍事的所有地方。恰好是类型的创建碍事,而不是类型的使用(通过多态处理),但是效果是一样的:添加新类型可能会引起问题。 + +解决方案是强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。 + +由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。 + +举例来说,让我们重新看一下**Shape**系统。 首先,我们需要一个用于所有示例的基本框架。 如果无法创建**Shape**对象,则需要抛出一个合适的异常: + +```java +// patterns/shapes/BadShapeCreation.java package patterns.shapes; +public class BadShapeCreation extends RuntimeException { + public BadShapeCreation(String msg) { + super(msg); + } +} +``` + +接下来,是一个**Shape**基类: + +```java +// patterns/shapes/Shape.java +package patterns.shapes; +public class Shape { + private static int counter = 0; + private int id = counter++; + @Override + public String toString(){ + return getClass().getSimpleName() + "[" + id + "]"; + } + public void draw() { + System.out.println(this + " draw"); + } + public void erase() { + System.out.println(this + " erase"); + } +} +``` + +该类自动为每一个**Shape**对象创建一个唯一的`id`。 + +`toString()`使用运行期信息来发现特定的**Shape**子类的名字。 + +现在我们能很快创建一些**Shape**子类了: + +```java +// patterns/shapes/Circle.java +package patterns.shapes; +public class Circle extends Shape {} +``` + +```java +// patterns/shapes/Square.java +package patterns.shapes; +public class Square extends Shape {} +``` + +```java +// patterns/shapes/Triangle.java +package patterns.shapes; +public class Triangle extends Shape {} +``` + +工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口: + +```java +// patterns/shapes/FactoryMethod.java +package patterns.shapes; +public interface FactoryMethod { + Shape create(String type); +} +``` + +`create()`接收一个参数,这个参数使其决定要创建哪一种**Shape**对象,这里是`String`,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂: + +```java +// patterns/shapes/FactoryTest.java +package patterns.shapes; +import java.util.stream.*; +public class FactoryTest { + public static void test(FactoryMethod factory) { + Stream.of("Circle", "Square", "Triangle", + "Square", "Circle", "Circle", "Triangle") + .map(factory::create) + .peek(Shape::draw) + .peek(Shape::erase) + .count(); // Terminal operation + } +} +``` + +在主函数`main()`里,要记住除非你在最后使用了一个终结操作,否则**Stream**不会做任何事情。在这里,`count()`的值被丢弃了。 + +创建工厂的一种方法是显式创建每种类型: + +```java +// patterns/ShapeFactory1.java +// A simple static factory method +import java.util.*; +import java.util.stream.*; +import patterns.shapes.*; +public class ShapeFactory1 implements FactoryMethod { + public Shape create(String type) { + switch(type) { + case "Circle": return new Circle(); + case "Square": return new Square(); + case "Triangle": return new Triangle(); + default: throw new BadShapeCreation(type); + } + } + public static void main(String[] args) { + FactoryTest.test(new ShapeFactory1()); + } +} +``` + +输出结果: + +```java +Circle[0] draw +Circle[0] erase +Square[1] draw +Square[1] erase +Triangle[2] draw +Triangle[2] erase +Square[3] draw +Square[3] erase +Circle[4] draw +Circle[4] erase +Circle[5] draw +Circle[5] erase +Triangle[6] draw +Triangle[6] erase +``` + +`create()`现在是添加新类型的Shape时系统中唯一需要更改的其他代码。 + +### 动态工厂 + +前面例子中的**静态**`create()`方法强制所有创建操作都集中在一个位置,因此这是添加新类型的**Shape**时唯一必须更改代码的地方。这当然是一个合理的解决方案,因为它把创建对象的过程限制在一个框内。但是,如果你在添加新类时无需修改任何内容,那就太好了。 以下版本使用反射在首次需要时将**Shape**的构造器动态加载到工厂列表中: + +```java +// patterns/ShapeFactory2.java +import java.util.*; +import java.lang.reflect.*; +import java.util.stream.*; +import patterns.shapes.*; +public class ShapeFactory2 implements FactoryMethod { + Map factories = new HashMap<>(); + static Constructor load(String id) { + System.out.println("loading " + id); + try { + return Class.forName("patterns.shapes." + id) + .getConstructor(); + } catch(ClassNotFoundException | + NoSuchMethodException e) { + throw new BadShapeCreation(id); + } + } + public Shape create(String id) { + try { + return (Shape)factories + .computeIfAbsent(id, ShapeFactory2::load) + .newInstance(); + } catch(InstantiationException | + IllegalAccessException | + InvocationTargetException e) { + throw new BadShapeCreation(id); + } + } + public static void main(String[] args) { + FactoryTest.test(new ShapeFactory2()); + } +} +``` + +输出结果: + +```java +loading Circle +Circle[0] draw +Circle[0] erase +loading Square +Square[1] draw +Square[1] erase +loading Triangle +Triangle[2] draw +Triangle[2] erase +Square[3] draw +Square[3] erase +Circle[4] draw +Circle[4] erase +Circle[5] draw +Circle[5] erase +Triangle[6] draw +Triangle[6] erase +``` + +和之前一样,`create()`方法基于你传递给它的**String**参数生成新的**Shape**s,但是在这里,它是通过在**HashMap**中查找作为键的**String**来实现的。 返回的值是一个构造器,该构造器用于通过调用`newInstance()`创建新的**Shape**对象。 + +然而,当你开始运行程序时,工厂的`map`为空。`create()`使用`map`的`computeIfAbsent()`方法来查找构造器(如果该构造器已存在于`map`中)。如果不存在则使用`load()`计算出该构造器,并将其插入到`map`中。 从输出中可以看到,每种特定类型的**Shape**都是在第一次请求时才加载的,然后只需要从`map`中检索它。 + +### 多态工厂 + +《设计模式》这本书强调指出,采用“工厂方法”模式的原因是可以从基本工厂中继承出不同类型的工厂。 再次修改示例,使工厂方法位于单独的类中: + +```java +// patterns/ShapeFactory3.java +// Polymorphic factory methods +import java.util.*; +import java.util.function.*; +import java.util.stream.*; +import patterns.shapes.*; +interface PolymorphicFactory { + Shape create(); +} +class RandomShapes implements Supplier { + private final PolymorphicFactory[] factories; + private Random rand = new Random(42); + RandomShapes(PolymorphicFactory... factories){ + this.factories = factories; + } + public Shape get() { + return factories[ rand.nextInt(factories.length)].create(); + } +} +public class ShapeFactory3 { + public static void main(String[] args) { + RandomShapes rs = new RandomShapes( + Circle::new, + Square::new, + Triangle::new); + Stream.generate(rs) + .limit(6) + .peek(Shape::draw) + .peek(Shape::erase) + .count(); + } +} +``` + +输出结果: + +```java +Triangle[0] draw +Triangle[0] erase +Circle[1] draw +Circle[1] erase +Circle[2] draw +Circle[2] erase +Triangle[3] draw +Triangle[3] erase +Circle[4] draw +Circle[4] erase +Square[5] draw +Square[5] erase +``` + +**RandomShapes**实现了**Supplier \**,因此可用于通过`Stream.generate()`创建**Stream**。 它的构造器采用**PolymorphicFactory**对象的可变参数列表。 变量参数列表以数组形式出现,因此列表是以数组形式在内部存储的。`get()`方法随机获取此数组中一个对象的索引,并在结果上调用`create()`以产生新的**Shape**对象。 添加新类型的**Shape**时,**RandomShapes**构造器是唯一需要更改的地方。 请注意,此构造器需要**Supplier \**。 我们传递给其**Shape**构造器的方法引用,该引用可满足**Supplier \**约定,因为Java 8支持结构一致性。 + +鉴于**ShapeFactory2.java**可能会抛出异常,使用此方法则没有任何异常——它在编译时完全确定。 + +### 抽象工厂 + +抽象工厂模式看起来像我们之前所见的工厂对象,但拥有不是一个工厂方法而是几个工厂方法, 每个工厂方法都会创建不同种类的对象。 这个想法是在创建工厂对象时,你决定如何使用该工厂创建的所有对象。 《设计模式》中提供的示例实现了跨各种图形用户界面(GUI)的可移植性:你创建一个适合你正在使用的GUI的工厂对象,然后从中请求菜单,按钮,滑块等等,它将自动为GUI创建适合该项目版本的组件。 因此,你可以将从一个GUI更改为另一个所产生的影响隔离限制在一处。 作为另一个示例,假设你正在创建一个通用游戏环境来支持不同类型的游戏。 使用抽象工厂看起来就像下文那样: + +```java +// patterns/abstractfactory/GameEnvironment.java +// An example of the Abstract Factory pattern +// {java patterns.abstractfactory.GameEnvironment} +package patterns.abstractfactory; +import java.util.function.*; +interface Obstacle { + void action(); +} + +interface Player { + void interactWith(Obstacle o); +} + +class Kitty implements Player { + @Override + public void interactWith(Obstacle ob) { + System.out.print("Kitty has encountered a "); + ob.action(); + } +} + +class KungFuGuy implements Player { + @Override + public void interactWith(Obstacle ob) { + System.out.print("KungFuGuy now battles a "); + ob.action(); + } +} + +class Puzzle implements Obstacle { + @Override + public void action() { + System.out.println("Puzzle"); + } +} + +class NastyWeapon implements Obstacle { + @Override + public void action() { + System.out.println("NastyWeapon"); + } +} + +// The Abstract Factory: +class GameElementFactory { + Supplier player; + Supplier obstacle; +} + +// Concrete factories: +class KittiesAndPuzzles extends GameElementFactory { + KittiesAndPuzzles() { + player = Kitty::new; + obstacle = Puzzle::new; + } +} + +class KillAndDismember extends GameElementFactory { + KillAndDismember() { + player = KungFuGuy::new; + obstacle = NastyWeapon::new; + } +} + +public class GameEnvironment { + private Player p; + private Obstacle ob; + + public GameEnvironment(GameElementFactory factory) { + p = factory.player.get(); + ob = factory.obstacle.get(); + } + public void play() { + p.interactWith(ob); + } + public static void main(String[] args) { + GameElementFactory kp = new KittiesAndPuzzles(), kd = new KillAndDismember(); + GameEnvironment g1 = new GameEnvironment(kp), g2 = new GameEnvironment(kd); + g1.play(); + g2.play(); + } +} + +``` + +输出结果: + +```java +Kitty has encountered a Puzzle +KungFuGuy now battles a NastyWeapon +``` + +在这种环境中,**Player**对象与**Obstacle**对象进行交互,但是根据你所玩游戏的类型,存在不同类型的玩家和障碍物。 你可以通过选择特定的**GameElementFactory**来确定游戏的类型,然后**GameEnvironment**控制游戏的设置和玩法。 在此示例中,设置和玩法非常简单,但是这些活动(初始条件和状态变化)可以决定游戏的大部分结果。 这里,**GameEnvironment**不是为继承而设计的,尽管这样做很有意义。 它还包含“双重调度”和“工厂方法”的示例,稍后将对这两个示例进行说明。 + ## 函数对象 From 38fa2d87cc2e8d8511edd9985c7a46f56e2ddd86 Mon Sep 17 00:00:00 2001 From: dellenovo Date: Wed, 22 Jan 2020 20:29:05 +0800 Subject: [PATCH 163/371] =?UTF-8?q?Fix=20issue=20#365:=E6=94=B9=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=AD=97=20(#366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix issue #362:重新翻译What’s impressive is it is not intellectually prohibitive to assemble such a model Signed-off-by: lifei.zhang * Fix issue #362:改两个字 Signed-off-by: lifei.zhang --- docs/book/20-Generics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 09b0364c..b03c5e64 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1454,13 +1454,13 @@ public class ReturnGenericType { 例如,假设一个应用使用了两个类库 **X** 和 **Y**,**Y** 使用了类库 **Z**。随着 Java 5 的出现,这个应用和这些类库的创建者最终可能希望迁移到泛型上。但是当进行迁移时,它们有着不同的动机和限制。为了实现迁移兼容性,每个类库与应用必须与其他所有的部分是否使用泛型无关。因此,它们不能探测其他类库是否使用了泛型。因此,某个特定的类库使用了泛型这样的证据必须被”擦除“。 -如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到 Java 泛型上的开发者们说再见了。类库毫无争议是编程语言的一部分,对生产效率有着极大的影响,所以这种代码无法接受。擦除是否是最佳的活唯一的迁移途径,还待时间来证明。 +如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到 Java 泛型上的开发者们说再见了。类库毫无争议是编程语言的一部分,对生产效率有着极大的影响,所以这种代码无法接受。擦除是否是最佳的或唯一的迁移途径,还待时间来证明。 ### 擦除的问题 因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下将泛型融入到语言中。擦除允许你继续使用现有的非泛型客户端代码,直至客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为它不会骤然破坏所有现有的代码。 -擦除的代码是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、**instanceof** 操作和 **new** 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。 +擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、**instanceof** 操作和 **new** 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。 考虑如下的代码段: From 058720f8c4a23d985b01f5d0a7ae37ec39110bb9 Mon Sep 17 00:00:00 2001 From: Qimiao Chen Date: Thu, 30 Jan 2020 18:15:09 +0800 Subject: [PATCH 164/371] =?UTF-8?q?=E8=AF=AD=E5=8F=A5=E9=94=99=E8=AF=AF=20?= =?UTF-8?q?(#368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 2e38bd9e..cbca57ff 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -8,7 +8,7 @@ 注解在一定程度上是把元数据和源代码文件结合在一起的趋势所激发的,而不是保存在外部文档。这同样是对像 C# 语言对于 Java 语言特性压力的一种回应。 -注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下有下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。 +注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。 注解的语法十分简单,主要是在现有语法中添加 @ 符号。Java 5 引入了前三种定义在 **java.lang** 包中的注解: From 953a122b5f11bcc9eed71f0b4dad941aea837266 Mon Sep 17 00:00:00 2001 From: Qimiao Chen Date: Fri, 31 Jan 2020 10:01:05 +0800 Subject: [PATCH 165/371] Polishing (#369) --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index cbca57ff..070e4ae2 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -107,7 +107,7 @@ public class PasswordUtils { } ``` -注解的元素在使用时表现为 名-值 对的形式,并且需要放置在 `@UseCase` 声明之后的括号内。在 `encryptPassword()` 方法的注解中,并没有给出 **description** 的默认值,所以在 **@interface UseCase** 的注解处理器分析处理这个类的时候会使用该元素的默认值。 +注解的元素在使用时表现为 名-值 对的形式,并且需要放置在 `@UseCase` 声明之后的括号内。在 `encryptPassword()` 方法的注解中,并没有给出 **description** 的值,所以在 **@interface UseCase** 的注解处理器分析处理这个类的时候会使用该元素的默认值。 你应该能够想象到如何使用这套工具来“勾勒”出将要建造的系统,然后在建造的过程中逐渐实现系统的各项功能。 From 5f24880e33dc6628cdb7cd5887494c9cd91933fb Mon Sep 17 00:00:00 2001 From: Qimiao Chen Date: Fri, 31 Jan 2020 14:50:24 +0800 Subject: [PATCH 166/371] Fix typo (#371) --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 070e4ae2..785676c5 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -580,7 +580,7 @@ public class SimpleProcessor 你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。 -**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,让后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 +**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。 From de0e00f1de88e9e01707de585dfbf5378d006471 Mon Sep 17 00:00:00 2001 From: Qimiao Chen Date: Sat, 1 Feb 2020 14:21:13 +0800 Subject: [PATCH 167/371] Polising (#370) * Polishing * Polishing * Polishing * Update 23-Annotations.md --- docs/book/23-Annotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 785676c5..8448423f 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -208,7 +208,7 @@ public @interface SimulatingNull { 当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。 -假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库名称、数据库列以及将 SQL 类型映射到属性的注解。 +假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。 以下是一个注解的定义,它告诉注解处理器应该创建一个数据库表: From 4b2d43edf4549fec1d265f0ae4787b7efde03722 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 2 Feb 2020 19:54:24 +0800 Subject: [PATCH 168/371] Update 03-Objects-Everywhere.md #367 --- docs/book/03-Objects-Everywhere.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index 95cf3898..e1ab1282 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -82,11 +82,18 @@ Java 确定了每种基本类型的内存占用大小。 这些大小不会像 char c = 'x'; Character ch = new Character(c); ``` -或者你也可以使用下面的形式,基本类型自动转换成包装类型(自动装箱): +或者你也可以使用下面的形式: ```java Character ch = new Character('x'); ``` + +基本类型自动转换成包装类型(自动装箱) + +```java +Character ch = 'x'; +``` + 相对的,包装类型转化为基本类型(自动拆箱): ```java From edd8100cdfdacca9b4e7a6addbff8f621e93a767 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sun, 2 Feb 2020 19:56:09 +0800 Subject: [PATCH 169/371] Update 03-Objects-Everywhere.md close #367 --- docs/book/03-Objects-Everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index e1ab1282..e6ebe9d9 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -370,7 +370,7 @@ void nothing2() { Java 采取了一种新的方法避免了以上这些问题:为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我将我的 foibles 类库命名为 com.mindviewinc.utility.foibles。反转域名后,`.` 用来代表子目录的划分。 -在 Java 1.0 和 Java 1.1 中,域扩展名 com、 edu、 org 和 net 等按惯例大写,因此类库中会出现这样类似的名称:com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。 +在 Java 1.0 和 Java 1.1 中,域扩展名 com、 edu、 org 和 net 等按惯例大写,因此类库中会出现这样类似的名称:Com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。 使用反向 URL 是一种新的命名空间方法,在此之前尚未有其他语言这么做过。Java 中有许多这些“创造性”地解决问题的方法。正如你想象,如果我们未经测试就添加一个功能并用于生产,那么在将来发现该功能的问题再想纠正,通常为时已晚(有些错误太严重了就得从语言中删除新功能。) From 9f9e25172324fcff14eba01d020699a3580a9df7 Mon Sep 17 00:00:00 2001 From: eliondog <1258468356@qq.com> Date: Thu, 6 Feb 2020 10:07:27 +0800 Subject: [PATCH 170/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E9=94=99?= =?UTF-8?q?=E5=88=AB=E5=AD=97=EF=BC=8C[=E5=95=86=E5=8A=A1=E8=99=9A?= =?UTF-8?q?=E4=BC=9A]=E6=94=B9=E4=B8=BA[=E5=95=86=E5=8A=A1=E8=81=9A?= =?UTF-8?q?=E4=BC=9A]=20(#374)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/00-Preface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index 93871fca..f1843204 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -87,7 +87,7 @@ Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你 感谢 *Ben Muschko* 在整理构建文件方面的工作,还有感谢 *Hans Dockter* 给 *Ben* 提供了时间。 -感谢 *Jeremy Cerise* 和 *Bill Frasure* 来到开发商务虚会预订,并随后提供了宝贵的帮助。 +感谢 *Jeremy Cerise* 和 *Bill Frasure* 来到开发商务聚会预订,并随后提供了宝贵的帮助。 感谢所有花时间和精力来科罗拉多州克雷斯特德比特(Crested Butte, Colorado)镇参加我的研讨会,开发商务聚会和其他活动的人!你们的贡献可能不容易看到,但却非常重要! From 9ad128cb71f7f5dd3316348c0ac533cd871389ed Mon Sep 17 00:00:00 2001 From: eliondog <1258468356@qq.com> Date: Thu, 6 Feb 2020 13:43:25 +0800 Subject: [PATCH 171/371] =?UTF-8?q?Fix=EF=BC=9A=E4=BF=AE=E6=AD=A3=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E9=97=AE=E9=A2=98=E5=8F=8A=E9=94=99=E5=AD=97=20(#375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改了错别字,[商务虚会]改为[商务聚会] * Fix:修正语句问题 * Fix:修正语句问题 * Fix:修正语句问题 * Fix:修正语句问题 * Fix:修正语句问题 * Fix:修正语句问题 --- docs/book/00-Introduction.md | 2 +- docs/book/01-What-is-an-Object.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index ed3cf59a..50749578 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -93,7 +93,7 @@ Sun 在图形界面的最后一次尝试,称为 JavaFX。当 Oracle 收购 Sun 现今 Swing 依然是 Java 发行版的一部分(只接受维护,不再有新功能开发)。而 Java 现在是一个开源项目,它应该始终可用。此外,Swing 和 JavaFX 有一些有限的交互性。这些可能是为了帮助开发者过渡到 JavaFX。 -桌面程序领域似乎从未尝勾起 Java 设计师的野心。Java 没有在图形界面取得该有的一席之地。另外,曾被大肆吹嘘的 JavaBeans 也没有获得任何影响力。(许多不幸的作者花了很多精力在 Swing 上编写书籍,甚至只用 JavaBeans 编写书籍)。Java 图形界面程序大多数情况下仅用于 IDE(集成开发环境)和一些企业内部应用程序。你可以采用 Java 开发图形界面,但这并非 Java 最擅长的领域。如果你必须学习 Swing,可以参考 *Thinking in Java* 第4版(可从 www.OnJava8.com 获得)或者通过其他专门的书籍中学习。。 +桌面程序领域似乎从未尝勾起 Java 设计师的野心。Java 没有在图形界面取得该有的一席之地。另外,曾被大肆吹嘘的 JavaBeans 也没有获得任何影响力。(许多不幸的作者花了很多精力在 Swing 上编写书籍,甚至只用 JavaBeans 编写书籍)。Java 图形界面程序大多数情况下仅用于 IDE(集成开发环境)和一些企业内部应用程序。你可以采用 Java 开发图形界面,但这并非 Java 最擅长的领域。如果你必须学习 Swing,可以参考 *Thinking in Java* 第4版(可从 www.OnJava8.com 获得)或者通过其他专门的书籍学习。
diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md index 83afe39b..5b0d6ca5 100644 --- a/docs/book/01-What-is-an-Object.md +++ b/docs/book/01-What-is-an-Object.md @@ -11,7 +11,7 @@ 所有编程语言都提供抽象机制。从某种程度上来说,问题的复杂度直接取决于抽象的类型和质量。这里的“类型”意思是:抽象的内容是什么?汇编语言是对底层机器的轻微抽象。接着出现的“命令式”语言(如 FORTRAN,BASIC 和 C)是对汇编语言的抽象。与汇编相比,这类语言已有了长足的改进,但它们的抽象原理依然要求我们着重考虑计算机的结构,而非问题本身的结构。 -程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一门副产业的“编程方法”学科。 +程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一个副产业“编程方法”学科。 为机器建模的另一个方法是为要解决的问题制作模型。对一些早期语言来说,如 LISP 和 APL,它们的做法是“从不同的角度观察世界”——“所有问题都归纳为列表”或“所有问题都归纳为算法”。PROLOG 则将所有 问题都归纳为决策链。对于这些语言,我们认为它们一部分是“基于约束”的编程,另一部分则是专为 @@ -37,7 +37,7 @@ Simula 是一个很好的例子。正如这个名字所暗示的,它的作用 因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。 -创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战性就是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。 +创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。 那么如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。 @@ -50,7 +50,7 @@ Light lt = new Light(); lt.on(); ``` -在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。 +在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。 为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。 From 350881d519780e8aeadaab86578079b2de22f4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B7=A6=E5=85=83?= <275395953@qq.com> Date: Sat, 8 Feb 2020 22:48:38 +0800 Subject: [PATCH 172/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E4=BA=94?= =?UTF-8?q?=E7=AB=A0=20=E8=AE=BE=E8=AE=A1=E6=A8=A1=E5=BC=8F=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E6=9B=B4=E6=96=B0=EF=BC=88=E2=80=9C=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E2=80=9D=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=89=20(#377)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/25-Patterns.md | 252 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index 22ce3cfb..65b09573 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -789,6 +789,258 @@ KungFuGuy now battles a NastyWeapon ## 函数对象 +一个 *函数对象* 封装了一个函数。其特点就是将被调用函数的选择与那个函数被调用的位置进行解耦。 + +*《设计模式》* 中也提到了这个术语,但是没有使用。然而,*函数对象* 的话题却在那本书的很多模式中被反复论及。 + +### 命令模式 + +从最直观的角度来看,*命令模式* 就是一个函数对象:一个作为对象的函数。我们可以将 *函数对象* 作为参数传递给其他方法或者对象,来执行特定的操作。 + +在Java 8之前,想要产生单个函数的效果,我们必须明确将方法包含在对象中,而这需要太多的仪式了。而利用Java 8的lambda特性, *命令模式* 的实现将是微不足道的。 + +```java +// patterns/CommandPattern.java +import java.util.*; + +public class CommandPattern { + public static void main(String[] args) { + List macro = Arrays.asList( + () -> System.out.print("Hello "), + () -> System.out.print("World! "), + () -> System.out.print("I'm the command pattern!") + ); + macro.forEach(Runnable::run); + } +} +/* Output: +Hello World! I'm the command pattern! +*/ +``` + +*命令模式* 的主要特点是允许向一个方法或者对象传递一个想要的动作。在上面的例子中,这个对象就是 **macro** ,而 *命令模式* 提供了将一系列需要一起执行的动作集进行排队的方法。在这里,*命令模式* 允许我们动态的创建新的行为,通常情况下我们需要编写新的代码才能完成这个功能,而在上面的例子中,我们可以通过解释运行一个脚本来完成这个功能(如果需要实现的东西很复杂请参考解释器模式)。 + +*《设计模式》* 认为“命令模式是回调的面向对象的替代品”。尽管如此,我认为"back"(回来)这个词是callback(回调)这一概念的基本要素。也就是说,我认为回调(callback)实际上是返回到回调的创建者所在的位置。另一方面,对于 *命令* 对象,通常只需创建它并将其交给某种方法或对象,而不是自始至终以其他方式联系命令对象。不管怎样,这就是我对它的看法。在本章的后面内容中,我将会把一组设计模式放在“回调”的标题下面。 + +### 策略模式 + +*策略模式* 看起来像是从同一个基类继承而来的一系列 *命令* 类。但是仔细查看 *命令模式*,你就会发现它也具有同样的结构:一系列分层次的 *函数对象*。不同之处在于,这些函数对象的用法和策略模式不同。就像前面的 `io/DirList.java` 那个例子,使用 *命令* 是为了解决特定问题 -- 从一个列表中选择文件。“不变的部分”是被调用的那个方法,而变化的部分被分离出来放到 *函数对象* 中。我认为 *命令模式* 在编码阶段提供了灵活性,而 *策略模式* 的灵活性在运行时才会体现出来。尽管如此,这种区别却是非常模糊的。 + +另外,*策略模式* 还可以添加一个“上下文(context)”,这个上下文(context)可以是一个代理类(surrogate class),用来控制对某个特定 *策略* 对象的选择和使用。就像 *桥接模式* 一样!下面我们来一探究竟: + +```java +// patterns/strategy/StrategyPattern.java +// {java patterns.strategy.StrategyPattern} +package patterns.strategy; +import java.util.function.*; +import java.util.*; + +// The common strategy base type: +class FindMinima { + Function, List> algorithm; +} + +// The various strategies: +class LeastSquares extends FindMinima { + LeastSquares() { + // Line is a sequence of points (Dummy data): + algorithm = (line) -> Arrays.asList(1.1, 2.2); + } +} + +class Perturbation extends FindMinima { + Perturbation() { + algorithm = (line) -> Arrays.asList(3.3, 4.4); + } +} + +class Bisection extends FindMinima { + Bisection() { + algorithm = (line) -> Arrays.asList(5.5, 6.6); + } +} + +// The "Context" controls the strategy: +class MinimaSolver { + private FindMinima strategy; + MinimaSolver(FindMinima strat) { + strategy = strat; + } + List minima(List line) { + return strategy.algorithm.apply(line); + } + void changeAlgorithm(FindMinima newAlgorithm) { + strategy = newAlgorithm; + } +} + +public class StrategyPattern { + public static void main(String[] args) { + MinimaSolver solver = + new MinimaSolver(new LeastSquares()); + List line = Arrays.asList( + 1.0, 2.0, 1.0, 2.0, -1.0, + 3.0, 4.0, 5.0, 4.0 ); + System.out.println(solver.minima(line)); + solver.changeAlgorithm(new Bisection()); + System.out.println(solver.minima(line)); + } +} +/* Output: +[1.1, 2.2] +[5.5, 6.6] +*/ +``` + +`MinimaSolver` 中的 `changeAlgorithm()` 方法将一个不同的策略插入到了 `私有` 域 `strategy` 中,这使得在调用 `minima()` 方法时,可以使用新的策略。 + +我们可以通过将上下文注入到 `FindMinima` 中来简化我们的解决方法。 + +```java +// patterns/strategy/StrategyPattern2.java // {java patterns.strategy.StrategyPattern2} +package patterns.strategy; +import java.util.function.*; +import java.util.*; + +// "Context" is now incorporated: +class FindMinima2 { + Function, List> algorithm; + FindMinima2() { leastSquares(); } // default + // The various strategies: + void leastSquares() { + algorithm = (line) -> Arrays.asList(1.1, 2.2); + } + void perturbation() { + algorithm = (line) -> Arrays.asList(3.3, 4.4); + } + void bisection() { + algorithm = (line) -> Arrays.asList(5.5, 6.6); + } + List minima(List line) { + return algorithm.apply(line); + } +} + +public class StrategyPattern2 { + public static void main(String[] args) { + FindMinima2 solver = new FindMinima2(); + List line = Arrays.asList( + 1.0, 2.0, 1.0, 2.0, -1.0, + 3.0, 4.0, 5.0, 4.0 ); + System.out.println(solver.minima(line)); + solver.bisection(); + System.out.println(solver.minima(line)); + } +} +/* Output: +[1.1, 2.2] +[5.5, 6.6] +*/ +``` + +`FindMinima2` 封装了不同的算法,也包含了“上下文”(Context),所以它便可以在一个单独的类中控制算法的选择了。 + +### 责任链模式 + +*责任链模式* 也许可以被看作一个使用了 *策略* 对象的“递归的动态一般化”。此时我们进行一次调用,在一个链序列中的每个策略都试图满足这个调用。这个过程直到有一个策略成功满足该调用或者到达链序列的末尾才结束。在递归方法中,一个方法将反复调用它自身直至达到某个终止条件;使用责任链,一个方法会调用相同的基类方法(拥有不同的实现),这个基类方法将会调用基类方法的其他实现,如此反复直至达到某个终止条件。 + +除了调用某个方法来满足某个请求以外,链中的多个方法都有机会满足这个请求,因此它有点专家系统的意味。由于责任链实际上就是一个链表,它能够动态创建,因此它可以看作是一个更一般的动态构建的 `switch` 语句。 + +在上面的 `StrategyPattern.java` 例子中,我们可能想自动发现一个解决方法。而 *责任链* 就可以达到这个目的: + +```java +// patterns/chain/ChainOfResponsibility.java +// Using the Functional interface +// {java patterns.chain.ChainOfResponsibility} +package patterns.chain; +import java.util.*; +import java.util.function.*; + +class Result { + boolean success; + List line; + Result(List data) { + success = true; + line = data; + } + Result() { + success = false; + line = Collections.emptyList(); + } +} + +class Fail extends Result {} + +interface Algorithm { + Result algorithm(List line); +} + +class FindMinima { + public static Result leastSquares(List line) { + System.out.println("LeastSquares.algorithm"); + boolean weSucceed = false; + if(weSucceed) // Actual test/calculation here + return new Result(Arrays.asList(1.1, 2.2)); + else // Try the next one in the chain: + return new Fail(); + } + public static Result perturbation(List line) { + System.out.println("Perturbation.algorithm"); + boolean weSucceed = false; + if(weSucceed) // Actual test/calculation here + return new Result(Arrays.asList(3.3, 4.4)); + else + return new Fail(); + } + public static Result bisection(List line) { + System.out.println("Bisection.algorithm"); + boolean weSucceed = true; + if(weSucceed) // Actual test/calculation here + return new Result(Arrays.asList(5.5, 6.6)); + else + return new Fail(); + } + static List, Result>> + algorithms = Arrays.asList( + FindMinima::leastSquares, + FindMinima::perturbation, + FindMinima::bisection + ); + public static Result minima(List line) { + for(Function, Result> alg : + algorithms) { + Result result = alg.apply(line); + if(result.success) + return result; + } + return new Fail(); + } +} + +public class ChainOfResponsibility { + public static void main(String[] args) { + FindMinima solver = new FindMinima(); + List line = Arrays.asList( + 1.0, 2.0, 1.0, 2.0, -1.0, + 3.0, 4.0, 5.0, 4.0); + Result result = solver.minima(line); + if(result.success) + System.out.println(result.line); + else + System.out.println("No algorithm found"); + } +} +/* Output: +LeastSquares.algorithm +Perturbation.algorithm +Bisection.algorithm +[5.5, 6.6] +*/ +``` + +我们从定义一个 `Result` 类开始,这个类包含一个 `success` 标志,因此接收者就可以知道算法是否成功执行,而 `line` 变量保存了真实的数据。当算法执行失败时, `Fail` 类可以作为返回值。要注意的是,当算法执行失败时,返回一个 `Result` 对象要比抛出一个异常更加合适,因为我们有时可能并不打算解决这个问题,而是希望程序继续执行下去。 + +每一个 `Algorithm` 接口的实现,都实现了不同的 `algorithm()` 方法。在 `FindMinama` 中,将会创建一个算法的列表(这就是所谓的“链”),而 `minima()` 方法只是遍历这个列表,然后找到能够成功执行的算法而已。 ## 接口改变 From 10422d7e0f2fa0ac6d15ffbc76e3f901d8c57882 Mon Sep 17 00:00:00 2001 From: ddtyjmyjm Date: Sun, 9 Feb 2020 15:02:01 +0800 Subject: [PATCH 173/371] =?UTF-8?q?=20Fix:=E4=BF=AE=E6=AD=A3=20Chap.7=20?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=E9=97=AE=E9=A2=98=20(#378)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix:修正 Chap.6 语句问题 * 修改一些错误 * 修改格式的使用 * 修改格式的使用 --- docs/book/07-Implementation-Hiding.md | 56 +++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index a331bd5b..884466c1 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -3,25 +3,25 @@ # 第七章 封装 -访问控制(或者隐藏实现)与"最初的实现不恰当"有关。 +> *访问控制(Access control)*(或者*隐藏实现(implementation hiding)*)与“最初的实现不恰当”有关。 -所有优秀的作者——包括这些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是重构的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。 +所有优秀的作者——包括那些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是*重构*(refactoring)的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。 -但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,客户端程序员希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:"如何区分变动的事物和不变的事物"。 +但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,一些用户(*客户端程序员(client programmers)*)希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:“如何区分变动的事物和不变的事物”。 -这个问题对于类库而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。 +这个问题对于类库(library)而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。 这可以通过约定解决。例如,类库开发者必须同意在修改类库中的一个类时,不会移除已有的方法,因为那样将会破坏客户端程序员的代码。与之相反的情况更加复杂。在有成员属性的情况下,类库开发者如何知道哪些属性被客户端程序员使用?这同样会发生在那些只为实现类库类而创建的方法上,它们也不是设计成可供客户端程序员调用的。如果类库开发者想删除旧的实现,添加新的实现,结果会怎样呢?任何这些成员的改动都可能破环客户端程序员的代码。因此类库开发者会被束缚,不能修改任何事物。 -为了解决这一问题,Java 提供了访问修饰符供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从"最大权限"到"最小权限"依次是:**public**,**protected**,包访问权限(没有关键字)和 **private**。根据上一段的内容,你可能会想,作为一名类库设计者,你会尽可能将一切都设为 **private**,仅向客户端程序员暴露你愿意他们使用的方法。这就是你通常所做的,尽管这与使用其他语言(尤其是 C)编程和访问不受任何限制的人们的直觉相违背。 +为了解决这一问题,Java 提供了*访问修饰符*(access specifier)供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从“最大权限”到“最小权限”依次是:**public**,**protected**,*包访问权限(package access)*(没有关键字)和 **private**。根据上一段的内容,你可能会想,作为一名类库设计者,你会尽可能将一切都设为 **private**,仅向客户端程序员暴露你愿意他们使用的方法。这就是你通常所做的,尽管这与那些使用其他语言(尤其是 C)编程以及习惯了不受限制地访问任何东西的人们的直觉相违背。 -然而,构建类库的概念和对类库组件的访问控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚到类库单元中。Java 中通过 package 关键字加以控制,类是在相同包下还是不同包下会影响访问修饰符。所以在这章开始,你将会学习如何将类库组件置于同一个包下,之后你就能明白访问修饰符的全部含义。 +然而,类库组件的概念和对类库组件访问的控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚的类库单元中。Java 中通过 **package** 关键字加以控制,类在相同包下还是在不同包下,会影响访问修饰符。所以在这章开始,你将会学习如何将类库组件置于同一个包下,之后你就能明白访问修饰符的全部含义。 ## 包的概念 -包内包含一组类,它们被组织在一个单独的命名空间下。 +包内包含一组类,它们被组织在一个单独的*命名空间*(namespace)下。 例如,标准 Java 发布中有一个工具库,它被组织在 **java.util** 命名空间下。**java.util** 中含有一个类,叫做 **ArrayList**。使用 **ArrayList** 的一种方式是用其全名 **java.util.ArrayList**。 @@ -55,15 +55,15 @@ import java.util.* 之所以使用导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。类 **A** 中的方法 `f()` 不会与类 **B** 中具有相同签名的方法 `f()` 冲突。但是如果类名冲突呢?假设你创建了一个 **Stack** 类,打算安装在一台已经有别人所写的 **Stack** 类的机器上,该怎么办呢?这种类名的潜在冲突,正是我们需要在 Java 中对命名空间进行完全控制的原因。为了解决冲突,我们为每个类创建一个唯一标识符组合。 -到目前为止的大部分示例都只存在单个文件,并为本地使用的,所以尚未受到包名的干扰。但是,这些示例其实已经位于包中了,叫做"未命名"包或默认包。这当然是一种选择,为了简单起见,本书其余部分会尽可能采用这种方式。但是,如果你打算为相同机器上的其他 Java 程序创建友好的类库或程序时,就必须仔细考虑以防类名冲突。 +到目前为止的大部分示例都只存在单个文件,并为本地使用的,所以尚未受到包名的干扰。但是,这些示例其实已经位于包中了,叫做“未命名”包或*默认包*(default package)。这当然是一种选择,为了简单起见,本书其余部分会尽可能采用这种方式。但是,如果你打算为相同机器上的其他 Java 程序创建友好的类库或程序时,就必须仔细考虑以防类名冲突。 -一个 Java 源代码文件称为一个*编译单元*(有时也称*翻译单元*)。每个编译单元的文件名后缀必须是 **.java**。在编译单元中可以有一个 **public** 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 **.java**)。每个编译单元中只能有一个 **public** 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 **public** 类,此时它们支持主 **public** 类。 +一个 Java 源代码文件称为一个*编译单元(compilation unit)*(有时也称*翻译单元(translation unit)*)。每个编译单元的文件名后缀必须是 **.java**。在编译单元中可以有一个 **public** 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 **.java**)。每个编译单元中只能有一个 **public** 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 **public** 类,此时它们为主 **public** 类提供“支持”类 。 ### 代码组织 -当编译一个 **.java** 文件时,**.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为"obj"文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。 +当编译一个 **.java** 文件时,**.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为“obj”文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。 -类库是一组类文件。每个源文件通常都含有一个 **public** 类和任意数量的非 **public** 类,因此每个文件都有一个构件。如果把这些组件集中在一起,就需要使用关键字 **package**。 +类库是一组类文件。每个源文件通常都含有一个 **public** 类和任意数量的非 **public** 类,因此每个文件都有一个 **public** 组件。如果把这些组件集中在一起,就需要使用关键字 **package**。 如果你使用了 **package** 语句,它必须是文件中除了注释之外的第一行代码。当你如下这样写: @@ -71,9 +71,9 @@ import java.util.* package hiding; ``` -意味着这个编译单元是一个名为 **hiding** 类库的一部分。换句话说,你正在声明的编译单元中的 **public** 类名称位于名为 **hiding** 的保护伞下。任何人想要使用该名称,必须指明完整的类名或者使用 **import** 关键字导入 **hiding**。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同) +意味着这个编译单元是一个名为 **hiding** 类库的一部分。换句话说,你正在声明的编译单元中的 **public** 类名称位于名为 **hiding** 的保护伞下。任何人想要使用该名称,必须指明完整的类名或者使用 **import** 关键字导入 **hiding** 。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同) -例如,假设文件名是 **MyClass.java**,这意味着文件中只能有一个 **public** 类,且类名必须是 MyClass(大小写也与文件名相同): +例如,假设文件名是 **MyClass.java** ,这意味着文件中只能有一个 **public** 类,且类名必须是 **MyClass**(大小写也与文件名相同): ```java // hiding/mypackage/MyClass.java @@ -117,7 +117,7 @@ public class ImportedMyClass { 将所有的文件放在一个子目录还解决了其他的两个问题:创建独一无二的包名和查找可能隐藏于目录结构某处的类。这是通过将 **.class** 文件所在的路径位置编码成 **package** 名称来实现的。按照惯例,**package** 名称是类的创建者的反顺序的 Internet 域名。如果你遵循惯例,因为 Internet 域名是独一无二的,所以你的 **package** 名称也应该是独一无二的,不会发生名称冲突。如果你没有自己的域名,你就得构造一组不大可能与他人重复的组合(比如你的姓名),来创建独一无二的 package 名称。如果你打算发布 Java 程序代码,那么花些力气去获取一个域名是值得的。 -此技巧的第二部分是把 **package** 名称分解成你机器上的一个目录,所以当 Java 解释器必须要加载一个 .class 文件时,它能定位到 **.class** 文件所在的位置。首先,它找出环境变量 **CLASSPATH**(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。**CLASSPATH** 包含一个或多个目录,用作查找 .**class** 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(包名 foo.bar.baz 变成 foo\bar\baz 或 foo/bar/baz 或其它,取决于你的操作系统)。然后这个路径与 **CLASSPATH** 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 **.class** 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。 +此技巧的第二部分是把 **package** 名称分解成你机器上的一个目录,所以当 Java 解释器必须要加载一个 .class 文件时,它能定位到 **.class** 文件所在的位置。首先,它找出环境变量 **CLASSPATH**(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。**CLASSPATH** 包含一个或多个目录,用作查找 .**class** 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(取决于你的操作系统,包名 **foo.bar.baz** 变成 **foo\bar\baz** 或 **foo/bar/baz** 或其它)。然后这个路径与 **CLASSPATH** 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 **.class** 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。 为了理解这点,比如说我的域名 **MindviewInc.com**,将之反转并全部改为小写后就是 **com.mindviewinc**,这将作为我创建的类的独一无二的全局名称。(com、edu、org等扩展名之前在 Java 包中都是大写,但是 Java 2 之后都统一用小写。)我决定再创建一个名为 **simple** 的类库,从而细分名称: @@ -197,7 +197,7 @@ com.mindviewinc.simple.List 当编译器遇到导入 **simple** 库的 **import** 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 **com/mindviewinc/simple**,然后从已编译的文件中找出名称相符者(对 **Vector** 而言是 **Vector.class**,对 **List** 而言是 **List.class**)。注意,这两个类和其中要访问的方法都必须是 **public** 修饰的。 -对于 Java 新手而言,设置 CLASSPATH 是一件麻烦的事(我最初使用时这么觉得),后面版本的 JDK 更加智能。你会发现当你安装好 JDK 时,即使不设置 CLASSPATH,也能够编译和运行基本的 Java 程序。但是,为了编译和运行本书的代码示例(从[https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 取得),你必须将本书程序代码树的基本目录加入到 CLASSPATH 中( gradlew 命令管理自身的 CLASSPATH,所以如果你想直接使用 javac 和 java,不用 Gradle 的话,就需要设置 CLASSPATH)。 +对于 Java 新手而言,设置 CLASSPATH 是一件麻烦的事(我最初使用时是这么觉得的),后面版本的 JDK 更加智能。你会发现当你安装好 JDK 时,即使不设置 CLASSPATH,也能够编译和运行基本的 Java 程序。但是,为了编译和运行本书的代码示例(从[https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 取得),你必须将本书程序代码树的基本目录加入到 CLASSPATH 中( gradlew 命令管理自身的 CLASSPATH,所以如果你想直接使用 javac 和 java,不用 Gradle 的话,就需要设置 CLASSPATH)。 ### 冲突 @@ -230,9 +230,9 @@ java.util.Vector v = new java.util.Vector(); 具备了以上知识,现在就可以创建自己的工具库来减少重复的程序代码了。 -一般来说,我会使用反转后的域名来命名要创建的工具包,比如 **com.mindviewinc.util**,但为了简化,这里我把工具包命名为 **onjava**。 +一般来说,我会使用反转后的域名来命名要创建的工具包,比如 **com.mindviewinc.util** ,但为了简化,这里我把工具包命名为 **onjava**。 -比如,下面是"控制流"一章中使用到的 `range()` 方法,采用了 for-in 语法进行简单的遍历: +比如,下面是“控制流”一章中使用到的 `range()` 方法,采用了 for-in 语法进行简单的遍历: ```java // onjava/Range.java @@ -276,13 +276,13 @@ public class Range { ### 使用 import 改变行为 -Java 没有 C 的*条件编译*功能,该功能使你不必更改任何程序代码而能够切换开关产生不同的行为。Java 之所以去掉此功能,可能是因为 C 在绝大多数情况下使用该功能解决跨平台问题:程序代码的不同部分要根据不同的平台来编译。而 Java 自身就是跨平台设计的,这个功能就没有必要了。 +Java 没有 C 的*条件编译*(conditional compilation)功能,该功能使你不必更改任何程序代码而能够切换开关产生不同的行为。Java 之所以去掉此功能,可能是因为 C 在绝大多数情况下使用该功能解决跨平台问题:程序代码的不同部分要根据不同的平台来编译。而 Java 自身就是跨平台设计的,这个功能就没有必要了。 但是,条件编译还有其他的用途。调试是一个很常见的用途,调试功能在开发过程中是开启的,在发布的产品中是禁用的。可以通过改变导入的 **package** 来实现这一目的,修改的方法是将程序中的代码从调试版改为发布版。这个技术可用于任何种类的条件代码。 ### 使用包的忠告 -当创建一个包时,包名就隐含了目录结构。这个包必须位于包名指定的目录中,该目录必须在以 CLASSPATH 开始的目录中可以查询到。 最初使用关键字 **package** 可能会有点不顺,因为除非遵守"包名对应目录路径"的规则,否则会收到很多意外的运行时错误信息如找不到特定的类,即使这个类就位于同一目录中。如果你收到类似信息,尝试把 **package** 语句注释掉,如果程序能运行的话,你就知道问题出现在哪里了。 +当创建一个包时,包名就隐含了目录结构。这个包必须位于包名指定的目录中,该目录必须在以 CLASSPATH 开始的目录中可以查询到。 最初使用关键字 **package** 可能会有点不顺,因为除非遵守“包名对应目录路径”的规则,否则会收到很多意外的运行时错误信息如找不到特定的类,即使这个类就位于同一目录中。如果你收到类似信息,尝试把 **package** 语句注释掉,如果程序能运行的话,你就知道问题出现在哪里了。 注意,编译过的代码通常位于与源代码的不同目录中。这是很多工程的标准,而且集成开发环境(IDE)通常会自动为我们做这些。必须保证 JVM 通过 CLASSPATH 能找到编译后的代码。 @@ -296,13 +296,13 @@ Java 访问权限修饰符 **public**,**protected** 和 **private** 位于定 ### 包访问权限 -本章之前的所有示例要么使用 **public** 访问修饰符,要么就没使用修饰符(默认访问)。默认访问权限没有关键字,通常被称为包访问权限(有时也称为 friendly)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 **private** 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。 +本章之前的所有示例要么使用 **public** 访问修饰符,要么就没使用修饰符(*默认访问权限(default access)*)。默认访问权限没有关键字,通常被称为*包访问权限(package access)*(有时也称为 **friendly**)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 **private** 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。 -包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类给它们的包访问权限的成员赋予了相互访问的权限,所以你"拥有”了包内的程序代码。只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。 +包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类赋予了它们包访问权限的成员相互访问的权限,所以你"拥有”了包内的程序代码。只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。 -类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说"嗨,我是 **Bob** 的朋友!",然后想看到 **Bob** 的 **protected**,包访问权限和 **private** 成员。取得对成员的访问权的唯一方式是: +类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说"嗨,我是 **Bob** 的朋友!",然后想看到 **Bob** 的 **protected**、包访问权限和 **private** 成员。取得对成员的访问权的唯一方式是: -1. 使成员成为 public。那么无论是谁,无论在哪,都可以访问它。 +1. 使成员成为 **public**。那么无论是谁,无论在哪,都可以访问它。 2. 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。 3. 在"复用"这一章你将看到,继承的类既可以访问 **public** 成员,也可以访问 **protected** 成员(但不能访问 **private** 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。但现在不用担心继承和 **protected**。 4. 提供访问器(accessor)和修改器(mutator)方法(有时也称为"get/set" 方法),从而读取和改变值。 @@ -327,7 +327,7 @@ public class Cookie { } ``` -记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 `.`,Java 就不会查找单独当前目录。 +记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找单独当前目录。 现在,使用 **Cookie** 创建一个程序: ```java @@ -384,7 +384,7 @@ class Pie { } ``` -最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 `.`,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。 +最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 **.**,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。 ### private: 你无法访问 @@ -556,7 +556,7 @@ new PublicConstructor(); ## 接口和实现 -访问控制通常被称为实现的隐藏。将数据和方法包装进类中并把具体实现隐藏被称作是封装。其结果就是一个同时带有特征和行为的数据类型。 +访问控制通常被称为*隐藏实现*(implementation hiding)。将数据和方法包装进类中并把具体实现隐藏被称作是*封装*(encapsulation)。其结果就是一个同时带有特征和行为的数据类型。 出于两个重要的原因,访问控制在数据类型内部划定了边界。第一个原因是确立客户端程序员可以使用和不能使用的边界。可以在结构中建立自己的内部机制而不必担心客户端程序员偶尔将内部实现作为他们可以使用的接口的一部分。 @@ -613,7 +613,7 @@ import hiding.*; 如果获取了一个在 **hiding** 包中的类,只用来完成 **Widget** 或 **hiding** 包下一些其他 **public** 类所要执行的任务,怎么办呢? 你不想自找麻烦为客户端程序员创建说明文档,并且你认为不久后会完全改变原有方案并将旧版本删除,替换成新版本。为了保留此灵活性,需要确保客户端程序员不依赖隐藏在 **hiding** 中的任何特定细节,那么把 **public** 关键字从类中去掉,给予它包访问权限,就可以了。 -当你创建了一个包访问权限的类,把类中的属性声明为 **private** 仍然是有意义的——应该尽可能将所有属性都声明为 **private**,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。由于一个包访问权限的类只能被用于包内,除非你被强制将某些方法声明为 **public**,这种情况下,编译器会告诉你。 +当你创建了一个包访问权限的类,把类中的属性声明为 **private** 仍然是有意义的——应该尽可能将所有属性都声明为 **private**,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。一个包访问权限的类只能被用于包内,除非强制将某些方法声明为 **public**,这种情况下,编译器会告诉你。 注意,类既不能是 **private** 的(这样除了该类自身,任何类都不能访问它),也不能是 **protected** 的。所以对于类的访问权限只有两种选择:包访问权限或者 **public**。为了防止类被外界访问,可以将所有的构造器声明为 **private**,这样只有你自己能创建对象(在类的 static 成员中): @@ -664,7 +664,7 @@ public class Lunch { **Soup1** 和 **Soup2** 展示了如何通过将你所有的构造器声明为 **private** 的方式防止直接创建某个类的对象。记住,如果你不显式地创建构造器,编译器会自动为你创建一个无参构造器(没有参数的构造器)。如果我们编写了无参构造器,那么编译器就不会自动创建构造器了。将构造器声明为 **private**,那么谁也无法创建该类的对象了。但是现在别人该怎么使用这个类呢?上述例子给出了两个选择。在 **Soup1** 中,有一个 **static** 方法,它的作用是创建一个新的 **Soup1** 对象并返回对象的引用。如果想要在返回引用之前在 **Soup1** 上做一些额外操作,或是记录创建了多少个 **Soup1** 对象(可以用来限制数量),这种做法是有用的。 -**Soup2** 用到了所谓的*设计模式*。这种模式叫做*单例模式*,因为它只允许创建类的一个对象。**Soup2** 类的对象是作为 **Soup2** 的 **static** **private** 成员而创建的,所以有且只有一个,你只能通过 **public** 修饰的 `access()` 方法访问到这个对象。 +**Soup2** 用到了所谓的*设计模式*(design pattern)。这种模式叫做*单例模式*(singleton),因为它只允许创建类的一个对象。**Soup2** 类的对象是作为 **Soup2** 的 **static** **private** 成员而创建的,所以有且只有一个,你只能通过 **public** 修饰的 `access()` 方法访问到这个对象。 From acca795d5ff23560d1d6c004864befb56edfe36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B7=A6=E5=85=83?= Date: Wed, 12 Feb 2020 20:15:18 +0800 Subject: [PATCH 174/371] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E4=BA=94?= =?UTF-8?q?=E7=AB=A0=20=E8=AE=BE=E8=AE=A1=E6=A8=A1=E5=BC=8F=20=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E6=9B=B4=E6=96=B0=EF=BC=88=E2=80=9C=E6=94=B9=E5=8F=98?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E2=80=9D=E5=B0=8F=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=89=20(#379)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 第二十五章 设计模式 翻译更新(“函数对象”小节翻译完成) * 第二十五章 设计模式 翻译更新(“改变接口”小节翻译完成) --- docs/book/25-Patterns.md | 138 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 1 deletion(-) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index 65b09573..d8a8bf87 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -1043,8 +1043,144 @@ Bisection.algorithm 每一个 `Algorithm` 接口的实现,都实现了不同的 `algorithm()` 方法。在 `FindMinama` 中,将会创建一个算法的列表(这就是所谓的“链”),而 `minima()` 方法只是遍历这个列表,然后找到能够成功执行的算法而已。 -## 接口改变 +## 改变接口 +有时候我们需要解决的问题很简单,仅仅是“我没有需要的接口”而已。有两种设计模式用来解决这个问题:*适配器模式* 接受一种类型并且提供一个对其他类型的接口。*外观模式* 为一组类创建了一个接口,这样做只是为了提供一种更方便的方法来处理库或资源。 + +### 适配器模式(Adapter) + +当我们手头有某个类,而我们需要的却是另外一个类,我们就可以通过 *适配器模式* 来解决问题。唯一需要做的就是产生出我们需要的那个类,有许多种方法可以完成这种适配。 + +```java +// patterns/adapt/Adapter.java +// Variations on the Adapter pattern +// {java patterns.adapt.Adapter} +package patterns.adapt; + +class WhatIHave { + public void g() {} + public void h() {} +} + +interface WhatIWant { + void f(); +} + +class ProxyAdapter implements WhatIWant { + WhatIHave whatIHave; + ProxyAdapter(WhatIHave wih) { + whatIHave = wih; + } + @Override + public void f() { + // Implement behavior using + // methods in WhatIHave: + whatIHave.g(); + whatIHave.h(); + } +} + +class WhatIUse { + public void op(WhatIWant wiw) { + wiw.f(); + } +} + +// Approach 2: build adapter use into op(): +class WhatIUse2 extends WhatIUse { + public void op(WhatIHave wih) { + new ProxyAdapter(wih).f(); + } +} + +// Approach 3: build adapter into WhatIHave: +class WhatIHave2 extends WhatIHave implements WhatIWant { + @Override + public void f() { + g(); + h(); + } +} + +// Approach 4: use an inner class: +class WhatIHave3 extends WhatIHave { + private class InnerAdapter implements WhatIWant { + @Override + public void f() { + g(); + h(); + } + } + public WhatIWant whatIWant() { + return new InnerAdapter(); + } +} + +public class Adapter { + public static void main(String[] args) { + WhatIUse whatIUse = new WhatIUse(); + WhatIHave whatIHave = new WhatIHave(); + WhatIWant adapt= new ProxyAdapter(whatIHave); + whatIUse.op(adapt); + // Approach 2: + WhatIUse2 whatIUse2 = new WhatIUse2(); + whatIUse2.op(whatIHave); + // Approach 3: + WhatIHave2 whatIHave2 = new WhatIHave2(); + whatIUse.op(whatIHave2); + // Approach 4: + WhatIHave3 whatIHave3 = new WhatIHave3(); + whatIUse.op(whatIHave3.whatIWant()); + } +} +``` + +我想冒昧的借用一下术语“proxy”(代理),因为在 *《设计模式》* 里,他们坚持认为一个代理(proxy)必须拥有和它所代理的对象一模一样的接口。但是,如果把这两个词一起使用,叫做“代理适配器(proxy adapter)”,似乎更合理一些。 + +### 外观模式(Façade) + +当我想方设法试图将需求初步(first-cut)转化成对象的时候,通常我使用的原则是: + +>“把所有丑陋的东西都隐藏到对象里去”。 + +基本上说,*外观模式* 干的就是这个事情。如果我们有一堆让人头晕的类以及交互(Interactions),而它们又不是客户端程序员必须了解的,那我们就可以为客户端程序员创建一个接口只提供那些必要的功能。 + +外观模式经常被实现为一个符合单例模式(Singleton)的抽象工厂(abstract factory)。当然,你可以通过创建包含 **静态** 工厂方法(static factory methods)的类来达到上述效果。 + +```java +// patterns/Facade.java + +class A { A(int x) {} } + +class B { B(long x) {} } + +class C { C(double x) {} } + +// Other classes that aren't exposed by the +// facade go here ... +public class Facade { + static A makeA(int x) { return new A(x); } + static B makeB(long x) { return new B(x); } + static C makeC(double x) { return new C(x); } + public static void main(String[] args) { + // The client programmer gets the objects + // by calling the static methods: + A a = Facade.makeA(1); + B b = Facade.makeB(1); + C c = Facade.makeC(1.0); + } +} +``` + +《设计模式》给出的例子并不是真正的 *外观模式* ,而仅仅是一个类使用了其他的类而已。 + +#### 包(Package)作为外观模式的变体 + +我感觉,*外观模式* 更倾向于“过程式的(procedural)”,也就是非面向对象的(non-object-oriented):我们是通过调用某些函数才得到对象。它和抽象工厂(Abstract factory)到底有多大差别呢?*外观模式* 关键的一点是隐藏某个库的一部分类(以及它们的交互),使它们对于客户端程序员不可见,这样那些类的接口就更加简练和易于理解了。 + +其实,这也正是 Java 的 packaging(包)的功能所完成的事情:在库以外,我们只能创建和使用被声明为公共(public)的那些类;所有非公共(non-public)的类只能被同一 package 的类使用。看起来,*外观模式* 似乎是 Java 内嵌的一个功能。 + +公平起见,*《设计模式》* 主要是写给 C++ 读者的。尽管 C++ 有命名空间(namespaces)机制来防止全局变量和类名称之间的冲突,但它并没有提供类隐藏的机制,而在 Java 里我们可以通过声明 non-public 类来实现这一点。我认为,大多数情况下 Java 的 package 功能就足以解决针对 *外观模式* 的问题了。 ## 解释器 From 2834f9093c6b14a6af940cfd432e0178d5509c4a Mon Sep 17 00:00:00 2001 From: wsb200514 Date: Sun, 23 Feb 2020 14:14:32 +0800 Subject: [PATCH 175/371] =?UTF-8?q?=E4=BD=86=E6=98=AF=E6=88=91=E4=B8=8D?= =?UTF-8?q?=E6=83=B3=E8=BF=9B=E5=85=A5=E7=96=AF=E7=8B=82=E7=9A=84=E4=BA=BA?= =?UTF-8?q?=E7=BE=A4=E4=BC=97->=E4=B8=AD=20(#385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 9ecea280..0f4d8d74 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -3,7 +3,7 @@ # 第二十四章 并发编程 ->爱丽丝:“但是我不想进入疯狂的人群众” +>爱丽丝:“但是我不想进入疯狂的人群中” > >猫咪:“oh,你无能为力,我们都疯了,我疯了,你也疯了” > From 400e96d81dbbfd0efcc0228ae3c43b64ef5e9b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Thu, 27 Feb 2020 19:03:19 +0800 Subject: [PATCH 176/371] =?UTF-8?q?=E5=BF=BD=E8=B7=AF->=E5=BF=BD=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/15-Exceptions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 653b548d..5ee290bb 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -206,7 +206,7 @@ MyException: Originated in g() FullConstructors.main(FullConstructors.java:24) ``` -新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键宇明确调用了其基类构造器,它接受一个字符串作为参数。 +新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。 在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace() 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本: @@ -2068,7 +2068,7 @@ try { 如果想把“被检查的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。 -这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽路异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样: +这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样: ```java // exceptions/TurnOffChecking.java From a653a6383a340c05afd963a376e4d61cb895db6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Fri, 28 Feb 2020 22:05:39 +0800 Subject: [PATCH 177/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index dd6514ce..773d88ae 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1348,7 +1348,7 @@ x.getClass().equals(Derived.class)) true ### 类方法提取器 -通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](#ch040.xhtml#appendix-object-serialization))。但是,有时动态提取有关类的信息很有用。 +通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](https://lingcoder.github.io/OnJava8/#/book/Appendix-Object-Serialization))。但是,有时动态提取有关类的信息很有用。 考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口: From 80bf77452addba0c6bcf55b0d279604745b2c876 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Sat, 29 Feb 2020 12:16:28 +0800 Subject: [PATCH 178/371] =?UTF-8?q?=E5=BF=BD=E8=B7=AF->=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=20(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 --- docs/book/15-Exceptions.md | 4 ++-- docs/book/19-Type-Information.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 653b548d..5ee290bb 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -206,7 +206,7 @@ MyException: Originated in g() FullConstructors.main(FullConstructors.java:24) ``` -新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键宇明确调用了其基类构造器,它接受一个字符串作为参数。 +新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。 在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace() 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本: @@ -2068,7 +2068,7 @@ try { 如果想把“被检查的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。 -这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽路异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样: +这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样: ```java // exceptions/TurnOffChecking.java diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index dd6514ce..773d88ae 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1348,7 +1348,7 @@ x.getClass().equals(Derived.class)) true ### 类方法提取器 -通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](#ch040.xhtml#appendix-object-serialization))。但是,有时动态提取有关类的信息很有用。 +通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](https://lingcoder.github.io/OnJava8/#/book/Appendix-Object-Serialization))。但是,有时动态提取有关类的信息很有用。 考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口: From aa75297ba6d759bfd3f9f3dda8b416d44dcf1bcf Mon Sep 17 00:00:00 2001 From: grisse Date: Sat, 29 Feb 2020 12:17:12 +0800 Subject: [PATCH 179/371] =?UTF-8?q?Fix=20#380=20=E5=92=8C=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E9=97=AD=E5=8C=85=E6=AE=B5=E8=90=BD=20(#387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/11-Inner-Classes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index ce3e0043..f7862a5c 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -368,7 +368,7 @@ public class Parcel5 { } ``` -**PDestination** 类是 `destination()` 方法的一部分,而不是 **Parcel5** 的一部分。所以,在 `destination()` 之外不能访问 **PDestination**,注意出现在 **return** 语句中的向上转型-返回的是 **Destination** 的引用,它是 **PDestination** 的基类。当然,在 `destination()` 中定义了内部类 **PDestination**,并不意味着一旦 `dest()` 方法执行完毕,**PDestination** 就不可用了。 +**PDestination** 类是 `destination()` 方法的一部分,而不是 **Parcel5** 的一部分。所以,在 `destination()` 之外不能访问 **PDestination**,注意出现在 **return** 语句中的向上转型-返回的是 **Destination** 的引用,它是 **PDestination** 的基类。当然,在 `destination()` 中定义了内部类 **PDestination**,并不意味着一旦 `destination()` 方法执行完毕,**PDestination** 就不可用了。 你可以在同一个子目录下的任意类中对某个内部类使用类标识符 **PDestination**,这并不会有命名冲突。 @@ -429,7 +429,7 @@ public class Parcel7 { } ``` -`contents()` 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个 **Contents** 对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义。 +`contents()` 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个 **Contents** 对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义。” 这种奇怪的语法指的是:“创建一个继承自 **Contents** 的匿名类的对象。”通过 **new** 表达式返回的引用被自动向上转型为对 **Contents** 的引用。上述匿名内部类的语法是下述形式的简化形式: @@ -823,7 +823,7 @@ public class MultiImplementation { 闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。 -在 Java 8 之前,生成闭包行为的唯一方式就是内部类。在 Java 8 之后,我们可以使用 lambda 表达式来生成闭包行为,并且语法更加精细和简洁;你将会在 [函数式编程 ]() 这一章节中学习相关细节。即使应该优先使用 lambda 表达式用于内部类闭包,你依旧会看到那些 Java 8 以前的代码,即使用内部类来表示闭包的方式,所以非常有必要来理解这种形式。 +在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁,你将会在 [函数式编程 ]() 这一章节中学习相关细节。尽管相对于内部类,你可能更喜欢使用 lambda 表达式实现闭包,但是你会看到并需要理解那些在 Java 8 之前通过内部类方式实现闭包的代码,因此仍然有必要来理解这种方式。 Java 最引人争议的问题之一就是,人们认为 Java 应该包含某种类似指针的机制,以允许回调(callback)。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。稍后将会看到这是一个非常有用的概念。如果回调是通过指针实现的,那么就只能寄希望于程序员不会误用该指针。然而,读者应该已经了解到,Java 更小心仔细,所以没有在语言中包括指针。 From 08235e78dad5e075a412f74f4e8c65fcfe5cbe9c Mon Sep 17 00:00:00 2001 From: grisse Date: Sat, 29 Feb 2020 12:17:39 +0800 Subject: [PATCH 180/371] fix #384 (#386) --- docs/book/14-Streams.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 3633550e..3950ebf7 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1128,6 +1128,7 @@ public class FileToWordsTest { ``` Not much of a cheese shop really +is it ``` 在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。 From 8729f5c259d2e8ee350cd382306837e30add079d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Tue, 3 Mar 2020 13:19:18 +0800 Subject: [PATCH 181/371] fix Class.getInterface() -> Class.getInterfaces() --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 773d88ae..98af551f 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -252,7 +252,7 @@ Canonical name : typeinfo.toys.Toy `printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。 -在主方法中调用的 `Class.getInterface()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 +在主方法中调用的 `Class.getInterfaces()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。 From cfa16bee91618b7a4980f4d4be57fb45ed83c418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Tue, 3 Mar 2020 14:53:16 +0800 Subject: [PATCH 182/371] =?UTF-8?q?=E5=90=A6=E8=80=85=20->=20=E5=90=A6?= =?UTF-8?q?=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 98af551f..580a0a3a 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -501,7 +501,7 @@ public class DynamicSupplier implements Supplier { 14 ``` -注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否者运行时会抛出异常。编译期对该程序不会产生任何警告信息。 +注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否则运行时会抛出异常。编译期对该程序不会产生任何警告信息。 当你将泛型语法用于 `Class` 对象时,`newInstance()` 将返回该对象的确切类型,而不仅仅只是在 `ToyTest.java` 中看到的基类 `Object`。然而,这在某种程度上有些受限: From 32b5603531247a202ddc3b07c7548c018924cf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Tue, 3 Mar 2020 15:02:34 +0800 Subject: [PATCH 183/371] =?UTF-8?q?=E7=BC=96=E8=AF=91=E6=9C=9F=20->=20?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=99=A8=20=EF=BC=8C=E5=8E=9F=E6=96=87"the?= =?UTF-8?q?=20compiler=20will=20only=20allow=20you=20to=20say=20that=20the?= =?UTF-8?q?=20superclass=20reference=20is=20=E2=80=9Csome=20class=20that?= =?UTF-8?q?=20is=20a=20superclass=20of=20FancyToy=E2=80=9D=20a"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 580a0a3a..6fbc8795 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -527,7 +527,7 @@ public class GenericToyTest { } ``` -如果你手头的是超类,那编译期将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 +如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 ### `cast()` 方法 From 849890da3b64a42b412c878d90907743078cb197 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Wed, 4 Mar 2020 10:01:48 +0800 Subject: [PATCH 184/371] fix Class.getInterface() -> Class.getInterfaces() (#392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" --- docs/book/19-Type-Information.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 773d88ae..6fbc8795 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -252,7 +252,7 @@ Canonical name : typeinfo.toys.Toy `printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。 -在主方法中调用的 `Class.getInterface()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 +在主方法中调用的 `Class.getInterfaces()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。 另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。 @@ -501,7 +501,7 @@ public class DynamicSupplier implements Supplier { 14 ``` -注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否者运行时会抛出异常。编译期对该程序不会产生任何警告信息。 +注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否则运行时会抛出异常。编译期对该程序不会产生任何警告信息。 当你将泛型语法用于 `Class` 对象时,`newInstance()` 将返回该对象的确切类型,而不仅仅只是在 `ToyTest.java` 中看到的基类 `Object`。然而,这在某种程度上有些受限: @@ -527,7 +527,7 @@ public class GenericToyTest { } ``` -如果你手头的是超类,那编译期将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 +如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是"某个类"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。 ### `cast()` 方法 From 93189c9d33ef8af795b32f6e71b7ab29c4637888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Wed, 4 Mar 2020 14:27:08 +0800 Subject: [PATCH 185/371] ierator -> iterator --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index 309d9cfe..a8c86bc7 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -2102,7 +2102,7 @@ table 与前一个例子中 initRow() 方法的调用次序完全相同。 虽然 Java 中的枚举比 C 或 C++中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。而本章正好说明了一个“小”功能所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。正如我在本书中一再强调的那样,优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。 -关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 ierator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。 +关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 iterator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。 From 661d8cca576013b95ed4ffacd6fed42076adf8c2 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Wed, 4 Mar 2020 15:12:46 +0800 Subject: [PATCH 186/371] fix ierator -> iterator (#393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index 309d9cfe..a8c86bc7 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -2102,7 +2102,7 @@ table 与前一个例子中 initRow() 方法的调用次序完全相同。 虽然 Java 中的枚举比 C 或 C++中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。而本章正好说明了一个“小”功能所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。正如我在本书中一再强调的那样,优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。 -关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 ierator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。 +关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 iterator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。 From ece6f5d401b92dc6fd70f7c1837b642286d83df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Wed, 4 Mar 2020 16:20:10 +0800 Subject: [PATCH 187/371] nextint -> nextInt --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 6fbc8795..3c70fe47 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1171,7 +1171,7 @@ class Part implements Supplier { private static Random rand = new Random(47); public Part get() { - int n = rand.nextint(prototypes.size()); + int n = rand.nextInt(prototypes.size()); return prototypes.get(n).get(); } } From da39cb240cafa21da5c0d6f8ba6538e98da47f43 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Wed, 4 Mar 2020 19:45:29 -0600 Subject: [PATCH 188/371] nextint -> nextInt (#394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt --- docs/book/19-Type-Information.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 6fbc8795..3c70fe47 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1171,7 +1171,7 @@ class Part implements Supplier { private static Random rand = new Random(47); public Part get() { - int n = rand.nextint(prototypes.size()); + int n = rand.nextInt(prototypes.size()); return prototypes.get(n).get(); } } From 75b665ecac2cc1a091f002d8e92ffd5b9489cb42 Mon Sep 17 00:00:00 2001 From: xgsteins <33283328+xgsteins@users.noreply.github.com> Date: Mon, 9 Mar 2020 10:25:21 +0800 Subject: [PATCH 189/371] Fix typo (#398) --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index a8c86bc7..0259ec56 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -817,7 +817,7 @@ UTILITY] KITCHEN] ``` -使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnunSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。 +使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnumSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。 EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在。 也就是说,在不超过一个 long 的表达能力的情况下,你的 EnumSet 可以应用于最多不超过 64 个元素的 enum。如果 enum 超过了 64 个元素会发生什么呢? From beeb36ad3e7f518bd730260a177fa910005b1f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Mon, 9 Mar 2020 15:27:12 +0800 Subject: [PATCH 190/371] =?UTF-8?q?fix=20=E8=BF=99=E5=8F=A5=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E7=AC=AC=E4=B8=80=E5=8F=8D=E5=BA=94=E6=B2=A1=E5=A4=AA?= =?UTF-8?q?=E7=9C=8B=E6=87=82=EF=BC=8C=E7=BB=93=E5=90=88=E5=8E=9F=E6=96=87?= =?UTF-8?q?=E5=92=8C=E4=B8=8B=E9=9D=A2=E7=9A=84=E4=BE=8B=E5=AD=90=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=86=E4=B8=80=E4=B8=8B=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/21-Arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index bda4af74..0ff7dad6 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2045,7 +2045,7 @@ public class ParallelSetAll { 与使用for循环手工执行复制相比,**copyOf()** 和 **copyOfRange()** 复制数组要快得多。这些方法被重载以处理所有类型。 -我们开始复制数组的整数和整数: +我们从复制 **int** 和 **Integer** 数组开始: ```JAVA // arrays/ArrayCopying.java // Demonstrate Arrays.copy() and Arrays.copyOf() From 3c08903cf0e58229fe8ae1360869bc55d6dd7024 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Tue, 10 Mar 2020 08:43:21 +0800 Subject: [PATCH 191/371] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=8B=E7=BF=BB=E8=AF=91=20(#399)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt * fix 这句翻译第一反应没太看懂,结合原文和下面的例子优化了一下翻译 --- docs/book/21-Arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index bda4af74..0ff7dad6 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2045,7 +2045,7 @@ public class ParallelSetAll { 与使用for循环手工执行复制相比,**copyOf()** 和 **copyOfRange()** 复制数组要快得多。这些方法被重载以处理所有类型。 -我们开始复制数组的整数和整数: +我们从复制 **int** 和 **Integer** 数组开始: ```JAVA // arrays/ArrayCopying.java // Demonstrate Arrays.copy() and Arrays.copyOf() From b84fd371c67351e9f7eee8bb17f4fc413543a64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E7=87=95=E9=BE=99?= Date: Tue, 10 Mar 2020 13:59:12 +0800 Subject: [PATCH 192/371] =?UTF-8?q?=E9=9D=A2=E5=90=91=E5=AF=B9=E8=B1=A1=20?= =?UTF-8?q?(#400)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/00-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index 50749578..ad07f27a 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -35,7 +35,7 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中, 可能你已在学校、书籍或网络上了学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。 -你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面对对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。 +你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面向对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。 ## JDK文档 From 7b8094287411818b64e35a75055c9f7272733dea Mon Sep 17 00:00:00 2001 From: bigpengry <32670376+bigpengry@users.noreply.github.com> Date: Tue, 10 Mar 2020 16:17:03 +0800 Subject: [PATCH 193/371] fix #381 (#401) --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 351a9ea9..31ffde5e 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -646,7 +646,7 @@ Derived dynamicGet() 在“初始化和清理”和“复用”两章中已经简单地介绍过构造器的调用顺序,但那时还没有介绍多态。 -在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。这么做是有意义的,因为构造器有着特殊的任务:检查对象是否被正确地构造。由于属性通常声明为 **private**,你必须假定派生类只能访问自己的成员而不能访问基类的成员。只有基类的构造器拥有恰当的知识和权限来初始化自身的元素。因此,必须得调用所有构造器;否则就不能构造完整的对象。这就是编译器强制每个派生类部分必须调用构造器的原因。如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)。 +在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。这么做是有意义的,因为构造器有着特殊的任务:检查对象是否被正确地构造。由于属性通常声明为 **private**,你必须假定派生类只能访问自己的成员而不能访问基类的成员。只有基类的构造器拥有恰当的知识和权限来初始化自身的元素。因此,必须得调用所有构造器;否则就不能构造完整的对象。这就是为什么编译器会强制调用每个派生类中的构造器的原因。如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)。 下面的例子展示了组合、继承和多态在构建顺序上的作用: From e2b674da283423c794e72c0132ace072bd41e3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Fri, 13 Mar 2020 11:57:16 +0800 Subject: [PATCH 194/371] fix ojb -> obj --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index b03c5e64..6b0c0517 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1409,7 +1409,7 @@ class Manipulator3 { private HasF obj; Manipulator3(HasF x) { - ojb = x; + obj = x; } public void manipulate() { From 8df287501de675e800911b9fa1d2031099b1df9f Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Fri, 13 Mar 2020 13:58:23 +0800 Subject: [PATCH 195/371] fix ojb -> obj (#402) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt * fix 这句翻译第一反应没太看懂,结合原文和下面的例子优化了一下翻译 * fix ojb -> obj --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index b03c5e64..6b0c0517 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1409,7 +1409,7 @@ class Manipulator3 { private HasF obj; Manipulator3(HasF x) { - ojb = x; + obj = x; } public void manipulate() { From 8ae607d7d254c10b5d0feba0123d9960046e19d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Fri, 13 Mar 2020 15:01:25 +0800 Subject: [PATCH 196/371] fix example code --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 6b0c0517..e78ed133 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1595,7 +1595,7 @@ import java.util.function.*; import onjava.*; public class FilledList extends ArrayList { - FilledList gen, int size) { + FilledList(Supplier gen, int size) { Suppliers.fill(this, gen, size); } From 03afe1ec52e6795ce1efd3d3c36adb221a6cbdfa Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Fri, 13 Mar 2020 16:19:45 +0800 Subject: [PATCH 197/371] fix example code (#403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt * fix 这句翻译第一反应没太看懂,结合原文和下面的例子优化了一下翻译 * fix ojb -> obj * fix example code --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 6b0c0517..e78ed133 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1595,7 +1595,7 @@ import java.util.function.*; import onjava.*; public class FilledList extends ArrayList { - FilledList gen, int size) { + FilledList(Supplier gen, int size) { Suppliers.fill(this, gen, size); } From 3b2c9dace55b80d5ef3beea1e24c10209e48c94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Sat, 14 Mar 2020 23:46:35 +0800 Subject: [PATCH 198/371] add package&class name --- docs/book/20-Generics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index e78ed133..7e0d32bc 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2702,6 +2702,8 @@ public class CompilerIntelligence { 下面展示一个简单的 **Holder** 类: ```java +// generics/Holder.java + public class Holder { private T value; From 4285663233016fe41f2f135a9790d4f4d7536d4f Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Sat, 14 Mar 2020 23:55:09 +0800 Subject: [PATCH 199/371] add package&class name (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt * fix 这句翻译第一反应没太看懂,结合原文和下面的例子优化了一下翻译 * fix ojb -> obj * fix example code * add package&class name --- docs/book/20-Generics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index e78ed133..7e0d32bc 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2702,6 +2702,8 @@ public class CompilerIntelligence { 下面展示一个简单的 **Holder** 类: ```java +// generics/Holder.java + public class Holder { private T value; From 31b40249c3fcb273a6d7020d0bff84762d121c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Sun, 15 Mar 2020 00:18:10 +0800 Subject: [PATCH 200/371] =?UTF-8?q?fix=20=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 7e0d32bc..a88918a5 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2756,7 +2756,7 @@ false */ ``` -**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何;额扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是"**? extends Fruit**",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。 +**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是"**? extends Fruit**",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。 但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。 From 2153062b0e46806e4c97aa72ad2bbbc7962fd7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E7=87=95=E9=BE=99?= Date: Sun, 15 Mar 2020 12:21:44 +0800 Subject: [PATCH 201/371] =?UTF-8?q?=E2=80=9CJava=20=E5=B0=B1=E4=B8=8D?= =?UTF-8?q?=E4=BC=9A=E6=9F=A5=E6=89=BE=E5=8D=95=E7=8B=AC=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E2=80=9D=E6=94=B9=E4=B8=BA=E2=80=9DJava=20?= =?UTF-8?q?=E5=B0=B1=E4=B8=8D=E4=BC=9A=E6=9F=A5=E6=89=BE=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E2=80=9C=20(#406)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 面向对象 * Update 07-Implementation-Hiding.md “Java 就不会查找单独当前目录”改为”Java 就不会查找当前目录“ --- docs/book/07-Implementation-Hiding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index 884466c1..02737cbf 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -327,7 +327,7 @@ public class Cookie { } ``` -记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找单独当前目录。 +记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找当前目录。 现在,使用 **Cookie** 创建一个程序: ```java From 62dcb5efabeab21a5836311ba8dc26cc51d4b9a3 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Sun, 15 Mar 2020 12:22:14 +0800 Subject: [PATCH 202/371] =?UTF-8?q?fix=20=E9=94=99=E5=88=AB=E5=AD=97?= =?UTF-8?q?=EF=BC=8C=E4=BB=BB=E4=BD=95=E9=A2=9D=EF=BC=9B=20=20->=20?= =?UTF-8?q?=E4=BB=BB=E4=BD=95=20(#405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 忽路->忽略 * 修复跳转链接错误 * fix Class.getInterface() -> Class.getInterfaces() * 否者 -> 否则 * 编译期 -> 编译器 ,原文"the compiler will only allow you to say that the superclass reference is “some class that is a superclass of FancyToy” a" * ierator -> iterator * nextint -> nextInt * fix 这句翻译第一反应没太看懂,结合原文和下面的例子优化了一下翻译 * fix ojb -> obj * fix example code * add package&class name * fix 错别字 --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 7e0d32bc..a88918a5 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2756,7 +2756,7 @@ false */ ``` -**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何;额扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是"**? extends Fruit**",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。 +**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是"**? extends Fruit**",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。 但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。 From b193b9b4808ce51d32c7eb27cab6b7cdc88050dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=9B=81=E9=91=AB?= Date: Sun, 15 Mar 2020 22:30:14 +0800 Subject: [PATCH 203/371] =?UTF-8?q?fix=20=E9=94=99=E5=88=AB=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index a88918a5..6c5aab81 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3584,7 +3584,7 @@ public class UseList { } ``` -因为擦除,所以重载方法产生了的类型签名。 +因为擦除,所以重载方法产生了相同的类型签名。 因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名: From 6096fafbea4d5dfaed2c6d10caa03085a52d4fe2 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Thu, 19 Mar 2020 20:56:00 +0800 Subject: [PATCH 204/371] =?UTF-8?q?=E5=AF=B9=E7=85=A7=E5=8E=9F=E6=96=87?= =?UTF-8?q?=E5=92=8C=E8=AF=AD=E5=A2=83=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=86?= =?UTF-8?q?=E6=95=B4=E6=AE=B5=E7=BF=BB=E8=AF=91=20(#410)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 0f4d8d74..6f19e1e3 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -82,17 +82,17 @@ _并行_ ## 并发的超能力 -想象一下,你是一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并移动走廊。走廊分开了。 +想象一下,你置身于一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并沿着走廊向下移动。走廊分开了。 你自己完成这项任务需要一百个生命周期。 -现在假设你有一个奇怪的超级大国。你可以将自己分开,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最后,你会有一个人在整个建筑物的每个终点走廊。 +现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。 -每个走廊都有一千个房间。你的超级大国正在变得有点瘦,所以你只能让自己50个人同时搜索房间。 +每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出50个自己来搜索这间房间。 -一旦克隆体进入房间,它必须搜索房间的所有裂缝和隐藏的口袋。它切换到第二个超级大国。它分成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不明白这种力量 - 一旦你启动它就会起作用。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然,不知何故,你只知道物品是否在房间里 +一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。 -我很想能够说,“你在科幻小说中的超级大国?这就是并发性。“每当你有更多的任务要解决时,它就像分裂两个一样简单。问题是我们用来描述这种现象的任何模型最终都是抽象的 +我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是泄漏抽象的(leaky abstraction)。 以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 From 91c0847608c83f0bfe4be22d1b0cb995d83380e8 Mon Sep 17 00:00:00 2001 From: XuYanxin Date: Thu, 19 Mar 2020 23:42:55 +0800 Subject: [PATCH 205/371] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BF=BB=E8=AF=91=20?= =?UTF-8?q?(#411)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 对照原文和语境,修复了整段翻译 * 优化翻译 * fix 翻译错误 * fix 单词少了一截 --- docs/book/24-Concurrent-Programming.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 6f19e1e3..09f88c73 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -117,7 +117,7 @@ _并行_ 当“同时”执行的任务相互干扰时,会出现问题。他可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,编写仅看起来可行的并发程序更为常见,但是在适当的条件下,将会失败。这些情况可能会发生,或者很少发生,你在测试期间从未看到它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 这是推动并发的最强有力的论据之一:如果你忽略它,你可能会被咬。 -因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证或检查异常那样的安全网来告诉你何时出现错误。通过并发,你可以自己动手,只有知识渊博,可疑和积极,才能用Java编写可靠的并发代码。 +因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。 @@ -129,7 +129,7 @@ _并行_ 使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。 -但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来像是一个双向的。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。 +但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。 可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 From 814e5b01683d4490737fcde5c44b3904c7418697 Mon Sep 17 00:00:00 2001 From: legendyql Date: Thu, 26 Mar 2020 12:47:11 +0800 Subject: [PATCH 206/371] Update 12-Collections.md (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化翻译: 英文原文:“Here you see both methods, as well as the more conventional addAll() method that’s part of all Collection types:” 原翻译:下边的示例展示了这两个方法,以及更通用的 addAll() 方法,所有 Collection 类型都包含该方法: 应改为:下边的示例展示了这两个方法,以及更通用的 、所有 Collection类型都包含的addAll()`方法: 原翻译中说的“addAll() 方法”,容易误解为上文所说的Collections.addAll()方法,作者此处指的是Collection类型的addAll()方法,改后的翻译意思更明确,更易理解。 --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index fa296446..bc0569b8 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -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** 中。下边的示例展示了这两个方法,以及更通用的 、所有 **Collection** 类型都包含的`addAll()` 方法: ```java // collections/AddingGroups.java From 4fb0202a445860cff40c81cff296b66f3cb77f09 Mon Sep 17 00:00:00 2001 From: xianwdong <408050009@qq.com> Date: Thu, 26 Mar 2020 21:55:55 +0800 Subject: [PATCH 207/371] fix typo (#419) --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 6c5aab81..c2175405 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -349,7 +349,7 @@ public class RandomList extends ArrayList { public static void main(String[] args) { RandomList rs = new RandomList<>(); - Array.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); + Arrays.stream("The quick brown fox jumped over the lazy brown dog".split(" ")).forEach(rs::add); IntStream.range(0, 11).forEach(i -> System.out.print(rs.select() + " ")); } From 30bc85130160d830d32e8cb0b85ac6c643ef6ec9 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Thu, 2 Apr 2020 00:54:09 +0800 Subject: [PATCH 208/371] fix #421 --- docs/book/07-Implementation-Hiding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index 02737cbf..8db30838 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -560,7 +560,7 @@ new PublicConstructor(); 出于两个重要的原因,访问控制在数据类型内部划定了边界。第一个原因是确立客户端程序员可以使用和不能使用的边界。可以在结构中建立自己的内部机制而不必担心客户端程序员偶尔将内部实现作为他们可以使用的接口的一部分。 -这直接引出了第二个原因:将接口与实现分离。如果在一组程序中使用结构,而客户端程序员只能向 **public** 接口发送消息的话,那么就可以自由地修改任何不是 **public** 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码。 +这直接引出了第二个原因:将接口与实现分离。如果在一组程序中使用接口,而客户端程序员只能向 **public** 接口发送消息的话,那么就可以自由地修改任何不是 **public** 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码。 为了清晰起见,你可以采用一种创建类的风格:**public** 成员放在类的开头,接着是 **protected** 成员,包访问权限成员,最后是 **private** 成员。这么做的好处是类的使用者可以从头读起,首先会看到对他们而言最重要的部分(public 成员,因为可以从文件外访问它们),直到遇到非 **public** 成员时停止阅读,下面就是内部实现了: From cbefc1d39fdb4919394e03ed94dbf95f38c834a7 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Thu, 2 Apr 2020 08:39:20 +0800 Subject: [PATCH 209/371] fix issue#415 --- docs/book/00-Preface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index f1843204..daea9e85 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -25,7 +25,7 @@ ## 语言设计错误 -每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错了什么还要糟糕。哎,每一种语言/库的设计错误都会永久地嵌入在 Java 的发行版中。 +每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错在哪里还要糟糕。唉,每一种语言/库的设计错误都会永久地嵌入在 Java 的发行版中。 诺贝尔经济学奖得主约瑟夫·斯蒂格利茨(*Joseph Stiglitz*)有一套适用于这里的人生哲学,叫做“承诺升级理论”:继续犯错误的成本由别人承担,而承认错误的成本由自己承担。 From 4db224983e0706cb73a57d5c4ddd39cfaaf8c02a Mon Sep 17 00:00:00 2001 From: john-h3 <369425422@qq.com> Date: Thu, 2 Apr 2020 14:51:55 +0800 Subject: [PATCH 210/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E4=BA=86=E4=BA=8C?= =?UTF-8?q?=E5=8D=81=E4=BA=94=E7=AB=A0=E7=9A=84=20Interpreter:=20Run-Time?= =?UTF-8?q?=20Flexibility=20(#422)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/25-Patterns.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index d8a8bf87..f3d37647 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -1183,8 +1183,11 @@ public class Facade { 公平起见,*《设计模式》* 主要是写给 C++ 读者的。尽管 C++ 有命名空间(namespaces)机制来防止全局变量和类名称之间的冲突,但它并没有提供类隐藏的机制,而在 Java 里我们可以通过声明 non-public 类来实现这一点。我认为,大多数情况下 Java 的 package 功能就足以解决针对 *外观模式* 的问题了。 -## 解释器 +## 解释器:运行时的弹性 +如果程序的用户需要更好的运行时弹性,例如创建脚本来增加需要的系统功能,你就能使用解释器设计模式。这个模式下,你可以创建一个语言解释器并将它嵌入你的程序内。 + +在开发程序的过程中,设计自己的语言并为它构建一个解释器是一件让人分心且耗时的事。最好的解决方案就是复用代码:使用一个已经构建好并被调试过的解释器。Python 语言可以免费地嵌入营利性的应用中而不需要任何的协议许可、授权费或者是任何的声明。此外,有一个完全使用 Java 字节码实现的 Python 版本(叫做 Jython), 能够轻易地合并到 Java 程序中。Python 是一门非常易学习的脚本语言,代码的读写很有逻辑性。它支持函数与对象,有大量的可用库,并且可运行在所有的平台上。你可以在 [www.Python.org](https://www.python.org/) 上下载 Python 并了解更多信息。 ## 回调 From 552e0dd66ad847f9479c8c95cf674108fcb81690 Mon Sep 17 00:00:00 2001 From: FengBaoheng Date: Fri, 3 Apr 2020 10:01:40 +0800 Subject: [PATCH 211/371] =?UTF-8?q?fix=20typo=20=E6=9C=AC=E7=AB=A0?= =?UTF-8?q?=E5=B0=8F=E8=8A=82->=E6=9C=AC=E7=AB=A0=E5=B0=8F=E7=BB=93=20(#42?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index c2175405..4a0a0add 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1090,7 +1090,7 @@ Serializable] */ ``` -在第十二章 [集合的本章小节](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。 +在第十二章 [集合的本章小结](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。 From a1903a20e1634a9631716993035871d9db3a90d7 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Mon, 6 Apr 2020 19:48:51 +0800 Subject: [PATCH 212/371] fix issue #426 --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 31ffde5e..0e4eac6e 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -1156,7 +1156,7 @@ SadActor **Stage** 对象中包含了 **Actor** 引用,该引用被初始化为指向一个 **HappyActor** 对象,这意味着 `performPlay()` 会产生一个特殊行为。但是既然引用可以在运行时与其他不同的对象绑定,那么它就可以被替换成对 **SadActor** 的引用,`performPlay()` 的行为随之改变。这样你就获得了运行时的动态灵活性(这被称为状态模式)。与之相反,我们不能在运行时决定继承不同的对象,那在编译时就完全确定下来了。 -有一条通用准则:使用继承表达行为的差异,使用属性表达状态的变化。在上个例子中,两者都用到了。通过继承的到的两个不同类在 `act()` 方法中表达了不同的行为,**Stage** 通过组合使自己的状态发生变化。这里状态的改变产生了行为的改变。 +有一条通用准则:使用继承表达行为的差异,使用属性表达状态的变化。在上个例子中,两者都用到了。通过继承得到的两个不同类在 `act()` 方法中表达了不同的行为,**Stage** 通过组合使自己的状态发生变化。这里状态的改变产生了行为的改变。 ### 替代 vs 扩展 From 7653db6417eadbc461c3b652b1b23ec3f65a93d8 Mon Sep 17 00:00:00 2001 From: alton zheng <53368134+alton-zheng@users.noreply.github.com> Date: Wed, 8 Apr 2020 10:16:37 +0800 Subject: [PATCH 213/371] alton#417 (#418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * alton#417 * fix issue#417 * fix issue#417 20200328 * fix issue#417 20180328 15:16 * fix issue#417 异常检查 -> 检查型异常 * fix issue#417 20180328 16:00 * fix issue#417 * fix issue#417 --- docs/book/24-Concurrent-Programming.md | 672 +++++++++++++++---------- 1 file changed, 408 insertions(+), 264 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 09f88c73..f863dc8b 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -26,6 +26,7 @@ 对于更多凌乱,低级别的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读Brian Goetz等人的Java Concurrency in Practice。虽然在写作时,这本书已有十多年的历史,但它仍然包含你必须了解和理解的必需品。理想情况下,本章和附录是该书的精心准备。另一个有价值的资源是**Bill Venner**的Inside the Java Virtual Machine,它详细描述了JVM的最内部工作方式,包括线程。 + ## 术语问题 在编程文献中并发、并行、多任务、多处理、多线程、分布式系统(以及可能的其他)使用了许多相互冲突的方式,并且经常被混淆。Brian Goetz在2016年的演讲中指出了这一点[From Concurrent to Parallel](https://www.youtube.com/watch?v=NsDE7E8sIdQ),他提出了一个合理的解释: @@ -739,7 +740,6 @@ public class ParallelStreamPuzzle3 { 实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,只需将**parallel()**打到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现你的目标。 - ## 创建和运行任务 如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。 @@ -1200,9 +1200,7 @@ public class CountingStream { - Lambda和方法引用作为任务 -使用lambdas和方法引用,你不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将notRunnables或Callables的参数传递给ExecutorService: - -使用lambdas和方法引用,你不仅限于使用**Runnables**和**Callables**。因为Java 8通过匹配签名来支持lambda和方法引用(即,它支持结构一致性),所以我们可以将不是**Runnables**或**Callables**的参数传递给**ExecutorService**: +在 `java8` , 你不需要受限于在 `Runnables ` 和 `Callables` 时,使用`lambdas` 和方法引用, 同样也可以通过匹配签名来引用(即,它支持结构一致性)。 所以我们可以将 `notRunnables` 或 `Callables` 的参数传递给`ExecutorService` : ```java // concurrent/LambdasAndMethodReferences.java @@ -1223,12 +1221,12 @@ public class LambdasAndMethodReferences { ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(() -> System.out.println("Lambda1")); - exec.submit(newNotRunnable()::go); + exec.submit(new NotRunnable()::go); exec.submit(() -> { System.out.println("Lambda2"); return 1; }); - exec.submit(newNotCallable()::get); + exec.submit(new NotCallable()::get); exec.shutdown(); } } @@ -1326,7 +1324,6 @@ public class QuittingTasks { 我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 - ## CompletableFuture类 作为介绍,这里是使用CompletableFutures在QuittingTasks.java中: @@ -1360,11 +1357,11 @@ public class QuittingCompletable { 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3 ``` -任务是一个**List **,就像在**QuittingTasks.java**中一样,但是在这个例子中,没有**peek()**将每个**QuittableTask**提交给**ExecutorService**。相反,在创建cfutures期间,每个任务都交给**CompletableFuture::runAsync**。这执行**VerifyTask.run(**)并返回**CompletableFuture **。因为**run()**不返回任何内容,所以在这种情况下我只使用**CompletableFuture**调用**join()**来等待它完成。 +任务是一个 `List`,就像在 `QuittingTasks.java` 中一样,但是在这个例子中,没有 `peek()` 将每个 `QuittableTask` 提交给 `ExecutorService`。相反,在创建 `cfutures` 期间,每个任务都交给 `CompletableFuture::runAsync`。这执行 `VerifyTask.run()` 并返回 `CompletableFuture` 。因为 `run()` 不返回任何内容,所以在这种情况下我只使用 `CompletableFuture` 调用 `join()` 来等待它完成。 -在此示例中需要注意的重要事项是,运行任务不需要**ExecutorService**。这由**CompletableFuture**管理(尽管有提供自己的**ExecutorService**的选项)。你也不需要调用**shutdown()**;事实上,除非你像我这样明确地调用**join()**,程序将尽快退出,而不必等待任务完成。 +在本例中需要注意的重要一点是,运行任务不需要使用 `ExecutorService`。而是直接交给 `CompletableFuture` 管理 (不过你可以向它提供自己定义的 `ExectorService`)。您也不需要调用 `shutdown()`;事实上,除非你像我在这里所做的那样显式地调用 `join()`,否则程序将尽快退出,而不必等待任务完成。 -这个例子只是一个起点。你很快就会看到ComplempleFutures能够做得更多。 +这个例子只是一个起点。你很快就会看到 `ComplempleFuture` 能够做得更多。 ### 基本用法 @@ -1379,7 +1376,7 @@ public class Machina { State step() { if(equals(END)) return END; - return values()[ordinal() + 1]; + return values()[ordinal() + 1]; } } private State state = State.START; @@ -1392,11 +1389,12 @@ public class Machina { new Nap(0.1); m.state = m.state.step(); } - System.out.println(m);return m; + System.out.println(m); + return m; } @Override - public StringtoString() { - return"Machina" + id + ": " + (state.equals(State.END)? "complete" : state); + public String toString() { + return"Machina" + id + ": " + (state.equals(State.END)? "complete" : state); } } @@ -1404,7 +1402,7 @@ public class Machina { 这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。 -我们可以用**CompletableFuture**做的一件事是使用**completedFuture()**将它包装在感兴趣的对象中 +**CompletableFuture**可以被用来做的一件事是, 使用**completedFuture()**将它感兴趣的对象进行包装。 ```java // concurrent/CompletedMachina.java @@ -1428,7 +1426,7 @@ public class CompletedMachina { 通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。 -当我们将**handle()**包装在**CompletableFuture**中时,我们发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,事情变得更加有趣: +当我们将**handle()**包装在**CompletableFuture**中时,发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,使得事情变得更加有趣: ```java // concurrent/CompletableApply.java @@ -1450,7 +1448,7 @@ public class CompletableApply { } ``` -输出结果: +**输出结果**: ``` Machina0: ONE @@ -1459,9 +1457,9 @@ Machina0: THREE Machina0: complete ``` -**thenApply()**应用一个接受输入并产生输出的函数。在这种情况下,**work()**函数产生与它相同的类型,因此每个得到的**CompletableFuture**仍然被输入为**Machina**,但是(类似于**Streams**中的**map()**)**Function**也可以返回不同的类型,这将反映在返回类型 +`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是(类似于流中的 `map()` )函数也可以返回不同的类型,这将体现在返回类型上。 -你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这样你就不会陷入麻烦的细节,这使得编写和理解代码变得更加简单。 +你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。 我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样: @@ -1493,10 +1491,10 @@ Machina0: complete 514 ``` -在这里,我们还添加了一个**Timer**,它向我们展示每一步增加100毫秒,还有一些额外的开销。 -**CompletableFutures**的一个重要好处是它们鼓励使用私有子类原则(不分享任何东西)。默认情况下,使用**thenApply()**来应用一个不与任何人通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发性方面非常有效[^5]。并行流和ComplempleFutures旨在支持这些原则。只要你不决定共享数据(共享非常容易,甚至意外)你可以编写相对安全的并发程序。 +这里我们还添加了一个 `Timer`,它的功能在每一步都显性地增加 100ms 等待时间之外,还将 `CompletableFuture` 内部 `thenApply` 带来的额外开销给体现出来了。 +**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。 -回调**thenApply()**开始一个操作,在这种情况下,在完成所有任务之前,不会完成**e CompletableFuture**的创建。虽然这有时很有用,但是启动所有任务通常更有价值,这样就可以运行时继续前进并执行其他操作。我们通过在操作结束时添加Async来实现此目的: +回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的: ```java // concurrent/CompletableApplyAsync.java @@ -1531,67 +1529,75 @@ Machina0: complete 552 ``` -同步调用(我们通常使用得那种)意味着“当你完成工作时,返回”,而异步调用以意味着“立刻返回但是继续后台工作。”正如你所看到的,**cf**的创建现在发生得跟快。每次调用 **thenApplyAsync()** 都会立刻返回,因此可以进行下一次调用,整个链接序列的完成速度比以前快得快。 +同步调用(我们通常使用的那种)意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。 -事实上,如果没有回调**cf.join() t**方法,程序会在完成其工作之前退出(尝试取出该行)对**join()**阻止了**main()**进程的进行,直到cf操作完成,我们可以看到大部分时间的确在哪里度过。 +事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到cf操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。 -这种“立即返回”的异步能力需要**CompletableFuture**库进行一些秘密工作。特别是,它必须将你需要的操作链存储为一组回调。当第一个后台操作完成并返回时,第二个后台操作必须获取生成的**Machina**并开始工作,当完成后,下一个操作将接管,等等。但是没有我们普通的函数调用序列,通过程序调用栈控制,这个顺序会丢失,所以它使用回调 - 一个函数地址表来存储。 +这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。 -幸运的是,你需要了解有关回调的所有信息。程序员将你手工造成的混乱称为“回调地狱”。通过异步调用,**CompletableFuture**为你管理所有回调。除非你知道关于你的系统有什么特定的改变,否则你可能想要使用异步调用。 +幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道系统的一些具体的变化,否则你更想使用异步调用来实现程序。 - 其他操作 -当你查看**CompletableFuture**的Javadoc时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有**thenApply()**,**thenApplyAsync()**和**thenApplyAsync()**的第二种形式,它接受运行任务的**Executor**(在本书中我们忽略了**Executor**选项)。 -这是一个显示所有“基本”操作的示例,它们不涉及组合两个CompletableFutures或异常(我们将在稍后查看)。首先,我们将重复使用两个实用程序以提供简洁和方便: +当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务(在本书中,我们忽略了 `Executor` 选项)。 + +下面的示例展示了所有"基本"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常(我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序: ```java -// concurrent/CompletableUtilities.java -package onjava; import java.util.concurrent.*; +package onjava; +import java.util.concurrent.*; + public class CompletableUtilities { - // Get and show value stored in a CF: - public static void showr(CompletableFuture c) { - try { - System.out.println(c.get()); - } catch(InterruptedException - | ExecutionException e) { - throw new RuntimeException(e); - } + // Get and show value stored in a CF: + public static void showr(CompletableFuture c) { + try { + System.out.println(c.get()); + } catch(InterruptedException + | ExecutionException e) { + throw new RuntimeException(e); } - // For CF operations that have no value: - public static void voidr(CompletableFuture c) { - try { - c.get(); // Returns void - } catch(InterruptedException - | ExecutionException e) { - throw new RuntimeException(e); - } + } + // For CF operations that have no value: + public static void voidr(CompletableFuture c) { + try { + c.get(); // Returns void + } catch(InterruptedException + | ExecutionException e) { + throw new RuntimeException(e); } + } } ``` -**showr()**在**CompletableFuture **上调用**get()**并显示结果,捕获两个可能的异常。**voidr()**是**CompletableFuture **的**showr()**版本,即**CompletableFutures**,仅在任务完成或失败时显示。 +`showr()` 在 `CompletableFuture` 上调用 `get()`,并显示结果,`try/catch` 两个可能会出现的异常。 -为简单起见,以下**CompletableFutures**只包装整数。**cfi()**是一个方便的方法,它在完成的**CompletableFuture **中包装一个**int**: +`voidr()` 是 `CompletableFuture` 的 `showr()` 版本,也就是说,`CompletableFutures` 只为任务完成或失败时显示信息。 + +为简单起见,下面的 `CompletableFutures` 只包装整数。`cfi()` 是一个便利的方法,它把一个整数包装在一个完整的 `CompletableFuture` : ```java // concurrent/CompletableOperations.java import java.util.concurrent.*; import static onjava.CompletableUtilities.*; + public class CompletableOperations { static CompletableFuture cfi(int i) { - return CompletableFuture.completedFuture( Integer.valueOf(i)); + return + CompletableFuture.completedFuture( + Integer.valueOf(i)); } + public static void main(String[] args) { showr(cfi(1)); // Basic test voidr(cfi(2).runAsync(() -> - System.out.println("runAsync"))); + System.out.println("runAsync"))); voidr(cfi(3).thenRunAsync(() -> - System.out.println("thenRunAsync"))); + System.out.println("thenRunAsync"))); voidr(CompletableFuture.runAsync(() -> - System.out.println("runAsync is static"))); + System.out.println("runAsync is static"))); showr(CompletableFuture.supplyAsync(() -> 99)); voidr(cfi(4).thenAcceptAsync(i -> - System.out.println("thenAcceptAsync: " + i))); + System.out.println("thenAcceptAsync: " + i))); showr(cfi(5).thenApplyAsync(i -> i + 42)); showr(cfi(6).thenComposeAsync(i -> cfi(i + 99))); CompletableFuture c = cfi(7); @@ -1603,24 +1609,27 @@ public class CompletableOperations { showr(c); c = new CompletableFuture<>(); c.cancel(true); - System.out.println("cancelled: " + c.isCancelled()); + System.out.println("cancelled: " + + c.isCancelled()); System.out.println("completed exceptionally: " + - c.isCompletedExceptionally()); + c.isCompletedExceptionally()); System.out.println("done: " + c.isDone()); System.out.println(c); c = new CompletableFuture<>(); System.out.println(c.getNow(777)); c = new CompletableFuture<>(); c.thenApplyAsync(i -> i + 42) - .thenApplyAsync(i -> i * 12); - System.out.println("dependents: " + c.getNumberOfDependents()); + .thenApplyAsync(i -> i * 12); + System.out.println("dependents: " + + c.getNumberOfDependents()); c.thenApplyAsync(i -> i / 2); - System.out.println("dependents: " + c.getNumberOfDependents()); + System.out.println("dependents: " + + c.getNumberOfDependents()); } } ``` -输出结果: +**输出结果** : ``` 1 @@ -1643,112 +1652,152 @@ dependents: 1 dependents: 2 ``` -**main()**包含一系列可由其**int**值引用的测试。**cfi(1)**演示了**showr()**正常工作。**cfi(2)**是调用**runAsync()**的示例。由于**Runnable**不产生返回值,因此结果是**CompletableFuture **,因此使用**voidr()**。 -注意使用**cfi(3)**,**thenRunAsync()**似乎与**runAsync()**一致,差异显示在后续的测试中: -**runAsync()**是一个静态方法,所以你不会像**cfi(2)**一样调用它。相反你可以在**QuittingCompletable.java**中使用它。后续测试中**supplyAsync()**也是静态方法,但是需要一个**Supplier**而不是**Runnable**并产生一个**CompletableFuture**来代替**CompletableFuture**。 -含有“then”的方法将进一步的操作应用于现有的**CompletableFuture **。与**thenRunAsync()**不同的是,将**cfi(4)**,**cfi(5)**和**cfi(6)**的“ then”方法作为未包装的**Integer**的参数。如你通过使用**voidr()**所见,然后**AcceptAsync()**接受了一个**Consumer**,因此不会产生结果。**thenApplyAsync()**接受一个**Function**并因此产生一个结果(该结果的类型可以不同于其参数)。**thenComposeAsync()**与**thenApplyAsync()**非常相似,不同之处在于其Function必须产生已经包装在**CompletableFuture**中的结果。 -**cfi(7)**示例演示了**obtrudeValue()**,它强制将值作为结果。**cfi(8)**使用**toCompletableFuture()**从**CompletionStage**生成**CompletableFuture**。**c.complete(9)**显示了如何通过给它一个结果来完成一个任务(**future**)(与**obtrudeValue()**相对,后者可能会迫使其结果替换该结果)。 -如果你调用**CompletableFuture**中的**cancel()**方法,它也会完成并且是非常好的完成。 -如果任务(**future**)未完成,则**getNow()**方法返回**CompletableFuture**的完成值,或者返回**getNow()**的替换参数。 -最后,我们看一下依赖(dependents)的概念。如果我们将两个**thenApplyAsync()**调用链接到**CompletableFuture**上,则依赖项的数量仍为1。但是,如果我们将另一个**thenApplyAsync()**直接附加到**c**,则现在有两个依赖项:两个链和另一个链。这表明你可以拥有一个**CompletionStage**,当它完成时,可以根据其结果派生多个新任务。 - -### 结合CompletableFutures - -第二类**CompletableFuture**方法采用两个**CompletableFuture**并以各种方式将它们组合在一起。一个**CompletableFuture**通常会先于另一个完成,就好像两者都在比赛中一样。这些方法使你可以以不同的方式处理结果。 -为了对此进行测试,我们将创建一个任务,该任务将完成的时间作为其参数之一,因此我们可以控制。 -**CompletableFuture**首先完成: - +- `main()` 包含一系列可由其 `int` 值引用的测试。 + - `cfi(1)` 演示了 `showr()` 正常工作。 + - `cfi(2)` 是调用 `runAsync()` 的示例。由于 `Runnable` 不产生返回值,因此使用了返回 `CompletableFuture ` 的`voidr()` 方法。 + - 注意使用 `cfi(3)`,`thenRunAsync()` 效果似乎与 上例 `cfi(2)` 使用的 `runAsync()`相同,差异在后续的测试中体现: + - `runAsync()` 是一个 `static` 方法,所以你通常不会像`cfi(2)`一样调用它。相反你可以在 `QuittingCompletable.java` 中使用它。 + - 后续测试中表明 `supplyAsync()` 也是静态方法,区别在于它需要一个 `Supplier` 而不是`Runnable`, 并产生一个`CompletableFuture` 而不是 `CompletableFuture`。 + - `then` 系列方法将对现有的 `CompletableFuture` 进一步操作。 + - 与 `thenRunAsync()` 不同,`cfi(4)`,`cfi(5)` 和`cfi(6)` "then" 方法的参数是未包装的 `Integer`。 + - 通过使用 `voidr()`方法可以看到: + - `AcceptAsync()`接收了一个 `Consumer`,因此不会产生结果。 + - `thenApplyAsync()` 接收一个`Function`, 并生成一个结果(该结果的类型可以不同于其输入类型)。 + - `thenComposeAsync()` 与 `thenApplyAsync()`非常相似,唯一区别在于其 `Function` 必须产生已经包装在`CompletableFuture`中的结果。 + - `cfi(7)` 示例演示了 `obtrudeValue()`,它强制将值作为结果。 + - `cfi(8)` 使用 `toCompletableFuture()` 从 `CompletionStage` 生成一个`CompletableFuture`。 + - `c.complete(9)` 显示了如何通过给它一个结果来完成一个`task`(`future`)(与 `obtrudeValue()` 相对,后者可能会迫使其结果替换该结果)。 + - 如果你调用 `CompletableFuture`中的 `cancel()`方法,如果已经完成此任务,则正常结束。 如果尚未完成,则使用 `CancellationException` 完成此 `CompletableFuture`。 + - 如果任务(`future`)完成,则**getNow()**方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。 + - 最后,我们看一下依赖(`dependents`)的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。 + - 这表明你可以使用一个`CompletionStage`,当它完成时,可以根据其结果派生多个新任务。 + + + +### 结合 CompletableFuture + +第二种类型的 `CompletableFuture` 方法采用两种 `CompletableFuture` 并以各异方式将它们组合在一起。就像两个人在比赛一样, 一个`CompletableFuture`通常比另一个更早地到达终点。这些方法允许你以不同的方式处理结果。 +为了测试这一点,我们将创建一个任务,它有一个我们可以控制的定义了完成任务所需要的时间量的参数。 +CompletableFuture 先完成: ```java // concurrent/Workable.java import java.util.concurrent.*; import onjava.Nap; + public class Workable { String id; final double duration; + public Workable(String id, double duration) { this.id = id; this.duration = duration; } + @Override public String toString() { return "Workable[" + id + "]"; } + public static Workable work(Workable tt) { new Nap(tt.duration); // Seconds tt.id = tt.id + "W"; System.out.println(tt); return tt; } + public static CompletableFuture make(String id, double duration) { - return CompletableFuture.completedFuture( new Workable(id, duration)) .thenApplyAsync(Workable::work); + return CompletableFuture + .completedFuture( + new Workable(id, duration) + ) + .thenApplyAsync(Workable::work); } } ``` -在**make()**中,**work()**方法应用于**CompletableFuture.work()**需要持续时间才能完成,然后将字母W附加到id上以指示工作已完成。 -现在,我们可以创建多个竞争的**CompletableFuture**,并使用**CompletableFuture**库: +在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母W附加到id上,表示工作已经完成。 + +现在我们可以创建多个竞争的 `CompletableFuture`,并使用 `CompletableFuture` 库中的各种方法来进行操作: ```java // concurrent/DualCompletableOperations.java import java.util.concurrent.*; import static onjava.CompletableUtilities.*; + public class DualCompletableOperations { static CompletableFuture cfA, cfB; + static void init() { cfA = Workable.make("A", 0.15); - cfB = Workable.make("B", 0.10);// Always wins + cfB = Workable.make("B", 0.10); // Always wins } + static void join() { cfA.join(); cfB.join(); System.out.println("*****************"); } + public static void main(String[] args) { init(); - voidr(cfA.runAfterEitherAsync(cfB, () -> System.out.println("runAfterEither"))); + voidr(cfA.runAfterEitherAsync(cfB, () -> + System.out.println("runAfterEither"))); join(); + init(); - voidr(cfA.runAfterBothAsync(cfB, () -> System.out.println("runAfterBoth"))); + voidr(cfA.runAfterBothAsync(cfB, () -> + System.out.println("runAfterBoth"))); join(); + init(); showr(cfA.applyToEitherAsync(cfB, w -> { System.out.println("applyToEither: " + w); return w; })); join(); + init(); voidr(cfA.acceptEitherAsync(cfB, w -> { System.out.println("acceptEither: " + w); })); join(); + init(); - voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> { System.out.println("thenAcceptBoth: " + w1 + ", " + w2); + voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> { + System.out.println("thenAcceptBoth: " + + w1 + ", " + w2); })); join(); + init(); showr(cfA.thenCombineAsync(cfB, (w1, w2) -> { - System.out.println("thenCombine: " + w1 + ", " + w2); + System.out.println("thenCombine: " + + w1 + ", " + w2); return w1; })); join(); + init(); CompletableFuture - cfC = Workable.make("C", 0.08), - cfD = Workable.make("D", 0.09); + cfC = Workable.make("C", 0.08), + cfD = Workable.make("D", 0.09); CompletableFuture.anyOf(cfA, cfB, cfC, cfD) - .thenRunAsync(() -> System.out.println("anyOf")); + .thenRunAsync(() -> + System.out.println("anyOf")); join(); + init(); cfC = Workable.make("C", 0.08); cfD = Workable.make("D", 0.09); CompletableFuture.allOf(cfA, cfB, cfC, cfD) - .thenRunAsync(() -> System.out.println("allOf")); + .thenRunAsync(() -> + System.out.println("allOf")); join(); } } ``` -输出结果: +**输出结果**: ``` Workable[BW] @@ -1791,109 +1840,149 @@ thenAcceptBoth: Workable[AW], Workable[BW] allOf ``` -为了便于访问,**cfA**和**cfB**是静态的。**init()**总是使用较短的延迟(因此总是“获胜”)使用“ B”初始化两者。**join()**是在这两种方法上调用**join()**并显示边框的另一种便捷方法。 -所有这些“双重”方法都以一个**CompletableFuture**作为调用该方法的对象,第二个**CompletableFuture**作为第一个参数,然后是要执行的操作。 -通过使用**Shower()**和**void()**,你可以看到“运行”和“接受”是终端操作,而“应用”和“组合”产生了新的承载载荷的**CompletableFutures**。 +- 为了方便访问, 将 `cfA` 和 `cfB` 定义为 `static`的。 + - `init()`方法用于 `A`, `B` 初始化这两个变量,因为 `B` 总是给出比`A`较短的延迟,所以总是 `win` 的一方。 + - `join()` 是在两个方法上调用 `join()` 并显示边框的另一个便利方法。 +- 所有这些 “`dual`” 方法都以一个 `CompletableFuture` 作为调用该方法的对象,第二个 `CompletableFuture` 作为第一个参数,然后是要执行的操作。 +- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载)的 `CompletableFuture`。 +- 方法的名称不言自明,你可以通过查看输出来验证这一点。一个特别有趣的方法是 `combineAsync()`,它等待两个 `CompletableFuture` 完成,然后将它们都交给一个 `BiFunction`,这个 `BiFunction` 可以将结果加入到最终的 `CompletableFuture` 的有效负载中。 -方法的名称是不言自明的,你可以通过查看输出来验证这一点。一个特别有趣的方法是CombineAsync(),它等待两个**CompletableFuture**完成,然后将它们都交给BiFunction,然后BiFunction可以将结果加入到所得**CompletableFuture**的有效负载中。 ### 模拟 -作为一个示例,说明如何使用**CompletableFutures**将一系列操作组合在一起,让我们模拟制作蛋糕的过程。在第一个阶段中,我们准备并将成分混合成面糊: +作为使用 `CompletableFuture` 将一系列操作组合的示例,让我们模拟一下制作蛋糕的过程。在第一阶段,我们准备并将原料混合成面糊: ```java // concurrent/Batter.java import java.util.concurrent.*; import onjava.Nap; + public class Batter { - static class Eggs {} - static class Milk {} - static class Sugar {} - static class Flour {} + static class Eggs { + } + + static class Milk { + } + + static class Sugar { + } + + static class Flour { + } + static T prepare(T ingredient) { new Nap(0.1); return ingredient; } + static CompletableFuture prep(T ingredient) { return CompletableFuture .completedFuture(ingredient) .thenApplyAsync(Batter::prepare); } + public static CompletableFuture mix() { - CompletableFuture eggs = prep(new Eggs()); CompletableFuture milk = prep(new Milk()); CompletableFuture sugar = prep(new Sugar()); CompletableFuture flour = prep(new Flour()); CompletableFuture.allOf(eggs, milk, sugar, flour) - .join(); + CompletableFuture eggs = prep(new Eggs()); + CompletableFuture milk = prep(new Milk()); + CompletableFuture sugar = prep(new Sugar()); + CompletableFuture flour = prep(new Flour()); + CompletableFuture + .allOf(eggs, milk, sugar, flour) + .join(); new Nap(0.1); // Mixing time return CompletableFuture.completedFuture(new Batter()); } } - ``` -每种成分都需要一些时间来准备。**allOf()**等待所有配料准备就绪,然后需要更多时间将其混合到面糊中。 - -接下来,我们将单批面糊放入四个锅中进行烘烤。产品作为**CompletableFutures**流返回: +每种原料都需要一些时间来准备。`allOf()` 等待所有的配料都准备好,然后使用更多些的时间将其混合成面糊。接下来,我们把单批面糊放入四个平底锅中烘烤。产品作为 `CompletableFutures` 流返回: ```java // concurrent/Baked.java + import java.util.concurrent.*; import java.util.stream.*; import onjava.Nap; + public class Baked { - static class Pan {} + static class Pan { + } + static Pan pan(Batter b) { new Nap(0.1); return new Pan(); } + static Baked heat(Pan p) { new Nap(0.1); return new Baked(); } - static CompletableFuture bake(CompletableFuture cfb){ - return cfb.thenApplyAsync(Baked::pan) - .thenApplyAsync(Baked::heat); + + static CompletableFuture bake(CompletableFuture cfb) { + return cfb + .thenApplyAsync(Baked::pan) + .thenApplyAsync(Baked::heat); } + public static Stream> batch() { CompletableFuture batter = Batter.mix(); - return Stream.of(bake(batter), bake(batter), bake(batter), bake(batter)); + return Stream.of( + bake(batter), + bake(batter), + bake(batter), + bake(batter) + ); } } ``` -最后,我们创建了一批糖,并用它对蛋糕进行糖化: +最后,我们制作了一批糖,并用它对蛋糕进行糖化: ```java // concurrent/FrostedCake.java + import java.util.concurrent.*; import java.util.stream.*; import onjava.Nap; + final class Frosting { - private Frosting() {} + private Frosting() { + } + static CompletableFuture make() { new Nap(0.1); - return CompletableFuture.completedFuture(new Frosting()); + return CompletableFuture + .completedFuture(new Frosting()); } } + public class FrostedCake { public FrostedCake(Baked baked, Frosting frosting) { new Nap(0.1); } + @Override public String toString() { return "FrostedCake"; } + public static void main(String[] args) { - Baked.batch() - .forEach(baked -> baked.thenCombineAsync(Frosting.make(), (cake, frosting) -> new FrostedCake(cake, frosting)) .thenAcceptAsync(System.out::println) - .join()); + Baked.batch().forEach( + baked -> baked + .thenCombineAsync(Frosting.make(), + (cake, frosting) -> + new FrostedCake(cake, frosting)) + .thenAcceptAsync(System.out::println) + .join()); } } ``` -一旦你对背后的想法感到满意。**CompletableFutures**它们相对易于使用。 +一旦你习惯了这种背后的想法, `CompletableFuture` 它们相对易于使用。 -### 例外情况 +### 异常 -与**CompletableFutur**e在处理链中包装对象的方式相同,它还可以缓冲异常。这些不会在处理过程中显示给调用者,而只会在你尝试提取结果时显示。为了展示它们是如何工作的,我们将从创建一个在某些情况下引发异常的类开始: +与 `CompletableFuture` 在处理链中包装对象的方式相同,它也会缓冲异常。这些在处理时调用者是无感的,但仅当你尝试提取结果时才会被告知。为了说明它们是如何工作的,我们首先创建一个类,它在特定的条件下抛出一个异常: ```java // concurrent/Breakable.java @@ -1901,18 +1990,25 @@ import java.util.concurrent.*; public class Breakable { String id; private int failcount; + public Breakable(String id, int failcount) { this.id = id; this.failcount = failcount; } + @Override public String toString() { return "Breakable_" + id + " [" + failcount + "]"; } + public static Breakable work(Breakable b) { - if(--b.failcount == 0) { - System.out.println( "Throwing Exception for " + b.id + ""); - throw new RuntimeException( "Breakable_" + b.id + " failed"); + if (--b.failcount == 0) { + System.out.println( + "Throwing Exception for " + b.id + "" + ); + throw new RuntimeException( + "Breakable_" + b.id + " failed" + ); } System.out.println(b); return b; @@ -1920,23 +2016,25 @@ public class Breakable { } ``` -**failcount**为正时,每次将对象传递给**work()**方法可减少**failcount**。当它为零时,**work()**会引发异常。如果你给它的**failcount**为零,则它永远不会引发异常。 -请注意,它报告在抛出异常时抛出异常。 -在下面的**test()**方法中,**work()**多次应用于**Breakable**,因此,如果**failcount**在范围内,则会引发异常。但是,在测试**A**到**E**中,你可以从输出中看到抛出了异常,但是它们从未出现: +当`failcount` > 0,且每次将对象传递给 `work()` 方法时, `failcount - 1` 。当`failcount - 1 = 0` 时,`work()` 将抛出一个异常。如果传给 `work()` 的 `failcount = 0` ,`work()` 永远不会抛出异常。 + +注意,异常信息此示例中被抛出( `RuntimeException` ) + +在下面示例 `test()` 方法中,`work()` 多次应用于 `Breakable`,因此如果 `failcount` 在范围内,就会抛出异常。然而,在测试`A`到`E`中,你可以从输出中看到抛出了异常,但它们从未出现: ```java // concurrent/CompletableExceptions.java import java.util.concurrent.*; public class CompletableExceptions { static CompletableFuture test(String id, int failcount) { - return - CompletableFuture.completedFuture( + return CompletableFuture.completedFuture( new Breakable(id, failcount)) .thenApply(Breakable::work) .thenApply(Breakable::work) .thenApply(Breakable::work) .thenApply(Breakable::work); } + public static void main(String[] args) { // Exceptions don't appear ... test("A", 1); @@ -1947,22 +2045,24 @@ public class CompletableExceptions { // ... until you try to fetch the value: try { test("F", 2).get(); // or join() - } catch(Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); } // Test for exceptions: System.out.println( - test("G", 2).isCompletedExceptionally()); + test("G", 2).isCompletedExceptionally() + ); // Counts as "done": System.out.println(test("H", 2).isDone()); // Force an exception: CompletableFuture cfi = - new CompletableFuture<>(); + new CompletableFuture<>(); System.out.println("done? " + cfi.isDone()); - cfi.completeExceptionally( new RuntimeException("forced")); + cfi.completeExceptionally( + new RuntimeException("forced")); try { cfi.get(); - } catch(Exception e) { + } catch (Exception e) { System.out.println(e.getMessage()); } } @@ -1999,10 +2099,11 @@ done? false java.lang.RuntimeException: forced ``` -测试**A**到**E**运行到抛出异常的地步,然后……什么都没有。只有在测试**F**中调用**get()**时,我们才能看到抛出的异常。 -测试**G**显示,你可以首先检查在处理过程中是否引发了异常,而没有引发该异常。但是,测试H告诉我们,无论异常成功与否,异常仍然可以被视为“完成” -代码的最后一部分显示了如何在**CompletableFuture**中插入异常,而不管是否存在任何故障。 -加入或获取结果时,我们不会使用粗略的try-catch,而是使用**CompletableFuture**提供的更复杂的机制来自动响应异常。你可以使用与所有**CompletableFuture**相同的表格来执行此操作:在链中插入**CompletableFuture**调用。有三个选项:**exclusively(**),**handle()**和**whenComplete()**: +测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试F中调用 `get()` 时,我们才会看到抛出的异常。 +测试 `G` 表明,你可以首先检查在处理期间是否抛出异常,而不抛出该异常。然而,test `H` 告诉我们,不管异常是否成功,它仍然被视为已“完成”。 +代码的最后一部分展示了如何将异常插入到 `CompletableFuture` 中,而不管是否存在任何失败。 +在连接或获取结果时,我们使用 `CompletableFuture` 提供的更复杂的机制来自动响应异常,而不是使用粗糙的 `try-catch`。 +你可以使用与我们看到的所有 `CompletableFuture` 相同的表单来完成此操作:在链中插入一个 `CompletableFuture` 调用。有三个选项 `exceptionally()`,`handle()`, `whenComplete()`: ```java // concurrent/CatchCompletableExceptions.java @@ -2010,38 +2111,42 @@ import java.util.concurrent.*; public class CatchCompletableExceptions { static void handleException(int failcount) { // Call the Function only if there's an - // exception, must produce same type as came in: + // exception, must produce same type as came in: CompletableExceptions - .test("exceptionally", failcount) - .exceptionally((ex) -> { // Function - if(ex == null) - System.out.println("I don't get it yet"); - return new Breakable(ex.getMessage(), 0); - }) - .thenAccept(str -> - System.out.println("result: " + str)); + .test("exceptionally", failcount) + .exceptionally((ex) -> { // Function + if (ex == null) + System.out.println("I don't get it yet"); + return new Breakable(ex.getMessage(), 0); + }) + .thenAccept(str -> + System.out.println("result: " + str)); + // Create a new result (recover): CompletableExceptions - .test("handle", failcount) - .handle((result, fail) -> { // BiFunction - if(fail != null) - return "Failure recovery object"; - else - return result + " is good"; }) - .thenAccept(str -> - System.out.println("result: " + str)); - // Do something but pass the same result through: + .test("handle", failcount) + .handle((result, fail) -> { // BiFunction + if (fail != null) + return "Failure recovery object"; + else + return result + " is good"; + }) + .thenAccept(str -> + System.out.println("result: " + str)); + + // Do something but pass the same result through: CompletableExceptions - .test("whenComplete", failcount) - .whenComplete((result, fail) -> {// BiConsumer - if(fail != null) - System.out.println("It failed"); - else - System.out.println(result + " OK"); - }) - .thenAccept(r -> - System.out.println("result: " + r)); + .test("whenComplete", failcount) + .whenComplete((result, fail) -> { // BiConsumer + if (fail != null) + System.out.println("It failed"); + else + System.out.println(result + " OK"); + }) + .thenAccept(r -> + System.out.println("result: " + r)); } + public static void main(String[] args) { System.out.println("**** Failure Mode ****"); handleException(2); @@ -2084,26 +2189,35 @@ Breakable_whenComplete [-4] OK result: Breakable_whenComplete [-4] ``` -只有在有异常的情况下,**exclusively()**参数才会运行。**Exclusively()**的局限性在于,该函数只能返回输入的相同类型的值。**exclusively()**通过将一个好的对象重新插入流中而恢复到可行状态。 -**handle()**始终被调用,你必须检查一下**fail**是否为**true**才能查看是否发生了异常。但是**handle()**可以产生任何新类型,因此它使你可以执行处理,而不仅可以像**exception()**那样进行恢复。 -**whenComplete()**就像**handle()**一样,你必须测试是否失败,但是该参数是使用者,并且不会修改正在传递的结果对象。 +- `exceptionally()` 参数仅在出现异常时才运行。`exceptionally()` 局限性在于,该函数只能返回输入类型相同的值。 -### 流异常 +- `exceptionally()` 通过将一个好的对象插入到流中来恢复到一个可行的状态。 -通过修改**CompletableExceptions.java**,看看**CompletableFuture**异常与**Streams**异常有何不同: +- `handle()` 一致被调用来查看是否发生异常(必须检查fail是否为true)。 + + - 但是 `handle()` 可以生成任何新类型,所以它允许执行处理,而不是像使用 `exceptionally()`那样简单地恢复。 + + - `whenComplete()` 类似于handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。 + + +### 流异常(Stream Exception) + +通过修改**CompletableExceptions.java**,看看 **CompletableFuture**异常与流异常有何不同: ```java // concurrent/StreamExceptions.java import java.util.concurrent.*; import java.util.stream.*; public class StreamExceptions { + static Stream test(String id, int failcount) { - return Stream.of(new Breakable(id, failcount)). - map(Breakable::work) - .map(Breakable::work - .map(Breakable::work) - .map(Breakable::work); + return Stream.of(new Breakable(id, failcount)) + .map(Breakable::work) + .map(Breakable::work) + .map(Breakable::work) + .map(Breakable::work); } + public static void main(String[] args) { // No operations are even applied ... test("A", 1); @@ -2114,8 +2228,8 @@ public class StreamExceptions { // ... until there's a terminal operation: System.out.println("Entering try"); try { - c.forEach(System.out::println);// [1] - } catch(Exception e) { + c.forEach(System.out::println); // [1] + } catch (Exception e) { System.out.println(e.getMessage()); } } @@ -2132,111 +2246,135 @@ Throwing Exception for C Breakable_C failed ``` -使用**CompletableFutures**,我们看到了测试**A**到**E**的进展,但是使用**Streams**,直到你应用了终端操作(如[1]的**forEach()**),一切都没有开始。**CompletableFuture**执行工作并捕获任何异常以供以后检索。比较这两者并不是一件容易的事,因为**Stream**没有终端操作根本无法执行任何操作,但是**Stream**绝对不会存储其异常。 +使用 `CompletableFuture`,我们可以看到测试A到E的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client + +`CompletableFuture` 执行工作并捕获任何异常供以后检索。比较这两者并不容易,因为 `Stream` 在没有终端操作的情况下根本不做任何事情——但是流绝对不会存储它的异常。 -### 检查异常 +### 检查性异常 -CompletableFutures和并行Streams都不支持包含已检查异常的操作。相反,你必须在调用操作时处理检查到的异常,这会产生不太优雅的代码: +`CompletableFuture` 和 `parallel Stream` 都不支持包含检查性异常的操作。相反,你必须在调用操作时处理检查到的异常,这会产生不太优雅的代码: ```java // concurrent/ThrowsChecked.java import java.util.stream.*; import java.util.concurrent.*; + public class ThrowsChecked { class Checked extends Exception {} + static ThrowsChecked nochecked(ThrowsChecked tc) { return tc; } + static ThrowsChecked withchecked(ThrowsChecked tc) throws Checked { return tc; } + static void testStream() { Stream.of(new ThrowsChecked()) - .map(ThrowsChecked::nochecked) - // .map(ThrowsChecked::withchecked); // [1] - .map(tc -> { - try { - return withchecked(tc); - } catch(Checked e) { - throw new RuntimeException(e); - } - }); + .map(ThrowsChecked::nochecked) + // .map(ThrowsChecked::withchecked); // [1] + .map( + tc -> { + try { + return withchecked(tc); + } catch (Checked e) { + throw new RuntimeException(e); + } + }); } + static void testCompletableFuture() { - CompletableFuture .completedFuture(new ThrowsChecked()) - .thenApply(ThrowsChecked::nochecked) - // .thenApply(ThrowsChecked::withchecked); // [2] - .thenApply(tc -> { - try { - return withchecked(tc); - } catch(Checked e) { - throw new RuntimeException(e); - } - }); + CompletableFuture + .completedFuture(new ThrowsChecked()) + .thenApply(ThrowsChecked::nochecked) + // .thenApply(ThrowsChecked::withchecked); // [2] + .thenApply( + tc -> { + try { + return withchecked(tc); + } catch (Checked e) { + throw new RuntimeException(e); + } + }); } } ``` -如果你尝试像对 **nochecked()** 一样对 **withchecked()** 使用方法引用,则编译器会抱怨[1]和[2]。相反,你必须写出lambda表达式(或编写一个不会引发异常的包装器方法)。 - +如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出lambda表达式(或者编写一个不会抛出异常的包装器方法)。 + ## 死锁 -由于任务可能会被阻塞,因此一个任务有可能卡在等待另一个任务上,而任务又在等待另一个任务,依此类推,直到链回到第一个任务上。你会遇到一个不断循环的任务,彼此等待,没有人能动。这称为死锁[^6] -如果你尝试运行某个程序并立即陷入死锁,则可以立即查找该错误。真正的问题是,当你的程序看起来运行良好,但具有隐藏潜力死锁。在这里,你可能没有任何迹象表明可能发生死锁,因此该缺陷在你的程序中是潜在的,直到它意外发生为止(通常是对客户而言(几乎肯定很难复制))。因此,通过仔细的程序设计防止死锁是开发并发系统的关键部分。 -埃德斯·迪克斯特拉(Essger Dijkstra)发明的"哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数字)。这些哲学家将一部分时间花在思考上,一部分时间在吃饭上。他们在思考的时候并不需要任何共享资源,但是他们使用的餐具数量有限。在最初的问题描述中,器物是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子。显然,每个哲学家都需要两个筷子才能吃饭。 -引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更普遍地说,筷子的数量与哲学家相同)。它们之间围绕桌子隔开。当一个哲学家想要吃饭时,该哲学家必须拿起左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到必要的筷子可用为止。 -**StickHolder**类通过将单个筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: +由于任务可以被阻塞,因此一个任务有可能卡在等待另一个任务上,而后者又在等待别的任务,这样一直下去,知道这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间相互等待的连续循环, 没有哪个线程能继续, 这称之为死锁[^6] +如果你运行一个程序,而它马上就死锁了, 你可以立即跟踪下去。真正的问题在于,程序看起来工作良好, 但是具有潜在的死锁危险。这时, 死锁可能发生,而事先却没有任何征兆, 所以 `bug` 会潜伏在你的程序例,直到客户发现它出乎意料的发生(以一种几乎肯定是很难重现的方式发生)。因此在编写并发程序的时候,进行仔细的程序设计以防止死锁是关键部分。 +埃德斯·迪克斯特拉(`Essger Dijkstra`)发明的“哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数目)。这些哲学家将花部分时间思考,花部分时间就餐。他们在思考的时候并不需要任何共享资源;但是他们使用的餐具数量有限。在最初的问题描述中,餐具是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子, 显然,每个哲学家都需要两根筷子才能吃饭。 +引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更一般地讲,筷子的数量与哲学家相同)。他们围在桌子周围,每人之间放一根筷子。 当一个哲学家要就餐时,该哲学家必须同时持有左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到可得到必须的筷子。 + +**StickHolder** 类通过将单根筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: ```java // concurrent/StickHolder.java import java.util.concurrent.*; public class StickHolder { - private static class Chopstick {} + private static class Chopstick { + } + private Chopstick stick = new Chopstick(); - private BlockingQueue holder = new ArrayBlockingQueue<>(1); + private BlockingQueue holder = + new ArrayBlockingQueue<>(1); + public StickHolder() { putDown(); } + public void pickUp() { try { - holder.take();// Blocks if unavailable - } catch(InterruptedException e) { + holder.take(); // Blocks if unavailable + } catch (InterruptedException e) { throw new RuntimeException(e); } } + public void putDown() { try { holder.put(stick); - } catch(InterruptedException e) { + } catch (InterruptedException e) { throw new RuntimeException(e); } } } ``` -为简单起见,**StickHolder**从未真正制作过**Chopstick**,而是在类中将其保密。如果调用**pickUp()**而该筷子不可用,则**pickUp()**会阻塞,直到另一位调用**putDown()**的哲学家返回了该摇杆。请注意,此类中的所有线程安全性都是通过**BlockingQueue**实现的。 +为简单起见,`Chopstick`(`static`) 实际上不是由 `StickHolder` 生产的,而是在其类中保持私有的。 + +如果您调用了`pickUp()`,而 `stick` 不可用,那么`pickUp()`将阻塞该 `stick`,直到另一个哲学家调用`putDown()` 将 `stick` 返回。 + +注意,该类中的所有线程安全都是通过 `BlockingQueue` 实现的。 -每个哲学家都是一个任务,尝试将左右两把筷子都拿起,使其可以进食,然后使用**putDown()**释放这些筷子: +每个哲学家都是一项任务,他们试图把筷子分别 `pickUp()` 在左手和右手上,这样筷子才能吃东西,然后通过 `putDown()` 放下 `stick`。 ```java // concurrent/Philosopher.java public class Philosopher implements Runnable { private final int seat; private final StickHolder left, right; + public Philosopher(int seat, StickHolder left, StickHolder right) { this.seat = seat; this.left = left; this.right = right; } + @Override public String toString() { return "P" + seat; } + @Override public void run() { - while(true) { - // System.out.println("Thinking"); - // [1] right.pickUp(); + while (true) { + // System.out.println("Thinking"); // [1] + right.pickUp(); left.pickUp(); System.out.println(this + " eating"); right.putDown(); @@ -2247,7 +2385,8 @@ public class Philosopher implements Runnable { ``` 没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 -结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为结果语法更简洁: + +结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为这种语法更简洁: ```java // concurrent/DiningPhilosophers.java @@ -2256,62 +2395,73 @@ public class Philosopher implements Runnable { import java.util.*; import java.util.concurrent.*; import onjava.Nap; + public class DiningPhilosophers { private StickHolder[] sticks; private Philosopher[] philosophers; + public DiningPhilosophers(int n) { sticks = new StickHolder[n]; Arrays.setAll(sticks, i -> new StickHolder()); philosophers = new Philosopher[n]; - Arrays.setAll(philosophers, - i -> new Philosopher(i, sticks[i], sticks[(i + 1) % n]));// [1] + Arrays.setAll(philosophers, i -> + new Philosopher(i, + sticks[i], sticks[(i + 1) % n])); // [1] // Fix by reversing stick order for this one: - // philosophers[1] = // [2] - // new Philosopher(0, sticks[0], sticks[1]); + // philosophers[1] = // [2] + // new Philosopher(0, sticks[0], sticks[1]); Arrays.stream(philosophers) - .forEach(CompletableFuture::runAsync);// [3] + .forEach(CompletableFuture::runAsync); // [3] } + public static void main(String[] args) { // Returns right away: - new DiningPhilosophers(5);// [4] + new DiningPhilosophers(5); // [4] // Keeps main() from exiting: new Nap(3, "Shutdown"); } } ``` -当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心似乎不会产生死锁,但似乎有两个以上的核心很容易产生死锁。此行为使该示例更好地说明了死锁,因为你可能正在具有两个内核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,仅仅因为你不容易看到死锁,并不意味着该程序就不会在两核计算机上死锁。该程序仍然容易死锁,很少发生-可以说是最坏的情况,因为问题不容易解决。 -在DiningPhilosophers构造函数中,每个哲学家都获得一个左右StickHolder的引用。除最后一个哲学家外,每个哲学家都通过以下方式初始化: -哲学家之间的下一双筷子。最后一位哲学家右手的筷子为零,因此圆桌会议完成了。那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右摇杆,将最后一个哲学家缠绕在第一个哲学家的旁边。 -现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 -要开始在[3]上运行的每个Philosopher,我调用runAsync(),这意味着DiningPhilosophers构造函数立即在[4]处返回。没有任何东西可以阻止main()完成,该程序只是退出而无济于事。Nap对象阻止main()退出,然后在三秒钟后强制退出(可能是)死锁的程序。 -在给定的配置中,哲学家几乎没有时间思考。因此,他们都在尝试吃饭时争夺筷子,而且僵局往往很快发生。你可以更改此: +- 当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心不会产生死锁,但两核以上却很容易产生死锁。 +- 此行为使该示例更好地说明了死锁,因为你可能正在具有2核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在2核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。 +- 在 `DiningPhilosophers` 的构造方法中,每个哲学家都获得一个左右筷子的引用。除最后一个哲学家外,都是通过把哲学家放在下一双空闲筷子之间来初始化: + - 最后一位哲学家得到了第0根筷子作为他的右筷子,所以圆桌就完成。 + - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。 +- 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 + - 为了让每个哲学家在[3]上运行,调用 `runAsync()`,这意味着DiningPhilosophers的构造函数立即返回到[4]。 + - 如果没有任何东西阻止 `main()` 完成,程序就会退出,不会做太多事情。 + - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出(假设/可能是)死锁程序。 + - 在给定的配置中,哲学家几乎不花时间思考。因此,他们在吃东西的时候都争着用筷子,而且往往很快就会陷入僵局。你可以改变这个: 1. 通过增加[4]的值来添加更多哲学家。 + 2. 在Philosopher.java中取消注释行[1]。 -任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个例子很有趣,因为它演示了程序似乎可以正确运行,同时仍然容易出现死锁。 -为了解决该问题,我们观察到当四个同时满足条件: +任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个示例相当有趣,因为它演示了看起来可以正确运行,但实际上会可能发生死锁的程序。 -1. 互斥。任务使用的至少一种资源必须不可共享。在这里,筷子一次只能由一位哲学家使用。 -2. 至少一个任务必须拥有资源,并等待获取当前由另一任务拥有的资源。也就是说,要使僵局发生,哲学家必须握住一根筷子,等待另一根筷子。 -3. 不能抢先从任务中夺走资源。任务仅作为正常事件释放资源。我们的哲学家很有礼貌,他们不会抓住其他哲学家的筷子。 -4. 可能发生循环等待,即一个任务等待另一个任务持有的资源,而该任务又等待另一个任务持有的资源,依此类推,直到一个任务正在等待另一个任务持有的资源。第一项任务,从而使一切陷入僵局。在**DiningPhilosophers.java**中,发生循环等待是因为每个哲学家都先尝试获取右筷子,然后再获取左筷子。 +要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁: + +1) 互斥条件。任务使用的资源中至少有一个不能共享的。 这里,一根筷子一次就只能被一个哲学家使用。 +2) 至少有一个任务它必须持有一个资源且正在等待获取一个被当前别的任务持有的资源。也就是说,要发生死锁,哲学家必须拿着一根筷子并且等待另一根。 +3) 资源不能被任务抢占, 任务必须把资源释放当作普通事件。哲学家很有礼貌,他们不会从其它哲学家那里抢筷子。 +4) 必须有循环等待, 这时,一个任务等待其它任务所持有的资源, 后者又在等待另一个任务所持有的资源, 这样一直下去,知道有一个任务在等待第一个任务所持有的资源, 使得大家都被锁住。 在 `DiningPhilosophers.java` 中, 因为每个哲学家都试图先得到右边的 筷子, 然后得到左边的 筷子, 所以发生了循环等待。 + +因为必须满足所有条件才能导致死锁,所以要阻止死锁的话,只需要破坏其中一个即可。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 -因为必须满足所有这些条件才能导致死锁,所以你只能阻止其中一个解除死锁。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。 没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 - -## 构造函数非线程安全 +## 构造方法非线程安全 当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” + 不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 -设想下使用一个**静态**字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: +设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: ```java //concurrent/HasID.java @@ -2377,7 +2527,8 @@ public class IDChecker { ``` **MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 -使用 Guava 库中的 **Sets.**`intersection()` 方法,计算出这两个返回的 List\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。 + +使用 Guava 库中的 **Sets.`intersection()` 方法,计算出这两个返回的 List\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。 现在我们可以测试上面的 **StaticIDField** 类了。代码示例: @@ -2478,6 +2629,7 @@ public class SharedConstructorArgument{ ``` 在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 + 同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: ```java @@ -2554,14 +2706,13 @@ public class SynchronizedFactory{ ``` 通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 + 这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 - + ## 复杂性和代价 假设你正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分: - - ```java // concurrent/Pizza.java import java.util.function.*; @@ -2920,17 +3071,15 @@ Pizza4: complete 使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 - - ## 本章小结 -需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“您的程序运行速度还不够快”。 +需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“你的程序运行速度还不够快”。 -如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以您应该仔细考虑是否值得为此付出努力,并考虑您能否以其他方式提升速度。 +如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以你应该仔细考虑是否值得为此付出努力,并考虑你能否以其他方式提升速度。 例如,迁移到更快的硬件(这可能比消耗程序员的时间要便宜得多)或者将程序分解成多个部分,然后在不同的机器上运行这些部分。 -奥卡姆剃刀是一个经常被误解的原则。 我看过至少一部电影,他们将其定义为”最简单的解决方案是正确的解决方案“,就好像这是某种毋庸置疑的法律。实际上,这是一个准则:面对多种方法时,请先尝试需要最少假设的方法。 在编程世界中,这已演变为“尝试可能可行的最简单的方法”。当您了解了特定工具的知识时——就像你现在了解了有关并发性的知识一样,你可能会很想使用它,或者提前规定你的解决方案必须能够“速度飞快”,从而来证明从一开始就进行并发设计是合理的。但是,我们的奥卡姆剃刀编程版本表示您应该首先尝试最简单的方法(这种方法开发起来也更便宜),然后看看它是否足够好。 +奥卡姆剃刀是一个经常被误解的原则。 我看过至少一部电影,他们将其定义为”最简单的解决方案是正确的解决方案“,就好像这是某种毋庸置疑的法律。实际上,这是一个准则:面对多种方法时,请先尝试需要最少假设的方法。 在编程世界中,这已演变为“尝试可能可行的最简单的方法”。当你了解了特定工具的知识时——就像你现在了解了有关并发性的知识一样,你可能会很想使用它,或者提前规定你的解决方案必须能够“速度飞快”,从而来证明从一开始就进行并发设计是合理的。但是,我们的奥卡姆剃刀编程版本表示你应该首先尝试最简单的方法(这种方法开发起来也更便宜),然后看看它是否足够好。 由于我出身于底层学术背景(物理学和计算机工程),所以我很容易想到所有小轮子转动的成本。我确定使用最简单的方法不够快的场景出现的次数已经数不过来了,但是尝试后却发现它实际上绰绰有余。 @@ -2946,54 +3095,48 @@ Pizza4: complete 4. 诸如饥饿,竞速,死锁和活锁(多线程各自处理单个任务而整体却无法完成)之类的问题。 -5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果您在后者上开发程序,则在分发程序时可能会感到非常惊讶。 +5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果你在后者上开发程序,则在分发程序时可能会感到非常惊讶。 - - -另外,并发的应用是一门艺术。 Java旨在允许您创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,您只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使您使用完全不同的方案。 +另外,并发的应用是一门艺术。 Java旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。 ### 共享内存陷阱 -并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且您必须确保多个任务不会同时读取和更改该资源。 - -我花了多年的时间研究并发并发。 我了解到您永远无法相信使用共享内存并发的程序可以正常工作。 您可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] +并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且你必须确保多个任务不会同时读取和更改该资源。 -我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上您可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 - -再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给您一种一切都棒极了的印象。当涉及到共享内存并发编程时,您永远不应该对自己的编程能力变得过于自信。 +我花了多年的时间研究并发并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] +我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 +再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给你一种一切都棒极了的印象。当涉及到共享内存并发编程时,你永远不应该对自己的编程能力变得过于自信。 ### This Albatross is Big -如果您对Java并发感到不知所措,那说明您身处在一家出色的公司里。您 可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 +如果你对Java并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 -事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当您使用旧代码时,仍然会遇到旧的解决方案。 +事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。 -在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,您很容易迷失于旧的设计中。 +在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。 ### 其他类库 -本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免您不知所措,我没有介绍您可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是您还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 +本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是你还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 ### 考虑为并发设计的语言 -通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设您不尝试共享内存)使您摆脱麻烦(在Java的世界范围内)。 +通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在Java的世界范围内)。 -如果您的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使您可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与您的主要Java代码进行交互。 或者,您可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11] +如果你的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与你的主要Java代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11] -你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许您通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许您在Scala中完成代码)。 +你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许你在Scala中完成代码)。 心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 -无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在您最不期望出现的时候咬您。 +无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在你最不期望出现的时候咬你。 ### 拓展阅读 《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 - - [^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 [^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 @@ -3007,4 +3150,5 @@ Pizza4: complete [^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。 -
+ +
\ No newline at end of file From d426b5557485c66c45744b28cb35db53ac647124 Mon Sep 17 00:00:00 2001 From: unclesesame Date: Sat, 11 Apr 2020 11:34:43 +0800 Subject: [PATCH 214/371] =?UTF-8?q?=E7=AC=AC=E5=8D=81=E4=B8=80=E5=92=8C?= =?UTF-8?q?=E5=8D=81=E4=BA=8C=E7=AB=A0=E7=9A=84=E8=AF=AD=E6=B3=95=E9=94=99?= =?UTF-8?q?=E8=AF=AF=20(#428)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 12-Collections.md 删除多余的 “的” * Update 11-Inner-Classes.md “获联”改为“获取” * Update 12-Collections.md --- docs/book/11-Inner-Classes.md | 2 +- docs/book/12-Collections.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index f7862a5c..ca4abfc2 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -957,7 +957,7 @@ public abstract class Event { `ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中覆盖 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。 -下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\<**Event**\> 类型(读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数,foreach 语法用来连续获联 **List** 中的 **Event**,`remove()` 方法用来从 **List** 中移除指定的 **Event**。 +下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\<**Event**\> 类型(读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数,foreach 语法用来连续获取 **List** 中的 **Event**,`remove()` 方法用来从 **List** 中移除指定的 **Event**。 ```java // innerclasses/controller/Controller.java diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index bc0569b8..8c9c88a5 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -1276,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 From 19760c32c1568ec88b9b17c18be94d91cf171e08 Mon Sep 17 00:00:00 2001 From: FengBaoheng <344092466@qq.com> Date: Mon, 13 Apr 2020 10:18:58 +0800 Subject: [PATCH 215/371] fix typos on 20-Generics (#430) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix typo 本章小节->本章小结 * fix typos on 20-Generics 修正20章的打字错误 --- docs/book/20-Generics.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 4a0a0add..e207114d 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -1292,6 +1292,7 @@ public class LostInformation { [K,V] [Q] [POSITION,MOMENTUM] +*/ ``` 根据 JDK 文档,**Class.getTypeParameters()** “返回一个 **TypeVariable** 对象数组,表示泛型声明中声明的类型参数...” 这暗示你可以发现这些参数类型。但是正如上例中输出所示,你只能看到用作参数占位符的标识符,这并非有用的信息。 @@ -1616,6 +1617,7 @@ public class FilledList extends ArrayList { /* Output: [Hello,Hello,Hello,Hello] [47,47,47,47] +*/ ``` 即使编译器无法得知 `add()` 中的 **T** 的任何信息,但它仍可以在编译期确保你放入 **FilledList** 中的对象是 **T** 类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以确保方法或类中使用的类型的内部一致性。 @@ -2636,7 +2638,7 @@ public class NonCovariantGenerics { } ``` -尽管你在首次阅读这段代码时会认为“不能将一个 **Apple** 集合赋值给一个 **Fruit** 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 **Apple** 的泛型赋值给一个涉及 **Fruit** 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 将持有 **Apple** 和 **Apple** 的子类型,**Fruit** 的 **List** 将持有任何类型的 **Fruit**。是的,这包括 **Apple**,但是它不是一个 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在类型上不等价于 **Fruit** 的 **List**,即使 **Apple** 是一种 **Fruit** 类型。 +尽管你在首次阅读这段代码时会认为“不能将一个 **Apple** 集合赋值给一个 **Fruit** 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 **Apple** 的泛型赋值给一个涉及 **Fruit** 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 将持有 **Apple** 和 **Apple** 的子类型,**Fruit** 的 **List** 将持有任何类型的 **Fruit**。是的,这包括 **Apple**,但是它不是一个 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在类型上不等价于 **Fruit** 的 **List**,即使 **Apple** 是一种 **Fruit** 类型。 真正的问题是我们在讨论的集合类型,而不是集合持有对象的类型。与数组不同,泛型没有内建的协变类型。这是因为数组是完全在语言中定义的,因此可以具有编译期和运行时的内建检查,但是在使用泛型时,编译器和运行时系统不知道你想用类型做什么,以及应该采用什么规则。 @@ -2736,7 +2738,7 @@ public class Holder { Holder apple = new Holder<>(new Apple()); Apple d = apple.get(); apple.set(d); -// Holder fruit = apple; // Cannot upcast + // Holder fruit = apple; // Cannot upcast Holder fruit = apple; // OK Fruit p = fruit.get(); d = (Apple) fruit.get(); @@ -2745,8 +2747,8 @@ public class Holder { } catch (Exception e) { System.out.println(e); } -// fruit.set(new Apple()); // Cannot call set() -// fruit.set(new Fruit()); // Cannot call set() + // fruit.set(new Apple()); // Cannot call set() + // fruit.set(new Fruit()); // Cannot call set() System.out.println(fruit.equals(d)); // OK } } @@ -3325,7 +3327,7 @@ Double ``` `f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中调用了 `f1()`,而 `f1()` 需要一个已知参数。这里所发生的是:在调用 `f2()` 的过程中捕获了参数类型,并在调用 `f1()` 时使用了这种类型。 -你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T ** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 +你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。 From 59ec312823618a2c2662c8c69870405c88a53b5c Mon Sep 17 00:00:00 2001 From: FengBaoheng <344092466@qq.com> Date: Tue, 14 Apr 2020 11:50:31 +0800 Subject: [PATCH 216/371] add examples link (#432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix typo 本章小节->本章小结 * fix typos on 20-Generics 修正20章的打字错误 * 添加示例代码链接 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 7d5c6774..187c1a55 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,13 @@ * 页数:2038 * 发行:仅电子版 + +## 示例代码 + +* [gradle: OnJava8-Examples](https://github.com/BruceEckel/OnJava8-Examples) +* [maven: OnJava8-Examples-Maven](https://github.com/sjsdfg/OnJava8-Examples-Maven) + + ## 贡献者 * 主译:[LingCoder](https://github.com/LingCoder),[sjsdfg](https://github.com/sjsdfg),[xiangflight](https://github.com/xiangflight) From 474fd6093c0bc5fcdcb82d3beef8b7d036ff1b44 Mon Sep 17 00:00:00 2001 From: springga Date: Thu, 16 Apr 2020 23:53:34 +0800 Subject: [PATCH 217/371] Update 01-What-is-an-Object.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit logical assumption应为“合理假设”而不是“逻辑假设” tend to be应为“通常”“往往”而不是“趋向于” 直译改为意译 --- docs/book/01-What-is-an-Object.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md index 5b0d6ca5..3c9ba058 100644 --- a/docs/book/01-What-is-an-Object.md +++ b/docs/book/01-What-is-an-Object.md @@ -266,7 +266,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共 第二种方法是在堆内存(Heap)中动态地创建对象。在这种方式下,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。 -动态方法有这样一个一般性的逻辑假设:对象趋向于变得复杂,因此额外的内存查找和释放对对象的创建影响不大。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。 +动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。 Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字构建该对象的动态实例。这又带来另一个问题:对象的生命周期。较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;然而如果你在堆内存创建对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。 @@ -291,4 +291,4 @@ Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就 OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。 -
\ No newline at end of file +
From ce26e6efaf2545467a481fb6c24dc5624eb63350 Mon Sep 17 00:00:00 2001 From: iwangbingzhi Date: Sun, 19 Apr 2020 16:07:44 +0800 Subject: [PATCH 218/371] update (#435) --- docs/book/Appendix-New-IO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/Appendix-New-IO.md b/docs/book/Appendix-New-IO.md index 04688e68..94e47660 100644 --- a/docs/book/Appendix-New-IO.md +++ b/docs/book/Appendix-New-IO.md @@ -133,7 +133,7 @@ public class ChannelCopy { ``` -**FileChannel** 用于读取;**FileChannel** 用于写入。当 **ByteBuffer** 分配好存储,调用 **FileChannel** 的 `read()` 方法返回 **-1**(毫无疑问,这是来源于 Unix 和 C 语言)时,说明输入流读取完了。在每次 `read()` 将数据放入缓冲区之后,`flip()` 都会准备好缓冲区,以便 `write()` 提取它的信息。在 `write()` 之后,数据仍然在缓冲区中,我们需要 `clear()` 来重置所有内部指针,以便在下一次 `read()` 中接受数据。 +**第一个FileChannel** 用于读取;**第二个FileChannel** 用于写入。当 **ByteBuffer** 分配好存储,调用 **FileChannel** 的 `read()` 方法返回 **-1**(毫无疑问,这是来源于 Unix 和 C 语言)时,说明输入流读取完了。在每次 `read()` 将数据放入缓冲区之后,`flip()` 都会准备好缓冲区,以便 `write()` 提取它的信息。在 `write()` 之后,数据仍然在缓冲区中,我们需要 `clear()` 来重置所有内部指针,以便在下一次 `read()` 中接受数据。 但是,上例并不是处理这种操作的理想方法。方法 `transferTo()` 和 `transferFrom()` 允许你直接连接此通道到彼通道: From 86ebfd142abcac15068c8db1144faf3e82ce4b13 Mon Sep 17 00:00:00 2001 From: unclesesame Date: Mon, 20 Apr 2020 12:47:29 +0800 Subject: [PATCH 219/371] =?UTF-8?q?=E7=AC=AC15=E7=AB=A0=20=E5=BC=82?= =?UTF-8?q?=E5=B8=B8:=20=E9=94=99=E5=88=AB=E5=AD=97=E6=9B=B4=E6=94=B9=20(#?= =?UTF-8?q?436)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit update typos --- docs/book/15-Exceptions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 5ee290bb..f114f4e8 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -914,7 +914,7 @@ DynamicFields.setField(DynamicFields.java:67) 至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField0 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。 -你会注意到,toString0 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 +你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,并结合“或(|)”符号。此 Java 7 功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。 @@ -1100,7 +1100,7 @@ on off ``` -程序的目的是要确保 main() 结束的时候开关必须是关闭的,所以在每个 try 块和异常处理程序的末尾都加入了对 sw.offo 方法的调用。但也可能有这种情况:异常被抛出,但没被处理程序捕获,这时 sw.off() 就得不到调用。但是有了 finally,只要把 try 块中的清理代码移放在一处即可: +程序的目的是要确保 main() 结束的时候开关必须是关闭的,所以在每个 try 块和异常处理程序的末尾都加入了对 sw.off() 方法的调用。但也可能有这种情况:异常被抛出,但没被处理程序捕获,这时 sw.off() 就得不到调用。但是有了 finally,只要把 try 块中的清理代码移放在一处即可: ```java // exceptions/WithFinally.java @@ -1272,7 +1272,7 @@ public class LostMessage { A trivial exception ``` -从输出中可以看到,VeryImportantException 不见了,它被 finally 子句里的 HoHumException 所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察党的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上例中的 dispose() 方法,全部打包放到 try-catch 子句里面)。 +从输出中可以看到,VeryImportantException 不见了,它被 finally 子句里的 HoHumException 所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察觉的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上例中的 dispose() 方法,全部打包放到 try-catch 子句里面)。 一种更加简单的丢失异常的方式是从 finally 子句中返回: @@ -1953,7 +1953,7 @@ try { ## 其他可选方式 异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形” -发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽格。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。 +发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。 异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。 @@ -2056,7 +2056,7 @@ public class MainException { 在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的,但这不是通用的方法。 -问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或若打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样: +问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或者打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样: ```java try { @@ -2163,7 +2163,7 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型 异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。 -异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过语如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。 +异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。 就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。 From 28473b2ecd8b8aeaa7a5d83052642ba5727728ca Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Tue, 21 Apr 2020 13:23:23 +0800 Subject: [PATCH 220/371] =?UTF-8?q?fix=20=E7=AC=AC=E4=B8=80=E7=AB=A0?= =?UTF-8?q?=E6=8B=AC=E5=8F=B7=E4=BD=BF=E7=94=A8=E7=9A=84=E9=94=99=E8=AF=AF?= =?UTF-8?q?=20(#437)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/01-What-is-an-Object.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md index 3c9ba058..0f3fe968 100644 --- a/docs/book/01-What-is-an-Object.md +++ b/docs/book/01-What-is-an-Object.md @@ -74,7 +74,7 @@ lt.on(); 2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造。 -Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。 +Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。 1. `public`(公开)表示任何人都可以访问和使用该元素; @@ -116,7 +116,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开 ![1545764724202](../images/1545764724202.png) -例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。 +例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。 ![1545764780795](../images/1545764780795.png) @@ -232,7 +232,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共 还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。 -在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联);Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因: +在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因: 1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。 @@ -256,7 +256,7 @@ Java 的单继承结构有很多好处。由于所有对象都具有一个公共 在简单的编程场景下,对象的清理并不是问题。我们创建对象,按需使用,最后销毁它。然而,情况往往要比这更复杂: -假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。 +假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。 现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。 From 4bff99a74dee3c4d1dfb19d937de85f631b41b59 Mon Sep 17 00:00:00 2001 From: taolei <809210721@qq.com> Date: Wed, 22 Apr 2020 14:08:02 +0800 Subject: [PATCH 221/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=AB=A0=E8=8A=82=E6=A0=87=E9=A2=98=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正第五章控制流 第一子节 'true和false'标题中错误拼写 close #439 --- SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SUMMARY.md b/SUMMARY.md index eea7c550..a15b48fc 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -72,7 +72,7 @@ * [运算符总结](docs/book/04-Operators.md#运算符总结) * [本章小结](docs/book/04-Operators.md#本章小结) * [第五章 控制流](docs/book/05-Control-Flow.md) - * [true和flase](docs/book/05-Control-Flow.md#true和flase) + * [true和false](docs/book/05-Control-Flow.md#true和false) * [if-else](docs/book/05-Control-Flow.md#if-else) * [迭代语句](docs/book/05-Control-Flow.md#迭代语句) * [for-in语法](docs/book/05-Control-Flow.md#for-in语法) From 94f4e80b211c6b455fc0a9924abb937fb0e100d3 Mon Sep 17 00:00:00 2001 From: JasonFCN <34146521+JasonFCN@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:24:20 +0800 Subject: [PATCH 222/371] Update 22-Enumerations.md (#442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 22-Enumerations.md 906行:“存人” 改为 “存入”。 * Update 22-Enumerations.md mian0 --> main() --- docs/book/22-Enumerations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index 0259ec56..d4076f05 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -903,7 +903,7 @@ Expected: java.lang.NullPointerException 与 EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。 -main0 方法的最后部分说明,enum 的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用 put() 方法来存人相应的值的话,其对应的值就是 null。 +main() 方法的最后部分说明,enum 的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用 put() 方法来存入相应的值的话,其对应的值就是 null。 与常量相关的方法(constant-specific methods 将在下一节中介绍)相比,EnumMap 有一个优点,那 EnumMap 允许程序员改变值对象,而常量相关的方法在编译期就被固定了。稍后你会看到,在你有多种类型的 enum,而且它们之间存在互操作的情况下,我们可以用 EnumMap 实现多路分发(multiple dispatching)。 @@ -2108,4 +2108,4 @@ table 与前一个例子中 initRow() 方法的调用次序完全相同。 -
\ No newline at end of file +
From 5d7462a629a410cb29f98938387daa0d846d8e4b Mon Sep 17 00:00:00 2001 From: unclesesame Date: Sun, 26 Apr 2020 17:37:46 +0800 Subject: [PATCH 223/371] =?UTF-8?q?16-19=E7=AB=A0=20=E7=BA=A0=E9=94=99=20(?= =?UTF-8?q?#443)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 第15章 异常: 错别字更改 update typos * 第19章 类型信息 纠错 第19章 类型信息 纠错 * 第17章 文件 纠错 第17章 文件 纠错 * 第16章 代码校验 纠错 第16章 代码校验 纠错 --- docs/book/16-Validating-Your-Code.md | 6 +++--- docs/book/17-Files.md | 10 +++++----- docs/book/19-Type-Information.md | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 46306059..527eef2e 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -180,7 +180,7 @@ Cleaning up 4 **gradlew test** -尽管可以用最简单的方法,如 **CountedListTest.java** 所示没那样,JUnit 还包括大量的测试结构,你可以到[官网](junit.org)上学习它们。 +尽管可以用最简单的方法,如 **CountedListTest.java** 所示那样,JUnit 还包括大量的测试结构,你可以到[官网](junit.org)上学习它们。 JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。 @@ -1240,7 +1240,7 @@ public class SLF4JLogging { **Aug 16, 2016 5:40:31 PM InfoLogging main** **INFO: hello logging** -日志系统会检测日志消息处所在的的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。 +日志系统会检测日志消息处所在的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。 ### 日志等级 @@ -1654,7 +1654,7 @@ N:数组的大小:**10^(2*k)**,通常来说,**k=1..7** 足够来练习 Q:setter 的操作成本 -这个 C/P/N/Q 模型在早期 JDK 8 的 Lambda 开发期间付出水面,大多数并行的 Stream 操作(**parallelSetAll()** 也基本相似)都满足这些结论:**N*Q**(主要工作量)对于并发性能尤为重要。并行算法在工作量较少时可能实际运行得更慢。 +这个 C/P/N/Q 模型在早期 JDK 8 的 Lambda 开发期间浮出水面,大多数并行的 Stream 操作(**parallelSetAll()** 也基本相似)都满足这些结论:**N*Q**(主要工作量)对于并发性能尤为重要。并行算法在工作量较少时可能实际运行得更慢。 在一些情况下操作竞争如此激烈使得并行毫无帮助,而不管 **N*Q** 有多大。当 **C** 很大时,**P** 就变得不太相关(内部并行在大量的外部并行面前显得多余)。此外,在一些情况下,并行分解会让相同的 **C** 个客户端运行得比它们顺序运行代码更慢。 diff --git a/docs/book/17-Files.md b/docs/book/17-Files.md index c92cb291..7a22b051 100644 --- a/docs/book/17-Files.md +++ b/docs/book/17-Files.md @@ -5,7 +5,7 @@ # 第十七章 文件 >在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。 -这种"困难方式"的全部细节都在 [Appendix: I/O Streams](./Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来是非常常用的,由于 I/O 糟糕的设计以至于 +这种"困难方式"的全部细节都在 [Appendix: I/O Streams](./Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于 很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。 好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 **java.nio.file** 包下面,过去人们通常把 **nio** 中的 **n** 理解为 **new** 即新的 **io**,现在更应该当成是 **non-blocking** 非阻塞 **io**(**io**就是*input/output输入/输出*)。**java.nio.file** 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件: @@ -124,7 +124,7 @@ true 我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10 上的程序输出。 当 **toString()** 方法生成完整形式的路径,你可以看到 **getFileName()** 方法总是返回当前文件名。 -通过使用 **Files** 工具类(我们接下类将会更多地使用它),可以测试一个文件是否存在,测试是否是一个"普通"文件还是一个目录等等。"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。"PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在"。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的 **Path**,并且还会删除任何冗余元素。 +通过使用 **Files** 工具类(我们接下来将会更多地使用它),可以测试一个文件是否存在,测试是否是一个"普通"文件还是一个目录等等。"Nofile.txt"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。"PathInfo.java"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为"存在"。一旦我们将其转换为绝对路径,我们将会得到一个从"C:"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的 **Path**,并且还会删除任何冗余元素。 这里你会看到 **URI** 看起来只能用于描述文件,实际上 **URI** 可以用于描述更多的东西;通过 [维基百科](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) 可以了解更多细节。现在我们成功地将 **URI** 转为一个 **Path** 对象。 @@ -465,7 +465,7 @@ test\Hello.txt 我们尝试使用 **createDirectory()** 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路径。我已经将 **populateTestDir()** 作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量 **variant**,我们都能使用 **createDirectories()** 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用 **createTempFile()** 生成一个临时文件。 -在调用 **populateTestDir()** 之后,我们在 **test** 目录下面下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与 **createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用".tmp"作为后缀。 +在调用 **populateTestDir()** 之后,我们在 **test** 目录下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与 **createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用".tmp"作为后缀。 为了展示结果,我们首次使用看起来很有希望的 **newDirectoryStream()**,但事实证明这个方法只是返回 **test** 目录内容的 Stream 流,并没有更多的内容。要获取目录树的全部内容的流,请使用 **Files.walk()**。 @@ -589,7 +589,7 @@ evt.kind(): ENTRY_DELETE 此时,**watcher.take()** 将等待并阻塞在这里。当目标事件发生时,会返回一个包含 **WatchEvent** 的 **Watchkey** 对象。展示的这三种方法是能对 **WatchEvent** 执行的全部操作。 -查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在 **Hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说"监视这个目录",自然会包含整个目录和下面子目录,但实际上的:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 **Watchservice**。 +查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在 **Hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说"监视这个目录",自然会包含整个目录和下面子目录,但实际上:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 **Watchservice**。 ```java // files/TreeWatcher.java @@ -851,4 +851,4 @@ Java 7 和 8 对于处理文件和目录的类库做了大量改进。如果您 -
\ No newline at end of file +
diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index 3c70fe47..f013b51a 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -785,11 +785,11 @@ public class ForNameCreator extends PetCreator { } ``` -`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException` 异常,因为你传入的是一个 `String` 类型的参数,它不能再编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。 +`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException` 异常,因为你传入的是一个 `String` 类型的参数,它不能在编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。 为了使得 `List` 装入的是具体的 `Class` 对象,类型转换是必须的,它会产生一个编译时警告。`loader()` 方法是分开编写的,然后它被放入到一个静态代码块里,因为 `@SuppressWarning` 注解不能够直接放置在静态代码块之上。 -为了对 `Pet` 进行计数,我们需要一个能跟踪不同类型的 `Pet` 的工具。`Map` 的是这个需求的首选,我们将 `Pet` 类型名作为键,将保存 `Pet` 数量的 `Integer` 作为值。通过这种方式,你就看可以询问:“有多少个 `Hamster` 对象?”我们可以使用 `instanceof` 来对 `Pet` 进行计数: +为了对 `Pet` 进行计数,我们需要一个能跟踪不同类型的 `Pet` 的工具。`Map` 是这个需求的首选,我们将 `Pet` 类型名作为键,将保存 `Pet` 数量的 `Integer` 作为值。通过这种方式,你就可以询问:“有多少个 `Hamster` 对象?”我们可以使用 `instanceof` 来对 `Pet` 进行计数: ```java // typeinfo/PetCount.java @@ -1052,7 +1052,7 @@ EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20} ### 递归计数 -`PetCount3.Counter` 中的 `Map` 预先加载了所有不同的 `Pet` 类。我们可以使用 `Class.isAssignableFrom()` 而不是预加载地图,并创建一个不限于计数 `Pet` 的通用工具: +`PetCount3.Counter` 中的 `Map` 预先加载了所有不同的 `Pet` 类。我们可以使用 `Class.isAssignableFrom()` 而不是预加载 `Map` ,并创建一个不限于计数 `Pet` 的通用工具: ```java // onjava/TypeCounter.java @@ -1336,7 +1336,7 @@ x.getClass().equals(Derived.class)) true 如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。 -起初,这看起来并没有那么大的限制,但是假设你引用了一个对不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类? +起初,这看起来并没有那么大的限制,但是假设你引用了一个不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类? 在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在编程时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆写这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。 @@ -1826,7 +1826,7 @@ caught EmptyTitleException `EmptyTitleException` 是一个 `RuntimeException`,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 `setTitle()` 传 `null` 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 `EmptyTitleException` 还有助于定位 BUG。 -`Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Option`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。 +`Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Optional`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。 在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段的 `Person` 对象为空,就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。 From 5c3e99026911674a806bc2f28aa2755e561fcca7 Mon Sep 17 00:00:00 2001 From: rocLv Date: Wed, 29 Apr 2020 21:03:24 +0800 Subject: [PATCH 224/371] Add jupyter format files (#445) * add jupyter version * update readme --- README.md | 23 + .../01-What-is-an-Object-checkpoint.ipynb | 408 + .../04-Operators-checkpoint.ipynb | 2214 +++++ jupyter/00-Introduction.ipynb | 111 + jupyter/00-On-Java-8.ipynb | 76 + jupyter/00-Preface.ipynb | 112 + jupyter/01-What-is-an-Object.ipynb | 422 + ...nstalling-Java-and-the-Book-Examples.ipynb | 305 + jupyter/03-Objects-Everywhere.ipynb | 1386 +++ jupyter/04-Operators.ipynb | 2247 +++++ jupyter/05-Control-Flow.ipynb | 1436 ++++ jupyter/06-Housekeeping.ipynb | 3097 +++++++ jupyter/07-Implementation-Hiding.ipynb | 1325 +++ jupyter/08-Reuse.ipynb | 1661 ++++ jupyter/09-Polymorphism.ipynb | 1809 ++++ jupyter/10-Interfaces.ipynb | 2640 ++++++ jupyter/11-Inner-Classes.ipynb | 2189 +++++ jupyter/12-Collections.ipynb | 2589 ++++++ jupyter/13-Functional-Programming.ipynb | 2395 ++++++ jupyter/14-Streams.ipynb | 3492 ++++++++ jupyter/15-Exceptions.ipynb | 3518 ++++++++ jupyter/16-Validating-Your-Code.ipynb | 2480 ++++++ jupyter/17-Files.ipynb | 1123 +++ jupyter/18-Strings.ipynb | 2481 ++++++ jupyter/19-Type-Information.ipynb | 3701 ++++++++ jupyter/20-Generics.ipynb | 7433 +++++++++++++++++ jupyter/21-Arrays.ipynb | 3498 ++++++++ jupyter/22-Enumerations.ipynb | 3076 +++++++ jupyter/23-Annotations.ipynb | 2737 ++++++ jupyter/24-Concurrent-Programming.ipynb | 4536 ++++++++++ jupyter/25-Patterns.ipynb | 1674 ++++ jupyter/Appendix-Becoming-a-Programmer.ipynb | 132 + ...ts-and-Costs-of-Static-Type-Checking.ipynb | 42 + jupyter/Appendix-Collection-Topics.ipynb | 3861 +++++++++ jupyter/Appendix-Data-Compression.ipynb | 417 + jupyter/Appendix-IO-Streams.ipynb | 716 ++ jupyter/Appendix-Javadoc.ipynb | 400 + jupyter/Appendix-Low-Level-Concurrency.ipynb | 2446 ++++++ jupyter/Appendix-New-IO.ipynb | 1446 ++++ jupyter/Appendix-Object-Serialization.ipynb | 1374 +++ ...pendix-Passing-and-Returning-Objects.ipynb | 88 + jupyter/Appendix-Programming-Guidelines.ipynb | 187 + jupyter/Appendix-Standard-IO.ipynb | 335 + jupyter/Appendix-Supplements.ipynb | 39 + ...itive-Legacy-of-C-plus-plus-and-Java.ipynb | 44 + ...ix-Understanding-equals-and-hashCode.ipynb | 1340 +++ jupyter/GLOSSARY.ipynb | 23 + 47 files changed, 79084 insertions(+) create mode 100644 jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb create mode 100644 jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb create mode 100644 jupyter/00-Introduction.ipynb create mode 100644 jupyter/00-On-Java-8.ipynb create mode 100644 jupyter/00-Preface.ipynb create mode 100644 jupyter/01-What-is-an-Object.ipynb create mode 100644 jupyter/02-Installing-Java-and-the-Book-Examples.ipynb create mode 100644 jupyter/03-Objects-Everywhere.ipynb create mode 100644 jupyter/04-Operators.ipynb create mode 100644 jupyter/05-Control-Flow.ipynb create mode 100644 jupyter/06-Housekeeping.ipynb create mode 100644 jupyter/07-Implementation-Hiding.ipynb create mode 100644 jupyter/08-Reuse.ipynb create mode 100644 jupyter/09-Polymorphism.ipynb create mode 100644 jupyter/10-Interfaces.ipynb create mode 100644 jupyter/11-Inner-Classes.ipynb create mode 100644 jupyter/12-Collections.ipynb create mode 100644 jupyter/13-Functional-Programming.ipynb create mode 100644 jupyter/14-Streams.ipynb create mode 100644 jupyter/15-Exceptions.ipynb create mode 100644 jupyter/16-Validating-Your-Code.ipynb create mode 100644 jupyter/17-Files.ipynb create mode 100644 jupyter/18-Strings.ipynb create mode 100644 jupyter/19-Type-Information.ipynb create mode 100644 jupyter/20-Generics.ipynb create mode 100644 jupyter/21-Arrays.ipynb create mode 100644 jupyter/22-Enumerations.ipynb create mode 100644 jupyter/23-Annotations.ipynb create mode 100644 jupyter/24-Concurrent-Programming.ipynb create mode 100644 jupyter/25-Patterns.ipynb create mode 100644 jupyter/Appendix-Becoming-a-Programmer.ipynb create mode 100644 jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb create mode 100644 jupyter/Appendix-Collection-Topics.ipynb create mode 100644 jupyter/Appendix-Data-Compression.ipynb create mode 100644 jupyter/Appendix-IO-Streams.ipynb create mode 100644 jupyter/Appendix-Javadoc.ipynb create mode 100644 jupyter/Appendix-Low-Level-Concurrency.ipynb create mode 100644 jupyter/Appendix-New-IO.ipynb create mode 100644 jupyter/Appendix-Object-Serialization.ipynb create mode 100644 jupyter/Appendix-Passing-and-Returning-Objects.ipynb create mode 100644 jupyter/Appendix-Programming-Guidelines.ipynb create mode 100644 jupyter/Appendix-Standard-IO.ipynb create mode 100644 jupyter/Appendix-Supplements.ipynb create mode 100644 jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb create mode 100644 jupyter/Appendix-Understanding-equals-and-hashCode.ipynb create mode 100644 jupyter/GLOSSARY.ipynb diff --git a/README.md b/README.md index 187c1a55..750efa20 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,29 @@ - [x] [附录:C++和Java的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) - [ ] [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) +## INSTALL + +1. 首先安装[Jupyter Lab](https://jupyter.org/) +2. 安装[Java Kernel](https://github.com/SpencerPark/IJava) + +注意: 打开文件后,在工具栏最右边选择`Java`。 Mac下按`CMD + Enter`可以运行Code。 + + Java SDK需要1.9及以上。可以用[sdkman](sdkman.io)安装. + +3. 代码运行。 + ```java + public class Hello { + public static void main(String [] args){ + System.out.println("Hello, world!") + } + + } + + //调用静态方法main + Hello.main(new String [0]); + + + ``` ## 一起交流 diff --git a/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb b/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb new file mode 100644 index 00000000..897dcd4d --- /dev/null +++ b/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb @@ -0,0 +1,408 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 第一章 对象的概念\n", + "\n", + "> “我们没有意识到惯用语言的结构有多大的力量。可以毫不夸张地说,它通过语义反应机制奴役我们。语言表现出来并在无意识中给我们留下深刻印象的结构会自动投射到我们周围的世界。” -- Alfred Korzybski (1930)\n", + "\n", + "计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。\n", + "\n", + "面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构。本章讲述 OOP 的基本概述。如果读者对此不太理解,可先行跳过本章。等你具备一定编程基础后,请务必再回头看。只有这样你才能深刻理解面向对象编程的重要性及设计方式。\n", + "\n", + "## 抽象\n", + "\n", + "所有编程语言都提供抽象机制。从某种程度上来说,问题的复杂度直接取决于抽象的类型和质量。这里的“类型”意思是:抽象的内容是什么?汇编语言是对底层机器的轻微抽象。接着出现的“命令式”语言(如 FORTRAN,BASIC 和 C)是对汇编语言的抽象。与汇编相比,这类语言已有了长足的改进,但它们的抽象原理依然要求我们着重考虑计算机的结构,而非问题本身的结构。\n", + "\n", + "程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一个副产业“编程方法”学科。\n", + "\n", + "为机器建模的另一个方法是为要解决的问题制作模型。对一些早期语言来说,如 LISP 和 APL,它们的做法是“从不同的角度观察世界”——“所有问题都归纳为列表”或“所有问题都归纳为算法”。PROLOG 则将所有\n", + "问题都归纳为决策链。对于这些语言,我们认为它们一部分是“基于约束”的编程,另一部分则是专为\n", + "处理图形符号设计的(后者被证明限制性太强)。每种方法都有自己特殊的用途,适合解决某一类的问题。只要超出了它们力所能及的范围,就会显得非常笨拙。\n", + "\n", + "面向对象的程序设计在此基础上跨出了一大步,程序员可利用一些工具表达“问题空间”内的元素。由于这种表达非常具有普遍性,所以不必受限于特定类型的问题。我们将问题空间中的元素以及它们在解决方案空间的表示称作“对象”(**Object**)。当然,还有一些在问题空间没有对应的对象体。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。所以当你在阅读描述解决方案的代码时,也是在阅读问题的表述。与我们以前见过的相比,这无疑是一种更加灵活、更加强大的语言抽象方法。总之,OOP 允许我们根据问题来描述问题,而不是根据运行解决方案的计算机。然而,它仍然与计算机有联系,每个对象都类似一台小计算机:它们有自己的状态并且可以进行特定的操作。这与现实世界的“对象”或者“物体”相似:它们都有自己的特征和行为。\n", + "\n", + "Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:\n", + "\n", + "> 1. **万物皆对象**。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。\n", + "> 2. **程序是一组对象,通过消息传递来告知彼此该做什么**。要请求调用一个对象的方法,你需要向该对象发送消息。\n", + "> 3. **每个对象都有自己的存储空间,可容纳其他对象**。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。\n", + "> 4. **每个对象都有一种类型**。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。\n", + "> 5. **同一类所有对象都能接收相同的消息**。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收发送给\"形状”的消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。\n", + "\n", + "*Grady Booch* 提供了对对象更简洁的描述:一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。\n", + "\n", + "## 接口\n", + "\n", + "亚里士多德(*Aristotle*)大概是第一个认真研究“类型”的哲学家,他曾提出过“鱼类和鸟类”这样的概念。所有对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。这种思想被首次应用于第一个面向对象编程语言 Simula-67,它在程序中使用基本关键字 **class** 来引入新的类型(class 和 type 通常可互换使用,有些人对它们进行了进一步区分,他们强调 type 决定了接口,而 class 是那个接口的一种特殊实现方式)。\n", + "\n", + "Simula 是一个很好的例子。正如这个名字所暗示的,它的作用是“模拟”(Simulate)类似“银行出纳员”这样的经典问题。在这个例子里,我们有一系列出纳员、客户、帐号、交易和货币单位等许多\"对象”。每类成员(元素)都具有一些通用的特征:每个帐号都有一定的余额;每名出纳都能接收客户的存款;等等。与此同时,每个成员都有自己的状态;每个帐号都有不同的余额;每名出纳都有一个名字。所以在计算机程序中,能用独一无二的实体分别表示出纳员、客户、帐号以及交易。这个实体便是“对象”,而且每个对象都隶属一个特定的“类”,那个类具有自己的通用特征与行为。\n", + "\n", + "因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。\n", + "\n", + "创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。\n", + "\n", + "那么如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。\n", + "\n", + "下面让我们以电灯泡为例:\n", + "\n", + "![reader](../images/reader.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Light lt = new Light();\n", + "lt.on();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。\n", + "\n", + "为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。\n", + "\n", + "上图遵循 **UML**(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 **UML** 设计图中显示,因此中间部分未显示,如本例所示。如果你只对类名感兴趣,则也不需要显示方法信息。\n", + "\n", + "## 服务提供\n", + "\n", + "在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。你的程序本身将为用户提供服务,并且它能通过调用其他对象提供的服务来实现这一点。我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。\n", + "\n", + "那么问题来了:我们该选择哪个对象来解决问题呢?例如,你正在开发一个记事本程序。*你可能会想到在屏幕输入默认的记事本对象*,一个用于检测不同类型打印机并执行打印的对象。这些对象中的某些已经有了。那对于还没有的对象,我们该设计成啥样呢?这些对象需要提供哪些服务,以及还需要调用其他哪些对象?\n", + "\n", + "我们可以将这些问题一一分解,抽象成一组服务。软件设计的基本原则是高内聚:每个组件的内部作用明确,功能紧密相关。然而经常有人将太多功能塞进一个对象中。例如:在支票打印模块中,你需要设计一个可以同时读取文本格式又能正确识别不同打印机型号的对象。正确的做法是提供三个或更多对象:一个对象检查所有排版布局的目录;一个或一组可以识别不同打印机型号的对象展示通用的打印界面;第三个对象组合上述两个服务来完成任务。这样,每个对象都提供了一组紧密的服务。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。\n", + "\n", + "## 封装\n", + "\n", + "我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。当应用程序员调用研发程序员开发的工具类时,双方建立了关系。应用程序员通过使用现成的工具类组装应用程序或者构建更大的工具库。如果工具类的创建者将类的内部所有信息都公开给调用者,那么有些使用规则就不容易被遵守。因为前者无法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种情况的发生。\n", + "\n", + "因此,使用访问控制的原因有以下两点:\n", + "\n", + "1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);\n", + "\n", + "2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造。\n", + "\n", + "Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。\n", + "\n", + " 1. `public`(公开)表示任何人都可以访问和使用该元素;\n", + "\n", + " 2. `private`(私有)除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;\n", + "\n", + " 3. `protected`(受保护)类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;\n", + "\n", + " 4. `default`(默认)如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。\n", + "\n", + "## 复用\n", + "\n", + "一个类经创建和测试后,理应是可复用的。然而很多时候,由于程序员没有足够的编程经验和远见,我们的代码复用性并不强。\n", + "\n", + "代码和设计方案的复用性是面向对象程序设计的优点之一。我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。这里涉及到“组合”和“聚合”的概念:\n", + "\n", + "* **组合**(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。\n", + "\n", + "* **聚合**(Aggregation)动态的**组合**。\n", + "\n", + "![UML-example](../images/1545758268350.png)\n", + "\n", + "上图中实心三角形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心三角形。\n", + "\n", + "(**译者注**:组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义。至于是聚合还是组合,需要根据实际的业务需求来判断。可能相同超类和子类,在不同的业务场景,关联关系会发生变化。只看代码是无法区分聚合和组合的,具体是哪一种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。)\n", + "\n", + "使用“组合”关系给我们的程序带来极大的灵活性。通常新建的类中,成员对象会使用 `private` 访问权限,这样应用程序员则无法对其直接访问。我们就可以在不影响客户代码的前提下,从容地修改那些成员。我们也可以在“运行时\"改变成员对象从而动态地改变程序的行为,这进一步增大了灵活性。下面一节要讲到的“继承”并不具备这种灵活性,因为编译器对通过继承创建的类进行了限制。\n", + "\n", + "在面向对象编程中经常重点强调“继承”。在新手程序员的印象里,或许先入为主地认为“继承应当随处可见”。沿着这种思路产生的程序设计通常拙劣又复杂。相反,在创建新类时首先要考虑“组合”,因为它更简单灵活,而且设计更加清晰。等我们有一些编程经验后,一旦需要用到继承,就会明显意识到这一点。\n", + "\n", + "## 继承\n", + "\n", + "“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。\n", + "\n", + "通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。\n", + "\n", + "![Inheritance-example](../images/1545763399825.png)\n", + "\n", + "这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。\n", + "\n", + "![1545764724202](../images/1545764724202.png)\n", + "\n", + "例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。\n", + "\n", + "![1545764780795](../images/1545764780795.png)\n", + "\n", + "例如,某些形状可以翻转。有些行为可能不同,比如计算形状的面积。类型层次结构体现了形状之间的相似性和差异性。以相同的术语将解决方案转换成问题是有用的,因为你不需要在问题描述和解决方案描述之间建立许多中间模型。通过使用对象,类型层次结构成为了主要模型,因此你可以直接从真实世界中对系统的描述过渡到用代码对系统进行描述。事实上,有时候,那些善于寻找复杂解决方案的人会被面向对象设计的简单性难倒。从现有类型继承创建新类型。这种新类型不仅包含现有类型的所有成员(尽管私有成员被隐藏起来并且不可访问),而且更重要的是它复制了基类的接口。也就是说,基类对象接收的所有消息也能被派生类对象接收。根据类接收的消息,我们知道类的类型,因此派生类与基类是相同的类型。\n", + "\n", + "在前面的例子中,“圆是形状”。这种通过继承的类型等价性是理解面向对象编程含义的基本门槛之一。因为基类和派生类都具有相同的基本接口,所以伴随此接口的必定有某些具体实现。也就是说,当对象接收到特定消息时,必须有可执行代码。如果继承一个类而不做其他任何事,则来自基类接口的方法直接进入派生类。这意味着派生类和基类不仅具有相同的类型,而且具有相同的行为,这么做没什么特别意义。\n", + "\n", + "有两种方法可以区分新的派生类与原始的基类。第一种方法很简单:在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。然而,还是要仔细考虑是否在基类中也要有这些额外的方法。这种设计的发现与迭代过程在面向对象程序设计中会经常发生。\n", + "\n", + "尽管继承有时意味着你要在接口中添加新方法(尤其是在以 **extends** 关键字表示继承的 Java 中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。\n", + "\n", + "### \"是一个\"与\"像是一个\"的关系\n", + "\n", + "对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作\"纯粹替代\",也经常被称作\"替代原则\"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说\"圆是一个形状\"。判断是否继承,就看在你的类之间有无这种 is-a 关系。\n", + "\n", + "有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。\n", + "\n", + "![1545764820176](../images/1545764820176.png)\n", + "\n", + "以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为\"温度控制系统\",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。\n", + "\n", + "当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。\n", + "\n", + "## 多态\n", + "\n", + "我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。在上个“形状”的例子中,“方法”(method)操纵的是通用“形状”,而不关心它们是“圆”、“正方形”、“三角形”还是某种尚未定义的形状。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。\n", + "\n", + "这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 例如,你可以通过通用的“形状”基类派生出新的“五角形”形状的子类,而不需要修改通用\"形状\"基类的方法。通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。\n", + "\n", + "这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。\n", + "\n", + "如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?举个例子,下图的 **BirdController** 对象和通用 **Bird** 对象中,**BirdController** 不知道 **Bird** 的确切类型却还能一起工作。从 **BirdController** 的角度来看,这是很方便的,因为它不需要编写特别的代码来确定 **Bird** 对象的确切类型或行为。那么,在调用 **move()** 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?\n", + "\n", + "![Bird-example](../images/1545839316314.png)\n", + "\n", + "这个问题的答案,是面向对象程序设计的妙诀:在传统意义上,编译器不能进行函数调用。由非 OOP 编译器产生的函数调用会引起所谓的**早期绑定**,这个术语你可能从未听说过,不会想过其他的函数调用方式。这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。\n", + "\n", + "通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用**后期绑定**的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。\n", + "\n", + "为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 **virtual** 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。\n", + "\n", + "为了演示多态性,我们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。\n", + "\n", + "代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void doSomething(Shape shape) {\n", + " shape.erase();\n", + " // ...\n", + " shape.draw();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此方法与任何 **Shape** 对话,因此它与所绘制和擦除的对象的具体类型无关。如果程序的其他部分使用 `doSomething()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " Circle circle = new Circle();\n", + " Triangle triangle = new Triangle();\n", + " Line line = new Line();\n", + " doSomething(circle);\n", + " doSomething(triangle);\n", + " doSomething(line);\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到无论传入的“形状”是什么,程序都正确的执行了。\n", + "\n", + "![shape-example](../images/1545841270997.png)\n", + "\n", + "这是一个非常令人惊奇的编程技巧。分析下面这行代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " doSomething(circle);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当预期接收 **Shape** 的方法被传入了 **Circle**,会发生什么。由于 **Circle** 也是一种 **Shape**,所\n", + "以 `doSomething(circle)` 能正确地执行。也就是说,`doSomething()` 能接收任意发送给 **Shape** 的消息。这是完全安全和合乎逻辑的事情。\n", + "\n", + "这种把子类当成其基类来处理的过程叫做“向上转型”(**upcasting**)。在面向对象的编程里,经常利用这种方法来给程序解耦。再看下面的 `doSomething()` 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " shape.erase();\n", + " // ...\n", + " shape.draw();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()`,你自己去做吧,注意细节。”\n", + "\n", + "尽管我们没作出任何特殊指示,程序的操作也是完全正确和恰当的。我们知道,为 Circle 调用`draw()` 时执行的代码与为一个 Square 或 Line 调用 `draw()` 时执行的代码是不同的。但在将 `draw()` 信息发给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确的操作。这非常神奇,因为当 Java 编译器为 `doSomething()` 编译代码时,它并不知道自己要操作的准确类型是什么。\n", + "\n", + "尽管我们确实可以保证最终会为 Shape 调用 `erase()` 和 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?\n", + "\n", + "发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。\n", + "\n", + "## 单继承结构\n", + "\n", + "自从 C++ 引入以来,一个 OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。\n", + "\n", + "Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。\n", + "\n", + "对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。\n", + "\n", + "另外,单继承的结构使得垃圾收集器的实现更为容易。这也是 Java 在 C++ 基础上的根本改进之一。\n", + "\n", + "由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如[异常处理](#异常处理)。同时,这也让我们的编程具有更大的灵活性。\n", + "\n", + "## 集合\n", + "\n", + "通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?\n", + "\n", + "在面向对象的设计中,问题的解决方案有些过于轻率:创建一个新类型的对象来引用、容纳其他的对象。当然,我们也可以使用多数编程语言都支持的“数组”(array)。在 Java 中“集合”(Collection)的使用率更高。(也可称之为“容器”,但“集合”这个称呼更通用。)\n", + "\n", + "“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们不用关心过程是如何实现的。\n", + "\n", + "还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。\n", + "\n", + "在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:\n", + "\n", + "1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。\n", + "\n", + "2. 不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。\n", + "\n", + "我们可以一开始使用 LinkedList 构建程序,在优化系统性能时改用 ArrayList。通过对 List 接口的抽象,我们可以很容易地将 LinkedList 改为 ArrayList。\n", + "\n", + "在 Java 5 泛型出来之前,集合中保存的是通用类型 `Object`。Java 单继承的结构意味着所有元素都基于 `Object` 类,所以在集合中可以保存任何类型的数据,易于重用。要使用这样的集合,我们先要往集合添加元素。由于 Java 5 版本前的集合只保存 `Object`,当我们往集合中添加元素时,元素便向上转型成了 `Object`,从而丢失自己原有的类型特性。这时我们再从集合中取出该元素时,元素的类型变成了 `Object`。那么我们该怎么将其转回原先具体的类型呢?这里,我们使用了强制类型转换将其转为更具体的类型,这个过程称为对象的“向下转型”。通过“向上转型”,我们知道“圆形”也是一种“形状”,这个过程是安全的。可是我们不能从“Object”看出其就是“圆形”或“形状”,所以除非我们能确定元素的具体类型信息,否则“向下转型”就是不安全的。也不能说这样的错误就是完全危险的,因为一旦我们转化了错误的类型,程序就会运行出错,抛出“运行时异常”(RuntimeException)。(后面的章节会提到) 无论如何,我们要寻找一种在取出集合元素时确定其具体类型的方法。另外,每次取出元素都要做额外的“向下转型”对程序和程序员都是一种开销。以某种方式创建集合,以确认保存元素的具体类型,减少集合元素“向下转型”的开销和可能出现的错误难道不好吗?这种解决方案就是:参数化类型机制(Parameterized Type Mechanism)。\n", + "\n", + "参数化类型机制可以使得编译器能够自动识别某个 `class` 的具体类型并正确地执行。举个例子,对集合的参数化类型机制可以让集合仅接受“形状”这种类型的元素,并以“形状”类型取出元素。Java 5 版本支持了参数化类型机制,称之为“泛型”(Generic)。泛型是 Java 5 的主要特性之一。你可以按以下方式向 ArrayList 中添加 Shape(形状):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " ArrayList shapes = new ArrayList<>();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "泛型的应用,让 Java 的许多标准库和组件都发生了改变。在本书的代码示例中,你也会经常看到泛型的身影。\n", + "\n", + "## 对象创建与生命周期\n", + "\n", + "我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。\n", + "\n", + "在简单的编程场景下,对象的清理并不是问题。我们创建对象,按需使用,最后销毁它。然而,情况往往要比这更复杂:\n", + "\n", + "假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。\n", + "\n", + "现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。\n", + "\n", + "对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。\n", + "\n", + "然而相对的,我们也牺牲了程序的灵活性。因为在编写代码时,我们必须要弄清楚对象的数量、生存时间还有类型。如果我们要用它来解决一个相当普遍的问题时(如计算机辅助设计、仓库管理或空中交通管制等),限制就太大了。\n", + "\n", + "第二种方法是在堆内存(Heap)中动态地创建对象。在这种方式下,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。\n", + "\n", + "动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。\n", + "\n", + "Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字构建该对象的动态实例。这又带来另一个问题:对象的生命周期。较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;然而如果你在堆内存创建对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。\n", + "\n", + "Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其他方面)。垃圾收集器知道对象什么时候不再被使用并且自动释放内存。结合单继承和仅可在堆中创建对象的机制,Java 的编码过程比用 C++ 要简单得多。我们所要做的决定和要克服的障碍也会少很多!\n", + "\n", + "## 异常处理\n", + "\n", + "自编程语言被发明以来,程序的错误处理一直都是个难题。因为很难设计出一个好的错误处理方案,所以许多编程语言都忽略了这个问题,把这个问题丢给了程序类库的设计者。他们提出了在许多情况下都可以工作但很容易被规避的半途而废的措施,通常只需忽略错误。多数错误处理方案的主要问题是:它们依赖程序员之间的约定俗成而不是语言层面的限制。换句话说,如果程序员赶时间或没想起来,这些方案就很容易被忘记。\n", + "\n", + "异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。这让我们的编码更简单:不用再反复检查错误了。另外,异常不像方法返回的错误值和方法设置用来表示发生错误的标志位那样可以被忽略。异常的发生是不会被忽略的,它终究会在某一时刻被处理。\n", + "\n", + "最后,“异常机制”提供了一种可靠地从错误状况中恢复的方法,使得我们可以编写出更健壮的程序。有时你只要处理好抛出的异常情况并恢复程序的运行即可,无需退出。\n", + "\n", + "Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。\n", + "\n", + "## 本章小结\n", + "\n", + "面向过程程序包含数据定义和函数调用。要找到程序的意图,你必须要在脑中建立一个模型,弄清函数调用和更底层的概念。这些程序令人困扰,因为它们的表示更多地面向计算机而不是我们要解决的问题,这就是我们在设计程序时需要中间表示的原因。OOP 在面向过程编程的基础上增加了许多新的概念,所以有人会认为使用 Java 来编程会比同等的面向过程编程要更复杂。在这里,我想给大家一个惊喜:通常按照 Java 规范编写的程序会比面向过程程序更容易被理解。\n", + "\n", + "你看到的是对象的概念,这些概念是站在“问题空间”的(而不是站在计算机角度的“解决方案空间”),以及发送消息给对象以指示该空间中的活动。面向对象编程的一个优点是:设计良好的 Java 程序代码更容易被人阅读理解。由于 Java 类库的复用性,通常程序要写的代码也会少得多。\n", + "\n", + "OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb b/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb new file mode 100644 index 00000000..655a709c --- /dev/null +++ b/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb @@ -0,0 +1,2214 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第四章 运算符\n", + "\n", + ">运算符操纵数据。\n", + "\n", + "Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。\n", + "\n", + "如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。\n", + "\n", + "\n", + "## 开始使用\n", + "\n", + "运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。\n", + "\n", + "几乎所有运算符都只能操作基本类型(Primitives)。唯一的例外是 `=`、`==` 和 `!=`,它们能操作所有对象(这也是令人混淆的一个地方)。除此以外,**String** 类支持 `+` 和 `+=`。\n", + "\n", + "\n", + "## 优先级\n", + "\n", + "运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Precedence.java\n", + "public class Precedence {\n", + " \n", + " public static void main(String[] args) {\n", + " int x = 1, y = 2, z = 3;\n", + " int a = x + y - 2/2 + z; // [1]\n", + " int b = x + (y - 2)/(2 + z); // [2]\n", + " System.out.println(\"a = \" + a);\n", + " System.out.println(\"b = \" + b);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " a = 5\n", + " b = 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些语句看起来大致相同,但从输出中我们可以看出它们具有非常不同的含义,具体取决于括号的使用。\n", + "\n", + "我们注意到,在 `System.out.println()` 语句中使用了 `+` 运算符。 但是在这里 `+` 代表的意思是字符串连接符。编译器会将 `+` 连接的非字符串尝试转换为字符串。上例中的输出结果说明了 a 和 b 都已经被转化成了字符串。\n", + "\n", + "\n", + "## 赋值\n", + "\n", + "运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 = A)。\n", + "\n", + "基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。\n", + "\n", + "如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java " + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Assignment.java\n", + "// Assignment with objects is a bit tricky\n", + "class Tank {\n", + " int level;\n", + "}\n", + "\n", + "public class Assignment {\n", + "\n", + " public static void main(String[] args) {\n", + " Tank t1 = new Tank();\n", + " Tank t2 = new Tank();\n", + " t1.level = 9;\n", + " t2.level = 47;\n", + " System.out.println(\"1: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " t1 = t2;\n", + " System.out.println(\"2: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " t1.level = 27;\n", + " System.out.println(\"3: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1: t1.level: 9, t2.level: 47\n", + "2: t1.level: 47, t2.level: 47\n", + "3: t1.level: 27, t2.level: 27" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个简单的 `Tank` 类,在 `main()` 方法创建了两个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。\n", + "\n", + "这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "t1.level = t2.level;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的“惊喜”。\n", + "\n", + " \n", + "### 方法调用中的别名现象\n", + "\n", + "当我们把对象传递给方法时,会发生别名现象。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/PassObject.java\n", + "// 正在传递的对象可能不是你之前使用的\n", + "class Letter {\n", + " char c;\n", + "}\n", + "\n", + "public class PassObject {\n", + " static void f(Letter y) {\n", + " y.c = 'z';\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Letter x = new Letter();\n", + " x.c = 'a';\n", + " System.out.println(\"1: x.c: \" + x.c);\n", + " f(x);\n", + " System.out.println(\"2: x.c: \" + x.c);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1: x.c: a\n", + "2: x.c: z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在许多编程语言中,方法 `f()` 似乎会在内部复制其参数 **Letter y**。但是一旦传递了一个引用,那么实际上 `y.c ='z';` 是在方法 `f()` 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:[对象传递和返回](./Appendix-Passing-and-Returning-Objects.md)。意识到这一点,我们可以警惕类似的陷阱。\n", + "\n", + "\n", + "## 算术运算符\n", + "\n", + "Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 `+`、减号 `-`、除号 `/`、乘号 `*` 以及取模 `%`(从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。\n", + "\n", + "Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。 可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。更多代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/MathOps.java\n", + "// The mathematical operators\n", + "import java.util.*;\n", + "\n", + "public class MathOps {\n", + " public static void main(String[] args) {\n", + " // Create a seeded random number generator:\n", + " Random rand = new Random(47);\n", + " int i, j, k;\n", + " // Choose value from 1 to 100:\n", + " j = rand.nextInt(100) + 1;\n", + " System.out.println(\"j : \" + j);\n", + " k = rand.nextInt(100) + 1;\n", + " System.out.println(\"k : \" + k);\n", + " i = j + k;\n", + " System.out.println(\"j + k : \" + i);\n", + " i = j - k;\n", + " System.out.println(\"j - k : \" + i);\n", + " i = k / j;\n", + " System.out.println(\"k / j : \" + i);\n", + " i = k * j;\n", + " System.out.println(\"k * j : \" + i);\n", + " i = k % j;\n", + " System.out.println(\"k % j : \" + i);\n", + " j %= k;\n", + " System.out.println(\"j %= k : \" + j);\n", + " // 浮点运算测试\n", + " float u, v, w; // Applies to doubles, too\n", + " v = rand.nextFloat();\n", + " System.out.println(\"v : \" + v);\n", + " w = rand.nextFloat();\n", + " System.out.println(\"w : \" + w);\n", + " u = v + w;\n", + " System.out.println(\"v + w : \" + u);\n", + " u = v - w;\n", + " System.out.println(\"v - w : \" + u);\n", + " u = v * w;\n", + " System.out.println(\"v * w : \" + u);\n", + " u = v / w;\n", + " System.out.println(\"v / w : \" + u);\n", + " // 下面的操作同样适用于 char, \n", + " // byte, short, int, long, and double:\n", + " u += v;\n", + " System.out.println(\"u += v : \" + u);\n", + " u -= v;\n", + " System.out.println(\"u -= v : \" + u);\n", + " u *= v;\n", + " System.out.println(\"u *= v : \" + u);\n", + " u /= v;\n", + " System.out.println(\"u /= v : \" + u); \n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "j : 59\n", + "k : 56\n", + "j + k : 115\n", + "j - k : 3\n", + "k / j : 0\n", + "k * j : 3304\n", + "k % j : 56\n", + "j %= k : 3\n", + "v : 0.5309454\n", + "w : 0.0534122\n", + "v + w : 0.5843576\n", + "v - w : 0.47753322\n", + "v * w : 0.028358962\n", + "v / w : 9.940527\n", + "u += v : 10.471473\n", + "u -= v : 9.940527\n", + "u *= v : 5.2778773\n", + "u /= v : 9.940527" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了生成随机数字,程序首先创建一个 **Random** 对象。不带参数的 **Random** 对象会利用当前的时间用作随机数生成器的“种子”(seed),从而为程序的每次执行生成不同的输出。在本书的示例中,重要的是每个示例末尾的输出尽可能一致,以便可以使用外部工具进行验证。所以我们通过在创建 **Random** 对象时提供种子(随机数生成器的初始化值,其始终为特定种子值产生相同的序列),让程序每次执行都生成相同的随机数,如此以来输出结果就是可验证的 [^1]。 若需要生成随机值,可删除代码示例中的种子参数。该对象通过调用方法 `nextInt()` 和 `nextFloat()`(还可以调用 `nextLong()` 或 `nextDouble()`),使用 **Random** 对象生成许多不同类型的随机数。`nextInt()` 的参数设置生成的数字的上限,下限为零,为了避免零除的可能性,结果偏移1。\n", + "\n", + "\n", + "### 一元加减运算符\n", + "\n", + "一元加 `+` 减 `-` 运算符的操作和二元是相同的。编译器可自动识别使用何种方式解析运算:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = -a;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的代码表意清晰,编译器可正确识别。下面再看一个示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = a * -b;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然编译器可以正确的识别,但是程序员可能会迷惑。为了避免混淆,推荐下面的写法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = a * (-b);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一元减号可以得到数据的负值。一元加号的作用相反,不过它唯一能影响的就是把较小的数值类型自动转换为 **int** 类型。\n", + "\n", + "\n", + "## 递增和递减\n", + "\n", + "和 C 语言类似,Java 提供了许多快捷运算方式。快捷运算可使代码可读性,可写性都更强。其中包括递增 `++` 和递减 `--`,意为“增加或减少一个单位”。举个例子来说,假设 a 是一个 **int** 类型的值,则表达式 `++a` 就等价于 `a = a + 1`。 递增和递减运算符不仅可以修改变量,还可以生成变量的值。\n", + "\n", + "每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀”和“后缀”。“前递增”表示 `++` 运算符位于变量或表达式的前面;而“后递增”表示 `++` 运算符位于变量的后面。类似地,“前递减”意味着 `--` 运算符位于变量的前面;而“后递减”意味着 `--` 运算符位于变量的后面。对于前递增和前递减(如 `++a` 或 `--a`),会先执行递增/减运算,再返回值。而对于后递增和后递减(如 `a++` 或 `a--`),会先返回值,再执行递增/减运算。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/AutoInc.java\n", + "// 演示 ++ 和 -- 运算符\n", + "public class AutoInc {\n", + " public static void main(String[] args) {\n", + " int i = 1;\n", + " System.out.println(\"i: \" + i);\n", + " System.out.println(\"++i: \" + ++i); // 前递增\n", + " System.out.println(\"i++: \" + i++); // 后递增\n", + " System.out.println(\"i: \" + i);\n", + " System.out.println(\"--i: \" + --i); // 前递减\n", + " System.out.println(\"i--: \" + i--); // 后递减\n", + " System.out.println(\"i: \" + i);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i: 1\n", + "++i: 2\n", + "i++: 2\n", + "i: 3\n", + "--i: 2\n", + "i--: 2\n", + "i: 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于前缀形式,我们将在执行递增/减操作后获取值;使用后缀形式,我们将在执行递增/减操作之前获取值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外) —— 它们修改了操作数的值。\n", + "\n", + "C++ 名称来自于递增运算符,暗示着“比 C 更进一步”。在早期的 Java 演讲中,*Bill Joy*(Java 作者之一)说“**Java = C++ --**”(C++ 减减),意味着 Java 在 C++ 的基础上减少了许多不必要的东西,因此语言更简单。随着进一步地学习,我们会发现 Java 的确有许多地方相对 C++ 来说更简便,但是在其他方面,难度并不会比 C++ 小多少。\n", + "\n", + "\n", + "## 关系运算符\n", + "\n", + "关系运算符会通过产生一个布尔(**boolean**)结果来表示操作数之间的关系。如果关系为真,则结果为 **true**,如果关系为假,则结果为 **false**。关系运算符包括小于 `<`,大于 `>`,小于或等于 `<=`,大于或等于 `>=`,等于 `==` 和不等于 `!=`。`==` 和 `!=` 可用于所有基本类型,但其他运算符不能用于基本类型 **boolean**,因为布尔值只能表示 **true** 或 **false**,所以比较它们之间的“大于”或“小于”没有意义。\n", + "\n", + "\n", + "### 测试对象等价\n", + "\n", + "关系运算符 `==` 和 `!=` 同样适用于所有对象之间的比较运算,但它们比较的内容却经常困扰 Java 的初学者。下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Equivalence.java\n", + "public class Equivalence {\n", + " public static void main(String[] args) {\n", + " Integer n1 = 47;\n", + " Integer n2 = 47;\n", + " System.out.println(n1 == n2);\n", + " System.out.println(n1 != n2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "true\n", + "false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把 47 改成 128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。\n", + "\n", + "那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 `equals()` 方法,下面是如何使用 `equals()` 方法的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/EqualsMethod.java\n", + "public class EqualsMethod {\n", + " public static void main(String[] args) {\n", + " Integer n1 = 47;\n", + " Integer n2 = 47;\n", + " System.out.println(n1.equals(n2));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "true" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的结果看起来是我们所期望的。但其实事情并非那么简单。下面我们来创建自己的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/EqualsMethod2.java\n", + "// 默认的 equals() 方法没有比较内容\n", + "class Value {\n", + " int i;\n", + "}\n", + "\n", + "public class EqualsMethod2 {\n", + " public static void main(String[] args) {\n", + " Value v1 = new Value();\n", + " Value v2 = new Value();\n", + " v1.i = v2.i = 100;\n", + " System.out.println(v1.equals(v2));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的结果再次令人困惑:结果是 **false**。原因: `equals()` 的默认行为是比较对象的引用而非具体内容。因此,除非你在新类中覆写 `equals()` 方法,否则我们将获取不到想要的结果。不幸的是,在学习 [复用](./08-Reuse.md)(**Reuse**) 章节后我们才能接触到“覆写”(**Override**),并且直到 [附录:集合主题](./Appendix-Collection-Topics.md),才能知道定义 `equals()` 方法的正确方式,但是现在明白 `equals()` 行为方式也可能为你节省一些时间。\n", + "\n", + "大多数 Java 库类通过覆写 `equals()` 方法比较对象的内容而不是其引用。\n", + "\n", + "\n", + "## 逻辑运算符\n", + "\n", + "每个逻辑运算符 `&&` (**AND**)、`||`(**OR**)和 `!`(**非**)根据参数的逻辑关系生成布尔值 `true` 或 `false`。下面的代码示例使用了关系运算符和逻辑运算符:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Bool.java\n", + "// 关系运算符和逻辑运算符\n", + "import java.util.*;\n", + "public class Bool {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " int i = rand.nextInt(100);\n", + " int j = rand.nextInt(100);\n", + " System.out.println(\"i = \" + i);\n", + " System.out.println(\"j = \" + j);\n", + " System.out.println(\"i > j is \" + (i > j));\n", + " System.out.println(\"i < j is \" + (i < j));\n", + " System.out.println(\"i >= j is \" + (i >= j));\n", + " System.out.println(\"i <= j is \" + (i <= j));\n", + " System.out.println(\"i == j is \" + (i == j));\n", + " System.out.println(\"i != j is \" + (i != j));\n", + " // 将 int 作为布尔处理不是合法的 Java 写法\n", + " //- System.out.println(\"i && j is \" + (i && j));\n", + " //- System.out.println(\"i || j is \" + (i || j));\n", + " //- System.out.println(\"!i is \" + !i);\n", + " System.out.println(\"(i < 10) && (j < 10) is \"\n", + " + ((i < 10) && (j < 10)) );\n", + " System.out.println(\"(i < 10) || (j < 10) is \"\n", + " + ((i < 10) || (j < 10)) );\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 58\n", + "j = 55\n", + "i > j is true\n", + "i < j is false\n", + "i >= j is true\n", + "i <= j is false\n", + "i == j is false\n", + "i != j is true\n", + "(i < 10) && (j < 10) is false\n", + "(i < 10) || (j < 10) is false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、 **OR**、 **NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 \"true\" 或 \"false\" 字符串)。\n", + "\n", + "我们可以将前一个程序中 **int** 的定义替换为除 **boolean** 之外的任何其他基本数据类型。但请注意,**float** 类型的数值比较非常严格,只要两个数字的最小位不同则两个数仍然不相等;只要数字最小位是大于 0 的,那么它就不等于 0。\n", + "\n", + "\n", + "### 短路\n", + "\n", + "逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators / ShortCircuit.java \n", + "// 逻辑运算符的短路行为\n", + "public class ShortCircuit {\n", + "\n", + " static boolean test1(int val) {\n", + " System.out.println(\"test1(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 1));\n", + " return val < 1;\n", + " }\n", + "\n", + " static boolean test2(int val) {\n", + " System.out.println(\"test2(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 2));\n", + " return val < 2;\n", + " }\n", + "\n", + " static boolean test3(int val) {\n", + " System.out.println(\"test3(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 3));\n", + " return val < 3;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " boolean b = test1(0) && test2(2) && test3(2);\n", + " System.out.println(\"expression is \" + b);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test1(0)\n", + "result: true\n", + "test2(2)\n", + "result: false\n", + "expression is false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个测试都对参数执行比较并返回 `true` 或 `false`。同时控制台也会在方法执行时打印他们的执行状态。 下面的表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "test1(0)&& test2(2)&& test3(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可能你的预期是程序会执行 3 个 **test** 方法并返回。我们来分析一下:第一个方法的结果返回 `true`,因此表达式会继续走下去。紧接着,第二个方法的返回结果是 `false`。这就代表这整个表达式的结果肯定为 `false`,所以就没有必要再判断剩下的表达式部分了。\n", + "\n", + "所以,运用“短路”可以节省部分不必要的运算,从而提高程序潜在的性能。\n", + "\n", + "\n", + "## 字面值常量\n", + "\n", + "通常,当我们向程序中插入一个字面值常量(**Literal**)时,编译器会确切地识别它的类型。当类型不明确时,必须辅以字面值常量关联来帮助编译器识别。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Literals.java\n", + "public class Literals {\n", + " public static void main(String[] args) {\n", + " int i1 = 0x2f; // 16进制 (小写)\n", + " System.out.println(\n", + " \"i1: \" + Integer.toBinaryString(i1));\n", + " int i2 = 0X2F; // 16进制 (大写)\n", + " System.out.println(\n", + " \"i2: \" + Integer.toBinaryString(i2));\n", + " int i3 = 0177; // 8进制 (前导0)\n", + " System.out.println(\n", + " \"i3: \" + Integer.toBinaryString(i3));\n", + " char c = 0xffff; // 最大 char 型16进制值\n", + " System.out.println(\n", + " \"c: \" + Integer.toBinaryString(c));\n", + " byte b = 0x7f; // 最大 byte 型16进制值 10101111;\n", + " System.out.println(\n", + " \"b: \" + Integer.toBinaryString(b));\n", + " short s = 0x7fff; // 最大 short 型16进制值\n", + " System.out.println(\n", + " \"s: \" + Integer.toBinaryString(s));\n", + " long n1 = 200L; // long 型后缀\n", + " long n2 = 200l; // long 型后缀 (容易与数值1混淆)\n", + " long n3 = 200;\n", + " \n", + " // Java 7 二进制字面值常量:\n", + " byte blb = (byte)0b00110101;\n", + " System.out.println(\n", + " \"blb: \" + Integer.toBinaryString(blb));\n", + " short bls = (short)0B0010111110101111;\n", + " System.out.println(\n", + " \"bls: \" + Integer.toBinaryString(bls));\n", + " int bli = 0b00101111101011111010111110101111;\n", + " System.out.println(\n", + " \"bli: \" + Integer.toBinaryString(bli));\n", + " long bll = 0b00101111101011111010111110101111;\n", + " System.out.println(\n", + " \"bll: \" + Long.toBinaryString(bll));\n", + " float f1 = 1;\n", + " float f2 = 1F; // float 型后缀\n", + " float f3 = 1f; // float 型后缀\n", + " double d1 = 1d; // double 型后缀\n", + " double d2 = 1D; // double 型后缀\n", + " // (long 型的字面值同样适用于十六进制和8进制 )\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i1: 101111\n", + "i2: 101111\n", + "i3: 1111111\n", + "c: 1111111111111111\n", + "b: 1111111\n", + "s: 111111111111111\n", + "blb: 110101\n", + "bls: 10111110101111\n", + "bli: 101111101011111010111110101111\n", + "bll: 101111101011111010111110101111" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在文本值的后面添加字符可以让编译器识别该文本值的类型。对于 **Long** 型数值,结尾使用大写 `L` 或小写 `l` 皆可(不推荐使用 `l`,因为容易与阿拉伯数值 1 混淆)。大写 `F` 或小写 `f` 表示 **float** 浮点数。大写 `D` 或小写 `d` 表示 **double** 双精度。\n", + "\n", + "十六进制(以 16 为基数),适用于所有整型数据类型,由前导 `0x` 或 `0X` 表示,后跟 0-9 或 a-f (大写或小写)。如果我们在初始化某个类型的数值时,赋值超出其范围,那么编译器会报错(不管值的数字形式如何)。在上例的代码中,**char**、**byte** 和 **short** 的值已经是最大了。如果超过这些值,编译器将自动转型为 **int**,并且提示我们需要声明强制转换(强制转换将在本章后面定义),意味着我们已越过该类型的范围界限。\n", + "\n", + "八进制(以 8 为基数)由 0~7 之间的数字和前导零 `0` 表示。\n", + "\n", + "Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,它可以初始化所有的整数类型。\n", + "\n", + "使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。\n", + "\n", + "\n", + "### 下划线\n", + "\n", + "Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 `_`,以使结果更清晰。这对于大数值的分组特别有用。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Underscores.java\n", + "public class Underscores {\n", + " public static void main(String[] args) {\n", + " double d = 341_435_936.445_667;\n", + " System.out.println(d);\n", + " int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;\n", + " System.out.println(Integer.toBinaryString(bin));\n", + " System.out.printf(\"%x%n\", bin); // [1]\n", + " long hex = 0x7f_e9_b7_aa;\n", + " System.out.printf(\"%x%n\", hex);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "3.41435936445667E8\n", + "101111101011111010111110101111\n", + "2fafafaf\n", + "7fe9b7aa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面是合理使用的规则:\n", + "\n", + "1. 仅限单 `_`,不能多条相连。\n", + "2. 数值开头和结尾不允许出现 `_`。\n", + "3. `F`、`D` 和 `L`的前后禁止出现 `_`。\n", + "4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。\n", + "\n", + "[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\\r\\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。\n", + "\n", + "\n", + "### 指数计数法\n", + "\n", + "指数总是采用一种我认为很不直观的记号方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Exponents.java\n", + "// \"e\" 表示 10 的几次幂\n", + "public class Exponents {\n", + " public static void main(String[] args) {\n", + " // 大写 E 和小写 e 的效果相同:\n", + " float expFloat = 1.39e-43f;\n", + " expFloat = 1.39E-43f;\n", + " System.out.println(expFloat);\n", + " double expDouble = 47e47d; // 'd' 是可选的\n", + " double expDouble2 = 47e47; // 自动转换为 double\n", + " System.out.println(expDouble);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1.39E-43\n", + "4.7E48" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 \"1.39 x e-43\",意味着 “1.39 × 2.718 的 -43 次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10 的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。\n", + "\n", + "理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是 “1.39 × 10 的 -43 次方”。\n", + "\n", + "注意如果编译器能够正确地识别类型,就不必使用后缀字符。对于下述语句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "long n3 = 200;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它并不存在含糊不清的地方,所以 200 后面的 L 大可省去。然而,对于下述语句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "float f4 = 1e-43f; //10 的幂数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器通常会将指数作为 **double** 类型来处理,所以假若没有这个后缀字符 `f`,编译器就会报错,提示我们应该将 **double** 型转换成 **float** 型。\n", + "\n", + "\n", + "## 位运算符\n", + "\n", + "位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。\n", + "\n", + "位运算源自 C 语言的底层操作。我们经常要直接操纵硬件,频繁设置硬件寄存器内的二进制位。Java 的设计初衷是电视机顶盒嵌入式开发,所以这种底层的操作仍被保留了下来。但是,你可能不会使用太多位运算。\n", + "\n", + "若两个输入位都是 1,则按位“与运算符” `&` 运算后结果是 1,否则结果是 0。若两个输入位里至少有一个是 1,则按位“或运算符” `|` 运算后结果是 1;只有在两个输入位都是 0 的情况下,运算结果才是 0。若两个输入位的某一个是 1,另一个不是 1,那么按位“异或运算符” `^` 运算后结果才是 1。按位“非运算符” `~` 属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位非运算后结果与输入位相反。例如输入 0,则输出 1;输入 1,则输出 0。\n", + "\n", + "位运算符和逻辑运算符都使用了同样的字符,只不过数量不同。位短,所以位运算符只有一个字符。位运算符可与等号 `=` 联合使用以接收结果及赋值:`&=`,`|=` 和 `^=` 都是合法的(由于 `~` 是一元运算符,所以不可与 `=` 联合使用)。\n", + "\n", + "我们将 **Boolean** 类型被视为“单位值”(one-bit value),所以它多少有些独特的地方。我们可以对 boolean 型变量执行与、或、异或运算,但不能执行非运算(大概是为了避免与逻辑“非”混淆)。对于布尔值,位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的位运算为我们新增了一个“异或”逻辑运算符,它并未包括在逻辑运算符的列表中。在移位表达式中,禁止使用布尔值,原因将在下面解释。\n", + "\n", + "\n", + "## 移位运算符\n", + "\n", + "移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。\n", + "\n", + "如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。\n", + "\n", + "移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/URShift.java\n", + "// 测试无符号右移\n", + "public class URShift {\n", + " public static void main(String[] args) {\n", + " int i = -1;\n", + " System.out.println(Integer.toBinaryString(i));\n", + " i >>>= 10;\n", + " System.out.println(Integer.toBinaryString(i));\n", + " long l = -1;\n", + " System.out.println(Long.toBinaryString(l));\n", + " l >>>= 10;\n", + " System.out.println(Long.toBinaryString(l));\n", + " short s = -1;\n", + " System.out.println(Integer.toBinaryString(s));\n", + " s >>>= 10;\n", + " System.out.println(Integer.toBinaryString(s));\n", + " byte b = -1;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " b >>>= 10;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " b = -1;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " System.out.println(Integer.toBinaryString(b>>>10));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "11111111111111111111111111111111\n", + "1111111111111111111111\n", + "1111111111111111111111111111111111111111111111111111111111111111\n", + "111111111111111111111111111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "1111111111111111111111" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在上例中,结果并未重新赋值给变量 **b** ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/BitManipulation.java\n", + "// 使用位运算符\n", + "import java.util.*;\n", + "public class BitManipulation {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " int i = rand.nextInt();\n", + " int j = rand.nextInt();\n", + " printBinaryInt(\"-1\", -1);\n", + " printBinaryInt(\"+1\", +1);\n", + " int maxpos = 2147483647;\n", + " printBinaryInt(\"maxpos\", maxpos);\n", + " int maxneg = -2147483648;\n", + " printBinaryInt(\"maxneg\", maxneg);\n", + " printBinaryInt(\"i\", i);\n", + " printBinaryInt(\"~i\", ~i);\n", + " printBinaryInt(\"-i\", -i);\n", + " printBinaryInt(\"j\", j);\n", + " printBinaryInt(\"i & j\", i & j);\n", + " printBinaryInt(\"i | j\", i | j);\n", + " printBinaryInt(\"i ^ j\", i ^ j);\n", + " printBinaryInt(\"i << 5\", i << 5);\n", + " printBinaryInt(\"i >> 5\", i >> 5);\n", + " printBinaryInt(\"(~i) >> 5\", (~i) >> 5);\n", + " printBinaryInt(\"i >>> 5\", i >>> 5);\n", + " printBinaryInt(\"(~i) >>> 5\", (~i) >>> 5);\n", + " long l = rand.nextLong();\n", + " long m = rand.nextLong();\n", + " printBinaryLong(\"-1L\", -1L);\n", + " printBinaryLong(\"+1L\", +1L);\n", + " long ll = 9223372036854775807L;\n", + " printBinaryLong(\"maxpos\", ll);\n", + " long lln = -9223372036854775808L;\n", + " printBinaryLong(\"maxneg\", lln);\n", + " printBinaryLong(\"l\", l);\n", + " printBinaryLong(\"~l\", ~l);\n", + " printBinaryLong(\"-l\", -l);\n", + " printBinaryLong(\"m\", m);\n", + " printBinaryLong(\"l & m\", l & m);\n", + " printBinaryLong(\"l | m\", l | m);\n", + " printBinaryLong(\"l ^ m\", l ^ m);\n", + " printBinaryLong(\"l << 5\", l << 5);\n", + " printBinaryLong(\"l >> 5\", l >> 5);\n", + " printBinaryLong(\"(~l) >> 5\", (~l) >> 5);\n", + " printBinaryLong(\"l >>> 5\", l >>> 5);\n", + " printBinaryLong(\"(~l) >>> 5\", (~l) >>> 5);\n", + " }\n", + "\n", + " static void printBinaryInt(String s, int i) {\n", + " System.out.println(\n", + " s + \", int: \" + i + \", binary:\\n \" +\n", + " Integer.toBinaryString(i));\n", + " }\n", + "\n", + " static void printBinaryLong(String s, long l) {\n", + " System.out.println(\n", + " s + \", long: \" + l + \", binary:\\n \" +\n", + " Long.toBinaryString(l));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果(前 32 行):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-1, int: -1, binary:\n", + "11111111111111111111111111111111\n", + "+1, int: 1, binary:\n", + "1\n", + "maxpos, int: 2147483647, binary:\n", + "1111111111111111111111111111111\n", + "maxneg, int: -2147483648, binary:\n", + "10000000000000000000000000000000\n", + "i, int: -1172028779, binary:\n", + "10111010001001000100001010010101\n", + "~i, int: 1172028778, binary:\n", + " 1000101110110111011110101101010\n", + "-i, int: 1172028779, binary:\n", + "1000101110110111011110101101011\n", + "j, int: 1717241110, binary:\n", + "1100110010110110000010100010110\n", + "i & j, int: 570425364, binary:\n", + "100010000000000000000000010100\n", + "i | j, int: -25213033, binary:\n", + "11111110011111110100011110010111\n", + "i ^ j, int: -595638397, binary:\n", + "11011100011111110100011110000011\n", + "i << 5, int: 1149784736, binary:\n", + "1000100100010000101001010100000\n", + "i >> 5, int: -36625900, binary:\n", + "11111101110100010010001000010100\n", + "(~i) >> 5, int: 36625899, binary:\n", + "10001011101101110111101011\n", + "i >>> 5, int: 97591828, binary:\n", + "101110100010010001000010100\n", + "(~i) >>> 5, int: 36625899, binary:\n", + "10001011101101110111101011\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。\n", + "\n", + "\n", + "## 三元运算符\n", + "\n", + "三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 **if-else** 语句是不同的。下面是它的表达式格式:\n", + "\n", + "**布尔表达式 ? 值 1 : 值 2**\n", + "\n", + "若表达式计算为 **true**,则返回结果 **值 1** ;如果表达式的计算为 **false**,则返回结果 **值 2**。\n", + "\n", + "当然,也可以换用普通的 **if-else** 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 **if-else** 不同的是,三元运算符是有返回结果的。请看下面的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/TernaryIfElse.java\n", + "public class TernaryIfElse {\n", + " \n", + "static int ternary(int i) {\n", + " return i < 10 ? i * 100 : i * 10;\n", + "}\n", + "\n", + "static int standardIfElse(int i) {\n", + " if(i < 10)\n", + " return i * 100;\n", + " else\n", + " return i * 10;\n", + "}\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(ternary(9));\n", + " System.out.println(ternary(10));\n", + " System.out.println(standardIfElse(9));\n", + " System.out.println(standardIfElse(10));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "900\n", + "100\n", + "900\n", + "100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看出,`ternary()` 中的代码更简短。然而,**standardIfElse()** 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。\n", + "\n", + "\n", + "## 字符串运算符\n", + "\n", + "这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 `+` 的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。\n", + "\n", + "我们注意到运用 `String +` 时有一些有趣的现象。若表达式以一个 **String** 类型开头(编译器会自动将双引号 `\"\"` 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/StringOperators.java\n", + "public class StringOperators {\n", + " public static void main(String[] args) {\n", + " int x = 0, y = 1, z = 2;\n", + " String s = \"x, y, z \";\n", + " System.out.println(s + x + y + z);\n", + " // 将 x 转换为字符串\n", + " System.out.println(x + \" \" + s);\n", + " s += \"(summed) = \"; \n", + " // 级联操作\n", + " System.out.println(s + (x + y + z));\n", + " // Integer.toString()方法的简写:\n", + " System.out.println(\"\" + x);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z 012\n", + "0 x, y, z\n", + "x, y, z (summed) = 3\n", + "0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意**:上例中第 1 输出语句的执行结果是 `012` 而并非 `3`,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 **s** 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 `+=` 运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 `s`。括号 `()` 可以控制表达式的计算顺序,以便在显示 **int** 之前对其进行实际求和。\n", + "\n", + "请注意主方法中的最后一个例子:我们经常会看到一个空字符串 `\"\"` 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 **Integer.toString()**)。\n", + "\n", + "\n", + "## 常见陷阱\n", + "\n", + "使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 `()`。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "while(x = y) {\n", + "// ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。\n", + "\n", + "唯一有种情况例外:当变量 `x` 和 `y` 都是布尔值,例如 `x=y` 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。\n", + "\n", + "在 C/C++ 里,类似的一个问题还有使用按位“与” `&` 和“或” `|` 运算,而非逻辑“与” `&&` 和“或” `||`。就象 `=` 和 `==` 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。\n", + "\n", + "\n", + "## 类型转换\n", + "\n", + "“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 **float** 变量赋值一个整数值,计算机会将 **int** 自动转换成 **float**。我们可以在程序未自动转换时显式、强制地使此类型发生转换。\n", + "\n", + "要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Casting.java\n", + "public class Casting {\n", + " public static void main(String[] args) {\n", + " int i = 200;\n", + " long lng = (long)i;\n", + " lng = i; // 没有必要的类型提升\n", + " long lng2 = (long)200;\n", + " lng2 = 200;\n", + " // 类型收缩\n", + " i = (int)lng2; // Cast required\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 **int** 型数据为 **long** 型。\n", + "\n", + "当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。\n", + "\n", + "除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。\n", + "\n", + "\n", + "### 截断和舍入\n", + "\n", + "在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/CastingNumbers.java\n", + "// 尝试转换 float 和 double 型数据为整型数据\n", + "public class CastingNumbers {\n", + " public static void main(String[] args) {\n", + " double above = 0.7, below = 0.4;\n", + " float fabove = 0.7f, fbelow = 0.4f;\n", + " System.out.println(\"(int)above: \" + (int)above);\n", + " System.out.println(\"(int)below: \" + (int)below);\n", + " System.out.println(\"(int)fabove: \" + (int)fabove);\n", + " System.out.println(\"(int)fbelow: \" + (int)fbelow);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(int)above: 0\n", + "(int)below: 0\n", + "(int)fabove: 0\n", + "(int)fbelow: 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,答案是,从 **float** 和 **double** 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 `java.lang.Math` 的 ` round()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/RoundingNumbers.java\n", + "// float 和 double 类型数据的四舍五入\n", + "public class RoundingNumbers {\n", + " public static void main(String[] args) {\n", + " double above = 0.7, below = 0.4;\n", + " float fabove = 0.7f, fbelow = 0.4f;\n", + " System.out.println(\n", + " \"Math.round(above): \" + Math.round(above));\n", + " System.out.println(\n", + " \"Math.round(below): \" + Math.round(below));\n", + " System.out.println(\n", + " \"Math.round(fabove): \" + Math.round(fabove));\n", + " System.out.println(\n", + " \"Math.round(fbelow): \" + Math.round(fbelow));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Math.round(above): 1\n", + "Math.round(below): 0\n", + "Math.round(fabove): 1\n", + "Math.round(fbelow): 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 `round()` 方法是 `java.lang` 的一部分,所以我们无需通过 `import` 就可以使用。\n", + "\n", + "\n", + "### 类型提升\n", + "\n", + "你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。\n", + "\n", + "\n", + "## Java没有sizeof\n", + "\n", + "在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。\n", + "\n", + "Java 不需要 ` sizeof()` 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。\n", + "\n", + "\n", + "## 运算符总结\n", + "\n", + "上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 `//` 过滤了。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/AllOps.java\n", + "// 测试所有基本类型的运算符操作\n", + "// 看看哪些是能被 Java 编译器接受的\n", + "public class AllOps {\n", + " // 布尔值的接收测试:\n", + " void f(boolean b) {}\n", + " void boolTest(boolean x, boolean y) {\n", + " // 算数运算符:\n", + " //- x = x * y;\n", + " //- x = x / y;\n", + " //- x = x % y;\n", + " //- x = x + y;\n", + " //- x = x - y;\n", + " //- x++;\n", + " //- x--;\n", + " //- x = +y;\n", + " //- x = -y;\n", + " // 关系运算符和逻辑运算符:\n", + " //- f(x > y);\n", + " //- f(x >= y);\n", + " //- f(x < y);\n", + " //- f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " f(!y);\n", + " x = x && y;\n", + " x = x || y;\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " //- x += y;\n", + " //- x -= y;\n", + " //- x *= y;\n", + " //- x /= y;\n", + " //- x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- char c = (char)x;\n", + " //- byte b = (byte)x;\n", + " //- short s = (short)x;\n", + " //- int i = (int)x;\n", + " //- long l = (long)x;\n", + " //- float f = (float)x;\n", + " //- double d = (double)x;\n", + " }\n", + "\n", + " void charTest(char x, char y) {\n", + " // 算数运算符:\n", + " x = (char)(x * y);\n", + " x = (char)(x / y);\n", + " x = (char)(x % y);\n", + " x = (char)(x + y);\n", + " x = (char)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (char) + y;\n", + " x = (char) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x= (char)~y;\n", + " x = (char)(x & y);\n", + " x = (char)(x | y);\n", + " x = (char)(x ^ y);\n", + " x = (char)(x << 1);\n", + " x = (char)(x >> 1);\n", + " x = (char)(x >>> 1);\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换\n", + " //- boolean bl = (boolean)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void byteTest(byte x, byte y) {\n", + " // 算数运算符:\n", + " x = (byte)(x* y);\n", + " x = (byte)(x / y);\n", + " x = (byte)(x % y);\n", + " x = (byte)(x + y);\n", + " x = (byte)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (byte) + y;\n", + " x = (byte) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " //按位运算符:\n", + " x = (byte)~y;\n", + " x = (byte)(x & y);\n", + " x = (byte)(x | y);\n", + " x = (byte)(x ^ y);\n", + " x = (byte)(x << 1);\n", + " x = (byte)(x >> 1);\n", + " x = (byte)(x >>> 1);\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void shortTest(short x, short y) {\n", + " // 算术运算符:\n", + " x = (short)(x * y);\n", + " x = (short)(x / y);\n", + " x = (short)(x % y);\n", + " x = (short)(x + y);\n", + " x = (short)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (short) + y;\n", + " x = (short) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = (short) ~ y;\n", + " x = (short)(x & y);\n", + " x = (short)(x | y);\n", + " x = (short)(x ^ y);\n", + " x = (short)(x << 1);\n", + " x = (short)(x >> 1);\n", + " x = (short)(x >>> 1);\n", + " // Compound assignment:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void intTest(int x, int y) {\n", + " // 算术运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " x = x << 1;\n", + " x = x >> 1;\n", + " x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void longTest(long x, long y) {\n", + " // 算数运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " x = x << 1;\n", + " x = x >> 1;\n", + " x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void floatTest(float x, float y) {\n", + " // 算数运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " //- x = x & y;\n", + " //- x = x | y;\n", + " //- x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " //- x &= y;\n", + " //- x ^= y;\n", + " //- x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void doubleTest(double x, double y) {\n", + " // 算术运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " //- x = x & y;\n", + " //- x = x | y;\n", + " //- x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " //- x &= y;\n", + " //- x ^= y;\n", + " //- x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意** :**boolean** 类型的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。\n", + "\n", + "在 **char**,**byte** 和 **short** 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 **int** 类型的运算则不用转换,因为默认就是 **int** 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 **int** 型的范围,这种情况下结果会发生溢出。下面的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Overflow.java\n", + "// 厉害了!内存溢出\n", + "public class Overflow {\n", + " public static void main(String[] args) {\n", + " int big = Integer.MAX_VALUE;\n", + " System.out.println(\"big = \" + big);\n", + " int bigger = big * 4;\n", + " System.out.println(\"bigger = \" + bigger);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "text" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "big = 2147483647\n", + "bigger = -4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。\n", + "\n", + "对于 **char**,**byte** 或者 **short**,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 **boolean** 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "如果你已接触过一门 C 语法风格编程语言,那么你在学习 Java 的运算符时实际上没有任何曲线。如果你觉得有难度,那么我推荐你要先去 www.OnJava8.com 观看 《Thinking in C》 的视频教程来补充一些前置知识储备。\n", + "\n", + "[^1]: 我在 *Pomona College* 大学读过两年本科,在那里 47 被称之为“魔法数字”(*magic number*),详见 [维基百科](https://en.wikipedia.org/wiki/47_(number)) 。\n", + "\n", + "[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/00-Introduction.ipynb b/jupyter/00-Introduction.ipynb new file mode 100644 index 00000000..01651402 --- /dev/null +++ b/jupyter/00-Introduction.ipynb @@ -0,0 +1,111 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 简介\n", + "\n", + "> “我的语言极限,即是我的世界的极限。” ——路德维希·维特根斯坦(*Wittgenstein*)\n", + "\n", + "这句话无论对于自然语言还是编程语言来说都是一样的。你所使用的编程语言会将你的思维模式固化并逐渐远离其他语言,而且往往发生在潜移默化中。Java 作为一门傲娇的语言尤其如此。\n", + "\n", + "Java 是一门派生语言,早期语言设计者为了不想在项目中使用 C++ 而创造了这种看起来很像 C++,却比 C++ 有了改进的新语言(原始的项目并未成功)。Java 最核心的变化就是加入了“虚拟机”和“垃圾回收机制”,这两个概念在之后的章节会有详细描述。 此外,Java 还在其他方面推动了行业发展。例如,现在绝大多数编程语言都支持文档注释语法和 HTML 文档生成工具。\n", + "\n", + "Java 最主要的概念之一“对象”来自 SmallTalk 语言。SmallTalk 语言恪守“对象”(在下一章中描述)是编程的最基本单元。于是,万物皆对象。历经时间的检验,人们发现这种信念太过狂热。有些人甚至认为“对象”的概念是完全错误的,应该舍弃。就我个人而言,把一切事物都抽象成对象不仅是一项不必要的负担,同时还会招致许多设计朝着不好的方向发展。尽管如此,“对象”的概念依然有其闪光点。固执地要求所有东西都是一个对象(特别是一直到最底层级别)是一种设计错误;相反,完全逃避“对象”的概念似乎同样太过苛刻。\n", + "\n", + "Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,我将尝试解释这些原因,力争让读者知晓这些功能,并明白为什么这些功能最终并不适用。这无关 Java 是一种好语言或者坏语言,一旦你了解了该语言的缺陷和局限性,你就能够:\n", + "\n", + "1. 明白有些功能特性为什么会被“废弃”。\n", + "\n", + "2. 熟悉语言边界,更好地设计和编码。\n", + "\n", + "编程的过程就是复杂性管理的过程:业务问题的复杂性,以及依赖的计算机的复杂性。由于这种复杂性,我们的大多数软件项目都失败了。\n", + "\n", + "许多语言设计决策时都考虑到了复杂性,并试图降低语言的复杂性,但在设计过程中遇到了一些更棘手的问题,最终导致语言设计不可避免地“碰壁”,复杂性增加。例如,C++ 必须向后兼容 C(允许 C 程序员轻松迁移),并且效率很高。这些目标非常实用,并且也是 C++ 在编程界取得了成功的原因之一,但同时也引入了额外的复杂性,导致某些用 C++ 编写的项目开发失败。当然,你可以责怪程序员和管理人员手艺不精,但如果有一种编程语言可以帮助你在开发过程中发现错误,那岂不是更好?\n", + "\n", + "虽然 VB(Visual BASIC)绑定在 BASIC 上,但 BASIC 实际上并不是一种可扩展的语言。大量扩展的堆积造成 VB 的语法难以维护。Perl 向后兼容 awk、sed、grep 以及其它要替换的 Unix 工具。因此它常常被诟病产生了一堆“只写代码”(*write-only code*,写代码的人自己都看不懂的代码)。另一方面,C ++,VB,Perl 和其他语言(如 SmallTalk)在设计时重点放在了对某些复杂问题的处理上,因而在解决这些特定类型的问题方面非常成功。\n", + "\n", + "通信革命使我们相互沟通更加便利。无论是一对一沟通,还是团队里的互相沟通,甚至是地球上不同地区的沟通。据说下一次革命需要的是一种全球性的思维,这种思维源于足量的人以及足量相互连接。我不知道 Java 是否能成为这场革命的工具之一,但至少这种可能性让我觉得:我现在正在做的传道授业的事情是有意义的!\n", + "\n", + "## 前提条件\n", + "\n", + "阅读本书需要读者对编程有基本的了解:\n", + "\n", + "- 程序是一系列“陈述(语句、代码)”构成\n", + "\n", + "- 子程序、方法、宏的概念\n", + "\n", + "- 控制语句(例如 **if**),循环结构(例如 **while**)\n", + "\n", + "可能你已在学校、书籍或网络上了学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。\n", + "\n", + "你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面向对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。\n", + "\n", + "## JDK文档\n", + "\n", + "甲骨文公司已经提供了免费的标准 JDK 文档。除非有必要,否则本书中将不再赘述 API 相关的使用细节。使用浏览器来即时搜索最新最全的 JDK 文档好过翻阅本书来查找。只有在需要补充特定的示例时,我才会提供有关的额外描述。\n", + "\n", + "## C编程思想\n", + "\n", + "*Thinking in C* 已经可以在 [www.OnJava8.com](https://archive.org/details/ThinkingInC) 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。\n", + "\n", + "## 源码下载\n", + "\n", + "本书中所有源代码的示例都在版权保护的前提下通过 GitHub 免费提供。你可以将这些代码用于教育。任何人不得在未经正确引用代码来源的情况下随意重新发布此代码示例。在每个代码文件中,你都可以找到以下版权声明文件作为参考:\n", + "\n", + "**Copyright.txt**\n", + "\n", + "©2017 MindView LLC。版权所有。如果上述版权声明,本段和以下内容,特此授予免费使用,复制,修改和分发此计算机源代码(源代码)及其文档的许可,且无需出于下述目的的书面协议所有副本中都有五个编号的段落。\n", + "\n", + "1. 允许编译源代码并将编译代码仅以可执行格式包含在个人和商业软件程序中。\n", + "\n", + "2. 允许在课堂情况下使用源代码而不修改源代码,包括在演示材料中,前提是 “On Java 8” 一书被引用为原点。\n", + "\n", + "3. 可以通过以下方式获得将源代码合并到印刷媒体中的许可:MindView LLC,PO Box 969,Crested Butte,CO 81224 MindViewInc@gmail.com \n", + "\n", + "4. 源代码和文档的版权归 MindView LLC 所有。提供的源代码没有任何明示或暗示的担保,包括任何适销性,适用于特定用途或不侵权的默示担保。MindView LLC 不保证任何包含源代码的程序的运行不会中断或没有错误。MindView LLC 不对任何目的的源代码或包含源代码的任何软件的适用性做出任何陈述。包含源代码的任何程序的质量和性能的全部风险来自源代码的用户。用户理解源代码是为研究和教学目的而开发的,建议不要仅仅因任何原因依赖源代码或任何包含源代码的程序。如果源代码或任何产生的软件证明有缺陷,则用户承担所有必要的维修,修理或更正的费用。\n", + "\n", + "5. 在任何情况下,MINDVIEW LLC 或其出版商均不对任何一方根据任何法律理论对直接,间接,特殊,偶发或后果性损害承担任何责任,包括利润损失,业务中断,商业信息丢失或任何其他保险公司。由于 MINDVIEW LLC 或其出版商已被告知此类损害的可能性,因此使用本源代码及其文档或因无法使用任何结果程序而导致的个人受伤或者个人受伤。MINDVIEW LLC 特别声明不提供任何担保,包括但不限于对适销性和特定用途适用性的暗示担保。此处提供的源代码和文档基于“原样”基础,没有MINDVIEW LLC的任何随附服务,MINDVIEW LLC 没有义务提供维护,支持,更新,增强或修改。\n", + "\n", + "**请注意**,MindView LLC 仅提供以下唯一网址发布更新书中的代码示例,https://github.com/BruceEckel/OnJava8-examples 。你可在上述条款范围内将示例免费使用于项目和课堂中。\n", + "\n", + "如果你在源代码中发现错误,请在下面的网址提交更正:https://github.com/BruceEckel/OnJava8-examples/issues \n", + "\n", + "## 编码样式\n", + "\n", + "本书中代码标识符(关键字,方法,变量和类名)以粗体,固定宽度代码字体显示。像 “*class” 这种在代码中高频率出现的关键字可能让你觉得粗体有点乏味。(译者注:由于中英排版差异,中文翻译过程并未完全参照原作者的说明。具体排版格式请参考[此处](https://github.com/ruanyf/document-style-guide))其他显示为正常字体。本书文本格式尽可能遵循 Oracle 常见样式,并保证在大多数 Java 开发环境中被支持。书中我使用了自己喜欢的字体风格。Java 是一种自由的编程语言,你也可以使用 IDE(集成开发环境)工具(如 IntelliJ IDEA,Eclipse 或 NetBeans)将格式更改为适合你的格式。\n", + "\n", + "本书代码文件使用自动化工具进行测试,并在最新版本的 Java 编译通过(除了那些特别标记的错误之外)。本书重点介绍并使用 Java 8 进行测试。如果你必须了解更早的语言版本,可以在 [www.OnJava8.com](http://www.OnJava8.com) 免费下载 《Thinking in Java》。\n", + "\n", + "## BUG提交\n", + "\n", + "本书经过多重校订,但还是难免有所遗漏被新读者发现。如果你在正文或示例中发现任何错误的内容,请在[此处](https://github.com/BruceEckel/OnJava8-examples/issues)提交错误以及建议更正,作者感激不尽。\n", + "\n", + "## 邮箱订阅\n", + "\n", + "你可以在 [www.OnJava8.com上](http://www.OnJava8.com) 订阅邮件。邮件不含广告并尽量提供干货。\n", + "\n", + "## Java图形界面\n", + "\n", + "Java 在图形用户界面和桌面程序方面的发展可以说是一段悲伤的历史。Java 1.0 中图形用户界面(GUI)库的原始设计目标是让用户能在所有平台提供一个漂亮的界面。但遗憾的是,这个理想没有实现。相反,Java 1.0 AWT(抽象窗口工具包)在所有平台都表现平平,并且有诸多限制。你只能使用四种字体。另外,Java 1.0 AWT 编程模型也很笨拙且非面向对象。我的一个曾在 Java 设计期间工作过的学生道出了缘由:早期的 AWT 设计是在仅仅在一个月内构思、设计和实施的。不得不说这是一个“奇迹”,但同时更是“设计失败”的绝佳教材。\n", + "\n", + "在 Java 1.1 版本的 AWT 中 情况有所改善,事件模型带来更加清晰的面向对象方法,并添加了JavaBeans,致力于面向易于创建可视化编程环境的组件编程模型(已废弃)。\n", + "\n", + "Java 2(Java 1.2)通过使用 Java 基类(JFC)内容替换来完成从旧版 Java 1.0 AWT 的转换。其中 GUI 部分称为 Swing。这是一组丰富的 JavaBeans,它们创建了一个合理的 GUI。修订版 3(3之前都不好)比以往更适用于开发图形界面程序。\n", + "\n", + "Sun 在图形界面的最后一次尝试,称为 JavaFX。当 Oracle 收购 Sun 时,他们将原来雄心勃勃的项目(包括脚本语言)改为库,现在它似乎是 Java 官方唯一还在开发中的 UI 工具包(参见维基百科关于 JavaFX 的文章) - 但即使如此,JavaFX 最终似乎也失败了。\n", + "\n", + "现今 Swing 依然是 Java 发行版的一部分(只接受维护,不再有新功能开发)。而 Java 现在是一个开源项目,它应该始终可用。此外,Swing 和 JavaFX 有一些有限的交互性。这些可能是为了帮助开发者过渡到 JavaFX。\n", + "\n", + "桌面程序领域似乎从未尝勾起 Java 设计师的野心。Java 没有在图形界面取得该有的一席之地。另外,曾被大肆吹嘘的 JavaBeans 也没有获得任何影响力。(许多不幸的作者花了很多精力在 Swing 上编写书籍,甚至只用 JavaBeans 编写书籍)。Java 图形界面程序大多数情况下仅用于 IDE(集成开发环境)和一些企业内部应用程序。你可以采用 Java 开发图形界面,但这并非 Java 最擅长的领域。如果你必须学习 Swing,可以参考 *Thinking in Java* 第4版(可从 www.OnJava8.com 获得)或者通过其他专门的书籍学习。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/00-On-Java-8.ipynb b/jupyter/00-On-Java-8.ipynb new file mode 100644 index 00000000..c16c3177 --- /dev/null +++ b/jupyter/00-On-Java-8.ipynb @@ -0,0 +1,76 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " \"cover\"\n", + "
\n", + "\n", + "
\n", + "\n", + "
\n", + "

On Java 8

\n", + "
\n", + "\n", + "
\n", + "

Bruce Eckel

\n", + "\n", + "
\n", + "\n", + "
MindView LLC
\n", + "\n", + "\n", + "
2017
\n", + "\n", + "\n", + "
©MindView LLC 版权所有
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + "\n", + "\n", + "\n", + "# On Java 8\n", + "\n", + "\n", + "\n", + "**版权©2017**\n", + "\n", + "\n", + "**作者 Bruce Eckel, President, MindView LLC.**\n", + "\n", + "\n", + "**版本号:7**\n", + "\n", + "\n", + "**ISBN 978-0-9818725-2-0**\n", + "\n", + "\n", + "**原书可在该网站购买 [www.OnJava8.com](http://www.OnJava8.com)** \n", + "\n", + "\n", + "\n", + "\n", + "本书出版自美国,版权所有,翻版必究。未经授权不得非法存储在检索系统中,或以电子,机械,影印,录制任何形式传输等。制造商和销售商使用商标用来区分其产品标识。如果这些名称出现在这本书中,并且出版商知道商标要求,则这些名称已经用大写字母或所有大写字母打印。\n", + "\n", + "Java 是甲骨文公司(Oracle. Inc.)的商标。Windows 95,Windows NT,Windows 2000,Windows XP,Windows 7,Windows 8 和 Windows 10 是微软公司(Microsoft Corporation)的商标。\n", + "此处提及的所有其他产品名称和公司名称均为其各自所有者的财产。作者和出版商在编写本书时已经仔细校对过,但不作任何明示或暗示的保证,对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损失,我们不承担任何责任。\n", + "\n", + "这本书是以平板电脑和计算机为载体的电子书,非传统纸质版书籍。 \n", + "故所有布局和格式设计旨在优化您在各种电子书阅读平台和系统上的观看体验。\n", + "封面由 Daniel Will-Harris 设计,[www.Will-Harris.com](http://www.Will-Harris.com)。\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/00-Preface.ipynb b/jupyter/00-Preface.ipynb new file mode 100644 index 00000000..e97ab2c8 --- /dev/null +++ b/jupyter/00-Preface.ipynb @@ -0,0 +1,112 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 前言\n", + "\n", + "> 本书基于 Java 8 版本来教授当前 Java 编程的最优实践。\n", + "\n", + "此前,我的另一本 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想》 第 4 版 Prentice Hall 2006)依然适用于 Java 5 编程。Android 编程就是始于此语言版本。\n", + "\n", + "随着 Java 8 的出现,这门语言在许多地方发生了翻天覆地的变化。在新的版本中,代码的运用和实现上与以往不尽相同。这也促使了我时隔多年后再次创作了这本新书。《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站上补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。\n", + "\n", + "与几年前我们依赖印刷媒体相比,YouTube,博客和 StackOverflow 等网站的出现让寻找答案变得简单。请结合这些学习途径和努力坚持下去。本书可作为编程入门书籍,同时也适用于想要扩展知识的在职程序员。每次在世界各地的演讲中,我都非常感谢 《*Thinking in Java*》 这本书给我带来的所有荣誉。它对于我重塑 [Reinventing Business](http://www.reinventing-business.com) 项目和促进交流是非常宝贵的。最后,写这本书的原因之一 希望这本书可以为我的这个项目众筹。似乎下一步要创建一个所谓的蓝绿色组织(Teal Organization)才合乎逻辑的。\n", + "\n", + "## 教学目标\n", + "\n", + "每章教授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。\n", + "\n", + "本书的教学目标:\n", + "\n", + "1. 循序渐进地呈现学习内容,以便于你在不依赖后置知识框架的情况下轻松完成现有的学习任务,同时尽量保证前面章节的内容在后面的学习中得到运用。如果确有必要引入我们还没学习到的知识概念,我会做个简短地介绍。\n", + "\n", + "2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意看到我的读者们因此能保持饶有兴趣地学习。\n", + "\n", + "3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。\n", + "\n", + "4. 希望本书能为你打下坚实的基础,方便你将来学习更难的课程和书籍。\n", + "\n", + "## 语言设计错误\n", + "\n", + "每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错在哪里还要糟糕。唉,每一种语言/库的设计错误都会永久地嵌入在 Java 的发行版中。\n", + "\n", + "诺贝尔经济学奖得主约瑟夫·斯蒂格利茨(*Joseph Stiglitz*)有一套适用于这里的人生哲学,叫做“承诺升级理论”:继续犯错误的成本由别人承担,而承认错误的成本由自己承担。\n", + "\n", + "看过我此前作品的读者们应该清楚,我一般倾向于指出这些错误。Java 拥有一批狂热的粉丝。他们把语言当成是阵营而不是纯粹的编程工具。我写过 Java 书籍,所以他们兀自认为我自然也是这个“阵营”的一份子。当我指出 Java 的这些错误时,会造成两种影响:\n", + "\n", + "1. 早先许多错误“阵营”的人成为了牺牲品。最终,时隔多年后,大家都意识到这是个设计上的错误。然而错误已然成为 Java 历史的一部分了。\n", + "\n", + "2. 更重要的是,新手程序员并没有经历过“语言为何采用某种方式实现”的争议过程。特别是那些隐约察觉不对却依然说服自己“我必须要这么做”或“我只是没学明白”从而继续错下去的人。更糟糕的是,教授这些编程知识的老师们没能深入地去研究这里是否有设计上的错误,而是继续错误的解读。总之,通过了解语言设计上的错误,能让开发者们更好地理解和意识到错误的本质,从而更快地进步。\n", + "\n", + "对编程语言的设计错误理解至关重要,甚至影响程序员的开发效率。部分公司在开发过程中避免使用语言的某些功能特性。这些功能特性表面上看起来高大上,但是弄不好却可能出现意料之外的错误,影响整个开发进程。\n", + "\n", + "已知的语言设计错误会给新的一门编程语言的作者提供参考。探索一门语言能做什么是很有趣的一件事,而语言设计错误能提醒你哪些“坑”是不能再趟的。多年以来,我一直感觉 Java 的设计者们有点脱离群众。Java 的有些设计错误错的太明显,我甚至怀疑设计者们到底是为出于服务用户还是其他动机设计了这些功能。Java 语言有许多臭名昭著的设计错误,很可能这也是诱惑所在。Java 似乎并不尊重开发者。为此我很长时间内不想与 Java 有任何瓜葛。很大程度上,这也是我不想碰 Java 的原因吧。\n", + "\n", + "如今再审视 Java 8,我发现了许多变化。设计者们对于语言和用户的态度似乎发生了根本性上的改变。忽视用户投诉多年之后,Java 的许多功能和类库都已被搞砸了。\n", + "\n", + "新功能的设计与以往有很大不同。掌舵者开始重视程序员的编程经验。新功能的开发都是在努力使语言变得更好,而非仅仅停留在快速堆砌功能而不去深入研究它们的含义。甚至有些新特性的实现方式非常优雅(至少在 Java 约束下尽可能优雅)。\n", + "\n", + "我猜测可能是部分设计者的离开让他们意识到了这点。说真的,我没想到会有这些变化!因为这些原因,写这本书的体验要比以往好很多。Java 8 包含了一系列基础和重要的改进。遗憾的是,为了严格地“向后兼容”,我们不大可能看到戏剧性的变化,当然我希望我是错的。尽管如此,我很赞赏那些敢于自我颠覆,并为 Java 设定更好路线的人。第一次,对于自己所写的部分 Java 8 代码我终于可以说“赞!”\n", + "\n", + "最后,本书所著时间似乎也还不错,因为 Java 8 引入的新功能已经强烈地影响了今后 Java 的编码方式。截止我在写这本书时,Java 9 似乎更专注于对语言底层的基础结构功能的重要更新,而非本书所关注的新编码方式。话说回来,得益于电子书出版形式的便捷,假如我发现本书有需要更新或添加的内容,我可以第一时间将新版本推送给现有读者。\n", + "\n", + "## 测试用例\n", + "\n", + "书中代码示例基于 Java 8 和 Gradle 编译构建,并且代码示例都保存在[这个自由访问的GitHub的仓库](https://github.com/BruceEckel/OnJava8-Examples) 中。我们需要内置的测试框架,以便于在每次构建系统时自动运行。否则,你将无法保证自己代码的可靠性。为了实现这一点,我创建了一个测试系统来显示和验证大多数示例的输出结果。这些输出结果我会附加在示例结尾的代码块中。有时仅显示必要的那几行或者首尾行。利用这种方式来改善读者的阅读和学习体验,同时也提供了一种验证示例正确性的方法。\n", + "\n", + "## 普及性\n", + "\n", + "Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你更容易找到工作。相关的培训材料,课程和其他可用的学习资源也很多。对于企业来说,招聘 Java 程序员相对容易。如果你不喜欢 Java 语言,那么最好不要拿他当作你谋生的工具,因为这种生活体验并不好。作为一家公司,在技术选型前一定不要单单只考虑 Java 程序员好招。每种语言都有其适用的范围,有可能你们的业务更适用于另一种编程语言来达到事半功倍的效果。如果你真的喜欢 Java,那么欢迎你。希望这本书能丰富你的编程经验!\n", + "\n", + "## 关于安卓\n", + "\n", + "本书基于 Java 8 版本。如果你是 Andriod 程序员,请务必学习 Java 5。在《On Java 8》出版的时候,我的另一本基于 Java 5 的著作 *Thinking in Java 4th Edition*(《Java编程思想》第四版)已经可以在[www.OnJava8.com](http://www.OnJava8.com)上免费下载了。此外,还有许多其他专用于 Andriod 编程的资源。\n", + "\n", + "## 电子版权声明\n", + "\n", + "《On Java 8》仅提供电子版,并且仅通过 [www.OnJava8.com](http://www.OnJava8.com) 提供。任何未经 授权的其他来源或流传送机构都是非法的。本作品受版权保护!未经许可,请勿通过以任何方式分享或发布。你可以使用这些示例进行教学,只要不对本书非法重新出版。有关完整详细信息,请参阅示例分发中的 Copyright.txt 文件。对于视觉障碍者,电子版本有可搜索性,字体大小调整或文本到语音等诸多好处。\n", + "\n", + "任何购买这本书的读者,还需要一台计算机来运行和写作代码。另外电子版在计算机上和移动设备上的显示效果俱佳,推荐使用平板设备阅读。相比购买传统纸质版的价格,平板电脑价格都足够便宜。在床上阅读电子版比看这样一本厚厚的实体书要方便得多。起初你可能会有些不习惯,但我相信很快你就会发现它带来的优点远胜过不适。我已经走过这个阶段,Google Play 图书的浏览器阅读体验非常好,包括在 Linux 和 iOS 设备上。作为一次尝试,我决定尝试通过 Google 图书进行出版。\n", + "\n", + "**注意**:在撰写本文时,通过 Google Play 图书网络浏览器应用阅读图书虽然可以忍受,但体验还是有点差强人意,我强烈推荐读者们使用平板电脑来阅读。\n", + "\n", + "## 版本说明\n", + "\n", + "本书采用 [Pandoc](http://pandoc.org) 风格的 Markdown 编写,使用 Pandoc 生成 ePub v3 格式。\n", + "\n", + "正文字体为 Georgia,标题字体为 Verdana。 代码字体使用的 Ubuntu Mono,因为它特别紧凑,单行能容纳更多的代码。 我选择将代码内联(而不是将列表放入图像,参照其他书籍),因为我觉得这个功能很重要:让代码块能适应字体大小得改变而改变(否则,买电子版,还图什么呢?)。\n", + "\n", + "书中的提取,编译和测试代码示例的构建过程都是自动化的。所有自动化操作都是通过我在 Python 3 中编写的程序来实现的。\n", + "\n", + "## 封面设计\n", + "\n", + "《On Java 8》的封面是根据 W.P.A.(Works Progress Administration 1935年至1943年美国大萧条期间的一个巨大项目,它使数百万失业人员重新就业)的马赛克创作的。它还让我想起了《绿野仙踪》(*The Wizard of Oz*)系列丛书中的插图。 我的好朋友、设计师丹 *Daniel Will-Harris*([www.will-harris.com](http://www.will-harris.com))和我都喜欢这个形象。\n", + "\n", + "## 感谢的人\n", + "\n", + "感谢 *Domain-Driven Design*(《领域驱动设计》 )的作者 *Eric Evans* 建议书名,以及其他新闻组校对的帮助。\n", + "\n", + "感谢 *James Ward* 为我开始使用 Gradle 工具构建这本书,以及他多年来的帮助和友谊。\n", + "\n", + "感谢 *Ben Muschko* 在整理构建文件方面的工作,还有感谢 *Hans Dockter* 给 *Ben* 提供了时间。\n", + "\n", + "感谢 *Jeremy Cerise* 和 *Bill Frasure* 来到开发商务聚会预订,并随后提供了宝贵的帮助。\n", + "\n", + "感谢所有花时间和精力来科罗拉多州克雷斯特德比特(Crested Butte, Colorado)镇参加我的研讨会,开发商务聚会和其他活动的人!你们的贡献可能不容易看到,但却非常重要!\n", + "\n", + "## 献礼\n", + "\n", + "> 谨以此书献给我敬爱的父亲 E. Wayne Eckel。\n", + "> 1924年4月1日至2016年11月23日\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/01-What-is-an-Object.ipynb b/jupyter/01-What-is-an-Object.ipynb new file mode 100644 index 00000000..252581ee --- /dev/null +++ b/jupyter/01-What-is-an-Object.ipynb @@ -0,0 +1,422 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 第一章 对象的概念\n", + "\n", + "> “我们没有意识到惯用语言的结构有多大的力量。可以毫不夸张地说,它通过语义反应机制奴役我们。语言表现出来并在无意识中给我们留下深刻印象的结构会自动投射到我们周围的世界。” -- Alfred Korzybski (1930)\n", + "\n", + "计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。\n", + "\n", + "面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构。本章讲述 OOP 的基本概述。如果读者对此不太理解,可先行跳过本章。等你具备一定编程基础后,请务必再回头看。只有这样你才能深刻理解面向对象编程的重要性及设计方式。\n", + "\n", + "## 抽象\n", + "\n", + "所有编程语言都提供抽象机制。从某种程度上来说,问题的复杂度直接取决于抽象的类型和质量。这里的“类型”意思是:抽象的内容是什么?汇编语言是对底层机器的轻微抽象。接着出现的“命令式”语言(如 FORTRAN,BASIC 和 C)是对汇编语言的抽象。与汇编相比,这类语言已有了长足的改进,但它们的抽象原理依然要求我们着重考虑计算机的结构,而非问题本身的结构。\n", + "\n", + "程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一个副产业“编程方法”学科。\n", + "\n", + "为机器建模的另一个方法是为要解决的问题制作模型。对一些早期语言来说,如 LISP 和 APL,它们的做法是“从不同的角度观察世界”——“所有问题都归纳为列表”或“所有问题都归纳为算法”。PROLOG 则将所有\n", + "问题都归纳为决策链。对于这些语言,我们认为它们一部分是“基于约束”的编程,另一部分则是专为\n", + "处理图形符号设计的(后者被证明限制性太强)。每种方法都有自己特殊的用途,适合解决某一类的问题。只要超出了它们力所能及的范围,就会显得非常笨拙。\n", + "\n", + "面向对象的程序设计在此基础上跨出了一大步,程序员可利用一些工具表达“问题空间”内的元素。由于这种表达非常具有普遍性,所以不必受限于特定类型的问题。我们将问题空间中的元素以及它们在解决方案空间的表示称作“对象”(**Object**)。当然,还有一些在问题空间没有对应的对象体。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。所以当你在阅读描述解决方案的代码时,也是在阅读问题的表述。与我们以前见过的相比,这无疑是一种更加灵活、更加强大的语言抽象方法。总之,OOP 允许我们根据问题来描述问题,而不是根据运行解决方案的计算机。然而,它仍然与计算机有联系,每个对象都类似一台小计算机:它们有自己的状态并且可以进行特定的操作。这与现实世界的“对象”或者“物体”相似:它们都有自己的特征和行为。\n", + "\n", + "Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:\n", + "\n", + "> 1. **万物皆对象**。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。\n", + "> 2. **程序是一组对象,通过消息传递来告知彼此该做什么**。要请求调用一个对象的方法,你需要向该对象发送消息。\n", + "> 3. **每个对象都有自己的存储空间,可容纳其他对象**。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。\n", + "> 4. **每个对象都有一种类型**。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。\n", + "> 5. **同一类所有对象都能接收相同的消息**。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收发送给\"形状”的消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。\n", + "\n", + "*Grady Booch* 提供了对对象更简洁的描述:一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。\n", + "\n", + "## 接口\n", + "\n", + "亚里士多德(*Aristotle*)大概是第一个认真研究“类型”的哲学家,他曾提出过“鱼类和鸟类”这样的概念。所有对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。这种思想被首次应用于第一个面向对象编程语言 Simula-67,它在程序中使用基本关键字 **class** 来引入新的类型(class 和 type 通常可互换使用,有些人对它们进行了进一步区分,他们强调 type 决定了接口,而 class 是那个接口的一种特殊实现方式)。\n", + "\n", + "Simula 是一个很好的例子。正如这个名字所暗示的,它的作用是“模拟”(Simulate)类似“银行出纳员”这样的经典问题。在这个例子里,我们有一系列出纳员、客户、帐号、交易和货币单位等许多\"对象”。每类成员(元素)都具有一些通用的特征:每个帐号都有一定的余额;每名出纳都能接收客户的存款;等等。与此同时,每个成员都有自己的状态;每个帐号都有不同的余额;每名出纳都有一个名字。所以在计算机程序中,能用独一无二的实体分别表示出纳员、客户、帐号以及交易。这个实体便是“对象”,而且每个对象都隶属一个特定的“类”,那个类具有自己的通用特征与行为。\n", + "\n", + "因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。\n", + "\n", + "创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。\n", + "\n", + "那么如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。\n", + "\n", + "下面让我们以电灯泡为例:\n", + "\n", + "![reader](../images/reader.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Light lt = new Light();\n", + "lt.on();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。\n", + "\n", + "为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。\n", + "\n", + "上图遵循 **UML**(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 **UML** 设计图中显示,因此中间部分未显示,如本例所示。如果你只对类名感兴趣,则也不需要显示方法信息。\n", + "\n", + "## 服务提供\n", + "\n", + "在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。你的程序本身将为用户提供服务,并且它能通过调用其他对象提供的服务来实现这一点。我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。\n", + "\n", + "那么问题来了:我们该选择哪个对象来解决问题呢?例如,你正在开发一个记事本程序。*你可能会想到在屏幕输入默认的记事本对象*,一个用于检测不同类型打印机并执行打印的对象。这些对象中的某些已经有了。那对于还没有的对象,我们该设计成啥样呢?这些对象需要提供哪些服务,以及还需要调用其他哪些对象?\n", + "\n", + "我们可以将这些问题一一分解,抽象成一组服务。软件设计的基本原则是高内聚:每个组件的内部作用明确,功能紧密相关。然而经常有人将太多功能塞进一个对象中。例如:在支票打印模块中,你需要设计一个可以同时读取文本格式又能正确识别不同打印机型号的对象。正确的做法是提供三个或更多对象:一个对象检查所有排版布局的目录;一个或一组可以识别不同打印机型号的对象展示通用的打印界面;第三个对象组合上述两个服务来完成任务。这样,每个对象都提供了一组紧密的服务。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。\n", + "\n", + "## 封装\n", + "\n", + "我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。当应用程序员调用研发程序员开发的工具类时,双方建立了关系。应用程序员通过使用现成的工具类组装应用程序或者构建更大的工具库。如果工具类的创建者将类的内部所有信息都公开给调用者,那么有些使用规则就不容易被遵守。因为前者无法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种情况的发生。\n", + "\n", + "因此,使用访问控制的原因有以下两点:\n", + "\n", + "1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);\n", + "\n", + "2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造。\n", + "\n", + "Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。\n", + "\n", + " 1. `public`(公开)表示任何人都可以访问和使用该元素;\n", + "\n", + " 2. `private`(私有)除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;\n", + "\n", + " 3. `protected`(受保护)类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;\n", + "\n", + " 4. `default`(默认)如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。\n", + "\n", + "## 复用\n", + "\n", + "一个类经创建和测试后,理应是可复用的。然而很多时候,由于程序员没有足够的编程经验和远见,我们的代码复用性并不强。\n", + "\n", + "代码和设计方案的复用性是面向对象程序设计的优点之一。我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。这里涉及到“组合”和“聚合”的概念:\n", + "\n", + "* **组合**(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。\n", + "\n", + "* **聚合**(Aggregation)动态的**组合**。\n", + "\n", + "![UML-example](../images/1545758268350.png)\n", + "\n", + "上图中实心三角形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心三角形。\n", + "\n", + "(**译者注**:组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义。至于是聚合还是组合,需要根据实际的业务需求来判断。可能相同超类和子类,在不同的业务场景,关联关系会发生变化。只看代码是无法区分聚合和组合的,具体是哪一种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。)\n", + "\n", + "使用“组合”关系给我们的程序带来极大的灵活性。通常新建的类中,成员对象会使用 `private` 访问权限,这样应用程序员则无法对其直接访问。我们就可以在不影响客户代码的前提下,从容地修改那些成员。我们也可以在“运行时\"改变成员对象从而动态地改变程序的行为,这进一步增大了灵活性。下面一节要讲到的“继承”并不具备这种灵活性,因为编译器对通过继承创建的类进行了限制。\n", + "\n", + "在面向对象编程中经常重点强调“继承”。在新手程序员的印象里,或许先入为主地认为“继承应当随处可见”。沿着这种思路产生的程序设计通常拙劣又复杂。相反,在创建新类时首先要考虑“组合”,因为它更简单灵活,而且设计更加清晰。等我们有一些编程经验后,一旦需要用到继承,就会明显意识到这一点。\n", + "\n", + "## 继承\n", + "\n", + "“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。\n", + "\n", + "通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。\n", + "\n", + "![Inheritance-example](../images/1545763399825.png)\n", + "\n", + "这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。\n", + "\n", + "![1545764724202](../images/1545764724202.png)\n", + "\n", + "例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。\n", + "\n", + "![1545764780795](../images/1545764780795.png)\n", + "\n", + "例如,某些形状可以翻转。有些行为可能不同,比如计算形状的面积。类型层次结构体现了形状之间的相似性和差异性。以相同的术语将解决方案转换成问题是有用的,因为你不需要在问题描述和解决方案描述之间建立许多中间模型。通过使用对象,类型层次结构成为了主要模型,因此你可以直接从真实世界中对系统的描述过渡到用代码对系统进行描述。事实上,有时候,那些善于寻找复杂解决方案的人会被面向对象设计的简单性难倒。从现有类型继承创建新类型。这种新类型不仅包含现有类型的所有成员(尽管私有成员被隐藏起来并且不可访问),而且更重要的是它复制了基类的接口。也就是说,基类对象接收的所有消息也能被派生类对象接收。根据类接收的消息,我们知道类的类型,因此派生类与基类是相同的类型。\n", + "\n", + "在前面的例子中,“圆是形状”。这种通过继承的类型等价性是理解面向对象编程含义的基本门槛之一。因为基类和派生类都具有相同的基本接口,所以伴随此接口的必定有某些具体实现。也就是说,当对象接收到特定消息时,必须有可执行代码。如果继承一个类而不做其他任何事,则来自基类接口的方法直接进入派生类。这意味着派生类和基类不仅具有相同的类型,而且具有相同的行为,这么做没什么特别意义。\n", + "\n", + "有两种方法可以区分新的派生类与原始的基类。第一种方法很简单:在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。然而,还是要仔细考虑是否在基类中也要有这些额外的方法。这种设计的发现与迭代过程在面向对象程序设计中会经常发生。\n", + "\n", + "尽管继承有时意味着你要在接口中添加新方法(尤其是在以 **extends** 关键字表示继承的 Java 中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。\n", + "\n", + "### \"是一个\"与\"像是一个\"的关系\n", + "\n", + "对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作\"纯粹替代\",也经常被称作\"替代原则\"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说\"圆是一个形状\"。判断是否继承,就看在你的类之间有无这种 is-a 关系。\n", + "\n", + "有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。\n", + "\n", + "![1545764820176](../images/1545764820176.png)\n", + "\n", + "以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为\"温度控制系统\",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。\n", + "\n", + "当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。\n", + "\n", + "## 多态\n", + "\n", + "我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。在上个“形状”的例子中,“方法”(method)操纵的是通用“形状”,而不关心它们是“圆”、“正方形”、“三角形”还是某种尚未定义的形状。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。\n", + "\n", + "这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 例如,你可以通过通用的“形状”基类派生出新的“五角形”形状的子类,而不需要修改通用\"形状\"基类的方法。通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。\n", + "\n", + "这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。\n", + "\n", + "如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?举个例子,下图的 **BirdController** 对象和通用 **Bird** 对象中,**BirdController** 不知道 **Bird** 的确切类型却还能一起工作。从 **BirdController** 的角度来看,这是很方便的,因为它不需要编写特别的代码来确定 **Bird** 对象的确切类型或行为。那么,在调用 **move()** 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?\n", + "\n", + "![Bird-example](../images/1545839316314.png)\n", + "\n", + "这个问题的答案,是面向对象程序设计的妙诀:在传统意义上,编译器不能进行函数调用。由非 OOP 编译器产生的函数调用会引起所谓的**早期绑定**,这个术语你可能从未听说过,不会想过其他的函数调用方式。这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。\n", + "\n", + "通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用**后期绑定**的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。\n", + "\n", + "为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 **virtual** 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。\n", + "\n", + "为了演示多态性,我们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。\n", + "\n", + "代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void doSomething(Shape shape) {\n", + " shape.erase();\n", + " // ...\n", + " shape.draw();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此方法与任何 **Shape** 对话,因此它与所绘制和擦除的对象的具体类型无关。如果程序的其他部分使用 `doSomething()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " Circle circle = new Circle();\n", + " Triangle triangle = new Triangle();\n", + " Line line = new Line();\n", + " doSomething(circle);\n", + " doSomething(triangle);\n", + " doSomething(line);\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到无论传入的“形状”是什么,程序都正确的执行了。\n", + "\n", + "![shape-example](../images/1545841270997.png)\n", + "\n", + "这是一个非常令人惊奇的编程技巧。分析下面这行代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " doSomething(circle);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当预期接收 **Shape** 的方法被传入了 **Circle**,会发生什么。由于 **Circle** 也是一种 **Shape**,所\n", + "以 `doSomething(circle)` 能正确地执行。也就是说,`doSomething()` 能接收任意发送给 **Shape** 的消息。这是完全安全和合乎逻辑的事情。\n", + "\n", + "这种把子类当成其基类来处理的过程叫做“向上转型”(**upcasting**)。在面向对象的编程里,经常利用这种方法来给程序解耦。再看下面的 `doSomething()` 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " shape.erase();\n", + " // ...\n", + " shape.draw();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()`,你自己去做吧,注意细节。”\n", + "\n", + "尽管我们没作出任何特殊指示,程序的操作也是完全正确和恰当的。我们知道,为 Circle 调用`draw()` 时执行的代码与为一个 Square 或 Line 调用 `draw()` 时执行的代码是不同的。但在将 `draw()` 信息发给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确的操作。这非常神奇,因为当 Java 编译器为 `doSomething()` 编译代码时,它并不知道自己要操作的准确类型是什么。\n", + "\n", + "尽管我们确实可以保证最终会为 Shape 调用 `erase()` 和 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?\n", + "\n", + "发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。\n", + "\n", + "## 单继承结构\n", + "\n", + "自从 C++ 引入以来,一个 OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。\n", + "\n", + "Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。\n", + "\n", + "对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。\n", + "\n", + "另外,单继承的结构使得垃圾收集器的实现更为容易。这也是 Java 在 C++ 基础上的根本改进之一。\n", + "\n", + "由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如[异常处理](#异常处理)。同时,这也让我们的编程具有更大的灵活性。\n", + "\n", + "## 集合\n", + "\n", + "通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?\n", + "\n", + "在面向对象的设计中,问题的解决方案有些过于轻率:创建一个新类型的对象来引用、容纳其他的对象。当然,我们也可以使用多数编程语言都支持的“数组”(array)。在 Java 中“集合”(Collection)的使用率更高。(也可称之为“容器”,但“集合”这个称呼更通用。)\n", + "\n", + "“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们不用关心过程是如何实现的。\n", + "\n", + "还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。\n", + "\n", + "在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:\n", + "\n", + "1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。\n", + "\n", + "2. 不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。\n", + "\n", + "我们可以一开始使用 LinkedList 构建程序,在优化系统性能时改用 ArrayList。通过对 List 接口的抽象,我们可以很容易地将 LinkedList 改为 ArrayList。\n", + "\n", + "在 Java 5 泛型出来之前,集合中保存的是通用类型 `Object`。Java 单继承的结构意味着所有元素都基于 `Object` 类,所以在集合中可以保存任何类型的数据,易于重用。要使用这样的集合,我们先要往集合添加元素。由于 Java 5 版本前的集合只保存 `Object`,当我们往集合中添加元素时,元素便向上转型成了 `Object`,从而丢失自己原有的类型特性。这时我们再从集合中取出该元素时,元素的类型变成了 `Object`。那么我们该怎么将其转回原先具体的类型呢?这里,我们使用了强制类型转换将其转为更具体的类型,这个过程称为对象的“向下转型”。通过“向上转型”,我们知道“圆形”也是一种“形状”,这个过程是安全的。可是我们不能从“Object”看出其就是“圆形”或“形状”,所以除非我们能确定元素的具体类型信息,否则“向下转型”就是不安全的。也不能说这样的错误就是完全危险的,因为一旦我们转化了错误的类型,程序就会运行出错,抛出“运行时异常”(RuntimeException)。(后面的章节会提到) 无论如何,我们要寻找一种在取出集合元素时确定其具体类型的方法。另外,每次取出元素都要做额外的“向下转型”对程序和程序员都是一种开销。以某种方式创建集合,以确认保存元素的具体类型,减少集合元素“向下转型”的开销和可能出现的错误难道不好吗?这种解决方案就是:参数化类型机制(Parameterized Type Mechanism)。\n", + "\n", + "参数化类型机制可以使得编译器能够自动识别某个 `class` 的具体类型并正确地执行。举个例子,对集合的参数化类型机制可以让集合仅接受“形状”这种类型的元素,并以“形状”类型取出元素。Java 5 版本支持了参数化类型机制,称之为“泛型”(Generic)。泛型是 Java 5 的主要特性之一。你可以按以下方式向 ArrayList 中添加 Shape(形状):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " ArrayList shapes = new ArrayList<>();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "泛型的应用,让 Java 的许多标准库和组件都发生了改变。在本书的代码示例中,你也会经常看到泛型的身影。\n", + "\n", + "## 对象创建与生命周期\n", + "\n", + "我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。\n", + "\n", + "在简单的编程场景下,对象的清理并不是问题。我们创建对象,按需使用,最后销毁它。然而,情况往往要比这更复杂:\n", + "\n", + "假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。\n", + "\n", + "现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。\n", + "\n", + "对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。\n", + "\n", + "然而相对的,我们也牺牲了程序的灵活性。因为在编写代码时,我们必须要弄清楚对象的数量、生存时间还有类型。如果我们要用它来解决一个相当普遍的问题时(如计算机辅助设计、仓库管理或空中交通管制等),限制就太大了。\n", + "\n", + "第二种方法是在堆内存(Heap)中动态地创建对象。在这种方式下,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。\n", + "\n", + "动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。\n", + "\n", + "Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字构建该对象的动态实例。这又带来另一个问题:对象的生命周期。较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;然而如果你在堆内存创建对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。\n", + "\n", + "Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其他方面)。垃圾收集器知道对象什么时候不再被使用并且自动释放内存。结合单继承和仅可在堆中创建对象的机制,Java 的编码过程比用 C++ 要简单得多。我们所要做的决定和要克服的障碍也会少很多!\n", + "\n", + "## 异常处理\n", + "\n", + "自编程语言被发明以来,程序的错误处理一直都是个难题。因为很难设计出一个好的错误处理方案,所以许多编程语言都忽略了这个问题,把这个问题丢给了程序类库的设计者。他们提出了在许多情况下都可以工作但很容易被规避的半途而废的措施,通常只需忽略错误。多数错误处理方案的主要问题是:它们依赖程序员之间的约定俗成而不是语言层面的限制。换句话说,如果程序员赶时间或没想起来,这些方案就很容易被忘记。\n", + "\n", + "异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。这让我们的编码更简单:不用再反复检查错误了。另外,异常不像方法返回的错误值和方法设置用来表示发生错误的标志位那样可以被忽略。异常的发生是不会被忽略的,它终究会在某一时刻被处理。\n", + "\n", + "最后,“异常机制”提供了一种可靠地从错误状况中恢复的方法,使得我们可以编写出更健壮的程序。有时你只要处理好抛出的异常情况并恢复程序的运行即可,无需退出。\n", + "\n", + "Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。\n", + "\n", + "## 本章小结\n", + "\n", + "面向过程程序包含数据定义和函数调用。要找到程序的意图,你必须要在脑中建立一个模型,弄清函数调用和更底层的概念。这些程序令人困扰,因为它们的表示更多地面向计算机而不是我们要解决的问题,这就是我们在设计程序时需要中间表示的原因。OOP 在面向过程编程的基础上增加了许多新的概念,所以有人会认为使用 Java 来编程会比同等的面向过程编程要更复杂。在这里,我想给大家一个惊喜:通常按照 Java 规范编写的程序会比面向过程程序更容易被理解。\n", + "\n", + "你看到的是对象的概念,这些概念是站在“问题空间”的(而不是站在计算机角度的“解决方案空间”),以及发送消息给对象以指示该空间中的活动。面向对象编程的一个优点是:设计良好的 Java 程序代码更容易被人阅读理解。由于 Java 类库的复用性,通常程序要写的代码也会少得多。\n", + "\n", + "OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Java", + "language": "java", + "name": "java" + }, + "language_info": { + "codemirror_mode": "java", + "file_extension": ".jshell", + "mimetype": "text/x-java-source", + "name": "Java", + "pygments_lexer": "java", + "version": "14.0.1+7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb b/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb new file mode 100644 index 00000000..f9d35d37 --- /dev/null +++ b/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "# 第二章 安装Java和本书用例\n", + "\n", + "现在,我们来为这次阅读之旅做些准备吧!\n", + "\n", + "在开始学习 Java 之前,你必须要先安装好 Java 和本书的源代码示例。因为考虑到可能有“专门的初学者”从本书开始学习编程,所以我会详细地教你如何使用命令行。 如果你已经有此方面的经验了,可以跳过这段安装说明。如果你对此处描述的任何术语或过程仍不清楚,还可以通过 [Google](https://google.com/) 搜索找到答案。具体的问题或困难请试着在 [StackOverflow](https://stackoverflow.com/) 上提问。或者去 [YouTube](https://youtube.com) 看有没有相关的安装说明。\n", + "\n", + "## 编辑器\n", + "\n", + "首先你需要安装一个编辑器来创建和修改本书用例里的 Java 代码。有可能你还需要使用编辑器来更改系统配置文件。\n", + "\n", + "相比一些重量级的 IDE(Integrated Development Environments,集成开发环境),如 Eclipse、NetBeans 和 IntelliJ IDEA (译者注:做项目强烈推荐IDEA),编辑器是一种更纯粹的文本编辑器。如果你已经有了一个用着顺手的 IDE,那就可以直接用了。为了方便后面的学习和统一下教学环境,我推荐大家使用 Atom 这个编辑器。大家可以在 [atom.io](https://atom.io) 上下载。\n", + "\n", + "Atom 是一个免费开源、易于安装且跨平台(支持 Window、Mac和Linux)的文本编辑器。内置支持 Java 文件。相比 IDE 的厚重,它比较轻量级,是学习本书的理想工具。Atom 包含了许多方便的编辑功能,相信你一定会爱上它!更多关于 Atom 使用的细节问题可以到它的网站上寻找。\n", + "\n", + "还有很多其他的编辑器。有一种亚文化的群体,他们热衷于争论哪个更好用!如果你找到一个你更喜欢的编辑器,换一种使用也没什么难度。重要的是,你要找一个用着舒服的。\n", + "\n", + "## Shell\n", + "\n", + "如果你之前没有接触过编程,那么有可能对 Shell(命令行窗口) 不太熟悉。shell 的历史可以追溯到早期的计算时代,当时在计算机上的操作是都通过输入命令进行的,计算机通过回显响应。所有的操作都是基于文本的。\n", + "\n", + "尽管和现在的图形用户界面相比,Shell 操作方式很原始。但是同时 shell 也为我们提供了许多有用的功能特性。在学习本书的过程中,我们会经常使用到 Shell,包括现在这部分的安装,还有运行 Java 程序。\n", + "\n", + "Mac:单击聚光灯(屏幕右上角的放大镜图标),然后键入 `terminal`。单击看起来像小电视屏幕的应用程序(你也可以单击“return”)。这就启动了你的用户下的 shell 窗口。\n", + "\n", + "windows:首先,通过目录打开 windows 资源管理器:\n", + "\n", + "- Windows 7: 单击屏幕左下角的“开始”图标,输入“explorer”后按回车键。\n", + "- Windows 8: 按 Windows+Q,输入 “explorer” 后按回车键。\n", + "- Windows 10: 按 Windows+E 打开资源管理器,导航到所需目录,单击窗口左上角的“文件“选项卡,选择“打开 Window PowerShell”启动 Shell。\n", + "\n", + "Linux: 在 home 目录打开 Shell。\n", + "\n", + "- Debian: 按 Alt+F2, 在弹出的对话框中输入“gnome-terminal”\n", + "- Ubuntu: 在屏幕中鼠标右击,选择 “打开终端”,或者按住 Ctrl+Alt+T\n", + "- Redhat: 在屏幕中鼠标右击,选择 “打开终端”\n", + "- Fedora: 按 Alt+F2,在弹出的对话框中输入“gnome-terminal”\n", + "\n", + "**目录**\n", + "\n", + "目录是 Shell 的基础元素之一。目录用来保存文件和其他目录。目录就好比树的分支。如果书籍是你系统上的一个目录,并且它有两个其他目录作为分支,例如数学和艺术,那么我们就可以说你有一个书籍目录,它包含数学和艺术两个子目录。注意:Windows 使用 `\\` 而不是 `/` 来分隔路径。\n", + "\n", + "**Shell基本操作**\n", + "\n", + "我在这展示的 Shell 操作和系统中大体相同。出于本书的原因,下面列举一些在 Shell 中的基本操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + "更改目录: cd <路径> \n", + " cd .. 移动到上级目录 \n", + " pushd <路径> 记住来源的同时移动到其他目录,popd 返回来源\n", + "\n", + "目录列举: ls 列举出当前目录下所有的文件和子目录名(不包含隐藏文件),\n", + " 可以选择使用通配符 * 来缩小搜索范围。\n", + " 示例(1): 列举所有以“.java”结尾的文件,输入 ls *.java (Windows: dir *.java)\n", + " 示例(2): 列举所有以“F”开头,“.java”结尾的文件,输入ls F*.java (Windows: dir F*.java)\n", + "\n", + "创建目录: \n", + " Mac/Linux 系统:mkdir \n", + " 示例:mkdir books \n", + " Windows 系统:md \n", + " 示例:md books\n", + "\n", + "移除文件: \n", + " Mac/Linux 系统:rm\n", + " 示例:rm somefile.java\n", + " Windows 系统:del \n", + " 示例:del somefile.java\n", + "\n", + "移除目录: \n", + " Mac/Linux 系统:rm -r\n", + " 示例:rm -r books\n", + " Windows 系统:deltree \n", + " 示例:deltree books\n", + "\n", + "重复命令: !! 重复上条命令\n", + " 示例:!n 重复倒数第n条命令\n", + "\n", + "命令历史: \n", + " Mac/Linux 系统:history\n", + " Windows 系统:按 F7 键\n", + "\n", + "文件解压:\n", + " Linux/Mac 都有命令行解压程序 unzip,你可以通过互联网为 Windows 安装命令行解压程序 unzip。\n", + " 图形界面下(Windows 资源管理器,Mac Finder,Linux Nautilus 或其他等效软件)右键单击该文件,\n", + " 在 Mac 上选择“open”,在 Linux 上选择“extract here”,或在 Windows 上选择“extract all…”。\n", + " 要了解关于 shell 的更多信息,请在维基百科中搜索 Windows shell,Mac/Linux用户可搜索 bash shell。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Java安装\n", + "\n", + "为了编译和运行代码示例,首先你必须安装 JDK(Java Development Kit,JAVA 软件开发工具包)。本书中采用的是 JDK 8。\n", + "\n", + "\n", + "**Windows**\n", + "\n", + "1. 以下为 Chocolatey 的[安装说明](https://chocolatey.org/)。\n", + "2. 在命令行提示符下输入下面的命令,等待片刻,结束后 Java 安装完成并自动完成环境变量设置。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " choco install jdk8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Macintosh**\n", + "\n", + "Mac 系统自带的 Java 版本太老,为了确保本书的代码示例能被正确执行,你必须将它先更新到 Java 8。我们需要管理员权限来运行下面的步骤:\n", + "\n", + "1. 以下为 HomeBrew 的[安装说明](https://brew.sh/)。安装完成后执行命令 `brew update` 更新到最新版本\n", + "2. 在命令行下执行下面的命令来安装 Java。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " brew cask install java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当以上安装都完成后,如果你有需要,可以使用游客账户来运行本书中的代码示例。\n", + "\n", + "**Linux**\n", + "\n", + "* **Ubuntu/Debian**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " sudo apt-get update\n", + " sudo apt-get install default-jdk" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **Fedora/Redhat**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " su-c \"yum install java-1.8.0-openjdk\"(注:执行引号内的内容就可以安装)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 校验安装\n", + "\n", + "打开新的命令行输入:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + "java -version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正常情况下 你应该看到以下类似信息(版本号信息可能不一样):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + "java version \"1.8.0_112\"\n", + "Java(TM) SE Runtime Environment (build 1.8.0_112-b15)\n", + "Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果提示命令找不到或者无法被识别,请根据安装说明重试;如果还不行,尝试到 [StackOverflow](https://stackoverflow.com/search?q=installing+java) 寻找答案。\n", + "\n", + "## 安装和运行代码示例\n", + "\n", + "当 Java 安装完毕,下一步就是安装本书的代码示例了。安装步骤所有平台一致:\n", + "\n", + "1. 从 [GitHub 仓库](https://github.com/BruceEckel/OnJava8-Examples/archive/master.zip)中下载本书代码示例\n", + "2. 解压到你所选目录里。\n", + "3. 使用 Windows 资源管理器,Mac Finder,Linux 的 Nautilus 或其他等效工具浏览,在该目录下打开 Shell。\n", + "4. 如果你在正确的目录中,你应该看到该目录中名为 gradlew 和 gradlew.bat 的文件,以及许多其他文件和目录。目录与书中的章节相对应。\n", + "5. 在shell中输入下面的命令运行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " Windows 系统:\n", + " gradlew run\n", + "\n", + " Mac/Linux 系统:\n", + " ./gradlew run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一次安装时 Gradle 需要安装自身和其他的相关的包,请稍等片刻。安装完成后,后续的安装将会快很多。\n", + "\n", + "**注意**: 第一次运行 gradlew 命令时必须连接互联网。\n", + "\n", + "**Gradle 基础任务**\n", + "\n", + "本书构建的大量 Gradle 任务都可以自动运行。Gradle 使用约定大于配置的方式,简单设置即可具备高可用性。本书中“一起去骑行”的某些任务不适用于此或无法执行成功。以下是你通常会使用上的 Gradle 任务列表:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + " 编译本书中的所有 java 文件,除了部分错误示范的\n", + " gradlew compileJava\n", + "\n", + " 编译并执行 java 文件(某些文件是库组件)\n", + " gradlew run\n", + "\n", + " 执行所有的单元测试(在本书第16章会有详细介绍)\n", + " gradlew test\n", + "\n", + " 编译并运行一个具体的示例程序\n", + " gradlew <本书章节>:<示例名称>\n", + " 示例:gradlew objects:HelloDate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/03-Objects-Everywhere.ipynb b/jupyter/03-Objects-Everywhere.ipynb new file mode 100644 index 00000000..6eabcaad --- /dev/null +++ b/jupyter/03-Objects-Everywhere.ipynb @@ -0,0 +1,1386 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "# 第三章 万物皆对象\n", + "\n", + "> 如果我们说另外一种不同的语言,我们会发觉一个不同的世界!— Ludwig Wittgenstein (1889-1951)\n", + "\n", + "相比 C++ ,Java 是一种更纯粹的面向对象编程语言。虽然它们都是混合语言,但在 Java 中,设计者们认为混合的作用并非像在 C++ 中那般重要。混合语言允许多种编程风格,这也是 C++ 支持向后兼容 C 的原因。正因为 C++ 是 C 语言的超集,所以它也同时包含了许多 C 语言不具备的特性,这使得 C++ 在某些方面过于复杂。\n", + "\n", + " Java 语言假设你只进行面向对象编程。开始学习之前,我们需要将思维置于面向对象的世界。本章你将了解到 Java 程序的基本组成,学习在 Java 中万物(几乎)皆对象的思想。\n", + "\n", + "\n", + "\n", + "## 对象操纵\n", + "\n", + "“名字代表什么?玫瑰即使不叫玫瑰,也依旧芬芳”。(引用自 莎士比亚,《罗密欧与朱丽叶》)。\n", + "\n", + "所有的编程语言都会操纵内存中的元素。有时程序员必须要有意识地直接或间接地操纵它们。在 C/C++ 中,对象的操纵是通过指针来完成的。\n", + "\n", + "Java 利用万物皆对象的思想和单一一致的语法方式来简化问题。虽万物皆可为对象,但我们所操纵的标识符实际上只是对对象的“引用” [^1]。 举例:我们可以用遥控器(引用)去操纵电视(对象)。只要拥有对象的“引用”,就可以操纵该“对象”。换句话说,我们无需直接接触电视,就可通过遥控器(引用)自由地控制电视(对象)的频道和音量。此外,没有电视,遥控器也可以单独存在。就是说,你仅仅有一个“引用”并不意味着你必然有一个与之关联的“对象”。 \n", + "\n", + "下面来创建一个 **String** 引用,用于保存单词或语句。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " String s;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里我们只是创建了一个 **String** 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 `s` 赋值--指向任何对象。通常更安全的做法是:创建一个引用的同时进行初始化。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " String s = \"asdf\";" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Java 语法允许我们使用带双引号的文本内容来初始化字符串。同样,其他类型的对象也有相应的初始化方式。\n", + "\n", + "\n", + "## 对象创建\n", + "\n", + "“引用”用来关联“对象”。在 Java 中,通常我们使用`new`操作符来创建一个新对象。`new` 关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " String s = new String(\"asdf\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以上展示了字符串对象的创建过程,以及如何初始化生成字符串。除了 **String** 类型以外,Java 本身自带了许多现成的数据类型。除此之外,我们还可以创建自己的数据类型。事实上,这是 Java 程序设计中的一项基本行为。在本书后面的学习中将会接触到。\n", + "\n", + "\n", + "### 数据存储\n", + "\n", + "那么,程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据:\n", + "\n", + "1. **寄存器**(Registers)最快的存储区域,位于 CPU 内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。\n", + "\n", + "2. **栈内存**(Stack)存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。\n", + "\n", + "3. **堆内存**(Heap)这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。\n", + "\n", + "4. **常量存储**(Constant storage)常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 [^3]。\n", + "\n", + "5. **非 RAM 存储**(Non-RAM storage)数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。\n", + "\n", + "\n", + "### 基本类型的存储\n", + "\n", + "有一组类型在 Java 中使用频率很高,它们需要特殊对待,这就是 Java 的基本类型。之所以这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法,Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量直接存储\"值\",并置于栈内存中,因此更加高效。\n", + "\n", + "Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。\n", + "\n", + "| 基本类型 | 大小 | 最小值 | 最大值 | 包装类型 |\n", + "| :------: | :------: | :------: | :------: | :------: |\n", + "| boolean | — | — | — | Boolean |\n", + "| char | 16 bits | Unicode 0 | Unicode 216 -1 | Character |\n", + "| byte | 8 bits | -128 | +127 | Byte |\n", + "| short | 16 bits | - 215 | + 215 -1 | Short |\n", + "| int | 32 bits | - 231 | + 231 -1 | Integer |\n", + "| long | 64 bits | - 263 | + 263 -1 | Long |\n", + "| float | 32 bits | IEEE754 | IEEE754 | Float |\n", + "| double | 64 bits |IEEE754 | IEEE754 | Double |\n", + "| void | — | — | — | Void |\n", + "\n", + "所有的数值类型都是有正/负符号的。布尔(boolean)类型的大小没有明确的规定,通常定义为取字面值 “true” 或 “false” 。基本类型有自己对应的包装类型,如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "char c = 'x';\n", + "Character ch = new Character(c);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "或者你也可以使用下面的形式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Character ch = new Character('x');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "基本类型自动转换成包装类型(自动装箱)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Character ch = 'x';" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "相对的,包装类型转化为基本类型(自动拆箱):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "char c = ch;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "个中原因将在以后的章节里解释。\n", + "\n", + "\n", + "### 高精度数值\n", + "\n", + "在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。\n", + "\n", + "这两个类包含的方法提供的操作,与对基本类型执行的操作相似。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。\n", + "\n", + "BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。\n", + "BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的货币计算。\n", + "\n", + "关于这两个类的详细信息,请参考 JDK 官方文档。\n", + "\n", + "\n", + "### 数组的存储\n", + "\n", + "许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。\n", + "\n", + "Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。(并且 Java 经常可以优化这些操作)。\n", + "\n", + "当我们创建对象数组时,实际上是创建了一个引用数组,并且每个引用的初始值都为 **null** 。在使用该数组之前,我们必须为每个引用指定一个对象 。如果我们尝试使用为 **null** 的引用,则会在运行时报错。因此,在 Java 中就防止了数组操作的常规错误。\n", + "\n", + "我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。\n", + "\n", + "\n", + "## 代码注释\n", + "\n", + "Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。注意,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "/* 这是\n", + "* 跨越多行的\n", + "* 注释\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但请记住,`/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面这样也是没有区别的。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "/* 这是跨越多\n", + "行的注释 */" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第二种注释形式来自 C++ 。它是单行注释,以 `//` 开头并一直持续到行结束。这种注释方便且常用,因为直观简单。所以你经常看到:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// 这是单行注释" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 对象清理\n", + "\n", + "在一些编程语言中,管理变量的生命周期需要大量的工作。一个变量需要存活多久?如果我们想销毁它,应该什么时候去做呢?变量生命周期的混乱会导致许多 bug,本小结向你介绍 Java 是如何通过释放存储来简化这个问题的。\n", + "\n", + "\n", + "### 作用域\n", + "\n", + "大多数程序语言都有作用域的概念。作用域决定了在该范围内定义的变量名的可见性和生存周期。在 C、 C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "{\n", + " int x = 12;\n", + " // 仅 x 变量可用\n", + " {\n", + " int q = 96;\n", + " // x 和 q 变量皆可用\n", + " }\n", + " // 仅 x 变量可用\n", + " // 变量 q 不在作用域内\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易于阅读。由于 Java 是一种自由格式的语言,额外的空格、制表符和回车并不会影响程序的执行结果。在 Java 中,你不能执行以下操作,即使这在 C 和 C++ 中是合法的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "{\n", + " int x = 12;\n", + " {\n", + " int x = 96; // Illegal\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在上例中, Java 编译器会在提示变量 x 已经被定义过了。因此,在 C/C++ 中将一个较大作用域的变量\"隐藏\"起来的做法,在 Java 中是不被允许的。 因为 Java 的设计者认为这样做会导致程序混乱。\n", + "\n", + "\n", + "### 对象作用域\n", + "\n", + "Java 对象与基本类型具有不同的生命周期。当我们使用 `new` 关键字来创建 Java 对象时,它的生命周期将会超出作用域。因此,下面这段代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "{\n", + " String s = new String(\"a string\");\n", + "} \n", + "// 作用域终点" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。在后面的章节中,我们还会学习怎么在编程中传递和复制对象的引用。\n", + "\n", + "只要你需要,`new` 出来的对象就会一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在使用完它们之后,将其销毁。\n", + "\n", + "那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?答案是:Java 的垃圾收集器会检查所有 `new` 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。\n", + "\n", + "\n", + "## 类的创建\n", + "\n", + "### 类型\n", + "\n", + "如果一切都是对象,那么是什么决定了某一类对象的外观和行为呢?换句话说,是什么确定了对象的类型?你可能很自然地想到 `type` 关键字。但是,事实上大多数面向对象的语言都使用 `class` 关键字类来描述一种新的对象。 通常在 `class` 关键字的后面的紧跟类的的名称。如下代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class ATypeName {\n", + " // 这里是类的内部\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在上例中,我们引入了一个新的类型,尽管这个类里只有一行注释。但是我们一样可以通过 `new` 关键字来创建一个这种类型的对象。如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "ATypeName a = new ATypeName();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。\n", + "\n", + "\n", + "### 字段\n", + "\n", + "当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和字段(field)。类的字段可以是基本类型,也可以是引用类型。如果类的字段是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其字段的空间。通常,字段不在对象间共享。下面是一个具有某些字段的类的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class DataOnly {\n", + " int i;\n", + " double d;\n", + " boolean b;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个类除了存储数据之外什么也不能做。但是,我们仍然可以通过下面的代码来创建它的一个对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " DataOnly data = new DataOnly();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们必须通过这个对象的引用来指定字段值。格式:对象名称.方法名称或字段名称。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " data.i = 47;\n", + " data.d = 1.1;\n", + " data.b = false;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你想修改对象内部包含的另一个对象的数据,可以通过这样的格式修改。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " myPlane.leftTank.capacity = 100;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以用这种方式嵌套许多对象(尽管这样的设计会带来混乱)。\n", + "\n", + "\n", + "### 基本类型默认值\n", + "\n", + "如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。\n", + "\n", + "| 基本类型 | 初始值 |\n", + "| :-----: |:-----: |\n", + "| boolean | false |\n", + "| char | \\u0000 (null) |\n", + "| byte | (byte) 0 |\n", + "| short |(short) 0 |\n", + "| int | 0 |\n", + "| long | 0L |\n", + "| float | 0.0f |\n", + "| double | 0.0d |\n", + "\n", + "这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。\n", + "\n", + "这种默认值的赋予并不适用于局部变量 —— 那些不属于类的字段的变量。 因此,若在方法中定义的基本类型数据,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " int x;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步, Java 将会提示我们“编译时错误,该变量可能尚未被初始化”。 这一点做的比 C++ 更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。\n", + "\n", + "\n", + "### 方法使用\n", + "\n", + "在许多语言(如 C 和 C++)中,使用术语 *函数* (function) 用来命名子程序。在 Java 中,我们使用术语 *方法*(method)来表示“做某事的方式”。\n", + "\n", + "在 Java 中,方法决定对象能接收哪些消息。方法的基本组成部分包括名称、参数、返回类型、方法体。格式如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " [返回类型] [方法名](/*参数列表*/){\n", + " // 方法体\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 返回类型\n", + "\n", + "方法的返回类型表明了当你调用它时会返回的结果类型。参数列表则显示了可被传递到方法内部的参数类型及名称。方法名和参数列表统称为**方法签名**(signature of the method)。签名作为方法的唯一标识。\n", + "\n", + "Java 中的方法只能作为类的一部分创建。它只能被对象所调用 [^4],并且该对象必须有权限来执行调用。若对象调用错误的方法,则程序将在编译时报错。\n", + "\n", + "我们可以像下面这样调用一个对象的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[对象引用].[方法名](参数1, 参数2, 参数3);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "若方法不带参数,例如一个对象引用 `a` 的方法 `f` 不带参数并返回 **int** 型结果,我们可以如下表示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int x = a.f();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中方法 `f` 的返回值类型必须和变量 `x` 的类型兼容 。调用方法的行为有时被称为向对象发送消息。面向对象编程可以总结为:向对象发送消息。\n", + "\n", + "\n", + "#### 参数列表\n", + "\n", + "方法参数列表指定了传递给方法的信息。正如你可能猜到的,这些信息就像 Java 中的其他所有信息 ,以对象的形式传递。参数列表必须指定每个对象的类型和名称。同样,我们并没有直接处理对象,而是在传递对象引用 [^5] 。但是引用的类型必须是正确的。如果方法需要 String 参数,则必须传入 String,否则编译器将报错。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int storage(String s) {\n", + " return s.length() * 2;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此方法计算并返回某个字符串所占的字节数。参数 `s` 的类型为 **String** 。将 s 传递给 `storage()` 后,我们可以把它看作和任何其他对象一样,可以向它发送消息。在这里,我们调用 `length()` 方法,它是一个 String 方法,返回字符串中的字符数。字符串中每个字符的大小为 16 位或 2 个字节。你还看到了 **return** 关键字,它执行两项操作。首先,它意味着“方法执行结束”。其次,如果方法有返回值,那么该值就紧跟 **return** 语句之后。这里,返回值是通过计算" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "s.length() * 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "产生的。在方法中,我们可以返回任何类型的数据。如果我们不想方法返回数据,则可以通过给方法标识 `void` 来表明这是一个无需返回值的方法。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "boolean flag() { \n", + " return true; \n", + "}\n", + "\n", + "double naturalLogBase() { \n", + " return 2.718; \n", + "}\n", + "\n", + "void nothing() {\n", + " return;\n", + "}\n", + "\n", + "void nothing2() {\n", + "\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当返回类型为 **void** 时, **return** 关键字仅用于退出方法,因此在方法结束处的 **return** 可被省略。我们可以随时从方法中返回,但若方法返回类型为非 `void`,则编译器会强制我们返回相应类型的值。\n", + "\n", + "上面的描述可能会让你感觉程序只不过是一堆包含各种方法的对象,在这些方法中,将对象作为参数并发送消息给其他对象。大部分情况下确实如此。但在下一章的运算符中我们将会学习如何在方法中做出决策来完成更底层、详细的工作。对于本章,知道如何发送消息就够了。\n", + "\n", + "\n", + "## 程序编写\n", + "\n", + "在看到第一个 Java 程序之前,我们还必须理解其他几个问题。\n", + "\n", + "### 命名可见性\n", + "\n", + "命名控制在任何一门编程语言中都是一个问题。如果你在两个模块中使用相同的命名,那么如何区分这两个名称,并防止两个名称发生“冲突”呢?在 C 语言编程中这是很具有挑战性的,因为程序通常是一个无法管理的名称海洋。C++ 将函数嵌套在类中,所以它们不会和嵌套在其他类中的函数名冲突。然而,C++ 还是允许全局数据和全局函数,因此仍有可能发生冲突。为了解决这个问题,C++ 使用附加的关键字引入了*命名空间*。\n", + "\n", + "Java 采取了一种新的方法避免了以上这些问题:为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我将我的 foibles 类库命名为 com.mindviewinc.utility.foibles。反转域名后,`.` 用来代表子目录的划分。\n", + "\n", + "在 Java 1.0 和 Java 1.1 中,域扩展名 com、 edu、 org 和 net 等按惯例大写,因此类库中会出现这样类似的名称:Com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。\n", + "\n", + "使用反向 URL 是一种新的命名空间方法,在此之前尚未有其他语言这么做过。Java 中有许多这些“创造性”地解决问题的方法。正如你想象,如果我们未经测试就添加一个功能并用于生产,那么在将来发现该功能的问题再想纠正,通常为时已晚(有些错误太严重了就得从语言中删除新功能。)\n", + "\n", + "使用反向 URL 将命名空间与文件路径相关联不会导致BUG,但它却给源代码管理带来麻烦。例如在 `com.mindviewinc.utility.foibles` 这样的目录结构中,我们创建了 `com` 和 `mindviewinc` 空目录。它们存在的唯一目的就是用来表示这个反向的 URL。\n", + "\n", + "这种方式似乎为我们在编写 Java 程序中的某个问题打开了大门。空目录填充了深层次结构,它们不仅用于表示反向 URL,还用于捕获其他信息。这些长路径基本上用于存储有关目录中的内容的数据。如果你希望以最初设计的方式使用目录,这种方法可以从“令人沮丧”到“令人抓狂”,对于生产级的 Java 代码,你必须使用专门为此设计的 IDE 来管理代码。例如 NetBeans,Eclipse 或 IntelliJ IDEA。实际上,这些 IDE 都为我们管理和创建深层次空目录结构。\n", + "\n", + "对于这本书中的例子,我不想让深层次结构给你的学习带来额外的麻烦,这实际上需要你在开始之前学习熟悉一种重量级的 IDE。所以,我们的每个章节的示例都位于一个浅的子目录中,以章节标题为名。这导致我偶尔会与遵循深层次方法的工具发生冲突。\n", + "\n", + "\n", + "### 使用其他组件\n", + "\n", + "无论何时在程序中使用预先定义好的类,编译器都必须找到该类。最简单的情况下,该类存在于被调用的源代码文件中。此时我们使用该类 —— 即使该类在文件的后面才会被定义(Java 消除了所谓的“前向引用”问题)。而如果一个类位于其他文件中,又会怎样呢?你可能认为编译器应该足够智能去找到它,但这样是有问题的。想象一下,假如你要使用某个类,但目录中存在多个同名的类(可能用途不同)。或者更糟糕的是,假设你正在编写程序,在构建过程中,你想将某个新类添加到类库中,但却与已有的类名称冲突。\n", + "\n", + "要解决此问题,你必须通过使用 **import** 关键字来告诉 Java 编译器具体要使用的类。**import** 指示编译器导入一个包,也就是一个类库(在其他语言中,一个库不仅包含类,还可能包括函数和数据,但请记住 Java 中的所有代码都必须写在类里)。大多数时候,我们都在使用 Java 标准库中的组件。有了这些构件,你就不必写一长串的反转域名。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import java.util.ArrayList;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例可以告诉编译器使用位于标准库 **util** 下的 ArrayList 类。但是,**util** 中包含许多类,我们可以使用通配符 `*` 来导入其中部分类,而无需显式得逐一声明这些类。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import java.util.*;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "本书中的示例很小,为简单起见,我们通常会使用 `.*` 形式略过导入。然而,许多教程书籍都会要求程序员逐一导入每个类。 \n", + "\n", + "\n", + "### static关键字\n", + "\n", + "类是对象的外观及行为方式的描述。通常只有在使用 `new` 创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。\n", + "\n", + "1. 有时你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。\n", + "\n", + "2. 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。\n", + "\n", + "**static** 关键字(从 C++ 采用)就符合上述两点要求。当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。 即使我们从未创建过该类的对象,也可以调用其静态方法或访问其静态字段。相反,对于普通的非静态字段和方法,我们必须要先创建一个对象并使用该对象来访问字段或方法,因为非静态字段和方法必须与特定对象关联 [^6] 。\n", + "\n", + "一些面向对象的语言使用类数据(class data)和类方法(class method),表示静态数据和方法只是作为类,而不是类的某个特定对象而存在的。有时 Java 文献也使用这些术语。\n", + "\n", + "我们可以在类的字段或方法前添加 `static` 关键字来表示这是一个静态字段或静态方法。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class StaticTest {\n", + " static int i = 47;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,即使你创建了两个 `StaticTest` 对象,但是静态变量 `i` 仍只占一份存储空间。两个对象都会共享相同的变量 `i`。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "StaticTest st1 = new StaticTest();\n", + "StaticTest st2 = new StaticTest();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`st1.i` 和 `st2.i` 指向同一块存储空间,因此它们的值都是 47。引用静态变量有两种方法。在前面的示例中,我们通过一个对象来定位它,例如 `st2.i`。我们也可以通过类名直接引用它,这种方式对于非静态成员则不可行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "StaticTest.i++;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`++` 运算符将会使变量结果 + 1。此时 `st1.i` 和 `st2.i` 的值都变成了 48。\n", + "\n", + "使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态字段或方法 [^7]。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class Incrementable {\n", + " static void increment() { \n", + " StaticTest.i++; \n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中,`Incrementable` 的 `increment()` 方法通过 `++` 运算符将静态数据 `i` 加 1。我们依然可以先实例化对象再调用该方法。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Incrementable sf = new Incrementable();\n", + "sf.increment();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当然了,首选的方法是直接通过类来调用它。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Incrementable.increment();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "相比非静态的对象,`static` 属性改变了数据创建的方式。同样,当 `static` 关键字修饰方法时,它允许我们无需创建对象就可以直接通过类的引用来调用该方法。正如我们所知,`static` 关键字的这些特性对于应用程序入口点的 `main()` 方法尤为重要。\n", + "\n", + "\n", + "## 小试牛刀\n", + "\n", + "最后,我们开始编写第一个完整的程序。我们使用 Java 标准库中的 **Date** 类来展示一个字符串和日期。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\n", + "// objects/HelloDate.java\n", + "import java.util.*;\n", + "\n", + "public class HelloDate {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Hello, it's: \");\n", + " System.out.println(new Date());\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在本书中,所有代码示例的第一行都是注释行,其中包含文件的路径信息(比如本章的目录名是 **objects**),后跟文件名。我的工具可以根据这些信息自动提取和测试书籍的代码,你也可以通过参考第一行注释轻松地在 Github 库中找到对应的代码示例。\n", + "\n", + "如果你想在代码中使用一些额外的类库,那么就必须在程序文件的开始处使用 **import** 关键字来导入它们。之所以说是额外的,因为有一些类库已经默认自动导入到每个文件里了。例如:`java.lang` 包。\n", + "\n", + "现在打开你的浏览器在 [Oracle](https://www.oracle.com/) 上查看文档。如果你还没有从 [Oracle](https://www.oracle.com/) 网站上下载 JDK 文档,那现在就去 [^8] 。查看包列表,你会看到 Java 附带的所有不同的类库。\n", + "\n", + "选择 `java.lang`,你会看到该库中所有类的列表。由于 `java.lang` 隐式包含在每个 Java 代码文件中,因此这些类是自动可用的。`java.lang` 类库中没有 **Date** 类,所以我们必须导入其他的类库(即 Date 所在的类库)。如果你不清楚某个类所在的类库或者想查看类库中所有的类,那么可以在 Java 文档中选择 “Tree” 查看。\n", + "\n", + "现在,我们可以找到 Java 附带的每个类。使用浏览器的“查找”功能查找 **Date**,搜索结果中将会列出 **java.util.Date**,我们就知道了 **Date** 在 **util** 库中,所以必须导入 **java.util.*** 才能使用 **Date**。\n", + "\n", + "如果你在文档中选择 **java.lang**,然后选择 **System**,你会看到 **System** 类中有几个字段,如果你选择了 **out**,你会发现它是一个静态的 **PrintStream** 对象。 所以,即使我们不使用 **new** 创建, **out** 对象就已经存在并可以使用。 **out** 对象可以执行的操作取决于它的类型: **PrintStream** ,其在文档中是一个超链接,如果单击该链接,我们将可以看到 **PrintStream** 对应的方法列表(更多详情,将在本书后面介绍)。 现在我们重点说的是 **println()** 这个方法。 它的作用是 “将信息输出到控制台,并以换行符结束”。既然如此,我们可以这样编码来输出信息到控制台。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "System.out.println(\"A String of things\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个 java 源文件中允许有多个类。同时,源文件的名称必须要和其中一个类名相同,否则编译器将会报错。每个独立的程序应该包含一个 `main()` 方法作为程序运行的入口。其方法签名和返回类型如下。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public static void main(String[] args) {\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "关键字 **public** 表示方法可以被外界访问到。( 更多详情将在 **隐藏实现** 章节讲到)\n", + "**main()** 方法的参数是一个 字符串(**String**) 数组。 参数 **args** 并没有在当前的程序中使用到,但是 Java 编译器强制要求必须要有, 这是因为它们被用于接收从命令行输入的参数。\n", + "\n", + "下面我们来看一段有趣的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "System.out.println(new Date());" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上面的示例中,我们创建了一个日期(**Date**)类型的对象并将其转化为字符串类型,输出到控制台中。 一旦这一行语句执行完毕,我们就不再需要该日期对象了。这时,Java 垃圾回收器就可以将其占用的内存回收,我们无需去主动清除它们。\n", + "\n", + "查看 JDK 文档时,我们可以看到在 **System** 类下还有很多其他有用的方法( Java 的牛逼之处还在于,它拥有一个庞大的标准库资源)。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// objects/ShowProperties.java\n", + "public class ShowProperties {\n", + " public static void main(String[] args) {\n", + " System.getProperties().list(System.out);\n", + " System.out.println(System.getProperty(\"user.name\"));\n", + " System.out.println(System.getProperty(\"java.library.path\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果(前20行):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "text" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "java.runtime.name=Java(TM) SE Runtime Environment\n", + "sun.boot.library.path=C:\\Program\n", + "Files\\Java\\jdk1.8.0_112\\jr...\n", + "java.vm.version=25.112-b15\n", + "java.vm.vendor=Oracle Corporation\n", + "java.vendor.url=http://java.oracle.com/\n", + "path.separator=;\n", + "java.vm.name=Java HotSpot(TM) 64-Bit Server VM\n", + "file.encoding.pkg=sun.io\n", + "user.script=\n", + "user.country=US\n", + "sun.java.launcher=SUN_STANDARD\n", + "sun.os.patch.level=\n", + "java.vm.specification.name=Java Virtual Machine\n", + "Specification\n", + "user.dir=C:\\Users\\Bruce\\Documents\\GitHub\\on-ja...\n", + "java.runtime.version=1.8.0_112-b15\n", + "java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment\n", + "java.endorsed.dirs=C:\\Program\n", + "Files\\Java\\jdk1.8.0_112\\jr...\n", + "os.arch=amd64\n", + "java.io.tmpdir=C:\\Users\\Bruce\\AppData\\Local\\Temp\\" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`main()` 方法中的第一行会输出所有的系统字段,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的字段。该例中我们使用到了 **user.name** 和 **java.library.path**。 \n", + "\n", + "\n", + "### 编译和运行\n", + "\n", + "要编译和运行本书中的代码示例,首先必须具有 Java 编程环境。 第二章的示例中描述了安装过程。如果你遵循这些说明,那么你将会在不受 Oracle 的限制的条件下用到 Java 开发工具包(JDK)。如果你使用其他开发系统,请查看该系统的文档以确定如何编译和运行程序。 第二章还介绍了如何安装本书的示例。 \n", + "\n", + "移动到子目录 **objects** 下并键入:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%bash\n", + "javac HelloDate.java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此命令不应产生任何响应。如果我们收到任何类型的错误消息,则表示未正确安装 JDK,那就得检查这些问题。\n", + "\n", + "若执行不报错的话,此时可以键入:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "java HelloDate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们将会得到正确的日期输出。这是我们编译和运行本书中每个程序(包含 `main()` 方法)的过程 [^9]。此外,本书的源代码在根目录中也有一个名为 **build.gradle** 的文件,其中包含用于自动构建,测试和运行本书文件的 **Gradle** 配置。当你第一次运行 `gradlew` 命令时,**Gradle** 将自动安装(前提是已安装Java)。\n", + "\n", + "\n", + "## 编码风格\n", + "\n", + "Java 编程语言编码规范(Code Conventions for the Java Programming Language)[^10] 要求类名的首字母大写。 如果类名是由多个单词构成的,则每个单词的首字母都应大写(不采用下划线来分隔)例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class AllTheColorsOfTheRainbow {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有时称这种命名风格叫“驼峰命名法”。对于几乎所有其他方法,字段(成员变量)和对象引用名都采用驼峰命名的方式,但是它们的首字母不需要大写。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class AllTheColorsOfTheRainbow {\n", + " int anIntegerRepresentingColors;\n", + " void changeTheHueOfTheColor(int newHue) {\n", + " // ...\n", + " }\n", + " // ...\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Oracle 的官方类库中,花括号的位置同样遵循和本书中上述示例相同的规范。\n", + "\n", + "## 本章小结\n", + "\n", + "本章向你展示了简单的 Java 程序编写以及该语言相关的基本概念。到目前为止,我们的示例都只是些简单的顺序执行。在接下来的两章里,我们将会接触到 Java 的一些基本操作符,以及如何去控制程序执行的流程。\n", + "\n", + "[^1]: 这里可能有争议。有人说这是一个指针,但这假定了一个潜在的实现。此外,Java 引用的语法更类似于 C++ 引用而非指针。在 《Thinking in Java》 的第 1 版中,我发明了一个新术语叫“句柄”(handle),因为 C++ 引用和Java 引用有一些重要的区别。作为一个从 C++ 的过来人,我不想混淆 Java 可能的最大受众 —— C++ 程序员。在《Thinking in Java》的第 2 版中,我认为“引用”(reference)是更常用的术语,从 C++ 转过来的人除了引用的术语之外,还有很多东西需要处理,所以他们不妨双脚都跳进去。但是,也有些人甚至不同意“引用”。在某书中我读到一个观点:Java 支持引用传递的说法是完全错误的,因为 Java 对象标识符(根据该作者)实际上是“对象引用”(object references),并且一切都是值传递。所以你不是通过引用传递,而是“通过值传递对象引用。人们可以质疑我的这种解释的准确性,但我认为我的方法简化了对概念的理解而又没对语言造成伤害(嗯,语言专家可能会说我骗你,但我会说我只是对此进行了适当的抽象。)\n", + "\n", + "[^2]: 大多数微处理器芯片都有额外的高速缓冲存储器,但这是按照传统存储器而不是寄存器。\n", + "\n", + "[^3]: 一个例子是字符串常量池。所有文字字符串和字符串值常量表达式都会自动放入特殊的静态存储中。\n", + "\n", + "[^4]: 静态方法,我们很快就能接触到,它可以在没有对象的情况下直接被类调用。\n", + "\n", + "[^5]: 通常除了前面提到的“特殊”数据类型 boolean、 char、 byte、 short、 int、 long、 float 和 double。通常来说,传递对象就意味者传递对象的引用。\n", + "\n", + "[^6]: 静态方法在使用之前不需要创建对象,因此它们不能直接调用非静态的成员或方法(因为非静态成员和方法必须要先实例化为对象才可以被使用)。\n", + "\n", + "[^7]: 在某些情况下,它还为编译器提供了更好的优化可能。\n", + "\n", + "[^8]: 请注意,此文档未包含在 JDK 中;你必须单独下载才能获得它。\n", + "\n", + "[^9]: 对于本书中编译和运行命令行的每个程序,你可能还需要设置 CLASSPATH 。\n", + "\n", + "[^10]: 为了保持本书的代码排版紧凑,我并没完全遵守规范,但我尽量会做到符合 Java 标准。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/04-Operators.ipynb b/jupyter/04-Operators.ipynb new file mode 100644 index 00000000..32004d06 --- /dev/null +++ b/jupyter/04-Operators.ipynb @@ -0,0 +1,2247 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第四章 运算符\n", + "\n", + ">运算符操纵数据。\n", + "\n", + "Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。\n", + "\n", + "如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。\n", + "\n", + "\n", + "## 开始使用\n", + "\n", + "运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。\n", + "\n", + "几乎所有运算符都只能操作基本类型(Primitives)。唯一的例外是 `=`、`==` 和 `!=`,它们能操作所有对象(这也是令人混淆的一个地方)。除此以外,**String** 类支持 `+` 和 `+=`。\n", + "\n", + "\n", + "## 优先级\n", + "\n", + "运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a = 5\n", + "b = 1\n" + ] + } + ], + "source": [ + "// operators/Precedence.java\n", + "public class Precedence {\n", + " \n", + " public static void main(String[] args) {\n", + " int x = 1, y = 2, z = 3;\n", + " int a = x + y - 2/2 + z; // [1]\n", + " int b = x + (y - 2)/(2 + z); // [2]\n", + " System.out.println(\"a = \" + a);\n", + " System.out.println(\"b = \" + b);\n", + " }\n", + "}\n", + "\n", + "Precedence.main(new String [0]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些语句看起来大致相同,但从输出中我们可以看出它们具有非常不同的含义,具体取决于括号的使用。\n", + "\n", + "我们注意到,在 `System.out.println()` 语句中使用了 `+` 运算符。 但是在这里 `+` 代表的意思是字符串连接符。编译器会将 `+` 连接的非字符串尝试转换为字符串。上例中的输出结果说明了 a 和 b 都已经被转化成了字符串。\n", + "\n", + "\n", + "## 赋值\n", + "\n", + "运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 = A)。\n", + "\n", + "**基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。**\n", + "\n", + "举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。\n", + "\n", + "如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "attributes": { + "classes": [ + "java " + ], + "id": "" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1: t1.level: 9, t2.level: 47\n", + "2: t1.level: 47, t2.level: 47\n", + "3: t1.level: 27, t2.level: 27\n" + ] + } + ], + "source": [ + "// operators/Assignment.java\n", + "// Assignment with objects is a bit tricky\n", + "class Tank {\n", + " int level;\n", + "}\n", + "\n", + "public class Assignment {\n", + "\n", + " public static void main(String[] args) {\n", + " Tank t1 = new Tank();\n", + " Tank t2 = new Tank();\n", + " t1.level = 9;\n", + " t2.level = 47;\n", + " System.out.println(\"1: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " t1 = t2;\n", + " System.out.println(\"2: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " t1.level = 27;\n", + " System.out.println(\"3: t1.level: \" + t1.level +\n", + " \", t2.level: \" + t2.level);\n", + " }\n", + "}\n", + "\n", + "Assignment.main(new String [0]);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1: t1.level: 9, t2.level: 47\n", + "2: t1.level: 47, t2.level: 47\n", + "3: t1.level: 27, t2.level: 27" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个简单的 `Tank` 类,在 `main()` 方法创建了两个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。\n", + "\n", + "这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "t1.level = t2.level;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的“惊喜”。\n", + "\n", + " \n", + "### 方法调用中的别名现象\n", + "\n", + "当我们把对象传递给方法时,会发生别名现象。" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1: x.c: a\n", + "2: x.c: z\n" + ] + } + ], + "source": [ + "// operators/PassObject.java\n", + "// 正在传递的对象可能不是你之前使用的\n", + "class Letter {\n", + " char c;\n", + "}\n", + "\n", + "public class PassObject {\n", + " static void f(Letter y) {\n", + " y.c = 'z';\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Letter x = new Letter();\n", + " x.c = 'a';\n", + " System.out.println(\"1: x.c: \" + x.c);\n", + " f(x);\n", + " System.out.println(\"2: x.c: \" + x.c);\n", + " }\n", + "}\n", + "\n", + "PassObject.main(new String[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1: x.c: a\n", + "2: x.c: z" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在许多编程语言中,方法 `f()` 似乎会在内部复制其参数 **Letter y**。但是一旦传递了一个引用,那么实际上 `y.c ='z';` 是在方法 `f()` 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:[对象传递和返回](./Appendix-Passing-and-Returning-Objects.md)。意识到这一点,我们可以警惕类似的陷阱。\n", + "\n", + "\n", + "## 算术运算符\n", + "\n", + "Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 `+`、减号 `-`、除号 `/`、乘号 `*` 以及取模 `%`(从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。\n", + "\n", + "Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。 可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。更多代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/MathOps.java\n", + "// The mathematical operators\n", + "import java.util.*;\n", + "\n", + "public class MathOps {\n", + " public static void main(String[] args) {\n", + " // Create a seeded random number generator:\n", + " Random rand = new Random(47);\n", + " int i, j, k;\n", + " // Choose value from 1 to 100:\n", + " j = rand.nextInt(100) + 1;\n", + " System.out.println(\"j : \" + j);\n", + " k = rand.nextInt(100) + 1;\n", + " System.out.println(\"k : \" + k);\n", + " i = j + k;\n", + " System.out.println(\"j + k : \" + i);\n", + " i = j - k;\n", + " System.out.println(\"j - k : \" + i);\n", + " i = k / j;\n", + " System.out.println(\"k / j : \" + i);\n", + " i = k * j;\n", + " System.out.println(\"k * j : \" + i);\n", + " i = k % j;\n", + " System.out.println(\"k % j : \" + i);\n", + " j %= k;\n", + " System.out.println(\"j %= k : \" + j);\n", + " // 浮点运算测试\n", + " float u, v, w; // Applies to doubles, too\n", + " v = rand.nextFloat();\n", + " System.out.println(\"v : \" + v);\n", + " w = rand.nextFloat();\n", + " System.out.println(\"w : \" + w);\n", + " u = v + w;\n", + " System.out.println(\"v + w : \" + u);\n", + " u = v - w;\n", + " System.out.println(\"v - w : \" + u);\n", + " u = v * w;\n", + " System.out.println(\"v * w : \" + u);\n", + " u = v / w;\n", + " System.out.println(\"v / w : \" + u);\n", + " // 下面的操作同样适用于 char, \n", + " // byte, short, int, long, and double:\n", + " u += v;\n", + " System.out.println(\"u += v : \" + u);\n", + " u -= v;\n", + " System.out.println(\"u -= v : \" + u);\n", + " u *= v;\n", + " System.out.println(\"u *= v : \" + u);\n", + " u /= v;\n", + " System.out.println(\"u /= v : \" + u); \n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "j : 59\n", + "k : 56\n", + "j + k : 115\n", + "j - k : 3\n", + "k / j : 0\n", + "k * j : 3304\n", + "k % j : 56\n", + "j %= k : 3\n", + "v : 0.5309454\n", + "w : 0.0534122\n", + "v + w : 0.5843576\n", + "v - w : 0.47753322\n", + "v * w : 0.028358962\n", + "v / w : 9.940527\n", + "u += v : 10.471473\n", + "u -= v : 9.940527\n", + "u *= v : 5.2778773\n", + "u /= v : 9.940527" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了生成随机数字,程序首先创建一个 **Random** 对象。不带参数的 **Random** 对象会利用当前的时间用作随机数生成器的“种子”(seed),从而为程序的每次执行生成不同的输出。在本书的示例中,重要的是每个示例末尾的输出尽可能一致,以便可以使用外部工具进行验证。所以我们通过在创建 **Random** 对象时提供种子(随机数生成器的初始化值,其始终为特定种子值产生相同的序列),让程序每次执行都生成相同的随机数,如此以来输出结果就是可验证的 [^1]。 若需要生成随机值,可删除代码示例中的种子参数。该对象通过调用方法 `nextInt()` 和 `nextFloat()`(还可以调用 `nextLong()` 或 `nextDouble()`),使用 **Random** 对象生成许多不同类型的随机数。`nextInt()` 的参数设置生成的数字的上限,下限为零,为了避免零除的可能性,结果偏移1。\n", + "\n", + "\n", + "### 一元加减运算符\n", + "\n", + "一元加 `+` 减 `-` 运算符的操作和二元是相同的。编译器可自动识别使用何种方式解析运算:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = -a;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的代码表意清晰,编译器可正确识别。下面再看一个示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = a * -b;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然编译器可以正确的识别,但是程序员可能会迷惑。为了避免混淆,推荐下面的写法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x = a * (-b);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一元减号可以得到数据的负值。一元加号的作用相反,不过它唯一能影响的就是把较小的数值类型自动转换为 **int** 类型。\n", + "\n", + "\n", + "## 递增和递减\n", + "\n", + "和 C 语言类似,Java 提供了许多快捷运算方式。快捷运算可使代码可读性,可写性都更强。其中包括递增 `++` 和递减 `--`,意为“增加或减少一个单位”。举个例子来说,假设 a 是一个 **int** 类型的值,则表达式 `++a` 就等价于 `a = a + 1`。 递增和递减运算符不仅可以修改变量,还可以生成变量的值。\n", + "\n", + "每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀”和“后缀”。“前递增”表示 `++` 运算符位于变量或表达式的前面;而“后递增”表示 `++` 运算符位于变量的后面。类似地,“前递减”意味着 `--` 运算符位于变量的前面;而“后递减”意味着 `--` 运算符位于变量的后面。对于前递增和前递减(如 `++a` 或 `--a`),会先执行递增/减运算,再返回值。而对于后递增和后递减(如 `a++` 或 `a--`),会先返回值,再执行递增/减运算。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/AutoInc.java\n", + "// 演示 ++ 和 -- 运算符\n", + "public class AutoInc {\n", + " public static void main(String[] args) {\n", + " int i = 1;\n", + " System.out.println(\"i: \" + i);\n", + " System.out.println(\"++i: \" + ++i); // 前递增\n", + " System.out.println(\"i++: \" + i++); // 后递增\n", + " System.out.println(\"i: \" + i);\n", + " System.out.println(\"--i: \" + --i); // 前递减\n", + " System.out.println(\"i--: \" + i--); // 后递减\n", + " System.out.println(\"i: \" + i);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i: 1\n", + "++i: 2\n", + "i++: 2\n", + "i: 3\n", + "--i: 2\n", + "i--: 2\n", + "i: 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于前缀形式,我们将在执行递增/减操作后获取值;使用后缀形式,我们将在执行递增/减操作之前获取值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外) —— 它们修改了操作数的值。\n", + "\n", + "C++ 名称来自于递增运算符,暗示着“比 C 更进一步”。在早期的 Java 演讲中,*Bill Joy*(Java 作者之一)说“**Java = C++ --**”(C++ 减减),意味着 Java 在 C++ 的基础上减少了许多不必要的东西,因此语言更简单。随着进一步地学习,我们会发现 Java 的确有许多地方相对 C++ 来说更简便,但是在其他方面,难度并不会比 C++ 小多少。\n", + "\n", + "\n", + "## 关系运算符\n", + "\n", + "关系运算符会通过产生一个布尔(**boolean**)结果来表示操作数之间的关系。如果关系为真,则结果为 **true**,如果关系为假,则结果为 **false**。关系运算符包括小于 `<`,大于 `>`,小于或等于 `<=`,大于或等于 `>=`,等于 `==` 和不等于 `!=`。`==` 和 `!=` 可用于所有基本类型,但其他运算符不能用于基本类型 **boolean**,因为布尔值只能表示 **true** 或 **false**,所以比较它们之间的“大于”或“小于”没有意义。\n", + "\n", + "\n", + "### 测试对象等价\n", + "\n", + "关系运算符 `==` 和 `!=` 同样适用于所有对象之间的比较运算,但它们比较的内容却经常困扰 Java 的初学者。下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Equivalence.java\n", + "public class Equivalence {\n", + " public static void main(String[] args) {\n", + " Integer n1 = 47;\n", + " Integer n2 = 47;\n", + " System.out.println(n1 == n2);\n", + " System.out.println(n1 != n2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "true\n", + "false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把 47 改成 128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。\n", + "\n", + "那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 `equals()` 方法,下面是如何使用 `equals()` 方法的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/EqualsMethod.java\n", + "public class EqualsMethod {\n", + " public static void main(String[] args) {\n", + " Integer n1 = 47;\n", + " Integer n2 = 47;\n", + " System.out.println(n1.equals(n2));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "true" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的结果看起来是我们所期望的。但其实事情并非那么简单。下面我们来创建自己的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/EqualsMethod2.java\n", + "// 默认的 equals() 方法没有比较内容\n", + "class Value {\n", + " int i;\n", + "}\n", + "\n", + "public class EqualsMethod2 {\n", + " public static void main(String[] args) {\n", + " Value v1 = new Value();\n", + " Value v2 = new Value();\n", + " v1.i = v2.i = 100;\n", + " System.out.println(v1.equals(v2));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例的结果再次令人困惑:结果是 **false**。原因: `equals()` 的默认行为是比较对象的引用而非具体内容。因此,除非你在新类中覆写 `equals()` 方法,否则我们将获取不到想要的结果。不幸的是,在学习 [复用](./08-Reuse.md)(**Reuse**) 章节后我们才能接触到“覆写”(**Override**),并且直到 [附录:集合主题](./Appendix-Collection-Topics.md),才能知道定义 `equals()` 方法的正确方式,但是现在明白 `equals()` 行为方式也可能为你节省一些时间。\n", + "\n", + "大多数 Java 库类通过覆写 `equals()` 方法比较对象的内容而不是其引用。\n", + "\n", + "\n", + "## 逻辑运算符\n", + "\n", + "每个逻辑运算符 `&&` (**AND**)、`||`(**OR**)和 `!`(**非**)根据参数的逻辑关系生成布尔值 `true` 或 `false`。下面的代码示例使用了关系运算符和逻辑运算符:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Bool.java\n", + "// 关系运算符和逻辑运算符\n", + "import java.util.*;\n", + "public class Bool {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " int i = rand.nextInt(100);\n", + " int j = rand.nextInt(100);\n", + " System.out.println(\"i = \" + i);\n", + " System.out.println(\"j = \" + j);\n", + " System.out.println(\"i > j is \" + (i > j));\n", + " System.out.println(\"i < j is \" + (i < j));\n", + " System.out.println(\"i >= j is \" + (i >= j));\n", + " System.out.println(\"i <= j is \" + (i <= j));\n", + " System.out.println(\"i == j is \" + (i == j));\n", + " System.out.println(\"i != j is \" + (i != j));\n", + " // 将 int 作为布尔处理不是合法的 Java 写法\n", + " //- System.out.println(\"i && j is \" + (i && j));\n", + " //- System.out.println(\"i || j is \" + (i || j));\n", + " //- System.out.println(\"!i is \" + !i);\n", + " System.out.println(\"(i < 10) && (j < 10) is \"\n", + " + ((i < 10) && (j < 10)) );\n", + " System.out.println(\"(i < 10) || (j < 10) is \"\n", + " + ((i < 10) || (j < 10)) );\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 58\n", + "j = 55\n", + "i > j is true\n", + "i < j is false\n", + "i >= j is true\n", + "i <= j is false\n", + "i == j is false\n", + "i != j is true\n", + "(i < 10) && (j < 10) is false\n", + "(i < 10) || (j < 10) is false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、 **OR**、 **NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 \"true\" 或 \"false\" 字符串)。\n", + "\n", + "我们可以将前一个程序中 **int** 的定义替换为除 **boolean** 之外的任何其他基本数据类型。但请注意,**float** 类型的数值比较非常严格,只要两个数字的最小位不同则两个数仍然不相等;只要数字最小位是大于 0 的,那么它就不等于 0。\n", + "\n", + "\n", + "### 短路\n", + "\n", + "逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators / ShortCircuit.java \n", + "// 逻辑运算符的短路行为\n", + "public class ShortCircuit {\n", + "\n", + " static boolean test1(int val) {\n", + " System.out.println(\"test1(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 1));\n", + " return val < 1;\n", + " }\n", + "\n", + " static boolean test2(int val) {\n", + " System.out.println(\"test2(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 2));\n", + " return val < 2;\n", + " }\n", + "\n", + " static boolean test3(int val) {\n", + " System.out.println(\"test3(\" + val + \")\");\n", + " System.out.println(\"result: \" + (val < 3));\n", + " return val < 3;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " boolean b = test1(0) && test2(2) && test3(2);\n", + " System.out.println(\"expression is \" + b);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test1(0)\n", + "result: true\n", + "test2(2)\n", + "result: false\n", + "expression is false" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个测试都对参数执行比较并返回 `true` 或 `false`。同时控制台也会在方法执行时打印他们的执行状态。 下面的表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "test1(0)&& test2(2)&& test3(2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可能你的预期是程序会执行 3 个 **test** 方法并返回。我们来分析一下:第一个方法的结果返回 `true`,因此表达式会继续走下去。紧接着,第二个方法的返回结果是 `false`。这就代表这整个表达式的结果肯定为 `false`,所以就没有必要再判断剩下的表达式部分了。\n", + "\n", + "所以,运用“短路”可以节省部分不必要的运算,从而提高程序潜在的性能。\n", + "\n", + "\n", + "## 字面值常量\n", + "\n", + "通常,当我们向程序中插入一个字面值常量(**Literal**)时,编译器会确切地识别它的类型。当类型不明确时,必须辅以字面值常量关联来帮助编译器识别。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Literals.java\n", + "public class Literals {\n", + " public static void main(String[] args) {\n", + " int i1 = 0x2f; // 16进制 (小写)\n", + " System.out.println(\n", + " \"i1: \" + Integer.toBinaryString(i1));\n", + " int i2 = 0X2F; // 16进制 (大写)\n", + " System.out.println(\n", + " \"i2: \" + Integer.toBinaryString(i2));\n", + " int i3 = 0177; // 8进制 (前导0)\n", + " System.out.println(\n", + " \"i3: \" + Integer.toBinaryString(i3));\n", + " char c = 0xffff; // 最大 char 型16进制值\n", + " System.out.println(\n", + " \"c: \" + Integer.toBinaryString(c));\n", + " byte b = 0x7f; // 最大 byte 型16进制值 10101111;\n", + " System.out.println(\n", + " \"b: \" + Integer.toBinaryString(b));\n", + " short s = 0x7fff; // 最大 short 型16进制值\n", + " System.out.println(\n", + " \"s: \" + Integer.toBinaryString(s));\n", + " long n1 = 200L; // long 型后缀\n", + " long n2 = 200l; // long 型后缀 (容易与数值1混淆)\n", + " long n3 = 200;\n", + " \n", + " // Java 7 二进制字面值常量:\n", + " byte blb = (byte)0b00110101;\n", + " System.out.println(\n", + " \"blb: \" + Integer.toBinaryString(blb));\n", + " short bls = (short)0B0010111110101111;\n", + " System.out.println(\n", + " \"bls: \" + Integer.toBinaryString(bls));\n", + " int bli = 0b00101111101011111010111110101111;\n", + " System.out.println(\n", + " \"bli: \" + Integer.toBinaryString(bli));\n", + " long bll = 0b00101111101011111010111110101111;\n", + " System.out.println(\n", + " \"bll: \" + Long.toBinaryString(bll));\n", + " float f1 = 1;\n", + " float f2 = 1F; // float 型后缀\n", + " float f3 = 1f; // float 型后缀\n", + " double d1 = 1d; // double 型后缀\n", + " double d2 = 1D; // double 型后缀\n", + " // (long 型的字面值同样适用于十六进制和8进制 )\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i1: 101111\n", + "i2: 101111\n", + "i3: 1111111\n", + "c: 1111111111111111\n", + "b: 1111111\n", + "s: 111111111111111\n", + "blb: 110101\n", + "bls: 10111110101111\n", + "bli: 101111101011111010111110101111\n", + "bll: 101111101011111010111110101111" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在文本值的后面添加字符可以让编译器识别该文本值的类型。对于 **Long** 型数值,结尾使用大写 `L` 或小写 `l` 皆可(不推荐使用 `l`,因为容易与阿拉伯数值 1 混淆)。大写 `F` 或小写 `f` 表示 **float** 浮点数。大写 `D` 或小写 `d` 表示 **double** 双精度。\n", + "\n", + "十六进制(以 16 为基数),适用于所有整型数据类型,由前导 `0x` 或 `0X` 表示,后跟 0-9 或 a-f (大写或小写)。如果我们在初始化某个类型的数值时,赋值超出其范围,那么编译器会报错(不管值的数字形式如何)。在上例的代码中,**char**、**byte** 和 **short** 的值已经是最大了。如果超过这些值,编译器将自动转型为 **int**,并且提示我们需要声明强制转换(强制转换将在本章后面定义),意味着我们已越过该类型的范围界限。\n", + "\n", + "八进制(以 8 为基数)由 0~7 之间的数字和前导零 `0` 表示。\n", + "\n", + "Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,它可以初始化所有的整数类型。\n", + "\n", + "使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。\n", + "\n", + "\n", + "### 下划线\n", + "\n", + "Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 `_`,以使结果更清晰。这对于大数值的分组特别有用。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Underscores.java\n", + "public class Underscores {\n", + " public static void main(String[] args) {\n", + " double d = 341_435_936.445_667;\n", + " System.out.println(d);\n", + " int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;\n", + " System.out.println(Integer.toBinaryString(bin));\n", + " System.out.printf(\"%x%n\", bin); // [1]\n", + " long hex = 0x7f_e9_b7_aa;\n", + " System.out.printf(\"%x%n\", hex);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "3.41435936445667E8\n", + "101111101011111010111110101111\n", + "2fafafaf\n", + "7fe9b7aa" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面是合理使用的规则:\n", + "\n", + "1. 仅限单 `_`,不能多条相连。\n", + "2. 数值开头和结尾不允许出现 `_`。\n", + "3. `F`、`D` 和 `L`的前后禁止出现 `_`。\n", + "4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。\n", + "\n", + "[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\\r\\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。\n", + "\n", + "\n", + "### 指数计数法\n", + "\n", + "指数总是采用一种我认为很不直观的记号方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Exponents.java\n", + "// \"e\" 表示 10 的几次幂\n", + "public class Exponents {\n", + " public static void main(String[] args) {\n", + " // 大写 E 和小写 e 的效果相同:\n", + " float expFloat = 1.39e-43f;\n", + " expFloat = 1.39E-43f;\n", + " System.out.println(expFloat);\n", + " double expDouble = 47e47d; // 'd' 是可选的\n", + " double expDouble2 = 47e47; // 自动转换为 double\n", + " System.out.println(expDouble);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1.39E-43\n", + "4.7E48" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 \"1.39 x e-43\",意味着 “1.39 × 2.718 的 -43 次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10 的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。\n", + "\n", + "理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是 “1.39 × 10 的 -43 次方”。\n", + "\n", + "注意如果编译器能够正确地识别类型,就不必使用后缀字符。对于下述语句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "long n3 = 200;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它并不存在含糊不清的地方,所以 200 后面的 L 大可省去。然而,对于下述语句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "float f4 = 1e-43f; //10 的幂数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器通常会将指数作为 **double** 类型来处理,所以假若没有这个后缀字符 `f`,编译器就会报错,提示我们应该将 **double** 型转换成 **float** 型。\n", + "\n", + "\n", + "## 位运算符\n", + "\n", + "位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。\n", + "\n", + "位运算源自 C 语言的底层操作。我们经常要直接操纵硬件,频繁设置硬件寄存器内的二进制位。Java 的设计初衷是电视机顶盒嵌入式开发,所以这种底层的操作仍被保留了下来。但是,你可能不会使用太多位运算。\n", + "\n", + "若两个输入位都是 1,则按位“与运算符” `&` 运算后结果是 1,否则结果是 0。若两个输入位里至少有一个是 1,则按位“或运算符” `|` 运算后结果是 1;只有在两个输入位都是 0 的情况下,运算结果才是 0。若两个输入位的某一个是 1,另一个不是 1,那么按位“异或运算符” `^` 运算后结果才是 1。按位“非运算符” `~` 属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位非运算后结果与输入位相反。例如输入 0,则输出 1;输入 1,则输出 0。\n", + "\n", + "位运算符和逻辑运算符都使用了同样的字符,只不过数量不同。位短,所以位运算符只有一个字符。位运算符可与等号 `=` 联合使用以接收结果及赋值:`&=`,`|=` 和 `^=` 都是合法的(由于 `~` 是一元运算符,所以不可与 `=` 联合使用)。\n", + "\n", + "我们将 **Boolean** 类型被视为“单位值”(one-bit value),所以它多少有些独特的地方。我们可以对 boolean 型变量执行与、或、异或运算,但不能执行非运算(大概是为了避免与逻辑“非”混淆)。对于布尔值,位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的位运算为我们新增了一个“异或”逻辑运算符,它并未包括在逻辑运算符的列表中。在移位表达式中,禁止使用布尔值,原因将在下面解释。\n", + "\n", + "\n", + "## 移位运算符\n", + "\n", + "移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。\n", + "\n", + "如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。\n", + "\n", + "移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/URShift.java\n", + "// 测试无符号右移\n", + "public class URShift {\n", + " public static void main(String[] args) {\n", + " int i = -1;\n", + " System.out.println(Integer.toBinaryString(i));\n", + " i >>>= 10;\n", + " System.out.println(Integer.toBinaryString(i));\n", + " long l = -1;\n", + " System.out.println(Long.toBinaryString(l));\n", + " l >>>= 10;\n", + " System.out.println(Long.toBinaryString(l));\n", + " short s = -1;\n", + " System.out.println(Integer.toBinaryString(s));\n", + " s >>>= 10;\n", + " System.out.println(Integer.toBinaryString(s));\n", + " byte b = -1;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " b >>>= 10;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " b = -1;\n", + " System.out.println(Integer.toBinaryString(b));\n", + " System.out.println(Integer.toBinaryString(b>>>10));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "11111111111111111111111111111111\n", + "1111111111111111111111\n", + "1111111111111111111111111111111111111111111111111111111111111111\n", + "111111111111111111111111111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "11111111111111111111111111111111\n", + "1111111111111111111111" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在上例中,结果并未重新赋值给变量 **b** ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/BitManipulation.java\n", + "// 使用位运算符\n", + "import java.util.*;\n", + "public class BitManipulation {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " int i = rand.nextInt();\n", + " int j = rand.nextInt();\n", + " printBinaryInt(\"-1\", -1);\n", + " printBinaryInt(\"+1\", +1);\n", + " int maxpos = 2147483647;\n", + " printBinaryInt(\"maxpos\", maxpos);\n", + " int maxneg = -2147483648;\n", + " printBinaryInt(\"maxneg\", maxneg);\n", + " printBinaryInt(\"i\", i);\n", + " printBinaryInt(\"~i\", ~i);\n", + " printBinaryInt(\"-i\", -i);\n", + " printBinaryInt(\"j\", j);\n", + " printBinaryInt(\"i & j\", i & j);\n", + " printBinaryInt(\"i | j\", i | j);\n", + " printBinaryInt(\"i ^ j\", i ^ j);\n", + " printBinaryInt(\"i << 5\", i << 5);\n", + " printBinaryInt(\"i >> 5\", i >> 5);\n", + " printBinaryInt(\"(~i) >> 5\", (~i) >> 5);\n", + " printBinaryInt(\"i >>> 5\", i >>> 5);\n", + " printBinaryInt(\"(~i) >>> 5\", (~i) >>> 5);\n", + " long l = rand.nextLong();\n", + " long m = rand.nextLong();\n", + " printBinaryLong(\"-1L\", -1L);\n", + " printBinaryLong(\"+1L\", +1L);\n", + " long ll = 9223372036854775807L;\n", + " printBinaryLong(\"maxpos\", ll);\n", + " long lln = -9223372036854775808L;\n", + " printBinaryLong(\"maxneg\", lln);\n", + " printBinaryLong(\"l\", l);\n", + " printBinaryLong(\"~l\", ~l);\n", + " printBinaryLong(\"-l\", -l);\n", + " printBinaryLong(\"m\", m);\n", + " printBinaryLong(\"l & m\", l & m);\n", + " printBinaryLong(\"l | m\", l | m);\n", + " printBinaryLong(\"l ^ m\", l ^ m);\n", + " printBinaryLong(\"l << 5\", l << 5);\n", + " printBinaryLong(\"l >> 5\", l >> 5);\n", + " printBinaryLong(\"(~l) >> 5\", (~l) >> 5);\n", + " printBinaryLong(\"l >>> 5\", l >>> 5);\n", + " printBinaryLong(\"(~l) >>> 5\", (~l) >>> 5);\n", + " }\n", + "\n", + " static void printBinaryInt(String s, int i) {\n", + " System.out.println(\n", + " s + \", int: \" + i + \", binary:\\n \" +\n", + " Integer.toBinaryString(i));\n", + " }\n", + "\n", + " static void printBinaryLong(String s, long l) {\n", + " System.out.println(\n", + " s + \", long: \" + l + \", binary:\\n \" +\n", + " Long.toBinaryString(l));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果(前 32 行):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-1, int: -1, binary:\n", + "11111111111111111111111111111111\n", + "+1, int: 1, binary:\n", + "1\n", + "maxpos, int: 2147483647, binary:\n", + "1111111111111111111111111111111\n", + "maxneg, int: -2147483648, binary:\n", + "10000000000000000000000000000000\n", + "i, int: -1172028779, binary:\n", + "10111010001001000100001010010101\n", + "~i, int: 1172028778, binary:\n", + " 1000101110110111011110101101010\n", + "-i, int: 1172028779, binary:\n", + "1000101110110111011110101101011\n", + "j, int: 1717241110, binary:\n", + "1100110010110110000010100010110\n", + "i & j, int: 570425364, binary:\n", + "100010000000000000000000010100\n", + "i | j, int: -25213033, binary:\n", + "11111110011111110100011110010111\n", + "i ^ j, int: -595638397, binary:\n", + "11011100011111110100011110000011\n", + "i << 5, int: 1149784736, binary:\n", + "1000100100010000101001010100000\n", + "i >> 5, int: -36625900, binary:\n", + "11111101110100010010001000010100\n", + "(~i) >> 5, int: 36625899, binary:\n", + "10001011101101110111101011\n", + "i >>> 5, int: 97591828, binary:\n", + "101110100010010001000010100\n", + "(~i) >>> 5, int: 36625899, binary:\n", + "10001011101101110111101011\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。\n", + "\n", + "\n", + "## 三元运算符\n", + "\n", + "三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 **if-else** 语句是不同的。下面是它的表达式格式:\n", + "\n", + "**布尔表达式 ? 值 1 : 值 2**\n", + "\n", + "若表达式计算为 **true**,则返回结果 **值 1** ;如果表达式的计算为 **false**,则返回结果 **值 2**。\n", + "\n", + "当然,也可以换用普通的 **if-else** 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 **if-else** 不同的是,三元运算符是有返回结果的。请看下面的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/TernaryIfElse.java\n", + "public class TernaryIfElse {\n", + " \n", + "static int ternary(int i) {\n", + " return i < 10 ? i * 100 : i * 10;\n", + "}\n", + "\n", + "static int standardIfElse(int i) {\n", + " if(i < 10)\n", + " return i * 100;\n", + " else\n", + " return i * 10;\n", + "}\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(ternary(9));\n", + " System.out.println(ternary(10));\n", + " System.out.println(standardIfElse(9));\n", + " System.out.println(standardIfElse(10));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "900\n", + "100\n", + "900\n", + "100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看出,`ternary()` 中的代码更简短。然而,**standardIfElse()** 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。\n", + "\n", + "\n", + "## 字符串运算符\n", + "\n", + "这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 `+` 的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。\n", + "\n", + "我们注意到运用 `String +` 时有一些有趣的现象。若表达式以一个 **String** 类型开头(编译器会自动将双引号 `\"\"` 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/StringOperators.java\n", + "public class StringOperators {\n", + " public static void main(String[] args) {\n", + " int x = 0, y = 1, z = 2;\n", + " String s = \"x, y, z \";\n", + " System.out.println(s + x + y + z);\n", + " // 将 x 转换为字符串\n", + " System.out.println(x + \" \" + s);\n", + " s += \"(summed) = \"; \n", + " // 级联操作\n", + " System.out.println(s + (x + y + z));\n", + " // Integer.toString()方法的简写:\n", + " System.out.println(\"\" + x);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x, y, z 012\n", + "0 x, y, z\n", + "x, y, z (summed) = 3\n", + "0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意**:上例中第 1 输出语句的执行结果是 `012` 而并非 `3`,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 **s** 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 `+=` 运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 `s`。括号 `()` 可以控制表达式的计算顺序,以便在显示 **int** 之前对其进行实际求和。\n", + "\n", + "请注意主方法中的最后一个例子:我们经常会看到一个空字符串 `\"\"` 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 **Integer.toString()**)。\n", + "\n", + "\n", + "## 常见陷阱\n", + "\n", + "使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 `()`。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "while(x = y) {\n", + "// ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。\n", + "\n", + "唯一有种情况例外:当变量 `x` 和 `y` 都是布尔值,例如 `x=y` 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。\n", + "\n", + "在 C/C++ 里,类似的一个问题还有使用按位“与” `&` 和“或” `|` 运算,而非逻辑“与” `&&` 和“或” `||`。就象 `=` 和 `==` 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。\n", + "\n", + "\n", + "## 类型转换\n", + "\n", + "“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 **float** 变量赋值一个整数值,计算机会将 **int** 自动转换成 **float**。我们可以在程序未自动转换时显式、强制地使此类型发生转换。\n", + "\n", + "要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Casting.java\n", + "public class Casting {\n", + " public static void main(String[] args) {\n", + " int i = 200;\n", + " long lng = (long)i;\n", + " lng = i; // 没有必要的类型提升\n", + " long lng2 = (long)200;\n", + " lng2 = 200;\n", + " // 类型收缩\n", + " i = (int)lng2; // Cast required\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 **int** 型数据为 **long** 型。\n", + "\n", + "当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。\n", + "\n", + "除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。\n", + "\n", + "\n", + "### 截断和舍入\n", + "\n", + "在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/CastingNumbers.java\n", + "// 尝试转换 float 和 double 型数据为整型数据\n", + "public class CastingNumbers {\n", + " public static void main(String[] args) {\n", + " double above = 0.7, below = 0.4;\n", + " float fabove = 0.7f, fbelow = 0.4f;\n", + " System.out.println(\"(int)above: \" + (int)above);\n", + " System.out.println(\"(int)below: \" + (int)below);\n", + " System.out.println(\"(int)fabove: \" + (int)fabove);\n", + " System.out.println(\"(int)fbelow: \" + (int)fbelow);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(int)above: 0\n", + "(int)below: 0\n", + "(int)fabove: 0\n", + "(int)fbelow: 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,答案是,从 **float** 和 **double** 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 `java.lang.Math` 的 ` round()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/RoundingNumbers.java\n", + "// float 和 double 类型数据的四舍五入\n", + "public class RoundingNumbers {\n", + " public static void main(String[] args) {\n", + " double above = 0.7, below = 0.4;\n", + " float fabove = 0.7f, fbelow = 0.4f;\n", + " System.out.println(\n", + " \"Math.round(above): \" + Math.round(above));\n", + " System.out.println(\n", + " \"Math.round(below): \" + Math.round(below));\n", + " System.out.println(\n", + " \"Math.round(fabove): \" + Math.round(fabove));\n", + " System.out.println(\n", + " \"Math.round(fbelow): \" + Math.round(fbelow));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Math.round(above): 1\n", + "Math.round(below): 0\n", + "Math.round(fabove): 1\n", + "Math.round(fbelow): 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 `round()` 方法是 `java.lang` 的一部分,所以我们无需通过 `import` 就可以使用。\n", + "\n", + "\n", + "### 类型提升\n", + "\n", + "你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。\n", + "\n", + "\n", + "## Java没有sizeof\n", + "\n", + "在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。\n", + "\n", + "Java 不需要 ` sizeof()` 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。\n", + "\n", + "\n", + "## 运算符总结\n", + "\n", + "上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 `//` 过滤了。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/AllOps.java\n", + "// 测试所有基本类型的运算符操作\n", + "// 看看哪些是能被 Java 编译器接受的\n", + "public class AllOps {\n", + " // 布尔值的接收测试:\n", + " void f(boolean b) {}\n", + " void boolTest(boolean x, boolean y) {\n", + " // 算数运算符:\n", + " //- x = x * y;\n", + " //- x = x / y;\n", + " //- x = x % y;\n", + " //- x = x + y;\n", + " //- x = x - y;\n", + " //- x++;\n", + " //- x--;\n", + " //- x = +y;\n", + " //- x = -y;\n", + " // 关系运算符和逻辑运算符:\n", + " //- f(x > y);\n", + " //- f(x >= y);\n", + " //- f(x < y);\n", + " //- f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " f(!y);\n", + " x = x && y;\n", + " x = x || y;\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " //- x += y;\n", + " //- x -= y;\n", + " //- x *= y;\n", + " //- x /= y;\n", + " //- x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- char c = (char)x;\n", + " //- byte b = (byte)x;\n", + " //- short s = (short)x;\n", + " //- int i = (int)x;\n", + " //- long l = (long)x;\n", + " //- float f = (float)x;\n", + " //- double d = (double)x;\n", + " }\n", + "\n", + " void charTest(char x, char y) {\n", + " // 算数运算符:\n", + " x = (char)(x * y);\n", + " x = (char)(x / y);\n", + " x = (char)(x % y);\n", + " x = (char)(x + y);\n", + " x = (char)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (char) + y;\n", + " x = (char) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x= (char)~y;\n", + " x = (char)(x & y);\n", + " x = (char)(x | y);\n", + " x = (char)(x ^ y);\n", + " x = (char)(x << 1);\n", + " x = (char)(x >> 1);\n", + " x = (char)(x >>> 1);\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换\n", + " //- boolean bl = (boolean)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void byteTest(byte x, byte y) {\n", + " // 算数运算符:\n", + " x = (byte)(x* y);\n", + " x = (byte)(x / y);\n", + " x = (byte)(x % y);\n", + " x = (byte)(x + y);\n", + " x = (byte)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (byte) + y;\n", + " x = (byte) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " //按位运算符:\n", + " x = (byte)~y;\n", + " x = (byte)(x & y);\n", + " x = (byte)(x | y);\n", + " x = (byte)(x ^ y);\n", + " x = (byte)(x << 1);\n", + " x = (byte)(x >> 1);\n", + " x = (byte)(x >>> 1);\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void shortTest(short x, short y) {\n", + " // 算术运算符:\n", + " x = (short)(x * y);\n", + " x = (short)(x / y);\n", + " x = (short)(x % y);\n", + " x = (short)(x + y);\n", + " x = (short)(x - y);\n", + " x++;\n", + " x--;\n", + " x = (short) + y;\n", + " x = (short) - y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = (short) ~ y;\n", + " x = (short)(x & y);\n", + " x = (short)(x | y);\n", + " x = (short)(x ^ y);\n", + " x = (short)(x << 1);\n", + " x = (short)(x >> 1);\n", + " x = (short)(x >>> 1);\n", + " // Compound assignment:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void intTest(int x, int y) {\n", + " // 算术运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " x = x << 1;\n", + " x = x >> 1;\n", + " x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void longTest(long x, long y) {\n", + " // 算数运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " x = ~y;\n", + " x = x & y;\n", + " x = x | y;\n", + " x = x ^ y;\n", + " x = x << 1;\n", + " x = x >> 1;\n", + " x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " x <<= 1;\n", + " x >>= 1;\n", + " x >>>= 1;\n", + " x &= y;\n", + " x ^= y;\n", + " x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " float f = (float)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void floatTest(float x, float y) {\n", + " // 算数运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " //- x = x & y;\n", + " //- x = x | y;\n", + " //- x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " //- x &= y;\n", + " //- x ^= y;\n", + " //- x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " double d = (double)x;\n", + " }\n", + "\n", + " void doubleTest(double x, double y) {\n", + " // 算术运算符:\n", + " x = x * y;\n", + " x = x / y;\n", + " x = x % y;\n", + " x = x + y;\n", + " x = x - y;\n", + " x++;\n", + " x--;\n", + " x = +y;\n", + " x = -y;\n", + " // 关系和逻辑运算符:\n", + " f(x > y);\n", + " f(x >= y);\n", + " f(x < y);\n", + " f(x <= y);\n", + " f(x == y);\n", + " f(x != y);\n", + " //- f(!x);\n", + " //- f(x && y);\n", + " //- f(x || y);\n", + " // 按位运算符:\n", + " //- x = ~y;\n", + " //- x = x & y;\n", + " //- x = x | y;\n", + " //- x = x ^ y;\n", + " //- x = x << 1;\n", + " //- x = x >> 1;\n", + " //- x = x >>> 1;\n", + " // 联合赋值:\n", + " x += y;\n", + " x -= y;\n", + " x *= y;\n", + " x /= y;\n", + " x %= y;\n", + " //- x <<= 1;\n", + " //- x >>= 1;\n", + " //- x >>>= 1;\n", + " //- x &= y;\n", + " //- x ^= y;\n", + " //- x |= y;\n", + " // 类型转换:\n", + " //- boolean bl = (boolean)x;\n", + " char c = (char)x;\n", + " byte b = (byte)x;\n", + " short s = (short)x;\n", + " int i = (int)x;\n", + " long l = (long)x;\n", + " float f = (float)x;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意** :**boolean** 类型的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。\n", + "\n", + "在 **char**,**byte** 和 **short** 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 **int** 类型的运算则不用转换,因为默认就是 **int** 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 **int** 型的范围,这种情况下结果会发生溢出。下面的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// operators/Overflow.java\n", + "// 厉害了!内存溢出\n", + "public class Overflow {\n", + " public static void main(String[] args) {\n", + " int big = Integer.MAX_VALUE;\n", + " System.out.println(\"big = \" + big);\n", + " int bigger = big * 4;\n", + " System.out.println(\"bigger = \" + bigger);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "text" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "big = 2147483647\n", + "bigger = -4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。\n", + "\n", + "对于 **char**,**byte** 或者 **short**,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 **boolean** 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "如果你已接触过一门 C 语法风格编程语言,那么你在学习 Java 的运算符时实际上没有任何曲线。如果你觉得有难度,那么我推荐你要先去 www.OnJava8.com 观看 《Thinking in C》 的视频教程来补充一些前置知识储备。\n", + "\n", + "[^1]: 我在 *Pomona College* 大学读过两年本科,在那里 47 被称之为“魔法数字”(*magic number*),详见 [维基百科](https://en.wikipedia.org/wiki/47_(number)) 。\n", + "\n", + "[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Java", + "language": "java", + "name": "java" + }, + "language_info": { + "codemirror_mode": "java", + "file_extension": ".jshell", + "mimetype": "text/x-java-source", + "name": "Java", + "pygments_lexer": "java", + "version": "14.0.1+7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/05-Control-Flow.ipynb b/jupyter/05-Control-Flow.ipynb new file mode 100644 index 00000000..7a0f129a --- /dev/null +++ b/jupyter/05-Control-Flow.ipynb @@ -0,0 +1,1436 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "# 第五章 控制流\n", + "\n", + "> 程序必须在执行过程中控制它的世界并做出选择。 在 Java 中,你需要执行控制语句来做出选择。\n", + "\n", + "Java 使用了 C 的所有执行控制语句,因此对于熟悉 C/C++ 编程的人来说,这部分内容轻车熟路。大多数面向过程编程语言都有共通的某种控制语句。在 Java 中,涉及的关键字包括 **if-else,while,do-while,for,return,break** 和选择语句 **switch**。 Java 并不支持备受诟病的 **goto**(尽管它在某些特殊场景中依然是最行之有效的方法)。 尽管如此,在 Java 中我们仍旧可以进行类似的逻辑跳转,但较之典型的 **goto** 用法限制更多。\n", + "\n", + "\n", + "## true和false\n", + "\n", + "所有的条件语句都利用条件表达式的“真”或“假”来决定执行路径。举例:\n", + "`a == b`。它利用了条件表达式 `==` 来比较 `a` 与 `b` 的值是否相等。 该表达式返回 `true` 或 `false`。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/TrueFalse.java\n", + "public class TrueFalse {\n", + "\tpublic static void main(String[] args) {\n", + "\t\tSystem.out.println(1 == 1);\n", + "\t\tSystem.out.println(1 == 2);\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "true false " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 **注意**:在 Java 中使用数值作为布尔值是非法的,即便这种操作在 C/C++ 中是被允许的(在这些语言中,“真”为非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,那么首先需要使用条件表达式来产生 **boolean** 类型的结果,例如 `if(a != 0)`。\n", + "\n", + "## if-else\n", + "\n", + "**if-else** 语句是控制程序执行流程最基本的形式。 其中 `else` 是可选的,因此可以有两种形式的 `if`。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(Boolean-expression) \n", + "\t“statement” " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "或" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(Boolean-expression) \n", + "\t“statement”\n", + "else\n", + " “statement”" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "布尔表达式(Boolean-expression)必须生成 **boolean** 类型的结果,执行语句 `statement` 既可以是以分号 `;` 结尾的一条简单语句,也可以是包含在大括号 `{}` 内的的复合语句 —— 封闭在大括号内的一组简单语句。 凡本书中提及“statement”一词,皆表示类似的执行语句。\n", + "\n", + "下面是一个有关 **if-else** 语句的例子。`test()` 方法可以告知你两个数值之间的大小关系。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/IfElse.java\n", + "public class IfElse {\n", + " static int result = 0;\n", + " static void test(int testval, int target) {\n", + " if(testval > target)\n", + " result = +1;\n", + " else if(testval < target) // [1]\n", + " result = -1;\n", + " else\n", + " result = 0; // Match\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " test(10, 5);\n", + " System.out.println(result);\n", + " test(5, 10);\n", + " System.out.println(result);\n", + " test(5, 5);\n", + " System.out.println(result);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1\n", + "-1\n", + "0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注解**:`else if` 并非新关键字,它仅是 `else` 后紧跟的一条新 `if` 语句。\n", + "\n", + "Java 和 C/C++ 同属“自由格式”的编程语言,但通常我们会在 Java 控制流程语句中采用首部缩进的规范,以便代码更具可读性。\n", + "\n", + "\n", + "## 迭代语句\n", + "\n", + "**while**,**do-while** 和 **for** 用来控制循环语句(有时也称迭代语句)。只有控制循环的布尔表达式计算结果为 `false`,循环语句才会停止。 \n", + "\n", + "\n", + "### while\n", + "\n", + "**while** 循环的形式是:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "while(Boolean-expression) \n", + " statement" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "执行语句会在每一次循环前,判断布尔表达式返回值是否为 `true`。下例可产生随机数,直到满足特定条件。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/WhileTest.java\n", + "// 演示 while 循环\n", + "public class WhileTest {\n", + " static boolean condition() {\n", + " boolean result = Math.random() < 0.99;\n", + " System.out.print(result + \", \");\n", + " return result;\n", + " }\n", + " public static void main(String[] args) {\n", + " while(condition())\n", + " System.out.println(\"Inside 'while'\");\n", + " System.out.println(\"Exited 'while'\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "...________...________...________...________...\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "true, Inside 'while'\n", + "false, Exited 'while'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`condition()` 方法使用到了 **Math** 库的**静态**方法 `random()`。该方法的作用是产生 0 和 1 之间 (包括 0,但不包括 1) 的一个 **double** 值。\n", + "\n", + "**result** 的值是通过比较运算符 `<` 产生的 **boolean** 类型的结果。当控制台输出 **boolean** 型值时,会自动将其转换为对应的文字形式 `true` 或 `false`。此处 `while` 条件表达式代表:“仅在 `condition()` 返回 `false` 时停止循环”。\n", + "\n", + "\n", + "### do-while\n", + "\n", + "**do-while** 的格式如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "do \n", + "\tstatement\n", + "while(Boolean-expression);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**while** 和 **do-while** 之间的唯一区别是:即使条件表达式返回结果为 `false`, **do-while** 语句也至少会执行一次。 在 **while** 循环体中,如布尔表达式首次返回的结果就为 `false`,那么循环体内的语句不会被执行。实际应用中,**while** 形式比 **do-while** 更为常用。\n", + "\n", + "\n", + "### for\n", + "\n", + "**for** 循环可能是最常用的迭代形式。 该循环在第一次迭代之前执行初始化。随后,它会执行布尔表达式,并在每次迭代结束时,进行某种形式的步进。**for** 循环的形式是:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "for(initialization; Boolean-expression; step)\n", + " statement" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "初始化 (initialization) 表达式、布尔表达式 (Boolean-expression) ,或者步进 (step) 运算,都可以为空。每次迭代之前都会判断布尔表达式的结果是否成立。一旦计算结果为 `false`,则跳出 **for** 循环体并继续执行后面代码。 每次循环结束时,都会执行一次步进。\n", + "\n", + "**for** 循环通常用于“计数”任务。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/ListCharacters.java\n", + "\n", + "public class ListCharacters {\n", + " public static void main(String[] args) {\n", + " for(char c = 0; c < 128; c++)\n", + " if(Character.isLowerCase(c))\n", + " System.out.println(\"value: \" + (int)c +\n", + " \" character: \" + c);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果(前 10 行):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "value: 97 character: a\n", + "value: 98 character: b\n", + "value: 99 character: c\n", + "value: 100 character: d\n", + "value: 101 character: e\n", + "value: 102 character: f\n", + "value: 103 character: g\n", + "value: 104 character: h\n", + "value: 105 character: i\n", + "value: 106 character: j\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意**:变量 **c** 是在 **for** 循环执行时才被定义的,并不是在主方法的开头。**c** 的作用域范围仅在 **for** 循环体内。\n", + "\n", + "传统的面向过程语言如 C 需要先在代码块(block)前定义好所有变量才能够使用。这样编译器才能在创建块时,为这些变量分配内存空间。在 Java 和 C++ 中,我们可以在整个块使用变量声明,并且可以在需要时才定义变量。 这种自然的编码风格使我们的代码更容易被人理解 [^1]。\n", + "\n", + "上例使用了 **java.lang.Character** 包装类,该类不仅包含了基本类型 `char` 的值,还封装了一些有用的方法。例如这里就用到了静态方法 `isLowerCase()` 来判断字符是否为小写。\n", + "\n", + "\n", + "\n", + "#### 逗号操作符\n", + "\n", + "在 Java 中逗号运算符(这里并非指我们平常用于分隔定义和方法参数的逗号分隔符)仅有一种用法:在 **for** 循环的初始化和步进控制中定义多个变量。我们可以使用逗号分隔多个语句,并按顺序计算这些语句。**注意**:要求定义的变量类型相同。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/CommaOperator.java\n", + "\n", + "public class CommaOperator {\n", + " public static void main(String[] args) {\n", + " for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {\n", + " System.out.println(\"i = \" + i + \" j = \" + j);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 1 j = 11\n", + "i = 2 j = 4\n", + "i = 3 j = 6\n", + "i = 4 j = 8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中 **int** 类型声明包含了 `i` 和 `j`。实际上,在初始化部分我们可以定义任意数量的同类型变量。**注意**:在 Java 中,仅允许 **for** 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。\n", + "\n", + "## for-in 语法 \n", + "\n", + "Java 5 引入了更为简洁的“增强版 **for** 循环”语法来操纵数组和集合。(更多细节,可参考 [数组](./21-Arrays.md) 和 [集合](./12-Collections.md) 章节内容)。大部分文档也称其为 **for-each** 语法,但因为了不与 Java 8 新添的 `forEach()` 产生混淆,因此我称之为 **for-in** 循环。 (Python 已有类似的先例,如:**for x in sequence**)。**注意**:你可能会在其他地方看到不同叫法。\n", + "\n", + "**for-in** 无需你去创建 **int** 变量和步进来控制循环计数。 下面我们来遍历获取 **float** 数组中的元素。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/ForInFloat.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class ForInFloat {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " float[] f = new float[10];\n", + " for(int i = 0; i < 10; i++)\n", + " f[i] = rand.nextFloat();\n", + " for(float x : f)\n", + " System.out.println(x);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0.72711575\n", + "0.39982635\n", + "0.5309454\n", + "0.0534122\n", + "0.16020656\n", + "0.57799757\n", + "0.18847865\n", + "0.4170137\n", + "0.51660204\n", + "0.73734957" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中我们展示了传统 **for** 循环的用法。接下来再来看下 **for-in** 的用法。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "for(float x : f) {" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这条语句定义了一个 **float** 类型的变量 `x`,继而将每一个 `f` 的元素赋值给它。\n", + "\n", + "任何一个返回数组的方法都可以使用 **for-in** 循环语法来遍历元素。例如 **String** 类有一个方法 `toCharArray()`,返回值类型为 **char** 数组,我们可以很容易地在 **for-in** 循环中遍历它。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/ForInString.java\n", + "\n", + "public class ForInString {\n", + " public static void main(String[] args) {\n", + " for(char c: \"An African Swallow\".toCharArray())\n", + " System.out.print(c + \" \");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "A n A f r i c a n S w a l l o w" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "很快我们能在 [集合](./12-Collections.md) 章节里学习到,**for-in** 循环适用于任何可迭代(*iterable*)的 对象。\n", + "\n", + "通常,**for** 循环语句都会在一个整型数值序列中步进。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "for(int i = 0; i < 100; i++)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正因如此,除非先创建一个 **int** 数组,否则我们无法使用 **for-in** 循环来操作。为简化测试过程,我已在 `onjava` 包中封装了 **Range** 类,利用其 `range()` 方法可自动生成恰当的数组。\n", + "\n", + "在 [封装](./07-Implementation-Hiding.md)(Implementation Hiding)这一章里我们介绍了静态导入(static import),无需了解细节就可以直接使用。 有关静态导入的语法,可以在 **import** 语句中看到:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/ForInInt.java\n", + "\n", + "import static onjava.Range.*;\n", + "\n", + "public class ForInInt {\n", + " public static void main(String[] args) {\n", + " for(int i : range(10)) // 0..9\n", + " System.out.print(i + \" \");\n", + " System.out.println();\n", + " for(int i : range(5, 10)) // 5..9\n", + " System.out.print(i + \" \");\n", + " System.out.println();\n", + " for(int i : range(5, 20, 3)) // 5..20 step 3\n", + " System.out.print(i + \" \");\n", + " System.out.println();\n", + " for(int i : range(20, 5, -3)) // Count down\n", + " System.out.print(i + \" \");\n", + " System.out.println();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0 1 2 3 4 5 6 7 8 9\n", + "5 6 7 8 9\n", + "5 8 11 14 17\n", + "20 17 14 11 8" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`range()` 方法已被 [重载](./06-Housekeeping.md#方法重载)(重载:同名方法,参数列表或类型不同)。上例中 `range()` 方法有多种重载形式:第一种产生从 0 至范围上限(不包含)的值;第二种产生参数一至参数二(不包含)范围内的整数值;第三种形式有一个步进值,因此它每次的增量为该值;第四种 `range()` 表明还可以递减。`range()` 无参方法是该生成器最简单的版本。有关内容会在本书稍后介绍。\n", + "\n", + "`range()` 的使用提高了代码可读性,让 **for-in** 循环在本书中适应更多的代码示例场景。\n", + "\n", + "请注意,`System.out.print()` 不会输出换行符,所以我们可以分段输出同一行。\n", + "\n", + "*for-in* 语法可以节省我们编写代码的时间。 更重要的是,它提高了代码可读性以及更好地描述代码意图(获取数组的每个元素)而不是详细说明这操作细节(创建索引,并用它来选择数组元素) 本书推荐使用 *for-in* 语法。\n", + "\n", + "## return\n", + "\n", + "在 Java 中有几个关键字代表无条件分支,这意味无需任何测试即可发生。这些关键字包括 **return**,**break**,**continue** 和跳转到带标签语句的方法,类似于其他语言中的 **goto**。\n", + "\n", + "**return** 关键字有两方面的作用:1.指定一个方法返回值 (在方法返回类型非 **void** 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 `return` 的这些特点来改写上例 `IfElse.java` 文件中的 `test()` 方法。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/TestWithReturn.java\n", + "\n", + "public class TestWithReturn {\n", + " static int test(int testval, int target) {\n", + " if(testval > target)\n", + " return +1;\n", + " if(testval < target)\n", + " return -1;\n", + " return 0; // Match\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(test(10, 5));\n", + " System.out.println(test(5, 10));\n", + " System.out.println(test(5, 5));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1\n", + "-1\n", + "0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里不需要 `else`,因为该方法执行到 `return` 就结束了。\n", + "\n", + "如果在方法签名中定义了返回值类型为 **void**,那么在代码执行结束时会有一个隐式的 **return**。 也就是说我们不用在总是在方法中显式地包含 **return** 语句。 **注意**:如果你的方法声明的返回值类型为非 **void** 类型,那么则必须确保每个代码路径都返回一个值。\n", + "\n", + "## break 和 continue\n", + "\n", + "在任何迭代语句的主体内,都可以使用 **break** 和 **continue** 来控制循环的流程。 其中,**break** 表示跳出当前循环体。而 **continue** 表示停止本次循环,开始下一次循环。\n", + "\n", + "下例向大家展示 **break** 和 **continue** 在 **for**、**while** 循环中的使用。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/BreakAndContinue.java\n", + "// Break 和 continue 关键字\n", + "\n", + "import static onjava.Range.*;\n", + "\n", + "public class BreakAndContinue {\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 100; i++) { // [1]\n", + " if(i == 74) break; // 跳出循环\n", + " if(i % 9 != 0) continue; // 下一次循环\n", + " System.out.print(i + \" \");\n", + " }\n", + " System.out.println();\n", + " // 使用 for-in 循环:\n", + " for(int i : range(100)) { // [2]\n", + " if(i == 74) break; // 跳出循环\n", + " if(i % 9 != 0) continue; // 下一次循环\n", + " System.out.print(i + \" \");\n", + " }\n", + " System.out.println();\n", + " int i = 0;\n", + " // \"无限循环\":\n", + " while(true) { // [3]\n", + " i++;\n", + " int j = i * 27;\n", + " if(j == 1269) break; // 跳出循环\n", + " if(i % 10 != 0) continue; // 循环顶部\n", + " System.out.print(i + \" \");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0 9 18 27 36 45 54 63 72\n", + "0 9 18 27 36 45 54 63 72\n", + "10 20 30 40" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[1]** 在这个 **for** 循环中,`i` 的值永远不会达到 100,因为一旦 `i` 等于 74,**break** 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要 **break**。因为 `i` 不能被 9 整除,**continue** 语句就会使循环从头开始。这使 **i** 递增)。如果能够整除,则将值显示出来。\n", + " **[2]** 使用 **for-in** 语法,结果相同。\n", + " **[3]** 无限 **while** 循环。循环内的 **break** 语句可中止循环。**注意**,**continue** 语句可将控制权移回循环的顶部,而不会执行 **continue** 之后的任何操作。 因此,只有当 `i` 的值可被 10 整除时才会输出。在输出中,显示值 0,因为 `0%9` 产生 0。还有一种无限循环的形式: `for(;;)`。 在编译器看来,它与 `while(true)` 无异,使用哪种完全取决于你的编程品味。\n", + "\n", + "\n", + "## 臭名昭著的 goto\n", + "\n", + "[**goto** 关键字](https://en.wikipedia.org/wiki/Goto) 很早就在程序设计语言中出现。事实上,**goto** 起源于[汇编](https://en.wikipedia.org/wiki/Assembly_language)(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。\n", + "\n", + "一个源码级别跳转的 **goto**,为何招致名誉扫地呢?若程序总是从一处跳转到另一处,还有什么办法能识别代码的控制流程呢?随着 *Edsger Dijkstra*发表著名的 “Goto 有害” 论(*Goto considered harmful*)以后,**goto** 便从此失宠。甚至有人建议将它从关键字中剔除。\n", + "\n", + "正如上述提及的经典情况,我们不应走向两个极端。问题不在 **goto**,而在于过度使用 **goto**。在极少数情况下,**goto** 实际上是控制流程的最佳方式。\n", + "\n", + "尽管 **goto** 仍是 Java 的一个保留字,但其并未被正式启用。可以说, Java 中并不支持 **goto**。然而,在 **break** 和 **continue** 这两个关键字的身上,我们仍能看出一些 **goto** 的影子。它们并不属于一次跳转,而是中断循环语句的一种方法。之所以把它们纳入 **goto** 问题中一起讨论,是由于它们使用了相同的机制:标签。\n", + "\n", + "“标签”是后面跟一个冒号的标识符。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "label1:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 —— 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 **break** 和 **continue** 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "label1:\n", + "outer-iteration { \n", + " inner-iteration {\n", + " // ...\n", + " break; // [1] \n", + " // ...\n", + " continue; // [2] \n", + " // ...\n", + " continue label1; // [3] \n", + " // ...\n", + " break label1; // [4] \n", + " } \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[1]** **break** 中断内部循环,并在外部循环结束。\n", + "**[2]** **continue** 移回内部循环的起始处。但在条件 3 中,**continue label1** 却同时中断内部循环以及外部循环,并移至 **label1** 处。\n", + "**[3]** 随后,它实际是继续循环,但却从外部循环开始。\n", + "**[4]** **break label1** 也会中断所有循环,并回到 **label1** 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。\n", + "\n", + "下面是 **for** 循环的一个例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/LabeledFor.java\n", + "// 搭配“标签 break”的 for 循环中使用 break 和 continue\n", + "\n", + "public class LabeledFor {\n", + " public static void main(String[] args) {\n", + " int i = 0;\n", + " outer: // 此处不允许存在执行语句\n", + " for(; true ;) { // 无限循环\n", + " inner: // 此处不允许存在执行语句\n", + " for(; i < 10; i++) {\n", + " System.out.println(\"i = \" + i);\n", + " if(i == 2) {\n", + " System.out.println(\"continue\");\n", + " continue;\n", + " }\n", + " if(i == 3) {\n", + " System.out.println(\"break\");\n", + " i++; // 否则 i 永远无法获得自增 \n", + " // 获得自增 \n", + " break;\n", + " }\n", + " if(i == 7) {\n", + " System.out.println(\"continue outer\");\n", + " i++; // 否则 i 永远无法获得自增 \n", + " // 获得自增 \n", + " continue outer;\n", + " }\n", + " if(i == 8) {\n", + " System.out.println(\"break outer\");\n", + " break outer;\n", + " }\n", + " for(int k = 0; k < 5; k++) {\n", + " if(k == 3) {\n", + " System.out.println(\"continue inner\");\n", + " continue inner;\n", + " }\n", + " }\n", + " }\n", + " }\n", + " // 在此处无法 break 或 continue 标签\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 0\n", + "continue inner\n", + "i = 1\n", + "continue inner\n", + "i = 2\n", + "continue\n", + "i = 3\n", + "break\n", + "i = 4\n", + "continue inner\n", + "i = 5\n", + "continue inner\n", + "i = 6\n", + "continue inner\n", + "i = 7\n", + "continue outer\n", + "i = 8\n", + "break outer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意 **break** 会中断 **for** 循环,而且在抵达 **for** 循环的末尾之前,递增表达式不会执行。由于 **break** 跳过了递增表达式,所以递增会在 `i==3` 的情况下直接执行。在 `i==7` 的情况下,`continue outer` 语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。\n", + "\n", + "如果没有 **break outer** 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于 **break** 本身只能中断最内层的循环(对于 **continue** 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个 **return** 即可。\n", + "\n", + "下面这个例子向大家展示了带标签的 **break** 以及 **continue** 语句在 **while** 循环中的用法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/LabeledWhile.java\n", + "// 带标签的 break 和 conitue 在 while 循环中的使用\n", + "\n", + "public class LabeledWhile {\n", + " public static void main(String[] args) {\n", + " int i = 0;\n", + " outer:\n", + " while(true) {\n", + " System.out.println(\"Outer while loop\");\n", + " while(true) {\n", + " i++;\n", + " System.out.println(\"i = \" + i);\n", + " if(i == 1) {\n", + " System.out.println(\"continue\");\n", + " continue;\n", + " }\n", + " if(i == 3) {\n", + " System.out.println(\"continue outer\");\n", + " continue outer;\n", + " }\n", + " if(i == 5) {\n", + " System.out.println(\"break\");\n", + " break;\n", + " }\n", + " if(i == 7) {\n", + " System.out.println(\"break outer\");\n", + " break outer;\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Outer while loop\n", + "i = 1\n", + "continue\n", + "i = 2\n", + "i = 3\n", + "continue outer\n", + "Outer while loop\n", + "i = 4\n", + "i = 5\n", + "break\n", + "Outer while loop\n", + "i = 6\n", + "i = 7\n", + "break outer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "同样的规则亦适用于 **while**:\n", + "\n", + "1. 简单的一个 **continue** 会退回最内层循环的开头(顶部),并继续执行。\n", + "\n", + "2. 带有标签的 **continue** 会到达标签的位置,并重新进入紧接在那个标签后面的循环。\n", + "\n", + "3. **break** 会中断当前循环,并移离当前标签的末尾。\n", + "\n", + "4. 带标签的 **break** 会中断当前循环,并移离由那个标签指示的循环的末尾。\n", + "\n", + "大家要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 **break** 或 **continue**。\n", + "\n", + "**break** 和 **continue** 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。\n", + "\n", + "在 *Dijkstra* 的 **“Goto 有害”** 论文中,他最反对的就是标签,而非 **goto**。他观察到 BUG 的数量似乎随着程序中标签的数量而增加[^2]。标签和 **goto** 使得程序难以分析。但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制,无法用于以临时方式传输控制。由此也引出了一个有趣的情形:对语言能力的限制,反而使它这项特性更加有价值。\n", + "\n", + "\n", + "## switch\n", + "\n", + "**switch** 有时也被划归为一种选择语句。根据整数表达式的值,**switch** 语句可以从一系列代码中选出一段去执行。它的格式如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "switch(integral-selector) {\n", + "\tcase integral-value1 : statement; break;\n", + "\tcase integral-value2 : statement;\tbreak;\n", + "\tcase integral-value3 : statement;\tbreak;\n", + "\tcase integral-value4 : statement;\tbreak;\n", + "\tcase integral-value5 : statement;\tbreak;\n", + "\t// ...\n", + "\tdefault: statement;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中,**integral-selector** (整数选择因子)是一个能够产生整数值的表达式,**switch** 能够将这个表达式的结果与每个 **integral-value** (整数值)相比较。若发现相符的,就执行对应的语句(简单或复合语句,其中并不需要括号)。若没有发现相符的,就执行 **default** 语句。\n", + "\n", + "在上面的定义中,大家会注意到每个 **case** 均以一个 **break** 结尾。这样可使执行流程跳转至 **switch** 主体的末尾。这是构建 **switch** 语句的一种传统方式,但 **break** 是可选的。若省略 **break,** 会继续执行后面的 **case** 语句的代码,直到遇到一个 **break** 为止。通常我们不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的 **default** 语句没有 **break**,因为执行流程已到了 **break** 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在 **default** 语句的末尾放置一个 **break**,尽管它并没有任何实际的作用。\n", + "\n", + "**switch** 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 **int** 或 **char** 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 **if** 语句。 在[下一章的结尾](./06-Housekeeping.md#枚举类型) 中,我们将会了解到**枚举类型**被用来搭配 **switch** 工作,并优雅地解决了这种限制。\n", + "\n", + "下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/VowelsAndConsonants.java\n", + "\n", + "// switch 执行语句的演示\n", + "import java.util.*;\n", + "\n", + "public class VowelsAndConsonants {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " for(int i = 0; i < 100; i++) {\n", + " int c = rand.nextInt(26) + 'a';\n", + " System.out.print((char)c + \", \" + c + \": \");\n", + " switch(c) {\n", + " case 'a':\n", + " case 'e':\n", + " case 'i':\n", + " case 'o':\n", + " case 'u': System.out.println(\"vowel\");\n", + " break;\n", + " case 'y':\n", + " case 'w': System.out.println(\"Sometimes vowel\");\n", + " break;\n", + " default: System.out.println(\"consonant\");\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "y, 121: Sometimes vowel\n", + "n, 110: consonant\n", + "z, 122: consonant\n", + "b, 98: consonant\n", + "r, 114: consonant\n", + "n, 110: consonant\n", + "y, 121: Sometimes vowel\n", + "g, 103: consonant\n", + "c, 99: consonant\n", + "f, 102: consonant\n", + "o, 111: vowel\n", + "w, 119: Sometimes vowel\n", + "z, 122: consonant\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于 `Random.nextInt(26)` 会产生 0 到 25 之间的一个值,所以在其上加上一个偏移量 `a`,即可产生小写字母。在 **case** 语句中,使用单引号引起的字符也会产生用于比较的整数值。\n", + "\n", + "请注意 **case** 语句能够堆叠在一起,为一段代码形成多重匹配,即只要符合多种条件中的一种,就执行那段特别的代码。这时也应该注意将 **break** 语句置于特定 **case** 的末尾,否则控制流程会继续往下执行,处理后面的 **case**。在下面的语句中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int c = rand.nextInt(26) + 'a';" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此处 `Random.nextInt()` 将产生 0~25 之间的一个随机 **int** 值,它将被加到 `a` 上。这表示 `a` 将自动被转换为 **int** 以执行加法。为了把 `c` 当作字符打印,必须将其转型为 **char**;否则,将会输出整数。\n", + "\n", + "\n", + "\n", + "## switch 字符串\n", + "\n", + "Java 7 增加了在字符串上 **switch** 的用法。 下例展示了从一组 **String** 中选择可能值的传统方法,以及新式方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/StringSwitch.java\n", + "\n", + "public class StringSwitch {\n", + " public static void main(String[] args) {\n", + " String color = \"red\";\n", + " // 老的方式: 使用 if-then 判断\n", + " if(\"red\".equals(color)) {\n", + " System.out.println(\"RED\");\n", + " } else if(\"green\".equals(color)) {\n", + " System.out.println(\"GREEN\");\n", + " } else if(\"blue\".equals(color)) {\n", + " System.out.println(\"BLUE\");\n", + " } else if(\"yellow\".equals(color)) {\n", + " System.out.println(\"YELLOW\");\n", + " } else {\n", + " System.out.println(\"Unknown\");\n", + " }\n", + " // 新的方法: 字符串搭配 switch\n", + " switch(color) {\n", + " case \"red\":\n", + " System.out.println(\"RED\");\n", + " break;\n", + " case \"green\":\n", + " System.out.println(\"GREEN\");\n", + " break;\n", + " case \"blue\":\n", + " System.out.println(\"BLUE\");\n", + " break;\n", + " case \"yellow\":\n", + " System.out.println(\"YELLOW\");\n", + " break;\n", + " default:\n", + " System.out.println(\"Unknown\");\n", + " break;\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RED\n", + "RED" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦理解了 **switch**,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。\n", + "\n", + "作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、 [0,1)、(0,1] 、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”)\n", + "\n", + "下面是一个可能提供答案的测试程序。 所有命令行参数都作为 **String** 对象传递,因此我们可以 **switch** 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 `args` 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用**空字符串** `\"\"` 替代;否则,选择 `args` 数组中的第一个元素:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// control/RandomBounds.java\n", + "\n", + "// Math.random() 会产生 0.0 和 1.0 吗?\n", + "// {java RandomBounds lower}\n", + "import onjava.*;\n", + "\n", + "public class RandomBounds {\n", + " public static void main(String[] args) {\n", + " new TimedAbort(3);\n", + " switch(args.length == 0 ? \"\" : args[0]) {\n", + " case \"lower\":\n", + " while(Math.random() != 0.0)\n", + " ; // 保持重试\n", + " System.out.println(\"Produced 0.0!\");\n", + " break;\n", + " case \"upper\":\n", + " while(Math.random() != 1.0)\n", + " ; // 保持重试\n", + " System.out.println(\"Produced 1.0!\");\n", + " break;\n", + " default:\n", + " System.out.println(\"Usage:\");\n", + " System.out.println(\"\\tRandomBounds lower\");\n", + " System.out.println(\"\\tRandomBounds upper\");\n", + " System.exit(1);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要运行该程序,请键入以下任一命令:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "java RandomBounds lower \n", + "// 或者\n", + "java RandomBounds upper" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 `onjava` 包中的 **TimedAbort** 类可使程序在三秒后中止。从结果来看,似乎 `Math.random()` 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 **double** 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:`Math.random()` 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1)来表示。由此可知,我们必须小心分析实验并了解它们的局限性。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "本章总结了我们对大多数编程语言中出现的基本特性的探索:计算,运算符优先级,类型转换,选择和迭代。 现在让我们准备好,开始步入面向对象和函数式编程的世界吧。 下一章的内容涵盖了 Java 编程中的重要问题:对象的[初始化和清理](./06-Housekeeping.md)。紧接着,还会介绍[封装](./07-Implementation-Hiding.md)(implementation hiding)的核心概念。\n", + "\n", + "\n", + "[^1]: 在早期的语言中,许多决策都是基于让编译器设计者的体验更好。 但在现代语言设计中,许多决策都是为了提高语言使用者的体验,尽管有时会有妥协 —— 这通常会让语言设计者后悔。\n", + "\n", + "[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误](https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation) 的例子。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/06-Housekeeping.ipynb b/jupyter/06-Housekeeping.ipynb new file mode 100644 index 00000000..c64222a9 --- /dev/null +++ b/jupyter/06-Housekeeping.ipynb @@ -0,0 +1,3097 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第六章 初始化和清理\n", + "\n", + "\"不安全\"的编程是造成编程代价昂贵的罪魁祸首之一。有两个安全性问题:初始化和清理。C 语言中很多的 bug 都是因为程序员忘记初始化导致的。尤其是很多类库的使用者不知道如何初始化类库组件,甚至他们必须得去初始化。清理则是另一个特殊的问题,因为当你使用一个元素做完事后就不会去关心这个元素,所以你很容易忘记清理它。这样就造成了元素使用的资源滞留不会被回收,直到程序消耗完所有的资源(特别是内存)。\n", + "\n", + "C++ 引入了构造器的概念,这是一个特殊的方法,每创建一个对象,这个方法就会被自动调用。Java 采用了构造器的概念,另外还使用了垃圾收集器(Garbage Collector, GC)去自动回收不再被使用的对象所占的资源。这一章将讨论初始化和清理的问题,以及在 Java 中对它们的支持。\n", + "\n", + "\n", + "\n", + "## 利用构造器保证初始化\n", + "\n", + "你可能想为每个类创建一个 `initialize()` 方法,该方法名暗示着在使用类之前需要先调用它。不幸的是,用户必须得记得去调用它。在 Java 中,类的设计者通过构造器保证每个对象的初始化。如果一个类有构造器,那么 Java 会在用户使用对象之前(即对象刚创建完成)自动调用对象的构造器方法,从而保证初始化。下个挑战是如何命名构造器方法。存在两个问题:第一个是任何命名都可能与类中其他已有元素的命名冲突;第二个是编译器必须始终知道构造器方法名称,从而调用它。C++ 的解决方法看起来是最简单且最符合逻辑的,所以 Java 中使用了同样的方式:构造器名称与类名相同。在初始化过程中自动调用构造器方法是有意义的。\n", + "\n", + "以下示例是包含了一个构造器的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/SimpleConstructor.java\n", + "// Demonstration of a simple constructor\n", + "\n", + "class Rock {\n", + " Rock() { // 这是一个构造器\n", + " System.out.print(\"Rock \");\n", + " }\n", + "}\n", + "\n", + "public class SimpleConstructor {\n", + " public static void main(String[] args) {\n", + " for (int i = 0; i < 10; i++) {\n", + " new Rock();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,当创建一个对象时:`new Rock()` ,内存被分配,构造器被调用。构造器保证了对象在你使用它之前进行了正确的初始化。\n", + "\n", + "有一点需要注意,构造器方法名与类名相同,不需要符合首字母小写的编程风格。在 C++ 中,无参构造器被称为默认构造器,这个术语在 Java 出现之前使用了很多年。但是,出于一些原因,Java 设计者们决定使用无参构造器这个名称,我(作者)认为这种叫法笨拙而且没有必要,所以我打算继续使用默认构造器。Java 8 引入了 **default** 关键字修饰方法,所以算了,我还是用无参构造器的叫法吧。\n", + "\n", + "跟其他方法一样,构造器方法也可以传入参数来定义如何创建一个对象。之前的例子稍作修改,使得构造器接收一个参数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/SimpleConstructor2.java\n", + "// Constructors can have arguments\n", + "\n", + "class Rock2 {\n", + " Rock2(int i) {\n", + " System.out.print(\"Rock \" + i + \" \");\n", + " }\n", + "}\n", + "\n", + "public class SimpleConstructor2 {\n", + " public static void main(String[] args) {\n", + " for (int i = 0; i < 8; i++) {\n", + " new Rock2(i);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果类 **Tree** 有一个构造方法,只接收一个参数用来表示树的高度,那么你可以像下面这样创建一棵树:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Tree t = new Tree(12); // 12-foot 树" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果 **Tree(int)** 是唯一的构造器,那么编译器就不允许你以其他任何方式创建 **Tree** 类型的对象。\n", + "\n", + "构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,你看不到对 `initialize()` 方法的显式调用,而从概念上来看,`initialize()` 方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。\n", + "\n", + "构造器没有返回值,它是一种特殊的方法。但它和返回类型为 `void` 的普通方法不同,普通方法可以返回空值,你还能选择让它返回别的类型;而构造器没有返回值,却同时也没有给你选择的余地(`new` 表达式虽然返回了刚创建的对象的引用,但构造器本身却没有返回任何值)。如果它有返回值,并且你也可以自己选择让它返回什么,那么编译器就还得知道接下来该怎么处理那个返回值(这个返回值没有接收者)。\n", + "\n", + "\n", + "\n", + "\n", + "## 方法重载\n", + "\n", + "任何编程语言中都具备的一项重要特性就是命名。当你创建一个对象时,就会给此对象分配的内存空间命名。方法是行为的命名。你通过名字指代所有的对象,属性和方法。良好命名的系统易于理解和修改。就好比写散文——目的是与读者沟通。\n", + "\n", + "将人类语言细微的差别映射到编程语言中会产生一个问题。通常,相同的词可以表达多种不同的含义——它们被\"重载\"了。特别是当含义的差别很小时,这会更加有用。你会说\"清洗衬衫\"、\"清洗车\"和\"清洗狗\"。而如果硬要这么说就会显得很愚蠢:\"以洗衬衫的方式洗衬衫\"、\"以洗车的方式洗车\"和\"以洗狗的方式洗狗\",因为听众根本不需要区分行为的动作。大多数人类语言都具有\"冗余\"性,所以即使漏掉几个词,你也能明白含义。你不需要对每个概念都使用不同的词汇——可以从上下文推断出含义。\n", + "\n", + "大多数编程语言(尤其是 C 语言)要求为每个方法(在这些语言中经常称为函数)提供一个独一无二的标识符。所以,你不能有一个 `print()` 函数既能打印整型,也能打印浮点型——每个函数名都必须不同。\n", + "\n", + "在 Java (C++) 中,还有一个因素也促使了必须使用方法重载:构造器。因为构造器方法名肯定是与类名相同,所以一个类中只会有一个构造器名。那么你怎么通过不同的方式创建一个对象呢?例如,你想创建一个类,这个类的初始化方式有两种:一种是标准化方式,另一种是从文件中读取信息的方式。你需要两个构造器:无参构造器和有一个 **String** 类型参数的构造器,该参数传入文件名。两个构造器具有相同的名字——与类名相同。因此,方法重载是必要的,它允许方法具有相同的方法名但接收的参数不同。尽管方法重载对于构造器是重要的,但是也可以对任何方法很方便地进行重载。\n", + "\n", + "下例展示了如何重载构造器和方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Overloading.java\n", + "// Both constructor and ordinary method overloading\n", + "\n", + "class Tree {\n", + " int height;\n", + " Tree() {\n", + " System.out.println(\"Planting a seedling\");\n", + " height = 0;\n", + " }\n", + " Tree(int initialHeight) {\n", + " height = initialHeight;\n", + " System.out.println(\"Creating new Tree that is \" + height + \" feet tall\");\n", + " }\n", + " void info() {\n", + " System.out.println(\"Tree is \" + height + \" feet tall\");\n", + " }\n", + " void info(String s) {\n", + " System.out.println(s + \": Tree is \" + height + \" feet tall\");\n", + " }\n", + "}\n", + "public class Overloading {\n", + " public static void main(String[] args) {\n", + " for (int i = 0; i < 5; i++) {\n", + " Tree t = new Tree(i);\n", + " t.info();\n", + " t.info(\"overloaded method\");\n", + " }\n", + " new Tree(); \n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Creating new Tree that is 0 feet tall\n", + "Tree is 0 feet tall\n", + "overloaded method: Tree is 0 feet tall\n", + "Creating new Tree that is 1 feet tall\n", + "Tree is 1 feet tall\n", + "overloaded method: Tree is 1 feet tall\n", + "Creating new Tree that is 2 feet tall\n", + "Tree is 2 feet tall\n", + "overloaded method: Tree is 2 feet tall\n", + "Creating new Tree that is 3 feet tall\n", + "Tree is 3 feet tall\n", + "overloaded method: Tree is 3 feet tall\n", + "Creating new Tree that is 4 feet tall\n", + "Tree is 4 feet tall\n", + "overloaded method: Tree is 4 feet tall\n", + "Planting a seedling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一个 **Tree** 对象既可以是一颗树苗,使用无参构造器创建,也可以是一颗在温室中已长大的树,已经有一定高度,这时候,就需要使用有参构造器创建。\n", + "\n", + "你也许想以多种方式调用 `info()` 方法。比如,如果你想打印额外的消息,就可以使用 `info(String)` 方法。如果你无话可说,就可以使用 `info()` 方法。用两个命名定义完全相同的概念看起来很奇怪,而使用方法重载,你就可以使用一个命名来定义一个概念。\n", + "\n", + "### 区分重载方法\n", + "\n", + "如果两个方法命名相同,Java是怎么知道你调用的是哪个呢?有一条简单的规则:每个被重载的方法必须有独一无二的参数列表。你稍微思考下,就会很明了了,除了通过参数列表的不同来区分两个相同命名的方法,其他也没什么方式了。你甚至可以根据参数列表中的参数顺序来区分不同的方法,尽管这会造成代码难以维护。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OverloadingOrder.java\n", + "// Overloading based on the order of the arguments\n", + "\n", + "public class OverloadingOrder {\n", + " static void f(String s, int i) {\n", + " System.out.println(\"String: \" + s + \", int: \" + i);\n", + " }\n", + "\n", + " static void f(int i, String s) {\n", + " System.out.println(\"int: \" + i + \", String: \" + s);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " f(\"String first\", 1);\n", + " f(99, \"Int first\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "String: String first, int: 1\n", + "int: 99, String: Int first" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "两个 `f()` 方法具有相同的参数,但是参数顺序不同,根据这个就可以区分它们。\n", + "\n", + "### 重载与基本类型\n", + "\n", + "基本类型可以自动从较小的类型转型为较大的类型。当这与重载结合时,这会令人有点困惑,下面是一个这样的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/PrimitiveOverloading.java\n", + "// Promotion of primitives and overloading\n", + "\n", + "public class PrimitiveOverloading {\n", + " void f1(char x) {\n", + " System.out.print(\"f1(char)\");\n", + " }\n", + " void f1(byte x) {\n", + " System.out.print(\"f1(byte)\");\n", + " }\n", + " void f1(short x) {\n", + " System.out.print(\"f1(short)\");\n", + " }\n", + " void f1(int x) {\n", + " System.out.print(\"f1(int)\");\n", + " }\n", + " void f1(long x) {\n", + " System.out.print(\"f1(long)\");\n", + " }\n", + " void f1(float x) {\n", + " System.out.print(\"f1(float)\");\n", + " }\n", + " void f1(double x) {\n", + " System.out.print(\"f1(double)\");\n", + " }\n", + " void f2(byte x) {\n", + " System.out.print(\"f2(byte)\");\n", + " }\n", + " void f2(short x) {\n", + " System.out.print(\"f2(short)\");\n", + " }\n", + " void f2(int x) {\n", + " System.out.print(\"f2(int)\");\n", + " }\n", + " void f2(long x) {\n", + " System.out.print(\"f2(long)\");\n", + " }\n", + " void f2(float x) {\n", + " System.out.print(\"f2(float)\");\n", + " }\n", + " void f2(double x) {\n", + " System.out.print(\"f2(double)\");\n", + " }\n", + " void f3(short x) {\n", + " System.out.print(\"f3(short)\");\n", + " }\n", + " void f3(int x) {\n", + " System.out.print(\"f3(int)\");\n", + " }\n", + " void f3(long x) {\n", + " System.out.print(\"f3(long)\");\n", + " }\n", + " void f3(float x) {\n", + " System.out.print(\"f3(float)\");\n", + " }\n", + " void f3(double x) {\n", + " System.out.print(\"f3(double)\");\n", + " }\n", + " void f4(int x) {\n", + " System.out.print(\"f4(int)\");\n", + " }\n", + " void f4(long x) {\n", + " System.out.print(\"f4(long)\");\n", + " }\n", + " void f4(float x) {\n", + " System.out.print(\"f4(float)\");\n", + " }\n", + " void f4(double x) {\n", + " System.out.print(\"f4(double)\");\n", + " }\n", + " void f5(long x) {\n", + " System.out.print(\"f5(long)\");\n", + " }\n", + " void f5(float x) {\n", + " System.out.print(\"f5(float)\");\n", + " }\n", + " void f5(double x) {\n", + " System.out.print(\"f5(double)\");\n", + " }\n", + " void f6(float x) {\n", + " System.out.print(\"f6(float)\");\n", + " }\n", + " void f6(double x) {\n", + " System.out.print(\"f6(double)\");\n", + " }\n", + " void f7(double x) {\n", + " System.out.print(\"f7(double)\");\n", + " }\n", + " void testConstVal() {\n", + " System.out.print(\"5: \");\n", + " f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);\n", + " System.out.println();\n", + " }\n", + " void testChar() {\n", + " char x = 'x';\n", + " System.out.print(\"char: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testByte() {\n", + " byte x = 0;\n", + " System.out.print(\"byte: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testShort() {\n", + " short x = 0;\n", + " System.out.print(\"short: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testInt() {\n", + " int x = 0;\n", + " System.out.print(\"int: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testLong() {\n", + " long x = 0;\n", + " System.out.print(\"long: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testFloat() {\n", + " float x = 0;\n", + " System.out.print(\"float: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + " void testDouble() {\n", + " double x = 0;\n", + " System.out.print(\"double: \");\n", + " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", + " System.out.println();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " PrimitiveOverloading p = new PrimitiveOverloading();\n", + " p.testConstVal();\n", + " p.testChar();\n", + " p.testByte();\n", + " p.testShort();\n", + " p.testInt();\n", + " p.testLong();\n", + " p.testFloat();\n", + " p.testDouble();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "5: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", + "char: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", + "byte: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double)\n", + "short: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double)\n", + "int: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", + "long: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double)\n", + "float: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double)\n", + "double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果传入的参数类型大于方法期望接收的参数类型,你必须首先做下转换,如果你不做的话,编译器就会报错。\n", + "\n", + "### 返回值的重载\n", + "\n", + "经常会有人困惑,\"为什么只能通过类名和参数列表,不能通过方法的返回值区分方法呢?\"。例如以下两个方法,它们有相同的命名和参数,但是很容易区分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void f(){}\n", + "int f() {return 1;}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有些情况下,编译器很容易就可以从上下文准确推断出该调用哪个方法,如 `int x = f()`。\n", + "\n", + "但是,你可以调用一个方法且忽略返回值。这叫做调用一个函数的副作用,因为你不在乎返回值,只是想利用方法做些事。所以如果你直接调用 `f()`,Java 编译器就不知道你想调用哪个方法,阅读者也不明所以。因为这个原因,所以你不能根据返回值类型区分重载的方法。为了支持新特性,Java 8 在一些具体情形下提高了猜测的准确度,但是通常来说并不起作用。\n", + "\n", + "\n", + "\n", + "## 无参构造器\n", + "\n", + "如前文所说,一个无参构造器就是不接收参数的构造器,用来创建一个\"默认的对象\"。如果你创建一个类,类中没有构造器,那么编译器就会自动为你创建一个无参构造器。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/DefaultConstructor.java\n", + "class Bird {}\n", + "public class DefaultConstructor {\n", + " public static void main(String[] args) {\n", + " Bird bird = new Bird(); // 默认的\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "表达式 `new Bird()` 创建了一个新对象,调用了无参构造器,尽管在 **Bird** 类中并没有显式的定义无参构造器。试想如果没有构造器,我们如何创建一个对象呢。但是,一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器。如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/NoSynthesis.java\n", + "class Bird2 {\n", + " Bird2(int i) {}\n", + " Bird2(double d) {}\n", + "}\n", + "public class NoSynthesis {\n", + " public static void main(String[] args) {\n", + " //- Bird2 b = new Bird2(); // No default\n", + " Bird2 b2 = new Bird2(1);\n", + " Bird2 b3 = new Bird2(1.0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你调用了 `new Bird2()` ,编译器会提示找不到匹配的构造器。当类中没有构造器时,编译器会说\"你一定需要构造器,那么让我为你创建一个吧\"。但是如果类中有构造器,编译器会说\"你已经写了构造器了,所以肯定知道你在做什么,如果你没有创建默认构造器,说明你本来就不需要\"。\n", + "\n", + "\n", + "\n", + "## this关键字\n", + "\n", + "对于两个相同类型的对象 **a** 和 **b**,你可能在想如何调用这两个对象的 `peel()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/BananaPeel.java\n", + "\n", + "class Banana {\n", + " void peel(int i) {\n", + " /*...*/\n", + " }\n", + "}\n", + "public class BananaPeel {\n", + " public static void main(String[] args) {\n", + " Banana a = new Banana(), b = new Banana();\n", + " a.peel(1);\n", + " b.peel(2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果只有一个方法 `peel()` ,那么怎么知道调用的是对象 **a** 的 `peel()`方法还是对象 **b** 的 `peel()` 方法呢?编译器做了一些底层工作,所以你可以像这样编写代码。`peel()` 方法中第一个参数隐密地传入了一个指向操作对象的\n", + "\n", + "引用。因此,上述例子中的方法调用像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Banana.peel(a, 1)\n", + "Banana.peel(b, 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,但能说明到底发生了什么。假设现在在方法内部,你想获得对当前对象的引用。但是,对象引用是被秘密地传达给编译器——并不在参数列表中。方便的是,有一个关键字: **this** 。**this** 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,**this** 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用其他该类中的方法,不要使用 **this**,直接调用即可,**this** 自动地应用于其他方法上了。因此你可以像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Apricot.java\n", + "\n", + "public class Apricot {\n", + " void pick() {\n", + " /* ... */\n", + " }\n", + "\n", + " void pit() {\n", + " pick();\n", + " /* ... */\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `pit()` 方法中,你可以使用 `this.pick()`,但是没有必要。编译器自动为你做了这些。**this** 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在 **return** 语句中返回对当前对象的引用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Leaf.java\n", + "// Simple use of the \"this\" keyword\n", + "\n", + "public class Leaf {\n", + "\n", + " int i = 0;\n", + "\n", + " Leaf increment() {\n", + " i++;\n", + " return this;\n", + " }\n", + "\n", + " void print() {\n", + " System.out.println(\"i = \" + i);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Leaf x = new Leaf();\n", + " x.increment().increment().increment().print();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 `increment()` 通过 **this** 关键字返回当前对象的引用,因此在相同的对象上可以轻易地执行多次操作。\n", + "\n", + "**this** 关键字在向其他方法传递当前对象时也很有用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/PassingThis.java\n", + "\n", + "class Person {\n", + " public void eat(Apple apple) {\n", + " Apple peeled = apple.getPeeled();\n", + " System.out.println(\"Yummy\");\n", + " }\n", + "}\n", + "\n", + "public class Peeler {\n", + " static Apple peel(Apple apple) {\n", + " // ... remove peel\n", + " return apple; // Peeled\n", + " }\n", + "}\n", + "\n", + "public class Apple {\n", + " Apple getPeeled() {\n", + " return Peeler.peel(this);\n", + " }\n", + "}\n", + "\n", + "public class PassingThis {\n", + " public static void main(String[] args) {\n", + " new Person().eat(new Apple());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Yummy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Apple** 因为某些原因(比如说工具类中的方法在多个类中重复出现,你不想代码重复),必须调用一个外部工具方法 `Peeler.peel()` 做一些行为。必须使用 **this** 才能将自身传递给外部方法。\n", + "\n", + "### 在构造器中调用构造器\n", + "\n", + "当你在一个类中写了多个构造器,有时你想在一个构造器中调用另一个构造器来避免代码重复。你通过 **this** 关键字实现这样的调用。\n", + "\n", + "通常当你说 **this**,意味着\"这个对象\"或\"当前对象\",它本身生成对当前对象的引用。在一个构造器中,当你给 **this** 一个参数列表时,它是另一层意思。它通过最直接的方式显式地调用匹配参数列表的构造器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Flower.java\n", + "// Calling constructors with \"this\"\n", + "\n", + "public class Flower {\n", + " int petalCount = 0;\n", + " String s = \"initial value\";\n", + "\n", + " Flower(int petals) {\n", + " petalCount = petals;\n", + " System.out.println(\"Constructor w/ int arg only, petalCount = \" + petalCount);\n", + " }\n", + "\n", + " Flower(String ss) {\n", + " System.out.println(\"Constructor w/ string arg only, s = \" + ss);\n", + " s = ss;\n", + " }\n", + "\n", + " Flower(String s, int petals) {\n", + " this(petals);\n", + " //- this(s); // Can't call two!\n", + " this.s = s; // Another use of \"this\"\n", + " System.out.println(\"String & int args\");\n", + " }\n", + "\n", + " Flower() {\n", + " this(\"hi\", 47);\n", + " System.out.println(\"no-arg constructor\");\n", + " }\n", + "\n", + " void printPetalCount() {\n", + " //- this(11); // Not inside constructor!\n", + " System.out.println(\"petalCount = \" + petalCount + \" s = \" + s);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Flower x = new Flower();\n", + " x.printPetalCount();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Constructor w/ int arg only, petalCount = 47\n", + "String & int args\n", + "no-arg constructor\n", + "petalCount = 47 s = hi" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从构造器 `Flower(String s, int petals)` 可以看出,其中只能通过 **this** 调用一次构造器。另外,必须首先调用构造器,否则编译器会报错。这个例子同样展示了 **this** 的另一个用法。参数列表中的变量名 **s** 和成员变量名 **s** 相同,会引起混淆。你可以通过 `this.s` 表明你指的是成员变量 **s**,从而避免重复。你经常会在 Java 代码中看到这种用法,同时本书中也会多次出现这种写法。在 `printPetalCount()` 方法中,编译器不允许你在一个构造器之外的方法里调用构造器。\n", + "\n", + "### static 的含义\n", + "\n", + "记住了 **this** 关键字的内容,你会对 **static** 修饰的方法有更加深入的理解:**static** 方法中不会存在 **this**。你不能在静态方法中调用非静态方法(反之可以)。静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以被其他的静态方法和静态属性访问。一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 **this**,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 **static** 方法,就该重新考虑自己的设计了。然而,**static** 的概念很实用,许多时候都要用到它。至于它是否真的\"面向对象\",就留给理论家去讨论吧。\n", + "\n", + "\n", + "\n", + "## 垃圾回收器\n", + "\n", + "程序员都了解初始化的重要性,但通常会忽略清理的重要性。毕竟,谁会去清理一个 **int** 呢?但是使用完一个对象就不管它并非总是安全的。Java 中有垃圾回收器回收无用对象占用的内存。但现在考虑一种特殊情况:你创建的对象不是通过 **new** 来分配内存的,而垃圾回收器只知道如何释放用 **new** 创建的对象的内存,所以它不知道如何回收不是 **new** 分配的内存。为了处理这种情况,Java 允许在类中定义一个名为 `finalize()` 的方法。\n", + "\n", + "它的工作原理\"假定\"是这样的:当垃圾回收器准备回收对象的内存时,首先会调用其 `finalize()` 方法,并在下一轮的垃圾回收动作发生时,才会真正回收对象占用的内存。所以如果你打算使用 `finalize()` ,就能在垃圾回收时做一些重要的清理工作。`finalize()` 是一个潜在的编程陷阱,因为一些程序员(尤其是 C++ 程序员)会一开始把它误认为是 C++ 中的析构函数(C++ 在销毁对象时会调用这个函数)。所以有必要明确区分一下:在 C++ 中,对象总是被销毁的(在一个 bug-free 的程序中),而在 Java 中,对象并非总是被垃圾回收,或者换句话说:\n", + "\n", + "1. 对象可能不被垃圾回收。\n", + "2. 垃圾回收不等同于析构。\n", + "\n", + "这意味着在你不再需要某个对象之前,如果必须执行某些动作,你得自己去做。Java 没有析构器或类似的概念,所以你必须得自己创建一个普通的方法完成这项清理工作。例如,对象在创建的过程中会将自己绘制到屏幕上。如果不是明确地从屏幕上将其擦除,它可能永远得不到清理。如果在 `finalize()` 方法中加入某种擦除功能,那么当垃圾回收发生时,`finalize()` 方法被调用(不保证一定会发生),图像就会被擦除,要是\"垃圾回收\"没有发生,图像则仍会保留下来。\n", + "\n", + "也许你会发现,只要程序没有濒临内存用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,而垃圾回收器一直没有释放你创建的任何对象的内存,则当程序退出时,那些资源会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。\n", + "\n", + "### `finalize()` 的用途\n", + "\n", + "如果你不能将 `finalize()` 作为通用的清理方法,那么这个方法有什么用呢?\n", + "\n", + "这引入了要记住的第3点:\n", + "\n", + "3. 垃圾回收只与内存有关。\n", + "\n", + "也就是说,使用垃圾回收的唯一原因就是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是 `finalize()` 方法),它们也必须同内存及其回收有关。\n", + "\n", + "但这是否意味着如果对象中包括其他对象,`finalize()` 方法就应该明确释放那些对象呢?不是,无论对象是如何创建的,垃圾回收器都会负责释放对象所占用的所有内存。这就将对 `finalize()` 的需求限制到一种特殊情况,即通过某种创建对象方式之外的方式为对象分配了存储空间。不过,你可能会想,Java 中万物皆对象,这种情况怎么可能发生?\n", + "\n", + "看起来之所以有 `finalize()` 方法,是因为在分配内存时可能采用了类似 C 语言中的做法,而非 Java 中的通常做法。这种情况主要发生在使用\"本地方法\"的情况下,本地方法是一种用 Java 语言调用非 Java 语言代码的形式(关于本地方法的讨论,见本书电子版第2版的附录B)。本地方法目前只支持 C 和 C++,但是它们可以调用其他语言写的代码,所以实际上可以调用任何代码。在非 Java 代码中,也许会调用 C 的 `malloc()` 函数系列来分配存储空间,而且除非调用 `free()` 函数,不然存储空间永远得不到释放,造成内存泄露。但是,`free()` 是 C 和 C++ 中的函数,所以你需要在 `finalize()` 方法里用本地方法调用它。\n", + "\n", + "读到这里,你可能明白了不会过多使用 `finalize()` 方法。对,它确实不是进行普通的清理工作的合适场所。那么,普通的清理工作在哪里执行呢?\n", + "\n", + "### 你必须实施清理\n", + "\n", + "要清理一个对象,用户必须在需要清理的时候调用执行清理动作的方法。这听上去相当直接,但却与 C++ 中的\"析构函数\"的概念稍有抵触。在 C++ 中,所有对象都会被销毁,或者说应该被销毁。如果在 C++ 中创建了一个局部对象(在栈上创建,在 Java 中不行),此时的销毁动作发生在以\"右花括号\"为边界的、此对象作用域的末尾处。如果对象是用 **new** 创建的(类似于 Java 中),那么当程序员调用 C++ 的 **delete** 操作符时(Java 中不存在),就会调用相应的析构函数。如果程序员忘记调用 **delete**,那么永远不会调用析构函数,这样就会导致内存泄露,对象的其他部分也不会得到清理。这种 bug 很难跟踪,也是让 C++ 程序员转向 Java 的一个主要因素。相反,在 Java 中,没有用于释放对象的 **delete**,因为垃圾回收器会帮助你释放存储空间。甚至可以肤浅地认为,正是由于垃圾回收的存在,使得 Java 没有析构函数。然而,随着学习的深入,你会明白垃圾回收器的存在并不能完全替代析构函数(而且绝对不能直接调用 `finalize()`,所以这也不是一种解决方案)。如果希望进行除释放存储空间之外的清理工作,还是得明确调用某个恰当的 Java 方法:这就等同于使用析构函数了,只是没有它方便。\n", + "\n", + "记住,无论是\"垃圾回收\"还是\"终结\",都不保证一定会发生。如果 Java 虚拟机(JVM)并未面临内存耗尽的情形,它可能不会浪费时间执行垃圾回收以恢复内存。\n", + "\n", + "### 终结条件\n", + "\n", + "通常,不能指望 `finalize()` ,你必须创建其他的\"清理\"方法,并明确地调用它们。所以看起来,`finalize()` 只对大部分程序员很难用到的一些晦涩内存清理里有用了。但是,`finalize()` 还有一个有趣的用法,它不依赖于每次都要对 `finalize()` 进行调用,这就是对象终结条件的验证。\n", + "\n", + "当对某个对象不感兴趣时——也就是它将被清理了,这个对象应该处于某种状态,这种状态下它占用的内存可以被安全地释放掉。例如,如果对象代表了一个打开的文件,在对象被垃圾回收之前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的 bug。`finalize()` 可以用来最终发现这个情况,尽管它并不总是被调用。如果某次 `finalize()` 的动作使得 bug 被发现,那么就可以据此找出问题所在——这才是人们真正关心的。以下是个简单的例子,示范了 `finalize()` 的可能使用方式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/TerminationCondition.java\n", + "// Using finalize() to detect a object that\n", + "// hasn't been properly cleaned up\n", + "\n", + "import onjava.*;\n", + "\n", + "class Book {\n", + " boolean checkedOut = false;\n", + "\n", + " Book(boolean checkOut) {\n", + " checkedOut = checkOut;\n", + " }\n", + "\n", + " void checkIn() {\n", + " checkedOut = false;\n", + " }\n", + "\n", + " @Override\n", + " protected void finalize() throws Throwable {\n", + " if (checkedOut) {\n", + " System.out.println(\"Error: checked out\");\n", + " }\n", + " // Normally, you'll also do this:\n", + " // super.finalize(); // Call the base-class version\n", + " }\n", + "}\n", + "\n", + "public class TerminationCondition {\n", + "\n", + " public static void main(String[] args) {\n", + " Book novel = new Book(true);\n", + " // Proper cleanup:\n", + " novel.checkIn();\n", + " // Drop the reference, forget to clean up:\n", + " new Book(true);\n", + " // Force garbage collection & finalization:\n", + " System.gc();\n", + " new Nap(1); // One second delay\n", + " }\n", + "\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Error: checked out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "本例的终结条件是:所有的 **Book** 对象在被垃圾回收之前必须被登记。但在 `main()` 方法中,有一本书没有登记。要是没有 `finalize()` 方法来验证终结条件,将会很难发现这个 bug。\n", + "\n", + "你可能注意到使用了 `@Override`。`@` 意味着这是一个注解,注解是关于代码的额外信息。在这里,该注解告诉编译器这不是偶然地重定义在每个对象中都存在的 `finalize()` 方法——程序员知道自己在做什么。编译器确保你没有拼错方法名,而且确保那个方法存在于基类中。注解也是对读者的提醒,`@Override` 在 Java 5 引入,在 Java 7 中改善,本书通篇会出现。\n", + "\n", + "注意,`System.gc()` 用于强制进行终结动作。但是即使不这么做,只要重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的 **Book** 对象。\n", + "\n", + "你应该总是假设基类版本的 `finalize()` 也要做一些重要的事情,使用 **super** 调用它,就像在 `Book.finalize()` 中看到的那样。本例中,它被注释掉了,因为它需要进行异常处理,而我们到现在还没有涉及到。\n", + "\n", + "### 垃圾回收器如何工作\n", + "\n", + "如果你以前用过的语言,在堆上分配对象的代价十分高昂,你可能自然会觉得 Java 中所有对象(基本类型除外)在堆上分配的方式也十分高昂。然而,垃圾回收器能很明显地提高对象的创建速度。这听起来很奇怪——存储空间的释放影响了存储空间的分配,但这确实是某些 Java 虚拟机的工作方式。这也意味着,Java 从堆空间分配的速度可以和其他语言在栈上分配空间的速度相媲美。\n", + "\n", + "例如,你可以把 C++ 里的堆想象成一个院子,里面每个对象都负责管理自己的地盘。一段时间后,对象可能被销毁,但地盘必须复用。在某些 Java 虚拟机中,堆的实现截然不同:它更像一个传送带,每分配一个新对象,它就向前移动一格。这意味着对象存储空间的分配速度特别快。Java 的\"堆指针\"只是简单地移动到尚未分配的区域,所以它的效率与 C++ 在栈上分配空间的效率相当。当然实际过程中,在簿记工作方面还有少量额外开销,但是这部分开销比不上查找可用空间开销大。\n", + "\n", + "你可能意识到了,Java 中的堆并非完全像传送带那样工作。要是那样的话,势必会导致频繁的内存页面调度——将其移进移出硬盘,因此会显得需要拥有比实际需要更多的内存。页面调度会显著影响性能。最终,在创建了足够多的对象后,内存资源被耗尽。其中的秘密在于垃圾回收器的介入。当它工作时,一边回收内存,一边使堆中的对象紧凑排列,这样\"堆指针\"就可以很容易地移动到更靠近传送带的开始处,也就尽量避免了页面错误。垃圾回收器通过重新排列对象,实现了一种高速的、有无限空间可分配的堆模型。\n", + "\n", + "要想理解 Java 中的垃圾回收,先了解其他系统中的垃圾回收机制将会很有帮助。一种简单但速度很慢的垃圾回收机制叫做*引用计数*。每个对象中含有一个引用计数器,每当有引用指向该对象时,引用计数加 1。当引用离开作用域或被置为 **null** 时,引用计数减 1。因此,管理引用计数是一个开销不大但是在程序的整个生命周期频繁发生的负担。垃圾回收器会遍历含有全部对象的列表,当发现某个对象的引用计数为 0 时,就释放其占用的空间(但是,引用计数模式经常会在计数为 0 时立即释放对象)。这个机制存在一个缺点:如果对象之间存在循环引用,那么它们的引用计数都不为 0,就会出现应该被回收但无法被回收的情况。对垃圾回收器而言,定位这样的循环引用所需的工作量极大。引用计数常用来说明垃圾回收的工作方式,但似乎从未被应用于任何一种 Java 虚拟机实现中。\n", + "\n", + "在更快的策略中,垃圾回收器并非基于引用计数。它们依据的是:对于任意\"活\"的对象,一定能最终追溯到其存活在栈或静态存储区中的引用。这个引用链条可能会穿过数个对象层次,由此,如果从栈或静态存储区出发,遍历所有的引用,你将会发现所有\"活\"的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是该对象包含的所有引用,如此反复进行,直到访问完\"根源于栈或静态存储区的引用\"所形成的整个网络。你所访问过的对象一定是\"活\"的。注意,这解决了对象间循环引用的问题,这些对象不会被发现,因此也就被自动回收了。\n", + "\n", + "在这种方式下,Java 虚拟机采用了一种*自适应*的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的 Java 虚拟机实现。其中有一种做法叫做停止-复制(stop-and-copy)。顾名思义,这需要先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有复制的就是需要被垃圾回收的。另外,当对象被复制到新堆时,它们是一个挨着一个紧凑排列,然后就可以按照前面描述的那样简单、直接地分配新空间了。\n", + "\n", + "当对象从一处复制到另一处,所有指向它的引用都必须修正。位于栈或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到(可以想象成一个表格,将旧地址映射到新地址)。\n", + "\n", + "这种所谓的\"复制回收器\"效率低下主要因为两个原因。其一:得有两个堆,然后在这两个分离的堆之间来回折腾,得维护比实际需要多一倍的空间。某些 Java 虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。\n", + "\n", + "其二在于复制本身。一旦程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。尽管如此,复制回收器仍然会将所有内存从一处复制到另一处,这很浪费。为了避免这种状况,一些 Java 虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种模式(即\"自适应\")。这种模式称为标记-清扫(mark-and-sweep),Sun 公司早期版本的 Java 虚拟机一直使用这种技术。对一般用途而言,\"标记-清扫\"方式速度相当慢,但是当你知道程序只会产生少量垃圾甚至不产生垃圾时,它的速度就很快了。\n", + "\n", + "\"标记-清扫\"所依据的思路仍然是从栈和静态存储区出发,遍历所有的引用,找出所有存活的对象。但是,每当找到一个存活对象,就给对象设一个标记,并不回收它。只有当标记过程完成后,清理动作才开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。\"标记-清扫\"后剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就需要重新整理剩下的对象。\n", + "\n", + "\"停止-复制\"指的是这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会暂停。在 Oracle 公司的文档中会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但是早期版本的 Java 虚拟机并不是这么实现垃圾回收器的。当可用内存较低时,垃圾回收器会暂停程序。同样,\"标记-清扫\"工作也必须在程序暂停的情况下才能进行。\n", + "\n", + "如前文所述,这里讨论的 Java 虚拟机中,内存分配以较大的\"块\"为单位。如果对象较大,它会占用单独的块。严格来说,\"停止-复制\"要求在释放旧对象之前,必须先将所有存活对象从旧堆复制到新堆,这导致了大量的内存复制行为。有了块,垃圾回收器就可以把对象复制到废弃的块。每个块都有年代数来记录自己是否存活。通常,如果块在某处被引用,其年代数加 1,垃圾回收器会对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会复制(只是年代数会增加),含有小型对象的那些块则被复制并整理。Java 虚拟机会监视,如果所有对象都很稳定,垃圾回收的效率降低的话,就切换到\"标记-清扫\"方式。同样,Java 虚拟机会跟踪\"标记-清扫\"的效果,如果堆空间出现很多碎片,就会切换回\"停止-复制\"方式。这就是\"自适应\"的由来,你可以给它个啰嗦的称呼:\"自适应的、分代的、停止-复制、标记-清扫\"式的垃圾回收器。\n", + "\n", + "Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为\"即时\"(Just-In-Time, JIT)编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快。当需要装载某个类(通常是创建该类的第一个对象)时,编译器会先找到其 **.class** 文件,然后将该类的字节码装入内存。你可以让即时编译器编译所有代码,但这种做法有两个缺点:一是这种加载动作贯穿整个程序生命周期内,累加起来需要花更多时间;二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序速度。另一种做法称为*惰性评估*,意味着即时编译器只有在必要的时候才编译代码。这样,从未被执行的代码也许就压根不会被 JIT 编译。新版 JDK 中的 Java HotSpot 技术就采用了类似的做法,代码每被执行一次就优化一些,所以执行的次数越多,它的速度就越快。\n", + "\n", + "\n", + "\n", + "## 成员初始化\n", + "\n", + "Java 尽量保证所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,这种保证会以编译时错误的方式呈现,所以如果写成:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void f() {\n", + " int i;\n", + " i++;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你会得到一条错误信息,告诉你 **i** 可能尚未初始化。编译器可以为 **i** 赋一个默认值,但是未初始化的局部变量更有可能是程序员的疏忽,所以采用默认值反而会掩盖这种失误。强制程序员提供一个初始值,往往能帮助找出程序里的 bug。\n", + "\n", + "要是类的成员变量是基本类型,情况就会变得有些不同。正如在\"万物皆对象\"一章中所看到的,类的每个基本类型数据成员保证都会有一个初始值。下面的程序可以验证这类情况,并显示它们的值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/InitialValues.java\n", + "// Shows default initial values\n", + "\n", + "public class InitialValues {\n", + " boolean t;\n", + " char c;\n", + " byte b;\n", + " short s;\n", + " int i;\n", + " long l;\n", + " float f;\n", + " double d;\n", + " InitialValues reference;\n", + "\n", + " void printInitialValues() {\n", + " System.out.println(\"Data type Initial value\");\n", + " System.out.println(\"boolean \" + t);\n", + " System.out.println(\"char[\" + c + \"]\");\n", + " System.out.println(\"byte \" + b);\n", + " System.out.println(\"short \" + s);\n", + " System.out.println(\"int \" + i);\n", + " System.out.println(\"long \" + l);\n", + " System.out.println(\"float \" + f);\n", + " System.out.println(\"double \" + d);\n", + " System.out.println(\"reference \" + reference);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " new InitialValues().printInitialValues();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Data type Initial value\n", + "boolean false\n", + "char[NUL]\n", + "byte 0\n", + "short 0\n", + "int 0\n", + "long 0\n", + "float 0.0\n", + "double 0.0\n", + "reference null" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可见尽管数据成员的初值没有给出,但它们确实有初值(char 值为 0,所以显示为空白)。所以这样至少不会出现\"未初始化变量\"的风险了。\n", + "\n", + "在类里定义一个对象引用时,如果不将其初始化,那么引用就会被赋值为 **null**。\n", + "\n", + "### 指定初始化\n", + "\n", + "怎么给一个变量赋初值呢?一种很直接的方法是在定义类成员变量的地方为其赋值。以下代码修改了 InitialValues 类成员变量的定义,直接提供了初值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/InitialValues2.java\n", + "// Providing explicit initial values\n", + "\n", + "public class InitialValues2 {\n", + " boolean bool = true;\n", + " char ch = 'x';\n", + " byte b = 47;\n", + " short s = 0xff;\n", + " int i = 999;\n", + " long lng = 1;\n", + " float f = 3.14f;\n", + " double d = 3.14159;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你也可以用同样的方式初始化非基本类型的对象。如果 **Depth** 是一个类,那么可以像下面这样创建一个对象并初始化它:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Measurement.java\n", + "\n", + "class Depth {}\n", + "\n", + "public class Measurement {\n", + " Depth d = new Depth();\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有为 **d** 赋予初值就尝试使用它,就会出现运行时错误,告诉你产生了一个异常(详细见\"异常\"章节)。\n", + "\n", + "你也可以通过调用某个方法来提供初值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/MethodInit.java\n", + "\n", + "public class MethodInit {\n", + " int i = f();\n", + " \n", + " int f() {\n", + " return 11;\n", + " }\n", + " \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个方法可以带有参数,但这些参数不能是未初始化的类成员变量。因此,可以这么写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/MethodInit2.java\n", + "\n", + "public class MethodInit2 {\n", + " int i = f();\n", + " int j = g(i);\n", + " \n", + " int f() {\n", + " return 11;\n", + " }\n", + " \n", + " int g(int n) {\n", + " return n * 10;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是你不能这么写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/MethodInit3.java\n", + "\n", + "public class MethodInit3 {\n", + " //- int j = g(i); // Illegal forward reference\n", + " int i = f();\n", + "\n", + " int f() {\n", + " return 11;\n", + " }\n", + "\n", + " int g(int n) {\n", + " return n * 10;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显然,上述程序的正确性取决于初始化的顺序,而与其编译方式无关。所以,编译器恰当地对\"向前引用\"发出了警告。\n", + "\n", + "这种初始化方式简单直观,但有个限制:类 **InitialValues** 的每个对象都有相同的初值,有时这的确是我们需要的,但有时却需要更大的灵活性。\n", + "\n", + "\n", + "\n", + "## 构造器初始化\n", + "\n", + "可以用构造器进行初始化,这种方式给了你更大的灵活性,因为你可以在运行时调用方法进行初始化。但是,这无法阻止自动初始化的进行,他会在构造器被调用之前发生。因此,如果使用如下代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Counter.java\n", + "\n", + "public class Counter {\n", + " int i;\n", + " \n", + " Counter() {\n", + " i = 7;\n", + " }\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**i** 首先会被初始化为 **0**,然后变为 **7**。对于所有的基本类型和引用,包括在定义时已明确指定初值的变量,这种情况都是成立的。因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前初始化元素——初始化早已得到了保证。, \n", + "\n", + "### 初始化的顺序\n", + "\n", + "在类中变量定义的顺序决定了它们初始化的顺序。即使变量定义散布在方法定义之间,它们仍会在任何方法(包括构造器)被调用之前得到初始化。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OrderOfInitialization.java\n", + "// Demonstrates initialization order\n", + "// When the constructor is called to create a\n", + "// Window object, you'll see a message:\n", + "\n", + "class Window {\n", + " Window(int marker) {\n", + " System.out.println(\"Window(\" + marker + \")\");\n", + " }\n", + "}\n", + "\n", + "class House {\n", + " Window w1 = new Window(1); // Before constructor\n", + "\n", + " House() {\n", + " // Show that we're in the constructor:\n", + " System.out.println(\"House()\");\n", + " w3 = new Window(33); // Reinitialize w3\n", + " }\n", + "\n", + " Window w2 = new Window(2); // After constructor\n", + "\n", + " void f() {\n", + " System.out.println(\"f()\");\n", + " }\n", + "\n", + " Window w3 = new Window(3); // At end\n", + "}\n", + "\n", + "public class OrderOfInitialization {\n", + " public static void main(String[] args) {\n", + " House h = new House();\n", + " h.f(); // Shows that construction is done\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Window(1)\n", + "Window(2)\n", + "Window(3)\n", + "House()\n", + "Window(33)\n", + "f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **House** 类中,故意把几个 **Window** 对象的定义散布在各处,以证明它们全都会在调用构造器或其他方法之前得到初始化。此外,**w3** 在构造器中被再次赋值。\n", + "\n", + "由输出可见,引用 **w3** 被初始化了两次:一次在调用构造器前,一次在构造器调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。这乍一看可能觉得效率不高,但保证了正确的初始化。试想,如果定义了一个重载构造器,在其中没有初始化 **w3**,同时在定义 **w3** 时没有赋予初值,那会产生怎样的后果呢?\n", + "\n", + "### 静态数据的初始化\n", + "\n", + "无论创建多少个对象,静态数据都只占用一份存储区域。**static** 关键字不能应用于局部变量,所以只能作用于属性(字段、域)。如果一个字段是静态的基本类型,你没有初始化它,那么它就会获得基本类型的标准初值。如果它是对象引用,那么它的默认初值就是 **null**。\n", + "\n", + "如果在定义时进行初始化,那么静态变量看起来就跟非静态变量一样。\n", + "\n", + "下面例子显示了静态存储区是何时初始化的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/StaticInitialization.java\n", + "// Specifying initial values in a class definition\n", + "\n", + "class Bowl {\n", + " Bowl(int marker) {\n", + " System.out.println(\"Bowl(\" + marker + \")\");\n", + " }\n", + " \n", + " void f1(int marker) {\n", + " System.out.println(\"f1(\" + marker + \")\");\n", + " }\n", + "}\n", + "\n", + "class Table {\n", + " static Bowl bowl1 = new Bowl(1);\n", + " \n", + " Table() {\n", + " System.out.println(\"Table()\");\n", + " bowl2.f1(1);\n", + " }\n", + " \n", + " void f2(int marker) {\n", + " System.out.println(\"f2(\" + marker + \")\");\n", + " }\n", + " \n", + " static Bowl bowl2 = new Bowl(2);\n", + "}\n", + "\n", + "class Cupboard {\n", + " Bowl bowl3 = new Bowl(3);\n", + " static Bowl bowl4 = new Bowl(4);\n", + " \n", + " Cupboard() {\n", + " System.out.println(\"Cupboard()\");\n", + " bowl4.f1(2);\n", + " }\n", + " \n", + " void f3(int marker) {\n", + " System.out.println(\"f3(\" + marker + \")\");\n", + " }\n", + " \n", + " static Bowl bowl5 = new Bowl(5);\n", + "}\n", + "\n", + "public class StaticInitialization {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"main creating new Cupboard()\");\n", + " new Cupboard();\n", + " System.out.println(\"main creating new Cupboard()\");\n", + " new Cupboard();\n", + " table.f2(1);\n", + " cupboard.f3(1);\n", + " }\n", + " \n", + " static Table table = new Table();\n", + " static Cupboard cupboard = new Cupboard();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Bowl(1)\n", + "Bowl(2)\n", + "Table()\n", + "f1(1)\n", + "Bowl(4)\n", + "Bowl(5)\n", + "Bowl(3)\n", + "Cupboard()\n", + "f1(2)\n", + "main creating new Cupboard()\n", + "Bowl(3)\n", + "Cupboard()\n", + "f1(2)\n", + "main creating new Cupboard()\n", + "Bowl(3)\n", + "Cupboard()\n", + "f1(2)\n", + "f2(1)\n", + "f3(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Bowl** 类展示类的创建,而 **Table** 和 **Cupboard** 在它们的类定义中包含 **Bowl** 类型的静态数据成员。注意,在静态数据成员定义之前,**Cupboard** 类中先定义了一个 **Bowl** 类型的非静态成员 **b3**。\n", + "\n", + "由输出可见,静态初始化只有在必要时刻才会进行。如果不创建 **Table** 对象,也不引用 **Table.bowl1** 或 **Table.bowl2**,那么静态的 **Bowl** 类对象 **bowl1** 和 **bowl2** 永远不会被创建。只有在第一个 Table 对象被创建(或被访问)时,它们才会被初始化。此后,静态对象不会再次被初始化。\n", + "\n", + "初始化的顺序先是静态对象(如果它们之前没有被初始化的话),然后是非静态对象,从输出中可以看出。要执行 `main()` 方法,必须加载 **StaticInitialization** 类,它的静态属性 **table** 和 **cupboard** 随后被初始化,这会导致它们对应的类也被加载,而由于它们都包含静态的 **Bowl** 对象,所以 **Bowl** 类也会被加载。因此,在这个特殊的程序中,所有的类都会在 `main()` 方法之前被加载。实际情况通常并非如此,因为在典型的程序中,不会像本例中所示的那样,将所有事物通过 **static** 联系起来。\n", + "\n", + "概括一下创建对象的过程,假设有个名为 **Dog** 的类:\n", + "\n", + "1. 即使没有显式地使用 **static** 关键字,构造器实际上也是静态方法。所以,当首次创建 **Dog** 类型的对象或是首次访问 **Dog** 类的静态方法或属性时,Java 解释器必须在类路径中查找,以定位 **Dog.class**。\n", + "2. 当加载完 **Dog.class** 后(后面会学到,这将创建一个 **Class** 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 **Class** 对象时初始化一次。\n", + "3. 当用 `new Dog()` 创建对象时,首先会在堆上为 **Dog** 对象分配足够的存储空间。\n", + "4. 分配的存储空间首先会被清零,即会将 **Dog** 对象中的所有基本类型数据设置为默认值(数字会被置为 0,布尔型和字符型也相同),引用被置为 **null**。\n", + "5. 执行所有出现在字段定义处的初始化动作。\n", + "6. 执行构造器。你将会在\"复用\"这一章看到,这可能会牵涉到很多动作,尤其当涉及继承的时候。\n", + "\n", + "### 显式的静态初始化\n", + "\n", + "你可以将一组静态初始化动作放在类里面一个特殊的\"静态子句\"(有时叫做静态块)中。像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Spoon.java\n", + "\n", + "public class Spoon {\n", + " static int i;\n", + " \n", + " static {\n", + " i = 47;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这看起来像个方法,但实际上它只是一段跟在 **static** 关键字后面的代码块。与其他静态初始化动作一样,这段代码仅执行一次:当首次创建这个类的对象或首次访问这个类的静态成员(甚至不需要创建该类的对象)时。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/ExplicitStatic.java\n", + "// Explicit static initialization with \"static\" clause\n", + "\n", + "class Cup {\n", + " Cup(int marker) {\n", + " System.out.println(\"Cup(\" + marker + \")\");\n", + " }\n", + " \n", + " void f(int marker) {\n", + " System.out.println(\"f(\" + marker + \")\");\n", + " }\n", + "}\n", + "\n", + "class Cups {\n", + " static Cup cup1;\n", + " static Cup cup2;\n", + " \n", + " static {\n", + " cup1 = new Cup(1);\n", + " cup2 = new Cup(2);\n", + " }\n", + " \n", + " Cups() {\n", + " System.out.println(\"Cups()\");\n", + " }\n", + "}\n", + "\n", + "public class ExplicitStatic {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Inside main()\");\n", + " Cups.cup1.f(99); // [1]\n", + " }\n", + " \n", + " // static Cups cups1 = new Cups(); // [2]\n", + " // static Cups cups2 = new Cups(); // [2]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Inside main\n", + "Cup(1)\n", + "Cup(2)\n", + "f(99)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "无论是通过标为 [1] 的行访问静态的 **cup1** 对象,还是把标为 [1] 的行去掉,让它去运行标为 [2] 的那行代码(去掉 [2] 的注释),**Cups** 的静态初始化动作都会执行。如果同时注释 [1] 和 [2] 处,那么 **Cups** 的静态初始化就不会进行。此外,把标为 [2] 处的注释都去掉还是只去掉一个,静态初始化只会执行一次。\n", + "\n", + "### 非静态实例初始化\n", + "\n", + "Java 提供了被称为*实例初始化*的类似语法,用来初始化每个对象的非静态变量,例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Mugs.java\n", + "// Instance initialization\n", + "\n", + "class Mug {\n", + " Mug(int marker) {\n", + " System.out.println(\"Mug(\" + marker + \")\");\n", + " }\n", + "}\n", + "\n", + "public class Mugs {\n", + " Mug mug1;\n", + " Mug mug2;\n", + " { // [1]\n", + " mug1 = new Mug(1);\n", + " mug2 = new Mug(2);\n", + " System.out.println(\"mug1 & mug2 initialized\");\n", + " }\n", + " \n", + " Mugs() {\n", + " System.out.println(\"Mugs()\");\n", + " }\n", + " \n", + " Mugs(int i) {\n", + " System.out.println(\"Mugs(int)\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " System.out.println(\"Inside main()\");\n", + " new Mugs();\n", + " System.out.println(\"new Mugs() completed\");\n", + " new Mugs(1);\n", + " System.out.println(\"new Mugs(1) completed\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Inside main\n", + "Mug(1)\n", + "Mug(2)\n", + "mug1 & mug2 initialized\n", + "Mugs()\n", + "new Mugs() completed\n", + "Mug(1)\n", + "Mug(2)\n", + "mug1 & mug2 initialized\n", + "Mugs(int)\n", + "new Mugs(1) completed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持\"匿名内部类\"(参见\"内部类\"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,实例初始化子句是在两个构造器之前执行的。\n", + "\n", + "\n", + "\n", + "## 数组初始化\n", + "\n", + "数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 [] 来定义和使用的。要定义一个数组引用,只需要在类型名加上方括号:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int[] a1;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "方括号也可放在标识符的后面,两者的含义是一样的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int a1[];" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这种格式符合 C 和 C++ 程序员的习惯。不过前一种格式或许更合理,毕竟它表明类型是\"一个 **int** 型数组\"。本书中采用这种格式。\n", + "\n", + "编译器不允许指定数组的大小。这又把我们带回有关\"引用\"的问题上。你所拥有的只是对数组的一个引用(你已经为该引用分配了足够的存储空间),但是还没有给数组对象本身分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。对于数组,初始化动作可以出现在代码的任何地方,但是也可以使用一种特殊的初始化表达式,它必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的值组成。这种情况下,存储空间的分配(相当于使用 **new**) 将由编译器负责。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int[] a1 = {1, 2, 3, 4, 5};" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "那么为什么在还没有数组的时候定义一个数组引用呢?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int[] a2;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Java 中可以将一个数组赋值给另一个数组,所以可以这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "a2 = a1;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其实真正做的只是复制了一个引用,就像下面演示的这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/ArraysOfPrimitives.java\n", + "\n", + "public class ArraysOfPrimitives {\n", + " public static void main(String[] args) {\n", + " int[] a1 = {1, 2, 3, 4, 5};\n", + " int[] a2;\n", + " a2 = a1;\n", + " for (int i = 0; i < a2.length; i++) {\n", + " a2[i] += 1;\n", + " }\n", + " for (int i = 0; i < a1.length; i++) {\n", + " System.out.println(\"a1[\" + i + \"] = \" + a1[i]);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "a1[0] = 2;\n", + "a1[1] = 3;\n", + "a1[2] = 4;\n", + "a1[3] = 5;\n", + "a1[4] = 6;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**a1** 初始化了,但是 **a2** 没有;这里,**a2** 在后面被赋给另一个数组。由于 **a1** 和 **a2** 是相同数组的别名,因此通过 **a2** 所做的修改在 **a1** 中也能看到。\n", + "\n", + "所有的数组(无论是对象数组还是基本类型数组)都有一个固定成员 **length**,告诉你这个数组有多少个元素,你不能对其修改。与 C 和 C++ 类似,Java 数组计数也是从 0 开始的,所能使用的最大下标数是 **length - 1**。超过这个边界,C 和 C++ 会默认接受,允许你访问所有内存,许多声名狼藉的 bug 都是由此而生。但是 Java 在你访问超出这个边界时,会报运行时错误(异常),从而避免此类问题。\n", + "\n", + "### 动态数组创建\n", + "\n", + "如果在编写程序时,不确定数组中需要多少个元素,那么该怎么办呢?你可以直接使用 **new** 在数组中创建元素。下面例子中,尽管创建的是基本类型数组,**new** 仍然可以工作(不能用 **new** 创建单个的基本类型数组):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/ArrayNew.java\n", + "// Creating arrays with new\n", + "import java.util.*;\n", + "\n", + "public class ArrayNew {\n", + " public static void main(String[] args) {\n", + " int[] a;\n", + " Random rand = new Random(47);\n", + " a = new int[rand.nextInt(20)];\n", + " System.out.println(\"length of a = \" + a.length);\n", + " System.out.println(Arrays.toString(a));\n", + " } \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "length of a = 18\n", + "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "数组的大小是通过 `Random.nextInt()` 随机确定的,这个方法会返回 0 到输入参数之间的一个值。 由于随机性,很明显数组的创建确实是在运行时进行的。此外,程序输出表明,数组元素中的基本数据类型值会自动初始化为空值(对于数字和字符是 0;对于布尔型是 **false**)。`Arrays.toString()` 是 **java.util** 标准类库中的方法,会产生一维数组的可打印版本。\n", + "\n", + "本例中,数组也可以在定义的同时进行初始化:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "int[] a = new int[rand.nextInt(20)];" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果可能的话,应该尽量这么做。\n", + "\n", + "如果你创建了一个非基本类型的数组,那么你创建的是一个引用数组。以整型的包装类型 **Integer** 为例,它是一个类而非基本类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/ArrayClassObj.java\n", + "// Creating an array of nonprimitive objects\n", + "\n", + "import java.util.*;\n", + "\n", + "public class ArrayClassObj {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " Integer[] a = new Integer[rand.nextInt(20)];\n", + " System.out.println(\"length of a = \" + a.length);\n", + " for (int i = 0; i < a.length; i++) {\n", + " a[i] = rand.nextInt(500); // Autoboxing\n", + " }\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "length of a = 18\n", + "[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,即使使用 new 创建数组之后:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Integer[] a = new Integer[rand.nextInt(20)];\t" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它只是一个引用数组,直到通过创建新的 **Integer** 对象(通过自动装箱),并把对象赋值给引用,初始化才算结束:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "a[i] = rand.nextInt(500);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果忘记了创建对象,但试图使用数组中的空引用,就会在运行时产生异常。\n", + "\n", + "也可以用花括号括起来的列表来初始化数组,有两种形式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/ArrayInit.java\n", + "// Array initialization\n", + "import java.util.*;\n", + "\n", + "public class ArrayInit {\n", + " public static void main(String[] args) {\n", + " Integer[] a = {\n", + " 1, 2,\n", + " 3, // Autoboxing\n", + " };\n", + " Integer[] b = new Integer[] {\n", + " 1, 2,\n", + " 3, // Autoboxing\n", + " };\n", + " System.out.println(Arrays.toString(a));\n", + " System.out.println(Arrays.toString(b));\n", + "\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[1, 2, 3]\n", + "[1, 2, 3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这两种形式中,初始化列表的最后一个逗号是可选的(这一特性使维护长列表变得更容易)。\n", + "\n", + "尽管第一种形式很有用,但是它更加受限,因为它只能用于数组定义处。第二种和第三种形式可以用在任何地方,甚至用在方法的内部。例如,你创建了一个 **String** 数组,将其传递给另一个类的 `main()` 方法,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/DynamicArray.java\n", + "// Array initialization\n", + "\n", + "public class DynamicArray {\n", + " public static void main(String[] args) {\n", + " Other.main(new String[] {\"fiddle\", \"de\", \"dum\"});\n", + " }\n", + "}\n", + "\n", + "class Other {\n", + " public static void main(String[] args) {\n", + " for (String s: args) {\n", + " System.out.print(s + \" \");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fiddle de dum " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Other.main()` 的参数是在调用处创建的,因此你甚至可以在方法调用处提供可替换的参数。\n", + "\n", + "### 可变参数列表\n", + "\n", + "你可以以一种类似 C 语言中的可变参数列表(C 通常把它称为\"varargs\")来创建和调用方法。这可以应用在参数个数或类型未知的场合。由于所有的类都最后继承于 **Object** 类(随着本书的进展,你会对此有更深的认识),所以你可以创建一个以 Object 数组为参数的方法,并像下面这样调用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/VarArgs.java\n", + "// Using array syntax to create variable argument lists\n", + "\n", + "class A {}\n", + "\n", + "public class VarArgs {\n", + " static void printArray(Object[] args) {\n", + " for (Object obj: args) {\n", + " System.out.print(obj + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " printArray(new Object[] {47, (float) 3.14, 11.11});\n", + " printArray(new Object[] {\"one\", \"two\", \"three\"});\n", + " printArray(new Object[] {new A(), new A(), new A()});\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "47 3.14 11.11 \n", + "one two three \n", + "A@15db9742 A@6d06d69c A@7852e922" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`printArray()` 的参数是 **Object** 数组,使用 for-in 语法遍历和打印数组的每一项。标准 Java 库能输出有意义的内容,但这里创建的是类的对象,打印出的内容是类名,后面跟着一个 **@** 符号以及多个十六进制数字。因而,默认行为(如果没有定义 `toString()` 方法的话,后面会讲这个方法)就是打印类名和对象的地址。\n", + "\n", + "你可能看到像上面这样编写的 Java 5 之前的代码,它们可以产生可变的参数列表。在 Java 5 中,这种期盼已久的特性终于添加了进来,就像在 `printArray()` 中看到的那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/NewVarArgs.java\n", + "// Using array syntax to create variable argument lists\n", + "\n", + "public class NewVarArgs {\n", + " static void printArray(Object... args) {\n", + " for (Object obj: args) {\n", + " System.out.print(obj + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " // Can take individual elements:\n", + " printArray(47, (float) 3.14, 11.11);\n", + " printArray(47, 3.14F, 11.11);\n", + " printArray(\"one\", \"two\", \"three\");\n", + " printArray(new A(), new A(), new A());\n", + " // Or an array:\n", + " printArray((Object[]) new Integer[] {1, 2, 3, 4});\n", + " printArray(); // Empty list is OK\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "47 3.14 11.11 \n", + "47 3.14 11.11 \n", + "one two three \n", + "A@15db9742 A@6d06d69c A@7852e922 \n", + "1 2 3 4 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有了可变参数,你就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你填充数组。你获取的仍然是一个数组,这就是为什么 `printArray()` 可以使用 for-in 迭代数组的原因。但是,这不仅仅只是从元素列表到数组的自动转换。注意程序的倒数第二行,一个 **Integer** 数组(通过自动装箱创建)被转型为一个 **Object** 数组(为了移除编译器的警告),并且传递给了 `printArray()`。显然,编译器会发现这是一个数组,不会执行转换。因此,如果你有一组事物,可以把它们当作列表传递,而如果你已经有了一个数组,该方法会把它们当作可变参数列表来接受。\n", + "\n", + "程序的最后一行表明,可变参数的个数可以为 0。当具有可选的尾随参数时,这一特性会有帮助:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OptionalTrailingArguments.java\n", + "\n", + "public class OptionalTrailingArguments {\n", + " static void f(int required, String... trailing) {\n", + " System.out.print(\"required: \" + required + \" \");\n", + " for (String s: trailing) {\n", + " System.out.print(s + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f(1, \"one\");\n", + " f(2, \"two\", \"three\");\n", + " f(0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "required: 1 one \n", + "required: 2 two three \n", + "required: 0 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这段程序展示了如何使用除了 **Object** 类之外类型的可变参数列表。这里,所有的可变参数都是 **String** 对象。可变参数列表中可以使用任何类型的参数,包括基本类型。下面例子展示了可变参数列表变为数组的情形,并且如果列表中没有任何元素,那么转变为大小为 0 的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/VarargType.java\n", + "\n", + "public class VarargType {\n", + " static void f(Character... args) {\n", + " System.out.print(args.getClass());\n", + " System.out.println(\" length \" + args.length);\n", + " }\n", + " \n", + " static void g(int... args) {\n", + " System.out.print(args.getClass());\n", + " System.out.println(\" length \" + args.length)\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f('a');\n", + " f();\n", + " g(1);\n", + " g();\n", + " System.out.println(\"int[]: \"+ new int[0].getClass());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class [Ljava.lang.Character; length 1\n", + "class [Ljava.lang.Character; length 0\n", + "class [I length 1\n", + "class [I length 0\n", + "int[]: class [I" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`getClass()` 方法属于 Object 类,将在\"类型信息\"一章中全面介绍。它会产生对象的类,并在打印该类时,看到表示该类类型的编码字符串。前导的 **[** 代表这是一个后面紧随的类型的数组,**I** 表示基本类型 **int**;为了进行双重检查,我在最后一行创建了一个 **int** 数组,打印了其类型。这样也验证了使用可变参数列表不依赖于自动装箱,而使用的是基本类型。\n", + "\n", + "然而,可变参数列表与自动装箱可以和谐共处,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/AutoboxingVarargs.java\n", + "\n", + "public class AutoboxingVarargs {\n", + " public static void f(Integer... args) {\n", + " for (Integer i: args) {\n", + " System.out.print(i + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f(1, 2);\n", + " f(4, 5, 6, 7, 8, 9);\n", + " f(10, 11, 12);\n", + " \n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1 2\n", + "4 5 6 7 8 9\n", + "10 11 12" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意吗,你可以在单个参数列表中将类型混合在一起,自动装箱机制会有选择地把 **int** 类型的参数提升为 **Integer**。\n", + "\n", + "可变参数列表使得方法重载更加复杂了,尽管乍看之下似乎足够安全:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OverloadingVarargs.java\n", + "\n", + "public class OverloadingVarargs {\n", + " static void f(Character... args) {\n", + " System.out.print(\"first\");\n", + " for (Character c: args) {\n", + " System.out.print(\" \" + c);\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " static void f(Integer... args) {\n", + " System.out.print(\"second\");\n", + " for (Integer i: args) {\n", + " System.out.print(\" \" + i);\n", + " }\n", + " System.out.println();\n", + " }\n", + " \n", + " static void f(Long... args) {\n", + " System.out.println(\"third\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f('a', 'b', 'c');\n", + " f(1);\n", + " f(2, 1);\n", + " f(0);\n", + " f(0L);\n", + " //- f(); // Won's compile -- ambiguous\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first a b c\n", + "second 1\n", + "second 2 1\n", + "second 0\n", + "third" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在每种情况下,编译器都会使用自动装箱来匹配重载的方法,然后调用最明确匹配的方法。\n", + "\n", + "但是如果调用不含参数的 `f()`,编译器就无法知道应该调用哪个方法了。尽管这个错误可以弄清楚,但是它可能会使客户端程序员感到意外。\n", + "\n", + "你可能会通过在某个方法中增加一个非可变参数解决这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OverloadingVarargs2.java\n", + "// {WillNotCompile}\n", + "\n", + "public class OverloadingVarargs2 {\n", + " static void f(float i, Character... args) {\n", + " System.out.println(\"first\");\n", + " }\n", + " \n", + " static void f(Character... args) {\n", + " System.out.println(\"second\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f(1, 'a');\n", + " f('a', 'b');\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**{WillNotCompile}** 注释把该文件排除在了本书的 Gradle 构建之外。如果你手动编译它,会得到下面的错误信息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "OverloadingVarargs2.java:14:error:reference to f is ambiguous f('a', 'b');\n", + "\\^\n", + "both method f(float, Character...) in OverloadingVarargs2 and method f(Character...) in OverloadingVarargs2 match 1 error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你给这两个方法都添加一个非可变参数,就可以解决问题了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/OverloadingVarargs3\n", + "\n", + "public class OverloadingVarargs3 {\n", + " static void f(float i, Character... args) {\n", + " System.out.println(\"first\");\n", + " }\n", + " \n", + " static void f(char c, Character... args) {\n", + " System.out.println(\"second\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f(1, 'a');\n", + " f('a', 'b');\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first\n", + "second" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你应该总是在重载方法的一个版本上使用可变参数列表,或者压根不用它。\n", + "\n", + "\n", + "\n", + "## 枚举类型\n", + "\n", + "Java 5 中添加了一个看似很小的特性 **enum** 关键字,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。以前,你需要创建一个整数常量集,但是这些值并不会将自身限制在这个常量集的范围内,因此使用它们更有风险,而且更难使用。枚举类型属于非常普遍的需求,C、C++ 和其他许多语言都已经拥有它了。在 Java 5 之前,Java 程序员必须了解许多细节并格外仔细地去达成 **enum** 的效果。现在 Java 也有了 **enum**,并且它的功能比 C/C++ 中的完备得多。下面是个简单的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Spiciness.java\n", + "\n", + "public enum Spiciness {\n", + " NOT, MILD, MEDIUM, HOT, FLAMING\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里创建了一个名为 **Spiciness** 的枚举类型,它有5个值。由于枚举类型的实例是常量,因此按照命名惯例,它们都用大写字母表示(如果名称中含有多个单词,使用下划线分隔)。\n", + "\n", + "要使用 **enum**,需要创建一个该类型的引用,然后将其赋值给某个实例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/SimpleEnumUse.java\n", + "\n", + "public class SimpleEnumUse {\n", + " public static void main(String[] args) {\n", + " Spiciness howHot = Spiciness.MEDIUM;\n", + " System.out.println(howHot);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "MEDIUM" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在你创建 **enum** 时,编译器会自动添加一些有用的特性。例如,它会创建 `toString()` 方法,以便你方便地显示某个 **enum** 实例的名称,这从上面例子中的输出可以看出。编译器还会创建 `ordinal()` 方法表示某个特定 **enum** 常量的声明顺序,`static values()` 方法按照 enum 常量的声明顺序,生成这些常量值构成的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/EnumOrder.java\n", + "\n", + "public class EnumOrder {\n", + " public static void main(String[] args) {\n", + " for (Spiciness s: Spiciness.values()) {\n", + " System.out.println(s + \", ordinal \" + s.ordinal());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NOT, ordinal 0\n", + "MILD, ordinal 1\n", + "MEDIUM, ordinal 2\n", + "HOT, ordinal 3\n", + "FLAMING, ordinal 4" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管 **enum** 看起来像是一种新的数据类型,但是这个关键字只是在生成 **enum** 的类时,产生了某些编译器行为,因此在很大程度上你可以将 **enum** 当作其他任何类。事实上,**enum** 确实是类,并且具有自己的方法。\n", + "\n", + "**enum** 有一个很实用的特性,就是在 **switch** 语句中使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// housekeeping/Burrito.java\n", + "\n", + "public class Burrito {\n", + " Spiciness degree;\n", + " \n", + " public Burrito(Spiciness degree) {\n", + " this.degree = degree;\n", + " }\n", + " \n", + " public void describe() {\n", + " System.out.print(\"This burrito is \");\n", + " switch(degree) {\n", + " case NOT:\n", + " System.out.println(\"not spicy at all.\");\n", + " break;\n", + " case MILD:\n", + " case MEDIUM:\n", + " System.out.println(\"a little hot.\");\n", + " break;\n", + " case HOT:\n", + " case FLAMING:\n", + " default:\n", + " System.out.println(\"maybe too hot\");\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Burrito plain = new Burrito(Spiciness.NOT),\n", + " greenChile = new Burrito(Spiciness.MEDIUM),\n", + " jalapeno = new Burrito(Spiciness.HOT);\n", + " plain.describe();\n", + " greenChile.describe();\n", + " jalapeno.describe();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "This burrito is not spicy at all.\n", + "This burrito is a little hot.\n", + "This burrito is maybe too hot." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于 **switch** 是在有限的可能值集合中选择,因此它与 **enum** 是绝佳的组合。注意,enum 的名称是如何能够倍加清楚地表明程序的目的的。\n", + "\n", + "通常,你可以将 **enum** 用作另一种创建数据类型的方式,然后使用所得到的类型。这正是关键所在,所以你不用过多地考虑它们。在 **enum** 被引入之前,你必须花费大量的精力去创建一个等同的枚举类型,并是安全可用的。\n", + "\n", + "这些介绍对于你理解和使用基本的 **enum** 已经足够了,我们会在\"枚举\"一章中进行更深入的探讨。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "构造器,这种看起来精巧的初始化机制,应该给了你很强的暗示:初始化在编程语言中的重要地位。C++ 的发明者 Bjarne Stroustrup 在设计 C++ 期间,在针对 C 语言的生产效率进行的最初调查中发现,错误的初始化会导致大量编程错误。这些错误很难被发现,同样,不合理的清理也会如此。因为构造器能保证进行正确的初始化和清理(没有正确的构造器调用,编译器就不允许创建对象),所以你就有了完全的控制和安全。\n", + "\n", + "在 C++ 中,析构器很重要,因为用 **new** 创建的对象必须被明确地销毁。在 Java 中,垃圾回收器会自动地释放所有对象的内存,所以很多时候类似的清理方法就不太需要了(但是当要用到的时候,你得自己动手)。在不需要类似析构器行为的时候,Java 的垃圾回收器极大地简化了编程,并加强了内存管理上的安全性。一些垃圾回收器甚至能清理其他资源,如图形和文件句柄。然而,垃圾回收器确实增加了运行时开销,由于 Java 解释器从一开始就很慢,所以这种开销到底造成多大的影响很难看出来。随着时间的推移,Java 在性能方面提升了很多,但是速度问题仍然是它涉足某些特定编程领域的障碍。\n", + "\n", + "由于要保证所有对象被创建,实际上构造器比这里讨论得更加复杂。特别是当通过*组合*或*继承*创建新类的时候,这种保证仍然成立,并且需要一些额外的语法来支持。在后面的章节中,你会学习组合,继承以及它们如何影响构造器。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/07-Implementation-Hiding.ipynb b/jupyter/07-Implementation-Hiding.ipynb new file mode 100644 index 00000000..ad55bfa3 --- /dev/null +++ b/jupyter/07-Implementation-Hiding.ipynb @@ -0,0 +1,1325 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第七章 封装\n", + "\n", + "> *访问控制(Access control)*(或者*隐藏实现(implementation hiding)*)与“最初的实现不恰当”有关。\n", + "\n", + "所有优秀的作者——包括那些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是*重构*(refactoring)的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。\n", + "\n", + "但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,一些用户(*客户端程序员(client programmers)*)希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:“如何区分变动的事物和不变的事物”。\n", + "\n", + "这个问题对于类库(library)而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。\n", + "\n", + "这可以通过约定解决。例如,类库开发者必须同意在修改类库中的一个类时,不会移除已有的方法,因为那样将会破坏客户端程序员的代码。与之相反的情况更加复杂。在有成员属性的情况下,类库开发者如何知道哪些属性被客户端程序员使用?这同样会发生在那些只为实现类库类而创建的方法上,它们也不是设计成可供客户端程序员调用的。如果类库开发者想删除旧的实现,添加新的实现,结果会怎样呢?任何这些成员的改动都可能破环客户端程序员的代码。因此类库开发者会被束缚,不能修改任何事物。\n", + "\n", + "为了解决这一问题,Java 提供了*访问修饰符*(access specifier)供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从“最大权限”到“最小权限”依次是:**public**,**protected**,*包访问权限(package access)*(没有关键字)和 **private**。根据上一段的内容,你可能会想,作为一名类库设计者,你会尽可能将一切都设为 **private**,仅向客户端程序员暴露你愿意他们使用的方法。这就是你通常所做的,尽管这与那些使用其他语言(尤其是 C)编程以及习惯了不受限制地访问任何东西的人们的直觉相违背。\n", + "\n", + "然而,类库组件的概念和对类库组件访问的控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚的类库单元中。Java 中通过 **package** 关键字加以控制,类在相同包下还是在不同包下,会影响访问修饰符。所以在这章开始,你将会学习如何将类库组件置于同一个包下,之后你就能明白访问修饰符的全部含义。\n", + "\n", + "\n", + "\n", + "## 包的概念\n", + "\n", + "包内包含一组类,它们被组织在一个单独的*命名空间*(namespace)下。\n", + "\n", + "例如,标准 Java 发布中有一个工具库,它被组织在 **java.util** 命名空间下。**java.util** 中含有一个类,叫做 **ArrayList**。使用 **ArrayList** 的一种方式是用其全名 **java.util.ArrayList**。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/FullQualification.java\n", + "\n", + "public class FullQualification {\n", + " public static void main(String[] args) {\n", + " java.util.ArrayList list = new java.util.ArrayList();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这种方式使得程序冗长乏味,因此你可以换一种方式,使用 **import** 关键字。如果需要导入某个类,就需要在 **import** 语句中声明:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/SingleImport.java\n", + "import java.util.ArrayList;\n", + "\n", + "public class SingleImport {\n", + " public static void main(String[] args) {\n", + " ArrayList list = new ArrayList();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在你就可以不加限定词,直接使用 **ArrayList** 了。但是对于 **java.util** 包下的其他类,你还是不能用。要导入其中所有的类,只需使用 **\\*** ,就像本书中其他示例那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import java.util.*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "之所以使用导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。类 **A** 中的方法 `f()` 不会与类 **B** 中具有相同签名的方法 `f()` 冲突。但是如果类名冲突呢?假设你创建了一个 **Stack** 类,打算安装在一台已经有别人所写的 **Stack** 类的机器上,该怎么办呢?这种类名的潜在冲突,正是我们需要在 Java 中对命名空间进行完全控制的原因。为了解决冲突,我们为每个类创建一个唯一标识符组合。\n", + "\n", + "到目前为止的大部分示例都只存在单个文件,并为本地使用的,所以尚未受到包名的干扰。但是,这些示例其实已经位于包中了,叫做“未命名”包或*默认包*(default package)。这当然是一种选择,为了简单起见,本书其余部分会尽可能采用这种方式。但是,如果你打算为相同机器上的其他 Java 程序创建友好的类库或程序时,就必须仔细考虑以防类名冲突。\n", + "\n", + "一个 Java 源代码文件称为一个*编译单元(compilation unit)*(有时也称*翻译单元(translation unit)*)。每个编译单元的文件名后缀必须是 **.java**。在编译单元中可以有一个 **public** 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 **.java**)。每个编译单元中只能有一个 **public** 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 **public** 类,此时它们为主 **public** 类提供“支持”类 。\n", + "\n", + "### 代码组织\n", + "\n", + "当编译一个 **.java** 文件时,**.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为“obj”文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。\n", + "\n", + "类库是一组类文件。每个源文件通常都含有一个 **public** 类和任意数量的非 **public** 类,因此每个文件都有一个 **public** 组件。如果把这些组件集中在一起,就需要使用关键字 **package**。\n", + "\n", + "如果你使用了 **package** 语句,它必须是文件中除了注释之外的第一行代码。当你如下这样写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "package hiding;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "意味着这个编译单元是一个名为 **hiding** 类库的一部分。换句话说,你正在声明的编译单元中的 **public** 类名称位于名为 **hiding** 的保护伞下。任何人想要使用该名称,必须指明完整的类名或者使用 **import** 关键字导入 **hiding** 。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同)\n", + "\n", + "例如,假设文件名是 **MyClass.java** ,这意味着文件中只能有一个 **public** 类,且类名必须是 **MyClass**(大小写也与文件名相同):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/mypackage/MyClass.java\n", + "package hiding.mypackage\n", + "\n", + "public class MyClass {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,如果有人想使用 **MyClass** 或 **hiding.mypackage** 中的其他 **public** 类,就必须使用关键字 **import** 来使 **hiding.mypackage** 中的名称可用。还有一种选择是使用完整的名称:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/QualifiedMyClass.java\n", + "\n", + "public class QualifiedMyClass {\n", + " public static void main(String[] args) {\n", + " hiding.mypackage.MyClass m = new hiding.mypackage.MyClass();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "关键字 **import** 使之更简洁:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/ImportedMyClass.java\n", + "import hiding.mypackage.*;\n", + "\n", + "public class ImportedMyClass {\n", + " public static void main(String[] args) {\n", + " MyClass m = new MyClass();\n", + " }\n", + "}\t" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**package** 和 **import** 这两个关键字将单一的全局命名空间分隔开,从而避免名称冲突。\n", + "\n", + "### 创建独一无二的包名\n", + "\n", + "你可能注意到,一个包从未真正被打包成单一的文件,它可以由很多 **.class** 文件构成,因而事情就变得有点复杂了。为了避免这种情况,一种合乎逻辑的做法是将特定包下的所有 **.class** 文件都放在一个目录下。也就是说,利用操作系统的文件结构的层次性。这是 Java 解决混乱问题的一种方式;稍后你还会在我们介绍 **jar** 工具时看到另一种方式。\n", + "\n", + "将所有的文件放在一个子目录还解决了其他的两个问题:创建独一无二的包名和查找可能隐藏于目录结构某处的类。这是通过将 **.class** 文件所在的路径位置编码成 **package** 名称来实现的。按照惯例,**package** 名称是类的创建者的反顺序的 Internet 域名。如果你遵循惯例,因为 Internet 域名是独一无二的,所以你的 **package** 名称也应该是独一无二的,不会发生名称冲突。如果你没有自己的域名,你就得构造一组不大可能与他人重复的组合(比如你的姓名),来创建独一无二的 package 名称。如果你打算发布 Java 程序代码,那么花些力气去获取一个域名是值得的。\n", + "\n", + "此技巧的第二部分是把 **package** 名称分解成你机器上的一个目录,所以当 Java 解释器必须要加载一个 .class 文件时,它能定位到 **.class** 文件所在的位置。首先,它找出环境变量 **CLASSPATH**(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。**CLASSPATH** 包含一个或多个目录,用作查找 .**class** 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(取决于你的操作系统,包名 **foo.bar.baz** 变成 **foo\\bar\\baz** 或 **foo/bar/baz** 或其它)。然后这个路径与 **CLASSPATH** 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 **.class** 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。\n", + "\n", + "为了理解这点,比如说我的域名 **MindviewInc.com**,将之反转并全部改为小写后就是 **com.mindviewinc**,这将作为我创建的类的独一无二的全局名称。(com、edu、org等扩展名之前在 Java 包中都是大写,但是 Java 2 之后都统一用小写。)我决定再创建一个名为 **simple** 的类库,从而细分名称:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "package com.mindviewinc.simple;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个包名可以用作下面两个文件的命名空间保护伞:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// com/mindviewinc/simple/Vector.java\n", + "// Creating a package\n", + "package com.mindviewinc.simple;\n", + "\n", + "public class Vector {\n", + " public Vector() {\n", + " System.out.println(\"com.mindviewinc.simple.Vector\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如前所述,**package** 语句必须是文件的第一行非注释代码。第二个文件看上去差不多:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// com/mindviewinc/simple/List.java\n", + "// Creating a package\n", + "package com.mindviewinc.simple;\n", + "\n", + "public class List {\n", + " System.out.println(\"com.mindview.simple.List\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这两个文件都位于我机器上的子目录中,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "C:\\DOC\\Java\\com\\mindviewinc\\simple" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(注意,本书的每个文件的第一行注释都指明了文件在源代码目录树中的位置——供本书的自动代码提取工具使用。)\n", + "\n", + "如果你回头看这个路径,会看到包名 **com.mindviewinc.simple**,但是路径的第一部分呢?CLASSPATH 环境变量会处理它。我机器上的环境变量部分如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CLASSPATH=.;D:\\JAVA\\LIB;C:\\DOC\\Java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "CLASSPATH 可以包含多个不同的搜索路径。\n", + "\n", + "但是在使用 JAR 文件时,有点不一样。你必须在类路径写清楚 JAR 文件的实际名称,不能仅仅是 JAR 文件所在的目录。因此,对于一个名为 **grape.jar** 的 JAR 文件,类路径应包括:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CLASSPATH=.;D\\JAVA\\LIB;C:\\flavors\\grape.jar" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦设置好类路径,下面的文件就可以放在任意目录:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/LibTest.java\n", + "// Uses the library\n", + "import com.mindviewinc.simple.*;\n", + "\n", + "public class LibTest {\n", + " public static void main(String[] args) {\n", + " Vector v = new Vector();\n", + " List l = new List();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "com.mindviewinc.simple.Vector\n", + "com.mindviewinc.simple.List" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当编译器遇到导入 **simple** 库的 **import** 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 **com/mindviewinc/simple**,然后从已编译的文件中找出名称相符者(对 **Vector** 而言是 **Vector.class**,对 **List** 而言是 **List.class**)。注意,这两个类和其中要访问的方法都必须是 **public** 修饰的。\n", + "\n", + "对于 Java 新手而言,设置 CLASSPATH 是一件麻烦的事(我最初使用时是这么觉得的),后面版本的 JDK 更加智能。你会发现当你安装好 JDK 时,即使不设置 CLASSPATH,也能够编译和运行基本的 Java 程序。但是,为了编译和运行本书的代码示例(从[https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 取得),你必须将本书程序代码树的基本目录加入到 CLASSPATH 中( gradlew 命令管理自身的 CLASSPATH,所以如果你想直接使用 javac 和 java,不用 Gradle 的话,就需要设置 CLASSPATH)。\n", + "\n", + "### 冲突\n", + "\n", + "如果通过 **\\*** 导入了两个包含相同名字类名的类库,会发生什么?例如,假设程序如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import com.mindviewinc.simple.*;\n", + "import java.util.*;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 **java.util.*** 也包含了 **Vector** 类,这就存在潜在的冲突。但是只要你不写导致冲突的代码,就不会有问题——这样很好,否则就得做很多类型检查工作来防止那些根本不会出现的冲突。\n", + "\n", + "现在如果要创建一个 Vector 类,就会出现冲突:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Vector v = new Vector();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里的 **Vector** 类指的是谁呢?编译器不知道,读者也不知道。所以编译器报错,强制你明确指明。对于标准的 Java 类 **Vector**,你可以这么写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "java.util.Vector v = new java.util.Vector();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这种写法完全指明了 **Vector** 类的位置(配合 CLASSPATH),那么就没有必要写 **import java.util.*** 语句,除非使用其他来自 **java.util** 中的类。\n", + "\n", + "或者,可以导入单个类以防冲突——只要不在同一个程序中使用有冲突的名字(若使用了有冲突的名字,必须明确指明全名)。\n", + "\n", + "### 定制工具库\n", + "\n", + "具备了以上知识,现在就可以创建自己的工具库来减少重复的程序代码了。\n", + "\n", + "一般来说,我会使用反转后的域名来命名要创建的工具包,比如 **com.mindviewinc.util** ,但为了简化,这里我把工具包命名为 **onjava**。\n", + "\n", + "比如,下面是“控制流”一章中使用到的 `range()` 方法,采用了 for-in 语法进行简单的遍历:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Range.java\n", + "// Array creation methods that can be used without\n", + "// qualifiers, using static imports:\n", + "package onjava;\n", + "\n", + "public class Range {\n", + " // Produce a sequence [0,n)\n", + " public static int[] range(int n) {\n", + " int[] result = new int[n];\n", + " for (int i = 0; i < n; i++) {\n", + " result[i] = i;\n", + " }\n", + " return result;\n", + " }\n", + " // Produce a sequence [start..end)\n", + " public static int[] range(int start, int end) {\n", + " int sz = end - start;\n", + " int[] result = new int[sz];\n", + " for (int i = 0; i < sz; i++) {\n", + " result[i] = start + i;\n", + " }\n", + " return result;\n", + " }\n", + " // Produce sequence [start..end) incrementing by step\n", + " public static int[] range(int start, int end, int step) {\n", + " int sz = (end - start) / step;\n", + " int[] result = new int[sz];\n", + " for (int i = 0; i < sz; i++) {\n", + " result[i] = start + (i * step);\n", + " }\n", + " return result;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个文件的位置一定是在某个以一个 CLASSPATH 位置开始,然后接着是 **onjava** 的目录下。编译完之后,就可以在系统的任何地方使用 **import static** 语句来使用这些方法了。\n", + "\n", + "从现在开始,无论何时你创建了有用的新工具,都可以把它加入到自己的类库中。在本书中,你将会看到更多的组件加入到 **onjava** 库。\n", + "\n", + "### 使用 import 改变行为\n", + "\n", + "Java 没有 C 的*条件编译*(conditional compilation)功能,该功能使你不必更改任何程序代码而能够切换开关产生不同的行为。Java 之所以去掉此功能,可能是因为 C 在绝大多数情况下使用该功能解决跨平台问题:程序代码的不同部分要根据不同的平台来编译。而 Java 自身就是跨平台设计的,这个功能就没有必要了。\n", + "\n", + "但是,条件编译还有其他的用途。调试是一个很常见的用途,调试功能在开发过程中是开启的,在发布的产品中是禁用的。可以通过改变导入的 **package** 来实现这一目的,修改的方法是将程序中的代码从调试版改为发布版。这个技术可用于任何种类的条件代码。\n", + "\n", + "### 使用包的忠告\n", + "\n", + "当创建一个包时,包名就隐含了目录结构。这个包必须位于包名指定的目录中,该目录必须在以 CLASSPATH 开始的目录中可以查询到。 最初使用关键字 **package** 可能会有点不顺,因为除非遵守“包名对应目录路径”的规则,否则会收到很多意外的运行时错误信息如找不到特定的类,即使这个类就位于同一目录中。如果你收到类似信息,尝试把 **package** 语句注释掉,如果程序能运行的话,你就知道问题出现在哪里了。\n", + "\n", + "注意,编译过的代码通常位于与源代码的不同目录中。这是很多工程的标准,而且集成开发环境(IDE)通常会自动为我们做这些。必须保证 JVM 通过 CLASSPATH 能找到编译后的代码。\n", + "\n", + "\n", + "\n", + "## 访问权限修饰符\n", + "\n", + "Java 访问权限修饰符 **public**,**protected** 和 **private** 位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象。\n", + "\n", + "如果不提供访问修饰符,就意味着\"包访问权限\"。所以无论如何,万物都有某种形式的访问控制权。接下来的几节中,你将学习各种类型的访问权限。\n", + "\n", + "### 包访问权限\n", + "\n", + "本章之前的所有示例要么使用 **public** 访问修饰符,要么就没使用修饰符(*默认访问权限(default access)*)。默认访问权限没有关键字,通常被称为*包访问权限(package access)*(有时也称为 **friendly**)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 **private** 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。\n", + "\n", + "包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类赋予了它们包访问权限的成员相互访问的权限,所以你\"拥有”了包内的程序代码。只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。\n", + "\n", + "类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说\"嗨,我是 **Bob** 的朋友!\",然后想看到 **Bob** 的 **protected**、包访问权限和 **private** 成员。取得对成员的访问权的唯一方式是:\n", + "\n", + "1. 使成员成为 **public**。那么无论是谁,无论在哪,都可以访问它。\n", + "2. 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。\n", + "3. 在\"复用\"这一章你将看到,继承的类既可以访问 **public** 成员,也可以访问 **protected** 成员(但不能访问 **private** 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。但现在不用担心继承和 **protected**。\n", + "4. 提供访问器(accessor)和修改器(mutator)方法(有时也称为\"get/set\" 方法),从而读取和改变值。\n", + "\n", + "### public: 接口访问权限\n", + "\n", + "当你使用关键字 **public**,就意味着紧随 public 后声明的成员对于每个人都是可用的,尤其是使用类库的客户端程序员更是如此。假设定义了一个包含下面编译单元的 **dessert** 包:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/dessert/Cookie.java\n", + "// Creates a library\n", + "package hiding.dessert;\n", + "\n", + "public class Cookie {\n", + " public Cookie() {\n", + " System.out.println(\"Cookie constructor\");\n", + " }\n", + " \n", + " void bite() {\n", + " System.out.println(\"bite\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的\"封装\"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找当前目录。\n", + "\n", + "现在,使用 **Cookie** 创建一个程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/Dinner.java\n", + "// Uses the library\n", + "import hiding.dessert.*;\n", + "\n", + "public class Dinner {\n", + " public static void main(String[] args) {\n", + " Cookie x = new Cookie();\n", + " // -x.bite(); // Can't access\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Cookie constructor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以创建一个 **Cookie** 对象,因为它构造器和类都是 **public** 的。(后面会看到更多 **public** 的概念)但是,在 **Dinner.java** 中无法访问到 **Cookie** 对象中的 `bite()` 方法,因为 `bite()` 只提供了包访问权限,因而在 **dessert** 包之外无法访问,编译器禁止你使用它。\n", + "\n", + "### 默认包\n", + "\n", + "你可能惊讶地发现,以下代码尽管看上去破坏了规则,但是仍然可以编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/Cake.java\n", + "// Accesses a class in a separate compilation unit\n", + "class Cake {\n", + " public static void main(String[] args) {\n", + " Pie x = new Pie();\n", + " x.f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Pie.f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "同一目录下的第二个文件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/Pie.java\n", + "// The other class\n", + "class Pie {\n", + " void f() {\n", + " System.out.println(\"Pie.f()\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 **.**,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。\n", + "\n", + "### private: 你无法访问\n", + "\n", + "关键字 **private** 意味着除了包含该成员的类,其他任何类都无法访问这个成员。同一包中的其他类无法访问 **private** 成员,因此这等于说是自己隔离自己。另一方面,让许多人合作创建一个包也是有可能的。使用 **private**,你可以自由地修改那个被修饰的成员,无需担心会影响同一包下的其他类。\n", + "\n", + "默认的包访问权限通常提供了足够的隐藏措施;记住,使用类的客户端程序员无法访问包访问权限成员。这样做很好,因为默认访问权限是一种我们常用的权限(同时也是一种在忘记添加任何访问权限时自动得到的权限)。因此,通常考虑的是把哪些成员声明成 **public** 供客户端程序员使用。所以,最初不常使用关键字 **private**,因为程序没有它也可以照常工作。然而,使用 **private** 是非常重要的,尤其是在多线程环境中。(在\"并发编程\"一章中将看到)。\n", + "\n", + "以下是一个使用 **private** 的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/IceCream.java\n", + "// Demonstrates \"private\" keyword\n", + "\n", + "class Sundae {\n", + " private Sundae() {}\n", + " static Sundae makeASundae() {\n", + " return new Sundae();\n", + " }\n", + "}\n", + "\n", + "public class IceCream {\n", + " public static void main(String[] args) {\n", + " //- Sundae x = new Sundae();\n", + " Sundae x = Sundae.makeASundae();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以上展示了 **private** 的用武之地:控制如何创建对象,防止别人直接访问某个特定的构造器(或全部构造器)。例子中,你无法通过构造器创建一个 **Sundae** 对象,而必须调用 `makeASundae()` 方法创建对象。\n", + "\n", + "任何可以肯定只是该类的\"助手\"方法,都可以声明为 **private**,以确保不会在包中的其他地方误用它,也防止了你会去改变或删除它。将方法声明为 **private** 确保了你拥有这种选择权。\n", + "\n", + "对于类中的 **private** 属性也是一样。除非必须公开底层实现(这种情况很少见),否则就将属性声明为 **private**。然而,不能因为类中某个对象的引用是 **private**,就认为其他对象也无法拥有该对象的 **public** 引用(参见附录:对象传递和返回)。\n", + "\n", + "### protected: 继承访问权限\n", + "\n", + "要理解 **protected** 的访问权限,我们在内容上需要作一点跳跃。首先,在介绍本书\"复用\"章节前,你不必真正理解本节的内容。但为了内容的完整性,这里作了简要介绍,举了个使用 **protected** 的例子。\n", + "\n", + "关键字 **protected** 处理的是继承的概念,通过继承可以利用一个现有的类——我们称之为基类,然后添加新成员到现有类中而不必碰现有类。我们还可以改变类的现有成员的行为。为了从一个类中继承,需要声明新类 extends 一个现有类,像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class Foo extends Bar {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类定义的其他部分看起来是一样的。\n", + "\n", + "如果你创建了一个新包,并从另一个包继承类,那么唯一能访问的就是被继承类的 **public** 成员。(如果在同一个包中继承,就可以操作所有的包访问权限的成员。)有时,基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就需要使用 **protected**。**protected** 也提供包访问权限,也就是说,相同包内的其他类可以访问 **protected** 元素。\n", + "\n", + "回顾下先前的文件 **Cookie.java**,下面的类不能调用包访问权限的方法 `bite()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/ChocolateChip.java\n", + "// Can't use package-access member from another package\n", + "import hiding.dessert.*;\n", + "\n", + "public class ChocolateChip extends Cookie {\n", + " public ChocolateChip() {\n", + " System.out.println(\"ChocolateChip constructor\");\n", + " } \n", + " \n", + " public void chomp() {\n", + " //- bite(); // Can't access bite\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " ChocolateChip x = new ChocolateChip();\n", + " x.chomp();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Cookie constructor\n", + "ChocolateChip constructor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果类 **Cookie** 中存在一个方法 `bite()`,那么它的任何子类中都存在 `bite()` 方法。但是因为 `bite()` 具有包访问权限并且位于另一个包中,所以我们在这个包中无法使用它。你可以把它声明为 **public**,但这样一来每个人都能访问它,这可能也不是你想要的。如果你将 **Cookie** 改成如下这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/cookie2/Cookie.java\n", + "package hiding.cookie2;\n", + "\n", + "public class Cookie {\n", + " public Cookie() {\n", + " System.out.println(\"Cookie constructor\");\n", + " }\n", + " \n", + " protected void bite() {\n", + " System.out.println(\"bite\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这样,`bite()` 对于所有继承 **Cookie** 的类,都是可访问的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/ChocolateChip2.java\n", + "import hiding.cookie2.*;\n", + "\n", + "public class ChocolateChip2 extends Cookie {\n", + " public ChocoalteChip2() {\n", + " System.out.println(\"ChocolateChip2 constructor\");\n", + " }\n", + " \n", + " public void chomp() {\n", + " bite(); // Protected method\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " ChocolateChip2 x = new ChocolateChip2();\n", + " x.chomp();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Cookie constructor\n", + "ChocolateChip2 constructor\n", + "bite" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管 `bite()` 也具有包访问权限,但它不是 **public** 的。\n", + "\n", + "### 包访问权限 Vs Public 构造器\n", + "\n", + "当你定义一个具有包访问权限的类时,你可以在类中定义一个 public 构造器,编译器不会报错:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/packageaccess/PublicConstructor.java\n", + "package hiding.packageaccess;\n", + "\n", + "class PublicConstructor {\n", + " public PublicConstructor() {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有一个 Checkstyle 工具,你可以运行命令 **gradlew hiding:checkstyleMain** 使用它,它会指出这种写法是虚假的,而且从技术上来说是错误的。实际上你不能从包外访问到这个 **public** 构造器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/CreatePackageAccessObject.java\n", + "// {WillNotCompile}\n", + "import hiding.packageaccess.*;\n", + "\n", + "public class CreatePackageAcessObject {\n", + " public static void main(String[] args) {\n", + " new PublicConstructor();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你编译下这个类,会得到编译错误信息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "CreatePackageAccessObject.java:6:error:\n", + "PublicConstructor is not public in hiding.packageaccess;\n", + "cannot be accessed from outside package\n", + "new PublicConstructor();\n", + "^\n", + "1 error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,在一个具有包访问权限的类中定义一个 **public** 的构造器并不能真的使这个构造器成为 **public**,在声明的时候就应该标记为编译时错误。\n", + "\n", + "\n", + "\n", + "## 接口和实现\n", + "\n", + "访问控制通常被称为*隐藏实现*(implementation hiding)。将数据和方法包装进类中并把具体实现隐藏被称作是*封装*(encapsulation)。其结果就是一个同时带有特征和行为的数据类型。\n", + "\n", + "出于两个重要的原因,访问控制在数据类型内部划定了边界。第一个原因是确立客户端程序员可以使用和不能使用的边界。可以在结构中建立自己的内部机制而不必担心客户端程序员偶尔将内部实现作为他们可以使用的接口的一部分。\n", + "\n", + "这直接引出了第二个原因:将接口与实现分离。如果在一组程序中使用接口,而客户端程序员只能向 **public** 接口发送消息的话,那么就可以自由地修改任何不是 **public** 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码。\n", + "\n", + "为了清晰起见,你可以采用一种创建类的风格:**public** 成员放在类的开头,接着是 **protected** 成员,包访问权限成员,最后是 **private** 成员。这么做的好处是类的使用者可以从头读起,首先会看到对他们而言最重要的部分(public 成员,因为可以从文件外访问它们),直到遇到非 **public** 成员时停止阅读,下面就是内部实现了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/OrganizedByAccess.java\n", + "\n", + "public class OrganizedByAccess {\n", + " public void pub1() {/* ... */}\n", + " public void pub2() {/* ... */}\n", + " public void pub3() {/* ... */}\n", + " private void priv1() {/* ... */}\n", + " private void priv2() {/* ... */}\n", + " private void priv3() {/* ... */}\n", + " private int i;\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这么做只能是程序阅读起来稍微容易一些,因为实现和接口还是混合在一起。也就是说,你仍然能看到源代码——实现部分,因为它就在类中。另外,javadoc 提供的注释文档功能降低了程序代码的可读性对客户端程序员的重要性。将接口展现给类的使用者实际上是类浏览器的任务,类浏览器会展示所有可用的类,并告诉你如何使用它们(比如说哪些成员可用)。在 Java 中,JDK 文档起到了类浏览器的作用。\n", + "\n", + "\n", + "\n", + "## 类访问权限\n", + "\n", + "访问权限修饰符也可以用于确定类库中的哪些类对于类库的使用者是可用的。如果希望某个类可以被客户端程序员使用,就把关键字 **public** 作用于整个类的定义。这甚至控制着客户端程序员能否创建类的对象。\n", + "\n", + "为了控制一个类的访问权限,修饰符必须出现在关键字 **class** 之前:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public class Widget {" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你的类库名是 **hiding**,那么任何客户端程序员都可以通过如下声明访问 **Widget**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import hiding.Widget;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "或者" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import hiding.*;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里有一些额外的限制:\n", + "\n", + "1. 每个编译单元(即每个文件)中只能有一个 **public** 类。这表示,每个编译单元有一个公共的接口用 **public** 类表示。该接口可以包含许多支持包访问权限的类。一旦一个编译单元中出现一个以上的 **public** 类,编译就会报错。\n", + "2. **public** 类的名称必须与含有该编译单元的文件名相同,包括大小写。所以对于 **Widget** 来说,文件名必须是 **Widget.java**,不能是 **widget.java** 或 **WIDGET.java**。再次强调,如果名字不匹配,编译器会报错。\n", + "3. 虽然不是很常见,但是编译单元内没有 **public** 类也是可能的。这时可以随意命名文件(尽管随意命名会让代码的阅读者和维护者感到困惑)。\n", + "\n", + "如果获取了一个在 **hiding** 包中的类,只用来完成 **Widget** 或 **hiding** 包下一些其他 **public** 类所要执行的任务,怎么办呢? 你不想自找麻烦为客户端程序员创建说明文档,并且你认为不久后会完全改变原有方案并将旧版本删除,替换成新版本。为了保留此灵活性,需要确保客户端程序员不依赖隐藏在 **hiding** 中的任何特定细节,那么把 **public** 关键字从类中去掉,给予它包访问权限,就可以了。\n", + "\n", + "当你创建了一个包访问权限的类,把类中的属性声明为 **private** 仍然是有意义的——应该尽可能将所有属性都声明为 **private**,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。一个包访问权限的类只能被用于包内,除非强制将某些方法声明为 **public**,这种情况下,编译器会告诉你。\n", + "\n", + "注意,类既不能是 **private** 的(这样除了该类自身,任何类都不能访问它),也不能是 **protected** 的。所以对于类的访问权限只有两种选择:包访问权限或者 **public**。为了防止类被外界访问,可以将所有的构造器声明为 **private**,这样只有你自己能创建对象(在类的 static 成员中):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// hiding/Lunch.java\n", + "// Demonstrates class access specifiers. Make a class\n", + "// effectively private with private constructors:\n", + "\n", + "class Soup1 {\n", + " private Soup1() {}\n", + " \n", + " public static Soup1 makeSoup() { // [1]\n", + " return new Soup1();\n", + " }\n", + "}\n", + "\n", + "class Soup2 {\n", + " private Soup2() {}\n", + " \n", + " private static Soup2 ps1 = new Soup2(); // [2]\n", + " \n", + " public static Soup2 access() {\n", + " return ps1;\n", + " }\n", + " \n", + " public void f() {}\n", + "}\n", + "// Only one public class allowed per file:\n", + "public class Lunch {\n", + " void testPrivate() {\n", + " // Can't do this! Private constructor:\n", + " //- Soup1 soup = new Soup1();\n", + " }\n", + " \n", + " void testStatic() {\n", + " Soup1 soup = Soup1.makeSoup();\n", + " }\n", + " \n", + " void testSingleton() {\n", + " Soup2.access().f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以像 [1] 那样通过 **static** 方法创建对象,也可以像 [2] 那样先创建一个静态对象,当用户需要访问它时返回对象的引用即可。\n", + "\n", + "到目前为止,大部分的方法要么返回 void,要么返回基本类型,所以 [1] 处的定义乍看之下会有点困惑。方法名(**makeSoup**)前面的 **Soup1** 表明了方法返回的类型。到目前为止,这里经常是 **void**,即不返回任何东西。然而也可以返回对象的引用,就像这里一样。这个方法返回了对 **Soup1** 类对象的引用。\n", + "\n", + "**Soup1** 和 **Soup2** 展示了如何通过将你所有的构造器声明为 **private** 的方式防止直接创建某个类的对象。记住,如果你不显式地创建构造器,编译器会自动为你创建一个无参构造器(没有参数的构造器)。如果我们编写了无参构造器,那么编译器就不会自动创建构造器了。将构造器声明为 **private**,那么谁也无法创建该类的对象了。但是现在别人该怎么使用这个类呢?上述例子给出了两个选择。在 **Soup1** 中,有一个 **static** 方法,它的作用是创建一个新的 **Soup1** 对象并返回对象的引用。如果想要在返回引用之前在 **Soup1** 上做一些额外操作,或是记录创建了多少个 **Soup1** 对象(可以用来限制数量),这种做法是有用的。\n", + "\n", + "**Soup2** 用到了所谓的*设计模式*(design pattern)。这种模式叫做*单例模式*(singleton),因为它只允许创建类的一个对象。**Soup2** 类的对象是作为 **Soup2** 的 **static** **private** 成员而创建的,所以有且只有一个,你只能通过 **public** 修饰的 `access()` 方法访问到这个对象。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "无论在什么样的关系中,划定一些供各成员共同遵守的界限是很重要的。当你创建了一个类库,也就与该类库的使用者产生了联系,他们是类库的客户端程序员,需要使用你的类库创建应用或更大的类库。\n", + "\n", + "没有规则,客户端程序员就可以对类的所有成员为所欲为,即使你希望他们不要操作部分成员。这种情况下,所有事物都是公开的。\n", + "\n", + "本章讨论了类库是如何通过类构建的:首先,介绍了将一组类打包到类库的方式,其次介绍了类如何控制对其成员的访问。\n", + "\n", + "据估计,用 C 语言开发项目,当代码量达到 5 万行和 10 万行时就会出现问题,因为 C 语言只有单一的命名空间,名称开始冲突造成额外的管理开销。在 Java 中,关键字 **package**,包命名模式和关键字 **import** 给了你对于名称的完全控制权,因此可以轻易地避免名称冲突的问题。\n", + "\n", + "控制成员访问权限有两个原因。第一个原因是使用户不要接触他们不该接触的部分,这部分对于类内部来说是必要的,但是不属于客户端程序员所需接口的一部分。因此将方法和属性声明为 **private** 对于客户端程序员来说是一种服务,可以让他们清楚地看到什么是重要的,什么可以忽略。这可以简化他们对类的理解。\n", + "\n", + "第二个也是最重要的原因是为了让类库设计者更改类内部的工作方式,而不用担心会影响到客户端程序员。比如最初以某种方式创建一个类,随后发现如果更改代码结构可以极大地提高运行速度。如果接口与实现被明确地隔离和保护,你可以实现这一目的,而不必强制客户端程序员重新编写代码。访问权限控制确保客户端程序员不会依赖某个类的底层实现的任何部分。\n", + "\n", + "当你具备更改底层实现的能力时,不但可以自由地改善设计,还可能会随意地犯错。无论如何细心地计划和设计,都有可能犯错。当了解到犯错是相对安全的时候,你可以更加放心地实验,更快地学会,更快地完成项目。\n", + "\n", + "类的 **public** 接口是用户真正看到的,所以在分析和设计阶段决定这部分接口是最重要的部分。尽管如此,你仍然有改变的空间。如果最初没有创建出正确的接口,可以添加更多的方法,只要你不删除那些客户端程序员已经在他们的代码中使用的东西。\n", + "\n", + "注意到访问权限控制关注的是类库创建者和外部使用者之间的关系,一种交流方式。很多情况下,事实并非如此。例如,你自己编写了所有的代码,或者在一个小组中工作,所有的东西都放在同一个包下。这些情况下,交流方式则是另外一种,此时严格地遵循访问权限规则也许不是最佳选择,默认(包)访问权限也许就足够好了。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/08-Reuse.ipynb b/jupyter/08-Reuse.ipynb new file mode 100644 index 00000000..ea06c47b --- /dev/null +++ b/jupyter/08-Reuse.ipynb @@ -0,0 +1,1661 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第八章 复用\n", + "\n", + "\n", + "> 代码复用是面向对象编程(OOP)最具魅力的原因之一。\n", + "\n", + "对于像 C 语言等面向过程语言来说,“复用”通常指的就是“复制代码”。任何语言都可通过简单复制来达到代码复用的目的,但是这样做的效果并不好。Java 围绕“类”(Class)来解决问题。我们可以直接使用别人构建或调试过的代码,而非创建新类、重新开始。\n", + "\n", + "如何在不污染源代码的前提下使用现存代码是需要技巧的。在本章里,你将学习到两种方式来达到这个目的:\n", + "\n", + "1. 第一种方式直接了当。在新类中创建现有类的对象。这种方式叫做“组合”(Composition),通过这种方式复用代码的功能,而非其形式。\n", + "\n", + "2. 第二种方式更为微妙。创建现有类类型的新类。照字面理解:采用现有类形式,又无需在编码时改动其代码,这种方式就叫做“继承”(Inheritance),编译器会做大部分的工作。**继承**是面向对象编程(OOP)的重要基础之一。更多功能相关将在[多态](./09-Polymorphism.md)(Polymorphism)章节中介绍。\n", + "\n", + "组合与继承的语法、行为上有许多相似的地方(这其实是有道理的,毕竟都是基于现有类型构建新的类型)。在本章中,你会学到这两种代码复用的方法。\n", + "\n", + "\n", + "\n", + "## 组合语法\n", + "\n", + "在前面的学习中,“组合”(Composition)已经被多次使用。你仅需要把对象的引用(object references)放置在一个新的类里,这就使用了组合。例如,假设你需要一个对象,其中内置了几个 **String** 对象,两个基本类型(primitives)的属性字段,一个其他类的对象。对于非基本类型对象,将引用直接放置在新类中,对于基本类型属性字段则仅进行声明。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/SprinklerSystem.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Composition for code reuse\n", + "\n", + "class WaterSource {\n", + " private String s;\n", + " WaterSource() {\n", + " System.out.println(\"WaterSource()\");\n", + " s = \"Constructed\";\n", + " }\n", + " @Override\n", + " public String toString() { return s; }\n", + "}\n", + "\n", + "public class SprinklerSystem {\n", + " private String valve1, valve2, valve3, valve4;\n", + " private WaterSource source = new WaterSource();\n", + " private int i;\n", + " private float f;\n", + " @Override\n", + " public String toString() {\n", + " return\n", + " \"valve1 = \" + valve1 + \" \" +\n", + " \"valve2 = \" + valve2 + \" \" +\n", + " \"valve3 = \" + valve3 + \" \" +\n", + " \"valve4 = \" + valve4 + \"\\n\" +\n", + " \"i = \" + i + \" \" + \"f = \" + f + \" \" +\n", + " \"source = \" + source; // [1]\n", + " }\n", + " public static void main(String[] args) {\n", + " SprinklerSystem sprinklers = new SprinklerSystem();\n", + " System.out.println(sprinklers);\n", + " }\n", + "}\n", + "/* Output:\n", + "WaterSource()\n", + "valve1 = null valve2 = null valve3 = null valve4 = null\n", + "i = 0 f = 0.0 source = Constructed\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注释来告诉编译器,以确保正确地覆盖。**@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。\n", + "\n", + "编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。初始化引用有四种方法:\n", + "\n", + "1. 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。\n", + "2. 在该类的构造函数中。\n", + "3. 在实际使用对象之前。这通常称为*延迟初始化*。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。\n", + "4. 使用实例初始化。\n", + "\n", + "以上四种实例创建的方法例子在这:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Bath.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Constructor initialization with composition\n", + "\n", + "class Soap {\n", + " private String s;\n", + " Soap() {\n", + " System.out.println(\"Soap()\");\n", + " s = \"Constructed\";\n", + " }\n", + " @Override\n", + " public String toString() { return s; }\n", + "}\n", + "\n", + "public class Bath {\n", + " private String // Initializing at point of definition:\n", + " s1 = \"Happy\",\n", + " s2 = \"Happy\",\n", + " s3, s4;\n", + " private Soap castille;\n", + " private int i;\n", + " private float toy;\n", + " public Bath() {\n", + " System.out.println(\"Inside Bath()\");\n", + " s3 = \"Joy\";\n", + " toy = 3.14f;\n", + " castille = new Soap();\n", + " }\n", + " // Instance initialization:\n", + " { i = 47; }\n", + " @Override\n", + " public String toString() {\n", + " if(s4 == null) // Delayed initialization:\n", + " s4 = \"Joy\";\n", + " return\n", + " \"s1 = \" + s1 + \"\\n\" +\n", + " \"s2 = \" + s2 + \"\\n\" +\n", + " \"s3 = \" + s3 + \"\\n\" +\n", + " \"s4 = \" + s4 + \"\\n\" +\n", + " \"i = \" + i + \"\\n\" +\n", + " \"toy = \" + toy + \"\\n\" +\n", + " \"castille = \" + castille;\n", + " }\n", + " public static void main(String[] args) {\n", + " Bath b = new Bath();\n", + " System.out.println(b);\n", + " }\n", + "}\n", + "/* Output:\n", + "Inside Bath()\n", + "Soap()\n", + "s1 = Happy\n", + "s2 = Happy\n", + "s3 = Joy\n", + "s4 = Joy\n", + "i = 47\n", + "toy = 3.14\n", + "castille = Constructed\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **Bath** 构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。\n", + "\n", + "当调用 `toString()` 时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。\n", + "\n", + "\n", + "\n", + "## 继承语法\n", + "\n", + "继承是所有面向对象语言的一个组成部分。事实证明,在创建类时总是要继承,因为除非显式地继承其他类,否则就隐式地继承 Java 的标准根类对象(Object)。\n", + "\n", + "组合的语法很明显,但是继承使用了一种特殊的语法。当你继承时,你说,“这个新类与那个旧类类似。你可以在类主体的左大括号前的代码中声明这一点,使用关键字 **extends** 后跟基类的名称。当你这样做时,你将自动获得基类中的所有字段和方法。这里有一个例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Detergent.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Inheritance syntax & properties\n", + "\n", + "class Cleanser {\n", + " private String s = \"Cleanser\";\n", + " public void append(String a) { s += a; }\n", + " public void dilute() { append(\" dilute()\"); }\n", + " public void apply() { append(\" apply()\"); }\n", + " public void scrub() { append(\" scrub()\"); }\n", + " @Override\n", + " public String toString() { return s; }\n", + " public static void main(String[] args) {\n", + " Cleanser x = new Cleanser();\n", + " x.dilute(); x.apply(); x.scrub();\n", + " System.out.println(x);\n", + " }\n", + "}\n", + "\n", + "public class Detergent extends Cleanser {\n", + " // Change a method:\n", + " @Override\n", + " public void scrub() {\n", + " append(\" Detergent.scrub()\");\n", + " super.scrub(); // Call base-class version\n", + " }\n", + " // Add methods to the interface:\n", + " public void foam() { append(\" foam()\"); }\n", + " // Test the new class:\n", + " public static void main(String[] args) {\n", + " Detergent x = new Detergent();\n", + " x.dilute();\n", + " x.apply();\n", + " x.scrub();\n", + " x.foam();\n", + " System.out.println(x);\n", + " System.out.println(\"Testing base class:\");\n", + " Cleanser.main(args);\n", + " }\n", + "}\n", + "/* Output:\n", + "Cleanser dilute() apply() Detergent.scrub() scrub()\n", + "foam()\n", + "Testing base class:\n", + "Cleanser dilute() apply() scrub()\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这演示了一些特性。首先,在 **Cleanser** 的 `append()` 方法中,使用 `+=` 操作符将字符串连接到 **s**,这是 Java 设计人员“重载”来处理字符串的操作符之一 (还有 + )。\n", + "\n", + "第二,**Cleanser** 和 **Detergent** 都包含一个 `main()` 方法。你可以为每个类创建一个 `main()` ; 这允许对每个类进行简单的测试。当你完成测试时,不需要删除 `main()`; 你可以将其留在以后的测试中。即使程序中有很多类都有 `main()` 方法,惟一运行的只有在命令行上调用的 `main()`。这里,当你使用 **java Detergent** 时候,就调用了 `Detergent.main()`。但是你也可以使用 **java Cleanser** 来调用 `Cleanser.main()`,即使 **Cleanser** 不是一个公共类。即使类只具有包访问权,也可以访问 `public main()`。\n", + "\n", + "在这里,`Detergent.main()` 显式地调用 `Cleanser.main()`,从命令行传递相同的参数(当然,你可以传递任何字符串数组)。\n", + "\n", + "**Cleanser** 中的所有方法都是公开的。请记住,如果不使用任何访问修饰符,则成员默认为包访问权限,这只允许包内成员访问。因此,如果没有访问修饰符,那么包内的任何人都可以使用这些方法。例如,**Detergent** 就没有问题。但是,如果其他包中的类继承 **Cleanser**,则该类只能访问 **Cleanser** 的公共成员。因此,为了允许继承,一般规则是所有字段为私有,所有方法为公共。(受保护成员也允许派生类访问;你以后会知道的。)在特定的情况下,你必须进行调整,但这是一个有用的指南。\n", + "\n", + "**Cleanser** 的接口中有一组方法: `append()`、`dilute()`、`apply()`、`scrub()` 和 `toString()`。因为 **Detergent** 是从 **Cleanser** 派生的(通过 **extends** 关键字),所以它会在其接口中自动获取所有这些方法,即使你没有在 **Detergent** 中看到所有这些方法的显式定义。那么,可以把继承看作是复用类。如在 `scrub()` 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 `scrub()` 内部,不能简单地调用 `scrub()`,因为这会产生递归调用。为了解决这个问题,Java的 **super** 关键字引用了当前类继承的“超类”(基类)。因此表达式 `super.scrub()` 调用方法 `scrub()` 的基类版本。\n", + "\n", + "继承时,你不受限于使用基类的方法。你还可以像向类添加任何方法一样向派生类添加新方法:只需定义它。方法 `foam()` 就是一个例子。`Detergent.main()` 中可以看到,对于 **Detergent** 对象,你可以调用 **Cleanser** 和 **Detergent** 中可用的所有方法 (如 `foam()` )。\n", + "\n", + "\n", + "\n", + "### 初始化基类\n", + "\n", + "现在涉及到两个类:基类和派生类。想象派生类生成的结果对象可能会让人感到困惑。从外部看,新类与基类具有相同的接口,可能还有一些额外的方法和字段。但是继承并不只是复制基类的接口。当你创建派生类的对象时,它包含基类的子对象。这个子对象与你自己创建基类的对象是一样的。只是从外部看,基类的子对象被包装在派生类的对象中。\n", + "\n", + "必须正确初始化基类子对象,而且只有一种方法可以保证这一点 : 通过调用基类构造函数在构造函数中执行初始化,该构造函数具有执行基类初始化所需的所有适当信息和特权。Java 自动在派生类构造函数中插入对基类构造函数的调用。下面的例子展示了三个层次的继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Cartoon.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Constructor calls during inheritance\n", + "\n", + "class Art {\n", + " Art() {\n", + " System.out.println(\"Art constructor\");\n", + " }\n", + "}\n", + "\n", + "class Drawing extends Art {\n", + " Drawing() {\n", + " System.out.println(\"Drawing constructor\");\n", + " }\n", + "}\n", + "\n", + "public class Cartoon extends Drawing {\n", + " public Cartoon() {\n", + " System.out.println(\"Cartoon constructor\");\n", + " }\n", + " public static void main(String[] args) {\n", + " Cartoon x = new Cartoon();\n", + " }\n", + "}\n", + "/* Output:\n", + "Art constructor\n", + "Drawing constructor\n", + "Cartoon constructor\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "构造从基类“向外”进行,因此基类在派生类构造函数能够访问它之前进行初始化。即使不为 **Cartoon** 创建构造函数,编译器也会为你合成一个无参数构造函数,调用基类构造函数。尝试删除 **Cartoon** 构造函数来查看这个。\n", + "\n", + "\n", + "\n", + "### 带参数的构造函数\n", + "\n", + "上面的所有例子中构造函数都是无参数的 ; 编译器很容易调用这些构造函数,因为不需要参数。如果没有无参数的基类构造函数,或者必须调用具有参数的基类构造函数,则必须使用 **super** 关键字和适当的参数列表显式地编写对基类构造函数的调用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Chess.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Inheritance, constructors and arguments\n", + "\n", + "class Game {\n", + " Game(int i) {\n", + " System.out.println(\"Game constructor\");\n", + " }\n", + "}\n", + "\n", + "class BoardGame extends Game {\n", + " BoardGame(int i) {\n", + " super(i);\n", + " System.out.println(\"BoardGame constructor\");\n", + " }\n", + "}\n", + "\n", + "public class Chess extends BoardGame {\n", + " Chess() {\n", + " super(11);\n", + " System.out.println(\"Chess constructor\");\n", + " }\n", + " public static void main(String[] args) {\n", + " Chess x = new Chess();\n", + " }\n", + "}\n", + "/* Output:\n", + "Game constructor\n", + "BoardGame constructor\n", + "Chess constructor\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有在 **BoardGame** 构造函数中调用基类构造函数,编译器就会报错找不到 `Game()` 的构造函数。此外,对基类构造函数的调用必须是派生类构造函数中的第一个操作。(如果你写错了,编译器会提醒你。)\n", + "\n", + "\n", + "\n", + "## 委托\n", + "\n", + "Java不直接支持的第三种重用关系称为委托。这介于继承和组合之间,因为你将一个成员对象放在正在构建的类中(比如组合),但同时又在新类中公开来自成员对象的所有方法(比如继承)。例如,宇宙飞船需要一个控制模块:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/SpaceShipControls.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "\n", + "public class SpaceShipControls {\n", + " void up(int velocity) {}\n", + " void down(int velocity) {}\n", + " void left(int velocity) {}\n", + " void right(int velocity) {}\n", + " void forward(int velocity) {}\n", + " void back(int velocity) {}\n", + " void turboBoost() {}\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "建造宇宙飞船的一种方法是使用继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/DerivedSpaceShip.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "\n", + "public class\n", + "DerivedSpaceShip extends SpaceShipControls {\n", + " private String name;\n", + " public DerivedSpaceShip(String name) {\n", + " this.name = name;\n", + " }\n", + " @Override\n", + " public String toString() { return name; }\n", + " public static void main(String[] args) {\n", + " DerivedSpaceShip protector =\n", + " new DerivedSpaceShip(\"NSEA Protector\");\n", + " protector.forward(100);\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然而, **DerivedSpaceShip** 并不是真正的“一种” **SpaceShipControls** ,即使你“告诉” **DerivedSpaceShip** 调用 `forward()`。更准确地说,一艘宇宙飞船包含了 **SpaceShipControls **,同时 **SpaceShipControls** 中的所有方法都暴露在宇宙飞船中。委托解决了这个难题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/SpaceShipDelegation.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "\n", + "public class SpaceShipDelegation {\n", + " private String name;\n", + " private SpaceShipControls controls =\n", + " new SpaceShipControls();\n", + " public SpaceShipDelegation(String name) {\n", + " this.name = name;\n", + " }\n", + " // Delegated methods:\n", + " public void back(int velocity) {\n", + " controls.back(velocity);\n", + " }\n", + " public void down(int velocity) {\n", + " controls.down(velocity);\n", + " }\n", + " public void forward(int velocity) {\n", + " controls.forward(velocity);\n", + " }\n", + " public void left(int velocity) {\n", + " controls.left(velocity);\n", + " }\n", + " public void right(int velocity) {\n", + " controls.right(velocity);\n", + " }\n", + " public void turboBoost() {\n", + " controls.turboBoost();\n", + " }\n", + " public void up(int velocity) {\n", + " controls.up(velocity);\n", + " }\n", + " public static void main(String[] args) {\n", + " SpaceShipDelegation protector =\n", + " new SpaceShipDelegation(\"NSEA Protector\");\n", + " protector.forward(100);\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "方法被转发到底层 **control** 对象,因此接口与继承的接口是相同的。但是,你对委托有更多的控制,因为你可以选择只在成员对象中提供方法的子集。\n", + "\n", + "虽然Java语言不支持委托,但是开发工具常常支持。例如,上面的例子是使用 JetBrains Idea IDE 自动生成的。\n", + "\n", + "\n", + "\n", + "## 结合组合与继承\n", + "\n", + "你将经常同时使用组合和继承。下面的例子展示了使用继承和组合创建类,以及必要的构造函数初始化:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/PlaceSetting.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Combining composition & inheritance\n", + "\n", + "class Plate {\n", + " Plate(int i) {\n", + " System.out.println(\"Plate constructor\");\n", + " }\n", + "}\n", + "\n", + "class DinnerPlate extends Plate {\n", + " DinnerPlate(int i) {\n", + " super(i);\n", + " System.out.println(\"DinnerPlate constructor\");\n", + " }\n", + "}\n", + "\n", + "class Utensil {\n", + " Utensil(int i) {\n", + " System.out.println(\"Utensil constructor\");\n", + " }\n", + "}\n", + "\n", + "class Spoon extends Utensil {\n", + " Spoon(int i) {\n", + " super(i);\n", + " System.out.println(\"Spoon constructor\");\n", + " }\n", + "}\n", + "\n", + "class Fork extends Utensil {\n", + " Fork(int i) {\n", + " super(i);\n", + " System.out.println(\"Fork constructor\");\n", + " }\n", + "}\n", + "\n", + "class Knife extends Utensil {\n", + " Knife(int i) {\n", + " super(i);\n", + " System.out.println(\"Knife constructor\");\n", + " }\n", + "}\n", + "\n", + "// A cultural way of doing something:\n", + "class Custom {\n", + " Custom(int i) {\n", + " System.out.println(\"Custom constructor\");\n", + " }\n", + "}\n", + "\n", + "public class PlaceSetting extends Custom {\n", + " private Spoon sp;\n", + " private Fork frk;\n", + " private Knife kn;\n", + " private DinnerPlate pl;\n", + " public PlaceSetting(int i) {\n", + " super(i + 1);\n", + " sp = new Spoon(i + 2);\n", + " frk = new Fork(i + 3);\n", + " kn = new Knife(i + 4);\n", + " pl = new DinnerPlate(i + 5);\n", + " System.out.println(\"PlaceSetting constructor\");\n", + " }\n", + " public static void main(String[] args) {\n", + " PlaceSetting x = new PlaceSetting(9);\n", + " }\n", + "}\n", + "/* Output:\n", + "Custom constructor\n", + "Utensil constructor\n", + "Spoon constructor\n", + "Utensil constructor\n", + "Fork constructor\n", + "Utensil constructor\n", + "Knife constructor\n", + "Plate constructor\n", + "DinnerPlate constructor\n", + "PlaceSetting constructor\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管编译器强制你初始化基类,并要求你在构造函数的开头就初始化基类,但它并不监视你以确保你初始化了成员对象。注意类是如何干净地分离的。你甚至不需要方法重用代码的源代码。你最多只导入一个包。(这对于继承和组合都是正确的。)\n", + "\n", + "\n", + "\n", + "### 保证适当的清理\n", + "\n", + "Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在\"异常\"章节中描述的——你必须通过在 **finally **子句中放置此类清理来防止异常。\n", + "\n", + "请考虑一个在屏幕上绘制图片的计算机辅助设计系统的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/CADSystem.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Ensuring proper cleanup\n", + "// {java reuse.CADSystem}\n", + "package reuse;\n", + "\n", + "class Shape {\n", + " Shape(int i) {\n", + " System.out.println(\"Shape constructor\");\n", + " }\n", + " void dispose() {\n", + " System.out.println(\"Shape dispose\");\n", + " }\n", + "}\n", + "\n", + "class Circle extends Shape {\n", + " Circle(int i) {\n", + " super(i);\n", + " System.out.println(\"Drawing Circle\");\n", + " }\n", + " @Override\n", + " void dispose() {\n", + " System.out.println(\"Erasing Circle\");\n", + " super.dispose();\n", + " }\n", + "}\n", + "\n", + "class Triangle extends Shape {\n", + " Triangle(int i) {\n", + " super(i);\n", + " System.out.println(\"Drawing Triangle\");\n", + " }\n", + " @Override\n", + " void dispose() {\n", + " System.out.println(\"Erasing Triangle\");\n", + " super.dispose();\n", + " }\n", + "}\n", + "\n", + "class Line extends Shape {\n", + " private int start, end;\n", + " Line(int start, int end) {\n", + " super(start);\n", + " this.start = start;\n", + " this.end = end;\n", + " System.out.println(\n", + " \"Drawing Line: \" + start + \", \" + end);\n", + " }\n", + " @Override\n", + " void dispose() {\n", + " System.out.println(\n", + " \"Erasing Line: \" + start + \", \" + end);\n", + " super.dispose();\n", + " }\n", + "}\n", + "\n", + "public class CADSystem extends Shape {\n", + " private Circle c;\n", + " private Triangle t;\n", + " private Line[] lines = new Line[3];\n", + " public CADSystem(int i) {\n", + " super(i + 1);\n", + " for(int j = 0; j < lines.length; j++)\n", + " lines[j] = new Line(j, j*j);\n", + " c = new Circle(1);\n", + " t = new Triangle(1);\n", + " System.out.println(\"Combined constructor\");\n", + " }\n", + " @Override\n", + " public void dispose() {\n", + " System.out.println(\"CADSystem.dispose()\");\n", + " // The order of cleanup is the reverse\n", + " // of the order of initialization:\n", + " t.dispose();\n", + " c.dispose();\n", + " for(int i = lines.length - 1; i >= 0; i--)\n", + " lines[i].dispose();\n", + " super.dispose();\n", + " }\n", + " public static void main(String[] args) {\n", + " CADSystem x = new CADSystem(47);\n", + " try {\n", + " // Code and exception handling...\n", + " } finally {\n", + " x.dispose();\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "Shape constructor\n", + "Shape constructor\n", + "Drawing Line: 0, 0\n", + "Shape constructor\n", + "Drawing Line: 1, 1\n", + "Shape constructor\n", + "Drawing Line: 2, 4\n", + "Shape constructor\n", + "Drawing Circle\n", + "Shape constructor\n", + "Drawing Triangle\n", + "Combined constructor\n", + "CADSystem.dispose()\n", + "Erasing Triangle\n", + "Shape dispose\n", + "Erasing Circle\n", + "Shape dispose\n", + "Erasing Line: 2, 4\n", + "Shape dispose\n", + "Erasing Line: 1, 1\n", + "Shape dispose\n", + "Erasing Line: 0, 0\n", + "Shape dispose\n", + "Shape dispose\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个系统中的所有东西都是某种 **Shape** (它本身是一种 **Object**,因为它是从根类隐式继承的) 。除了使用 **super** 调用该方法的基类版本外,每个类还覆盖 `dispose()` 方法。特定的 **Shape** 类——**Circle**、**Triangle** 和 **Line**,都有 “draw” 构造函数,尽管在对象的生命周期中调用的任何方法都可以负责做一些需要清理的事情。每个类都有自己的 `dispose()` 方法来将非内存的内容恢复到对象存在之前的状态。\n", + "\n", + "在 `main()` 中,有两个关键字是你以前没有见过的,在\"异常\"一章之前不会详细解释: **try** 和 **finally**。**try** 关键字表示后面的块 (用花括号分隔 )是一个受保护的区域,这意味着它得到了特殊处理。其中一个特殊处理是,无论 **try** 块如何退出,在这个保护区域之后的 **finally** 子句中的代码总是被执行。(通过异常处理,可以用许多不同寻常的方式留下 **try** 块。)这里,**finally** 子句的意思是,“无论发生什么,始终调用 `x.dispose()`。”\n", + "\n", + "在清理方法 (在本例中是 `dispose()` ) 中,还必须注意基类和成员对象清理方法的调用顺序,以防一个子对象依赖于另一个子对象。首先,按与创建的相反顺序执行特定于类的所有清理工作。(一般来说,这要求基类元素仍然是可访问的。) 然后调用基类清理方法,如这所示。\n", + "\n", + "在很多情况下,清理问题不是问题;你只需要让垃圾收集器来完成这项工作。但是,当你必须执行显式清理时,就需要多做努力,更加细心,因为在垃圾收集方面没有什么可以依赖的。可能永远不会调用垃圾收集器。如果调用,它可以按照它想要的任何顺序回收对象。除了内存回收外,你不能依赖垃圾收集来做任何事情。如果希望进行清理,可以使用自己的清理方法,不要使用 `finalize()`。\n", + "\n", + "\n", + "\n", + "### 名称隐藏\n", + "\n", + "如果 Java 基类的方法名多次重载,则在派生类中重新定义该方法名不会隐藏任何基类版本。不管方法是在这个级别定义的,还是在基类中定义的,重载都会起作用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Hide.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Overloading a base-class method name in a derived\n", + "// class does not hide the base-class versions\n", + "\n", + "class Homer {\n", + " char doh(char c) {\n", + " System.out.println(\"doh(char)\");\n", + " return 'd';\n", + " }\n", + " float doh(float f) {\n", + " System.out.println(\"doh(float)\");\n", + " return 1.0f;\n", + " }\n", + "}\n", + "\n", + "class Milhouse {}\n", + "\n", + "class Bart extends Homer {\n", + " void doh(Milhouse m) {\n", + " System.out.println(\"doh(Milhouse)\");\n", + " }\n", + "}\n", + "\n", + "public class Hide {\n", + " public static void main(String[] args) {\n", + " Bart b = new Bart();\n", + " b.doh(1);\n", + " b.doh('x');\n", + " b.doh(1.0f);\n", + " b.doh(new Milhouse());\n", + " }\n", + "}\n", + "/* Output:\n", + "doh(float)\n", + "doh(char)\n", + "doh(float)\n", + "doh(Milhouse)\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。在下一章中你将看到,使用与基类中完全相同的签名和返回类型覆盖相同名称的方法要常见得多。否则就会令人困惑。\n", + "\n", + "你已经看到了Java 5 **@Override **注释,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法时,你可以选择添加这个注释,如果你不小心用了重载而不是重写,编译器会产生一个错误消息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Lisa.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// {WillNotCompile}\n", + "\n", + "class Lisa extends Homer {\n", + " @Override void doh(Milhouse m) {\n", + " System.out.println(\"doh(Milhouse)\");\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:方法不会覆盖超类中的方法, **@Override** 注释防止你意外地重载。\n", + "\n", + "\n", + "\n", + "## 组合与继承的选择\n", + "\n", + "组合和继承都允许在新类中放置子对象(组合是显式的,而继承是隐式的)。你或许想知道这二者之间的区别,以及怎样在二者间做选择。\n", + "\n", + "当你想在新类中包含一个已有类的功能时,使用组合,而非继承。也就是说,在新类中嵌入一个对象(通常是私有的),以实现其功能。新类的使用者看到的是你所定义的新类的接口,而非嵌入对象的接口。\n", + "\n", + "有时让类的用户直接访问到新类中的组合成分是有意义的。只需将成员对象声明为 **public** 即可(可以把这当作“半委托”的一种)。成员对象隐藏了具体实现,所以这是安全的。当用户知道你正在组装一组部件时,会使得接口更加容易理解。下面的 car 对象是个很好的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Car.java\n", + "// Composition with public objects\n", + "class Engine {\n", + " public void start() {}\n", + " public void rev() {}\n", + " public void stop() {}\n", + "}\n", + "\n", + "class Wheel {\n", + " public void inflate(int psi) {}\n", + "}\n", + "\n", + "class Window {\n", + " public void rollup() {}\n", + " public void rolldown() {}\n", + "}\n", + "\n", + "class Door {\n", + " public Window window = new Window();\n", + " \n", + " public void open() {}\n", + " public void close() {}\n", + "}\n", + "\n", + "public class Car {\n", + " public Engine engine = new Engine();\n", + " public Wheel[] wheel = new Wheel[4];\n", + " public Door left = new Door(), right = new Door(); // 2-door\n", + " \n", + " public Car() {\n", + " for (int i = 0; i < 4; i++) {\n", + " wheel[i] = new Wheel();\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Car car = new Car();\n", + " car.left.window.rollup();\n", + " car.wheel[0].inflate(72);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为在这个例子中 car 的组合也是问题分析的一部分(不是底层设计的部分),所以声明成员为 **public** 有助于客户端程序员理解如何使用类,且降低了类创建者面临的代码复杂度。但是,记住这是一个特例。通常来说,属性还是应该声明为 **private**。\n", + "\n", + "当使用继承时,使用一个现有类并开发出它的新版本。通常这意味着使用一个通用类,并为了某个特殊需求将其特殊化。稍微思考下,你就会发现,用一个交通工具对象来组成一部车是毫无意义的——车不包含交通工具,它就是交通工具。这种“是一个”的关系是用继承来表达的,而“有一个“的关系则用组合来表达。\n", + "\n", + "\n", + "\n", + "## protected\n", + "\n", + "即然你已经接触到继承,关键字 **protected** 就变得有意义了。在理想世界中,仅靠关键字 **private** 就足够了。在实际项目中,却经常想把一个事物尽量对外界隐藏,而允许派生类的成员访问。\n", + "\n", + "关键字 **protected** 就起这个作用。它表示“就类的用户而言,这是 **private** 的。但对于任何继承它的子类或在同一包中的类,它是可访问的。”(**protected** 也提供了包访问权限)\n", + "\n", + "尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。然后通过 **protected** 控制类的继承者的访问权限。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Orc.java\n", + "// The protected keyword\n", + "class Villain {\n", + " private String name;\n", + " \n", + " protected void set(String nm) {\n", + " name = nm;\n", + " }\n", + " \n", + " Villain(String name) {\n", + " this.name = name;\n", + " }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"I'm a Villain and my name is \" + name;\n", + " }\n", + "}\n", + "\n", + "public class Orc extends Villain {\n", + " private int orcNumber;\n", + " \n", + " public Orc(String name, int orcNumber) {\n", + " super(name);\n", + " this.orcNumber = orcNumber;\n", + " }\n", + " \n", + " public void change(String name, int orcNumber) {\n", + " set(name); // Available because it's protected\n", + " this.orcNumber = orcNumber;\n", + " }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"Orc \" + orcNumber + \": \" + super.toString();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Orc orc = new Orc(\"Limburger\", 12);\n", + " System.out.println(orc);\n", + " orc.change(\"Bob\", 19);\n", + " System.out.println(orc);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Orc 12: I'm a Villain and my name is Limburger\n", + "Orc 19: I'm a Villain and my name is Bob" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`change()` 方法可以访问 `set()` 方法,因为 `set()` 方法是 **protected**。注意到,类 **Orc** 的 `toString()` 方法也使用了基类的版本。\n", + "\n", + "\n", + "\n", + "## 向上转型\n", + "\n", + "继承最重要的方面不是为新类提供方法。它是新类与基类的一种关系。简而言之,这种关系可以表述为“新类是已有类的一种类型”。\n", + "\n", + "这种描述并非是解释继承的一种花哨方式,这是直接由语言支持的。例如,假设有一个基类 **Instrument** 代表音乐乐器和一个派生类 **Wind**。 因为继承保证了基类的所有方法在派生类中也是可用的,所以任意发送给该基类的消息也能发送给派生类。如果 **Instrument** 有一个 `play()` 方法,那么 **Wind** 也有该方法。这意味着你可以准确地说 **Wind** 对象也是一种类型的 **Instrument**。下面例子展示了编译器是如何支持这一概念的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Wind.java\n", + "// Inheritance & upcasting\n", + "class Instrument {\n", + " public void play() {}\n", + " \n", + " static void tune(Instrument i) {\n", + " // ...\n", + " i.play();\n", + " }\n", + "}\n", + "\n", + "// Wind objects are instruments\n", + "// because they have the same interface:\n", + "public class Wind extends Instrument {\n", + " public static void main(String[] args) {\n", + " Wind flute = new Wind();\n", + " Instrument.tune(flute); // Upcasting\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`tune()` 方法接受了一个 **Instrument** 类型的引用。但是,在 **Wind** 的 `main()` 方法里,`tune()` 方法却传入了一个 **Wind** 引用。鉴于 Java 对类型检查十分严格,一个接收一种类型的方法接受了另一种类型看起来很奇怪,除非你意识到 **Wind** 对象同时也是一个 **Instrument** 对象,而且 **Instrument** 的 `tune` 方法一定会存在于 **Wind** 中。在 `tune()` 中,代码对 **Instrument** 和 所有 **Instrument** 的派生类起作用,这种把 **Wind** 引用转换为 **Instrument** 引用的行为称作*向上转型*。\n", + "\n", + "该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是:\n", + "\n", + "![Wind 类图](../images/1561774164644.png)\n", + "\n", + "继承图中派生类转型为基类是向上的,所以通常称作*向上转型*。因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的。也就是说,派生类是基类的一个超集。它可能比基类包含更多的方法,但它必须至少具有与基类一样的方法。在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因。\n", + "\n", + "也可以执行与向上转型相反的向下转型,但是会有问题,对于该问题会放在下一章和“类型信息”一章进行更深入的探讨。\n", + "\n", + "### 再论组合和继承\n", + "\n", + "在面向对象编程中,创建和使用代码最有可能的方法是将数据和方法一起打包到类中,然后使用该类的对象。也可以使用已有的类通过组合来创建新类。继承其实不太常用。因此尽管在教授 OOP 的过程中我们多次强调继承,但这并不意味着要尽可能使用它。恰恰相反,尽量少使用它,除非确实使用继承是有帮助的。一种判断使用组合还是继承的最清晰的方法是问一问自己是否需要把新类向上转型为基类。如果必须向上转型,那么继承就是必要的,但如果不需要,则要进一步考虑是否该采用继承。“多态”一章提出了一个使用向上转型的最有力的理由,但是只要记住问一问“我需要向上转型吗?”,就能在这两者中作出较好的选择。\n", + "\n", + "\n", + "\n", + "## final关键字\n", + "\n", + "根据上下文环境,Java 的关键字 **final** 的含义有些微的不同,但通常它指的是“这是不能被改变的”。防止改变有两个原因:设计或效率。因为这两个原因相差很远,所以有可能误用关键字 **final**。\n", + "\n", + "以下几节讨论了可能使用 **final** 的三个地方:数据、方法和类。\n", + "\n", + "### final 数据\n", + "\n", + "许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如:\n", + "\n", + "1. 一个永不改变的编译时常量。\n", + "2. 一个在运行时初始化就不会改变的值。\n", + "\n", + "对于编译时常量这种情况,编译器可以把常量带入计算中;也就是说,可以在编译时计算,减少了一些运行时的负担。在 Java 中,这类常量必须是基本类型,而且用关键字 **final** 修饰。你必须在定义常量的时候进行赋值。\n", + "\n", + "一个被 **static** 和 **final** 同时修饰的属性只会占用一段不能改变的存储空间。\n", + "\n", + "当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。\n", + "\n", + "下面例子展示了 **final** 属性的使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/FinalData.java\n", + "// The effect of final on fields\n", + "import java.util.*;\n", + "\n", + "class Value {\n", + " int i; // package access\n", + " \n", + " Value(int i) {\n", + " this.i = i;\n", + " }\n", + "}\n", + "\n", + "public class FinalData {\n", + " private static Random rand = new Random(47);\n", + " private String id;\n", + " \n", + " public FinalData(String id) {\n", + " this.id = id;\n", + " }\n", + " // Can be compile-time constants:\n", + " private final int valueOne = 9;\n", + " private static final int VALUE_TWO = 99;\n", + " // Typical public constant:\n", + " public static final int VALUE_THREE = 39;\n", + " // Cannot be compile-time constants:\n", + " private final int i4 = rand.nextInt(20);\n", + " static final int INT_5 = rand.nextInt(20);\n", + " private Value v1 = new Value(11);\n", + " private final Value v2 = new Value(22);\n", + " private static final Value VAL_3 = new Value(33);\n", + " // Arrays:\n", + " private final int[] a = {1, 2, 3, 4, 5, 6};\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return id + \": \" + \"i4 = \" + i4 + \", INT_5 = \" + INT_5;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " FinalData fd1 = new FinalData(\"fd1\");\n", + " //- fd1.valueOne++; // Error: can't change value\n", + " fd1.v2.i++; // Object isn't constant\n", + " fd1.v1 = new Value(9); // OK -- not final\n", + " for (int i = 0; i < fd1.a.length; i++) {\n", + " fd1.a[i]++; // Object isn't constant\n", + " }\n", + " //- fd1.v2 = new Value(0); // Error: Can't\n", + " //- fd1.VAL_3 = new Value(1); // change reference\n", + " //- fd1.a = new int[3];\n", + " System.out.println(fd1);\n", + " System.out.println(\"Creating new FinalData\");\n", + " FinalData fd2 = new FinalData(\"fd2\");\n", + " System.out.println(fd1);\n", + " System.out.println(fd2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fd1: i4 = 15, INT_5 = 18\n", + "Creating new FinalData\n", + "fd1: i4 = 15, INT_5 = 18\n", + "fd2: i4 = 13, INT_5 = 18" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 **valueOne** 和 **VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。**VALUE_THREE** 是一种更加典型的常量定义的方式:**public** 意味着可以在包外访问,**static** 强调只有一个,**final** 说明是一个常量。\n", + "\n", + "按照惯例,带有恒定初始值的 **final** **static** 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。(源于 C 语言中定义常量的方式。)\n", + "\n", + "我们不能因为某数据被 **final** 修饰就认为在编译时可以知道它的值。由上例中的 **i4** 和 **INT_5** 可以看出,它们在运行时才会赋值随机数。示例部分也展示了将 **final** 值定义为 **static** 和非 **static** 的区别。此区别只有当值在运行时被初始化时才会显现,因为编译器对编译时数值一视同仁。(而且编译时数值可能因优化而消失。)当运行程序时就能看到这个区别。注意到 **fd1** 和 **fd2** 的 **i4** 值不同,但 **INT_5** 的值并没有因为创建了第二个 **FinalData** 对象而改变,这是因为它是 **static** 的,在加载时已经被初始化,并不是每次创建新对象时都初始化。\n", + "\n", + "**v1** 到 **VAL_3** 变量说明了 **final** 引用的意义。正如你在 `main()` 中所见,**v2** 是 **final** 的并不意味着你不能修改它的值。因为它是引用,所以只是说明它不能指向一个新的对象。这对于数组具有同样的意义,数组只不过是另一种引用。(我不知道有什么方法能使数组引用本身成为 **final**。)看起来,声明引用为 **final** 没有声明基本类型 **final** 有用。\n", + "\n", + "### 空白 final\n", + "\n", + "空白 final 指的是没有初始化值的 **final** 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 **final** 属性值不同,也能保持它的不变性。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/BlankFinal.java\n", + "// \"Blank\" final fields\n", + "class Poppet {\n", + " private int i;\n", + " \n", + " Poppet(int ii) {\n", + " i = ii;\n", + " }\n", + "}\n", + "\n", + "public class BlankFinal {\n", + " private final int i = 0; // Initialized final\n", + " private final int j; // Blank final\n", + " private final Poppet p; // Blank final reference\n", + " // Blank finals MUST be initialized in constructor\n", + " public BlankFinal() {\n", + " j = 1; // Initialize blank final\n", + " p = new Poppet(1); // Init blank final reference\n", + " }\n", + " \n", + " public BlankFinal(int x) {\n", + " j = x; // Initialize blank final\n", + " p = new Poppet(x); // Init blank final reference\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " new BlankFinal();\n", + " new BlankFinal(47);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。\n", + "\n", + "### final 参数\n", + "\n", + "在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/FinalArguments.java\n", + "// Using \"final\" with method arguments\n", + "class Gizmo {\n", + " public void spin() {\n", + " \n", + " }\n", + "}\n", + "\n", + "public class FinalArguments {\n", + " void with(final Gizmo g) {\n", + " //-g = new Gizmo(); // Illegal -- g is final\n", + " }\n", + " \n", + " void without(Gizmo g) {\n", + " g = new Gizmo(); // OK -- g is not final\n", + " g.spin();\n", + " }\n", + " \n", + " //void f(final int i) { i++; } // Can't change\n", + " // You can only read from a final primitive\n", + " int g(final int i) {\n", + " return i + 1;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " FinalArguments bf = new FinalArguments();\n", + " bf.without(null);\n", + " bf.with(null);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "方法 `f()` 和 `g()` 展示了 **final** 基本类型参数的使用情况。你只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类。这将在”内部类“章节中详解。\n", + "\n", + "### final 方法\n", + "\n", + "使用 **final** 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。\n", + "\n", + "过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。\n", + "\n", + "在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 **final**。\n", + "\n", + "### final 和 private\n", + "\n", + "类中所有的 **private** 方法都隐式地指定为 **final**。因为不能访问 **private** 方法,所以不能覆写它。可以给 **private** 方法添加 **final** 修饰,但是并不能给方法带来额外的含义。\n", + "\n", + "以下情况会令人困惑,当你试图覆写一个 **private** 方法(隐式是 **final** 的)时,看上去奏效,而且编译器不会给出错误信息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/FinalOverridingIllusion.java\n", + "// It only looks like you can override\n", + "// a private or private final method\n", + "class WithFinals {\n", + " // Identical to \"private\" alone:\n", + " private final void f() {\n", + " System.out.println(\"WithFinals.f()\");\n", + " }\n", + " // Also automatically \"final\":\n", + " private void g() {\n", + " System.out.println(\"WithFinals.g()\");\n", + " }\n", + "}\n", + "\n", + "class OverridingPrivate extends WithFinals {\n", + " private final void f() {\n", + " System.out.println(\"OverridingPrivate.f()\");\n", + " }\n", + " \n", + " private void g() {\n", + " System.out.println(\"OverridingPrivate.g()\");\n", + " }\n", + "}\n", + "\n", + "class OverridingPrivate2 extends OverridingPrivate {\n", + " public final void f() {\n", + " System.out.println(\"OverridingPrivate2.f()\");\n", + " } \n", + " \n", + " public void g() {\n", + " System.out.println(\"OverridingPrivate2.g()\");\n", + " }\n", + "}\n", + "\n", + "public class FinalOverridingIllusion {\n", + " public static void main(String[] args) {\n", + " OverridingPrivate2 op2 = new OverridingPrivate2();\n", + " op2.f();\n", + " op2.g();\n", + " // You can upcast:\n", + " OverridingPrivate op = op2;\n", + " // But you can't call the methods:\n", + " //- op.f();\n", + " //- op.g();\n", + " // Same here:\n", + " WithFinals wf = op2;\n", + " //- wf.f();\n", + " //- wf.g();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "OverridingPrivate2.f()\n", + "OverridingPrivate2.g()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"覆写\"只发生在方法是基类的接口时。也就是说,必须能将一个对象向上转型为基类并调用相同的方法(这一点在下一章阐明)。如果一个方法是 **private** 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 **public**,**protected** 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。由于 **private** 方法无法触及且能有效隐藏,除了把它看作类中的一部分,其他任何事物都不需要考虑到它。\n", + "\n", + "### final 类\n", + "\n", + "当说一个类是 **final** (**final** 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Jurassic.java\n", + "// Making an entire class final\n", + "class SmallBrain {}\n", + "\n", + "final class Dinosaur {\n", + " int i = 7;\n", + " int j = 1;\n", + " SmallBrain x = new SmallBrain();\n", + " \n", + " void f() {}\n", + "}\n", + "\n", + "//- class Further extends Dinosaur {}\n", + "// error: Cannot extend final class 'Dinosaur'\n", + "public class Jurassic {\n", + " public static void main(String[] args) {\n", + " Dinosaur n = new Dinosaur();\n", + " n.f();\n", + " n.i = 40;\n", + " n.j++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。\n", + "\n", + "### final 忠告\n", + "\n", + "在设计类时将一个方法指明为 final 看上去是明智的。你可能会觉得没人会覆写那个方法。有时这是对的。\n", + "\n", + "但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。\n", + "\n", + "Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,而且从效率考虑(这近乎是个幻想),如果它的所有方法没有被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。\n", + "\n", + "第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。\n", + "\n", + "Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。\n", + "\n", + "\n", + "\n", + "## 类初始化和加载\n", + "\n", + "在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 **statics** 初始化的顺序不会造成麻烦。在 C++ 中,如果一个 **static** 期望使用另一个 **static**,而另一个 **static** 还没有初始化,就会出现问题。\n", + "\n", + "Java 中不存在这样的问题,因为它采用了一种不同的方式加载。因为 Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 **static** 属性或方法。构造器也是一个 **static** 方法尽管它的 **static** 关键字是隐式的。因此,准确地说,一个类当它任意一个 **static** 成员被访问时,就会被加载。\n", + "\n", + "首次使用时就是 **static** 初始化发生时。所有的 **static** 对象和 **static** 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。**static** 变量只被初始化一次。\n", + "\n", + "### 继承和初始化\n", + "\n", + "了解包括继承在内的整个初始化过程是有帮助的,这样可以对所发生的一切有全局性的把握。考虑下面的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// reuse/Beetle.java\n", + "// The full process of initialization\n", + "class Insect {\n", + " private int i = 9;\n", + " protected int j;\n", + " \n", + " Insect() {\n", + " System.out.println(\"i = \" + i + \", j = \" + j);\n", + " j = 39;\n", + " }\n", + " \n", + " private static int x1 = printInit(\"static Insect.x1 initialized\");\n", + " \n", + " static int printInit(String s) {\n", + " System.out.println(s);\n", + " return 47;\n", + " }\n", + "}\n", + "\n", + "public class Beetle extends Insect {\n", + " private int k = printInit(\"Beetle.k.initialized\");\n", + " \n", + " public Beetle() {\n", + " System.out.println(\"k = \" + k);\n", + " System.out.println(\"j = \" + j);\n", + " }\n", + " \n", + " private static int x2 = printInit(\"static Beetle.x2 initialized\");\n", + " \n", + " public static void main(String[] args) {\n", + " System.out.println(\"Beetle constructor\");\n", + " Beetle b = new Beetle();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "static Insect.x1 initialized\n", + "static Beetle.x2 initialized\n", + "Beetle constructor\n", + "i = 9, j = 0\n", + "Beetle.k initialized\n", + "k = 47\n", + "j = 39" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当执行 **java Beetle**,首先会试图访问 **Beetle** 类的 `main()` 方法(一个静态方法),加载器启动并找出 **Beetle** 类的编译代码(在名为 **Beetle.class** 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。)\n", + "\n", + "如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 **Insect**)的 **static** 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 **static** 的初始化可能依赖基类成员是否被正确地初始化。\n", + "\n", + "至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "继承和组合都是从已有类型创建新类型。组合将已有类型作为新类型底层实现的一部分,继承复用的是接口。\n", + "\n", + "使用继承时,派生类具有基类接口,因此可以向上转型为基类,这对于多态至关重要,在下一章你将看到。\n", + "\n", + "尽管在面向对象编程时极力强调继承,但在开始设计时,优先使用组合(或委托),只有当确实需要时再使用继承。组合更具灵活性。另外,通过对成员类型使用继承的技巧,可以在运行时改变成员的类型和行为。因此,可以在运行时改变组合对象的行为。\n", + "\n", + "在设计一个系统时,目标是发现或创建一系列类,每个类有特定的用途,而且既不应太大(包括太多功能难以复用),也不应太小(不添加其他功能就无法使用)。如果设计变得过于复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常是有帮助的。\n", + "\n", + "当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/09-Polymorphism.ipynb b/jupyter/09-Polymorphism.ipynb new file mode 100644 index 00000000..f06488fb --- /dev/null +++ b/jupyter/09-Polymorphism.ipynb @@ -0,0 +1,1809 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第九章 多态\n", + "\n", + "> 曾经有人请教我 “ Babbage 先生,如果输入错误的数字到机器中,会得出正确结果吗?” 我无法理解产生如此问题的概念上的困惑。 —— Charles Babbage (1791 - 1871)\n", + "\n", + "多态是面向对象编程语言中,继数据抽象和继承之外的第三个重要特性。\n", + "\n", + "多态提供了另一个维度的接口与实现分离,以解耦做什么和怎么做。多态不仅能改善代码的组织,提高代码的可读性,而且能创建有扩展性的程序——无论在最初创建项目时还是在添加新特性时都可以“生长”的程序。\n", + "\n", + "封装通过合并特征和行为来创建新的数据类型。隐藏实现通过将细节**私有化**把接口与实现分离。这种类型的组织机制对于有面向过程编程背景的人来说,更容易理解。而多态是消除类型之间的耦合。在上一章中,继承允许把一个对象视为它本身的类型或它的基类类型。这样就能把很多派生自一个基类的类型当作同一类型处理,因而一段代码就可以无差别地运行在所有不同的类型上了。多态方法调用允许一种类型表现出与相似类型的区别,只要这些类型派生自一个基类。这种区别是当你通过基类调用时,由方法的不同行为表现出来的。\n", + "\n", + "在本章中,通过一些基本、简单的例子(这些例子中只保留程序中与多态有关的行为),你将逐步学习多态(也称为*动态绑定*或*后期绑定*或*运行时绑定*)。\n", + "\n", + "\n", + "\n", + "## 向上转型回顾\n", + "\n", + "在上一章中,你看到了如何把一个对象视作它的自身类型或它的基类类型。这种把一个对象引用当作它的基类引用的做法称为向上转型,因为继承图中基类一般都位于最上方。\n", + "\n", + "同样你也在下面的音乐乐器例子中发现了问题。即然几个例子都要演奏乐符(**Note**),首先我们先在包中单独创建一个 Note 枚举类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/music/Note.java\n", + "// Notes to play on musical instruments\n", + "package polymorphism.music;\n", + "\n", + "public enum Note {\n", + " MIDDLE_C, C_SHARP, B_FLAT; // Etc.\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "枚举已经在”第 6 章初始化和清理“一章中介绍过了。\n", + "\n", + "这里,**Wind** 是一种 **Instrument**;因此,**Wind** 继承 **Instrument**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/music/Instrument.java\n", + "package polymorphism.music;\n", + "\n", + "class Instrument {\n", + " public void play(Note n) {\n", + " System.out.println(\"Instrument.play()\");\n", + " }\n", + "}\n", + "\n", + "// polymorphism/music/Wind.java\n", + "package polymorphism.music;\n", + "// Wind objects are instruments\n", + "// because they have the same interface:\n", + "public class Wind extends Instrument {\n", + " // Redefine interface method:\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Wind.play() \" + n);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Music** 的方法 `tune()` 接受一个 **Instrument** 引用,同时也接受任何派生自 **Instrument** 的类引用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/music/Music.java\n", + "// Inheritance & upcasting\n", + "// {java polymorphism.music.Music}\n", + "package polymorphism.music;\n", + "\n", + "public class Music {\n", + " public static void tune(Instrument i) {\n", + " // ...\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Wind flute = new Wind();\n", + " tune(flute); // Upcasting\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Wind.play() MIDDLE_C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `main()` 中你看到了 `tune()` 方法传入了一个 **Wind** 引用,而没有做类型转换。这样做是允许的—— **Instrument** 的接口一定存在于 **Wind** 中,因此 **Wind** 继承了 **Instrument**。从 **Wind** 向上转型为 **Instrument** 可能“缩小”接口,但不会比 **Instrument** 的全部接口更少。\n", + "\n", + "### 忘掉对象类型\n", + "\n", + "**Music.java** 看起来似乎有点奇怪。为什么所有人都故意忘记掉对象类型呢?当向上转型时,就会发生这种情况,而且看起来如果 `tune()` 接受的参数是一个 **Wind** 引用会更为直观。这会带来一个重要问题:如果你那么做,就要为系统内 **Instrument** 的每种类型都编写一个新的 `tune()` 方法。假设按照这种推理,再增加 **Stringed** 和 **Brass** 这两种 **Instrument** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/music/Music2.java\n", + "// Overloading instead of upcasting\n", + "// {java polymorphism.music.Music2}\n", + "package polymorphism.music;\n", + "\n", + "class Stringed extends Instrument {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Stringed.play() \" + n);\n", + " }\n", + "}\n", + "\n", + "class Brass extends Instrument {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Brass.play() \" + n);\n", + " }\n", + "}\n", + "\n", + "public class Music2 {\n", + " public static void tune(Wind i) {\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " public static void tune(Stringed i) {\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " public static void tune(Brass i) {\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Wind flute = new Wind();\n", + " Stringed violin = new Stringed();\n", + " Brass frenchHorn = new Brass();\n", + " tune(flute); // No upcasting\n", + " tune(violin);\n", + " tune(frenchHorn);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Wind.play() MIDDLE_C\n", + "Stringed.play() MIDDLE_C\n", + "Brass.play() MIDDLE_C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这样行得通,但是有一个主要缺点:必须为添加的每个新 **Instrument** 类编写特定的方法。这意味着开始时就需要更多的编程,而且以后如果添加类似 `tune()` 的新方法或 **Instrument** 的新类型时,还有大量的工作要做。考虑到如果你忘记重载某个方法,编译器也不会提示你,这会造成类型的整个处理过程变得难以管理。\n", + "\n", + "如果只写一个方法以基类作为参数,而不用管是哪个具体派生类,这样会变得更好吗?也就是说,如果忘掉派生类,编写的代码只与基类打交道,会不会更好呢?\n", + "\n", + "这正是多态所允许的。但是大部分拥有面向过程编程背景的程序员会对多态的运作方式感到一些困惑。\n", + "\n", + "\n", + "\n", + "## 转机\n", + "\n", + "运行程序后会看到 **Music.java** 的难点。**Wind.play()** 的输出结果正是我们期望的,然而它看起来似乎不应该得出这样的结果。观察 `tune()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public static void tune(Instrument i) {\n", + " // ...\n", + " i.play(Note.MIDDLE_C);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它接受一个 **Instrument** 引用。那么编译器是如何知道这里的 **Instrument** 引用指向的是 **Wind**,而不是 **Brass** 或 **Stringed** 呢?编译器无法得知。为了深入理解这个问题,有必要研究一下*绑定*这个主题。\n", + "\n", + "### 方法调用绑定\n", + "\n", + "将一个方法调用和一个方法主体关联起来称作*绑定*。若绑定发生在程序运行前(如果有的话,由编译器和链接器实现),叫做*前期绑定*。你可能从来没有听说这个术语,因为它是面向过程语言不需选择默认的绑定方式,例如在 C 语言中就只有*前期绑定*这一种方法调用。\n", + "\n", + "上述程序让人困惑的地方就在于前期绑定,因为编译器只知道一个 **Instrument** 引用,它无法得知究竟会调用哪个方法。\n", + "\n", + "解决方法就是*后期绑定*,意味着在运行时根据对象的类型进行绑定。后期绑定也称为*动态绑定*或*运行时绑定*。当一种语言实现了后期绑定,就必须具有某种机制在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器仍然不知道对象的类型,但是方法调用机制能找到正确的方法体并调用。每种语言的后期绑定机制都不同,但是可以想到,对象中一定存在某种类型信息。\n", + "\n", + "Java 中除了 **static** 和 **final** 方法(**private** 方法也是隐式的 **final**)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。\n", + "\n", + "为什么将一个对象指明为 **final** ?正如前一章所述,它可以防止方法被重写。但更重要的一点可能是,它有效地”关闭了“动态绑定,或者说告诉编译器不需要对其进行动态绑定。这可以让编译器为 **final** 方法生成更高效的代码。然而,大部分情况下这样做不会对程序的整体性能带来什么改变,因此最好是为了设计使用 **final**,而不是为了提升性能而使用。\n", + "\n", + "### 产生正确的行为\n", + "\n", + "一旦当你知道 Java 中所有方法都是通过后期绑定来实现多态时,就可以编写只与基类打交道的代码,而且代码对于派生类来说都能正常地工作。或者换种说法,你向对象发送一条消息,让对象自己做正确的事。\n", + "\n", + "面向对象编程中的经典例子是形状 **Shape**。这个例子很直观,但不幸的是,它可能让初学者困惑,认为面向对象编程只适合图形化程序设计,实际上不是这样。\n", + "\n", + "形状的例子中,有一个基类称为 **Shape** ,多个不同的派生类型分别是:**Circle**,**Square**,**Triangle** 等等。这个例子之所以好用,是因为我们可以直接说“圆(Circle)是一种形状(Shape)”,这很容易理解。继承图展示了它们之间的关系:\n", + "\n", + "![形状继承图](../images/1562204648023.png)\n", + "\n", + "向上转型就像下面这么简单:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Shape s = new Circle();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这会创建一个 **Circle** 对象,引用被赋值给 **Shape** 类型的变量 s,这看似错误(将一种类型赋值给另一种类型),然而是没问题的,因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。\n", + "\n", + "假设你调用了一个基类方法(在各个派生类中都被重写):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "s.draw()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可能再次认为 **Shape** 的 `draw()` 方法被调用,因为 s 是一个 **Shape** 引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是 **Circle** 的 `draw()` 方法,这是正确的。\n", + "\n", + "下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 为它的所有子类建立了公共接口——所有的形状都可以被绘画和擦除:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/shape/Shape.java\n", + "package polymorphism.shape;\n", + "\n", + "public class Shape {\n", + " public void draw() {}\n", + " public void erase() {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "派生类通过重写这些方法为每个具体的形状提供独一无二的方法行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/shape/Circle.java\n", + "package polymorphism.shape;\n", + "\n", + "public class Circle extends Shape {\n", + " @Override\n", + " public void draw() {\n", + " System.out.println(\"Circle.draw()\");\n", + " }\n", + " @Override\n", + " public void erase() {\n", + " System.out.println(\"Circle.erase()\");\n", + " }\n", + "}\n", + "\n", + "// polymorphism/shape/Square.java\n", + "package polymorphism.shape;\n", + "\n", + "public class Square extends Shape {\n", + " @Override\n", + " public void draw() {\n", + " System.out.println(\"Square.draw()\");\n", + " }\n", + " @Override\n", + " public void erase() {\n", + " System.out.println(\"Square.erase()\");\n", + " }\n", + " }\n", + "\n", + "// polymorphism/shape/Triangle.java\n", + "package polymorphism.shape;\n", + "\n", + "public class Triangle extends Shape {\n", + " @Override\n", + " public void draw() {\n", + " System.out.println(\"Triangle.draw()\");\n", + " }\n", + " @Override\n", + " public void erase() {\n", + " System.out.println(\"Triangle.erase()\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**RandomShapes** 是一种工厂,每当我们调用 `get()` 方法时,就会产生一个指向随机创建的 **Shape** 对象的引用。注意,向上转型发生在 **return** 语句中,每条 **return** 语句取得一个指向某个 **Circle**,**Square** 或 **Triangle** 的引用, 并将其以 **Shape** 类型从 `get()` 方法发送出去。因此无论何时调用 `get()` 方法,你都无法知道具体的类型是什么,因为你总是得到一个简单的 **Shape** 引用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/shape/RandomShapes.java\n", + "// A \"factory\" that randomly creates shapes\n", + "package polymorphism.shape;\n", + "import java.util.*;\n", + "\n", + "public class RandomShapes {\n", + " private Random rand = new Random(47);\n", + " \n", + " public Shape get() {\n", + " switch(rand.nextInt(3)) {\n", + " default:\n", + " case 0: return new Circle();\n", + " case 1: return new Square();\n", + " case 2: return new Triangle();\n", + " }\n", + " }\n", + " \n", + " public Shape[] array(int sz) {\n", + " Shape[] shapes = new Shape[sz];\n", + " // Fill up the array with shapes:\n", + " for (int i = 0; i < shapes.length; i++) {\n", + " shapes[i] = get();\n", + " }\n", + " return shapes;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`array()` 方法分配并填充了 **Shape** 数组,这里使用了 for-in 表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/Shapes.java\n", + "// Polymorphism in Java\n", + "import polymorphism.shape.*;\n", + "\n", + "public class Shapes {\n", + " public static void main(String[] args) {\n", + " RandomShapes gen = new RandomShapes();\n", + " // Make polymorphic method calls:\n", + " for (Shape shape: gen.array(9)) {\n", + " shape.draw();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Triangle.draw()\n", + "Triangle.draw()\n", + "Square.draw()\n", + "Triangle.draw()\n", + "Square.draw()\n", + "Triangle.draw()\n", + "Square.draw()\n", + "Triangle.draw()\n", + "Circle.draw()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`main()` 方法中包含了一个 **Shape** 引用组成的数组,其中每个元素通过调用 **RandomShapes** 类的 `get()` 方法生成。现在你只知道拥有一些形状,但除此之外一无所知(编译器也是如此)。然而当遍历这个数组为每个元素调用 `draw()` 方法时,从运行程序的结果中可以看到,与类型有关的特定行为奇迹般地发生了。\n", + "\n", + "随机生成形状是为了让大家理解:在编译时,编译器不需要知道任何具体信息以进行正确的调用。所有对方法 `draw()` 的调用都是通过动态绑定进行的。\n", + "\n", + "### 可扩展性\n", + "\n", + "现在让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 `tune()` 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 `tune()` 的模型,只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。\n", + "\n", + "考虑一下乐器的例子,如果在基类中添加更多的方法,并加入一些新类,将会发生什么呢:\n", + "\n", + "![乐器继承图](../images/1562252767216.png)\n", + "\n", + "所有的新类都可以和原有类正常运行,不需要改动 `tune()` 方法。即使 `tune()` 方法单独存放在某个文件中,而且向 **Instrument** 接口中添加了新的方法,`tune()` 方法也无需再编译就能正确运行。下面是类图的实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/music3/Music3.java\n", + "// An extensible program\n", + "// {java polymorphism.music3.Music3}\n", + "package polymorphism.music3;\n", + "import polymorphism.music.Note;\n", + "\n", + "class Instrument {\n", + " void play(Note n) {\n", + " System.out.println(\"Instrument.play() \" + n);\n", + " }\n", + " \n", + " String what() {\n", + " return \"Instrument\";\n", + " }\n", + " \n", + " void adjust() {\n", + " System.out.println(\"Adjusting Instrument\");\n", + " }\n", + "}\n", + "\n", + "class Wind extends Instrument {\n", + " @Override\n", + " void play(Note n) {\n", + " System.out.println(\"Wind.play() \" + n);\n", + " }\n", + " @Override\n", + " String what() {\n", + " return \"Wind\";\n", + " }\n", + " @Override\n", + " void adjust() {\n", + " System.out.println(\"Adjusting Wind\");\n", + " }\n", + "}\n", + "\n", + "class Percussion extends Instrument {\n", + " @Override\n", + " void play(Note n) {\n", + " System.out.println(\"Percussion.play() \" + n);\n", + " }\n", + " @Override\n", + " String what() {\n", + " return \"Percussion\";\n", + " }\n", + " @Override\n", + " void adjust() {\n", + " System.out.println(\"Adjusting Percussion\");\n", + " }\n", + "}\n", + "\n", + "class Stringed extends Instrument {\n", + " @Override\n", + " void play(Note n) {\n", + " System.out.println(\"Stringed.play() \" + n);\n", + " } \n", + " @Override\n", + " String what() {\n", + " return \"Stringed\";\n", + " }\n", + " @Override\n", + " void adjust() {\n", + " System.out.println(\"Adjusting Stringed\");\n", + " }\n", + "}\n", + "\n", + "class Brass extends Wind {\n", + " @Override\n", + " void play(Note n) {\n", + " System.out.println(\"Brass.play() \" + n);\n", + " }\n", + " @Override\n", + " void adjust() {\n", + " System.out.println(\"Adjusting Brass\");\n", + " }\n", + "}\n", + "\n", + "class Woodwind extends Wind {\n", + " @Override\n", + " void play(Note n) {\n", + " System.out.println(\"Woodwind.play() \" + n);\n", + " }\n", + " @Override\n", + " String what() {\n", + " return \"Woodwind\";\n", + " }\n", + "}\n", + "\n", + "public class Music3 {\n", + " // Doesn't care about type, so new types\n", + " // added to the system still work right:\n", + " public static void tune(Instrument i) {\n", + " // ...\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " public static void tuneAll(Instrument[] e) {\n", + " for (Instrument i: e) {\n", + " tune(i);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " // Upcasting during addition to the array:\n", + " Instrument[] orchestra = {\n", + " new Wind(),\n", + " new Percussion(),\n", + " new Stringed(),\n", + " new Brass(),\n", + " new Woodwind()\n", + " };\n", + " tuneAll(orchestra);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Wind.play() MIDDLE_C\n", + "Percussion.play() MIDDLE_C\n", + "Stringed.play() MIDDLE_C\n", + "Brass.play() MIDDLE_C\n", + "Woodwind.play() MIDDLE_C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "新方法 `what()` 返回一个带有类描述的 **String** 引用,`adjust()` 提供一些乐器调音的方法。\n", + "\n", + "在 `main()` 方法中,当向 **orchestra** 数组添加元素时,元素会自动向上转型为 **Instrument**。\n", + "\n", + "`tune()` 方法可以忽略周围所有代码发生的变化,仍然可以正常运行。这正是我们期待多态能提供的特性。代码中的修改不会破坏程序中其他不应受到影响的部分。换句话说,多态是一项“将改变的事物与不变的事物分离”的重要技术。\n", + "\n", + "### 陷阱:“重写”私有方法\n", + "\n", + "你可能天真地试图像下面这样做:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/PrivateOverride.java\n", + "// Trying to override a private method\n", + "// {java polymorphism.PrivateOverride}\n", + "package polymorphism;\n", + "\n", + "public class PrivateOverride {\n", + " private void f() {\n", + " System.out.println(\"private f()\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " PrivateOverride po = new Derived();\n", + " po.f();\n", + " }\n", + "}\n", + "\n", + "public Derived extends PrivateOverride {\n", + " public void f() {\n", + " System.out.println(\"public f()\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "private f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可能期望输出是 **public f()**,然而 **private** 方法可以当作是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。\n", + "\n", + "结论是只有非 **private** 方法才能被重写,但是得小心重写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。\n", + "\n", + "如果使用了 `@Override` 注解,就能检测出问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/PrivateOverride2.java\n", + "// Detecting a mistaken override using @Override\n", + "// {WillNotCompile}\n", + "package polymorphism;\n", + "\n", + "public class PrivateOverride2 {\n", + " private void f() {\n", + " System.out.println(\"private f()\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " PrivateOverride2 po = new Derived2();\n", + " po.f();\n", + " }\n", + "}\n", + "\n", + "class Derived2 extends PrivateOverride2 {\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"public f()\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器报错信息是:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "error: method does not override or\n", + "implement a method from a supertype" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 陷阱:属性与静态方法\n", + "\n", + "一旦学会了多态,就可以以多态的思维方式考虑每件事。然而,只有普通的方法调用可以是多态的。例如,如果你直接访问一个属性,该访问会在编译时解析:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/FieldAccess.java\n", + "// Direct field access is determined at compile time\n", + "class Super {\n", + " public int field = 0;\n", + " \n", + " public int getField() {\n", + " return field;\n", + " }\n", + "}\n", + "\n", + "class Sub extends Super {\n", + " public int field = 1;\n", + " \n", + " @Override\n", + " public int getField() {\n", + " return field;\n", + " }\n", + " \n", + " public int getSuperField() {\n", + " return super.field;\n", + " }\n", + "}\n", + "\n", + "public class FieldAccess {\n", + " public static void main(String[] args) {\n", + " Super sup = new Sub(); // Upcast\n", + " System.out.println(\"sup.field = \" + sup.field + \n", + " \", sup.getField() = \" + sup.getField());\n", + " Sub sub = new Sub();\n", + " System.out.println(\"sub.field = \" + sub.field + \n", + " \", sub.getField() = \" + sub.getField()\n", + " + \", sub.getSuperField() = \" + sub.getSuperField())\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sup.field = 0, sup.getField() = 1\n", + "sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当 **Sub** 对象向上转型为 **Super** 引用时,任何属性访问都被编译器解析,因此不是多态的。在这个例子中,**Super.field** 和 **Sub.field** 被分配了不同的存储空间,因此,**Sub** 实际上包含了两个称为 **field** 的属性:它自己的和来自 **Super** 的。然而,在引用 **Sub** 的 **field** 时,默认的 **field** 属性并不是 **Super** 版本的 **field** 属性。为了获取 **Super** 的 **field** 属性,需要显式地指明 **super.field**。\n", + "\n", + "尽管这看起来是个令人困惑的问题,实际上基本不会发生。首先,通常会将所有的属性都指明为 **private**,因此不能直接访问它们,只能通过方法来访问。此外,你可能也不会给基类属性和派生类属性起相同的名字,这样做会令人困惑。\n", + "\n", + "如果一个方法是静态(**static**)的,它的行为就不具有多态性:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/StaticPolymorphism.java\n", + "// static methods are not polymorphic\n", + "class StaticSuper {\n", + " public static String staticGet() {\n", + " return \"Base staticGet()\";\n", + " }\n", + " \n", + " public String dynamicGet() {\n", + " return \"Base dynamicGet()\";\n", + " }\n", + "}\n", + "\n", + "class StaticSub extends StaticSuper {\n", + " public static String staticGet() {\n", + " return \"Derived staticGet()\";\n", + " }\n", + " @Override\n", + " public String dynamicGet() {\n", + " return \"Derived dynamicGet()\";\n", + " }\n", + "}\n", + "\n", + "public class StaticPolymorphism {\n", + " public static void main(String[] args) {\n", + " StaticSuper sup = new StaticSub(); // Upcast\n", + " System.out.println(StaticSuper.staticGet());\n", + " System.out.println(sup.dynamicGet());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Base staticGet()\n", + "Derived dynamicGet()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "静态的方法只与类关联,与单个的对象无关。\n", + "\n", + "\n", + "\n", + "## 构造器和多态\n", + "\n", + "通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。这个理解可以帮助你避免一些不愉快的困扰。\n", + "\n", + "### 构造器调用顺序\n", + "\n", + "在“初始化和清理”和“复用”两章中已经简单地介绍过构造器的调用顺序,但那时还没有介绍多态。\n", + "\n", + "在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。这么做是有意义的,因为构造器有着特殊的任务:检查对象是否被正确地构造。由于属性通常声明为 **private**,你必须假定派生类只能访问自己的成员而不能访问基类的成员。只有基类的构造器拥有恰当的知识和权限来初始化自身的元素。因此,必须得调用所有构造器;否则就不能构造完整的对象。这就是为什么编译器会强制调用每个派生类中的构造器的原因。如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)。\n", + "\n", + "下面的例子展示了组合、继承和多态在构建顺序上的作用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/Sandwich.java\n", + "// Order of constructor calls\n", + "// {java polymorphism.Sandwich}\n", + "package polymorphism;\n", + "\n", + "class Meal {\n", + " Meal() {\n", + " System.out.println(\"Meal()\");\n", + " }\n", + "}\n", + "\n", + "class Bread {\n", + " Bread() {\n", + " System.out.println(\"Bread()\");\n", + " }\n", + "}\n", + "\n", + "class Cheese {\n", + " Cheese() {\n", + " System.out.println(\"Cheese()\");\n", + " }\n", + "}\n", + "\n", + "class Lettuce {\n", + " Lettuce() {\n", + " System.out.println(\"Lettuce()\");\n", + " }\n", + "}\n", + "\n", + "class Lunch extends Meal {\n", + " Lunch() {\n", + " System.out.println(\"Lunch()\");\n", + " }\n", + "}\n", + "\n", + "class PortableLunch extends Lunch {\n", + " PortableLunch() {\n", + " System.out.println(\"PortableLunch()\");\n", + " }\n", + "}\n", + "\n", + "public class Sandwich extends PortableLunch {\n", + " private Bread b = new Bread();\n", + " private Cheese c = new Cheese();\n", + " private Lettuce l = new Lettuce();\n", + " \n", + " public Sandwich() {\n", + " System.out.println(\"Sandwich()\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " new Sandwich();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Meal()\n", + "Lunch()\n", + "PortableLunch()\n", + "Bread()\n", + "Cheese()\n", + "Lettuce()\n", + "Sandwich()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个例子用其他类创建了一个复杂的类。每个类都在构造器中声明自己。重要的类是 **Sandwich**,它反映了三层继承(如果算上 **Object** 的话,就是四层),包含了三个成员对象。\n", + "\n", + "从创建 **Sandwich** 对象的输出中可以看出对象的构造器调用顺序如下:\n", + "\n", + "1. 基类构造器被调用。这个步骤被递归地重复,这样一来类层次的顶级父类会被最先构造,然后是它的派生类,以此类推,直到最底层的派生类。\n", + "2. 按声明顺序初始化成员。\n", + "3. 调用派生类构造器的方法体。\n", + "\n", + "构造器的调用顺序很重要。当使用继承时,就已经知道了基类的一切,并可以访问基类中任意 **public** 和 **protected** 的成员。这意味着在派生类中可以假定所有的基类成员都是有效的。在一个标准方法中,构造动作已经发生过,对象其他部分的所有成员都已经创建好。\n", + "\n", + "在构造器中必须确保所有的成员都已经构建完。唯一能保证这点的方法就是首先调用基类的构造器。接着,在派生类的构造器中,所有你可以访问的基类成员都已经初始化。另一个在构造器中能知道所有成员都是有效的理由是:无论何时有可能的话,你应该在所有成员对象(通过组合将对象置于类中)定义处初始化它们(例如,例子中的 **b**、**c** 和 **l**)。如果遵循这条实践,就可以帮助确保所有的基类成员和当前对象的成员对象都已经初始化。\n", + "\n", + "不幸的是,这不能处理所有情况,在下一节会看到。\n", + "\n", + "### 继承和清理\n", + "\n", + "在使用组合和继承创建新类时,大部分时候你无需关心清理。子对象通常会留给垃圾收集器处理。如果你存在清理问题,那么必须用心地为新类创建一个 `dispose()` 方法(这里用的是我选择的名称,你可以使用更好的名称)。由于继承,如果有其他特殊的清理工作的话,就必须在派生类中重写 `dispose()` 方法。当重写 `dispose()` 方法时,记得调用基类的 `dispose()` 方法,否则基类的清理工作不会发生:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/Frog.java\n", + "// Cleanup and inheritance\n", + "// {java polymorphism.Frog}\n", + "package polymorphism;\n", + "\n", + "class Characteristic {\n", + " private String s;\n", + " \n", + " Characteristic(String s) {\n", + " this.s = s;\n", + " System.out.println(\"Creating Characteristic \" + s);\n", + " }\n", + " \n", + " protected void dispose() {\n", + " System.out.println(\"disposing Characteristic \" + s);\n", + " }\n", + "}\n", + "\n", + "class Description {\n", + " private String s;\n", + " \n", + " Description(String s) {\n", + " this.s = s;\n", + " System.out.println(\"Creating Description \" + s);\n", + " }\n", + " \n", + " protected void dispose() {\n", + " System.out.println(\"disposing Description \" + s);\n", + " }\n", + "}\n", + "\n", + "class LivingCreature {\n", + " private Characteristic p = new Characteristic(\"is alive\");\n", + " private Description t = new Description(\"Basic Living Creature\");\n", + " \n", + " LivingCreature() {\n", + " System.out.println(\"LivingCreature()\");\n", + " }\n", + " \n", + " protected void dispose() {\n", + " System.out.println(\"LivingCreature dispose\");\n", + " t.dispose();\n", + " p.dispose();\n", + " }\n", + "}\n", + "\n", + "class Animal extends LivingCreature {\n", + " private Characteristic p = new Characteristic(\"has heart\");\n", + " private Description t = new Description(\"Animal not Vegetable\");\n", + " \n", + " Animal() {\n", + " System.out.println(\"Animal()\");\n", + " }\n", + " \n", + " @Override\n", + " protected void dispose() {\n", + " System.out.println(\"Animal dispose\");\n", + " t.dispose();\n", + " p.dispose();\n", + " super.dispose();\n", + " }\n", + "}\n", + "\n", + "class Amphibian extends Animal {\n", + " private Characteristic p = new Characteristic(\"can live in water\");\n", + " private Description t = new Description(\"Both water and land\");\n", + " \n", + " Amphibian() {\n", + " System.out.println(\"Amphibian()\");\n", + " }\n", + " \n", + " @Override\n", + " protected void dispose() {\n", + " System.out.println(\"Amphibian dispose\");\n", + " t.dispose();\n", + " p.dispose();\n", + " super.dispose();\n", + " }\n", + "}\n", + "\n", + "public class Frog extends Amphibian {\n", + " private Characteristic p = new Characteristic(\"Croaks\");\n", + " private Description t = new Description(\"Eats Bugs\");\n", + " \n", + " public Frog() {\n", + " System.out.println(\"Frog()\");\n", + " }\n", + " \n", + " @Override\n", + " protected void dispose() {\n", + " System.out.println(\"Frog dispose\");\n", + " t.dispose();\n", + " p.dispose();\n", + " super.dispose();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Frog frog = new Frog();\n", + " System.out.println(\"Bye!\");\n", + " frog.dispose();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Creating Characteristic is alive\n", + "Creating Description Basic Living Creature\n", + "LivingCreature()\n", + "Creating Characteristiv has heart\n", + "Creating Description Animal not Vegetable\n", + "Animal()\n", + "Creating Characteristic can live in water\n", + "Creating Description Both water and land\n", + "Amphibian()\n", + "Creating Characteristic Croaks\n", + "Creating Description Eats Bugs\n", + "Frog()\n", + "Bye!\n", + "Frog dispose\n", + "disposing Description Eats Bugs\n", + "disposing Characteristic Croaks\n", + "Amphibian dispose\n", + "disposing Description Both wanter and land\n", + "disposing Characteristic can live in water\n", + "Animal dispose\n", + "disposing Description Animal not Vegetable\n", + "disposing Characteristic has heart\n", + "LivingCreature dispose\n", + "disposing Description Basic Living Creature\n", + "disposing Characteristic is alive" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "层级结构中的每个类都有 **Characteristic** 和 **Description** 两个类型的成员对象,它们必须得被销毁。销毁的顺序应该与初始化的顺序相反,以防一个对象依赖另一个对象。对于属性来说,就意味着与声明的顺序相反(因为属性是按照声明顺序初始化的)。对于基类(遵循 C++ 析构函数的形式),首先进行派生类的清理工作,然后才是基类的清理。这是因为派生类的清理可能调用基类的一些方法,所以基类组件这时得存活,不能过早地被销毁。输出显示了,**Frog** 对象的所有部分都是按照创建的逆序销毁的。\n", + "\n", + "尽管通常不必进行清理工作,但万一需要时,就得谨慎小心地执行。\n", + "\n", + "**Frog** 对象拥有自己的成员对象,它创建了这些成员对象,并且知道它们能存活多久,所以它知道何时调用 `dispose()` 方法。然而,一旦某个成员对象被其它一个或多个对象共享时,问题就变得复杂了,不能只是简单地调用 `dispose()`。这里,也许就必须使用*引用计数*来跟踪仍然访问着共享对象的对象数量,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/ReferenceCounting.java\n", + "// Cleaning up shared member objects\n", + "class Shared {\n", + " private int refcount = 0;\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + " \n", + " Shared() {\n", + " System.out.println(\"Creating \" + this);\n", + " }\n", + " \n", + " public void addRef() {\n", + " refcount++;\n", + " }\n", + " \n", + " protected void dispose() {\n", + " if (--refcount == 0) {\n", + " System.out.println(\"Disposing \" + this);\n", + " }\n", + " }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"Shared \" + id;\n", + " }\n", + "}\n", + "\n", + "class Composing {\n", + " private Shared shared;\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + " \n", + " Composing(Shared shared) {\n", + " System.out.println(\"Creating \" + this);\n", + " this.shared = shared;\n", + " this.shared.addRef();\n", + " }\n", + " \n", + " protected void dispose() {\n", + " System.out.println(\"disposing \" + this);\n", + " shared.dispose();\n", + " }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"Composing \" + id;\n", + " }\n", + "}\n", + "\n", + "public class ReferenceCounting {\n", + " public static void main(String[] args) {\n", + " Shared shared = new Shared();\n", + " Composing[] composing = {\n", + " new Composing(shared),\n", + " new Composing(shared),\n", + " new Composing(shared),\n", + " new Composing(shared),\n", + " new Composing(shared),\n", + " };\n", + " for (Composing c: composing) {\n", + " c.dispose();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Creating Shared 0\n", + "Creating Composing 0\n", + "Creating Composing 1\n", + "Creating Composing 2\n", + "Creating Composing 3\n", + "Creating Composing 4\n", + "disposing Composing 0\n", + "disposing Composing 1\n", + "disposing Composing 2\n", + "disposing Composing 3\n", + "disposing Composing 4\n", + "Disposing Shared 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**static long counter** 跟踪所创建的 **Shared** 实例数量,还提供了 **id** 的值。**counter** 的类型是 **long** 而不是 **int**,以防溢出(这只是个良好实践,对于本书的所有示例,**counter** 不会溢出)。**id** 是 **final** 的,因为它的值在初始化时确定后不应该变化。\n", + "\n", + "在将一个 **shared** 对象附着在类上时,必须记住调用 `addRef()`,而 `dispose()` 方法会跟踪引用数,以确定在何时真正地执行清理工作。使用这种技巧需要加倍细心,但是如果正在共享需要被清理的对象,就没有太多选择了。\n", + "\n", + "### 构造器内部多态方法的行为\n", + "\n", + "构造器调用的层次结构带来了一个困境。如果在构造器中调用了正在构造的对象的动态绑定方法,会发生什么呢?\n", + "\n", + "在普通的方法中,动态绑定的调用是在运行时解析的,因为对象不知道它属于方法所在的类还是类的派生类。\n", + "\n", + "如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用,这使得一些 bug 很隐蔽,难以发现。\n", + "\n", + "从概念上讲,构造器的工作就是创建对象(这并非是平常的工作)。在构造器内部,整个对象可能只是部分形成——只知道基类对象已经初始化。如果构造器只是构造对象过程中的一个步骤,且构造的对象所属的类是从构造器所属的类派生出的,那么派生部分在当前构造器被调用时还没有初始化。然而,一个动态绑定的方法调用向外深入到继承层次结构中,它可以调用派生类的方法。如果你在构造器中这么做,就可能调用一个方法,该方法操纵的成员可能还没有初始化——这肯定会带来灾难。\n", + "\n", + "下面例子展示了这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/PolyConstructors.java\n", + "// Constructors and polymorphism\n", + "// don't produce what you might expect\n", + "class Glyph {\n", + " void draw() {\n", + " System.out.println(\"Glyph.draw()\");\n", + " }\n", + "\n", + " Glyph() {\n", + " System.out.println(\"Glyph() before draw()\");\n", + " draw();\n", + " System.out.println(\"Glyph() after draw()\");\n", + " }\n", + "}\n", + "\n", + "class RoundGlyph extends Glyph {\n", + " private int radius = 1;\n", + "\n", + " RoundGlyph(int r) {\n", + " radius = r;\n", + " System.out.println(\"RoundGlyph.RoundGlyph(), radius = \" + radius);\n", + " }\n", + "\n", + " @Override\n", + " void draw() {\n", + " System.out.println(\"RoundGlyph.draw(), radius = \" + radius);\n", + " }\n", + "}\n", + "\n", + "public class PolyConstructors {\n", + " public static void main(String[] args) {\n", + " new RoundGlyph(5);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Glyph() before draw()\n", + "RoundGlyph.draw(), radius = 0\n", + "Glyph() after draw()\n", + "RoundGlyph.RoundGlyph(), radius = 5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Glyph** 的 `draw()` 被设计为可重写,在 **RoundGlyph** 这个方法被重写。但是 **Glyph** 的构造器里调用了这个方法,结果调用了 **RoundGlyph** 的 `draw()` 方法,这看起来正是我们的目的。输出结果表明,当 **Glyph** 构造器调用了 `draw()` 时,**radius** 的值不是默认初始值 1 而是 0。这可能会导致在屏幕上只画了一个点或干脆什么都不画,于是我们只能干瞪眼,试图找到程序不工作的原因。\n", + "\n", + "前一小节描述的初始化顺序并不十分完整,而这正是解决谜团的关键所在。初始化的实际过程是:\n", + "\n", + "1. 在所有事发生前,分配给对象的存储空间会被初始化为二进制 0。\n", + "2. 如前所述调用基类构造器。此时调用重写后的 `draw()` 方法(是的,在调用 **RoundGraph** 构造器之前调用),由步骤 1 可知,**radius** 的值为 0。\n", + "3. 按声明顺序初始化成员。\n", + "4. 最终调用派生类的构造器。\n", + "\n", + "这么做有个优点:所有事物至少初始化为 0(或某些特殊数据类型与 0 等价的值),而不是仅仅留作垃圾。这包括了通过组合嵌入类中的对象引用,被赋予 **null**。如果忘记初始化该引用,就会在运行时出现异常。观察输出结果,就会发现所有事物都是 0。\n", + "\n", + "另一方面,应该震惊于输出结果。逻辑方面我们已经做得非常完美,然而行为仍不可思议的错了,编译器也没有报错(C++ 在这种情况下会产生更加合理的行为)。像这样的 bug 很容易被忽略,需要花很长时间才能发现。\n", + "\n", + "因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 **final** 方法(这也适用于可被看作是 **final** 的 **private** 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。\n", + "\n", + "\n", + "\n", + "## 协变返回类型\n", + "\n", + "Java 5 中引入了协变返回类型,这表示派生类的被重写方法可以返回基类方法返回类型的派生类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/CovariantReturn.java\n", + "class Grain {\n", + " @Override\n", + " public String toString() {\n", + " return \"Grain\";\n", + " }\n", + "}\n", + "\n", + "class Wheat extends Grain {\n", + " @Override\n", + " public String toString() {\n", + " return \"Wheat\";\n", + " }\n", + "}\n", + "\n", + "class Mill {\n", + " Grain process() {\n", + " return new Grain();\n", + " }\n", + "}\n", + "\n", + "class WheatMill extends Mill {\n", + " @Override\n", + " Wheat process() {\n", + " return new Wheat();\n", + " }\n", + "}\n", + "\n", + "public class CovariantReturn {\n", + " public static void main(String[] args) {\n", + " Mill m = new Mill();\n", + " Grain g = m.process();\n", + " System.out.println(g);\n", + " m = new WheatMill();\n", + " g = m.process();\n", + " System.out.println(g);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Grain\n", + "Wheat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "关键区别在于 Java 5 之前的版本强制要求被重写的 `process()` 方法必须返回 **Grain** 而不是 **Wheat**,即使 **Wheat** 派生自 **Grain**,因而也应该是一种合法的返回类型。协变返回类型允许返回更具体的 **Wheat** 类型。\n", + "\n", + "\n", + "\n", + "## 使用继承设计\n", + "\n", + "学习过多态之后,一切看似都可以被继承,因为多态是如此巧妙的工具。这会给设计带来负担。事实上,如果利用已有类创建新类首先选择继承的话,事情会变得莫名的复杂。\n", + "\n", + "更好的方法是首先选择组合,特别是不知道该使用哪种方法时。组合不会强制设计是继承层次结构,而且组合更加灵活,因为可以动态地选择类型(因而选择相应的行为),而继承要求必须在编译时知道确切类型。下面例子说明了这点:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/Transmogrify.java\n", + "// Dynamically changing the behavior of an object\n", + "// via composition (the \"State\" design pattern)\n", + "class Actor {\n", + " public void act() {}\n", + "}\n", + "\n", + "class HappyActor extends Actor {\n", + " @Override\n", + " public void act() {\n", + " System.out.println(\"HappyActor\");\n", + " }\n", + "}\n", + "\n", + "class SadActor extends Actor {\n", + " @Override\n", + " public void act() {\n", + " System.out.println(\"SadActor\");\n", + " }\n", + "}\n", + "\n", + "class Stage {\n", + " private Actor actor = new HappyActor();\n", + " \n", + " public void change() {\n", + " actor = new SadActor();\n", + " }\n", + " \n", + " public void performPlay() {\n", + " actor.act();\n", + " }\n", + "}\n", + "\n", + "public class Transmogrify {\n", + " public static void main(String[] args) {\n", + " Stage stage = new Stage();\n", + " stage.performPlay();\n", + " stage.change();\n", + " stage.performPlay();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "HappyActor\n", + "SadActor" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Stage** 对象中包含了 **Actor** 引用,该引用被初始化为指向一个 **HappyActor** 对象,这意味着 `performPlay()` 会产生一个特殊行为。但是既然引用可以在运行时与其他不同的对象绑定,那么它就可以被替换成对 **SadActor** 的引用,`performPlay()` 的行为随之改变。这样你就获得了运行时的动态灵活性(这被称为状态模式)。与之相反,我们不能在运行时决定继承不同的对象,那在编译时就完全确定下来了。\n", + "\n", + "有一条通用准则:使用继承表达行为的差异,使用属性表达状态的变化。在上个例子中,两者都用到了。通过继承得到的两个不同类在 `act()` 方法中表达了不同的行为,**Stage** 通过组合使自己的状态发生变化。这里状态的改变产生了行为的改变。\n", + "\n", + "### 替代 vs 扩展\n", + "\n", + "采用“纯粹”的方式创建继承层次结构看上去是最清晰的方法。即只有基类的方法才能在派生类中被重写,就像下图这样:\n", + "\n", + "![类图](../images/1562406479787.png)\n", + "\n", + "这被称作纯粹的“is - a\"关系,因为类的接口已经确定了它是什么。继承可以确保任何派生类都拥有基类的接口,绝对不会少。如果按图上这么做,派生类将只拥有基类的接口。\n", + "\n", + "纯粹的替代意味着派生类可以完美地替代基类,当使用它们时,完全不需要知道这些子类的信息。也就是说,基类可以接收任意发送给派生类的消息,因为它们具有完全相同的接口。只需将派生类向上转型,不要关注对象的具体类型。所有一切都可以通过多态处理。\n", + "\n", + "按这种方式思考,似乎只有纯粹的“is - a”关系才是唯一明智的做法,其他任何设计只会导致混乱且注定失败。这其实也是个陷阱。一旦按这种方式开始思考,就会转而发现继承扩展接口(遗憾的是,extends 关键字似乎怂恿我们这么做)才是解决特定问题的完美方案。这可以称为“is - like - a” 关系,因为派生类就像是基类——它有着相同的基本接口,但还具有需要额外方法实现的其他特性:\n", + "\n", + "![](../images/1562409366637.png)\n", + "\n", + "虽然这是一种有用且明智的方法(依赖具体情况),但是也存在缺点。派生类中接口的扩展部分在基类中不存在(不能通过基类访问到这些扩展接口),因此一旦向上转型,就不能通过基类调用这些新方法:\n", + "\n", + "![](../images/1562409926765.png)\n", + "\n", + "如果不向上转型,就不会遇到这个问题。但是通常情况下,我们需要重新查明对象的确切类型,从而能够访问该类型中的扩展方法。下一节说明如何做到这点。\n", + "\n", + "### 向下转型与运行时类型信息\n", + "\n", + "由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,那么为了重新获取类型信息,就需要在继承层次中向下移动,使用*向下转型*。\n", + "\n", + "向上转型永远是安全的,因为基类不会具有比派生类更多的接口。因此,每条发送给基类接口的消息都能被接收。但是对于向下转型,你无法知道一个形状是圆,它有可能是三角形、正方形或其他一些类型。\n", + "\n", + "为了解决这个问题,必须得有某种方法确保向下转型是正确的,防止意外转型到一个错误类型,进而发送对象无法接收的消息。这么做是不安全的。\n", + "\n", + "在某些语言中(如 C++),必须执行一个特殊的操作来获得安全的向下转型,但是在 Java 中,每次转型都会被检查!所以即使只是进行一次普通的加括号形式的类型转换,在运行时这个转换仍会被检查,以确保它的确是希望的那种类型。如果不是,就会得到 ClassCastException (类转型异常)。这种在运行时检查类型的行为称作运行时类型信息。下面例子展示了 RTTI 的行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// polymorphism/RTTI.java\n", + "// Downcasting & Runtime type information (RTTI)\n", + "// {ThrowsException}\n", + "class Useful {\n", + " public void f() {}\n", + " public void g() {}\n", + "}\n", + "\n", + "class MoreUseful extends Useful {\n", + " @Override\n", + " public void f() {}\n", + " @Override\n", + " public void g() {}\n", + " public void u() {}\n", + " public void v() {}\n", + " public void w() {}\n", + "}\n", + "\n", + "public class RTTI {\n", + " public static void main(String[] args) {\n", + " Useful[] x = {\n", + " new Useful(),\n", + " new MoreUseful()\n", + " };\n", + " x[0].f();\n", + " x[1].g();\n", + " // Compile time: method not found in Useful:\n", + " //- x[1].u();\n", + " ((MoreUseful) x[1]).u(); // Downcast/RTTI\n", + " ((MoreUseful) x[0]).u(); // Exception thrown\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Exception in thread \"main\"\n", + "java.lang.ClassCastException: Useful cannot be cast to\n", + "MoreUseful\n", + "at RTTI.main" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正如前面类图所示,**MoreUseful** 扩展了 **Useful** 的接口。而 **MoreUseful** 也继承了 **Useful**,所以它可以向上转型为 **Useful**。在 `main()` 方法中可以看到这种情况的发生。因为两个对象都是 **Useful** 类型,所以对它们都可以调用 `f()` 和 `g()` 方法。如果试图调用 `u()` 方法(只存在于 **MoreUseful** 中),就会得到编译时错误信息。\n", + "\n", + "为了访问 **MoreUseful** 对象的扩展接口,就得尝试向下转型。如果转型为正确的类型,就转型成功。否则,就会得到 ClassCastException 异常。你不必为这个异常编写任何特殊代码,因为它指出了程序员在程序的任何地方都可能犯的错误。**{ThrowsException}** 注释标签告知本书的构建系统:在运行程序时,预期抛出一个异常。\n", + "\n", + "RTTI 不仅仅包括简单的转型。例如,它还提供了一种方法,使你可以在试图向下转型前检查所要处理的类型。“类型信息”一章中会详细阐述运行时类型信息的方方面面。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "多态意味着“不同的形式”。在面向对象编程中,我们持有从基类继承而来的相同接口和使用该接口的不同形式:不同版本的动态绑定方法。\n", + "\n", + "在本章中,你可以看到,如果不使用数据抽象和继承,就不可能理解甚至创建多态的例子。多态是一种不能单独看待的特性(比如像 **switch** 语句那样),它只能作为类关系全景中的一部分,与其他特性协同工作。\n", + "\n", + "为了在程序中有效地使用多态乃至面向对象的技术,就必须扩展自己的编程视野,不能只看到单一类中的成员和消息,而要看到类之间的共同特性和它们之间的关系。尽管这需要很大的努力,但是这么做是值得的。它能带来更快的程序开发、更好的代码组织、扩展性更好的程序和更易维护的代码。\n", + "\n", + "但是记住,多态可能被滥用。仔细分析代码以确保多态确实能带来好处。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/10-Interfaces.ipynb b/jupyter/10-Interfaces.ipynb new file mode 100644 index 00000000..30453dc4 --- /dev/null +++ b/jupyter/10-Interfaces.ipynb @@ -0,0 +1,2640 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十章 接口\n", + "\n", + "接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。\n", + "\n", + "这种机制在编程语言中不常见,例如 C++ 只对这种概念有间接的支持。而在 Java 中存在这些关键字,说明这些思想很重要,Java 为它们提供了直接支持。\n", + "\n", + "首先,我们将学习抽象类,一种介于普通类和接口之间的折中手段。尽管你的第一想法是创建接口,但是对于构建具有属性和未实现方法的类来说,抽象类也是重要且必要的工具。你不可能总是使用纯粹的接口。\n", + "\n", + "\n", + "\n", + "## 抽象类和方法\n", + "\n", + "在上一章的乐器例子中,基类 **Instrument** 中的方法往往是“哑”方法。如果调用了这些方法,就会出现一些错误。这是因为接口的目的是为它的派生类创建一个通用接口。\n", + "\n", + "在那些例子中,创建这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立了一个基本形式,以此表达所有派生类的共同部分。另一种说法把 **Instrument** 称为抽象基类,或简称抽象类。\n", + "\n", + "对于像 **Instrument** 那样的抽象类来说,它的对象几乎总是没有意义的。创建一个抽象类是为了通过通用接口操纵一系列类。因此,**Instrument** 只是表示接口,不是具体实现,所以创建一个 **Instrument** 的对象毫无意义,我们可能希望阻止用户这么做。通过让 **Instrument** 所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。\n", + "\n", + "Java 提供了一个叫做*抽象方法*的机制,这个方法是不完整的:它只有声明没有方法体。下面是抽象方法的声明语法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "abstract void f();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "包含抽象方法的类叫做*抽象类*。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interface/Basic.java\n", + "abstract class Basic {\n", + " abstract void unimplemented();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果一个抽象类是不完整的,当试图创建这个类的对象时,Java 会怎么做呢?它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。这样保证了抽象类的纯粹性,我们不用担心误用它。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AttemptToUseBasic.java\n", + "// {WillNotCompile}\n", + "public class AttemptToUseBasic {\n", + " Basic b = new Basic();\n", + " // error: Basic is abstract; cannot be instantiated\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上 **abstract** 关键字。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Basic2.java\n", + "abstract class Basic2 extends Basic {\n", + " int f() {\n", + " return 111;\n", + " }\n", + " \n", + " abstract void g() {\n", + " // unimplemented() still not implemented\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以将一个不包含任何抽象方法的类指明为 **abstract**,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AbstractWithoutAbstracts.java\n", + "abstract class Basic3 {\n", + " int f() {\n", + " return 111;\n", + " }\n", + " \n", + " // No abstract methods\n", + "}\n", + "\n", + "public class AbstractWithoutAbstracts {\n", + " // Basic b3 = new Basic3();\n", + " // error: Basic 3 is abstract; cannot be instantiated\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Instantiable.java\n", + "abstract class Uninstantiable {\n", + " abstract void f();\n", + " abstract int g();\n", + "}\n", + "\n", + "public class Instantiable extends Uninstantiable {\n", + " @Override\n", + " void f() {\n", + " System.out.println(\"f()\");\n", + " }\n", + " \n", + " @Override\n", + " int g() {\n", + " return 22;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Uninstantiable ui = new Instantiable();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "留意 `@Override` 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 `@Override` 是多余的。但是,`@Override` 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 `@Override`,即使在没有这个注解,编译器告诉我错误的时候。 \n", + "\n", + "记住,事实上的访问权限是“friendly”。你很快会看到接口自动将其方法指明为 **public**。事实上,接口只允许 **public** 方法,如果不加访问修饰符的话,接口的方法不是 **friendly** 而是 **public**。然而,抽象类允许每件事:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AbstractAccess.java\n", + "abstract class AbstractAccess {\n", + " private void m1() {}\n", + " \n", + " // private abstract void m1a(); // illegal\n", + " \n", + " protected void m2() {}\n", + " \n", + " protected abstract void m2a();\n", + " \n", + " void m3() {}\n", + " \n", + " abstract void m3a();\n", + " \n", + " public void m4() {}\n", + " \n", + " public abstract void m4a();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**private abstract** 被禁止了是有意义的,因为你不可能在 **AbstractAccess** 的任何子类中合法地定义它。\n", + "\n", + "上一章的 **Instrument** 类可以很轻易地转换为一个抽象类。只需要部分方法是 **abstract** 即可。将一个类指明为 **abstract** 并不强制类中的所有方法必须都是抽象方法。如下图所示:\n", + "\n", + "![类图](../images/1562653648586.png)\n", + "\n", + "下面是修改成使用抽象类和抽象方法的管弦乐器的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/music4/Music4.java\n", + "// Abstract classes and methods\n", + "// {java interfaces.music4.Music4}\n", + "package interfaces.music4;\n", + "import polymorphism.music.Note;\n", + "\n", + "abstract class Instrument {\n", + " private int i; // Storage allocated for each\n", + " \n", + " public abstract void play(Note n);\n", + " \n", + " public String what() {\n", + " return \"Instrument\";\n", + " }\n", + " \n", + " public abstract void adjust();\n", + "}\n", + "\n", + "class Wind extends Instrument {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Wind.play() \" + n);\n", + " }\n", + " \n", + " @Override\n", + " public String what() {\n", + " return \"Wind\";\n", + " }\n", + " \n", + " @Override\n", + " public void adjust() {\n", + " System.out.println(\"Adjusting Wind\");\n", + " }\n", + "}\n", + "\n", + "class Percussion extends Instrument {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Percussion.play() \" + n);\n", + " }\n", + " \n", + " @Override\n", + " public String what() {\n", + " return \"Percussion\";\n", + " }\n", + " \n", + " @Override\n", + " public void adjust() {\n", + " System.out.println(\"Adjusting Percussion\");\n", + " }\n", + "}\n", + "\n", + "class Stringed extends Instrument {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Stringed.play() \" + n);\n", + " }\n", + " \n", + " @Override\n", + " public String what() {\n", + " return \"Stringed\";\n", + " }\n", + " \n", + " @Override\n", + " public void adjust() {\n", + " System.out.println(\"Adjusting Stringed\");\n", + " }\n", + "}\n", + "\n", + "class Brass extends Wind {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Brass.play() \" + n);\n", + " }\n", + " \n", + " @Override\n", + " public void adjust() {\n", + " System.out.println(\"Adjusting Brass\");\n", + " }\n", + "}\n", + "\n", + "class Woodwind extends Wind {\n", + " @Override\n", + " public void play(Note n) {\n", + " System.out.println(\"Woodwind.play() \" + n);\n", + " }\n", + " \n", + " @Override\n", + " public String what() {\n", + " return \"Woodwind\";\n", + " }\n", + "}\n", + "\n", + "public class Music4 {\n", + " // Doesn't care about type, so new types\n", + " // added to system still work right:\n", + " static void tune(Instrument i) {\n", + " // ...\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " static void tuneAll(Instrument[] e) {\n", + " for (Instrument i: e) {\n", + " tune(i);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " // Upcasting during addition to the array:\n", + " Instrument[] orchestra = {\n", + " new Wind(),\n", + " new Percussion(),\n", + " new Stringed(),\n", + " new Brass(),\n", + " new Woodwind()\n", + " };\n", + " tuneAll(orchestra);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Wind.play() MIDDLE_C\n", + "Percussion.play() MIDDLE_C\n", + "Stringed.play() MIDDLE_C\n", + "Brass.play() MIDDLE_C\n", + "Woodwind.play() MIDDLE_C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除了 **Instrument**,基本没区别。\n", + "\n", + "创建抽象类和抽象方法是有帮助的,因为它们使得类的抽象性很明确,并能告知用户和编译器使用意图。抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法。\n", + "\n", + "\n", + "\n", + "## 接口创建\n", + "\n", + "使用 **interface** 关键字创建接口。在本书中,interface 和 class 一样随处常见,除非特指关键字 **interface**,其他情况下都采用正常字体书写 interface。\n", + "\n", + "描述 Java 8 之前的接口更加容易,因为它们只允许抽象方法。像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/PureInterface.java\n", + "// Interface only looked like this before Java 8\n", + "public interface PureInterface {\n", + " int m1(); \n", + " void m2();\n", + " double m3();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们甚至不用为方法加上 **abstract** 关键字,因为方法在接口中。Java 知道这些方法不能有方法体(仍然可以为方法加上 **abstract** 关键字,但是看起来像是不明白接口,徒增难堪罢了)。\n", + "\n", + "因此,在 Java 8之前我们可以这么说:**interface** 关键字产生一个完全抽象的类,没有提供任何实现。我们只能描述类应该像什么,做什么,但不能描述怎么做,即只能决定方法名、参数列表和返回类型,但是无法确定方法体。接口只提供形式,通常来说没有实现,尽管在某些受限制的情况下可以有实现。\n", + "\n", + "一个接口表示:所有实现了该接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。所以,接口被用来建立类之间的协议。(一些面向对象编程语言中,使用 protocol 关键字完成相同的功能。)\n", + "\n", + "Java 8 中接口稍微有些变化,因为 Java 8 允许接口包含默认方法和静态方法——基于某些重要原因,看到后面你会理解。接口的基本概念仍然没变,介于类型之上、实现之下。接口与抽象类最明显的区别可能就是使用上的惯用方式。接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable,而抽象类通常是类层次结构的一部分或一件事物的类型,如 String 或 ActionHero。\n", + "\n", + "使用关键字 **interface** 而不是 **class** 来创建接口。和类一样,需要在关键字 **interface** 前加上 **public** 关键字(但只是在接口名与文件名相同的情况下),否则接口只有包访问权限,只能在接口相同的包下才能使用它。\n", + "\n", + "接口同样可以包含属性,这些属性被隐式指明为 **static** 和 **final**。\n", + "\n", + "使用 **implements** 关键字使一个类遵循某个特定接口(或一组接口),它表示:接口只是外形,现在我要说明它是如何工作的。除此之外,它看起来像继承。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/ImplementingAnInterface.java\n", + "interface Concept { // Package access\n", + " void idea1();\n", + " void idea2();\n", + "}\n", + "\n", + "class Implementation implements Concept {\n", + " @Override\n", + " public void idea1() {\n", + " System.out.println(\"idea1\");\n", + " }\n", + " \n", + " @Override\n", + " public void idea2() {\n", + " System.out.println(\"idea2\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以选择显式地声明接口中的方法为 **public**,但是即使你不这么做,它们也是 **public** 的。所以当实现一个接口时,来自接口中的方法必须被定义为 **public**。否则,它们只有包访问权限,这样在继承时,它们的可访问权限就被降低了,这是 Java 编译器所不允许的。\n", + "\n", + "### 默认方法\n", + "\n", + "Java 8 为关键字 **default** 增加了一个新的用途(之前只用于 **switch** 语句和注解中)。当在接口中使用它时,任何实现接口却没有定义方法的时候可以使用 **default** 创建的方法体。默认方法比抽象类中的方法受到更多的限制,但是非常有用,我们将在“流式编程”一章中看到。现在让我们看下如何使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AnInterface.java\n", + "interface AnInterface {\n", + " void firstMethod();\n", + " void secondMethod();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以像这样实现接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AnImplementation.java\n", + "public class AnImplementation implements AnInterface {\n", + " public void firstMethod() {\n", + " System.out.println(\"firstMethod\");\n", + " }\n", + " \n", + " public void secondMethod() {\n", + " System.out.println(\"secondMethod\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " AnInterface i = new AnImplementation();\n", + " i.firstMethod();\n", + " i.secondMethod();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "firstMethod\n", + "secondMethod" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们在 **AnInterface** 中增加一个新方法 `newMethod()`,而在 **AnImplementation** 中没有实现它,编译器就会报错:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "AnImplementation.java:3:error: AnImplementation is not abstract and does not override abstract method newMethod() in AnInterface\n", + "public class AnImplementation implements AnInterface {\n", + "^\n", + "1 error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们使用关键字 **default** 为 `newMethod()` 方法提供默认的实现,那么所有与接口有关的代码能正常工作,不受影响,而且这些代码还可以调用新的方法 `newMethod()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/InterfaceWithDefault.java\n", + "interface InterfaceWithDefault {\n", + " void firstMethod();\n", + " void secondMethod();\n", + " \n", + " default void newMethod() {\n", + " System.out.println(\"newMethod\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "关键字 **default** 允许在接口中提供方法实现——在 Java 8 之前被禁止。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Implementation2.java\n", + "public class Implementation2 implements InterfaceWithDefault {\n", + " @Override\n", + " public void firstMethod() {\n", + " System.out.println(\"firstMethod\");\n", + " }\n", + " \n", + " @Override\n", + " public void secondMethod() {\n", + " System.out.println(\"secondMethod\")\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " InterfaceWithDefault i = new Implementation2();\n", + " i.firstMethod();\n", + " i.secondMethod();\n", + " i.newMethod();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "firstMethod\n", + "secondMethod\n", + "newMethod" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管 **Implementation2** 中未定义 `newMethod()`,但是可以使用 `newMethod()` 了。 \n", + "\n", + "增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为*守卫方法*或*虚拟扩展方法*。\n", + "\n", + "### 多继承\n", + "\n", + "多继承意味着一个类可能从多个父类型中继承特征和特性。\n", + "\n", + "Java 在设计之初,C++ 的多继承机制饱受诟病。Java 过去是一种严格要求单继承的语言:只能继承自一个类(或抽象类),但可以实现任意多个接口。在 Java 8 之前,接口没有包袱——它只是方法外貌的描述。\n", + "\n", + "多年后的现在,Java 通过默认方法具有了某种多继承的特性。结合带有默认方法的接口意味着结合了多个基类中的行为。因为接口中仍然不允许存在属性(只有静态属性,不适用),所以属性仍然只会来自单个基类或抽象类,也就是说,不会存在状态的多继承。正如下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/MultipleInheritance.java\n", + "import java.util.*;\n", + "\n", + "interface One {\n", + " default void first() {\n", + " System.out.println(\"first\");\n", + " }\n", + "}\n", + "\n", + "interface Two {\n", + " default void second() {\n", + " System.out.println(\"second\");\n", + " }\n", + "}\n", + "\n", + "interface Three {\n", + " default void third() {\n", + " System.out.println(\"third\");\n", + " }\n", + "}\n", + "\n", + "class MI implements One, Two, Three {}\n", + "\n", + "public class MultipleInheritance {\n", + " public static void main(String[] args) {\n", + " MI mi = new MI();\n", + " mi.first();\n", + " mi.second();\n", + " mi.third();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "first\n", + "second\n", + "third" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在我们做些在 Java 8 之前不可能完成的事:结合多个源的实现。只要基类方法中的方法名和参数列表不同,就能工作得很好,否则会得到编译器错误:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interface/MICollision.java\n", + "import java.util.*;\n", + "\n", + "interface Bob1 {\n", + " default void bob() {\n", + " System.out.println(\"Bob1::bob\");\n", + " }\n", + "}\n", + "\n", + "interface Bob2 {\n", + " default void bob() {\n", + " System.out.println(\"Bob2::bob\");\n", + " }\n", + "}\n", + "\n", + "// class Bob implements Bob1, Bob2 {}\n", + "/* Produces:\n", + "error: class Bob inherits unrelated defaults\n", + "for bob() from types Bob1 and Bob2\n", + "class Bob implements Bob1, Bob2 {}\n", + "^\n", + "1 error\n", + "*/\n", + "\n", + "interface Sam1 {\n", + " default void sam() {\n", + " System.out.println(\"Sam1::sam\");\n", + " }\n", + "}\n", + "\n", + "interface Sam2 {\n", + " default void sam(int i) {\n", + " System.out.println(i * 2);\n", + " }\n", + "}\n", + "\n", + "// This works because the argument lists are distinct:\n", + "class Sam implements Sam1, Sam2 {}\n", + "\n", + "interface Max1 {\n", + " default void max() {\n", + " System.out.println(\"Max1::max\");\n", + " }\n", + "}\n", + "\n", + "interface Max2 {\n", + " default int max() {\n", + " return 47;\n", + " }\n", + "}\n", + "\n", + "// class Max implements Max1, Max2 {}\n", + "/* Produces:\n", + "error: types Max2 and Max1 are imcompatible;\n", + "both define max(), but with unrelated return types\n", + "class Max implements Max1, Max2 {}\n", + "^\n", + "1 error\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Sam** 类中的两个 `sam()` 方法有相同的方法名但是签名不同——方法签名包括方法名和参数类型,编译器也是用它来区分方法。但是从 **Max** 类可看出,返回类型不是方法签名的一部分,因此不能用来区分方法。为了解决这个问题,需要覆写冲突的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Jim.java\n", + "import java.util.*;\n", + "\n", + "interface Jim1 {\n", + " default void jim() {\n", + " System.out.println(\"Jim1::jim\");\n", + " }\n", + "}\n", + "\n", + "interface Jim2 {\n", + " default void jim() {\n", + " System.out.println(\"Jim2::jim\");\n", + " }\n", + "}\n", + "\n", + "public class Jim implements Jim1, Jim2 {\n", + " @Override\n", + " public void jim() {\n", + " Jim2.super.jim();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " new Jim().jim();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Jim2::jim" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当然,你可以重定义 `jim()` 方法,但是也能像上例中那样使用 **super** 关键字选择基类实现中的一种。\n", + "\n", + "### 接口中的静态方法\n", + "\n", + "Java 8 允许在接口中添加静态方法。这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Operations.java\n", + "package onjava;\n", + "import java.util.*;\n", + "\n", + "public interface Operations {\n", + " void execute();\n", + " \n", + " static void runOps(Operations... ops) {\n", + " for (Operations op: ops) {\n", + " op.execute();\n", + " }\n", + " }\n", + " \n", + " static void show(String msg) {\n", + " System.out.println(msg);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是模版方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interface/Machine.java\n", + "import java.util.*;\n", + "import onjava.Operations;\n", + "\n", + "class Bing implements Operations {\n", + " @Override\n", + " public void execute() {\n", + " Operations.show(\"Bing\");\n", + " }\n", + "}\n", + "\n", + "class Crack implements Operations {\n", + " @Override\n", + " public void execute() {\n", + " Operations.show(\"Crack\");\n", + " }\n", + "}\n", + "\n", + "class Twist implements Operations {\n", + " @Override\n", + " public void execute() {\n", + " Operations.show(\"Twist\");\n", + " }\n", + "}\n", + "\n", + "public class Machine {\n", + " public static void main(String[] args) {\n", + " Operations.runOps(\n", + " \tnew Bing(), new Crack(), new Twist());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Bing\n", + "Crack\n", + "Twist" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里展示了创建 **Operations** 的不同方式:一个外部类(Bing),一个匿名类,一个方法引用和 lambda 表达式——毫无疑问用在这里是最好的解决方法。\n", + "\n", + "这个特性是一项改善,因为它允许把静态方法放在更合适的地方。\n", + "\n", + "### Instrument 作为接口\n", + "\n", + "回顾下乐器的例子,使用接口的话:\n", + "\n", + "![类图](../images/1562737974623.png)\n", + "\n", + "类 **Woodwind** 和 **Brass** 说明一旦实现了某个接口,那么其实现就变成一个普通类,可以按常规方式扩展它。\n", + "\n", + "接口的工作方式使得我们不需要显式声明其中的方法为 **public**,它们自动就是 **public** 的。`play()` 和 `adjust()` 使用 **default** 关键字定义实现。在 Java 8 之前,这些定义要在每个实现中重复实现,显得多余且令人烦恼:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/music5/Music5.java\n", + "// {java interfaces.music5.Music5}\n", + "package interfaces.music5;\n", + "import polymorphism.music.Note;\n", + "\n", + "interface Instrument {\n", + " // Compile-time constant:\n", + " int VALUE = 5; // static & final\n", + " \n", + " default void play(Note n) // Automatically public \n", + " System.out.println(this + \".play() \" + n);\n", + " }\n", + " \n", + " default void adjust() {\n", + " System.out.println(\"Adjusting \" + this);\n", + " }\n", + "}\n", + "\n", + "class Wind implements Instrument {\n", + " @Override\n", + " public String toString() {\n", + " return \"Wind\";\n", + " }\n", + "}\n", + "\n", + "class Percussion implements Instrument {\n", + " @Override\n", + " public String toString() {\n", + " return \"Percussion\";\n", + " }\n", + "}\n", + "\n", + "class Stringed implements Instrument {\n", + " @Override\n", + " public String toString() {\n", + " return \"Stringed\";\n", + " }\n", + "}\n", + "\n", + "class Brass extends Wind {\n", + " @Override\n", + " public String toString() {\n", + " return \"Brass\";\n", + " }\n", + "}\n", + "\n", + "class Woodwind extends Wind {\n", + " @Override\n", + " public String toString() {\n", + " return \"Woodwind\";\n", + " }\n", + "}\n", + "\n", + "public class Music5 {\n", + " // Doesn't care about type, so new types\n", + " // added to the system still work right:\n", + " static void tune(Instrument i) {\n", + " // ...\n", + " i.play(Note.MIDDLE_C);\n", + " }\n", + " \n", + " static void tuneAll(Instrument[] e) {\n", + " for (Instrument i: e) {\n", + " tune(i);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " // Upcasting during addition to the array:\n", + " Instrument[] orchestra = {\n", + " new Wind(),\n", + " new Percussion(),\n", + " new Stringed(),\n", + " new Brass(),\n", + " new Woodwind()\n", + " }\n", + " tuneAll(orchestra);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Wind.play() MIDDLE_C\n", + "Percussion.play() MIDDLE_C\n", + "Stringed.play() MIDDLE_C\n", + "Brass.play() MIDDLE_C\n", + "Woodwind.play() MIDDLE_C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个版本的例子的另一个变化是:`what()` 被修改为 `toString()` 方法,因为 `toString()` 实现的正是 `what()` 方法要实现的逻辑。因为 `toString()` 是根基类 **Object** 的方法,所以它不需要出现在接口中。\n", + "\n", + "注意到,无论是将其向上转型为称作 **Instrument** 的普通类,或称作 **Instrument** 的抽象类,还是叫作 **Instrument** 的接口,其行为都是相同的。事实上,从 `tune()` 方法上看不出来 **Instrument** 到底是一个普通类、抽象类,还是一个接口。\n", + "\n", + "\n", + "\n", + "## 抽象类和接口\n", + "\n", + "尤其是在 Java 8 引入 **default** 方法之后,选择用抽象类还是用接口变得更加令人困惑。下表做了明确的区分:\n", + "\n", + "| 特性 | 接口 | 抽象类 |\n", + "| :------------------: | :--------------------------------------------------------: | :--------------------------------------: |\n", + "| 组合 | 新类可以组合多个接口 | 只能继承单一抽象类 |\n", + "| 状态 | 不能包含属性(除了静态属性,不支持对象状态) | 可以包含属性,非抽象方法可能引用这些属性 |\n", + "| 默认方法 和 抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 |\n", + "| 构造器 | 没有构造器 | 可以有构造器 |\n", + "| 可见性 | 隐式 **public** | 可以是 **protected** 或友元 |\n", + "\n", + "抽象类仍然是一个类,在创建新类时只能继承它一个。而创建类的过程中可以实现多个接口。\n", + "\n", + "有一条实际经验:尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。\n", + "\n", + "\n", + "\n", + "## 完全解耦\n", + "\n", + "当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类。如果想把方法应用于那个继承层级结构之外的类,就会触霉头。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。\n", + "\n", + "例如有一个类 **Process** 有两个方法 `name()` 和 `process()`。`process()` 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 **Processor**。下例中,**Processor** 的各个子类修改 String 对象(注意,返回类型可能是协变类型而非参数类型):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Applicator.java\n", + "import java.util.*;\n", + "\n", + "class Processor {\n", + " public String name() {\n", + " return getClass().getSimpleName();\n", + " }\n", + " \n", + " public Object process(Object input) {\n", + " return input;\n", + " }\n", + "}\n", + "\n", + "class Upcase extends Processor {\n", + " // 返回协变类型\n", + " @Override \n", + " public String process(Object input) {\n", + " return ((String) input).toUpperCase();\n", + " }\n", + "}\n", + "\n", + "class Downcase extends Processor {\n", + " @Override\n", + " public String process(Object input) {\n", + " return ((String) input).toLowerCase();\n", + " }\n", + "}\n", + "\n", + "class Splitter extends Processor {\n", + " @Override\n", + " public String process(Object input) {\n", + " // split() divides a String into pieces:\n", + " return Arrays.toString(((String) input).split(\" \"));\n", + " }\n", + "}\n", + "\n", + "public class Applicator {\n", + " public static void apply(Processor p, Object s) {\n", + " System.out.println(\"Using Processor \" + p.name());\n", + " System.out.println(p.process(s));\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " String s = \"We are such stuff as dreams are made on\";\n", + " apply(new Upcase(), s);\n", + " apply(new Downcase(), s);\n", + " apply(new Splitter(), s);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Using Processor Upcase\n", + "WE ARE SUCH STUFF AS DREAMS ARE MADE ON\n", + "Using Processor Downcase\n", + "we are such stuff as dreams are made on\n", + "Using Processor Splitter\n", + "[We, are, such, stuff, as, dreams, are, made, on]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Applicator** 的 `apply()` 方法可以接受任何类型的 **Processor**,并将其应用到一个 **Object** 对象上输出结果。像本例中这样,创建一个能根据传入的参数类型从而具备不同行为的方法称为*策略*设计模式。方法包含算法中不变的部分,策略包含变化的部分。策略就是传入的对象,它包含要执行的代码。在这里,**Processor** 对象是策略,`main()` 方法展示了三种不同的应用于 **String s** 上的策略。\n", + "\n", + "`split()` 是 **String** 类中的方法,它接受 **String** 类型的对象并以传入的参数作为分割界限,返回一个数组 **String[]**。在这里用它是为了更快地创建 **String** 数组。\n", + "\n", + "假设现在发现了一组电子滤波器,它们看起来好像能使用 **Applicator** 的 `apply()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/filters/Waveform.java\n", + "package interfaces.filters;\n", + "\n", + "public class Waveform {\n", + " private static long counter;\n", + " private final long id = counter++;\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"Waveform \" + id;\n", + " }\n", + "}\n", + "\n", + "// interfaces/filters/Filter.java\n", + "package interfaces.filters;\n", + "\n", + "public class Filter {\n", + " public String name() {\n", + " return getClass().getSimpleName();\n", + " }\n", + " \n", + " public Waveform process(Waveform input) {\n", + " return input;\n", + " }\n", + "}\n", + "\n", + "// interfaces/filters/LowPass.java\n", + "package interfaces.filters;\n", + "\n", + "public class LowPass extends Filter {\n", + " double cutoff;\n", + " \n", + " public LowPass(double cutoff) {\n", + " this.cutoff = cutoff;\n", + " }\n", + " \n", + " @Override\n", + " public Waveform process(Waveform input) {\n", + " return input; // Dummy processing 哑处理\n", + " }\n", + "}\n", + "\n", + "// interfaces/filters/HighPass.java\n", + "package interfaces.filters;\n", + "\n", + "public class HighPass extends Filter {\n", + " double cutoff;\n", + " \n", + " public HighPass(double cutoff) {\n", + " this.cutoff = cutoff;\n", + " }\n", + " \n", + " @Override\n", + " public Waveform process(Waveform input) {\n", + " return input;\n", + " }\n", + "}\n", + "\n", + "// interfaces/filters/BandPass.java\n", + "package interfaces.filters;\n", + "\n", + "public class BandPass extends Filter {\n", + " double lowCutoff, highCutoff;\n", + " \n", + " public BandPass(double lowCut, double highCut) {\n", + " lowCutoff = lowCut;\n", + " highCutoff = highCut;\n", + " }\n", + " \n", + " @Override\n", + " public Waveform process(Waveform input) {\n", + " return input;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Filter** 类与 **Processor** 类具有相同的接口元素,但是因为它不是继承自 **Processor** —— 因为 **Filter** 类的创建者根本不知道你想将它当作 **Processor** 使用 —— 因此你不能将 **Applicator** 的 `apply()` 方法应用在 **Filter** 类上,即使这样做也能正常运行。主要是因为 **Applicator** 的 `apply()` 方法和 **Processor** 过于耦合,这阻止了 **Applicator** 的 `apply()` 方法被复用。另外要注意的一点是 Filter 类中 `process()` 方法的输入输出都是 **Waveform**。\n", + "\n", + "但如果 **Processor** 是一个接口,那么限制就会变得松动到足以复用 **Applicator** 的 `apply()` 方法,用来接受那个接口参数。下面是修改后的 **Processor** 和 **Applicator** 版本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/interfaceprocessor/Processor.java\n", + "package interfaces.interfaceprocessor;\n", + "\n", + "public interface Processor {\n", + " default String name() {\n", + " return getClass().getSimpleName();\n", + " }\n", + " \n", + " Object process(Object input);\n", + "}\n", + "\n", + "// interfaces/interfaceprocessor/Applicator.java\n", + "package interfaces.interfaceprocessor;\n", + "\n", + "public class Applicator {\n", + " public static void apply(Processor p, Object s) {\n", + " System.out.println(\"Using Processor \" + p.name());\n", + " System.out.println(p.process(s));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "复用代码的第一种方式是客户端程序员遵循接口编写类,像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/interfaceprocessor/StringProcessor.java\n", + "// {java interfaces.interfaceprocessor.StringProcessor}\n", + "package interfaces.interfaceprocessor;\n", + "import java.util.*;\n", + "\n", + "interface StringProcessor extends Processor {\n", + " @Override\n", + " String process(Object input); // [1]\n", + " String S = \"If she weighs the same as a duck, she's made of wood\"; // [2]\n", + " \n", + " static void main(String[] args) { // [3]\n", + " Applicator.apply(new Upcase(), S);\n", + " Applicator.apply(new Downcase(), S);\n", + " Applicator.apply(new Splitter(), S);\n", + " }\n", + "}\n", + "\n", + "class Upcase implements StringProcessor {\n", + " // 返回协变类型\n", + " @Override\n", + " public String process(Object input) {\n", + " return ((String) input).toUpperCase();\n", + " }\n", + "}\n", + "\n", + "class Downcase implements StringProcessor {\n", + " @Override\n", + " public String process(Object input) {\n", + " return ((String) input).toLowerCase();\n", + " }\n", + "}\n", + "\n", + "class Splitter implements StringProcessor {\n", + " @Override\n", + " public String process(Object input) {\n", + " return Arrays.toString(((String) input).split(\" \"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Using Processor Upcase\n", + "IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD\n", + "Using Processor Downcase\n", + "if she weighs the same as a duck, she's made of wood\n", + "Using Processor Splitter\n", + "[If, she, weighs, the, same, as, a, duck,, she's, made, of, wood]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ">[1] 该声明不是必要的,即使移除它,编译器也不会报错。但是注意这里的协变返回类型从 Object 变成了 String。\n", + ">\n", + ">[2] S 自动就是 final 和 static 的,因为它是在接口中定义的。\n", + ">\n", + ">[3] 可以在接口中定义 `main()` 方法。\n", + "\n", + "这种方式运作得很好,然而你经常遇到的情况是无法修改类。例如在电子滤波器的例子中,类库是被发现而不是创建的。在这些情况下,可以使用*适配器*设计模式。适配器允许代码接受已有的接口产生需要的接口,如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/interfaceprocessor/FilterProcessor.java\n", + "// {java interfaces.interfaceprocessor.FilterProcessor}\n", + "package interfaces.interfaceprocessor;\n", + "import interfaces.filters.*;\n", + "\n", + "class FilterAdapter implements Processor {\n", + " Filter filter;\n", + " \n", + " FilterAdapter(Filter filter) {\n", + " this.filter = filter;\n", + " }\n", + " \n", + " @Override\n", + " public String name() {\n", + " return filter.name();\n", + " }\n", + " \n", + " @Override\n", + " public Waveform process(Object input) {\n", + " return filter.process((Waveform) input);\n", + " }\n", + "}\n", + "\n", + "public class FilterProcessor {\n", + " public static void main(String[] args) {\n", + " Waveform w = new Waveform();\n", + " Applicator.apply(new FilterAdapter(new LowPass(1.0)), w);\n", + " Applicator.apply(new FilterAdapter(new HighPass(2.0)), w);\n", + " Applicator.apply(new FilterAdapter(new BandPass(3.0, 4.0)), w);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Using Processor LowPass\n", + "Waveform 0\n", + "Using Processor HighPass\n", + "Waveform 0\n", + "Using Processor BandPass\n", + "Waveform 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这种使用适配器的方式中,**FilterAdapter** 的构造器接受已有的接口 **Filter**,继而产生需要的 **Processor** 接口的对象。你可能还注意到 **FilterAdapter** 中使用了委托。\n", + "\n", + "协变允许我们从 `process()` 方法中产生一个 **Waveform** 而非 **Object** 对象。\n", + "\n", + "将接口与实现解耦使得接口可以应用于多种不同的实现,因而代码更具可复用性。\n", + "\n", + "\n", + "\n", + "## 多接口结合\n", + "\n", + "接口没有任何实现——也就是说,没有任何与接口相关的存储——因此无法阻止结合的多接口。这是有价值的,因为你有时需要表示“一个 **x** 是一个 **a** 和一个 **b** 以及一个 **c**”。\n", + "\n", + "![类图](../images/1562999314238.png)\n", + "\n", + "派生类并不要求必须继承自抽象的或“具体的”(没有任何抽象方法)的基类。如果继承一个非接口的类,那么只能继承一个类,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个由多个接口组合而成的具体类产生的新类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Adventure.java\n", + "// Multiple interfaces\n", + "interface CanFight {\n", + " void fight();\n", + "}\n", + "\n", + "interface CanSwim {\n", + " void swim();\n", + "}\n", + "\n", + "interface CanFly {\n", + " void fly();\n", + "}\n", + "\n", + "class ActionCharacter {\n", + " public void fight(){}\n", + "}\n", + "\n", + "class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {\n", + " public void swim() {}\n", + " \n", + " public void fly() {}\n", + "}\n", + "\n", + "public class Adventure {\n", + " public static void t(CanFight x) {\n", + " x.fight();\n", + " }\n", + " \n", + " public static void u(CanSwim x) {\n", + " x.swim();\n", + " }\n", + " \n", + " public static void v(CanFly x) {\n", + " x.fly();\n", + " }\n", + " \n", + " public static void w(ActionCharacter x) {\n", + " x.fight();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Hero h = new Hero();\n", + " t(h); // Treat it as a CanFight\n", + " u(h); // Treat it as a CanSwim\n", + " v(h); // Treat it as a CanFly\n", + " w(h); // Treat it as an ActionCharacter\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类 **Hero** 结合了具体类 **ActionCharacter** 和接口 **CanFight**、**CanSwim** 和 **CanFly**。当通过这种方式结合具体类和接口时,需要将具体类放在前面,后面跟着接口(否则编译器会报错)。\n", + "\n", + "接口 **CanFight** 和类 **ActionCharacter** 中的 `fight()` 方法签名相同,而在类 Hero 中也没有提供 `fight()` 的定义。可以扩展一个接口,但是得到的是另一个接口。当想创建一个对象时,所有的定义必须首先都存在。类 **Hero** 中没有显式地提供 `fight()` 的定义,是由于该方法在类 **ActionCharacter** 中已经定义过,这样才使得创建 **Hero** 对象成为可能。\n", + "\n", + "在类 **Adventure** 中可以看到四个方法,它们把不同的接口和具体类作为参数。当创建一个 **Hero** 对象时,它可以被传入这些方法中的任意一个,意味着它可以依次向上转型为每个接口。Java 中这种接口的设计方式,使得程序员不需要付出特别的努力。\n", + "\n", + "记住,前面例子展示了使用接口的核心原因之一:为了能够向上转型为多个基类型(以及由此带来的灵活性)。然而,使用接口的第二个原因与使用抽象基类相同:防止客户端程序员创建这个类的对象,确保这仅仅只是一个接口。这带来了一个问题:应该使用接口还是抽象类呢?如果创建不带任何方法定义或成员变量的基类,就选择接口而不是抽象类。事实上,如果知道某事物是一个基类,可以考虑用接口实现它(这个主题在本章总结会再次讨论)。\n", + "\n", + "\n", + "\n", + "## 使用继承扩展接口\n", + "\n", + "通过继承,可以很容易在接口中增加方法声明,还可以在新接口中结合多个接口。这两种情况都可以得到新接口,如下例所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/HorrorShow.java\n", + "// Extending an interface with inheritance\n", + "interface Monster {\n", + " void menace();\n", + "}\n", + "\n", + "interface DangerousMonster extends Monster {\n", + " void destroy();\n", + "}\n", + "\n", + "interface Lethal {\n", + " void kill();\n", + "}\n", + "\n", + "class DragonZilla implements DangerousMonster {\n", + " @Override\n", + " public void menace() {}\n", + " \n", + " @Override\n", + " public void destroy() {}\n", + "}\n", + "\n", + "interface Vampire extends DangerousMonster, Lethal {\n", + " void drinkBlood();\n", + "}\n", + "\n", + "class VeryBadVampire implements Vampire {\n", + " @Override\n", + " public void menace() {}\n", + " \n", + " @Override\n", + " public void destroy() {}\n", + " \n", + " @Override\n", + " public void kill() {}\n", + " \n", + " @Override\n", + " public void drinkBlood() {}\n", + "}\n", + "\n", + "public class HorrorShow {\n", + " static void u(Monster b) {\n", + " b.menace();\n", + " }\n", + " \n", + " static void v(DangerousMonster d) {\n", + " d.menace();\n", + " d.destroy();\n", + " }\n", + " \n", + " static void w(Lethal l) {\n", + " l.kill();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " DangerousMonster barney = new DragonZilla();\n", + " u(barney);\n", + " v(barney);\n", + " Vampire vlad = new VeryBadVampire();\n", + " u(vlad);\n", + " v(vlad);\n", + " w(vlad);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接口 **DangerousMonster** 是 **Monster** 简单扩展的一个新接口,类 **DragonZilla** 实现了这个接口。\n", + "\n", + "**Vampire** 中使用的语法仅适用于接口继承。通常来说,**extends** 只能用于单一类,但是在构建接口时可以引用多个基类接口。注意到,接口名之间用逗号分隔。\n", + "\n", + "### 结合接口时的命名冲突\n", + "\n", + "当实现多个接口时可能会存在一个小陷阱。在前面的例子中,**CanFight** 和 **ActionCharacter** 具有完全相同的 `fight()` 方法。完全相同的方法没有问题,但是如果它们的签名或返回类型不同会怎么样呢?这里有一个例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/InterfaceCollision.java\n", + "interface I1 {\n", + " void f();\n", + "}\n", + "\n", + "interface I2 {\n", + " int f(int i);\n", + "}\n", + "\n", + "interface I3 {\n", + " int f();\n", + "}\n", + "\n", + "class C {\n", + " public int f() {\n", + " return 1;\n", + " }\n", + "}\n", + "\n", + "class C2 implements I1, I2 {\n", + " @Override\n", + " public void f() {}\n", + " \n", + " @Override\n", + " public int f(int i) {\n", + " return 1; // 重载\n", + " }\n", + "}\n", + "\n", + "class C3 extends C implements I2 {\n", + " @Override\n", + " public int f(int i) {\n", + " return 1; // 重载\n", + " }\n", + "}\n", + "\n", + "class C4 extends C implements I3 {\n", + " // 完全相同,没问题\n", + " @Override\n", + " public int f() {\n", + " return 1;\n", + " }\n", + "}\n", + "\n", + "// 方法的返回类型不同\n", + "//- class C5 extends C implements I1 {}\n", + "//- interface I4 extends I1, I3 {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "覆写、实现和重载令人不快地搅和在一起带来了困难。同时,重载方法仅根据返回类型是区分不了的。当不注释最后两行时,报错信息如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "error: C5 is not abstract and does not override abstract\n", + "method f() in I1\n", + "class C5 extends C implements I1 {}\n", + "error: types I3 and I1 are incompatible; both define f(),\n", + "but with unrelated return types\n", + "interfacce I4 extends I1, I3 {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当打算组合接口时,在不同的接口中使用相同的方法名通常会造成代码可读性的混乱,尽量避免这种情况。\n", + "\n", + "\n", + "\n", + "## 接口适配\n", + "\n", + "接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象给方法则交由你来做。\n", + "\n", + "因此,接口的一种常见用法是前面提到的*策略*设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性。\n", + "\n", + "例如,类 **Scanner** 的构造器接受的是一个 **Readable** 接口(在“字符串”一章中学习更多相关内容)。你会发现 **Readable** 没有用作 Java 标准库中其他任何方法的参数——它是单独为 **Scanner** 创建的,因此 **Scanner** 没有将其参数限制为某个特定类。通过这种方式,**Scanner** 可以与更多的类型协作。如果你创建了一个新类并想让 **Scanner** 作用于它,就让它实现 **Readable** 接口,像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/RandomStrings.java\n", + "// Implementing an interface to conform to a method\n", + "import java.nio.*;\n", + "import java.util.*;\n", + "\n", + "public class RandomStrings implements Readable {\n", + " private static Random rand = new Random(47);\n", + " private static final char[] CAPITALS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", + " private static final char[] LOWERS = \"abcdefghijklmnopqrstuvwxyz\".toCharArray();\n", + " private static final char[] VOWELS = \"aeiou\".toCharArray();\n", + " private int count;\n", + " \n", + " public RandomStrings(int count) {\n", + " this.count = count;\n", + " }\n", + " \n", + " @Override\n", + " public int read(CharBuffer cb) {\n", + " if (count-- == 0) {\n", + " return -1; // indicates end of input\n", + " }\n", + " cb.append(CAPITALS[rand.nextInt(CAPITALS.length)]);\n", + " for (int i = 0; i < 4; i++) {\n", + " cb.append(VOWELS[rand.nextInt(VOWELS.length)]);\n", + " cb.append(LOWERS[rand.nextInt(LOWERS.length)]);\n", + " }\n", + " cb.append(\" \");\n", + " return 10; // Number of characters appended\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Scanner s = new Scanner(new RandomStrings(10));\n", + " while (s.hasNext()) {\n", + " System.out.println(s.next());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Yazeruyac\n", + "Fowenucor\n", + "Goeazimom\n", + "Raeuuacio\n", + "Nuoadesiw\n", + "Hageaikux\n", + "Ruqicibui\n", + "Numasetih\n", + "Kuuuuozog\n", + "Waqizeyoy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Readable** 接口只需要实现 `read()` 方法(注意 `@Override` 注解的突出方法)。在 `read()` 方法里,将输入内容添加到 **CharBuffer** 参数中(有多种方法可以实现,查看 **CharBuffer** 文档),或在没有输入时返回 **-1**。\n", + "\n", + "假设你有一个类没有实现 **Readable** 接口,怎样才能让 **Scanner** 作用于它呢?下面是一个产生随机浮点数的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/RandomDoubles.java\n", + "import java.util.*;\n", + "\n", + "public interface RandomDoubles {\n", + " Random RAND = new Random(47);\n", + " \n", + " default double next() {\n", + " return RAND.nextDouble();\n", + " }\n", + " \n", + " static void main(String[] args) {\n", + " RandomDoubles rd = new RandomDoubles(){};\n", + " for (int i = 0; i < 7; i++) {\n", + " System.out.println(rd.next() + \" \");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0.7271157860730044 \n", + "0.5309454508634242 \n", + "0.16020656493302599 \n", + "0.18847866977771732 \n", + "0.5166020801268457 \n", + "0.2678662084200585 \n", + "0.2613610344283964" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以再次使用适配器模式,但这里适配器类可以实现两个接口。因此,通过关键字 **interface** 提供的多继承,我们可以创建一个既是 **RandomDoubles**,又是 **Readable** 的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/AdaptedRandomDoubles.java\n", + "// creating an adapter with inheritance\n", + "import java.nio.*;\n", + "import java.util.*;\n", + "\n", + "public class AdaptedRandomDoubles implements RandomDoubles, Readable {\n", + " private int count;\n", + " \n", + " public AdaptedRandomDoubles(int count) {\n", + " this.count = count;\n", + " }\n", + " \n", + " @Override\n", + " public int read(CharBuffer cb) {\n", + " if (count-- == 0) {\n", + " return -1;\n", + " }\n", + " String result = Double.toString(next()) + \" \";\n", + " cb.append(result);\n", + " return result.length();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Scanner s = new Scanner(new AdaptedRandomDoubles(7));\n", + " while (s.hasNextDouble()) {\n", + " System.out.print(s.nextDouble() + \" \");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0.7271157860730044 0.5309454508634242 \n", + "0.16020656493302599 0.18847866977771732 \n", + "0.5166020801268457 0.2678662084200585 \n", + "0.2613610344283964" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为你可以以这种方式在已有类中增加新接口,所以这就意味着一个接受接口类型的方法提供了一种让任何类都可以与该方法进行适配的方式。这就是使用接口而不是类的强大之处。\n", + "\n", + "\n", + "\n", + "## 接口字段\n", + "\n", + "因为接口中的字段都自动是 **static** 和 **final** 的,所以接口就成为了创建一组常量的方便的工具。在 Java 5 之前,这是产生与 C 或 C++ 中的 enum (枚举类型) 具有相同效果的唯一方式。所以你可能在 Java 5 之前的代码中看到:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Months.java\n", + "// Using interfaces to create groups of constants\n", + "public interface Months {\n", + " int \n", + " JANUARY = 1, FEBRUARY = 2, MARCH = 3,\n", + " APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,\n", + " AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,\n", + " NOVEMBER = 11, DECEMBER = 12;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意 Java 中使用大写字母的风格定义具有初始化值的 **static** **final** 变量。接口中的字段自动是 **public** 的,所以没有显式指明这点。\n", + "\n", + "自 Java 5 开始,我们有了更加强大和灵活的关键字 **enum**,那么在接口中定义常量组就显得没什么意义了。然而当你阅读遗留的代码时,在很多场合你还会碰到这种旧的习惯用法。在“枚举”一章中你会学习到更多关于枚举的内容。\n", + "\n", + "### 初始化接口中的字段\n", + "\n", + "接口中定义的字段不能是“空 **final**\",但是可以用非常量表达式初始化。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/RandVals.java\n", + "// Initializing interface fields with\n", + "// non-constant initializers\n", + "import java.util.*;\n", + "\n", + "public interface RandVals {\n", + " Random RAND = new Random(47);\n", + " int RANDOM_INT = RAND.nextInt(10);\n", + " long RANDOM_LONG = RAND.nextLong() * 10;\n", + " float RANDOM_FLOAT = RAND.nextLong() * 10;\n", + " double RANDOM_DOUBLE = RAND.nextDouble() * 10;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为字段是 **static** 的,所以它们在类第一次被加载时初始化,这发生在任何字段首次被访问时。下面是个简单的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/TestRandVals.java\n", + "public class TestRandVals {\n", + " public static void main(String[] args) {\n", + " System.out.println(RandVals.RANDOM_INT);\n", + " System.out.println(RandVals.RANDOM_LONG);\n", + " System.out.println(RandVals.RANDOM_FLOAT);\n", + " System.out.println(RandVals.RANDOM_DOUBLE);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "8\n", + "-32032247016559954\n", + "-8.5939291E18\n", + "5.779976127815049" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些字段不是接口的一部分,它们的值被存储在接口的静态存储区域中。\n", + "\n", + "\n", + "\n", + "## 接口嵌套\n", + "\n", + "接口可以嵌套在类或其他接口中。下面揭示一些有趣的特性:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/nesting/NestingInterfaces.java\n", + "// {java interfaces.nesting.NestingInterfaces}\n", + "package interfaces.nesting;\n", + "\n", + "class A {\n", + " interface B {\n", + " void f();\n", + " }\n", + " \n", + " public class BImp implements B {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " public class BImp2 implements B {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " public interface C {\n", + " void f();\n", + " }\n", + " \n", + " class CImp implements C {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " private class CImp2 implements C {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " private interface D {\n", + " void f();\n", + " }\n", + " \n", + " private class DImp implements D {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " public class DImp2 implements D {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " public D getD() {\n", + " return new DImp2();\n", + " }\n", + " \n", + " private D dRef;\n", + " \n", + " public void receiveD(D d) {\n", + " dRef = d;\n", + " dRef.f();\n", + " }\n", + "}\n", + "\n", + "interface E {\n", + " interface G {\n", + " void f();\n", + " }\n", + " // Redundant \"public\"\n", + " public interface H {\n", + " void f();\n", + " }\n", + " \n", + " void g();\n", + " // Cannot be private within an interface\n", + " //- private interface I {}\n", + "}\n", + "\n", + "public class NestingInterfaces {\n", + " public class BImp implements A.B {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " class CImp implements A.C {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " // Cannot implements a private interface except\n", + " // within that interface's defining class:\n", + " //- class DImp implements A.D {\n", + " //- public void f() {}\n", + " //- }\n", + " class EImp implements E {\n", + " @Override\n", + " public void g() {}\n", + " }\n", + " \n", + " class EGImp implements E.G {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " \n", + " class EImp2 implements E {\n", + " @Override\n", + " public void g() {}\n", + " \n", + " class EG implements E.G {\n", + " @Override\n", + " public void f() {}\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " A a = new A();\n", + " // Can't access to A.D:\n", + " //- A.D ad = a.getD();\n", + " // Doesn't return anything but A.D:\n", + " //- A.DImp2 di2 = a.getD();\n", + " // cannot access a member of the interface:\n", + " //- a.getD().f();\n", + " // Only another A can do anything with getD():\n", + " A a2 = new A();\n", + " a2.receiveD(a.getD());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在类中嵌套接口的语法是相当显而易见的。就像非嵌套接口一样,它们具有 **public** 或包访问权限的可见性。\n", + "\n", + "作为一种新添加的方式,接口也可以是 **private** 的,例如 **A.D**(同样的语法同时适用于嵌套接口和嵌套类)。那么 **private** 嵌套接口有什么好处呢?你可能猜测它只是被用来实现一个 **private** 内部类,就像 **DImp**。然而 **A.DImp2** 展示了它可以被实现为 **public** 类,但是 **A.DImp2** 只能被自己使用,你无法说它实现了 **private** 接口 **D**,所以实现 **private** 接口是一种可以强制该接口中的方法定义不会添加任何类型信息(即不可以向上转型)的方式。\n", + "\n", + "`getD()` 方法产生了一个与 **private** 接口有关的窘境。它是一个 **public** 方法却返回了对 **private** 接口的引用。能对这个返回值做些什么呢?`main()` 方法里进行了一些使用返回值的尝试但都失败了。返回值必须交给有权使用它的对象,本例中另一个 **A** 通过 `receiveD()` 方法接受了它。\n", + "\n", + "接口 **E** 说明了接口之间也能嵌套。然而,作用于接口的规则——尤其是,接口中的元素必须是 **public** 的——在此都会被严格执行,所以嵌套在另一个接口中的接口自动就是 **public** 的,不能指明为 **private**。\n", + "\n", + "类 **NestingInterfaces** 展示了嵌套接口的不同实现方式。尤其是当实现某个接口时,并不需要实现嵌套在其内部的接口。同时,**private** 接口不能在定义它的类之外被实现。\n", + "\n", + "添加这些特性的最初原因看起来像是出于对严格的语法一致性的考虑,但是我通常认为,一旦你了解了某种特性,就总能找到其用武之地。\n", + "\n", + "\n", + "\n", + "## 接口和工厂方法模式\n", + "\n", + "接口是多实现的途径,而生成符合某个接口的对象的典型方式是*工厂方法*设计模式。不同于直接调用构造器,只需调用工厂对象中的创建方法就能生成对象的实现——理论上,通过这种方式可以将接口与实现的代码完全分离,使得可以透明地将某个实现替换为另一个实现。这里是一个展示工厂方法结构的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Factories.java\n", + "interface Service {\n", + " void method1();\n", + " void method2();\n", + "}\n", + "\n", + "interface ServiceFactory {\n", + " Service getService();\n", + "}\n", + "\n", + "class Service1 implements Service {\n", + " Service1() {} // Package access\n", + " \n", + " @Override\n", + " public void method1() {\n", + " System.out.println(\"Service1 method1\");\n", + " }\n", + " \n", + " @Override\n", + " public void method2() {\n", + " System.out.println(\"Service1 method2\");\n", + " }\n", + "}\n", + "\n", + "class Service1Factory implements ServiceFactory {\n", + " @Override\n", + " public Service getService() {\n", + " return new Service1();\n", + " }\n", + "}\n", + "\n", + "class Service2 implements Service {\n", + " Service2() {} // Package access\n", + " \n", + " @Override\n", + " public void method1() {\n", + " System.out.println(\"Service2 method1\");\n", + " }\n", + " \n", + " @Override\n", + " public void method2() {\n", + " System.out.println(\"Service2 method2\");\n", + " }\n", + "}\n", + "\n", + "class Service2Factory implements ServiceFactory {\n", + " @Override\n", + " public Service getService() {\n", + " return new Service2();\n", + " }\n", + "}\n", + "\n", + "public class Factories {\n", + " public static void serviceConsumer(ServiceFactory fact) {\n", + " Service s = fact.getService();\n", + " s.method1();\n", + " s.method2();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " serviceConsumer(new Service1Factory());\n", + " // Services are completely interchangeable:\n", + " serviceConsumer(new Service2Factory());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Service1 method1\n", + "Service1 method2\n", + "Service2 method1\n", + "Service2 method2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有工厂方法,代码就必须在某处指定将要创建的 **Service** 的确切类型,从而调用恰当的构造器。\n", + "\n", + "为什么要添加额外的间接层呢?一个常见的原因是创建框架。假设你正在创建一个游戏系统;例如,在相同的棋盘下国际象棋和西洋跳棋:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// interfaces/Games.java\n", + "// A Game framework using Factory Methods\n", + "interface Game {\n", + " boolean move();\n", + "}\n", + "\n", + "interface GameFactory {\n", + " Game getGame();\n", + "}\n", + "\n", + "class Checkers implements Game {\n", + " private int moves = 0;\n", + " private static final int MOVES = 3;\n", + " \n", + " @Override\n", + " public boolean move() {\n", + " System.out.println(\"Checkers move \" + moves);\n", + " return ++moves != MOVES;\n", + " }\n", + "}\n", + "\n", + "class CheckersFactory implements GameFactory {\n", + " @Override\n", + " public Game getGame() {\n", + " return new Checkers();\n", + " }\n", + "}\n", + "\n", + "class Chess implements Game {\n", + " private int moves = 0;\n", + " private static final int MOVES = 4;\n", + " \n", + " @Override\n", + " public boolean move() {\n", + " System.out.println(\"Chess move \" + moves);\n", + " return ++moves != MOVES;\n", + " }\n", + "}\n", + "\n", + "class ChessFactory implements GameFactory {\n", + " @Override\n", + " public Game getGame() {\n", + " return new Chess();\n", + " }\n", + "}\n", + "\n", + "public class Games {\n", + " public static void playGame(GameFactory factory) {\n", + " Game s = factory.getGame();\n", + " while (s.move()) {\n", + " ;\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " playGame(new CheckersFactory());\n", + " playGame(new ChessFactory());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Checkers move 0\n", + "Checkers move 1\n", + "Checkers move 2\n", + "Chess move 0\n", + "Chess move 1\n", + "Chess move 2\n", + "Chess move 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果类 **Games** 表示一段很复杂的代码,那么这种方式意味着你可以在不同类型的游戏里复用这段代码。你可以再想象一些能够从这个模式中受益的更加精巧的游戏。\n", + "\n", + "在下一章,你将会看到一种更加优雅的使用匿名内部类的工厂实现方式。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "认为接口是好的选择,从而使用接口不用具体类,这具有诱惑性。几乎任何时候,创建类都可以替代为创建一个接口和工厂。\n", + "\n", + "很多人都掉进了这个陷阱,只要有可能就创建接口和工厂。这种逻辑看起来像是可能会使用不同的实现,所以总是添加这种抽象性。这变成了一种过早的设计优化。\n", + "\n", + "任何抽象性都应该是由真正的需求驱动的。当有必要时才应该使用接口进行重构,而不是到处添加额外的间接层,从而带来额外的复杂性。这种复杂性非常显著,如果你让某人去处理这种复杂性,只是因为你意识到“以防万一”而添加新接口,而没有其他具有说服力的原因——好吧,如果我碰上了这种设计,就会质疑此人所作的所有其他设计了。\n", + "\n", + "恰当的原则是优先使用类而不是接口。从类开始,如果使用接口的必要性变得很明确,那么就重构。接口是一个伟大的工具,但它们容易被滥用。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/11-Inner-Classes.ipynb b/jupyter/11-Inner-Classes.ipynb new file mode 100644 index 00000000..5d361004 --- /dev/null +++ b/jupyter/11-Inner-Classes.ipynb @@ -0,0 +1,2189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第十一章 内部类\n", + "\n", + "> 一个定义在另一个类中的类,叫作内部类。\n", + "\n", + "内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。\n", + "\n", + "最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,\"Why inner classes?\"就应该使得内部类的益处明确显现了。\n", + "\n", + "本章剩余部分包含了对内部类语法更加详尽的探索,这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始不需要。因此,本章最初的部分也许就是你现在所需的全部,你可以将更详尽的探索当作参考资料。\n", + "\n", + "\n", + "\n", + "## 创建内部类\n", + "\n", + "创建内部类的方式就如同你想的一样——把类的定义置于外围类的里面:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel1.java\n", + "// Creating inner classes\n", + "public class Parcel1 {\n", + " class Contents {\n", + " private int i = 11;\n", + " \n", + " public int value() { return i; }\n", + " }\n", + " \n", + " class Destination {\n", + " private String label;\n", + " \n", + " Destination(String whereTo) {\n", + " label = whereTo;\n", + " }\n", + " \n", + " String readLabel() { return label; }\n", + " }\n", + " // Using inner classes looks just like\n", + " // using any other class, within Parcel1:\n", + " public void ship(String dest) {\n", + " Contents c = new Contents();\n", + " Destination d = new Destination(dest);\n", + " System.out.println(d.readLabel());\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Parcel1 p = new Parcel1();\n", + " p.ship(\"Tasmania\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Tasmania" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当我们在 `ship()` 方法里面使用内部类的时候,与使用普通类没什么不同。在这里,明显的区别只是内部类的名字是嵌套在 **Parcel1** 里面的。\n", + "\n", + "更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用,就像在 `to()` 和 `contents()` 方法中看到的那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel2.java\n", + "// Returning a reference to an inner class\n", + "public class Parcel2 {\n", + " class Contents {\n", + " private int i = 11;\n", + " \n", + " public int value() { return i; }\n", + " }\n", + " \n", + " class Destination {\n", + " private String label;\n", + " \n", + " Destination(String whereTo) {\n", + " label = whereTo;\n", + " }\n", + " \n", + " String readLabel() { return label; }\n", + " }\n", + " \n", + " public Destination to(String s) {\n", + " return new Destination(s);\n", + " }\n", + " \n", + " public Contents contents() {\n", + " return new Contents();\n", + " }\n", + " \n", + " public void ship(String dest) {\n", + " Contents c = contents();\n", + " Destination d = to(dest);\n", + " System.out.println(d.readLabel());\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Parcel2 p = new Parcel2();\n", + " p.ship(\"Tasmania\");\n", + " Parcel2 q = new Parcel2();\n", + " // Defining references to inner classes:\n", + " Parcel2.Contents c = q.contents();\n", + " Parcel2.Destination d = q.to(\"Borneo\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Tasmania" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 `main()` 方法中那样,具体地指明这个对象的类型:*OuterClassName.InnerClassName*。(译者注:在外部类的静态方法中也可以直接指明类型 *InnerClassName*,在其他类中需要指明 *OuterClassName.InnerClassName*。)\n", + "\n", + "\n", + "\n", + "## 链接外部类\n", + "\n", + "到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Sequence.java\n", + "// Holds a sequence of Objects\n", + "interface Selector {\n", + " boolean end();\n", + " Object current();\n", + " void next();\n", + "}\n", + "public class Sequence {\n", + " private Object[] items;\n", + " private int next = 0;\n", + " public Sequence(int size) {\n", + " items = new Object[size];\n", + " }\n", + " public void add(Object x) {\n", + " if(next < items.length)\n", + " items[next++] = x;\n", + " }\n", + " private class SequenceSelector implements Selector {\n", + " private int i = 0;\n", + " @Override\n", + " public boolean end() { return i == items.length; }\n", + " @Override\n", + " public Object current() { return items[i]; }\n", + " @Override\n", + " public void next() { if(i < items.length) i++; }\n", + " }\n", + " public Selector selector() {\n", + " return new SequenceSelector();\n", + " }\n", + " public static void main(String[] args) {\n", + " Sequence sequence = new Sequence(10);\n", + " for(int i = 0; i < 10; i++)\n", + " sequence.add(Integer.toString(i));\n", + " Selector selector = sequence.selector();\n", + " while(!selector.end()) {\n", + " System.out.print(selector.current() + \" \");\n", + " selector.next();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0 1 2 3 4 5 6 7 8 9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。\n", + "\n", + "这里,**SequenceSelector** 是提供 **Selector** 功能的 **private** 类。可以看到,在 `main()` 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 `selector()` 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。\n", + "最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外围类中的一个 **private** 字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。\n", + "\n", + "所以内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。\n", + "\n", + "\n", + "## 使用 .this 和 .new\n", + "\n", + "如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 **this**。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 **.this**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/DotThis.java\n", + "// Accessing the outer-class object\n", + "public class DotThis {\n", + " void f() { System.out.println(\"DotThis.f()\"); }\n", + " \n", + " public class Inner {\n", + " public DotThis outer() {\n", + " return DotThis.this;\n", + " // A plain \"this\" would be Inner's \"this\"\n", + " }\n", + " }\n", + " \n", + " public Inner inner() { return new Inner(); }\n", + " \n", + " public static void main(String[] args) {\n", + " DotThis dt = new DotThis();\n", + " DotThis.Inner dti = dt.inner();\n", + " dti.outer().f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "DotThis.f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 **new** 表达式中提供对其他外部类对象的引用,这是需要使用 **.new** 语法,就像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/DotNew.java\n", + "// Creating an inner class directly using .new syntax\n", + "public class DotNew {\n", + " public class Inner {}\n", + " public static void main(String[] args) {\n", + " DotNew dn = new DotNew();\n", + " DotNew.Inner dni = dn.new Inner();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 **DotNew**,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Inner。\n", + "\n", + "下面你可以看到将 **.new** 应用于 Parcel 的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel3.java\n", + "// Using .new to create instances of inner classes\n", + "public class Parcel3 {\n", + " class Contents {\n", + " private int i = 11;\n", + " public int value() { return i; }\n", + " }\n", + " class Destination {\n", + " private String label;\n", + " Destination(String whereTo) { label = whereTo; }\n", + " String readLabel() { return label; }\n", + " }\n", + " public static void main(String[] args) {\n", + " Parcel3 p = new Parcel3();\n", + " // Must use instance of outer class\n", + " // to create an instance of the inner class:\n", + " Parcel3.Contents c = p.new Contents();\n", + " Parcel3.Destination d =\n", + " p.new Destination(\"Tasmania\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。\n", + "\n", + "\n", + "\n", + "## 内部类与向上转型\n", + "\n", + "当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。)这是因为此内部类-某个接口的实现-能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。\n", + "\n", + "我们可以创建前一个示例的接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Destination.java\n", + "public interface Destination {\n", + " String readLabel();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Contents.java\n", + "public interface Contents {\n", + " int value();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在 **Contents** 和 **Destination** 表示客户端程序员可用的接口。记住,接口的所有成员自动被设置为 **public**。\n", + "\n", + "当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型,看下面的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/TestParcel.java\n", + "class Parcel4 {\n", + " private class PContents implements Contents {\n", + " private int i = 11;\n", + " @Override\n", + " public int value() { return i; }\n", + " }\n", + " protected final class PDestination implements Destination {\n", + " private String label;\n", + " private PDestination(String whereTo) {\n", + " label = whereTo;\n", + " }\n", + " @Override\n", + " public String readLabel() { return label; }\n", + " }\n", + " public Destination destination(String s) {\n", + " return new PDestination(s);\n", + " }\n", + " public Contents contents() {\n", + " return new PContents();\n", + " }\n", + "}\n", + "public class TestParcel {\n", + " public static void main(String[] args) {\n", + " Parcel4 p = new Parcel4();\n", + " Contents c = p.contents();\n", + " Destination d = p.destination(\"Tasmania\");\n", + " // Illegal -- can't access private class:\n", + " //- Parcel4.PContents pc = p.new PContents();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **Parcel4** 中,内部类 **PContents** 是 **private**,所以除了 **Parcel4**,没有人能访问它。普通(非内部)类的访问权限不能被设为 **private** 或者 **protected**;他们只能设置为 **public** 或 **package** 访问权限。\n", + "\n", + "**PDestination** 是 **protected**,所以只有 **Parcel4** 及其子类、还有与 **Parcel4** 同一个包中的类(因为 **protected** 也给予了包访问权)能访问 **PDestination**,其他类都不能访问 **PDestination**,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成 **private** 内部类(或 **protected** 内部类,除非是继承自它的子类),因为不能访问其名字,就像在 **TestParcel** 类中看到的那样。\n", + "\n", + "**private** 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给 Java 编译器提供了生成高效代码的机会。\n", + "\n", + "\n", + "\n", + "## 内部类方法和作用域\n", + "\n", + "到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。\n", + "\n", + "这么做有两个理由:\n", + "\n", + "1. 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。\n", + "2. 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。\n", + "\n", + "在后面的例子中,先前的代码将被修改,以用来实现:\n", + "\n", + "1. 一个定义在方法中的类。\n", + "2. 一个定义在作用域内的类,此作用域在方法的内部。\n", + "3. 一个实现了接口的匿名类。\n", + "4. 一个匿名类,它扩展了没有默认构造器的类。\n", + "5. 一个匿名类,它执行字段初始化。\n", + "6. 一个匿名类,它通过实例初始化实现构造(匿名内部类不可能有构造器)。\n", + "\n", + "第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel5.java\n", + "// Nesting a class within a method\n", + "public class Parcel5 {\n", + " public Destination destination(String s) {\n", + " final class PDestination implements Destination {\n", + " private String label;\n", + " \n", + " private PDestination(String whereTo) {\n", + " label = whereTo;\n", + " }\n", + " \n", + " @Override\n", + " public String readLabel() { return label; }\n", + " }\n", + " return new PDestination(s);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Parcel5 p = new Parcel5();\n", + " Destination d = p.destination(\"Tasmania\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**PDestination** 类是 `destination()` 方法的一部分,而不是 **Parcel5** 的一部分。所以,在 `destination()` 之外不能访问 **PDestination**,注意出现在 **return** 语句中的向上转型-返回的是 **Destination** 的引用,它是 **PDestination** 的基类。当然,在 `destination()` 中定义了内部类 **PDestination**,并不意味着一旦 `destination()` 方法执行完毕,**PDestination** 就不可用了。\n", + "\n", + "你可以在同一个子目录下的任意类中对某个内部类使用类标识符 **PDestination**,这并不会有命名冲突。\n", + "\n", + "下面的例子展示了如何在任意的作用域内嵌入一个内部类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel6.java\n", + "// Nesting a class within a scope\n", + "public class Parcel6 {\n", + " private void internalTracking(boolean b) {\n", + " if(b) {\n", + " class TrackingSlip {\n", + " private String id;\n", + " TrackingSlip(String s) {\n", + " id = s;\n", + " }\n", + " String getSlip() { return id; }\n", + " }\n", + " TrackingSlip ts = new TrackingSlip(\"slip\");\n", + " String s = ts.getSlip();\n", + " }\n", + " // Can't use it here! Out of scope:\n", + " //- TrackingSlip ts = new TrackingSlip(\"x\");\n", + " }\n", + " public void track() { internalTracking(true); }\n", + " public static void main(String[] args) {\n", + " Parcel6 p = new Parcel6();\n", + " p.track();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**TrackingSlip** 类被嵌入在 **if** 语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义 **Trackingslip** 的作用域之外,它是不可用的,除此之外,它与普通的类一样。\n", + "\n", + "\n", + "\n", + "## 匿名内部类\n", + "\n", + "下面的例子看起来有点奇怪:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel7.java\n", + "// Returning an instance of an anonymous inner class\n", + "public class Parcel7 {\n", + " public Contents contents() {\n", + " return new Contents() { // Insert class definition\n", + " private int i = 11;\n", + " \n", + " @Override\n", + " public int value() { return i; }\n", + " }; // Semicolon required\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Parcel7 p = new Parcel7();\n", + " Contents c = p.contents();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`contents()` 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个 **Contents** 对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义。”\n", + "\n", + "这种奇怪的语法指的是:“创建一个继承自 **Contents** 的匿名类的对象。”通过 **new** 表达式返回的引用被自动向上转型为对 **Contents** 的引用。上述匿名内部类的语法是下述形式的简化形式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel7b.java\n", + "// Expanded version of Parcel7.java\n", + "public class Parcel7b {\n", + " class MyContents implements Contents {\n", + " private int i = 11;\n", + " @Override\n", + " public int value() { return i; }\n", + " }\n", + " \n", + " public Contents contents() {\n", + " return new MyContents();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Parcel7b p = new Parcel7b();\n", + " Contents c = p.contents();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个匿名内部类中,使用了默认的构造器来生成 **Contents**。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel8.java\n", + "// Calling the base-class constructor\n", + "public class Parcel8 {\n", + " public Wrapping wrapping(int x) {\n", + " // Base constructor call:\n", + " return new Wrapping(x) { // [1]\n", + " @Override\n", + " public int value() {\n", + " return super.value() * 47;\n", + " }\n", + " }; // [2]\n", + " }\n", + " public static void main(String[] args) {\n", + " Parcel8 p = new Parcel8();\n", + " Wrapping w = p.wrapping(10);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- \\[1\\] 将合适的参数传递给基类的构造器。\n", + "- \\[2\\] 在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。\n", + "\n", + "尽管 **Wrapping** 只是一个具有具体实现的普通类,但它还是被导出类当作公共“接口”来使用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Wrapping.java\n", + "public class Wrapping {\n", + " private int i;\n", + " public Wrapping(int x) { i = x; }\n", + " public int value() { return i; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了多样性,**Wrapping** 拥有一个要求传递一个参数的构造器。\n", + "\n", + "在匿名类中定义字段时,还能够对其执行初始化操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel9.java\n", + "public class Parcel9 {\n", + " // Argument must be final or \"effectively final\"\n", + " // to use within the anonymous inner class:\n", + " public Destination destination(final String dest) {\n", + " return new Destination() {\n", + " private String label = dest;\n", + " @Override\n", + " public String readLabel() { return label; }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " Parcel9 p = new Parcel9();\n", + " Destination d = p.destination(\"Tasmania\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 **final** 的(也就是说,它在初始化后不会改变,所以可以被当作 **final**),就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但是通常最好加上 **final** 作为一种暗示。\n", + "\n", + "如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/AnonymousConstructor.java\n", + "// Creating a constructor for an anonymous inner class\n", + "abstract class Base {\n", + " Base(int i) {\n", + " System.out.println(\"Base constructor, i = \" + i);\n", + " }\n", + " public abstract void f();\n", + "}\n", + "public class AnonymousConstructor {\n", + " public static Base getBase(int i) {\n", + " return new Base(i) {\n", + " { System.out.println(\n", + " \"Inside instance initializer\"); }\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"In anonymous f()\");\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " Base base = getBase(47);\n", + " base.f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Base constructor, i = 47\n", + "Inside instance initializer\n", + "In anonymous f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在此例中,不要求变量一定是 **final** 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。\n", + "\n", + "下例是带实例初始化的\"parcel\"形式。注意 `destination()` 的参数必须是 **final** 的,因为它们是在匿名类内部使用的(译者注:即使不加 **final**, Java 8 的编译器也会为我们自动加上 **final**,以保证数据的一致性)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel10.java\n", + "// Using \"instance initialization\" to perform\n", + "// construction on an anonymous inner class\n", + "public class Parcel10 {\n", + " public Destination\n", + " destination(final String dest, final float price) {\n", + " return new Destination() {\n", + " private int cost;\n", + " // Instance initialization for each object:\n", + " {\n", + " cost = Math.round(price);\n", + " if(cost > 100)\n", + " System.out.println(\"Over budget!\");\n", + " }\n", + " private String label = dest;\n", + " @Override\n", + " public String readLabel() { return label; }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " Parcel10 p = new Parcel10();\n", + " Destination d = p.destination(\"Tasmania\", 101.395F);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Over budget!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 **if** 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。\n", + "\n", + "匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。\n", + "\n", + "\n", + "\n", + "## 嵌套类\n", + "\n", + "如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着:\n", + "\n", + "1. 要创建嵌套类的对象,并不需要其外围类的对象。\n", + "2. 不能从嵌套类的对象中访问非静态的外围类对象。\n", + "\n", + "嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Parcel11.java\n", + "// Nested classes (static inner classes)\n", + "public class Parcel11 {\n", + " private static class ParcelContents implements Contents {\n", + " private int i = 11;\n", + " @Override\n", + " public int value() { return i; }\n", + " }\n", + " protected static final class ParcelDestination\n", + " implements Destination {\n", + " private String label;\n", + " private ParcelDestination(String whereTo) {\n", + " label = whereTo;\n", + " }\n", + " @Override\n", + " public String readLabel() { return label; }\n", + " // Nested classes can contain other static elements:\n", + " public static void f() {}\n", + " static int x = 10;\n", + " static class AnotherLevel {\n", + " public static void f() {}\n", + " static int x = 10;\n", + " }\n", + " }\n", + " public static Destination destination(String s) {\n", + " return new ParcelDestination(s);\n", + " }\n", + " public static Contents contents() {\n", + " return new ParcelContents();\n", + " }\n", + " public static void main(String[] args) {\n", + " Contents c = contents();\n", + " Destination d = destination(\"Tasmania\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `main()` 中,没有任何 **Parcel11** 的对象是必需的;而是使用选取 **static** 成员的普通语法来调用方法-这些方法返回对 **Contents** 和 **Destination** 的引用。\n", + "\n", + "就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外围类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。\n", + "\n", + "### 接口内部的类\n", + "\n", + "嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/ClassInInterface.java\n", + "// {java ClassInInterface$Test}\n", + "public interface ClassInInterface {\n", + " void howdy();\n", + " class Test implements ClassInInterface {\n", + " @Override\n", + " public void howdy() {\n", + " System.out.println(\"Howdy!\");\n", + " }\n", + " public static void main(String[] args) {\n", + " new Test().howdy();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Howdy!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。\n", + "\n", + "我曾在本书中建议过,在每个类中都写一个 `main()` 方法,用来测试这个类。这样做有一个缺点,那就是必须带着那些已编译过的额外代码。如果这对你是个麻烦,那就可以使用嵌套类来放置测试代码。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/TestBed.java\n", + "// Putting test code in a nested class\n", + "// {java TestBed$Tester}\n", + "public class TestBed {\n", + " public void f() { System.out.println(\"f()\"); }\n", + " public static class Tester {\n", + " public static void main(String[] args) {\n", + " TestBed t = new TestBed();\n", + " t.f();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这生成了一个独立的类 **TestBed$Tester**(要运行这个程序,执行 **java TestBed$Tester**,在 Unix/Linux 系统中需要转义 **$**)。你可以使用这个类测试,但是不必在发布的产品中包含它,可以在打包产品前删除 **TestBed$Tester.class**。\n", + "\n", + "### 从多层嵌套类中访问外部类的成员\n", + "\n", + "一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/MultiNestingAccess.java\n", + "// Nested classes can access all members of all\n", + "// levels of the classes they are nested within\n", + "class MNA {\n", + " private void f() {}\n", + " class A {\n", + " private void g() {}\n", + " public class B {\n", + " void h() {\n", + " g();\n", + " f();\n", + " }\n", + " }\n", + " }\n", + "}\n", + "public class MultiNestingAccess {\n", + " public static void main(String[] args) {\n", + " MNA mna = new MNA();\n", + " MNA.A mnaa = mna.new A();\n", + " MNA.A.B mnaab = mnaa.new B();\n", + " mnaab.h();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到在 **MNA.A.B** 中,调用方法 `g()` 和 `f()` 不需要任何条件(即使它们被定义为 **private**)。这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。\"**.new**\"语法能产生正确的作用域,所以不必在调用构造器时限定类名。\n", + "\n", + "\n", + "\n", + "## 为什么需要内部类\n", + "\n", + "至此,我们已经看到了许多描述内部类的语法和语义,但是这并不能同答“为什么需要内部类”这个问题。那么,Java 设计者们为什么会如此费心地增加这项基本的语言特性呢?\n", + "\n", + "一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。\n", + "\n", + "内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:\n", + "\n", + "> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。\n", + "\n", + "如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(译注:类或抽象类)。\n", + "\n", + "为了看到更多的细节,让我们考虑这样一种情形:即必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有两种选择;使用单一类,或者使用内部类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/mui/MultiInterfaces.java\n", + "// Two ways a class can implement multiple interfaces\n", + "// {java innerclasses.mui.MultiInterfaces}\n", + "package innerclasses.mui;\n", + "interface A {}\n", + "interface B {}\n", + "class X implements A, B {}\n", + "class Y implements A {\n", + " B makeB() {\n", + " // Anonymous inner class:\n", + " return new B() {};\n", + " }\n", + "}\n", + "public class MultiInterfaces {\n", + " static void takesA(A a) {}\n", + " static void takesB(B b) {}\n", + " public static void main(String[] args) {\n", + " X x = new X();\n", + " Y y = new Y();\n", + " takesA(x);\n", + " takesA(y);\n", + " takesB(x);\n", + " takesB(y.makeB());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当然,这里假设在两种方式下的代码结构都确实有逻辑意义。然而遇到问题的时候,通常问题本身就能给出某些指引,告诉你是应该使用单一类,还是使用内部类。但如果没有任何其他限制,从实现的观点来看,前面的例子并没有什么区别,它们都能正常运作。\n", + "\n", + "如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/MultiImplementation.java\n", + "// For concrete or abstract classes, inner classes\n", + "// produce \"multiple implementation inheritance\"\n", + "// {java innerclasses.MultiImplementation}\n", + "package innerclasses;\n", + "\n", + "class D {}\n", + "\n", + "abstract class E {}\n", + "\n", + "class Z extends D {\n", + " E makeE() {\n", + " return new E() {}; \n", + " }\n", + "}\n", + "\n", + "public class MultiImplementation {\n", + " static void takesD(D d) {}\n", + " static void takesE(E e) {}\n", + " \n", + " public static void main(String[] args) {\n", + " Z z = new Z();\n", + " takesD(z);\n", + " takesE(z.makeE());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性:\n", + "\n", + "1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。\n", + "2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。\n", + "稍后就会展示一个这样的例子。\n", + "3. 创建内部类对象的时刻并不依赖于外围类对象的创建\n", + "4. 内部类并没有令人迷惑的\"is-a”关系,它就是一个独立的实体。\n", + "\n", + "举个例子,如果 **Sequence.java** 不使用内部类,就必须声明\"**Sequence** 是一个 **Selector**\",对于某个特定的 **Sequence** 只能有一个 **Selector**,然而使用内部类很容易就能拥有另一个方法 `reverseSelector()`,用它来生成一个反方向遍历序列的 **Selector**,只有内部类才有这种灵活性。\n", + "\n", + "### 闭包与回调\n", + "\n", + "闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。\n", + "\n", + "在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁,你将会在 [函数式编程 ]() 这一章节中学习相关细节。尽管相对于内部类,你可能更喜欢使用 lambda 表达式实现闭包,但是你会看到并需要理解那些在 Java 8 之前通过内部类方式实现闭包的代码,因此仍然有必要来理解这种方式。\n", + "\n", + "Java 最引人争议的问题之一就是,人们认为 Java 应该包含某种类似指针的机制,以允许回调(callback)。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。稍后将会看到这是一个非常有用的概念。如果回调是通过指针实现的,那么就只能寄希望于程序员不会误用该指针。然而,读者应该已经了解到,Java 更小心仔细,所以没有在语言中包括指针。\n", + "\n", + "通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全。见下例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/Callbacks.java\n", + "// Using inner classes for callbacks\n", + "// {java innerclasses.Callbacks}\n", + "package innerclasses;\n", + "interface Incrementable {\n", + " void increment();\n", + "}\n", + "// Very simple to just implement the interface:\n", + "class Callee1 implements Incrementable {\n", + " private int i = 0;\n", + " @Override\n", + " public void increment() {\n", + " i++;\n", + " System.out.println(i);\n", + " }\n", + "}\n", + "class MyIncrement {\n", + " public void increment() {\n", + " System.out.println(\"Other operation\");\n", + " }\n", + " static void f(MyIncrement mi) { mi.increment(); }\n", + "}\n", + "// If your class must implement increment() in\n", + "// some other way, you must use an inner class:\n", + "class Callee2 extends MyIncrement {\n", + " private int i = 0;\n", + " @Override\n", + " public void increment() {\n", + " super.increment();\n", + " i++;\n", + " System.out.println(i);\n", + " }\n", + " private class Closure implements Incrementable {\n", + " @Override\n", + " public void increment() {\n", + " // Specify outer-class method, otherwise\n", + " // you'll get an infinite recursion:\n", + " Callee2.this.increment();\n", + " }\n", + " }\n", + " Incrementable getCallbackReference() {\n", + " return new Closure();\n", + " }\n", + "}\n", + "class Caller {\n", + " private Incrementable callbackReference;\n", + " Caller(Incrementable cbh) {\n", + " callbackReference = cbh;\n", + " }\n", + " void go() { callbackReference.increment(); }\n", + "}\n", + "public class Callbacks {\n", + " public static void main(String[] args) {\n", + " Callee1 c1 = new Callee1();\n", + " Callee2 c2 = new Callee2();\n", + " MyIncrement.f(c2);\n", + " Caller caller1 = new Caller(c1);\n", + " Caller caller2 =\n", + " new Caller(c2.getCallbackReference());\n", + " caller1.go();\n", + " caller1.go();\n", + " caller2.go();\n", + " caller2.go();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Other operation\n", + "1\n", + "1\n", + "2\n", + "Other operation\n", + "2\n", + "Other operation\n", + "3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。\n", + "\n", + "注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。\n", + "内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。\n", + "\n", + "**Caller** 的构造器需要一个 **Incrementable** 的引用作为参数(虽然可以在任意时刻捕获回调引用),然后在以后的某个时刻,**Caller** 对象可以使用此引用回调 **Callee** 类。\n", + "\n", + "回调的价值在于它的灵活性-可以在运行时动态地决定需要调用什么方法。例如,在图形界面实现 GUI 功能的时候,到处都用到回调。\n", + "\n", + "### 内部类与控制框架\n", + "\n", + "在将要介绍的控制框架(control framework)中,可以看到更多使用内部类的具体例子。\n", + "\n", + "应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题。这是设计模式中模板方法的一个例子,模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。\n", + "\n", + "控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作*事件驱动*系统。应用程序设计中常见的问题之一是图形用户接口(GUI),它几乎完全是事件驱动的系统。\n", + "\n", + "要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。接下来的问题就是,对于要控制什么,控制框架并不包含任何具体的信息。那些信息是在实现算法的 `action()` 部分时,通过继承来提供的。\n", + "\n", + "首先,接口描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。下面的例子包含了某些实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/controller/Event.java\n", + "// The common methods for any control event\n", + "package innerclasses.controller;\n", + "import java.time.*; // Java 8 time classes\n", + "public abstract class Event {\n", + " private Instant eventTime;\n", + " protected final Duration delayTime;\n", + " public Event(long millisecondDelay) {\n", + " delayTime = Duration.ofMillis(millisecondDelay);\n", + " start();\n", + " }\n", + " public void start() { // Allows restarting\n", + " eventTime = Instant.now().plus(delayTime);\n", + " }\n", + " public boolean ready() {\n", + " return Instant.now().isAfter(eventTime);\n", + " }\n", + " public abstract void action();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当希望运行 **Event** 并随后调用 `start()` 时,那么构造器就会捕获(从对象创建的时刻开始的)时间,此时间是这样得来的:`start()` 获取当前时间,然后加上一个延迟时间,这样生成触发事件的时间。`start()` 是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用 **Event** 对象。例如,如果想要重复一个事件,只需简单地在 `action()` 中调用 `start()` 方法。\n", + "\n", + "`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中覆盖 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。\n", + "\n", + "下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\\<**Event**\\> 类型(读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数,foreach 语法用来连续获取 **List** 中的 **Event**,`remove()` 方法用来从 **List** 中移除指定的 **Event**。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/controller/Controller.java\n", + "// The reusable framework for control systems\n", + "package innerclasses.controller;\n", + "import java.util.*;\n", + "public class Controller {\n", + " // A class from java.util to hold Event objects:\n", + " private List eventList = new ArrayList<>();\n", + " public void addEvent(Event c) { eventList.add(c); }\n", + " public void run() {\n", + " while(eventList.size() > 0)\n", + " // Make a copy so you're not modifying the list\n", + " // while you're selecting the elements in it:\n", + " for(Event e : new ArrayList<>(eventList))\n", + " if(e.ready()) {\n", + " System.out.println(e);\n", + " e.action();\n", + " eventList.remove(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`run()` 方法循环遍历 **eventList**,寻找就绪的(`ready()`)、要运行的 **Event** 对象。对找到的每一个就绪的(`ready()`)事件,使用对象的 `toString()` 打印其信息,调用其 `action()` 方法,然后从列表中移除此 **Event**。\n", + "\n", + "注意,在目前的设计中你并不知道 **Event** 到底做了什么。这正是此设计的关键所在—\"使变化的事物与不变的事物相互分离”。用我的话说,“变化向量”就是各种不同的 **Event** 对象所具有的不同行为,而你通过创建不同的 **Event** 子类来表现不同的行为。\n", + "\n", + "这正是内部类要做的事情,内部类允许:\n", + "\n", + "1. 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的 `action()`。\n", + "2. 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。\n", + "\n", + "考虑此控制框架的一个特定实现,如控制温室的运作:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类 **Event** 的多种派生版本。对于温室系统的每一种行为,都继承创建一个新的 **Event** 内部类,并在要实现的 `action()` 中编写控制代码。\n", + "\n", + "作为典型的应用程序框架,**GreenhouseControls** 类继承自 **Controller**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/GreenhouseControls.java\n", + "// This produces a specific application of the\n", + "// control system, all in a single class. Inner\n", + "// classes allow you to encapsulate different\n", + "// functionality for each type of event.\n", + "import innerclasses.controller.*;\n", + "public class GreenhouseControls extends Controller {\n", + " private boolean light = false;\n", + " public class LightOn extends Event {\n", + " public LightOn(long delayTime) {\n", + " super(delayTime); \n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here to\n", + " // physically turn on the light.\n", + " light = true;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Light is on\";\n", + " }\n", + " }\n", + " public class LightOff extends Event {\n", + " public LightOff(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here to\n", + " // physically turn off the light.\n", + " light = false;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Light is off\";\n", + " }\n", + " }\n", + " private boolean water = false;\n", + " public class WaterOn extends Event {\n", + " public WaterOn(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here.\n", + " water = true;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Greenhouse water is on\";\n", + " }\n", + " }\n", + " public class WaterOff extends Event {\n", + " public WaterOff(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here.\n", + " water = false;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Greenhouse water is off\";\n", + " }\n", + " }\n", + " private String thermostat = \"Day\";\n", + " public class ThermostatNight extends Event {\n", + " public ThermostatNight(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here.\n", + " thermostat = \"Night\";\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Thermostat on night setting\";\n", + " }\n", + " }\n", + " public class ThermostatDay extends Event {\n", + " public ThermostatDay(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " // Put hardware control code here.\n", + " thermostat = \"Day\";\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Thermostat on day setting\";\n", + " }\n", + " }\n", + " // An example of an action() that inserts a\n", + " // new one of itself into the event list:\n", + " public class Bell extends Event {\n", + " public Bell(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " addEvent(new Bell(delayTime.toMillis()));\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Bing!\";\n", + " }\n", + " }\n", + " public class Restart extends Event {\n", + " private Event[] eventList;\n", + " public\n", + " Restart(long delayTime, Event[] eventList) {\n", + " super(delayTime);\n", + " this.eventList = eventList;\n", + " for(Event e : eventList)\n", + " addEvent(e);\n", + " }\n", + " @Override\n", + " public void action() {\n", + " for(Event e : eventList) {\n", + " e.start(); // Rerun each event\n", + " addEvent(e);\n", + " }\n", + " start(); // Rerun this Event\n", + " addEvent(this);\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Restarting system\";\n", + " }\n", + " }\n", + " public static class Terminate extends Event {\n", + " public Terminate(long delayTime) {\n", + " super(delayTime);\n", + " }\n", + " @Override\n", + " public void action() { System.exit(0); }\n", + " @Override\n", + " public String toString() {\n", + " return \"Terminating\";\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,**light**,**water** 和 **thermostat** 都属于外围类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。\n", + "\n", + "大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外围类 **GreenhouseContrlos** 的所有方法。\n", + "\n", + "一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。\n", + "\n", + "下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是命令设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/GreenhouseController.java\n", + "// Configure and execute the greenhouse system\n", + "import innerclasses.controller.*;\n", + "public class GreenhouseController {\n", + " public static void main(String[] args) {\n", + " GreenhouseControls gc = new GreenhouseControls();\n", + " // Instead of using code, you could parse\n", + " // configuration information from a text file:\n", + " gc.addEvent(gc.new Bell(900));\n", + " Event[] eventList = {\n", + " gc.new ThermostatNight(0),\n", + " gc.new LightOn(200),\n", + " gc.new LightOff(400),\n", + " gc.new WaterOn(600),\n", + " gc.new WaterOff(800),\n", + " gc.new ThermostatDay(1400)\n", + " };\n", + " gc.addEvent(gc.new Restart(2000, eventList));\n", + " gc.addEvent(\n", + " new GreenhouseControls.Terminate(5000));\n", + " gc.run();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Thermostat on night setting\n", + "Light is on\n", + "Light is off\n", + "Greenhouse water is on\n", + "Greenhouse water is off\n", + "Bing!\n", + "Thermostat on day setting\n", + "Bing!\n", + "Restarting system\n", + "Thermostat on night setting\n", + "Light is on\n", + "Light is off\n", + "Greenhouse water is on\n", + "Bing!\n", + "Greenhouse water is off\n", + "Thermostat on day setting\n", + "Bing!\n", + "Restarting system\n", + "Thermostat on night setting\n", + "Light is on\n", + "Light is off\n", + "Bing!\n", + "Greenhouse water is on\n", + "Greenhouse water is off\n", + "Terminating" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个类的作用是初始化系统,所以它添加了所有相应的事件。**Restart** 事件反复运行,而且它每次都会将 **eventList** 加载到 **GreenhouseControls** 对象中。如果提供了命令行参数,系统会以它作为毫秒数,决定什么时候终止程序(这是测试程序时使用的)。\n", + "\n", + "当然,更灵活的方法是避免对事件进行硬编码。\n", + "\n", + "这个例子应该使读者更了解内部类的价值了,特别是在控制框架中使用内部类的时候。\n", + "\n", + "\n", + "\n", + "## 继承内部类\n", + "\n", + "因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/InheritInner.java\n", + "// Inheriting an inner class\n", + "class WithInner {\n", + " class Inner {}\n", + "}\n", + "public class InheritInner extends WithInner.Inner {\n", + " //- InheritInner() {} // Won't compile\n", + " InheritInner(WithInner wi) {\n", + " wi.super();\n", + " }\n", + " public static void main(String[] args) {\n", + " WithInner wi = new WithInner();\n", + " InheritInner ii = new InheritInner(wi);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,**InheritInner** 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "enclosingClassReference.super();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这样才提供了必要的引用,然后程序才能编译通过。\n", + "\n", + "\n", + "\n", + "## 内部类可以被覆盖么?\n", + "\n", + "如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外围类的一个方法,其实并不起什么作用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/BigEgg.java\n", + "// An inner class cannot be overridden like a method\n", + "class Egg {\n", + " private Yolk y;\n", + " protected class Yolk {\n", + " public Yolk() {\n", + " System.out.println(\"Egg.Yolk()\");\n", + " }\n", + " }\n", + " Egg() {\n", + " System.out.println(\"New Egg()\");\n", + " y = new Yolk();\n", + " }\n", + "}\n", + "public class BigEgg extends Egg {\n", + " public class Yolk {\n", + " public Yolk() {\n", + " System.out.println(\"BigEgg.Yolk()\");\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " new BigEgg();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "New Egg()\n", + "Egg.Yolk()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。\n", + "\n", + "这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/BigEgg2.java\n", + "// Proper inheritance of an inner class\n", + "class Egg2 {\n", + " protected class Yolk {\n", + " public Yolk() {\n", + " System.out.println(\"Egg2.Yolk()\");\n", + " }\n", + " public void f() {\n", + " System.out.println(\"Egg2.Yolk.f()\");\n", + " }\n", + " }\n", + " private Yolk y = new Yolk();\n", + " Egg2() { System.out.println(\"New Egg2()\"); }\n", + " public void insertYolk(Yolk yy) { y = yy; }\n", + " public void g() { y.f(); }\n", + "}\n", + "public class BigEgg2 extends Egg2 {\n", + " public class Yolk extends Egg2.Yolk {\n", + " public Yolk() {\n", + " System.out.println(\"BigEgg2.Yolk()\");\n", + " }\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"BigEgg2.Yolk.f()\");\n", + " }\n", + " }\n", + " public BigEgg2() { insertYolk(new Yolk()); }\n", + " public static void main(String[] args) {\n", + " Egg2 e2 = new BigEgg2();\n", + " e2.g();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Egg2.Yolk()\n", + "New Egg2()\n", + "Egg2.Yolk()\n", + "BigEgg2.Yolk()\n", + "BigEgg2.Yolk.f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且覆盖了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,覆盖后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。\n", + "\n", + "\n", + "\n", + "## 局部内部类\n", + "\n", + "前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// innerclasses/LocalInnerClass.java\n", + "// Holds a sequence of Objects\n", + "interface Counter {\n", + " int next();\n", + "}\n", + "public class LocalInnerClass {\n", + " private int count = 0;\n", + " Counter getCounter(final String name) {\n", + " // A local inner class:\n", + " class LocalCounter implements Counter {\n", + " LocalCounter() {\n", + " // Local inner class can have a constructor\n", + " System.out.println(\"LocalCounter()\");\n", + " }\n", + " @Override\n", + " public int next() {\n", + " System.out.print(name); // Access local final\n", + " return count++;\n", + " }\n", + " }\n", + " return new LocalCounter();\n", + " }\n", + " // Repeat, but with an anonymous inner class:\n", + " Counter getCounter2(final String name) {\n", + " return new Counter() {\n", + " // Anonymous inner class cannot have a named\n", + " // constructor, only an instance initializer:\n", + " {\n", + " System.out.println(\"Counter()\");\n", + " }\n", + " @Override\n", + " public int next() {\n", + " System.out.print(name); // Access local final\n", + " return count++;\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " LocalInnerClass lic = new LocalInnerClass();\n", + " Counter\n", + " c1 = lic.getCounter(\"Local inner \"),\n", + " c2 = lic.getCounter2(\"Anonymous inner \");\n", + " for(int i = 0; i < 5; i++)\n", + " System.out.println(c1.next());\n", + " for(int i = 0; i < 5; i++)\n", + " System.out.println(c2.next());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "LocalCounter()\n", + "Counter()\n", + "Local inner 0\n", + "Local inner 1\n", + "Local inner 2\n", + "Local inner 3\n", + "Local inner 4\n", + "Anonymous inner 5\n", + "Anonymous inner 6\n", + "Anonymous inner 7\n", + "Anonymous inner 8\n", + "Anonymous inner 9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Counter** 返回的是序列中的下一个值。我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。\n", + "\n", + "所以使用局部内部类而不使用匿名内部类的另一个理由就是,需要不止一个该内部类的对象。\n", + "\n", + "\n", + "\n", + "## 内部类标识符\n", + "\n", + "由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个\"meta-class\",叫做 **Class** 对象)。\n", + "\n", + "你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“**$**\",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Counter.class\n", + "LocalInnerClass$1.class\n", + "LocalInnerClass$LocalCounter.class\n", + "LocalInnerClass.class" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“**$**”的后面。\n", + "\n", + "虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。)\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,同样能够解决 C++ 中的用多重继承所能解决的问题。然而,多重继承在 C++ 中被证明是相当难以使用的,相比较而言,Java 的接口和内部类就容易理解多了。\n", + "\n", + "虽然这些特性本身是相当直观的,但是就像多态机制一样,这些特性的使用应该是设计阶段考虑的问题。随着时间的推移,读者将能够更好地识别什么情况下应该使用接口,什么情况使用内部类,或者两者同时使用。但此时,读者至少应该已经完全理解了它们的语法和语义。\n", + "\n", + "当读者见到这些语言特性的实际应用时,就能最终理解它们了。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/12-Collections.ipynb b/jupyter/12-Collections.ipynb new file mode 100644 index 00000000..c2141709 --- /dev/null +++ b/jupyter/12-Collections.ipynb @@ -0,0 +1,2589 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十二章 集合\n", + "\n", + "> 如果一个程序只包含固定数量的对象且对象的生命周期都是已知的,那么这是一个非常简单的程序。\n", + "\n", + "通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。因此,不能依靠创建命名的引用来持有每一个对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "MyType aReference;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为从来不会知道实际需要多少个这样的引用。\n", + "\n", + "大多数编程语言都提供了某种方法来解决这个基本问题。Java有多种方式保存对象(确切地说,是对象的引用)。例如前边曾经学习过的数组,它是编译器支持的类型。数组是保存一组对象的最有效的方式,如果想要保存一组基本类型数据,也推荐使用数组。但是数组具有固定的大小尺寸,而且在更一般的情况下,在写程序的时候并不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制就显得太过受限了。\n", + "\n", + "**java.util** 库提供了一套相当完整的*集合类*(collection classes)来解决这个问题,其中基本的类型有 **List** 、 **Set** 、 **Queue** 和 **Map**。这些类型也被称作*容器类*(container classes),但我将使用Java类库使用的术语。集合提供了完善的方法来保存对象,可以使用这些工具来解决大量的问题。\n", + "\n", + "集合还有一些其它特性。例如, **Set** 对于每个值都只保存一个对象, **Map** 是一个关联数组,允许将某些对象与其他对象关联起来。Java集合类都可以自动地调整自己的大小。因此,与数组不同,在编程时,可以将任意数量的对象放置在集合中,而不用关心集合应该有多大。\n", + "\n", + "尽管在 Java 中没有直接的关键字支持,[^1]但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。\n", + "\n", + "\n", + "## 泛型和类型安全的集合\n", + "\n", + "使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 `add()` 插入对象;然后用 `get()` 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 `size()` 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。\n", + "\n", + "在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 `@SuppressWarning` 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ApplesAndOrangesWithoutGenerics.java\n", + "// Simple collection use (suppressing compiler warnings)\n", + "// {ThrowsException}\n", + "import java.util.*;\n", + "\n", + "class Apple {\n", + " private static long counter;\n", + " private final long id = counter++;\n", + " public long id() { return id; }\n", + "}\n", + "\n", + "class Orange {}\n", + "\n", + "public class ApplesAndOrangesWithoutGenerics {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " ArrayList apples = new ArrayList();\n", + " for(int i = 0; i < 3; i++)\n", + " apples.add(new Apple());\n", + " // No problem adding an Orange to apples:\n", + " apples.add(new Orange());\n", + " for(Object apple : apples) {\n", + " ((Apple) apple).id();\n", + " // Orange is detected only at run time\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"main\"\n", + "java.lang.ClassCastException: Orange cannot be cast to\n", + "Apple\n", + " at ApplesAndOrangesWithoutGenerics.main(ApplesA\n", + "ndOrangesWithoutGenerics.java:23)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 `add()` 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 `get()` 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 `id()` 方法之前,强制执行转型。否则,将会产生语法错误。\n", + "\n", + "在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。\n", + "\n", + "在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。\n", + "\n", + "通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ApplesAndOrangesWithGenerics.java\n", + "import java.util.*;\n", + "\n", + "public class ApplesAndOrangesWithGenerics {\n", + " public static void main(String[] args) {\n", + " ArrayList apples = new ArrayList<>();\n", + " for(int i = 0; i < 3; i++)\n", + " apples.add(new Apple());\n", + " // Compile-time error:\n", + " // apples.add(new Orange());\n", + " for(Apple apple : apples) {\n", + " System.out.println(apple.id());\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "0\n", + "1\n", + "2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "ArrayList apples = new ArrayList();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。虽然*类型推断*(type inference)只是个很小的请求,Java 语言团队仍然欣然接受并进行了改进。\n", + "\n", + "有了 **ArrayList** 声明中的类型指定,编译器会阻止将 **Orange** 放入 **apples** ,因此,这会成为一个编译期错误而不是运行时错误。\n", + "\n", + "使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。\n", + "\n", + "当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/GenericsAndUpcasting.java\n", + "import java.util.*;\n", + "\n", + "class GrannySmith extends Apple {}\n", + "class Gala extends Apple {}\n", + "class Fuji extends Apple {}\n", + "class Braeburn extends Apple {}\n", + "\n", + "public class GenericsAndUpcasting {\n", + " public static void main(String[] args) {\n", + " ArrayList apples = new ArrayList<>();\n", + " apples.add(new GrannySmith());\n", + " apples.add(new Gala());\n", + " apples.add(new Fuji());\n", + " apples.add(new Braeburn());\n", + " for(Apple apple : apples)\n", + " System.out.println(apple);\n", + " }\n", + "}\n", + "/* Output:\n", + "GrannySmith@15db9742\n", + "Gala@6d06d69c\n", + "Fuji@7852e922\n", + "Braeburn@4e25154f\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,可以将 **Apple** 的子类型添加到被指定为保存 **Apple** 对象的集合中。\n", + "\n", + "程序的输出是从 **Object** 默认的 `toString()` 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 `hashCode()` 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。\n", + "\n", + "\n", + "## 基本概念\n", + "\n", + "Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:\n", + "\n", + "1. **集合(Collection)** :一个独立元素的序列,这些元素都服从一条或多条规则。**List** 必须以插入的顺序保存元素, **Set** 不能包含重复元素, **Queue** 按照*排队规则*来确定对象产生的顺序(通常与它们被插入的顺序相同)。\n", + "2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map** 允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map** 是强大的编程工具。\n", + "\n", + "尽管并非总是可行,但在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 **List** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "List apples = new ArrayList<>();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意, **ArrayList** 已经被向上转型为了 **List** ,这与之前示例中的处理方式正好相反。使用接口的目的是,如果想要改变具体实现,只需在创建时修改它就行了,就像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "List apples = new LinkedList<>();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,应该创建一个具体类的对象,将其向上转型为对应的接口,然后在其余代码中都是用这个接口。\n", + "\n", + "这种方式并非总是有效的,因为某些具体类有额外的功能。例如, **LinkedList** 具有 **List** 接口中未包含的额外方法,而 **TreeMap** 也具有在 **Map** 接口中未包含的方法。如果需要使用这些方法,就不能将它们向上转型为更通用的接口。\n", + "\n", + "**Collection** 接口概括了*序列*的概念——一种存放一组对象的方式。下面是个简单的示例,用 **Integer** 对象填充了一个 **Collection** (这里用 **ArrayList** 表示),然后打印集合中的每个元素:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SimpleCollection.java\n", + "import java.util.*;\n", + "\n", + "public class SimpleCollection {\n", + " public static void main(String[] args) {\n", + " Collection c = new ArrayList<>();\n", + " for(int i = 0; i < 10; i++)\n", + " c.add(i); // Autoboxing\n", + " for(Integer i : c)\n", + " System.out.print(i + \", \");\n", + " }\n", + "}\n", + "/* Output:\n", + "0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个例子仅使用了 **Collection** 中的方法(即 `add()` ),所以使用任何继承自 **Collection** 的类的对象都可以正常工作。但是 **ArrayList** 是最基本的序列类型。\n", + "\n", + "`add()` 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 `add()` “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,`add()` 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。\n", + "\n", + "可以使用 *for-in* 语法来遍历所有的 **Collection** ,就像这里所展示的那样。在本章的后续部分,还将学习到一个更灵活的概念,*迭代器*。\n", + "\n", + "\n", + "\n", + "## 添加元素组\n", + "\n", + "在 **java.util** 包中的 **Arrays** 和 **Collections** 类中都有很多实用的方法,可以在一个 **Collection** 中添加一组元素。\n", + "\n", + "`Arrays.asList()` 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 `Collections.addAll()` 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 、所有 **Collection** 类型都包含的`addAll()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/AddingGroups.java\n", + "// Adding groups of elements to Collection objects\n", + "import java.util.*;\n", + "\n", + "public class AddingGroups {\n", + " public static void main(String[] args) {\n", + " Collection collection =\n", + " new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));\n", + " Integer[] moreInts = { 6, 7, 8, 9, 10 };\n", + " collection.addAll(Arrays.asList(moreInts));\n", + " // Runs significantly faster, but you can't\n", + " // construct a Collection this way:\n", + " Collections.addAll(collection, 11, 12, 13, 14, 15);\n", + " Collections.addAll(collection, moreInts);\n", + " // Produces a list \"backed by\" an array:\n", + " List list = Arrays.asList(16,17,18,19,20);\n", + " list.set(1, 99); // OK -- modify an element\n", + " // list.add(21); // Runtime error; the underlying\n", + " // array cannot be resized.\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 `Arrays.asList()` 来为这个构造器产生输入。但是, `Collections.addAll()` 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 `Collections.addAll()` ,因此这是首选方式。\n", + "\n", + "`Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。\n", + "\n", + "也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `remove()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/AsListInference.java\n", + "import java.util.*;\n", + "\n", + "class Snow {}\n", + "class Powder extends Snow {}\n", + "class Light extends Powder {}\n", + "class Heavy extends Powder {}\n", + "class Crusty extends Snow {}\n", + "class Slush extends Snow {}\n", + "\n", + "public class AsListInference {\n", + " public static void main(String[] args) {\n", + " List snow1 = Arrays.asList(\n", + " new Crusty(), new Slush(), new Powder());\n", + " //- snow1.add(new Heavy()); // Exception\n", + "\n", + " List snow2 = Arrays.asList(\n", + " new Light(), new Heavy());\n", + " //- snow2.add(new Slush()); // Exception\n", + "\n", + " List snow3 = new ArrayList<>();\n", + " Collections.addAll(snow3,\n", + " new Light(), new Heavy(), new Powder());\n", + " snow3.add(new Crusty());\n", + "\n", + " // Hint with explicit type argument specification:\n", + " List snow4 = Arrays.asList(\n", + " new Light(), new Heavy(), new Slush());\n", + " //- snow4.add(new Powder()); // Exception\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **snow4** 中,注意 `Arrays.asList()` 中间的“暗示”(即 `` ),告诉编译器 `Arrays.asList()` 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。\n", + "\n", + "\n", + "## 集合的打印\n", + "\n", + "必须使用 `Arrays.toString()` 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/PrintingCollections.java\n", + "// Collections print themselves automatically\n", + "import java.util.*;\n", + "\n", + "public class PrintingCollections {\n", + " static Collection\n", + " fill(Collection collection) {\n", + " collection.add(\"rat\");\n", + " collection.add(\"cat\");\n", + " collection.add(\"dog\");\n", + " collection.add(\"dog\");\n", + " return collection;\n", + " }\n", + " static Map fill(Map map) {\n", + " map.put(\"rat\", \"Fuzzy\");\n", + " map.put(\"cat\", \"Rags\");\n", + " map.put(\"dog\", \"Bosco\");\n", + " map.put(\"dog\", \"Spot\");\n", + " return map;\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(fill(new ArrayList<>()));\n", + " System.out.println(fill(new LinkedList<>()));\n", + " System.out.println(fill(new HashSet<>()));\n", + " System.out.println(fill(new TreeSet<>()));\n", + " System.out.println(fill(new LinkedHashSet<>()));\n", + " System.out.println(fill(new HashMap<>()));\n", + " System.out.println(fill(new TreeMap<>()));\n", + " System.out.println(fill(new LinkedHashMap<>()));\n", + " }\n", + "}\n", + "/* Output:\n", + "[rat, cat, dog, dog]\n", + "[rat, cat, dog, dog]\n", + "[rat, cat, dog]\n", + "[cat, dog, rat]\n", + "[rat, cat, dog]\n", + "{rat=Fuzzy, cat=Rags, dog=Spot}\n", + "{cat=Rags, dog=Spot, rat=Fuzzy}\n", + "{rat=Fuzzy, cat=Rags, dog=Spot}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。\n", + "\n", + "默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。\n", + "\n", + "第一个 `fill()` 方法适用于所有类型的 **Collection** ,这些类型都实现了 `add()` 方法以添加新元素。\n", + "\n", + "**ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。\n", + "\n", + "**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。\n", + "\n", + "**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。\n", + "\n", + "`Map.put(key, value)` 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 `Map.get(key)` 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。\n", + "\n", + "请注意,这里没有指定(或考虑) **Map** 的大小,因为它会自动调整大小。 此外, **Map** 还知道如何打印自己,它会显示相关联的键和值。\n", + "\n", + "本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。\n", + "\n", + "键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。\n", + "\n", + "\n", + "\n", + "## 列表List\n", + "\n", + "**List**s承诺将元素保存在特定的序列中。 **List** 接口在 **Collection** 的基础上添加了许多方法,允许在 **List** 的中间插入和删除元素。\n", + "\n", + "有两种类型的 **List** :\n", + "\n", + "- 基本的 **ArrayList** ,擅长随机访问元素,但在 **List** 中间插入和删除元素时速度较慢。\n", + "- **LinkedList** ,它通过代价较低的在 **List** 中间进行的插入和删除操作,提供了优化的顺序访问。 **LinkedList** 对于随机访问来说相对较慢,但它具有比 **ArrayList** 更大的特征集。\n", + "\n", + "下面的示例导入 **typeinfo.pets** ,超前使用了[类型信息]()一章中的类库。这个类库包含了 **Pet** 类层次结构,以及用于随机生成 **Pet** 对象的一些工具类。此时不需要了解完整的详细信息,只需要知道两点:\n", + "\n", + "1. 有一个 **Pet** 类,以及 **Pet** 的各种子类型。\n", + "2. 静态的 `Pets.arrayList()` 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ListFeatures.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class ListFeatures {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " List pets = Pets.list(7);\n", + " System.out.println(\"1: \" + pets);\n", + " Hamster h = new Hamster();\n", + " pets.add(h); // Automatically resizes\n", + " System.out.println(\"2: \" + pets);\n", + " System.out.println(\"3: \" + pets.contains(h));\n", + " pets.remove(h); // Remove by object\n", + " Pet p = pets.get(2);\n", + " System.out.println(\n", + " \"4: \" + p + \" \" + pets.indexOf(p));\n", + " Pet cymric = new Cymric();\n", + " System.out.println(\"5: \" + pets.indexOf(cymric));\n", + " System.out.println(\"6: \" + pets.remove(cymric));\n", + " // Must be the exact object:\n", + " System.out.println(\"7: \" + pets.remove(p));\n", + " System.out.println(\"8: \" + pets);\n", + " pets.add(3, new Mouse()); // Insert at an index\n", + " System.out.println(\"9: \" + pets);\n", + " List sub = pets.subList(1, 4);\n", + " System.out.println(\"subList: \" + sub);\n", + " System.out.println(\"10: \" + pets.containsAll(sub));\n", + " Collections.sort(sub); // In-place sort\n", + " System.out.println(\"sorted subList: \" + sub);\n", + " // Order is not important in containsAll():\n", + " System.out.println(\"11: \" + pets.containsAll(sub));\n", + " Collections.shuffle(sub, rand); // Mix it up\n", + " System.out.println(\"shuffled subList: \" + sub);\n", + " System.out.println(\"12: \" + pets.containsAll(sub));\n", + " List copy = new ArrayList<>(pets);\n", + " sub = Arrays.asList(pets.get(1), pets.get(4));\n", + " System.out.println(\"sub: \" + sub);\n", + " copy.retainAll(sub);\n", + " System.out.println(\"13: \" + copy);\n", + " copy = new ArrayList<>(pets); // Get a fresh copy\n", + " copy.remove(2); // Remove by index\n", + " System.out.println(\"14: \" + copy);\n", + " copy.removeAll(sub); // Only removes exact objects\n", + " System.out.println(\"15: \" + copy);\n", + " copy.set(1, new Mouse()); // Replace an element\n", + " System.out.println(\"16: \" + copy);\n", + " copy.addAll(2, sub); // Insert a list in the middle\n", + " System.out.println(\"17: \" + copy);\n", + " System.out.println(\"18: \" + pets.isEmpty());\n", + " pets.clear(); // Remove all elements\n", + " System.out.println(\"19: \" + pets);\n", + " System.out.println(\"20: \" + pets.isEmpty());\n", + " pets.addAll(Pets.list(4));\n", + " System.out.println(\"21: \" + pets);\n", + " Object[] o = pets.toArray();\n", + " System.out.println(\"22: \" + o[3]);\n", + " Pet[] pa = pets.toArray(new Pet[0]);\n", + " System.out.println(\"23: \" + pa[3].id());\n", + " }\n", + "}\n", + "/* Output:\n", + "1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]\n", + "2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]\n", + "3: true\n", + "4: Cymric 2\n", + "5: -1\n", + "6: false\n", + "7: true\n", + "8: [Rat, Manx, Mutt, Pug, Cymric, Pug]\n", + "9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug]\n", + "subList: [Manx, Mutt, Mouse]\n", + "10: true\n", + "sorted subList: [Manx, Mouse, Mutt]\n", + "11: true\n", + "shuffled subList: [Mouse, Manx, Mutt]\n", + "12: true\n", + "sub: [Mouse, Pug]\n", + "13: [Mouse, Pug]\n", + "14: [Rat, Mouse, Mutt, Pug, Cymric, Pug]\n", + "15: [Rat, Mutt, Cymric, Pug]\n", + "16: [Rat, Mouse, Cymric, Pug]\n", + "17: [Rat, Mouse, Mouse, Pug, Cymric, Pug]\n", + "18: false\n", + "19: []\n", + "20: true\n", + "21: [Manx, Cymric, Rat, EgyptianMau]\n", + "22: EgyptianMau\n", + "23: 14\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "打印行都编了号,因此可从输出追溯到源代码。 第 1 行输出展示了原始的由 **Pet** 组成的 **List** 。 与数组不同, **List** 可以在创建后添加或删除元素,并自行调整大小。这正是它的重要价值:一种可修改的序列。在第 2 行输出中可以看到添加一个 **Hamster** 的结果,该对象将被追加到列表的末尾。\n", + "\n", + "可以使用 `contains()` 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 `remove()` 方法。同样,如果有一个对象的引用,可以使用 `indexOf()` 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。\n", + "\n", + "当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 `equals()` 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 `indexOf()` 方法,结果仍为 **-1** (表示未找到),并且尝试调用 `remove()` 方法来删除这个对象将返回 **false** 。对于其他类, `equals()` 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 `equals()` 行为而发生变化。\n", + "\n", + "第 7、8 行输出展示了删除与 **List** 中的对象完全匹配的对象是成功的。\n", + "\n", + "可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。\n", + "\n", + "`subList()` 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 `containsAll()` 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 `Collections.sort()` 和 `Collections.shuffle()` 方法,不会影响 `containsAll()` 的结果。 `subList()` 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。\n", + "\n", + "`retainAll()` 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 `equals()` 方法。\n", + "\n", + "第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 `equals()` 的行为。\n", + "\n", + "`removeAll()` 方法也是基于 `equals()` 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。\n", + "\n", + "`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。\n", + "\n", + "第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间位置,而不是仅能用 **Collection** 的 `addAll()` 方法将其追加到列表的末尾。\n", + "\n", + "第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。\n", + "\n", + "第 22、23 行输出展示了如何使用 `toArray()` 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 `toArray()` 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 `id()` 方法,可以在所产生的数组中的对象上调用这个方法。\n", + "\n", + "\n", + "\n", + "## 迭代器Iterators\n", + "\n", + "在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , `add()` 是插入元素的一种方式, `get()` 是获取元素的一种方式。\n", + "\n", + "如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 **List** 编码的,但是后来发现如果能够将相同的代码应用于 **Set** 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?\n", + "\n", + "*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:\n", + "\n", + "1. 使用 `iterator()` 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。\n", + "2. 使用 `next()` 方法获得序列中的下一个元素。\n", + "3. 使用 `hasNext()` 方法检查序列中是否还有元素。\n", + "4. 使用 `remove()` 方法将迭代器最近返回的那个元素删除。\n", + "\n", + "为了观察它的工作方式,这里再次使用[类型信息]()章节中的 **Pet** 工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SimpleIteration.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class SimpleIteration {\n", + " public static void main(String[] args) {\n", + " List pets = Pets.list(12);\n", + " Iterator it = pets.iterator();\n", + " while(it.hasNext()) {\n", + " Pet p = it.next();\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " }\n", + " System.out.println();\n", + " // A simpler approach, when possible:\n", + " for(Pet p : pets)\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " System.out.println();\n", + " // An Iterator can also remove elements:\n", + " it = pets.iterator();\n", + " for(int i = 0; i < 6; i++) {\n", + " it.next();\n", + " it.remove();\n", + " }\n", + " System.out.println(pets);\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster\n", + "[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 `hasNext()` 和 `next()` 关心的事情。\n", + "\n", + "如果只是想向前遍历 **List** ,并不打算修改 **List** 对象本身,那么使用 *for-in* 语法更加简洁。\n", + "\n", + "**Iterator** 还可以删除由 `next()` 生成的最后一个元素,这意味着在调用 `remove()` 之前必须先调用 `next()` 。[^4]\n", + "\n", + "在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。\n", + "\n", + "现在考虑创建一个 `display()` 方法,它不必知晓集合的确切类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/CrossCollectionIteration.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class CrossCollectionIteration {\n", + " public static void display(Iterator it) {\n", + " while(it.hasNext()) {\n", + " Pet p = it.next();\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " public static void main(String[] args) {\n", + " List pets = Pets.list(8);\n", + " LinkedList petsLL = new LinkedList<>(pets);\n", + " HashSet petsHS = new HashSet<>(pets);\n", + " TreeSet petsTS = new TreeSet<>(pets);\n", + " display(pets.iterator());\n", + " display(petsLL.iterator());\n", + " display(petsHS.iterator());\n", + " display(petsTS.iterator());\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug\n", + "0:Rat\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`display()` 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。\n", + "\n", + "我们可以使用 **Iterable** 接口生成上一个示例的更简洁版本,该接口描述了“可以产生 **Iterator** 的任何东西”:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/CrossCollectionIteration2.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class CrossCollectionIteration2 {\n", + " public static void display(Iterable ip) {\n", + " Iterator it = ip.iterator();\n", + " while(it.hasNext()) {\n", + " Pet p = it.next();\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " public static void main(String[] args) {\n", + " List pets = Pets.list(8);\n", + " LinkedList petsLL = new LinkedList<>(pets);\n", + " HashSet petsHS = new HashSet<>(pets);\n", + " TreeSet petsTS = new TreeSet<>(pets);\n", + " display(pets);\n", + " display(petsLL);\n", + " display(petsHS);\n", + " display(petsTS);\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug\n", + "0:Rat\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里所有的类都是 **Iterable** ,所以现在对 `display()` 的调用显然更简单。\n", + "\n", + "\n", + "### ListIterator\n", + "\n", + "**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。 **Iterator** 只能向前移动,而 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 `set()` 方法替换它访问过的最近一个元素。可以通过调用 `listIterator()` 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 `listIterator(n)` 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ListIteration.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class ListIteration {\n", + " public static void main(String[] args) {\n", + " List pets = Pets.list(8);\n", + " ListIterator it = pets.listIterator();\n", + " while(it.hasNext())\n", + " System.out.print(it.next() +\n", + " \", \" + it.nextIndex() +\n", + " \", \" + it.previousIndex() + \"; \");\n", + " System.out.println();\n", + " // Backwards:\n", + " while(it.hasPrevious())\n", + " System.out.print(it.previous().id() + \" \");\n", + " System.out.println();\n", + " System.out.println(pets);\n", + " it = pets.listIterator(3);\n", + " while(it.hasNext()) {\n", + " it.next();\n", + " it.set(Pets.get());\n", + " }\n", + " System.out.println(pets);\n", + " }\n", + "}\n", + "/* Output:\n", + "Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug,\n", + "5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;\n", + "7 6 5 4 3 2 1 0\n", + "[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]\n", + "[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster,\n", + "EgyptianMau]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Pets.get()` 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。\n", + "\n", + "\n", + "\n", + "## 链表LinkedList\n", + "\n", + "**LinkedList** 也像 **ArrayList** 一样实现了基本的 **List** 接口,但它在 **List** 中间执行插入和删除操作时比 **ArrayList** 更高效。然而,它在随机访问操作效率方面却要逊色一些。\n", + "\n", + "**LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque)** 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 **Queue** 中)。例如:\n", + "\n", + "- `getFirst()` 和 `element()` 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 `peek()` 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。\n", + "- `removeFirst()` 和 `remove()` 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 `poll()` 稍有差异,它在列表为空时返回 **null** 。\n", + "- `addFirst()` 在列表的开头插入一个元素。\n", + "- `offer()` 与 `add()` 和 `addLast()` 相同。 它们都在列表的尾部(末尾)添加一个元素。\n", + "- `removeLast()` 删除并返回列表的最后一个元素。\n", + "\n", + "下面的示例展示了这些功能之间基本的相似性和差异性。它并不是重复执行 **ListFeatures.java** 中所示的行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/LinkedListFeatures.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class LinkedListFeatures {\n", + " public static void main(String[] args) {\n", + " LinkedList pets =\n", + " new LinkedList<>(Pets.list(5));\n", + " System.out.println(pets);\n", + " // Identical:\n", + " System.out.println(\n", + " \"pets.getFirst(): \" + pets.getFirst());\n", + " System.out.println(\n", + " \"pets.element(): \" + pets.element());\n", + " // Only differs in empty-list behavior:\n", + " System.out.println(\"pets.peek(): \" + pets.peek());\n", + " // Identical; remove and return the first element:\n", + " System.out.println(\n", + " \"pets.remove(): \" + pets.remove());\n", + " System.out.println(\n", + " \"pets.removeFirst(): \" + pets.removeFirst());\n", + " // Only differs in empty-list behavior:\n", + " System.out.println(\"pets.poll(): \" + pets.poll());\n", + " System.out.println(pets);\n", + " pets.addFirst(new Rat());\n", + " System.out.println(\"After addFirst(): \" + pets);\n", + " pets.offer(Pets.get());\n", + " System.out.println(\"After offer(): \" + pets);\n", + " pets.add(Pets.get());\n", + " System.out.println(\"After add(): \" + pets);\n", + " pets.addLast(new Hamster());\n", + " System.out.println(\"After addLast(): \" + pets);\n", + " System.out.println(\n", + " \"pets.removeLast(): \" + pets.removeLast());\n", + " }\n", + "}\n", + "/* Output:\n", + "[Rat, Manx, Cymric, Mutt, Pug]\n", + "pets.getFirst(): Rat\n", + "pets.element(): Rat\n", + "pets.peek(): Rat\n", + "pets.remove(): Rat\n", + "pets.removeFirst(): Manx\n", + "pets.poll(): Cymric\n", + "[Mutt, Pug]\n", + "After addFirst(): [Rat, Mutt, Pug]\n", + "After offer(): [Rat, Mutt, Pug, Cymric]\n", + "After add(): [Rat, Mutt, Pug, Cymric, Pug]\n", + "After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]\n", + "pets.removeLast(): Hamster\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Pets.list()` 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 `element()` , `offer()` , `peek()` , `poll()` 和 `remove()` 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。\n", + "\n", + "\n", + "\n", + "## 堆栈Stack\n", + "\n", + "堆栈是“后进先出”(LIFO)集合。它有时被称为*叠加栈*(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。\n", + "\n", + "Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们永远坚持 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/StackTest.java\n", + "import java.util.*;\n", + "\n", + "public class StackTest {\n", + " public static void main(String[] args) {\n", + " Deque stack = new ArrayDeque<>();\n", + " for(String s : \"My dog has fleas\".split(\" \"))\n", + " stack.push(s);\n", + " while(!stack.isEmpty())\n", + " System.out.print(stack.pop() + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "fleas has dog My\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "即使它是作为一个堆栈在使用,我们仍然必须将其声明为 **Deque** 。有时一个名为 **Stack** 的类更能把事情讲清楚:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Stack.java\n", + "// A Stack class built with an ArrayDeque\n", + "package onjava;\n", + "import java.util.Deque;\n", + "import java.util.ArrayDeque;\n", + "\n", + "public class Stack {\n", + " private Deque storage = new ArrayDeque<>();\n", + " public void push(T v) { storage.push(v); }\n", + " public T peek() { return storage.peek(); }\n", + " public T pop() { return storage.pop(); }\n", + " public boolean isEmpty() { return storage.isEmpty(); }\n", + " @Override\n", + " public String toString() {\n", + " return storage.toString();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。\n", + "\n", + "如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。\n", + "\n", + "下面将使用 **StackTest.java** 中的相同代码来演示这个新的 **Stack** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/StackTest2.java\n", + "import onjava.*;\n", + "\n", + "public class StackTest2 {\n", + " public static void main(String[] args) {\n", + " Stack stack = new Stack<>();\n", + " for(String s : \"My dog has fleas\".split(\" \"))\n", + " stack.push(s);\n", + " while(!stack.isEmpty())\n", + " System.out.print(stack.pop() + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "fleas has dog My\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果想在自己的代码中使用这个 **Stack** 类,当在创建其实例时,就需要完整指定包名,或者更改这个类的名称;否则,就有可能会与 **java.util** 包中的 **Stack** 发生冲突。例如,如果我们在上面的例子中导入 **java.util.***,那么就必须使用包名来防止冲突:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/StackCollision.java\n", + "\n", + "public class StackCollision {\n", + " public static void main(String[] args) {\n", + " onjava.Stack stack = new onjava.Stack<>();\n", + " for(String s : \"My dog has fleas\".split(\" \"))\n", + " stack.push(s);\n", + " while(!stack.isEmpty())\n", + " System.out.print(stack.pop() + \" \");\n", + " System.out.println();\n", + " java.util.Stack stack2 =\n", + " new java.util.Stack<>();\n", + " for(String s : \"My dog has fleas\".split(\" \"))\n", + " stack2.push(s);\n", + " while(!stack2.empty())\n", + " System.out.print(stack2.pop() + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "fleas has dog My\n", + "fleas has dog My\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管已经有了 **java.util.Stack** ,但是 **ArrayDeque** 可以产生更好的 **Stack** ,因此更可取。\n", + "\n", + "还可以使用显式导入来控制对“首选” **Stack** 实现的选择:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "import onjava.Stack;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,任何对 **Stack** 的引用都将选择 **onjava** 版本,而在选择 **java.util.Stack** 时,必须使用全限定名称(full qualification)。\n", + "\n", + "\n", + "## 集合Set\n", + "\n", + "**Set** 不保存重复的元素。 如果试图将相同对象的多个实例添加到 **Set** 中,那么它会阻止这种重复行为。 **Set** 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 **Set** 中。因此,查找通常是 **Set** 最重要的操作,因此通常会选择 **HashSet** 实现,该实现针对快速查找进行了优化。\n", + "\n", + "**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的内容将在[附录:集合主题]()中介绍。\n", + "\n", + "下面是使用存放 **Integer** 对象的 **HashSet** 的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SetOfInteger.java\n", + "import java.util.*;\n", + "\n", + "public class SetOfInteger {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " Set intset = new HashSet<>();\n", + " for(int i = 0; i < 10000; i++)\n", + " intset.add(rand.nextInt(30));\n", + " System.out.println(intset);\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", + "16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 0 到 29 之间的 10000 个随机整数被添加到 **Set** 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。\n", + "\n", + "早期 Java 版本中的 **HashSet** 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 **Integer** 按顺序排序。但是,您不应该依赖此行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SetOfString.java\n", + "import java.util.*;\n", + "\n", + "public class SetOfString {\n", + " public static void main(String[] args) {\n", + " Set colors = new HashSet<>();\n", + " for(int i = 0; i < 100; i++) {\n", + " colors.add(\"Yellow\");\n", + " colors.add(\"Blue\");\n", + " colors.add(\"Red\");\n", + " colors.add(\"Red\");\n", + " colors.add(\"Orange\");\n", + " colors.add(\"Yellow\");\n", + " colors.add(\"Blue\");\n", + " colors.add(\"Purple\");\n", + " }\n", + " System.out.println(colors);\n", + " }\n", + "}\n", + "/* Output:\n", + "[Red, Yellow, Blue, Purple, Orange]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**String** 对象似乎没有排序。要对结果进行排序,一种方法是使用 **TreeSet** 而不是 **HashSet** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SortedSetOfString.java\n", + "import java.util.*;\n", + "\n", + "public class SortedSetOfString {\n", + " public static void main(String[] args) {\n", + " Set colors = new TreeSet<>();\n", + " for(int i = 0; i < 100; i++) {\n", + " colors.add(\"Yellow\");\n", + " colors.add(\"Blue\");\n", + " colors.add(\"Red\");\n", + " colors.add(\"Red\");\n", + " colors.add(\"Orange\");\n", + " colors.add(\"Yellow\");\n", + " colors.add(\"Blue\");\n", + " colors.add(\"Purple\");\n", + " }\n", + " System.out.println(colors);\n", + " }\n", + "}\n", + "/* Output:\n", + "[Blue, Orange, Purple, Red, Yellow]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最常见的操作之一是使用 `contains()` 测试成员归属性,但也有一些其它操作,这可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/SetOperations.java\n", + "import java.util.*;\n", + "\n", + "public class SetOperations {\n", + " public static void main(String[] args) {\n", + " Set set1 = new HashSet<>();\n", + " Collections.addAll(set1,\n", + " \"A B C D E F G H I J K L\".split(\" \"));\n", + " set1.add(\"M\");\n", + " System.out.println(\"H: \" + set1.contains(\"H\"));\n", + " System.out.println(\"N: \" + set1.contains(\"N\"));\n", + " Set set2 = new HashSet<>();\n", + " Collections.addAll(set2, \"H I J K L\".split(\" \"));\n", + " System.out.println(\n", + " \"set2 in set1: \" + set1.containsAll(set2));\n", + " set1.remove(\"H\");\n", + " System.out.println(\"set1: \" + set1);\n", + " System.out.println(\n", + " \"set2 in set1: \" + set1.containsAll(set2));\n", + " set1.removeAll(set2);\n", + " System.out.println(\n", + " \"set2 removed from set1: \" + set1);\n", + " Collections.addAll(set1, \"X Y Z\".split(\" \"));\n", + " System.out.println(\n", + " \"'X Y Z' added to set1: \" + set1);\n", + " }\n", + "}\n", + "/* Output:\n", + "H: true\n", + "N: false\n", + "set2 in set1: true\n", + "set1: [A, B, C, D, E, F, G, I, J, K, L, M]\n", + "set2 in set1: false\n", + "set2 removed from set1: [A, B, C, D, E, F, G, M]\n", + "'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y,\n", + "Z]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些方法名都是自解释的,JDK 文档中还有一些其它的方法。\n", + "\n", + "能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 `java.nio.file.Files.readAllLines()` 方法,可以打开一个文件,并将其作为一个 **List\\** 读取,每个 **String** 都是输入文件中的一行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/UniqueWords.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "\n", + "public class UniqueWords {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " List lines = Files.readAllLines(\n", + " Paths.get(\"SetOperations.java\"));\n", + " Set words = new TreeSet<>();\n", + " for(String line : lines)\n", + " for(String word : line.split(\"\\\\W+\"))\n", + " if(word.trim().length() > 0)\n", + " words.add(word);\n", + " System.out.println(words);\n", + " }\n", + "}\n", + "/* Output:\n", + "[A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K,\n", + "L, M, N, Output, Set, SetOperations, String, System, X,\n", + "Y, Z, add, addAll, added, args, class, collections,\n", + "contains, containsAll, false, from, import, in, java,\n", + "main, new, out, println, public, remove, removeAll,\n", + "removed, set1, set2, split, static, to, true, util,\n", + "void]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们逐步浏览文件中的每一行,并使用 `String.split()` 将其分解为单词,这里使用正则表达式 **\\\\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写和小写字母位于不同的组中。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/UniqueWordsAlphabetic.java\n", + "// Producing an alphabetic listing\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "\n", + "public class UniqueWordsAlphabetic {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " List lines = Files.readAllLines(\n", + " Paths.get(\"SetOperations.java\"));\n", + " Set words =\n", + " new TreeSet<>(String.CASE_INSENSITIVE_ORDER);\n", + " for(String line : lines)\n", + " for(String word : line.split(\"\\\\W+\"))\n", + " if(word.trim().length() > 0)\n", + " words.add(word);\n", + " System.out.println(words);\n", + " }\n", + "}\n", + "/* Output:\n", + "[A, add, addAll, added, args, B, C, class, collections,\n", + "contains, containsAll, D, E, F, false, from, G, H,\n", + "HashSet, I, import, in, J, java, K, L, M, main, N, new,\n", + "out, Output, println, public, remove, removeAll,\n", + "removed, Set, set1, set2, SetOperations, split, static,\n", + "String, System, to, true, util, void, X, Y, Z]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Comparator** 比较器将在[数组]()章节详细介绍。\n", + "\n", + "\n", + "## 映射Map\n", + "\n", + "将对象映射到其他对象的能力是解决编程问题的有效方法。例如,考虑一个程序,它被用来检查 Java 的 **Random** 类的随机性。理想情况下, **Random** 会产生完美的数字分布,但为了测试这一点,则需要生成大量的随机数,并计算落在各种范围内的数字个数。 **Map** 可以很容易地解决这个问题。在本例中,键是 **Random** 生成的数字,而值是该数字出现的次数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/Statistics.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Simple demonstration of HashMap\n", + "import java.util.*;\n", + "\n", + "public class Statistics {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " Map m = new HashMap<>();\n", + " for(int i = 0; i < 10000; i++) {\n", + " // Produce a number between 0 and 20:\n", + " int r = rand.nextInt(20);\n", + " Integer freq = m.get(r); // [1]\n", + " m.put(r, freq == null ? 1 : freq + 1);\n", + " }\n", + " System.out.println(m);\n", + " }\n", + "}\n", + "/* Output:\n", + "{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519,\n", + "7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506,\n", + "14=477, 15=497, 16=533, 17=509, 18=478, 19=464}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 `get()` 返回 **null** (这意味着该数字第一次出现)。否则, `get()` 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。\n", + "\n", + "接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 `containsKey()` 和 `containsValue()` 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/PetMap.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class PetMap {\n", + " public static void main(String[] args) {\n", + " Map petMap = new HashMap<>();\n", + " petMap.put(\"My Cat\", new Cat(\"Molly\"));\n", + " petMap.put(\"My Dog\", new Dog(\"Ginger\"));\n", + " petMap.put(\"My Hamster\", new Hamster(\"Bosco\"));\n", + " System.out.println(petMap);\n", + " Pet dog = petMap.get(\"My Dog\");\n", + " System.out.println(dog);\n", + " System.out.println(petMap.containsKey(\"My Dog\"));\n", + " System.out.println(petMap.containsValue(dog));\n", + " }\n", + "}\n", + "/* Output:\n", + "{My Dog=Dog Ginger, My Cat=Cat Molly, My\n", + "Hamster=Hamster Bosco}\n", + "Dog Ginger\n", + "true\n", + "true\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\\>** 即可:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\n", + "// collections/MapOfList.java\n", + "// {java collections.MapOfList}\n", + "package collections;\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class MapOfList {\n", + " public static final Map>\n", + " petPeople = new HashMap<>();\n", + " static {\n", + " petPeople.put(new Person(\"Dawn\"),\n", + " Arrays.asList(\n", + " new Cymric(\"Molly\"),\n", + " new Mutt(\"Spot\")));\n", + " petPeople.put(new Person(\"Kate\"),\n", + " Arrays.asList(new Cat(\"Shackleton\"),\n", + " new Cat(\"Elsie May\"), new Dog(\"Margrett\")));\n", + " petPeople.put(new Person(\"Marilyn\"),\n", + " Arrays.asList(\n", + " new Pug(\"Louie aka Louis Snorkelstein Dupree\"),\n", + " new Cat(\"Stanford\"),\n", + " new Cat(\"Pinkola\")));\n", + " petPeople.put(new Person(\"Luke\"),\n", + " Arrays.asList(\n", + " new Rat(\"Fuzzy\"), new Rat(\"Fizzy\")));\n", + " petPeople.put(new Person(\"Isaac\"),\n", + " Arrays.asList(new Rat(\"Freckly\")));\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(\"People: \" + petPeople.keySet());\n", + " System.out.println(\"Pets: \" + petPeople.values());\n", + " for(Person person : petPeople.keySet()) {\n", + " System.out.println(person + \" has:\");\n", + " for(Pet pet : petPeople.get(person))\n", + " System.out.println(\" \" + pet);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "People: [Person Dawn, Person Kate, Person Isaac, Person\n", + "Marilyn, Person Luke]\n", + "Pets: [[Cymric Molly, Mutt Spot], [Cat Shackleton, Cat\n", + "Elsie May, Dog Margrett], [Rat Freckly], [Pug Louie aka\n", + "Louis Snorkelstein Dupree, Cat Stanford, Cat Pinkola],\n", + "[Rat Fuzzy, Rat Fizzy]]\n", + "Person Dawn has:\n", + " Cymric Molly\n", + " Mutt Spot\n", + "Person Kate has:\n", + " Cat Shackleton\n", + " Cat Elsie May\n", + " Dog Margrett\n", + "Person Isaac has:\n", + " Rat Freckly\n", + "Person Marilyn has:\n", + " Pug Louie aka Louis Snorkelstein Dupree\n", + " Cat Stanford\n", + " Cat Pinkola\n", + "Person Luke has:\n", + " Rat Fuzzy\n", + " Rat Fizzy\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 `keySet()` 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。\n", + "\n", + "\n", + "\n", + "## 队列Queue\n", + "\n", + "队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在[并发编程]()中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。\n", + "\n", + "**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中与 **Queue** 相关(Queue-specific)的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/QueueDemo.java\n", + "// Upcasting to a Queue from a LinkedList\n", + "import java.util.*;\n", + "\n", + "public class QueueDemo {\n", + " public static void printQ(Queue queue) {\n", + " while(queue.peek() != null)\n", + " System.out.print(queue.remove() + \" \");\n", + " System.out.println();\n", + " }\n", + " public static void main(String[] args) {\n", + " Queue queue = new LinkedList<>();\n", + " Random rand = new Random(47);\n", + " for(int i = 0; i < 10; i++)\n", + " queue.offer(rand.nextInt(i + 10));\n", + " printQ(queue);\n", + " Queue qc = new LinkedList<>();\n", + " for(char c : \"Brontosaurus\".toCharArray())\n", + " qc.offer(c);\n", + " printQ(qc);\n", + " }\n", + "}\n", + "/* Output:\n", + "8 1 1 1 5 14 3 1 0 1\n", + "B r o n t o s a u r u s\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`offer()` 是与 **Queue** 相关的方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 `peek()` 和 `element()` 都返回队头元素而不删除它,但是如果队列为空,则 `element()` 抛出 **NoSuchElementException** ,而 `peek()` 返回 **null** 。 `poll()` 和 `remove()`* 都删除并返回队头元素,但如果队列为空,`poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。\n", + "\n", + "自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。\n", + "\n", + "与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。\n", + "\n", + "\n", + "### 优先级队列PriorityQueue\n", + "\n", + "先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个弹出的元素应该是等待时间最长的元素。\n", + "\n", + "优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。\n", + "\n", + "当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。\n", + "\n", + "让 **PriorityQueue** 与 **Integer** , **String** 和 **Character** 这样的内置类型一起工作易如反掌。在下面的示例中,第一组值与前一个示例中的随机值相同,可以看到它们从 **PriorityQueue** 中弹出的顺序与前一个示例不同:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/PriorityQueueDemo.java\n", + "import java.util.*;\n", + "\n", + "public class PriorityQueueDemo {\n", + " public static void main(String[] args) {\n", + " PriorityQueue priorityQueue =\n", + " new PriorityQueue<>();\n", + " Random rand = new Random(47);\n", + " for(int i = 0; i < 10; i++)\n", + " priorityQueue.offer(rand.nextInt(i + 10));\n", + " QueueDemo.printQ(priorityQueue);\n", + "\n", + " List ints = Arrays.asList(25, 22, 20,\n", + " 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);\n", + " priorityQueue = new PriorityQueue<>(ints);\n", + " QueueDemo.printQ(priorityQueue);\n", + " priorityQueue = new PriorityQueue<>(\n", + " ints.size(), Collections.reverseOrder());\n", + " priorityQueue.addAll(ints);\n", + " QueueDemo.printQ(priorityQueue);\n", + "\n", + " String fact = \"EDUCATION SHOULD ESCHEW OBFUSCATION\";\n", + " List strings =\n", + " Arrays.asList(fact.split(\"\"));\n", + " PriorityQueue stringPQ =\n", + " new PriorityQueue<>(strings);\n", + " QueueDemo.printQ(stringPQ);\n", + " stringPQ = new PriorityQueue<>(\n", + " strings.size(), Collections.reverseOrder());\n", + " stringPQ.addAll(strings);\n", + " QueueDemo.printQ(stringPQ);\n", + "\n", + " Set charSet = new HashSet<>();\n", + " for(char c : fact.toCharArray())\n", + " charSet.add(c); // Autoboxing\n", + " PriorityQueue characterPQ =\n", + " new PriorityQueue<>(charSet);\n", + " QueueDemo.printQ(characterPQ);\n", + " }\n", + "}\n", + "/* Output:\n", + "0 1 1 1 1 1 3 5 8 14\n", + "1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25\n", + "25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1\n", + " A A B C C C D D E E E F H H I I L N N O O O O S S\n", + "S T T U U U W\n", + "W U U U T T S S S O O O O N N L I I H H F E E E D D C C\n", + "C B A A\n", + " A B C D E F H I L N O S T U W\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\\** 构造器的调用,和第二个对 **PriorityQueue\\** 的调用使用了由 `Collections.reverseOrder()` (Java 5 中新添加的)产生的反序的 **Comparator** 。\n", + "\n", + "最后一部分添加了一个 **HashSet** 来消除重复的 **Character**。\n", + "\n", + "**Integer** , **String** 和 **Character** 可以与 **PriorityQueue** 一起使用,因为这些类已经内置了自然排序。如果想在 **PriorityQueue** 中使用自己的类,则必须包含额外的功能以产生自然排序,或者必须提供自己的 **Comparator** 。在[附录:集合主题]()中有一个更复杂的示例来演示这种情况。\n", + "\n", + "\n", + "## 集合与迭代器\n", + "\n", + "**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。\n", + "\n", + "使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 `iterator()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/InterfaceVsIterator.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class InterfaceVsIterator {\n", + " public static void display(Iterator it) {\n", + " while(it.hasNext()) {\n", + " Pet p = it.next();\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " public static void display(Collection pets) {\n", + " for(Pet p : pets)\n", + " System.out.print(p.id() + \":\" + p + \" \");\n", + " System.out.println();\n", + " }\n", + " public static void main(String[] args) {\n", + " List petList = Pets.list(8);\n", + " Set petSet = new HashSet<>(petList);\n", + " Map petMap = new LinkedHashMap<>();\n", + " String[] names = (\"Ralph, Eric, Robin, Lacey, \" +\n", + " \"Britney, Sam, Spot, Fluffy\").split(\", \");\n", + " for(int i = 0; i < names.length; i++)\n", + " petMap.put(names[i], petList.get(i));\n", + " display(petList);\n", + " display(petSet);\n", + " display(petList.iterator());\n", + " display(petSet.iterator());\n", + " System.out.println(petMap);\n", + " System.out.println(petMap.keySet());\n", + " display(petMap.values());\n", + " display(petMap.values().iterator());\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt,\n", + "Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}\n", + "[Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy]\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "两个版本的 `display()` 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 `display()` 方法与低层集合的特定实现解耦。\n", + "\n", + "在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 `display(Collection)` 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。\n", + "\n", + "当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,即使我们不在 `display()` 方法中使用它们,也必须这样做。虽然这可以通过继承 **AbstractCollection** 而很容易地实现,但是无论如何还是要被强制去实现 `iterator()` 和 `size()` 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/CollectionSequence.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class CollectionSequence\n", + "extends AbstractCollection {\n", + " private Pet[] pets = Pets.array(8);\n", + " @Override\n", + " public int size() { return pets.length; }\n", + " @Override\n", + " public Iterator iterator() {\n", + " return new Iterator() { // [1]\n", + " private int index = 0;\n", + " @Override\n", + " public boolean hasNext() {\n", + " return index < pets.length;\n", + " }\n", + " @Override\n", + " public Pet next() { return pets[index++]; }\n", + " @Override\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " CollectionSequence c = new CollectionSequence();\n", + " InterfaceVsIterator.display(c);\n", + " InterfaceVsIterator.display(c.iterator());\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`remove()` 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。\n", + "\n", + "- **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。\n", + "\n", + "这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/NonCollectionSequence.java\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "class PetSequence {\n", + " protected Pet[] pets = Pets.array(8);\n", + "}\n", + "\n", + "public class NonCollectionSequence extends PetSequence {\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " private int index = 0;\n", + " @Override\n", + " public boolean hasNext() {\n", + " return index < pets.length;\n", + " }\n", + " @Override\n", + " public Pet next() { return pets[index++]; }\n", + " @Override\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " NonCollectionSequence nc =\n", + " new NonCollectionSequence();\n", + " InterfaceVsIterator.display(nc.iterator());\n", + " }\n", + "}\n", + "/* Output:\n", + "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", + "7:Manx\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "生成 **Iterator** 是将序列与消费该序列的方法连接在一起耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。\n", + "\n", + "\n", + "## for-in和迭代器\n", + "\n", + "到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是一个更通用的证明:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ForInCollections.java\n", + "// All collections work with for-in\n", + "import java.util.*;\n", + "\n", + "public class ForInCollections {\n", + " public static void main(String[] args) {\n", + " Collection cs = new LinkedList<>();\n", + " Collections.addAll(cs,\n", + " \"Take the long way home\".split(\" \"));\n", + " for(String s : cs)\n", + " System.out.print(\"'\" + s + \"' \");\n", + " }\n", + "}\n", + "/* Output:\n", + "'Take' 'the' 'long' 'way' 'home'\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于 **cs** 是一个 **Collection** ,因此该代码展示了使用 *for-in* 是所有 **Collection** 对象的特征。\n", + "\n", + "这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 `iterator()` 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/IterableClass.java\n", + "// Anything Iterable works with for-in\n", + "import java.util.*;\n", + "\n", + "public class IterableClass implements Iterable {\n", + " protected String[] words = (\"And that is how \" +\n", + " \"we know the Earth to be banana-shaped.\"\n", + " ).split(\" \");\n", + " @Override\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " private int index = 0;\n", + " @Override\n", + " public boolean hasNext() {\n", + " return index < words.length;\n", + " }\n", + " @Override\n", + " public String next() { return words[index++]; }\n", + " @Override\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " for(String s : new IterableClass())\n", + " System.out.print(s + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "And that is how we know the Earth to be banana-shaped.\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`iterator()` 返回的是实现了 **Iterator\\** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在主方法中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。\n", + "\n", + "在 Java 5 中,许多类都是 **Iterable** ,主要包括所有的 **Collection** 类(但不包括各种 **Maps** )。 例如,下面的代码可以显示所有的操作系统环境变量:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/EnvironmentVariables.java\n", + "// {VisuallyInspectOutput}\n", + "import java.util.*;\n", + "\n", + "public class EnvironmentVariables {\n", + " public static void main(String[] args) {\n", + " for(Map.Entry entry: System.getenv().entrySet()) {\n", + " System.out.println(entry.getKey() + \": \" +\n", + " entry.getValue());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`System.getenv()` [^7]返回一个 **Map** , `entrySet()` 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。\n", + "\n", + "*for-in* 语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ArrayIsNotIterable.java\n", + "import java.util.*;\n", + "\n", + "public class ArrayIsNotIterable {\n", + " static void test(Iterable ib) {\n", + " for(T t : ib)\n", + " System.out.print(t + \" \");\n", + " }\n", + " public static void main(String[] args) {\n", + " test(Arrays.asList(1, 2, 3));\n", + " String[] strings = { \"A\", \"B\", \"C\" };\n", + " // An array works in for-in, but it's not Iterable:\n", + " //- test(strings);\n", + " // You must explicitly convert it to an Iterable:\n", + " test(Arrays.asList(strings));\n", + " }\n", + "}\n", + "/* Output:\n", + "1 2 3 A B C\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尝试将数组作为一个 **Iterable** 参数传递会导致失败。这说明不存在任何从数组到 **Iterable** 的自动转换; 必须手工执行这种转换。\n", + "\n", + "\n", + "### 适配器方法惯用法\n", + "\n", + "如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。\n", + "\n", + "一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。\n", + "在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/AdapterMethodIdiom.java\n", + "// The \"Adapter Method\" idiom uses for-in\n", + "// with additional kinds of Iterables\n", + "import java.util.*;\n", + "\n", + "class ReversibleArrayList extends ArrayList {\n", + " ReversibleArrayList(Collection c) {\n", + " super(c);\n", + " }\n", + " public Iterable reversed() {\n", + " return new Iterable() {\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " int current = size() - 1;\n", + " public boolean hasNext() {\n", + " return current > -1;\n", + " }\n", + " public T next() { return get(current--); }\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " };\n", + " }\n", + "}\n", + "\n", + "public class AdapterMethodIdiom {\n", + " public static void main(String[] args) {\n", + " ReversibleArrayList ral =\n", + " new ReversibleArrayList(\n", + " Arrays.asList(\"To be or not to be\".split(\" \")));\n", + " // Grabs the ordinary iterator via iterator():\n", + " for(String s : ral)\n", + " System.out.print(s + \" \");\n", + " System.out.println();\n", + " // Hand it the Iterable of your choice\n", + " for(String s : ral.reversed())\n", + " System.out.print(s + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "To be or not to be\n", + "be to not or be To\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在主方法中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 `reversed()` 方法,它会产生不同的行为。\n", + "\n", + "通过使用这种方式,可以在 **IterableClass.java** 示例中添加两种适配器方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/MultiIterableClass.java\n", + "// Adding several Adapter Methods\n", + "import java.util.*;\n", + "\n", + "public class MultiIterableClass extends IterableClass {\n", + " public Iterable reversed() {\n", + " return new Iterable() {\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " int current = words.length - 1;\n", + " public boolean hasNext() {\n", + " return current > -1;\n", + " }\n", + " public String next() {\n", + " return words[current--];\n", + " }\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " };\n", + " }\n", + " public Iterable randomized() {\n", + " return new Iterable() {\n", + " public Iterator iterator() {\n", + " List shuffled =\n", + " new ArrayList(Arrays.asList(words));\n", + " Collections.shuffle(shuffled, new Random(47));\n", + " return shuffled.iterator();\n", + " }\n", + " };\n", + " }\n", + " public static void main(String[] args) {\n", + " MultiIterableClass mic = new MultiIterableClass();\n", + " for(String s : mic.reversed())\n", + " System.out.print(s + \" \");\n", + " System.out.println();\n", + " for(String s : mic.randomized())\n", + " System.out.print(s + \" \");\n", + " System.out.println();\n", + " for(String s : mic)\n", + " System.out.print(s + \" \");\n", + " }\n", + "}\n", + "/* Output:\n", + "banana-shaped. be to Earth the know we how is that And\n", + "is banana-shaped. Earth that how the be And we know to\n", + "And that is how we know the Earth to be banana-shaped.\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,第二个方法 `random()` 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。\n", + "\n", + "从输出中可以看到, `Collections.shuffle()` 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 `randomized()` 方法用一个 **ArrayList** 将 `Arrays.asList()` 的结果包装了起来。如果这个由 `Arrays.asList()` 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/ModifyingArraysAsList.java\n", + "import java.util.*;\n", + "\n", + "public class ModifyingArraysAsList {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };\n", + " List list1 =\n", + " new ArrayList<>(Arrays.asList(ia));\n", + " System.out.println(\"Before shuffling: \" + list1);\n", + " Collections.shuffle(list1, rand);\n", + " System.out.println(\"After shuffling: \" + list1);\n", + " System.out.println(\"array: \" + Arrays.toString(ia));\n", + "\n", + " List list2 = Arrays.asList(ia);\n", + " System.out.println(\"Before shuffling: \" + list2);\n", + " Collections.shuffle(list2, rand);\n", + " System.out.println(\"After shuffling: \" + list2);\n", + " System.out.println(\"array: \" + Arrays.toString(ia));\n", + " }\n", + "}\n", + "/* Output:\n", + "Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", + "After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]\n", + "array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", + "Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", + "After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]\n", + "array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在第一种情况下, `Arrays.asList()` 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 `Arrays.asList(ia)` 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 `Arrays.asList()` 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果执行的操作会修改这个 **List** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "Java 提供了许多保存对象的方法:\n", + "\n", + "1. 数组将数字索引与对象相关联。它保存类型明确的对象,因此在查找对象时不必对结果做类型转换。它可以是多维的,可以保存基本类型的数据。虽然可以在运行时创建数组,但是一旦创建数组,就无法更改数组的大小。\n", + "\n", + "2. **Collection** 保存单一的元素,而 **Map** 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种 **Collection** 和各种 **Map** 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。\n", + "\n", + "3. 像数组一样, **List** 也将数字索引与对象相关联,因此,数组和 **List** 都是有序集合。\n", + "\n", + "4. 如果要执行大量的随机访问,则使用 **ArrayList** ,如果要经常从表中间插入或删除元素,则应该使用 **LinkedList** 。\n", + "\n", + "5. 队列和堆栈的行为是通过 **LinkedList** 提供的。\n", + "\n", + "6. **Map** 是一种将对象(而非数字)与对象相关联的设计。 **HashMap** 专为快速访问而设计,而 **TreeMap** 保持键始终处于排序状态,所以没有 **HashMap** 快。 **LinkedHashMap** 按插入顺序保存其元素,但使用散列提供快速访问的能力。\n", + "\n", + "7. **Set** 不接受重复元素。 **HashSet** 提供最快的查询速度,而 **TreeSet** 保持元素处于排序状态。 **LinkedHashSet** 按插入顺序保存其元素,但使用散列提供快速访问的能力。\n", + "\n", + "8. 不要在新代码中使用遗留类 **Vector** ,**Hashtable** 和 **Stack** 。\n", + "\n", + "浏览一下Java集合的简图(不包含抽象类或遗留组件)会很有帮助。这里仅包括在一般情况下会碰到的接口和类。(译者注:下图为原著PDF中的截图,可能由于未知原因存在问题。这里可参考译者绘制版[^8])\n", + "\n", + "![simple collection taxonomy](../images/simple-collection-taxonomy.png)\n", + "\n", + "### 简单集合分类\n", + "\n", + "可以看到,实际上只有四个基本的集合组件: **Map** , **List** , **Set** 和 **Queue** ,它们各有两到三个实现版本(**Queue** 的 **java.util.concurrent** 实现未包含在此图中)。最常使用的集合用黑色粗线线框表示。\n", + "\n", + "虚线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的虚线表示特定的类实现了一个接口。实心箭头表示某个类可以生成箭头指向的类的对象。例如,任何 **Collection** 都可以生成 **Iterator** , **List** 可以生成 **ListIterator** (也能生成普通的 **Iterator** ,因为 **List** 继承自 **Collection** )。\n", + "\n", + "下面的示例展示了各种不同的类在方法上的差异。实际代码来自[泛型]()章节,在这里只是调用它来产生输出。程序的输出还展示了在每个类或接口中所实现的接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collections/CollectionDifferences.java\n", + "import onjava.*;\n", + "\n", + "public class CollectionDifferences {\n", + " public static void main(String[] args) {\n", + " CollectionMethodDifferences.main(args);\n", + " }\n", + "}\n", + "/* Output:\n", + "Collection: [add, addAll, clear, contains, containsAll,\n", + "equals, forEach, hashCode, isEmpty, iterator,\n", + "parallelStream, remove, removeAll, removeIf, retainAll,\n", + "size, spliterator, stream, toArray]\n", + "Interfaces in Collection: [Iterable]\n", + "Set extends Collection, adds: []\n", + "Interfaces in Set: [Collection]\n", + "HashSet extends Set, adds: []\n", + "Interfaces in HashSet: [Set, Cloneable, Serializable]\n", + "LinkedHashSet extends HashSet, adds: []\n", + "Interfaces in LinkedHashSet: [Set, Cloneable,\n", + "Serializable]\n", + "TreeSet extends Set, adds: [headSet,\n", + "descendingIterator, descendingSet, pollLast, subSet,\n", + "floor, tailSet, ceiling, last, lower, comparator,\n", + "pollFirst, first, higher]\n", + "Interfaces in TreeSet: [NavigableSet, Cloneable,\n", + "Serializable]\n", + "List extends Collection, adds: [replaceAll, get,\n", + "indexOf, subList, set, sort, lastIndexOf, listIterator]\n", + "Interfaces in List: [Collection]\n", + "ArrayList extends List, adds: [trimToSize,\n", + "ensureCapacity]\n", + "Interfaces in ArrayList: [List, RandomAccess,\n", + "Cloneable, Serializable]\n", + "LinkedList extends List, adds: [offerFirst, poll,\n", + "getLast, offer, getFirst, removeFirst, element,\n", + "removeLastOccurrence, peekFirst, peekLast, push,\n", + "pollFirst, removeFirstOccurrence, descendingIterator,\n", + "pollLast, removeLast, pop, addLast, peek, offerLast,\n", + "addFirst]\n", + "Interfaces in LinkedList: [List, Deque, Cloneable,\n", + "Serializable]\n", + "Queue extends Collection, adds: [poll, peek, offer,\n", + "element]\n", + "Interfaces in Queue: [Collection]\n", + "PriorityQueue extends Queue, adds: [comparator]\n", + "Interfaces in PriorityQueue: [Serializable]\n", + "Map: [clear, compute, computeIfAbsent,\n", + "computeIfPresent, containsKey, containsValue, entrySet,\n", + "equals, forEach, get, getOrDefault, hashCode, isEmpty,\n", + "keySet, merge, put, putAll, putIfAbsent, remove,\n", + "replace, replaceAll, size, values]\n", + "HashMap extends Map, adds: []\n", + "Interfaces in HashMap: [Map, Cloneable, Serializable]\n", + "LinkedHashMap extends HashMap, adds: []\n", + "Interfaces in LinkedHashMap: [Map]\n", + "SortedMap extends Map, adds: [lastKey, subMap,\n", + "comparator, firstKey, headMap, tailMap]\n", + "Interfaces in SortedMap: [Map]\n", + "TreeMap extends Map, adds: [descendingKeySet,\n", + "navigableKeySet, higherEntry, higherKey, floorKey,\n", + "subMap, ceilingKey, pollLastEntry, firstKey, lowerKey,\n", + "headMap, tailMap, lowerEntry, ceilingEntry,\n", + "descendingMap, pollFirstEntry, lastKey, firstEntry,\n", + "floorEntry, comparator, lastEntry]\n", + "Interfaces in TreeMap: [NavigableMap, Cloneable,\n", + "Serializable]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 `entrySet()` 和 `values()` 方法来产生 **Collection** 。\n", + "\n", + "请注意,标记接口 **java.util.RandomAccess** 附加到了 **ArrayList** 上,但不附加到 **LinkedList** 上。这为根据特定 **List** 动态改变其行为的算法提供了信息。\n", + "\n", + "从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了 **java.util** 中更多的有关集合的内容后(特别是在[附录:集合主题]()中的内容),就会发现出了继承结构有点奇怪外,还有更多的问题。集合类库一直以来都是设计难题——解决这些问题涉及到要去满足经常彼此之间互为牵制的各方面需求。所以要做好准备,在各处做出妥协。\n", + "\n", + "尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面,但我想你很快就会找到自己的路子,来获得和使用这个类库中的类。\n", + "\n", + "[^1]: 许多语言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。\n", + "\n", + "[^2]: 这里是操作符重载的用武之地,C++和C#的集合类都使用操作符重载生成了更简洁的语法。\n", + "\n", + "[^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。\n", + "\n", + "[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。\n", + "\n", + "[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。\n", + "\n", + "[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。\n", + "\n", + "[^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。\n", + "\n", + "[^8]: 下面是译者绘制的 Java 集合框架简图,黄色为接口,绿色为抽象类,蓝色为具体类。虚线箭头表示实现关系,实线箭头表示继承关系。\n", + "![collection](../images/collection.png)\n", + "![map](../images/map.png)\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/13-Functional-Programming.ipynb b/jupyter/13-Functional-Programming.ipynb new file mode 100644 index 00000000..d0ec74d0 --- /dev/null +++ b/jupyter/13-Functional-Programming.ipynb @@ -0,0 +1,2395 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十三章 函数式编程\n", + "\n", + "\n", + "\n", + "> 函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。\n", + "\n", + "在计算机时代早期,内存是稀缺和昂贵的。几乎每个人都用汇编语言编程。人们对编译器有所了解,但仅仅想到编译生成的代码肯定会比手工编码多很多字节。\n", + "\n", + "通常,只是为了使程序适合有限的内存,程序员通过修改内存中的代码来节省代码空间,以便在程序执行时执行不同的操作。这种技术被称为**自修改代码** (self-modifying code)。只要程序足够小,少数人可以维护所有棘手和神秘的汇编代码,你就可以让它运行起来。\n", + "\n", + "随着内存和处理器变得更便宜、更快。C 语言出现并被大多数汇编程序员认为更“高级”。人们发现使用 C 可以显著提高生产力。同时,使用 C 创建自修改代码仍然不难。\n", + "\n", + "随着硬件越来越便宜,程序的规模和复杂性都在增长。这一切只是让程序工作变得困难。我们想方设法使代码更加一致和易懂。使用纯粹的自修改代码造成的结果就是:我们很难确定程序在做什么。它也难以测试:除非你想一点点测试输出,代码转换和修改等等过程?\n", + "\n", + "然而,使用代码以某种方式操纵其他代码的想法也很有趣,只要能保证它更安全。从代码创建,维护和可靠性的角度来看,这个想法非常吸引人。我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。难道这不会让我们更有效率,同时创造更健壮的代码吗?\n", + "\n", + "这就是**函数式编程**(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,一些非函数式语言已经习惯了使用函数式编程产生的优雅的语法。\n", + "\n", + "你也可以这样想:\n", + "\n", + "OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。\n", + "\n", + "纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。将值传递给函数,该函数然后生成新值但从不修改自身外部的任何东西(包括其参数或该函数范围之外的元素)。当强制执行此操作时,你知道任何错误都不是由所谓的副作用引起的,因为该函数仅创建并返回结果,而不是其他任何错误。\n", + "\n", + "更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。\n", + "\n", + "需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:为“并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例[^2],但还不足以证明纯函数式语言就是解决编程问题的最佳方法。\n", + "\n", + "FP 思想值得融入非 FP 语言,如 Python。Java 8 也从中吸收并支持了 FP。我们将在此章探讨。\n", + "\n", + "\n", + "\n", + "## 新旧对比\n", + "\n", + "\n", + "通常,传递给方法的数据不同,结果不同。如果我们希望方法在调用时行为不同,该怎么做呢?结论是:只要能将代码传递给方法,我们就可以控制它的行为。此前,我们通过在方法中创建包含所需行为的对象,然后将该对象传递给我们想要控制的方法来完成此操作。下面我们用传统形式和 Java 8 的方法引用、Lambda 表达式分别演示。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Strategize.java\n", + "\n", + "interface Strategy {\n", + " String approach(String msg);\n", + "}\n", + "\n", + "class Soft implements Strategy {\n", + " public String approach(String msg) {\n", + " return msg.toLowerCase() + \"?\";\n", + " }\n", + "}\n", + "\n", + "class Unrelated {\n", + " static String twice(String msg) {\n", + " return msg + \" \" + msg;\n", + " }\n", + "}\n", + "\n", + "public class Strategize {\n", + " Strategy strategy;\n", + " String msg;\n", + " Strategize(String msg) {\n", + " strategy = new Soft(); // [1]\n", + " this.msg = msg;\n", + " }\n", + "\n", + " void communicate() {\n", + " System.out.println(strategy.approach(msg));\n", + " }\n", + "\n", + " void changeStrategy(Strategy strategy) {\n", + " this.strategy = strategy;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Strategy[] strategies = {\n", + " new Strategy() { // [2]\n", + " public String approach(String msg) {\n", + " return msg.toUpperCase() + \"!\";\n", + " }\n", + " },\n", + " msg -> msg.substring(0, 5), // [3]\n", + " Unrelated::twice // [4]\n", + " };\n", + " Strategize s = new Strategize(\"Hello there\");\n", + " s.communicate();\n", + " for(Strategy newStrategy : strategies) {\n", + " s.changeStrategy(newStrategy); // [5]\n", + " s.communicate(); // [6]\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hello there?\n", + "HELLO THERE!\n", + "Hello\n", + "Hello there Hello there" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Strategy** 接口提供了单一的 `approach()` 方法来承载函数式功能。通过创建不同的 **Strategy** 对象,我们可以创建不同的行为。\n", + "\n", + "传统上,我们通过创建一个实现 **Strategy** 接口的类来实现此行为,比如在 **Soft**。\n", + "\n", + "- **[1]** 在 **Strategize** 中,**Soft** 作为默认策略,在构造函数中赋值。\n", + "\n", + "- **[2]** 一种略显简短且更自发的方法是创建一个**匿名内部类**。即使这样,仍有相当数量的冗余代码。你总是要仔细观察:“哦,原来这样,这里使用了匿名内部类。”\n", + "\n", + "- **[3]** Java 8 的 Lambda 表达式。由箭头 `->` 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。\n", + "\n", + "- **[4]** Java 8 的**方法引用**,由 `::` 区分。在 `::` 的左边是类或对象的名称,在 `::` 的右边是方法的名称,但没有参数列表。\n", + "\n", + "- **[5]** 在使用默认的 **Soft** **strategy** 之后,我们逐步遍历数组中的所有 **Strategy**,并使用 `changeStrategy()` 方法将每个 **Strategy** 放入 变量 `s` 中。\n", + "\n", + "- **[6]** 现在,每次调用 `communicate()` 都会产生不同的行为,具体取决于此刻正在使用的策略**代码对象**。我们传递的是行为,而非仅数据。[^3]\n", + "\n", + "在 Java 8 之前,我们能够通过 **[1]** 和 **[2]** 的方式传递功能。然而,这种语法的读写非常笨拙,并且我们别无选择。方法引用和 Lambda 表达式的出现让我们可以在需要时**传递功能**,而不是仅在必要才这么做。\n", + "\n", + "\n", + "\n", + "## Lambda表达式\n", + "\n", + "\n", + "Lambda 表达式是使用**最小可能**语法编写的函数定义:\n", + "\n", + "1. Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。\n", + "\n", + "2. Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。\n", + "\n", + "我们在 **Strategize.java** 中看到了一个 Lambda 表达式,但还有其他语法变体:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/LambdaExpressions.java\n", + "\n", + "interface Description {\n", + " String brief();\n", + "}\n", + "\n", + "interface Body {\n", + " String detailed(String head);\n", + "}\n", + "\n", + "interface Multi {\n", + " String twoArg(String head, Double d);\n", + "}\n", + "\n", + "public class LambdaExpressions {\n", + "\n", + " static Body bod = h -> h + \" No Parens!\"; // [1]\n", + "\n", + " static Body bod2 = (h) -> h + \" More details\"; // [2]\n", + "\n", + " static Description desc = () -> \"Short info\"; // [3]\n", + "\n", + " static Multi mult = (h, n) -> h + n; // [4]\n", + "\n", + " static Description moreLines = () -> { // [5]\n", + " System.out.println(\"moreLines()\");\n", + " return \"from moreLines()\";\n", + " };\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(bod.detailed(\"Oh!\"));\n", + " System.out.println(bod2.detailed(\"Hi!\"));\n", + " System.out.println(desc.brief());\n", + " System.out.println(mult.twoArg(\"Pi! \", 3.14159));\n", + " System.out.println(moreLines.brief());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Oh! No Parens!\n", + "Hi! More details\n", + "Short info\n", + "Pi! 3.14159\n", + "moreLines()\n", + "from moreLines()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们从三个接口开始,每个接口都有一个单独的方法(很快就会理解它的重要性)。但是,每个方法都有不同数量的参数,以便演示 Lambda 表达式语法。\n", + "\n", + "任何 Lambda 表达式的基本语法是:\n", + "\n", + "1. 参数。\n", + "\n", + "2. 接着 `->`,可视为“产出”。\n", + "\n", + "3. `->` 之后的内容都是方法体。\n", + "\n", + " - **[1]** 当只用一个参数,可以不需要括号 `()`。 然而,这是一个特例。\n", + "\n", + " - **[2]** 正常情况使用括号 `()` 包裹参数。 为了保持一致性,也可以使用括号 `()` 包裹单个参数,虽然这种情况并不常见。\n", + "\n", + " - **[3]** 如果没有参数,则必须使用括号 `()` 表示空参数列表。\n", + "\n", + " - **[4]** 对于多个参数,将参数列表放在括号 `()` 中。\n", + "\n", + "到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 **return** 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。\n", + "\n", + "**[5]** 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 **return**。\n", + "\n", + "Lambda 表达式通常比**匿名内部类**产生更易读的代码,因此我们将在本书中尽可能使用它们。\n", + "\n", + "### 递归\n", + "\n", + "递归函数是一个自我调用的函数。可以编写递归的 Lambda 表达式,但需要注意:递归方法必须是实例变量或静态变量,否则会出现编译时错误。 我们将为每个案例创建一个示例。\n", + "\n", + "这两个示例都需要一个接受 **int** 型参数并生成 **int** 的接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/IntCall.java\n", + "\n", + "interface IntCall {\n", + " int call(int arg);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "整数 n 的阶乘将所有小于或等于 n 的正整数相乘。 阶乘函数是一个常见的递归示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/RecursiveFactorial.java\n", + "\n", + "public class RecursiveFactorial {\n", + " static IntCall fact;\n", + " public static void main(String[] args) {\n", + " fact = n -> n == 0 ? 1 : n * fact.call(n - 1);\n", + " for(int i = 0; i <= 10; i++)\n", + " System.out.println(fact.call(i));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1\n", + "1\n", + "2\n", + "6\n", + "24\n", + "120\n", + "720\n", + "5040\n", + "40320\n", + "362880\n", + "3628800" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则将无限递归并产生异常。\n", + "\n", + "我们可以将 `Fibonacci` 序列改为使用递归 Lambda 表达式来实现,这次使用实例变量:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/RecursiveFibonacci.java\n", + "\n", + "public class RecursiveFibonacci {\n", + " IntCall fib;\n", + "\n", + " RecursiveFibonacci() {\n", + " fib = n -> n == 0 ? 0 :\n", + " n == 1 ? 1 :\n", + " fib.call(n - 1) + fib.call(n - 2);\n", + " }\n", + " \n", + " int fibonacci(int n) { return fib.call(n); }\n", + "\n", + " public static void main(String[] args) {\n", + " RecursiveFibonacci rf = new RecursiveFibonacci();\n", + " for(int i = 0; i <= 10; i++)\n", + " System.out.println(rf.fibonacci(i));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0\n", + "1\n", + "1\n", + "2\n", + "3\n", + "5\n", + "8\n", + "13\n", + "21\n", + "34\n", + "55" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "将 `Fibonacci` 序列中的最后两个元素求和来产生下一个元素。\n", + "\n", + "\n", + "\n", + "## 方法引用\n", + "\n", + "\n", + "Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 `::` [^4],然后跟方法名称。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/MethodReferences.java\n", + "\n", + "import java.util.*;\n", + "\n", + "interface Callable { // [1]\n", + " void call(String s);\n", + "}\n", + "\n", + "class Describe {\n", + " void show(String msg) { // [2]\n", + " System.out.println(msg);\n", + " }\n", + "}\n", + "\n", + "public class MethodReferences {\n", + " static void hello(String name) { // [3]\n", + " System.out.println(\"Hello, \" + name);\n", + " }\n", + " static class Description {\n", + " String about;\n", + " Description(String desc) { about = desc; }\n", + " void help(String msg) { // [4]\n", + " System.out.println(about + \" \" + msg);\n", + " }\n", + " }\n", + " static class Helper {\n", + " static void assist(String msg) { // [5]\n", + " System.out.println(msg);\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " Describe d = new Describe();\n", + " Callable c = d::show; // [6]\n", + " c.call(\"call()\"); // [7]\n", + "\n", + " c = MethodReferences::hello; // [8]\n", + " c.call(\"Bob\");\n", + "\n", + " c = new Description(\"valuable\")::help; // [9]\n", + " c.call(\"information\");\n", + "\n", + " c = Helper::assist; // [10]\n", + " c.call(\"Help!\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "call()\n", + "Hello, Bob\n", + "valuable information\n", + "Help!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[1]** 我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。\n", + "\n", + "**[2]** `show()` 的签名(参数类型和返回类型)符合 **Callable** 的 `call()` 的签名。\n", + "\n", + "**[3]** `hello()` 也符合 `call()` 的签名。 \n", + "\n", + "**[4]** `help()` 也符合,它是静态内部类中的非静态方法。\n", + "\n", + "**[5]** `assist()` 是静态内部类中的静态方法。\n", + "\n", + "**[6]** 我们将 **Describe** 对象的方法引用赋值给 **Callable** ,它没有 `show()` 方法,而是 `call()` 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 **Callable** 的 `call()` 方法的签名。\n", + "\n", + "**[7]** 我们现在可以通过调用 `call()` 来调用 `show()`,因为 Java 将 `call()` 映射到 `show()`。\n", + "\n", + "**[8]** 这是一个**静态**方法引用。\n", + "\n", + "**[9]** 这是 **[6]** 的另一个版本:对已实例化对象的方法的引用,有时称为*绑定方法引用*。\n", + "\n", + "**[10]** 最后,获取静态内部类的方法引用的操作与 **[8]** 中外部类方式一样。\n", + "\n", + "上例只是简短的介绍,我们很快就能看到方法引用的全部变化。\n", + "\n", + "### Runnable接口\n", + "\n", + "**Runnable** 接口自 1.0 版以来一直在 Java 中,因此不需要导入。它也符合特殊的单方法接口格式:它的方法 `run()` 不带参数,也没有返回值。因此,我们可以使用 Lambda 表达式和方法引用作为 **Runnable**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/RunnableMethodReference.java\n", + "\n", + "// 方法引用与 Runnable 接口的结合使用\n", + "\n", + "class Go {\n", + " static void go() {\n", + " System.out.println(\"Go::go()\");\n", + " }\n", + "}\n", + "\n", + "public class RunnableMethodReference {\n", + " public static void main(String[] args) {\n", + "\n", + " new Thread(new Runnable() {\n", + " public void run() {\n", + " System.out.println(\"Anonymous\");\n", + " }\n", + " }).start();\n", + "\n", + " new Thread(\n", + " () -> System.out.println(\"lambda\")\n", + " ).start();\n", + "\n", + " new Thread(Go::go).start();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Anonymous\n", + "lambda\n", + "Go::go()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Thread** 对象将 **Runnable** 作为其构造函数参数,并具有会调用 `run()` 的方法 `start()`。 **注意**,只有**匿名内部类**才需要具有名为 `run()` 的方法。\n", + "\n", + "\n", + "\n", + "### 未绑定的方法引用\n", + "\n", + "\n", + "未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/UnboundMethodReference.java\n", + "\n", + "// 没有方法引用的对象\n", + "\n", + "class X {\n", + " String f() { return \"X::f()\"; }\n", + "}\n", + "\n", + "interface MakeString {\n", + " String make();\n", + "}\n", + "\n", + "interface TransformX {\n", + " String transform(X x);\n", + "}\n", + "\n", + "public class UnboundMethodReference {\n", + " public static void main(String[] args) {\n", + " // MakeString ms = X::f; // [1]\n", + " TransformX sp = X::f;\n", + " X x = new X();\n", + " System.out.println(sp.transform(x)); // [2]\n", + " System.out.println(x.f()); // 同等效果\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X::f()\n", + "X::f()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "截止目前,我们已经知道了与接口方法同名的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果:即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。\n", + "\n", + "要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数的接口,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,这在 Java 中是允许的。这次我们需要调整下心里预期——使用未绑定的引用时,函数方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 理由是:你需要一个对象来调用方法。\n", + "\n", + "**[2]** 的结果有点像脑筋急转弯。 我接受未绑定的引用并对其调用 `transform()`,将其传递给 `X`,并以某种方式导致对 `x.f()` 的调用。 Java 知道它必须采用第一个参数,这实际上就是 `this`,并在其上调用方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/MultiUnbound.java\n", + "\n", + "// 未绑定的方法与多参数的结合运用\n", + "\n", + "class This {\n", + " void two(int i, double d) {}\n", + " void three(int i, double d, String s) {}\n", + " void four(int i, double d, String s, char c) {}\n", + "}\n", + "\n", + "interface TwoArgs {\n", + " void call2(This athis, int i, double d);\n", + "}\n", + "\n", + "interface ThreeArgs {\n", + " void call3(This athis, int i, double d, String s);\n", + "}\n", + "\n", + "interface FourArgs {\n", + " void call4(\n", + " This athis, int i, double d, String s, char c);\n", + "}\n", + "\n", + "public class MultiUnbound {\n", + " public static void main(String[] args) {\n", + " TwoArgs twoargs = This::two;\n", + " ThreeArgs threeargs = This::three;\n", + " FourArgs fourargs = This::four;\n", + " This athis = new This();\n", + " twoargs.call2(athis, 11, 3.14);\n", + " threeargs.call3(athis, 11, 3.14, \"Three\");\n", + " fourargs.call4(athis, 11, 3.14, \"Four\", 'Z');\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了说明这一点,我将类命名为 **This** ,函数方法的第一个参数则是 **athis**,但是你应该选择其他名称以防止生产代码混淆。\n", + "\n", + "### 构造函数引用\n", + "\n", + "你还可以捕获构造函数的引用,然后通过引用调用该构造函数。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/CtorReference.java\n", + "\n", + "class Dog {\n", + " String name;\n", + " int age = -1; // For \"unknown\"\n", + " Dog() { name = \"stray\"; }\n", + " Dog(String nm) { name = nm; }\n", + " Dog(String nm, int yrs) { name = nm; age = yrs; }\n", + "}\n", + "\n", + "interface MakeNoArgs {\n", + " Dog make();\n", + "}\n", + "\n", + "interface Make1Arg {\n", + " Dog make(String nm);\n", + "}\n", + "\n", + "interface Make2Args {\n", + " Dog make(String nm, int age);\n", + "}\n", + "\n", + "public class CtorReference {\n", + " public static void main(String[] args) {\n", + " MakeNoArgs mna = Dog::new; // [1]\n", + " Make1Arg m1a = Dog::new; // [2]\n", + " Make2Args m2a = Dog::new; // [3]\n", + "\n", + " Dog dn = mna.make();\n", + " Dog d1 = m1a.make(\"Comet\");\n", + " Dog d2 = m2a.make(\"Ralph\", 4);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Dog** 有三个构造函数,函数接口内的 `make()` 方法反映了构造函数参数列表( `make()` 方法名称可以不同)。\n", + "\n", + "**注意**我们如何对 **[1]**,**[2]** 和 **[3]** 中的每一个使用 `Dog :: new`。 这 3 个构造函数只有一个相同名称:`:: new`,但在每种情况下都赋值给不同的接口。编译器可以检测并知道从哪个构造函数引用。\n", + "\n", + "编译器能识别并调用你的构造函数( 在本例中为 `make()`)。\n", + "\n", + "\n", + "## 函数式接口\n", + "\n", + "\n", + "方法引用和 Lambda 表达式必须被赋值,同时编译器需要识别类型信息以确保类型正确。 Lambda 表达式特别引入了新的要求。 代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "x -> x.toString()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们清楚这里返回类型必须是 **String**,但 `x` 是什么类型呢?\n", + "\n", + "Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 `x` 的类型。\n", + "\n", + "下面是第 2 个代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "(x, y) -> x + y" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在 `x` 和 `y` 可以是任何支持 `+` 运算符连接的数据类型,可以是两个不同的数值类型或者是 1 个 **String** 加任意一种可自动转换为 **String** 的数据类型(这包括了大多数类型)。 但是,当 Lambda 表达式被赋值时,编译器必须确定 `x` 和 `y` 的确切类型以生成正确的代码。\n", + "\n", + "该问题也适用于方法引用。 假设你要传递 `System.out :: println` 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型?\n", + "\n", + "为了解决这个问题,Java 8 引入了 `java.util.function` 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为函数式方法。\n", + "\n", + "在编写接口时,可以使用 `@FunctionalInterface` 注解强制执行此“函数式方法”模式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/FunctionalAnnotation.java\n", + "\n", + "@FunctionalInterface\n", + "interface Functional {\n", + " String goodbye(String arg);\n", + "}\n", + "\n", + "interface FunctionalNoAnn {\n", + " String goodbye(String arg);\n", + "}\n", + "\n", + "/*\n", + "@FunctionalInterface\n", + "interface NotFunctional {\n", + " String goodbye(String arg);\n", + " String hello(String arg);\n", + "}\n", + "产生错误信息:\n", + "NotFunctional is not a functional interface\n", + "multiple non-overriding abstract methods\n", + "found in interface NotFunctional\n", + "*/\n", + "\n", + "public class FunctionalAnnotation {\n", + " public String goodbye(String arg) {\n", + " return \"Goodbye, \" + arg;\n", + " }\n", + " public static void main(String[] args) {\n", + " FunctionalAnnotation fa =\n", + " new FunctionalAnnotation();\n", + " Functional f = fa::goodbye;\n", + " FunctionalNoAnn fna = fa::goodbye;\n", + " // Functional fac = fa; // Incompatible\n", + " Functional fl = a -> \"Goodbye, \" + a;\n", + " FunctionalNoAnn fnal = a -> \"Goodbye, \" + a;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的值在 `NotFunctional` 的定义中可见:接口中如果有多个方法则会产生编译时错误消息。\n", + "\n", + "仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。\n", + "\n", + "尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java 不允许我们将 `FunctionalAnnotation` 像 `fac` 定义一样直接赋值给 `Functional`,因为它没有明确地实现 `Functional` 接口。 令人惊奇的是 ,Java 8 允许我们以简便的语法为接口赋值函数。\n", + "\n", + "`java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。\n", + "\n", + " 以下是基本命名准则:\n", + "\n", + "1. 如果只处理对象而非基本类型,名称则为 `Function`,`Consumer`,`Predicate` 等。参数类型通过泛型添加。\n", + "\n", + "2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但基本 `Supplier` 类型例外。\n", + "\n", + "3. 如果返回值为基本类型,则用 `To` 表示,如 `ToLongFunction ` 和 `IntToLongFunction`。\n", + "\n", + "4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。\n", + "\n", + "5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。\n", + "\n", + "6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。\n", + "\n", + "下表描述了 `java.util.function` 中的目标类型(包括例外情况):\n", + "\n", + "| **特征** |**函数式方法名**|**示例**|\n", + "| :---- | :----: | :----: |\n", + "|无参数;
无返回值|**Runnable**
(java.lang)
`run()`|**Runnable**|\n", + "|无参数;
返回类型任意|**Supplier**
`get()`
`getAs类型()`| **Supplier``
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier**|\n", + "|无参数;
返回类型任意|**Callable**
(java.util.concurrent)
`call()`|**Callable``**|\n", + "|1 参数;
无返回值|**Consumer**
`accept()`|**`Consumer`
IntConsumer
LongConsumer
DoubleConsumer**|\n", + "|2 参数 **Consumer**|**BiConsumer**
`accept()`|**`BiConsumer`**|\n", + "|2 参数 **Consumer**;
1 引用;
1 基本类型|**Obj类型Consumer**
`accept()`|**`ObjIntConsumer`
`ObjLongConsumer`
`ObjDoubleConsumer`**|\n", + "|1 参数;
返回类型不同|**Function**
`apply()`
**To类型** 和 **类型To类型**
`applyAs类型()`|**Function``
IntFunction``
`LongFunction`
DoubleFunction``
ToIntFunction``
`ToLongFunction`
`ToDoubleFunction`
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**|\n", + "|1 参数;
返回类型相同|**UnaryOperator**
`apply()`|**`UnaryOperator`
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator**|\n", + "|2 参数类型相同;
返回类型相同|**BinaryOperator**
`apply()`|**`BinaryOperator`
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator**|\n", + "|2 参数类型相同;
返回整型|Comparator
(java.util)
`compare()`|**`Comparator`**|\n", + "|2 参数;
返回布尔型|**Predicate**
`test()`|**`Predicate`
`BiPredicate`
IntPredicate
LongPredicate
DoublePredicate**|\n", + "|参数基本类型;
返回基本类型|**类型To类型Function**
`applyAs类型()`|**IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**|\n", + "|2 参数类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**|\n", + "\n", + "\n", + "此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出更多行的函数式接口。\n", + "\n", + "可以看出,在创建 `java.util.function` 时,设计者们做出了一些选择。 \n", + "\n", + "例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我对他们放弃的原因表示同情)。这些选择是疏忽还是有人认为其他组合的使用情况出现得很少(他们是如何得出这个结论的)?\n", + "\n", + "你还可以看到基本类型给 Java 添加了多少复杂性。为了缓和效率问题,该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然受到语言设计选择不佳的影响。\n", + "\n", + "下面枚举了基于 Lambda 表达式的所有不同 **Function** 变体的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/FunctionVariants.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "class Foo {}\n", + "\n", + "class Bar {\n", + " Foo f;\n", + " Bar(Foo f) { this.f = f; }\n", + "}\n", + "\n", + "class IBaz {\n", + " int i;\n", + " IBaz(int i) {\n", + " this.i = i;\n", + " }\n", + "}\n", + "\n", + "class LBaz {\n", + " long l;\n", + " LBaz(long l) {\n", + " this.l = l;\n", + " }\n", + "}\n", + "\n", + "class DBaz {\n", + " double d;\n", + " DBaz(double d) {\n", + " this.d = d;\n", + " }\n", + "}\n", + "\n", + "public class FunctionVariants {\n", + " static Function f1 = f -> new Bar(f);\n", + " static IntFunction f2 = i -> new IBaz(i);\n", + " static LongFunction f3 = l -> new LBaz(l);\n", + " static DoubleFunction f4 = d -> new DBaz(d);\n", + " static ToIntFunction f5 = ib -> ib.i;\n", + " static ToLongFunction f6 = lb -> lb.l;\n", + " static ToDoubleFunction f7 = db -> db.d;\n", + " static IntToLongFunction f8 = i -> i;\n", + " static IntToDoubleFunction f9 = i -> i;\n", + " static LongToIntFunction f10 = l -> (int)l;\n", + " static LongToDoubleFunction f11 = l -> l;\n", + " static DoubleToIntFunction f12 = d -> (int)d;\n", + " static DoubleToLongFunction f13 = d -> (long)d;\n", + "\n", + " public static void main(String[] args) {\n", + " Bar b = f1.apply(new Foo());\n", + " IBaz ib = f2.apply(11);\n", + " LBaz lb = f3.apply(11);\n", + " DBaz db = f4.apply(11);\n", + " int i = f5.applyAsInt(ib);\n", + " long l = f6.applyAsLong(lb);\n", + " double d = f7.applyAsDouble(db);\n", + " l = f8.applyAsLong(12);\n", + " d = f9.applyAsDouble(12);\n", + " i = f10.applyAsInt(12);\n", + " d = f11.applyAsDouble(12);\n", + " i = f12.applyAsInt(13.0);\n", + " l = f13.applyAsLong(13.0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。\n", + "\n", + "主方法中的每个测试都显示了 `Function` 接口中不同类型的 `apply()` 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。\n", + "\n", + "方法引用有自己的小魔法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "/ functional/MethodConversion.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "class In1 {}\n", + "class In2 {}\n", + "\n", + "public class MethodConversion {\n", + " static void accept(In1 i1, In2 i2) {\n", + " System.out.println(\"accept()\");\n", + " }\n", + " static void someOtherName(In1 i1, In2 i2) {\n", + " System.out.println(\"someOtherName()\");\n", + " }\n", + " public static void main(String[] args) {\n", + " BiConsumer bic;\n", + "\n", + " bic = MethodConversion::accept;\n", + " bic.accept(new In1(), new In2());\n", + "\n", + " bic = MethodConversion::someOtherName;\n", + " // bic.someOtherName(new In1(), new In2()); // Nope\n", + " bic.accept(new In1(), new In2());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "accept()\n", + "someOtherName()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "查看 `BiConsumer` 的文档,你会看到 `accept()` 方法。 实际上,如果我们将方法命名为 `accept()`,它就可以作为方法引用。 但是我们也可用不同的名称,比如 `someOtherName()`。只要参数类型、返回类型与 `BiConsumer` 的 `accept()` 相同即可。\n", + "\n", + "因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 `accept()`),而不是你的方法名。\n", + "\n", + "现在我们来看看所有基于类的函数式,应用于方法引用(即那些不涉及基本类型的函数)。下例我们创建了一个最简单的函数式签名。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/ClassFunctionals.java\n", + "\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "class AA {}\n", + "class BB {}\n", + "class CC {}\n", + "\n", + "public class ClassFunctionals {\n", + " static AA f1() { return new AA(); }\n", + " static int f2(AA aa1, AA aa2) { return 1; }\n", + " static void f3(AA aa) {}\n", + " static void f4(AA aa, BB bb) {}\n", + " static CC f5(AA aa) { return new CC(); }\n", + " static CC f6(AA aa, BB bb) { return new CC(); }\n", + " static boolean f7(AA aa) { return true; }\n", + " static boolean f8(AA aa, BB bb) { return true; }\n", + " static AA f9(AA aa) { return new AA(); }\n", + " static AA f10(AA aa1, AA aa2) { return new AA(); }\n", + " public static void main(String[] args) {\n", + " Supplier s = ClassFunctionals::f1;\n", + " s.get();\n", + " Comparator c = ClassFunctionals::f2;\n", + " c.compare(new AA(), new AA());\n", + " Consumer cons = ClassFunctionals::f3;\n", + " cons.accept(new AA());\n", + " BiConsumer bicons = ClassFunctionals::f4;\n", + " bicons.accept(new AA(), new BB());\n", + " Function f = ClassFunctionals::f5;\n", + " CC cc = f.apply(new AA());\n", + " BiFunction bif = ClassFunctionals::f6;\n", + " cc = bif.apply(new AA(), new BB());\n", + " Predicate p = ClassFunctionals::f7;\n", + " boolean result = p.test(new AA());\n", + " BiPredicate bip = ClassFunctionals::f8;\n", + " result = bip.test(new AA(), new BB());\n", + " UnaryOperator uo = ClassFunctionals::f9;\n", + " AA aa = uo.apply(new AA());\n", + " BinaryOperator bo = ClassFunctionals::f10;\n", + " aa = bo.apply(new AA(), new AA());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请**注意**,每个方法名称都是随意的(如 `f1()`,`f2()`等)。正如你刚才看到的,一旦将方法引用赋值给函数接口,我们就可以调用与该接口关联的函数方法。 在此示例中为 `get()`、`compare()`、`accept()`、`apply()` 和 `test()`。\n", + "\n", + "\n", + "\n", + "### 多参数函数式接口\n", + "\n", + "`java.util.functional` 中的接口是有限的。比如有了 `BiFunction`,但它不能变化。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/TriFunction.java\n", + "\n", + "@FunctionalInterface\n", + "public interface TriFunction {\n", + " R apply(T t, U u, V v);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "简单测试,验证它是否有效:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/TriFunctionTest.java\n", + "\n", + "public class TriFunctionTest {\n", + " static int f(int i, long l, double d) { return 99; }\n", + " public static void main(String[] args) {\n", + " TriFunction tf =\n", + " TriFunctionTest::f;\n", + " tf = (i, l, d) -> 12;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里我们测试了方法引用和 Lambda 表达式。\n", + "\n", + "### 缺少基本类型的函数\n", + "\n", + "让我们重温一下 `BiConsumer`,看看我们如何创建缺少 **int**,**long** 和 **double** 的各种排列:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/BiConsumerPermutations.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class BiConsumerPermutations {\n", + " static BiConsumer bicid = (i, d) ->\n", + " System.out.format(\"%d, %f%n\", i, d);\n", + " static BiConsumer bicdi = (d, i) ->\n", + " System.out.format(\"%d, %f%n\", i, d);\n", + " static BiConsumer bicil = (i, l) ->\n", + " System.out.format(\"%d, %d%n\", i, l);\n", + " public static void main(String[] args) {\n", + " bicid.accept(47, 11.34);\n", + " bicdi.accept(22.45, 92);\n", + " bicil.accept(1, 11L);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "47, 11.340000\n", + "92, 22.450000\n", + "1, 11" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里使用 `System.out.format()` 来显示。它类似于 `System.out.println()` 但提供了更多的显示选项。 这里,`%f` 表示我将 `n` 作为浮点值给出,`%d` 表示 `n` 是一个整数值。 这其中可以包含空格,输入 `%n` 会换行 — 当然使用传统的 `\\n` 也能换行,但 `%n` 是自动跨平台的,这是使用 `format()` 的另一个原因。\n", + "\n", + "上例简单使用了包装类型,装箱和拆箱用于在基本类型之间来回转换。 我们也可以使用包装类型,如 `Function`,而不是预定义的基本类型。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/FunctionWithWrapped.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class FunctionWithWrapped {\n", + " public static void main(String[] args) {\n", + " Function fid = i -> (double)i;\n", + " IntToDoubleFunction fid2 = i -> i;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有强制转换,则会收到错误消息:“Integer cannot be converted to Double”(**Integer** 无法转换为 **Double**),而使用 **IntToDoubleFunction** 就没有此类问题。 **IntToDoubleFunction** 接口的源代码是这样的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@FunctionalInterface \n", + "public interface IntToDoubleFunction { \n", + " double applyAsDouble(int value); \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "之所以我们可以简单地编写 `Function ` 并返回合适的结果,很明显是为了性能。使用基本类型可以防止传递参数和返回结果过程中的自动装箱和自动拆箱。\n", + "\n", + "似乎是考虑到使用频率,某些函数类型并没有预定义。\n", + "\n", + "当然,如果因缺少基本类型而造成的性能问题,你也可以轻松编写自己的接口( 参考 Java 源代码)——尽管这里出现性能瓶颈的可能性不大。\n", + "\n", + "\n", + "## 高阶函数\n", + "\n", + "\n", + "这个名字可能听起来令人生畏,但是:[高阶函数](https://en.wikipedia.org/wiki/Higher-order_function)(Higher-order Function)只是一个消费或产生函数的函数。\n", + "\n", + "我们先来看看如何产生一个函数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/ProduceFunction.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "interface\n", + "FuncSS extends Function {} // [1]\n", + "\n", + "public class ProduceFunction {\n", + " static FuncSS produce() {\n", + " return s -> s.toLowerCase(); // [2]\n", + " }\n", + " public static void main(String[] args) {\n", + " FuncSS f = produce();\n", + " System.out.println(f.apply(\"YELLING\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "yelling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,`produce()` 是高阶函数。\n", + "\n", + "**[1]** 使用继承,可以轻松地为专用接口创建别名。\n", + "\n", + "**[2]** 使用 Lambda 表达式,可以轻松地在方法中创建和返回一个函数。\n", + "\n", + "要消费一个函数,消费函数需要在参数列表正确地描述函数类型。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/ConsumeFunction.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "class One {}\n", + "class Two {}\n", + "\n", + "public class ConsumeFunction {\n", + " static Two consume(Function onetwo) {\n", + " return onetwo.apply(new One());\n", + " }\n", + " public static void main(String[] args) {\n", + " Two two = consume(one -> new Two());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当基于消费函数生成新函数时,事情就变得相当有趣了。代码示例如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/TransformFunction.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "class I {\n", + " @Override\n", + " public String toString() { return \"I\"; }\n", + "}\n", + "\n", + "class O {\n", + " @Override\n", + " public String toString() { return \"O\"; }\n", + "}\n", + "\n", + "public class TransformFunction {\n", + " static Function transform(Function in) {\n", + " return in.andThen(o -> {\n", + " System.out.println(o);\n", + " return o;\n", + " });\n", + " }\n", + " public static void main(String[] args) {\n", + " Function f2 = transform(i -> {\n", + " System.out.println(i);\n", + " return new O();\n", + " });\n", + " O o = f2.apply(new I());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "I\n", + "O" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里,`transform()` 生成一个与传入的函数具有相同签名的函数,但是你可以生成任何你想要的类型。\n", + "\n", + "这里使用到了 `Function` 接口中名为 `andThen()` 的默认方法,该方法专门用于操作函数。 顾名思义,在调用 `in` 函数之后调用 `toThen()`(还有个 `compose()` 方法,它在 `in` 函数之前应用新函数)。 要附加一个 `andThen()` 函数,我们只需将该函数作为参数传递。 `transform()` 产生的是一个新函数,它将 `in` 的动作与 `andThen()` 参数的动作结合起来。\n", + "\n", + "\n", + "\n", + "## 闭包\n", + "\n", + "\n", + "在上一节的 `ProduceFunction.java` 中,我们从方法中返回 Lambda 函数。 虽然过程简单,但是有些问题必须再回过头来探讨一下。\n", + "\n", + "**闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。\n", + "\n", + "考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。\n", + "\n", + "首先,下例函数中,方法返回访问对象字段和方法参数。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure1.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Closure1 {\n", + " int i;\n", + " IntSupplier makeFun(int x) {\n", + " return () -> x + i++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,垃圾收集器几乎肯定会保留一个对象,并将现有的函数以这种方式绑定到该对象上[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/SharedStorage.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class SharedStorage {\n", + " public static void main(String[] args) {\n", + " Closure1 c1 = new Closure1();\n", + " IntSupplier f1 = c1.makeFun(0);\n", + " IntSupplier f2 = c1.makeFun(0);\n", + " IntSupplier f3 = c1.makeFun(0);\n", + " System.out.println(f1.getAsInt());\n", + " System.out.println(f2.getAsInt());\n", + " System.out.println(f3.getAsInt());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0\n", + "1\n", + "2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每次调用 `getAsInt()` 都会增加 `i`,表明存储是共享的。\n", + "\n", + "如果 `i` 是 `makeFun()` 的局部变量怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍可以编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure2.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Closure2 {\n", + " IntSupplier makeFun(int x) {\n", + " int i = 0;\n", + " return () -> x + i;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由 `makeFun()` 返回的 `IntSupplier` “关闭” `i` 和 `x`,因此当你调用返回的函数时两者仍然有效。 但请**注意**,我没有像 `Closure1.java` 那样递增 `i`,因为会产生编译时错误。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure3.java\n", + "\n", + "// {WillNotCompile}\n", + "import java.util.function.*;\n", + "\n", + "public class Closure3 {\n", + " IntSupplier makeFun(int x) {\n", + " int i = 0;\n", + " // x++ 和 i++ 都会报错:\n", + " return () -> x++ + i++;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。\n", + "\n", + "如果使用 `final` 修饰 `x`和 `i`,就不能再递增它们的值了。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure4.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Closure4 {\n", + " IntSupplier makeFun(final int x) {\n", + " final int i = 0;\n", + " return () -> x + i;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "那么为什么在 `Closure2.java` 中, `x` 和 `i` 非 `final` 却可以运行呢?\n", + "\n", + "这就叫做**等同 final 效果**(Effectively Final)。这个术语是在 Java 8 才开始出现的,表示虽然没有明确地声明变量是 `final` 的,但是因变量值没被改变过而实际有了 `final` 同等的效果。 如果局部变量的初始值永远不会改变,那么它实际上就是 `final` 的。\n", + "\n", + "如果 `x` 和 `i` 的值在方法中的其他位置发生改变(但不在返回的函数内部),则编译器仍将视其为错误。每个递增操作则会分别产生错误消息。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "/ functional/Closure5.java\n", + "\n", + "// {无法编译成功}\n", + "import java.util.function.*;\n", + "\n", + "public class Closure5 {\n", + " IntSupplier makeFun(int x) {\n", + " int i = 0;\n", + " i++;\n", + " x++;\n", + " return () -> x + i;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**等同 final 效果**意味着可以在变量声明前加上 **final** 关键字而不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。\n", + "\n", + "通过在闭包中使用 `final` 关键字提前修饰变量 `x` 和 `i` , 我们解决了 `Closure5.java` 中的问题。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\n", + "// functional/Closure6.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Closure6 {\n", + " IntSupplier makeFun(int x) {\n", + " int i = 0;\n", + " i++;\n", + " x++;\n", + " final int iFinal = i;\n", + " final int xFinal = x;\n", + " return () -> xFinal + iFinal;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中 `iFinal` 和 `xFinal` 的值在赋值后并没有改变过,因此在这里使用 `final` 是多余的。\n", + "\n", + "如果这里是引用的话,需要把 **int** 型更改为 **Integer** 型。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure7.java\n", + "\n", + "// {无法编译成功}\n", + "import java.util.function.*;\n", + "\n", + "public class Closure7 {\n", + " IntSupplier makeFun(int x) {\n", + " Integer i = 0;\n", + " i = i + 1;\n", + " return () -> x + i;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器非常智能,它能识别变量 `i` 的值被更改过了。 对于包装类型的处理可能比较特殊,因此我们尝试下 **List**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure8.java\n", + "\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "public class Closure8 {\n", + " Supplier> makeFun() {\n", + " final List ai = new ArrayList<>();\n", + " ai.add(1);\n", + " return () -> ai;\n", + " }\n", + " public static void main(String[] args) {\n", + " Closure8 c7 = new Closure8();\n", + " List\n", + " l1 = c7.makeFun().get(),\n", + " l2 = c7.makeFun().get();\n", + " System.out.println(l1);\n", + " System.out.println(l2);\n", + " l1.add(42);\n", + " l2.add(96);\n", + " System.out.println(l1);\n", + " System.out.println(l2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[1]\n", + "[1]\n", + "[1, 42]\n", + "[1, 96]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,这次一切正常。我们改变了 **List** 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList`, 它们之间互不干扰。\n", + "\n", + "请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。\n", + "\n", + "下面我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是**等同 final 效果**错误消息的触发点。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Closure9.java\n", + "\n", + "// {无法编译成功}\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "public class Closure9 {\n", + " Supplier> makeFun() {\n", + " List ai = new ArrayList<>();\n", + " ai = new ArrayList<>(); // Reassignment\n", + " return () -> ai;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例,重新赋值引用会触发错误消息。如果只修改指向的对象则没问题,只要没有其他人获得对该对象的引用(这意味着你有多个实体可以修改对象,此时事情会变得非常混乱),基本上就是安全的[^6]。\n", + "\n", + "让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是“在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。\n", + "\n", + "\n", + "\n", + "### 作为闭包的内部类\n", + "\n", + "我们可以使用匿名内部类重写之前的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/AnonymousClosure.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class AnonymousClosure {\n", + " IntSupplier makeFun(int x) {\n", + " int i = 0;\n", + " // 同样规则的应用:\n", + " // i++; // 非等同 final 效果\n", + " // x++; // 同上\n", + " return new IntSupplier() {\n", + " public int getAsInt() { return x + i; }\n", + " };\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "实际上只要有内部类,就会有闭包(Java 8 只是简化了闭包操作)。在 Java 8 之前,变量 `x` 和 `i` 必须被明确声明为 `final`。在 Java 8 中,内部类的规则放宽,包括**等同 final 效果**。\n", + "\n", + "\n", + "## 函数组合\n", + "\n", + "\n", + "函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,有一个使用 `andThen()` 的函数组合示例。一些 `java.util.function` 接口中包含支持函数组合的方法 [^7]。\n", + "\n", + "| 组合方法 | 支持接口 |\n", + "| :----- | :----- |\n", + "| `andThen(argument)`
根据参数执行原始操作 | **Function
BiFunction
Consumer
BiConsumer
IntConsumer
LongConsumer
DoubleConsumer
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator** |\n", + "| `compose(argument)`
根据参数执行原始操作 | **Function
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator** |\n", + "| `and(argument)`
短路**逻辑与**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", + "| `or(argument)`
短路**逻辑或**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", + "| `negate()`
该谓词的**逻辑否**谓词| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", + "\n", + "\n", + "下例使用了 `Function` 里的 `compose()`和 `andThen()`。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/FunctionComposition.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class FunctionComposition {\n", + " static Function\n", + " f1 = s -> {\n", + " System.out.println(s);\n", + " return s.replace('A', '_');\n", + " },\n", + " f2 = s -> s.substring(3),\n", + " f3 = s -> s.toLowerCase(),\n", + " f4 = f1.compose(f2).andThen(f3);\n", + " public static void main(String[] args) {\n", + " System.out.println(\n", + " f4.apply(\"GO AFTER ALL AMBULANCES\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "AFTER ALL AMBULANCES\n", + "_fter _ll _mbul_nces" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里我们重点看正在创建的新函数 `f4`。它调用 `apply()` 的方式与常规几乎无异[^8]。\n", + "\n", + "当 `f1` 获得字符串时,它已经被`f2` 剥离了前三个字符。这是因为 `compose(f2)` 表示 `f2` 的调用发生在 `f1` 之前。\n", + "\n", + "下例是 `Predicate` 的逻辑运算演示.代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/PredicateComposition.java\n", + "\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class PredicateComposition {\n", + " static Predicate\n", + " p1 = s -> s.contains(\"bar\"),\n", + " p2 = s -> s.length() < 5,\n", + " p3 = s -> s.contains(\"foo\"),\n", + " p4 = p1.negate().and(p2).or(p3);\n", + " public static void main(String[] args) {\n", + " Stream.of(\"bar\", \"foobar\", \"foobaz\", \"fongopuckey\")\n", + " .filter(p4)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "foobar\n", + "foobaz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`p4` 获取到了所有谓词并组合成一个更复杂的谓词。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。\n", + "\n", + "正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。\n", + "\n", + "从输出结果我们可以看到 `p4` 的工作流程:任何带有 `foo` 的东西都会留下,即使它的长度大于 5。 `fongopuckey` 因长度超出和不包含 `bar` 而被丢弃。\n", + "\n", + "\n", + "## 柯里化和部分求值\n", + "\n", + "[柯里化](https://en.wikipedia.org/wiki/Currying)(Currying)的名称来自于其发明者之一 *Haskell Curry*。他可能是计算机领域唯一名字被命名重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/CurryingAndPartials.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class CurryingAndPartials {\n", + " // 未柯里化:\n", + " static String uncurried(String a, String b) {\n", + " return a + b;\n", + " }\n", + " public static void main(String[] args) {\n", + " // 柯里化的函数:\n", + " Function> sum =\n", + " a -> b -> a + b; // [1]\n", + "\n", + " System.out.println(uncurried(\"Hi \", \"Ho\"));\n", + "\n", + " Function\n", + " hi = sum.apply(\"Hi \"); // [2]\n", + " System.out.println(hi.apply(\"Ho\"));\n", + "\n", + " // 部分应用:\n", + " Function sumHi =\n", + " sum.apply(\"Hup \");\n", + " System.out.println(sumHi.apply(\"Ho\"));\n", + " System.out.println(sumHi.apply(\"Hey\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Hi Ho\n", + "Hi Ho\n", + "Hup Ho\n", + "Hup Hey" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**[1]** 这一连串的箭头很巧妙。*注意*,在函数接口声明中,第二个参数是另一个函数。\n", + "\n", + "**[2]** 柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “无参函数” 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。\n", + "\n", + "我们可以通过添加级别来柯里化一个三参数函数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/Curry3Args.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Curry3Args {\n", + " public static void main(String[] args) {\n", + " Function>> sum =\n", + " a -> b -> c -> a + b + c;\n", + " Function> hi =\n", + " sum.apply(\"Hi \");\n", + " Function ho =\n", + " hi.apply(\"Ho \");\n", + " System.out.println(ho.apply(\"Hup\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Hi Ho Hup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于每个级别的箭头级联(Arrow-cascading),你在类型声明中包裹了另一个 **Function**。\n", + "\n", + "处理基本类型和装箱时,请使用适当的 **Function** 接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// functional/CurriedIntAdd.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class CurriedIntAdd {\n", + " public static void main(String[] args) {\n", + " IntFunction\n", + " curriedIntAdd = a -> b -> a + b;\n", + " IntUnaryOperator add4 = curriedIntAdd.apply(4);\n", + " System.out.println(add4.applyAsInt(5));\n", + "\t }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以在互联网上找到更多的柯里化示例。通常它们是用 Java 之外的语言实现的,但如果理解了柯里化的基本概念,你可以很轻松地用 Java 实现它们。\n", + "\n", + "\n", + "## 纯函数式编程\n", + "\n", + "\n", + "即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。\n", + "\n", + "这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些规则) 或 Clojure (需要的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "\n", + "Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。在下一章中,你会看到它们在流式编程中的应用。相信你会像我一样,喜欢上流式编程。\n", + "\n", + "这些特性满足大部分 Java 程序员的需求。他们开始羡慕嫉妒 Clojure、Scala 这类新语言的功能,并试图阻止 Java 程序员流失到其他阵营 (就算不能阻止,起码提供了更好的选择)。\n", + "\n", + "但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它的使用还是会让人感觉沮丧和鸡肋。\n", + "\n", + "当你遇到学习困难时,请记住通过 IDE(NetBeans、IntelliJ Idea 和 Eclipse)获得帮助,因为 IDE 可以智能提示你何时使用 Lambda 表达式或方法引用,甚至有时还能为你优化代码。\n", + "\n", + "\n", + "\n", + "[^1]: 功能粘贴在一起的方法的确有点与众不同,但它仍不失为一个库。\n", + "[^2]: 例如,这个电子书是利用 [Pandoc](http://pandoc.org/) 制作出来的,它是用纯函数式语言 [Haskell](https://www.haskell.org/) 编写的一个程序 。\n", + "[^3]: 有时函数式语言将其描述为“代码即数据”。\n", + "[^4]: 这个语法来自 C++。\n", + "[^5]: 我还没有验证过这种说法。\n", + "[^6]: 当你理解了[并发编程](./24-Concurrent-Programming.md)章节的内容,你就能明白为什么更改共享变量 “不是线程安全的” 的了。\n", + "[^7]: 接口能够支持方法的原因是它们是 Java 8 默认方法,你将在下一章中了解到。\n", + "[^8]: 一些语言,如 Python,允许像调用其他函数一样调用组合函数。但这是 Java,所以我们做做可为之事。\n", + "[^9]: 例如,[Immutables](https://immutables.github.io/) 和 [Mutability Detector](https://mutabilitydetector.github.io/MutabilityDetector/)。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/14-Streams.ipynb b/jupyter/14-Streams.ipynb new file mode 100644 index 00000000..4e5ca7c7 --- /dev/null +++ b/jupyter/14-Streams.ipynb @@ -0,0 +1,3492 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十四章 流式编程\n", + "\n", + "> 集合优化了对象的存储,而流和对象的处理有关。\n", + "\n", + "流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。\n", + "\n", + "利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。\n", + "\n", + "在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。\n", + "\n", + "举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。实际上,你的关注点首先是创建一个有序集合。围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你想做什么:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Randoms.java\n", + "import java.util.*;\n", + "public class Randoms {\n", + " public static void main(String[] args) {\n", + " new Random(47)\n", + " .ints(5, 20)\n", + " .distinct()\n", + " .limit(7)\n", + " .sorted()\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "6\n", + "10\n", + "13\n", + "16\n", + "17\n", + "18\n", + "19" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了数值产生的边界。这将生成一个整数流。我们可以使用中间流操作(intermediate stream operation) `distinct()` 来获取它们的非重复值,然后使用 `limit()` 方法获取前 7 个元素。接下来,我们使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对每个流对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用。`System.out::println` 。\n", + "\n", + "注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。\n", + "\n", + "声明式编程(Declarative programming)是一种:声明要做什么,而非怎么做的编程风格。正如我们在函数式编程中所看到的。**注意**,命令式编程的形式更难以理解。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/ImperativeRandoms.java\n", + "import java.util.*;\n", + "public class ImperativeRandoms {\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " SortedSet rints = new TreeSet<>();\n", + " while(rints.size() < 7) {\n", + " int r = rand.nextInt(20);\n", + " if(r < 5) continue;\n", + " rints.add(r);\n", + " }\n", + " System.out.println(rints);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[7, 8, 9, 11, 13, 15, 18]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `Randoms.java` 中,我们无需定义任何变量,但在这里我们定义了 3 个变量: `rand`,`rints` 和 `r`。由于 `nextInt()` 方法没有下限的原因(其内置的下限永远为 0),这段代码实现起来更复杂。所以我们要生成额外的值来过滤小于 5 的结果。\n", + "\n", + "**注意**,你必须要研究程序的真正意图,而在 `Randoms.java` 中,代码只是告诉了你它正在做什么。这种语义清晰性也是 Java 8 的流式编程更受推崇的重要原因。\n", + "\n", + "在 `ImperativeRandoms.java` 中显式地编写迭代机制称为外部迭代。而在 `Randoms.java` 中,流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。\n", + "\n", + "另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。\n", + "\n", + "\n", + "\n", + "## 流支持\n", + "\n", + "Java 设计者面临着这样一个难题:现存的大量类库不仅为 Java 所用,同时也被应用在整个 Java 生态圈数百万行的代码中。如何将一个全新的流的概念融入到现有类库中呢?\n", + "\n", + "比如在 **Random** 中添加更多的方法。只要不改变原有的方法,现有代码就不会受到干扰。\n", + "\n", + "问题是,接口部分怎么改造呢?特别是涉及集合类接口的部分。如果你想把一个集合转换为流,直接向接口添加新方法会破坏所有老的接口实现类。\n", + "\n", + "Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `default`(`默认`)修饰的方法。通过这种方案,设计者们可以将流式(*stream*)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。\n", + "\n", + "下面我们来看下每种类型的流操作。\n", + "\n", + "\n", + "## 流创建\n", + "\n", + "你可以通过 `Stream.of()` 很容易地将一组元素转化成为流(`Bubble` 类在本章的后面定义):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/StreamOf.java\n", + "import java.util.stream.*;\n", + "public class StreamOf {\n", + " public static void main(String[] args) {\n", + " Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))\n", + " .forEach(System.out::println);\n", + " Stream.of(\"It's \", \"a \", \"wonderful \", \"day \", \"for \", \"pie!\")\n", + " .forEach(System.out::print);\n", + " System.out.println();\n", + " Stream.of(3.14159, 2.718, 1.618)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Bubble(1)\n", + "Bubble(2)\n", + "Bubble(3)\n", + "It's a wonderful day for pie!\n", + "3.14159\n", + "2.718\n", + "1.618" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除此之外,每个集合都可以通过调用 `stream()` 方法来产生一个流。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/CollectionToStream.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class CollectionToStream {\n", + " public static void main(String[] args) {\n", + " List bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));\n", + " System.out.println(bubbles.stream()\n", + " .mapToInt(b -> b.i)\n", + " .sum());\n", + " \n", + " Set w = new HashSet<>(Arrays.asList(\"It's a wonderful day for pie!\".split(\" \")));\n", + " w.stream()\n", + " .map(x -> x + \" \")\n", + " .forEach(System.out::print);\n", + " System.out.println();\n", + " \n", + " Map m = new HashMap<>();\n", + " m.put(\"pi\", 3.14159);\n", + " m.put(\"e\", 2.718);\n", + " m.put(\"phi\", 1.618);\n", + " m.entrySet().stream()\n", + " .map(e -> e.getKey() + \": \" + e.getValue())\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "6\n", + "a pie! It's for wonderful day\n", + "phi: 1.618\n", + "e: 2.718\n", + "pi: 3.14159" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在创建 `List` 对象之后,我们只需要简单地调用所有集合中都有的 `stream()`。中间操作 `map()` 会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到后续的流中。通常 `map()` 会获取对象并产生新的对象,但在这里产生了特殊的用于数值类型的流。例如,`mapToInt()` 方法将一个对象流(object stream)转换成为包含整型数字的 `IntStream`。同样,针对 `Float` 和 `Double` 也有类似名字的操作。\n", + "\n", + "我们通过调用字符串的 `split()`(该方法会根据参数来拆分字符串)来获取元素用于定义变量 `w`。稍后你会知道 `split()` 参数可以是十分复杂,但在这里我们只是根据空格来分割字符串。\n", + "\n", + "为了从 **Map** 集合中产生流数据,我们首先调用 `entrySet()` 产生一个对象流,每个对象都包含一个 `key` 键以及与其相关联的 `value` 值。然后分别调用 `getKey()` 和 `getValue()` 获取值。\n", + "\n", + "### 随机数流\n", + "\n", + "`Random` 类被一组生成流的方法增强了。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/RandomGenerators.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class RandomGenerators {\n", + " public static void show(Stream stream) {\n", + " stream\n", + " .limit(4)\n", + " .forEach(System.out::println);\n", + " System.out.println(\"++++++++\");\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " show(rand.ints().boxed());\n", + " show(rand.longs().boxed());\n", + " show(rand.doubles().boxed());\n", + " // 控制上限和下限:\n", + " show(rand.ints(10, 20).boxed());\n", + " show(rand.longs(50, 100).boxed());\n", + " show(rand.doubles(20, 30).boxed());\n", + " // 控制流大小:\n", + " show(rand.ints(2).boxed());\n", + " show(rand.longs(2).boxed());\n", + " show(rand.doubles(2).boxed());\n", + " // 控制流的大小和界限\n", + " show(rand.ints(3, 3, 9).boxed());\n", + " show(rand.longs(3, 12, 22).boxed());\n", + " show(rand.doubles(3, 11.5, 12.3).boxed());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "-1172028779\n", + "1717241110\n", + "-2014573909\n", + "229403722\n", + "++++++++\n", + "2955289354441303771\n", + "3476817843704654257\n", + "-8917117694134521474\n", + "4941259272818818752\n", + "++++++++\n", + "0.2613610344283964\n", + "0.0508673570556899\n", + "0.8037155449603999\n", + "0.7620665811558285\n", + "++++++++\n", + "16\n", + "10\n", + "11\n", + "12\n", + "++++++++\n", + "65\n", + "99\n", + "54\n", + "58\n", + "++++++++\n", + "29.86777681078574\n", + "24.83968447804611\n", + "20.09247112332014\n", + "24.046793846338723\n", + "++++++++\n", + "1169976606\n", + "1947946283\n", + "++++++++\n", + "2970202997824602425\n", + "-2325326920272830366\n", + "++++++++\n", + "0.7024254510631527\n", + "0.6648552384607359\n", + "++++++++\n", + "6\n", + "7\n", + "7\n", + "++++++++\n", + "17\n", + "12\n", + "20\n", + "++++++++\n", + "12.27872414236691\n", + "11.732085449736195\n", + "12.196509449817267\n", + "++++++++" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了消除冗余代码,我创建了一个泛型方法 `show(Stream stream)` (在讲解泛型之前就使用这个特性,确实有点作弊,但是回报是值得的)。类型参数 `T` 可以是任何类型,所以这个方法对 **Integer**、**Long** 和 **Double** 类型都生效。但是 **Random** 类只能生成基本类型 **int**, **long**, **double** 的流。幸运的是, `boxed()` 流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得 `show()` 能够接受流。\n", + "\n", + "我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个文本文件提供字符串对象的例子。\n", + "\n", + "Cheese.dat 文件内容:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// streams/Cheese.dat\n", + "Not much of a cheese shop really, is it?\n", + "Finest in the district, sir.\n", + "And what leads you to that conclusion?\n", + "Well, it's so clean.\n", + "It's certainly uncontaminated by cheese." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们通过 **File** 类将 Cheese.dat 文件的所有行读取到 `List` 中。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/RandomWords.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "public class RandomWords implements Supplier {\n", + " List words = new ArrayList<>();\n", + " Random rand = new Random(47);\n", + " RandomWords(String fname) throws IOException {\n", + " List lines = Files.readAllLines(Paths.get(fname));\n", + " // 略过第一行\n", + " for (String line : lines.subList(1, lines.size())) {\n", + " for (String word : line.split(\"[ .?,]+\"))\n", + " words.add(word.toLowerCase());\n", + " }\n", + " }\n", + " public String get() {\n", + " return words.get(rand.nextInt(words.size()));\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return words.stream()\n", + " .collect(Collectors.joining(\" \"));\n", + " }\n", + " public static void main(String[] args) throws Exception {\n", + " System.out.println(\n", + " Stream.generate(new RandomWords(\"Cheese.dat\"))\n", + " .limit(10)\n", + " .collect(Collectors.joining(\" \")));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "it shop sir the much cheese by conclusion district is" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里你可以看到更为复杂的 `split()` 运用。在构造器中,每一行都被 `split()` 通过空格或者被方括号包裹的任意标点符号进行分割。在结束方括号后面的 `+` 代表 `+` 前面的东西可以出现一次或者多次。\n", + "\n", + "我们注意到在构造函数中循环体使用命令式编程(外部迭代)。在以后的例子中,你甚至会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。\n", + "\n", + "在 `toString()` 和主方法中你看到了 `collect()` 收集操作,它根据参数来组合所有流中的元素。\n", + "\n", + "当你使用 **Collectors.**`joining()`,你将会得到一个 `String` 类型的结果,每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于产生不同的结果。\n", + "\n", + "在主方法中,我们提前看到了 **Stream.**`generate()` 的用法,它可以把任意 `Supplier` 用于生成 `T` 类型的流。\n", + "\n", + "\n", + "### int 类型的范围\n", + "\n", + "`IntStream` 类提供了 `range()` 方法用于生成整型序列的流。编写循环时,这个方法会更加便利:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Ranges.java\n", + "import static java.util.stream.IntStream.*;\n", + "public class Ranges {\n", + " public static void main(String[] args) {\n", + " // 传统方法:\n", + " int result = 0;\n", + " for (int i = 10; i < 20; i++)\n", + " result += i;\n", + " System.out.println(result);\n", + " // for-in 循环:\n", + " result = 0;\n", + " for (int i : range(10, 20).toArray())\n", + " result += i;\n", + " System.out.println(result);\n", + " // 使用流:\n", + " System.out.println(range(10, 20).sum());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "145\n", + "145\n", + "145" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在主方法中的第一种方式是我们传统编写 `for` 循环的方式;第二种方式,我们使用 `range()` 创建了流并将其转化为数组,然后在 `for-in` 代码块中使用。但是,如果你能像第三种方法那样全程使用流是更好的。我们对范围中的数字进行求和。在流中可以很方便的使用 `sum()` 操作求和。\n", + "\n", + "注意 **IntStream.**`range()` 相比 `onjava.Range.range()` 拥有更多的限制。这是由于其可选的第三个参数,后者允许步长大于 1,并且可以从大到小来生成。\n", + "\n", + "实用小功能 `repeat()` 可以用来替换简单的 `for` 循环。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Repeat.java\n", + "package onjava;\n", + "import static java.util.stream.IntStream.*;\n", + "public class Repeat {\n", + " public static void repeat(int n, Runnable action) {\n", + " range(0, n).forEach(i -> action.run());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其产生的循环更加清晰:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Looping.java\n", + "import static onjava.Repeat.*;\n", + "public class Looping {\n", + " static void hi() {\n", + " System.out.println(\"Hi!\");\n", + " }\n", + " public static void main(String[] args) {\n", + " repeat(3, () -> System.out.println(\"Looping!\"));\n", + " repeat(2, Looping::hi);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Looping!\n", + "Looping!\n", + "Looping!\n", + "Hi!\n", + "Hi!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "原则上,在代码中包含并解释 `repeat()` 并不值得。诚然它是一个相当透明的工具,但结果取决于你的团队和公司的运作方式。\n", + "\n", + "### generate()\n", + "\n", + "参照 `RandomWords.java` 中 **Stream.**`generate()` 搭配 `Supplier` 使用的例子。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Generator.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class Generator implements Supplier {\n", + " Random rand = new Random(47);\n", + " char[] letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", + " \n", + " public String get() {\n", + " return \"\" + letters[rand.nextInt(letters.length)];\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " String word = Stream.generate(new Generator())\n", + " .limit(30)\n", + " .collect(Collectors.joining());\n", + " System.out.println(word);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "YNZBRNYGCFOWZNTCQRGSEGZMMJMROE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围,所以使用数组边界是经过深思熟虑的。\n", + "\n", + "如果要创建包含相同对象的流,只需要传递一个生成那些对象的 `lambda` 到 `generate()` 中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Duplicator.java\n", + "import java.util.stream.*;\n", + "public class Duplicator {\n", + " public static void main(String[] args) {\n", + " Stream.generate(() -> \"duplicate\")\n", + " .limit(3)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "duplicate\n", + "duplicate\n", + "duplicate" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如下是在本章之前例子中使用过的 `Bubble` 类。**注意**它包含了自己的静态生成器(Static generator)方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Bubble.java\n", + "import java.util.function.*;\n", + "public class Bubble {\n", + " public final int i;\n", + " \n", + " public Bubble(int n) {\n", + " i = n;\n", + " }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"Bubble(\" + i + \")\";\n", + " }\n", + " \n", + " private static int count = 0;\n", + " public static Bubble bubbler() {\n", + " return new Bubble(count++);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于 `bubbler()` 与 `Supplier` 是接口兼容的,我们可以将其方法引用直接传递给 **Stream.**`generate()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Bubbles.java\n", + "import java.util.stream.*;\n", + "public class Bubbles {\n", + " public static void main(String[] args) {\n", + " Stream.generate(Bubble::bubbler)\n", + " .limit(5)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Bubble(0)\n", + "Bubble(1)\n", + "Bubble(2)\n", + "Bubble(3)\n", + "Bubble(4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这是一个对于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。\n", + "\n", + "### iterate()\n", + "\n", + "**Stream.**`iterate()` 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 `iterate()`,依次类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Fibonacci.java\n", + "import java.util.stream.*;\n", + "public class Fibonacci {\n", + " int x = 1;\n", + " \n", + " Stream numbers() {\n", + " return Stream.iterate(0, i -> {\n", + " int result = x + i;\n", + " x = i;\n", + " return result;\n", + " });\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " new Fibonacci().numbers()\n", + " .skip(20) // 过滤前 20 个\n", + " .limit(10) // 然后取 10 个\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "6765\n", + "10946\n", + "17711\n", + "28657\n", + "46368\n", + "75025\n", + "121393\n", + "196418\n", + "317811\n", + "514229" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "斐波那契数列将数列中最后两个元素进行求和以产生下一个元素。`iterate()` 只能记忆结果,因此我们需要利用一个变量 `x` 追踪另外一个元素。\n", + "\n", + "在主方法中,我们使用了一个之前没有见过的 `skip()` 操作。它根据参数丢弃指定数量的流元素。在这里,我们丢弃了前 20 个元素。\n", + "\n", + "### 流的建造者模式\n", + "\n", + "在建造者设计模式(也称构造器模式)中,首先创建一个 `builder` 对象,传递给它多个构造器信息,最后执行“构造”。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FileToWordsBuilder.java\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class FileToWordsBuilder {\n", + " Stream.Builder builder = Stream.builder();\n", + " \n", + " public FileToWordsBuilder(String filePath) throws Exception {\n", + " Files.lines(Paths.get(filePath))\n", + " .skip(1) // 略过开头的注释行\n", + " .forEach(line -> {\n", + " for (String w : line.split(\"[ .?,]+\"))\n", + " builder.add(w);\n", + " });\n", + " }\n", + " \n", + " Stream stream() {\n", + " return builder.build();\n", + " }\n", + " \n", + " public static void main(String[] args) throws Exception {\n", + " new FileToWordsBuilder(\"Cheese.dat\")\n", + " .stream()\n", + " .limit(7)\n", + " .map(w -> w + \" \")\n", + " .forEach(System.out::print);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Not much of a cheese shop really" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意**,构造器会添加文件中的所有单词(除了第一行,它是包含文件路径信息的注释),但是其并没有调用 `build()`。只要你不调用 `stream()` 方法,就可以继续向 `builder` 对象中添加单词。\n", + "\n", + "在该类的更完整形式中,你可以添加一个标志位用于查看 `build()` 是否被调用,并且可能的话增加一个可以添加更多单词的方法。在 `Stream.Builder` 调用 `build()` 方法后继续尝试添加单词会产生一个异常。\n", + "\n", + "### Arrays\n", + "\n", + "`Arrays` 类中含有一个名为 `stream()` 的静态方法用于把数组转换成为流。我们可以重写 `interfaces/Machine.java` 中的主方法用于创建一个流,并将 `execute()` 应用于每一个元素。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Machine2.java\n", + "import java.util.*;\n", + "import onjava.Operations;\n", + "public class Machine2 {\n", + " public static void main(String[] args) {\n", + " Arrays.stream(new Operations[] {\n", + " () -> Operations.show(\"Bing\"),\n", + " () -> Operations.show(\"Crack\"),\n", + " () -> Operations.show(\"Twist\"),\n", + " () -> Operations.show(\"Pop\")\n", + " }).forEach(Operations::execute);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Bing\n", + "Crack\n", + "Twist\n", + "Pop" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`new Operations[]` 表达式动态创建了 `Operations` 对象的数组。\n", + "\n", + "`stream()` 同样可以产生 **IntStream**,**LongStream** 和 **DoubleStream**。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/ArrayStreams.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class ArrayStreams {\n", + " public static void main(String[] args) {\n", + " Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })\n", + " .forEach(n -> System.out.format(\"%f \", n));\n", + " System.out.println();\n", + " \n", + " Arrays.stream(new int[] { 1, 3, 5 })\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " \n", + " Arrays.stream(new long[] { 11, 22, 44, 66 })\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " \n", + " // 选择一个子域:\n", + " Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "3.141590 2.718000 1.618000\n", + "1 3 5\n", + "11 22 44 66\n", + "7 15 28" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最后一次 `stream()` 的调用有两个额外的参数。第一个参数告诉 `stream()` 从数组的哪个位置开始选择元素,第二个参数用于告知在哪里停止。每种不同类型的 `stream()` 都有类似的操作。\n", + "\n", + "### 正则表达式\n", + "\n", + "Java 的正则表达式将在[字符串](18-Strings.md)这一章节详细介绍。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 **CharSequence**,因此不能将流作为 `splitAsStream()` 的参数。\n", + "\n", + "我们再一次查看将文件处理为单词流的过程。这一次,我们使用流将文件分割为单独的字符串,接着使用正则表达式将字符串转化为单词流。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FileToWordsRegexp.java\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "import java.util.regex.Pattern;\n", + "public class FileToWordsRegexp {\n", + " private String all;\n", + " public FileToWordsRegexp(String filePath) throws Exception {\n", + " all = Files.lines(Paths.get(filePath))\n", + " .skip(1) // First (comment) line\n", + " .collect(Collectors.joining(\" \"));\n", + " }\n", + " public Stream stream() {\n", + " return Pattern\n", + " .compile(\"[ .,?]+\").splitAsStream(all);\n", + " }\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " FileToWordsRegexp fw = new FileToWordsRegexp(\"Cheese.dat\");\n", + " fw.stream()\n", + " .limit(7)\n", + " .map(w -> w + \" \")\n", + " .forEach(System.out::print);\n", + " fw.stream()\n", + " .skip(7)\n", + " .limit(2)\n", + " .map(w -> w + \" \")\n", + " .forEach(System.out::print);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Not much of a cheese shop really is it" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在构造器中我们读取了文件中的所有内容(跳过第一行注释,并将其转化成为单行字符串)。现在,当你调用 `stream()` 的时候,可以像往常一样获取一个流,但这次你可以多次调用 `stream()` 在已存储的字符串中创建一个新的流。这里有个限制,整个文件必须存储在内存中;在大多数情况下这并不是什么问题,但是这损失了流操作非常重要的优势:\n", + "\n", + "1. 流“不需要存储”。当然它们需要一些内部存储,但是这只是序列的一小部分,和持有整个序列并不相同。\n", + "2. 它们是懒加载计算的。\n", + "\n", + "幸运的是,我们稍后就会知道如何解决这个问题。\n", + "\n", + "\n", + "\n", + "## 中间操作\n", + "\n", + "中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。\n", + "\n", + "### 跟踪和调试\n", + "\n", + "`peek()` 操作的目的是帮助调试。它允许你无修改地查看流中的元素。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Peeking.java\n", + "class Peeking {\n", + " public static void main(String[] args) throws Exception {\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .skip(21)\n", + " .limit(4)\n", + " .map(w -> w + \" \")\n", + " .peek(System.out::print)\n", + " .map(String::toUpperCase)\n", + " .peek(System.out::print)\n", + " .map(String::toLowerCase)\n", + " .forEach(System.out::print);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Well WELL well it IT it s S s so SO so" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`FileToWords` 稍后定义,但它的功能实现貌似和之前我们看到的差不多:产生字符串对象的流。之后在其通过管道时调用 `peek()` 进行处理。\n", + "\n", + "因为 `peek()` 符合无返回值的 **Consumer** 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。\n", + "\n", + "### 流元素排序\n", + "\n", + "在 `Randoms.java` 中,我们熟识了 `sorted()` 的默认比较器实现。其实它还有另一种形式的实现:传入一个 **Comparator** 参数。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/SortedComparator.java\n", + "import java.util.*;\n", + "public class SortedComparator {\n", + " public static void main(String[] args) throws Exception {\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .skip(10)\n", + " .limit(10)\n", + " .sorted(Comparator.reverseOrder())\n", + " .map(w -> w + \" \")\n", + " .forEach(System.out::print);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "you what to the that sir leads in district And" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`sorted()` 预设了一些默认的比较器。这里我们使用的是反转“自然排序”。当然你也可以把 Lambda 函数作为参数传递给 `sorted()`。\n", + "\n", + "### 移除元素\n", + "\n", + "* `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。\n", + "\n", + "* `filter(Predicate)`:过滤操作会保留与传递进去的过滤器函数计算结果为 `true` 元素。\n", + "\n", + "在下例中,`isPrime()` 作为过滤器函数,用于检测质数。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Prime.java\n", + "import java.util.stream.*;\n", + "import static java.util.stream.LongStream.*;\n", + "public class Prime {\n", + " public static Boolean isPrime(long n) {\n", + " return rangeClosed(2, (long)Math.sqrt(n))\n", + " .noneMatch(i -> n % i == 0);\n", + " }\n", + " public LongStream numbers() {\n", + " return iterate(2, i -> i + 1)\n", + " .filter(Prime::isPrime);\n", + " }\n", + " public static void main(String[] args) {\n", + " new Prime().numbers()\n", + " .limit(10)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " new Prime().numbers()\n", + " .skip(90)\n", + " .limit(10)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "2 3 5 7 11 13 17 19 23 29\n", + "467 479 487 491 499 503 509 521 523 541" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`rangeClosed()` 包含了上限值。如果不能整除,即余数不等于 0,则 `noneMatch()` 操作返回 `true`,如果出现任何等于 0 的结果则返回 `false`。 `noneMatch()` 操作一旦有失败就会退出。\n", + "\n", + "### 应用函数到元素\n", + "\n", + "- `map(Function)`:将函数操作应用在输入流的元素中,并将返回值传递到输出流中。\n", + "\n", + "- `mapToInt(ToIntFunction)`:操作同上,但结果是 **IntStream**。\n", + "\n", + "- `mapToLong(ToLongFunction)`:操作同上,但结果是 **LongStream**。\n", + "\n", + "- `mapToDouble(ToDoubleFunction)`:操作同上,但结果是 **DoubleStream**。\n", + "\n", + "在这里,我们使用 `map()` 映射多种函数到一个字符串流中。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FunctionMap.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "class FunctionMap {\n", + " static String[] elements = { \"12\", \"\", \"23\", \"45\" };\n", + " static Stream\n", + " testStream() {\n", + " return Arrays.stream(elements);\n", + " }\n", + " static void test(String descr, Function func) {\n", + " System.out.println(\" ---( \" + descr + \" )---\");\n", + " testStream()\n", + " .map(func)\n", + " .forEach(System.out::println);\n", + " }\n", + " public static void main(String[] args) {\n", + " test(\"add brackets\", s -> \"[\" + s + \"]\");\n", + " test(\"Increment\", s -> {\n", + " try {\n", + " return Integer.parseInt(s) + 1 + \"\";\n", + " }\n", + " catch(NumberFormatException e) {\n", + " return s;\n", + " }\n", + " }\n", + " );\n", + " test(\"Replace\", s -> s.replace(\"2\", \"9\"));\n", + " test(\"Take last digit\", s -> s.length() > 0 ?\n", + " s.charAt(s.length() - 1) + \"\" : s);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "---( add brackets )---\n", + "[12]\n", + "[]\n", + "[23]\n", + "[45]\n", + "---( Increment )---\n", + "13\n", + "24\n", + "46\n", + "---( Replace )---\n", + "19\n", + "93\n", + "45\n", + "---( Take last digit )---\n", + "2\n", + "3\n", + "5" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在上面的自增示例中,我们使用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出 **NumberFormatException** 异常,我们只须回过头来将原始字符串放回到输出流中。\n", + "\n", + "在以上例子中,`map()` 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FunctionMap2.java\n", + "// Different input and output types (不同的输入输出类型)\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class Numbered {\n", + " final int n;\n", + " Numbered(int n) {\n", + " this.n = n;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Numbered(\" + n + \")\";\n", + " }\n", + "}\n", + "class FunctionMap2 {\n", + " public static void main(String[] args) {\n", + " Stream.of(1, 5, 7, 9, 11, 13)\n", + " .map(Numbered::new)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Numbered(1)\n", + "Numbered(5)\n", + "Numbered(7)\n", + "Numbered(9)\n", + "Numbered(11)\n", + "Numbered(13)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们将获取到的整数通过构造器 `Numbered::new` 转化成为 `Numbered` 类型。\n", + "\n", + "如果使用 **Function** 返回的结果是数值类型的一种,我们必须使用合适的 `mapTo数值类型` 进行替代。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FunctionMap3.java\n", + "// Producing numeric output streams( 产生数值输出流)\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class FunctionMap3 {\n", + " public static void main(String[] args) {\n", + " Stream.of(\"5\", \"7\", \"9\")\n", + " .mapToInt(Integer::parseInt)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " Stream.of(\"17\", \"19\", \"23\")\n", + " .mapToLong(Long::parseLong)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " Stream.of(\"17\", \"1.9\", \".23\")\n", + " .mapToDouble(Double::parseDouble)\n", + " .forEach(n -> System.out.format(\"%f \", n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "5 7 9\n", + "17 19 23\n", + "17.000000 1.900000 0.230000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "遗憾的是,Java 设计者并没有尽最大努力去消除基本类型。\n", + "\n", + "### 在 `map()` 中组合流\n", + "\n", + "假设我们现在有了一个传入的元素流,并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。\n", + "\n", + "`flatMap()` 做了两件事:将产生流的函数应用在每个元素上(与 `map()` 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。\n", + "\n", + "`flatMap(Function)`:当 `Function` 产生流时使用。\n", + "\n", + "`flatMapToInt(Function)`:当 `Function` 产生 `IntStream` 时使用。\n", + "\n", + "`flatMapToLong(Function)`:当 `Function` 产生 `LongStream` 时使用。\n", + "\n", + "`flatMapToDouble(Function)`:当 `Function` 产生 `DoubleStream` 时使用。\n", + "\n", + "为了弄清它的工作原理,我们从传入一个刻意设计的函数给 `map()` 开始。该函数接受一个整数并产生一个字符串流:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/StreamOfStreams.java\n", + "import java.util.stream.*;\n", + "public class StreamOfStreams {\n", + " public static void main(String[] args) {\n", + " Stream.of(1, 2, 3)\n", + " .map(i -> Stream.of(\"Gonzo\", \"Kermit\", \"Beaker\"))\n", + " .map(e-> e.getClass().getName())\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "java.util.stream.ReferencePipeline$Head\n", + "java.util.stream.ReferencePipeline$Head\n", + "java.util.stream.ReferencePipeline$Head" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用 `flatMap()` 解决这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FlatMap.java\n", + "import java.util.stream.*;\n", + "public class FlatMap {\n", + " public static void main(String[] args) {\n", + " Stream.of(1, 2, 3)\n", + " .flatMap(i -> Stream.of(\"Gonzo\", \"Fozzie\", \"Beaker\"))\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Gonzo\n", + "Fozzie\n", + "Beaker\n", + "Gonzo\n", + "Fozzie\n", + "Beaker\n", + "Gonzo\n", + "Fozzie\n", + "Beaker" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从映射返回的每个流都会自动扁平为组成它的字符串。\n", + "\n", + "下面是另一个演示,我们从一个整数流开始,然后使用每一个整数去创建更多的随机数。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/StreamOfRandoms.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class StreamOfRandoms {\n", + " static Random rand = new Random(47);\n", + " public static void main(String[] args) {\n", + " Stream.of(1, 2, 3, 4, 5)\n", + " .flatMapToInt(i -> IntStream.concat(\n", + " rand.ints(0, 100).limit(i), IntStream.of(-1)))\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里我们引入了 `concat()`,它以参数顺序组合两个流。 如此,我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。\n", + "\n", + "因为 `rand.ints()` 产生的是一个 `IntStream`,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数形式。\n", + "\n", + "让我们再看一下将文件划分为单词流的任务。我们最后使用到的是 **FileToWordsRegexp.java**,它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。\n", + "\n", + "下面,我们再使用 ` flatMap()` 来解决这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FileToWords.java\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "import java.util.regex.Pattern;\n", + "public class FileToWords {\n", + " public static Stream stream(String filePath) throws Exception {\n", + " return Files.lines(Paths.get(filePath))\n", + " .skip(1) // First (comment) line\n", + " .flatMap(line ->\n", + " Pattern.compile(\"\\\\W+\").splitAsStream(line));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`stream()` 现在是一个静态方法,因为它可以自己完成整个流创建过程。\n", + "\n", + "**注意**:`\\\\W+` 是一个正则表达式。他表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\\\w` 表示“单词字符”。\n", + "\n", + "我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ".flatMap(line -> Arrays.stream(line.split(\"\\\\W+\"))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有了真正的、而非 `FileToWordsRegexp.java` 中基于集合存储的流,我们每次使用都必须从头创建,因为流并不能被复用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/FileToWordsTest.java\n", + "import java.util.stream.*;\n", + "public class FileToWordsTest {\n", + " public static void main(String[] args) throws Exception {\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .limit(7)\n", + " .forEach(s -> System.out.format(\"%s \", s));\n", + " System.out.println();\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .skip(7)\n", + " .limit(2)\n", + " .forEach(s -> System.out.format(\"%s \", s));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Not much of a cheese shop really\n", + "is it" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。\n", + "\n", + "\n", + "## Optional类\n", + "\n", + "在我们学习终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)?\n", + "\n", + "**Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括:\n", + "\n", + "- `findFirst()` 返回一个包含第一个元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", + "- `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", + "- `max()` 和 `min()` 返回一个包含最大值或者最小值的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", + "\n", + " `reduce()` 不再以 `identity` 形式开头,而是将其返回值包装在 **Optional** 中。(`identity` 对象成为其他形式的 `reduce()` 的默认结果,因此不存在空结果的风险)\n", + "\n", + "对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。\n", + "\n", + "以下是对空流进行所有这些操作的简单测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/OptionalsFromEmptyStreams.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class OptionalsFromEmptyStreams {\n", + " public static void main(String[] args) {\n", + " System.out.println(Stream.empty()\n", + " .findFirst());\n", + " System.out.println(Stream.empty()\n", + " .findAny());\n", + " System.out.println(Stream.empty()\n", + " .max(String.CASE_INSENSITIVE_ORDER));\n", + " System.out.println(Stream.empty()\n", + " .min(String.CASE_INSENSITIVE_ORDER));\n", + " System.out.println(Stream.empty()\n", + " .reduce((s1, s2) -> s1 + s2));\n", + " System.out.println(IntStream.empty()\n", + " .average());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "OptionalDouble.empty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当流为空的时候你会获得一个 **Optional.empty** 对象,而不是抛出异常。**Optional** 拥有 `toString()` 方法可以用于展示有用信息。\n", + "\n", + "注意,空流是通过 `Stream.empty()` 创建的。如果你在没有任何上下文环境的情况下调用 `Stream.empty()`,Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Stream s = Stream.empty();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "就可以在调用 `empty()` 时推断类型。\n", + "\n", + "这个示例展示了 **Optional** 的两个基本用法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/OptionalBasics.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class OptionalBasics {\n", + " static void test(Optional optString) {\n", + " if(optString.isPresent())\n", + " System.out.println(optString.get()); \n", + " else\n", + " System.out.println(\"Nothing inside!\");\n", + " }\n", + " public static void main(String[] args) {\n", + " test(Stream.of(\"Epithets\").findFirst());\n", + " test(Stream.empty().findFirst());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Epithets\n", + "Nothing inside!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当你接收到 **Optional** 对象时,应首先调用 `isPresent()` 检查其中是否包含元素。如果存在,可使用 `get()` 获取。\n", + "\n", + "\n", + "\n", + "### 便利函数\n", + "\n", + "有许多便利函数可以解包 **Optional** ,这简化了上述“对所包含的对象的检查和执行操作”的过程:\n", + "\n", + "- `ifPresent(Consumer)`:当值存在时调用 **Consumer**,否则什么也不做。\n", + "- `orElse(otherObject)`:如果值存在则直接返回,否则生成 **otherObject**。\n", + "- `orElseGet(Supplier)`:如果值存在则直接返回,否则使用 **Supplier** 函数生成一个可替代对象。\n", + "- `orElseThrow(Supplier)`:如果值存在直接返回,否则使用 **Supplier** 函数生成一个异常。\n", + "\n", + "如下是针对不同便利函数的简单演示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Optionals.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "public class Optionals {\n", + " static void basics(Optional optString) {\n", + " if(optString.isPresent())\n", + " System.out.println(optString.get()); \n", + " else\n", + " System.out.println(\"Nothing inside!\");\n", + " }\n", + " static void ifPresent(Optional optString) {\n", + " optString.ifPresent(System.out::println);\n", + " }\n", + " static void orElse(Optional optString) {\n", + " System.out.println(optString.orElse(\"Nada\"));\n", + " }\n", + " static void orElseGet(Optional optString) {\n", + " System.out.println(\n", + " optString.orElseGet(() -> \"Generated\"));\n", + " }\n", + " static void orElseThrow(Optional optString) {\n", + " try {\n", + " System.out.println(optString.orElseThrow(\n", + " () -> new Exception(\"Supplied\")));\n", + " } catch(Exception e) {\n", + " System.out.println(\"Caught \" + e);\n", + " }\n", + " }\n", + " static void test(String testName, Consumer> cos) {\n", + " System.out.println(\" === \" + testName + \" === \");\n", + " cos.accept(Stream.of(\"Epithets\").findFirst());\n", + " cos.accept(Stream.empty().findFirst());\n", + " }\n", + " public static void main(String[] args) {\n", + " test(\"basics\", Optionals::basics);\n", + " test(\"ifPresent\", Optionals::ifPresent);\n", + " test(\"orElse\", Optionals::orElse);\n", + " test(\"orElseGet\", Optionals::orElseGet);\n", + " test(\"orElseThrow\", Optionals::orElseThrow);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "=== basics ===\n", + "Epithets\n", + "Nothing inside!\n", + "=== ifPresent ===\n", + "Epithets\n", + "=== orElse ===\n", + "Epithets\n", + "Nada\n", + "=== orElseGet ===\n", + "Epithets\n", + "Generated\n", + "=== orElseThrow ===\n", + "Epithets\n", + "Caught java.lang.Exception: Supplied" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。\n", + "\n", + "`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在 [异常](./15-Exceptions.md) 这一章节中学习。\n", + "\n", + "\n", + "\n", + "### 创建 Optional\n", + "\n", + "当我们在自己的代码中加入 **Optional** 时,可以使用下面 3 个静态方法:\n", + "\n", + "- `empty()`:生成一个空 **Optional**。\n", + "- `of(value)`:将一个非空值包装到 **Optional** 里。\n", + "- `ofNullable(value)`:针对一个可能为空的值,为空时自动生成 **Optional.empty**,否则将值包装在 **Optional** 中。\n", + "\n", + "下面来看看它是如何工作的。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/CreatingOptionals.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "class CreatingOptionals {\n", + " static void test(String testName, Optional opt) {\n", + " System.out.println(\" === \" + testName + \" === \");\n", + " System.out.println(opt.orElse(\"Null\"));\n", + " }\n", + " public static void main(String[] args) {\n", + " test(\"empty\", Optional.empty());\n", + " test(\"of\", Optional.of(\"Howdy\"));\n", + " try {\n", + " test(\"of\", Optional.of(null));\n", + " } catch(Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " test(\"ofNullable\", Optional.ofNullable(\"Hi\"));\n", + " test(\"ofNullable\", Optional.ofNullable(null));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "=== empty ===\n", + "Null\n", + "=== of ===\n", + "Howdy\n", + "java.lang.NullPointerException\n", + "=== ofNullable ===\n", + "Hi\n", + "=== ofNullable ===\n", + "Null" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们不能通过传递 `null` 到 `of()` 来创建 `Optional` 对象。最安全的方法是, 使用 `ofNullable()` 来优雅地处理 `null`。\n", + "\n", + "### Optional 对象操作\n", + "\n", + "当我们的流管道生成了 **Optional** 对象,下面 3 个方法可使得 **Optional** 的后续能做更多的操作:\n", + "\n", + "- `filter(Predicate)`:将 **Predicate** 应用于 **Optional** 中的内容并返回结果。当 **Optional** 不满足 **Predicate** 时返回空。如果 **Optional** 为空,则直接返回。\n", + "\n", + "- `map(Function)`:如果 **Optional** 不为空,应用 **Function** 于 **Optional** 中的内容,并返回结果。否则直接返回 **Optional.empty**。\n", + "\n", + "- `flatMap(Function)`:同 `map()`,但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。\n", + "\n", + "以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时移除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/OptionalFilter.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "class OptionalFilter {\n", + " static String[] elements = {\n", + " \"Foo\", \"\", \"Bar\", \"Baz\", \"Bingo\"\n", + " };\n", + " static Stream testStream() {\n", + " return Arrays.stream(elements);\n", + " }\n", + " static void test(String descr, Predicate pred) {\n", + " System.out.println(\" ---( \" + descr + \" )---\");\n", + " for(int i = 0; i <= elements.length; i++) {\n", + " System.out.println(\n", + " testStream()\n", + " .skip(i)\n", + " .findFirst()\n", + " .filter(pred));\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " test(\"true\", str -> true);\n", + " test(\"false\", str -> false);\n", + " test(\"str != \\\"\\\"\", str -> str != \"\");\n", + " test(\"str.length() == 3\", str -> str.length() == 3);\n", + " test(\"startsWith(\\\"B\\\")\",\n", + " str -> str.startsWith(\"B\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "---( true )---\n", + "Optional[Foo]\n", + "Optional[]\n", + "Optional[Bar]\n", + "Optional[Baz]\n", + "Optional[Bingo]\n", + "Optional.empty\n", + "---( false )---\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional.empty\n", + "---( str != \"\" )---\n", + "Optional[Foo]\n", + "Optional.empty\n", + "Optional[Bar]\n", + "Optional[Baz]\n", + "Optional[Bingo]\n", + "Optional.empty\n", + "---( str.length() == 3 )---\n", + "Optional[Foo]\n", + "Optional.empty\n", + "Optional[Bar]\n", + "Optional[Baz]\n", + "Optional.empty\n", + "Optional.empty\n", + "---( startsWith(\"B\") )---\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional[Bar]\n", + "Optional[Baz]\n", + "Optional[Bingo]\n", + "Optional.empty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "即使输出看起来像流,特别是 `test()` 中的 for 循环。每一次的 for 循环时重新启动流,然后根据 for 循环的索引跳过指定个数的元素,这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素,结果会包装在 **Optional** 中。\n", + "\n", + "**注意**,不同于普通 for 循环,这里的索引值范围并不是 `i < elements.length`, 而是 `i <= elements.length`。所以最后一个元素实际上超出了流。方便的是,这将自动成为 **Optional.empty**,你可以在每一个测试的结尾中看到。\n", + "\n", + "同 `map()` 一样 , `Optional.map()` 应用于函数。它仅在 **Optional** 不为空时才应用映射函数,并将 **Optional** 的内容提取到映射函数。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/OptionalMap.java\n", + "import java.util.Arrays;\n", + "import java.util.function.Function;\n", + "import java.util.stream.Stream;\n", + "\n", + "class OptionalMap {\n", + " static String[] elements = {\"12\", \"\", \"23\", \"45\"};\n", + "\n", + " static Stream testStream() {\n", + " return Arrays.stream(elements);\n", + " }\n", + "\n", + " static void test(String descr, Function func) {\n", + " System.out.println(\" ---( \" + descr + \" )---\");\n", + " for (int i = 0; i <= elements.length; i++) {\n", + " System.out.println(\n", + " testStream()\n", + " .skip(i)\n", + " .findFirst() // Produces an Optional\n", + " .map(func));\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " // If Optional is not empty, map() first extracts\n", + " // the contents which it then passes\n", + " // to the function:\n", + " test(\"Add brackets\", s -> \"[\" + s + \"]\");\n", + " test(\"Increment\", s -> {\n", + " try {\n", + " return Integer.parseInt(s) + 1 + \"\";\n", + " } catch (NumberFormatException e) {\n", + " return s;\n", + " }\n", + " });\n", + " test(\"Replace\", s -> s.replace(\"2\", \"9\"));\n", + " test(\"Take last digit\", s -> s.length() > 0 ?\n", + " s.charAt(s.length() - 1) + \"\" : s);\n", + " }\n", + " // After the function is finished, map() wraps the\n", + " // result in an Optional before returning it:\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "---( Add brackets )---\n", + "Optional[[12]]\n", + "Optional[[]]\n", + "Optional[[23]]\n", + "Optional[[45]]\n", + "Optional.empty\n", + "---( Increment )---\n", + "Optional[13]\n", + "Optional[]\n", + "Optional[24]\n", + "Optional[46]\n", + "Optional.empty\n", + "---( Replace )---\n", + "Optional[19]\n", + "Optional[]\n", + "Optional[93]\n", + "Optional[45]\n", + "Optional.empty\n", + "---( Take last digit )---\n", + "Optional[2]\n", + "Optional[]\n", + "Optional[3]\n", + "Optional[5]\n", + "Optional.empty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "映射函数的返回结果会自动包装成为 **Optional**。**Optional.empty** 会被直接跳过。\n", + "\n", + "**Optional** 的 `flatMap()` 应用于已生成 **Optional** 的映射函数,所以 `flatMap()` 不会像 `map()` 那样将结果封装在 **Optional** 中。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/OptionalFlatMap.java\n", + "import java.util.Arrays;\n", + "import java.util.Optional;\n", + "import java.util.function.Function;\n", + "import java.util.stream.Stream;\n", + "\n", + "class OptionalFlatMap {\n", + " static String[] elements = {\"12\", \"\", \"23\", \"45\"};\n", + "\n", + " static Stream testStream() {\n", + " return Arrays.stream(elements);\n", + " }\n", + "\n", + " static void test(String descr,\n", + " Function> func) {\n", + " System.out.println(\" ---( \" + descr + \" )---\");\n", + " for (int i = 0; i <= elements.length; i++) {\n", + " System.out.println(\n", + " testStream()\n", + " .skip(i)\n", + " .findFirst()\n", + " .flatMap(func));\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " test(\"Add brackets\",\n", + " s -> Optional.of(\"[\" + s + \"]\"));\n", + " test(\"Increment\", s -> {\n", + " try {\n", + " return Optional.of(\n", + " Integer.parseInt(s) + 1 + \"\");\n", + " } catch (NumberFormatException e) {\n", + " return Optional.of(s);\n", + " }\n", + " });\n", + " test(\"Replace\",\n", + " s -> Optional.of(s.replace(\"2\", \"9\")));\n", + " test(\"Take last digit\",\n", + " s -> Optional.of(s.length() > 0 ?\n", + " s.charAt(s.length() - 1) + \"\"\n", + " : s));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "---( Add brackets )---\n", + "Optional[[12]]\n", + "Optional[[]]\n", + "Optional[[23]]\n", + "Optional[[45]]\n", + "Optional.empty\n", + " ---( Increment )---\n", + "Optional[13]\n", + "Optional[]\n", + "Optional[24]\n", + "Optional[46]\n", + "Optional.empty\n", + " ---( Replace )---\n", + "Optional[19]\n", + "Optional[]\n", + "Optional[93]\n", + "Optional[45]\n", + "Optional.empty\n", + " ---( Take last digit )---\n", + "Optional[2]\n", + "Optional[]\n", + "Optional[3]\n", + "Optional[5]\n", + "Optional.empty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "同 `map()`,`flatMap()` 将提取非空 **Optional** 的内容并将其应用在映射函数。唯一的区别就是 `flatMap()` 不会把结果包装在 **Optional** 中,因为映射函数已经被包装过了。在如上示例中,我们已经在每一个映射函数中显式地完成了包装,但是很显然 `Optional.flatMap()` 是为那些自己已经生成 **Optional** 的函数而设计的。\n", + "\n", + "\n", + "### Optional 流\n", + "\n", + "假设你的生成器可能产生 `null` 值,那么当用它来创建流时,你会自然地想到用 **Optional** 来包装元素。如下是它的样子,代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Signal.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "public class Signal {\n", + " private final String msg;\n", + " public Signal(String msg) { this.msg = msg; }\n", + " public String getMsg() { return msg; }\n", + " @Override\n", + " public String toString() {\n", + " return \"Signal(\" + msg + \")\";\n", + " }\n", + " static Random rand = new Random(47);\n", + " public static Signal morse() {\n", + " switch(rand.nextInt(4)) {\n", + " case 1: return new Signal(\"dot\");\n", + " case 2: return new Signal(\"dash\");\n", + " default: return null;\n", + " }\n", + " }\n", + " public static Stream> stream() {\n", + " return Stream.generate(Signal::morse)\n", + " .map(signal -> Optional.ofNullable(signal));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当我们使用这个流的时候,必须要弄清楚如何解包 **Optional**。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/StreamOfOptionals.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class StreamOfOptionals {\n", + " public static void main(String[] args) {\n", + " Signal.stream()\n", + " .limit(10)\n", + " .forEach(System.out::println);\n", + " System.out.println(\" ---\");\n", + " Signal.stream()\n", + " .limit(10)\n", + " .filter(Optional::isPresent)\n", + " .map(Optional::get)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Optional[Signal(dash)]\n", + "Optional[Signal(dot)]\n", + "Optional[Signal(dash)]\n", + "Optional.empty\n", + "Optional.empty\n", + "Optional[Signal(dash)]\n", + "Optional.empty\n", + "Optional[Signal(dot)]\n", + "Optional[Signal(dash)]\n", + "Optional[Signal(dash)]\n", + "---\n", + "Signal(dot)\n", + "Signal(dot)\n", + "Signal(dash)\n", + "Signal(dash)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。\n", + "\n", + "\n", + "\n", + "## 终端操作\n", + "\n", + "\n", + "以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中所做的最后一件事。\n", + "\n", + "\n", + "\n", + "### 数组\n", + "- `toArray()`:将流转换成适当类型的数组。\n", + "- `toArray(generator)`:在特殊情况下,生成自定义类型的数组。\n", + "\n", + "当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假设我们需要复用流产生的随机数时,就可以这么使用。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/RandInts.java\n", + "package streams;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class RandInts {\n", + " private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray();\n", + " public static IntStream rands() {\n", + " return Arrays.stream(rints);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中。这样一来,每次调用 `rands()` 的时候可以重复获取相同的整数流。\n", + "\n", + "\n", + "### 循环\n", + "\n", + "- `forEach(Consumer)`常见如 `System.out::println` 作为 **Consumer** 函数。\n", + "- `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。\n", + "\n", + "第一种形式:无序操作,仅在引入并行流时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。\n", + "\n", + "`parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。\n", + "\n", + "下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/ForEach.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import static streams.RandInts.*;\n", + "public class ForEach {\n", + " static final int SZ = 14;\n", + " public static void main(String[] args) {\n", + " rands().limit(SZ)\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " rands().limit(SZ)\n", + " .parallel()\n", + " .forEach(n -> System.out.format(\"%d \", n));\n", + " System.out.println();\n", + " rands().limit(SZ)\n", + " .parallel()\n", + " .forEachOrdered(n -> System.out.format(\"%d \", n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "258 555 693 861 961 429 868 200 522 207 288 128 551 589\n", + "551 861 429 589 200 522 555 693 258 128 868 288 961 207\n", + "258 555 693 861 961 429 868 200 522 207 288 128 551 589" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。\n", + "\n", + "在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。\n", + "\n", + "\n", + "\n", + "### 集合\n", + "\n", + "- `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。\n", + "- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。\n", + "\n", + "在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 `java.util.stream.Collectors` 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。\n", + "\n", + "假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过将集合的构造函数引用传递给 `Collectors.toCollection()`,从而构建任何类型的集合。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/TreeSetOfWords.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "public class TreeSetOfWords {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " Set words2 =\n", + " Files.lines(Paths.get(\"TreeSetOfWords.java\"))\n", + " .flatMap(s -> Arrays.stream(s.split(\"\\\\W+\")))\n", + " .filter(s -> !s.matches(\"\\\\d+\")) // No numbers\n", + " .map(String::trim)\n", + " .filter(s -> s.length() > 2)\n", + " .limit(100)\n", + " .collect(Collectors.toCollection(TreeSet::new));\n", + " System.out.println(words2);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[Arrays, Collectors, Exception, Files, Output, Paths,\n", + "Set, String, System, TreeSet, TreeSetOfWords, args,\n", + "class, collect, file, filter, flatMap, get, import,\n", + "java, length, limit, lines, main, map, matches, new,\n", + "nio, numbers, out, println, public, split, static,\n", + "stream, streams, throws, toCollection, trim, util,\n", + "void, words2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果展平映射成为单词流。使用 `matches(\\\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。\n", + "\n", + "\n", + "\n", + "我们也可以在流中生成 **Map**。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/MapCollector.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class Pair {\n", + " public final Character c;\n", + " public final Integer i;\n", + " Pair(Character c, Integer i) {\n", + " this.c = c;\n", + " this.i = i;\n", + " }\n", + " public Character getC() { return c; }\n", + " public Integer getI() { return i; }\n", + " @Override\n", + " public String toString() {\n", + " return \"Pair(\" + c + \", \" + i + \")\";\n", + " }\n", + "}\n", + "class RandomPair {\n", + " Random rand = new Random(47);\n", + " // An infinite iterator of random capital letters:\n", + " Iterator capChars = rand.ints(65,91)\n", + " .mapToObj(i -> (char)i)\n", + " .iterator();\n", + " public Stream stream() {\n", + " return rand.ints(100, 1000).distinct()\n", + " .mapToObj(i -> new Pair(capChars.next(), i));\n", + " }\n", + "}\n", + "public class MapCollector {\n", + " public static void main(String[] args) {\n", + " Map map =\n", + " new RandomPair().stream()\n", + " .limit(8)\n", + " .collect(\n", + " Collectors.toMap(Pair::getI, Pair::getC));\n", + " System.out.println(map);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以这里创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 允许我们在 `stream()` 中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。\n", + "\n", + "在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。\n", + "\n", + "大多数情况下,`java.util.stream.Collectors` 中预设的 **Collector** 就能满足我们的要求。除此之外,你还可以使用第二种形式的 `collect()`。 我把它留作更高级的练习,下例给出基本用法:\n", + "\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/SpecialCollector.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class SpecialCollector {\n", + " public static void main(String[] args) throws Exception {\n", + " ArrayList words =\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .collect(ArrayList::new,\n", + " ArrayList::add,\n", + " ArrayList::addAll);\n", + " words.stream()\n", + " .filter(s -> s.equals(\"cheese\"))\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cheese\n", + "cheese" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。\n", + "\n", + "\n", + "\n", + "### 组合\n", + "\n", + "- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。\n", + "- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。\n", + "- `reduce(identity, BiFunction, BinaryOperator)`:更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。\n", + "\n", + "下面来看下 `reduce` 的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Reduce.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "class Frobnitz {\n", + " int size;\n", + " Frobnitz(int sz) { size = sz; }\n", + " @Override\n", + " public String toString() {\n", + " return \"Frobnitz(\" + size + \")\";\n", + " }\n", + " // Generator:\n", + " static Random rand = new Random(47);\n", + " static final int BOUND = 100;\n", + " static Frobnitz supply() {\n", + " return new Frobnitz(rand.nextInt(BOUND));\n", + " }\n", + "}\n", + "public class Reduce {\n", + " public static void main(String[] args) {\n", + " Stream.generate(Frobnitz::supply)\n", + " .limit(10)\n", + " .peek(System.out::println)\n", + " .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)\n", + " .ifPresent(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Frobnitz(58)\n", + "Frobnitz(55)\n", + "Frobnitz(93)\n", + "Frobnitz(61)\n", + "Frobnitz(61)\n", + "Frobnitz(29)\n", + "Frobnitz(68)\n", + "Frobnitz(0)\n", + "Frobnitz(22)\n", + "Frobnitz(7)\n", + "Frobnitz(29)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。\n", + "\n", + "Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。\n", + "\n", + "`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。当取得第一个长度小于 50 的 `Frobnitz`,只要得到结果就会忽略其他。这是个非常奇怪的约束, 也确实让我们对 `reduce()` 有了更多的了解。\n", + "\n", + "\n", + "### 匹配\n", + "\n", + "- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。\n", + "- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。\n", + "- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。\n", + "\n", + "我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Matching.java\n", + "// Demonstrates short-circuiting of *Match() operations\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "import static streams.RandInts.*;\n", + "\n", + "interface Matcher extends BiPredicate, Predicate> {}\n", + " \n", + "public class Matching {\n", + " static void show(Matcher match, int val) {\n", + " System.out.println(\n", + " match.test(\n", + " IntStream.rangeClosed(1, 9)\n", + " .boxed()\n", + " .peek(n -> System.out.format(\"%d \", n)),\n", + " n -> n < val));\n", + " }\n", + " public static void main(String[] args) {\n", + " show(Stream::allMatch, 10);\n", + " show(Stream::allMatch, 4);\n", + " show(Stream::anyMatch, 2);\n", + " show(Stream::anyMatch, 0);\n", + " show(Stream::noneMatch, 5);\n", + " show(Stream::noneMatch, 0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1 2 3 4 5 6 7 8 9 true\n", + "1 2 3 4 false\n", + "1 true\n", + "1 2 3 4 5 6 7 8 9 false\n", + "1 false\n", + "1 2 3 4 5 6 7 8 9 true" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**BiPredicate** 是一个二元谓词,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。**Matcher** 适用于所有的 **Stream::\\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\\*Match** 函数的调用。\n", + "\n", + "`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。\n", + "\n", + "### 查找\n", + "\n", + "- `findFirst()`:返回第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**。\n", + "- `findAny(`:返回含有任意流元素的 **Optional**,如果流为空返回 **Optional.empty**。\n", + "\n", + "代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/SelectElement.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import static streams.RandInts.*;\n", + "public class SelectElement {\n", + " public static void main(String[] args) {\n", + " System.out.println(rands().findFirst().getAsInt());\n", + " System.out.println(\n", + " rands().parallel().findFirst().getAsInt());\n", + " System.out.println(rands().findAny().getAsInt());\n", + " System.out.println(\n", + " rands().parallel().findAny().getAsInt());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "258\n", + "258\n", + "258\n", + "242" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`findFirst()` 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。\n", + "\n", + "如果必须选择流中最后一个元素,那就使用 `reduce()`。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/LastElement.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class LastElement {\n", + " public static void main(String[] args) {\n", + " OptionalInt last = IntStream.range(10, 20)\n", + " .reduce((n1, n2) -> n2);\n", + " System.out.println(last.orElse(-1));\n", + " // Non-numeric object:\n", + " Optional lastobj =\n", + " Stream.of(\"one\", \"two\", \"three\")\n", + " .reduce((n1, n2) -> n2);\n", + " System.out.println(\n", + " lastobj.orElse(\"Nothing there!\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "19\n", + "three" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字 **Optional** 类型( numeric optional type),否则使用 **Optional** 类型,就像上例中的 `Optional`。\n", + "\n", + "\n", + "\n", + "### 信息\n", + "\n", + "- `count()`:流中的元素个数。\n", + "- `max(Comparator)`:根据所传入的 **Comparator** 所决定的“最大”元素。\n", + "- `min(Comparator)`:根据所传入的 **Comparator** 所决定的“最小”元素。\n", + "\n", + "**String** 类型有预设的 **Comparator** 实现。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/Informational.java\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "public class Informational {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " System.out.println(\n", + " FileToWords.stream(\"Cheese.dat\").count());\n", + " System.out.println(\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .min(String.CASE_INSENSITIVE_ORDER)\n", + " .orElse(\"NONE\"));\n", + " System.out.println(\n", + " FileToWords.stream(\"Cheese.dat\")\n", + " .max(String.CASE_INSENSITIVE_ORDER)\n", + " .orElse(\"NONE\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "32\n", + "a\n", + "you" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`min()` 和 `max()` 的返回类型为 **Optional**,这需要我们使用 `orElse()`来解包。\n", + "\n", + "\n", + "### 数字流信息\n", + "\n", + "- `average()` :求取流元素平均值。\n", + "- `max()` 和 `min()`:数值流操作无需 **Comparator**。\n", + "- `sum()`:对所有流元素进行求和。\n", + "- `summaryStatistics()`:生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// streams/NumericStreamInfo.java\n", + "import java.util.stream.*;\n", + "import static streams.RandInts.*;\n", + "public class NumericStreamInfo {\n", + " public static void main(String[] args) {\n", + " System.out.println(rands().average().getAsDouble());\n", + " System.out.println(rands().max().getAsInt());\n", + " System.out.println(rands().min().getAsInt());\n", + " System.out.println(rands().sum());\n", + " System.out.println(rands().summaryStatistics());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "507.94\n", + "998\n", + "8\n", + "50794\n", + "IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例操作对于 **LongStream** 和 **DoubleStream** 同样适用。\n", + "\n", + "## 本章小结\n", + "\n", + "流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。在本书的剩余部分,我们将尽可能地使用流。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/15-Exceptions.ipynb b/jupyter/15-Exceptions.ipynb new file mode 100644 index 00000000..bd409430 --- /dev/null +++ b/jupyter/15-Exceptions.ipynb @@ -0,0 +1,3518 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十五章 异常\n", + "\n", + "> Java 的基本理念是“结构不佳的代码不能运行”。\n", + "\n", + "改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在 Java 中它显得格外重要,因为 Java 的主要目标之一就是创建供他人使用的程序构件。\n", + "\n", + "发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。\n", + "\n", + "> 要想创建健壮的系统,它的每一个构件都必须是健壮的。\n", + "\n", + "Java 使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。\n", + "\n", + "Java 中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更加确信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。\n", + "\n", + "因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理程序,并将展示当方法出问题的时候,如何产生自定义的异常。\n", + "\n", + "\n", + "\n", + "## 异常概念\n", + "\n", + "C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不关我的事”。所以,程序员不去检查错误情形也就不足为奇了(何况对某些错误情形的检查确实很无聊)。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。\n", + "\n", + "解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++ 的基础之上(尽管看上去更像 Object Pascal)。\n", + "\n", + "“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在那里将作出正确的决定。\n", + "\n", + "异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。\n", + "\n", + "\n", + "\n", + "## 基本异常\n", + "\n", + "异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。\n", + "\n", + "除法就是一个简单的例子。除数有可能为 0,所以先进行检查很有必要。但除数为 0 代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为 0 的情况。但如果这是一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。\n", + "\n", + "当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。\n", + "\n", + "举一个抛出异常的简单例子。对于对象引用 t,传给你的时候可能尚未被初始化。所以在使用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样就把错误信息传播到了“更大”的环境中。这被称为*抛出一个异常*,看起来像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(t == null)\n", + " throw new NullPointerException();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。具体是哪个“地方”后面很快就会介绍。\n", + "\n", + "异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。\n", + "\n", + "异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++ 这样的语言中,这可真是个问题,尤其是 C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略问题,从而会陷入完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。\n", + "\n", + "\n", + "\n", + "### 异常参数\n", + "\n", + "与使用 Java 中的其他对象一样,我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "throw new NullPointerException(\"t = null\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "不久读者将看到,要把这个字符串的内容提取出来可以有多种不同的方法。\n", + "\n", + "关键字 **throw** 将产生许多有趣的结果。在使用 **new** 创建了异常对象之后,此对象的引用将传给 **throw**。尽管异常对象的类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。\n", + "\n", + "抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层级。)\n", + "\n", + "此外,能够抛出任意类型的 **Throwable** 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,唯一的信息只有异常的类型名,而在异常对象内部没有任何有意义的信息。)\n", + "\n", + "## 异常捕获\n", + "\n", + "要明白异常是如何被捕获的,必须首先理解监控区域(guarded region)的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。\n", + "\n", + "### try 语句块\n", + "\n", + "如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,所以称为 try 块。它是跟在 try 关键字之后的普通程序块:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + " // Code that might generate exceptions\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读,因为代码的意图和错误检查不是混淆在一起的。\n", + "\n", + "### 异常处理程序\n", + "\n", + "当然,抛出的异常必须在某处得到处理。这个“地点”就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在 try 块之后,以关键字 catch 表示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + " // Code that might generate exceptions\n", + "} catch(Type1 id1) {\n", + " // Handle exceptions of Type1\n", + "} catch(Type2 id2) {\n", + " // Handle exceptions of Type2\n", + "} catch(Type3 id3) {\n", + " // Handle exceptions of Type3\n", + "}\n", + "// etc." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个 catch 子句(异常处理程序)看起来就像是接收且仅接收一个特殊类型的参数的方法。可以在处理程序的内部使用标识符(id1,id2 等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。\n", + "\n", + "异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句结束,则处理程序的查找过程结束。注意,只有匹配的 catch 子句才能得到执行;这与 switch 语句不同,switch 语句需要在每一个 case 后面跟一个 break,以避免执行后续的 case 子句。\n", + "\n", + "注意在 try 块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只需要提供一个针对此类型的异常处理程序。\n", + "\n", + "### 终止与恢复\n", + "\n", + "异常处理理论上有两种基本模型。Java 支持终止模型(它是 Java 和 C++所支持的模型)。在这种模型中,将假设错误非常严重,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。\n", + "\n", + "另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用 Java 实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者,把 try 块放在 while 循环里,这样就不断地进入 try 块,直到得到满意的结果。\n", + "\n", + "在过去,使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。\n", + "\n", + "\n", + "\n", + "## 自定义异常\n", + "\n", + "不必拘泥于 Java 中已有的异常类型。Java 提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。\n", + "\n", + "要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生无参构造器,所以这几乎不用写多少代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/InheritingExceptions.java\n", + "// Creating your own exceptions\n", + "class SimpleException extends Exception {}\n", + "\n", + "public class InheritingExceptions {\n", + " public void f() throws SimpleException {\n", + " System.out.println(\n", + " \"Throw SimpleException from f()\");\n", + " throw new SimpleException();\n", + " }\n", + " public static void main(String[] args) {\n", + " InheritingExceptions sed =\n", + " new InheritingExceptions();\n", + " try {\n", + " sed.f();\n", + " } catch(SimpleException e) {\n", + " System.out.println(\"Caught it!\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Throw SimpleException from f()\n", + "Caught it!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器创建了无参构造器,它将自动调用基类的无参构造器。本例中不会得到像 SimpleException(String) 这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。\n", + "\n", + "本例的结果被打印到了控制台上,本书的输出显示系统正是在控制台上自动地捕获和测试这些结果的。但是,你也许想通过写入 System.err 而将错误发送给标准错误流。通常这比把错误信息输出到 System.out 要好,因为 System.out 也许会被重定向。如果把结果送到 System.err,它就不会随 System.out 一起被重定向,这样更容易被用户注意。\n", + "\n", + "你也可以为异常类创建一个接受字符串参数的构造器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/FullConstructors.java\n", + "class MyException extends Exception {\n", + " MyException() {}\n", + " MyException(String msg) { super(msg); }\n", + "}\n", + "public class FullConstructors {\n", + " public static void f() throws MyException {\n", + " System.out.println(\"Throwing MyException from f()\");\n", + " throw new MyException();\n", + " }\n", + " public static void g() throws MyException {\n", + " System.out.println(\"Throwing MyException from g()\");\n", + " throw new MyException(\"Originated in g()\");\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " f();\n", + " } catch(MyException e) {\n", + " e.printStackTrace(System.out);\n", + " }\n", + " try {\n", + " g();\n", + " } catch(MyException e) {\n", + " e.printStackTrace(System.out);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Throwing MyException from f()\n", + "MyException\n", + " at FullConstructors.f(FullConstructors.java:11)\n", + " at\n", + "FullConstructors.main(FullConstructors.java:19)\n", + "Throwing MyException from g()\n", + "MyException: Originated in g()\n", + " at FullConstructors.g(FullConstructors.java:15)\n", + " at\n", + "FullConstructors.main(FullConstructors.java:24)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。\n", + "\n", + "在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace() 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "e.printStackTrace();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "信息就会被输出到标准错误流。\n", + "\n", + "### 异常与记录日志\n", + "\n", + "你可能还想使用 java.util.logging 工具将输出记录到日志中。基本的日志记录功能还是相当简单易懂的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/LoggingExceptions.java\n", + "// An exception that reports through a Logger\n", + "// {ErrorOutputExpected}\n", + "import java.util.logging.*;\n", + "import java.io.*;\n", + "class LoggingException extends Exception {\n", + " private static Logger logger =\n", + " Logger.getLogger(\"LoggingException\");\n", + " LoggingException() {\n", + " StringWriter trace = new StringWriter();\n", + " printStackTrace(new PrintWriter(trace));\n", + " logger.severe(trace.toString());\n", + " }\n", + "}\n", + "public class LoggingExceptions {\n", + " public static void main(String[] args) {\n", + " try {\n", + " throw new LoggingException();\n", + " } catch(LoggingException e) {\n", + " System.err.println(\"Caught \" + e);\n", + " }\n", + " try {\n", + " throw new LoggingException();\n", + " } catch(LoggingException e) {\n", + " System.err.println(\"Caught \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "___[ Error Output ]___\n", + "May 09, 2017 6:07:17 AM LoggingException \n", + "SEVERE: LoggingException\n", + "at\n", + "LoggingExceptions.main(LoggingExceptions.java:20)\n", + "Caught LoggingException\n", + "May 09, 2017 6:07:17 AM LoggingException \n", + "SEVERE: LoggingException\n", + "at\n", + "LoggingExceptions.main(LoggingExceptions.java:25)\n", + "Caught LoggingException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一个 java.io.PrintWriter 对象作为参数(PrintWriter 会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。\n", + "\n", + "尽管由于 LoggingException 将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便,并因此不需要客户端程序员的干预就可以自动运行,但是更常见的情形是我们需要捕获和记录其他人编写的异常,因此我们必须在异常处理程序中生成日志消息;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/LoggingExceptions2.java\n", + "// Logging caught exceptions\n", + "// {ErrorOutputExpected}\n", + "import java.util.logging.*;\n", + "import java.io.*;\n", + "public class LoggingExceptions2 {\n", + " private static Logger logger =\n", + " Logger.getLogger(\"LoggingExceptions2\");\n", + " static void logException(Exception e) {\n", + " StringWriter trace = new StringWriter();\n", + " e.printStackTrace(new PrintWriter(trace));\n", + " logger.severe(trace.toString());\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " throw new NullPointerException();\n", + " } catch(NullPointerException e) {\n", + " logException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "___[ Error Output ]___\n", + "May 09, 2017 6:07:17 AM LoggingExceptions2 logException\n", + "SEVERE: java.lang.NullPointerException\n", + "at\n", + "LoggingExceptions2.main(LoggingExceptions2.java:17)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "还可以更进一步自定义异常,比如加入额外的构造器和成员:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/ExtraFeatures.java\n", + "// Further embellishment of exception classes\n", + "class MyException2 extends Exception {\n", + " private int x;\n", + " MyException2() {}\n", + " MyException2(String msg) { super(msg); }\n", + " MyException2(String msg, int x) {\n", + " super(msg);\n", + " this.x = x;\n", + " }\n", + " public int val() { return x; }\n", + " @Override\n", + " public String getMessage() {\n", + " return \"Detail Message: \"+ x\n", + " + \" \"+ super.getMessage();\n", + " }\n", + "}\n", + "public class ExtraFeatures {\n", + " public static void f() throws MyException2 {\n", + " System.out.println(\n", + " \"Throwing MyException2 from f()\");\n", + " throw new MyException2();\n", + " }\n", + " public static void g() throws MyException2 {\n", + " System.out.println(\n", + " \"Throwing MyException2 from g()\");\n", + " throw new MyException2(\"Originated in g()\");\n", + " }\n", + " public static void h() throws MyException2 {\n", + " System.out.println(\n", + " \"Throwing MyException2 from h()\");\n", + " throw new MyException2(\"Originated in h()\", 47);\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " f();\n", + " } catch(MyException2 e) {\n", + " e.printStackTrace(System.out);\n", + " }\n", + " try {\n", + " g();\n", + " } catch(MyException2 e) {\n", + " e.printStackTrace(System.out);\n", + " }\n", + " try {\n", + " h();\n", + " } catch(MyException2 e) {\n", + " e.printStackTrace(System.out);\n", + " System.out.println(\"e.val() = \" + e.val());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Throwing MyException2 from f()\n", + "MyException2: Detail Message: 0 null\n", + "at ExtraFeatures.f(ExtraFeatures.java:24)\n", + "at ExtraFeatures.main(ExtraFeatures.java:38)\n", + "Throwing MyException2 from g()\n", + "MyException2: Detail Message: 0 Originated in g()\n", + "at ExtraFeatures.g(ExtraFeatures.java:29)\n", + "at ExtraFeatures.main(ExtraFeatures.java:43)\n", + "Throwing MyException2 from h()\n", + "MyException2: Detail Message: 47 Originated in h()\n", + "at ExtraFeatures.h(ExtraFeatures.java:34)\n", + "at ExtraFeatures.main(ExtraFeatures.java:48)\n", + "e.val() = 47" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "新的异常添加了字段 x 以及设定 x 值的构造器和读取数据的方法。此外,还覆盖了 Throwable.\n", + "getMessage() 方法,以产生更详细的信息。对于异常类来说,getMessage() 方法有点类似于 toString() 方法。\n", + "\n", + "既然异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数 Java 库里的异常都是这么用的),所以对异常所添加的其他功能也许根本用不上。\n", + "\n", + "## 异常声明\n", + "\n", + "Java 鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找 throw 语句来获知相关信息,然而程序库通常并不与源代码一起发布。为了预防这样的问题,Java 提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。\n", + "\n", + "异常说明使用了附加的关键字 throws,后面接一个所有潜在异常类型的列表,所以方法定义可能看起来像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void f() throws TooBig, TooSmall, DivZero { // ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是,要是这样写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void f() { // ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "就表示此方法不会抛出任何异常(除了从 RuntimeException 继承的异常,它们可以在没有异常说明的情况下被抛出,这些将在后面进行讨论)。\n", + "\n", + "代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,Java 在编译时就可以保证一定水平的异常正确性。\n", + "\n", + "不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。\n", + "\n", + "这种在编译时被强制检查的异常称为被检查的异常。\n", + "\n", + "## 捕获所有异常\n", + "\n", + "可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类 Exception,就可以做到这一点(事实上还有其他的基类,但 Exception 是所有编程行为相关的基类):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "catch(Exception e) {\n", + " System.out.println(\"Caught an exception\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。\n", + "\n", + "因为 Exception 是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息,不过可以调用它从其基类 Throwable 继承的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "String getMessage()\n", + "String getLocalizedMessage()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "用来获取详细信息,或用本地语言表示的详细信息。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "String toString()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "返回对 Throwable 的简单描述,要是有详细信息的话,也会把它包含在内。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "void printStackTrace()\n", + "void printStackTrace(PrintStream)\n", + "void printStackTrace(java.io.PrintWriter)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "打印 Throwable 和 Throwable 的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。其中第一个版本输出到标准错误,后两个版本允许选择要输出的流(在[附录 I/O 流 ]() 中,你将会理解为什么有两种不同的流)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Throwable fillInStackTrace()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。\n", + "\n", + "此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass)也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName)方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。\n", + "\n", + "下面的例子演示了如何使用 Exception 类型的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/ExceptionMethods.java\n", + "// Demonstrating the Exception Methods\n", + "public class ExceptionMethods {\n", + " public static void main(String[] args) {\n", + " try {\n", + " throw new Exception(\"My Exception\");\n", + " } catch(Exception e) {\n", + " System.out.println(\"Caught Exception\");\n", + " System.out.println(\n", + " \"getMessage():\" + e.getMessage());\n", + " System.out.println(\"getLocalizedMessage():\" +\n", + " e.getLocalizedMessage());\n", + " System.out.println(\"toString():\" + e);\n", + " System.out.println(\"printStackTrace():\");\n", + " e.printStackTrace(System.out);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Caught Exception\n", + "getMessage():My Exception\n", + "getLocalizedMessage():My Exception\n", + "toString():java.lang.Exception: My Exception\n", + "printStackTrace():\n", + "java.lang.Exception: My Exception\n", + "at\n", + "ExceptionMethods.main(ExceptionMethods.java:7)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。\n", + "\n", + "### 多重捕获\n", + "\n", + "如果有一组具有相同基类的异常,你想使用同一方式进行捕获,那你直接 catch 它们的基类型。但是,如果这些异常没有共同的基类型,在 Java 7 之前,你必须为每一个类型编写一个 catch:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/SameHandler.java\n", + "class EBase1 extends Exception {}\n", + "class Except1 extends EBase1 {}\n", + "class EBase2 extends Exception {}\n", + "class Except2 extends EBase2 {}\n", + "class EBase3 extends Exception {}\n", + "class Except3 extends EBase3 {}\n", + "class EBase4 extends Exception {}\n", + "class Except4 extends EBase4 {}\n", + "\n", + "public class SameHandler {\n", + " void x() throws Except1, Except2, Except3, Except4 {}\n", + " void process() {}\n", + " void f() {\n", + " try {\n", + " x();\n", + " } catch(Except1 e) {\n", + " process();\n", + " } catch(Except2 e) {\n", + " process();\n", + " } catch(Except3 e) {\n", + " process();\n", + " } catch(Except4 e) {\n", + " process();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过 Java 7 的多重捕获机制,你可以使用“或”将不同类型的异常组合起来,只需要一行 catch 语句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/MultiCatch.java\n", + "public class MultiCatch {\n", + " void x() throws Except1, Except2, Except3, Except4 {}\n", + " void process() {}\n", + " void f() {\n", + " try {\n", + " x();\n", + " \n", + " } catch(Except1 | Except2 | Except3 | Except4 e) {\n", + " process();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "或者以其他的组合方式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/MultiCatch2.java\n", + "public class MultiCatch2 {\n", + " void x() throws Except1, Except2, Except3, Except4 {}\n", + " void process1() {}\n", + " void process2() {}\n", + " void f() {\n", + " try {\n", + " x();\n", + " } catch(Except1 | Except2 e) {\n", + " process1();\n", + " } catch(Except3 | Except4 e) {\n", + " process2();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这对书写更整洁的代码很有帮助。\n", + "\n", + "### 栈轨迹\n", + "\n", + "printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个 Throwable 被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。下面的程序是一个简单的演示示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/WhoCalled.java\n", + "// Programmatic access to stack trace information\n", + "public class WhoCalled {\n", + " static void f() {\n", + "// Generate an exception to fill in the stack trace\n", + " try {\n", + " throw new Exception();\n", + " } catch(Exception e) {\n", + " for(StackTraceElement ste : e.getStackTrace())\n", + " System.out.println(ste.getMethodName());\n", + " }\n", + " }\n", + " static void g() { f(); }\n", + " static void h() { g(); }\n", + " public static void main(String[] args) {\n", + " f();\n", + " System.out.println(\"*******\");\n", + " g();\n", + " System.out.println(\"*******\");\n", + " h();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "f\n", + "main\n", + "*******\n", + "f\n", + "g\n", + "main\n", + "*******\n", + "f\n", + "g\n", + "h\n", + "main" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,我们只打印了方法名,但实际上还可以打印整个 StackTraceElement,它包含其他附加的信息。\n", + "\n", + "### 重新抛出异常\n", + "\n", + "有时希望把刚捕获的异常重新抛出,尤其是在使用 Exception 捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "catch(Exception e) {\n", + " System.out.println(\"An exception was thrown\");\n", + " throw e;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "重抛异常会把异常抛给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。\n", + "\n", + "如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 filInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/Rethrowing.java\n", + "// Demonstrating fillInStackTrace()\n", + "public class Rethrowing {\n", + " public static void f() throws Exception {\n", + " System.out.println(\n", + " \"originating the exception in f()\");\n", + " throw new Exception(\"thrown from f()\");\n", + " }\n", + " public static void g() throws Exception {\n", + " try {\n", + " f();\n", + " } catch(Exception e) {\n", + " System.out.println(\n", + " \"Inside g(), e.printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " throw e;\n", + " }\n", + " }\n", + " public static void h() throws Exception {\n", + " try {\n", + " f();\n", + " } catch(Exception e) {\n", + " System.out.println(\n", + " \"Inside h(), e.printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " throw (Exception)e.fillInStackTrace();\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " g();\n", + " } catch(Exception e) {\n", + " System.out.println(\"main: printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " }\n", + " try {\n", + " h();\n", + " } catch(Exception e) {\n", + " System.out.println(\"main: printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "originating the exception in f()\n", + "Inside g(), e.printStackTrace()\n", + "java.lang.Exception: thrown from f()\n", + "at Rethrowing.f(Rethrowing.java:8)\n", + "at Rethrowing.g(Rethrowing.java:12)\n", + "at Rethrowing.main(Rethrowing.java:32)\n", + "main: printStackTrace()\n", + "java.lang.Exception: thrown from f()\n", + "at Rethrowing.f(Rethrowing.java:8)\n", + "at Rethrowing.g(Rethrowing.java:12)\n", + "at Rethrowing.main(Rethrowing.java:32)\n", + "originating the exception in f()\n", + "Inside h(), e.printStackTrace()\n", + "java.lang.Exception: thrown from f()\n", + "at Rethrowing.f(Rethrowing.java:8)\n", + "at Rethrowing.h(Rethrowing.java:22)\n", + "at Rethrowing.main(Rethrowing.java:38)\n", + "main: printStackTrace()\n", + "java.lang.Exception: thrown from f()\n", + "at Rethrowing.h(Rethrowing.java:27)\n", + "at Rethrowing.main(Rethrowing.java:38)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "调用 fillInStackTrace() 的那一行就成了异常的新发生地了。\n", + "\n", + "有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用 filInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/RethrowNew.java\n", + "// Rethrow a different object from the one you caught\n", + "class OneException extends Exception {\n", + " OneException(String s) { super(s); }\n", + "}\n", + "class TwoException extends Exception {\n", + " TwoException(String s) { super(s); }\n", + "}\n", + "public class RethrowNew {\n", + " public static void f() throws OneException {\n", + " System.out.println(\n", + " \"originating the exception in f()\");\n", + " throw new OneException(\"thrown from f()\");\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " try {\n", + " f();\n", + " } catch(OneException e) {\n", + " System.out.println(\n", + " \"Caught in inner try, e.printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " throw new TwoException(\"from inner try\");\n", + " }\n", + " } catch(TwoException e) {\n", + " System.out.println(\n", + " \"Caught in outer try, e.printStackTrace()\");\n", + " e.printStackTrace(System.out);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "originating the exception in f()\n", + "Caught in inner try, e.printStackTrace()\n", + "OneException: thrown from f()\n", + "at RethrowNew.f(RethrowNew.java:16)\n", + "at RethrowNew.main(RethrowNew.java:21)\n", + "Caught in outer try, e.printStackTrace()\n", + "TwoException: from inner try\n", + "at RethrowNew.main(RethrowNew.java:26)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最后那个异常仅知道自己来自 main(),而对 f() 一无所知。\n", + "\n", + "永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。\n", + "\n", + "### 精准的重新抛出异常\n", + "\n", + "在 Java 7 之前,如果遇到异常,则只能重新抛出该类型的异常。这导致在 Java 7 中修复的代码不精确。所以在 Java 7 之前,这无法编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class BaseException extends Exception {}\n", + "class DerivedException extends BaseException {}\n", + "\n", + "public class PreciseRethrow {\n", + " void catcher() throws DerivedException {\n", + " try {\n", + " throw new DerivedException();\n", + " } catch(BaseException e) {\n", + " throw e;\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 catch 捕获了一个 BaseException,编译器强迫你声明 catcher() 抛出 BaseException,即使它实际上抛出了更具体的 DerivedException。从 Java 7 开始,这段代码就可以编译,这是一个很小但很有用的修复。\n", + "\n", + "### 异常链\n", + "\n", + "常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在 JDK1.4 以前,程序员必须自己编写代码来保存原始异常的信息。现在所有 Throwable 的子类在构造器中都可以接受一个 cause(因由)对象作为参数。这个 cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。\n", + "\n", + "有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause0 方法而不是构造器。\n", + "\n", + "下面的例子能让你在运行时动态地向 DymamicFields 对象添加字段:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/DynamicFields.java\n", + "// A Class that dynamically adds fields to itself to\n", + "// demonstrate exception chaining\n", + "class DynamicFieldsException extends Exception {}\n", + "public class DynamicFields {\n", + " private Object[][] fields;\n", + " public DynamicFields(int initialSize) {\n", + " fields = new Object[initialSize][2];\n", + " for(int i = 0; i < initialSize; i++)\n", + " fields[i] = new Object[] { null, null };\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " StringBuilder result = new StringBuilder();\n", + " for(Object[] obj : fields) {\n", + " result.append(obj[0]);\n", + " result.append(\": \");\n", + " result.append(obj[1]);\n", + " result.append(\"\\n\");\n", + " }\n", + " return result.toString();\n", + " }\n", + " private int hasField(String id) {\n", + " for(int i = 0; i < fields.length; i++)\n", + " if(id.equals(fields[i][0]))\n", + " return i;\n", + " return -1;\n", + " }\n", + " private int getFieldNumber(String id)\n", + " throws NoSuchFieldException {\n", + " int fieldNum = hasField(id);\n", + " if(fieldNum == -1)\n", + " throw new NoSuchFieldException();\n", + " return fieldNum;\n", + " }\n", + " private int makeField(String id) {\n", + " for(int i = 0; i < fields.length; i++)\n", + " if(fields[i][0] == null) {\n", + " fields[i][0] = id;\n", + " return i;\n", + " }\n", + "// No empty fields. Add one:\n", + " Object[][] tmp = new Object[fields.length + 1][2];\n", + " for(int i = 0; i < fields.length; i++)\n", + " tmp[i] = fields[i];\n", + " for(int i = fields.length; i < tmp.length; i++)\n", + " tmp[i] = new Object[] { null, null };\n", + " fields = tmp;\n", + "// Recursive call with expanded fields:\n", + " return makeField(id);\n", + " }\n", + " public Object\n", + " getField(String id) throws NoSuchFieldException {\n", + " return fields[getFieldNumber(id)][1];\n", + " }\n", + " public Object setField(String id, Object value)\n", + " throws DynamicFieldsException {\n", + " if(value == null) {\n", + "// Most exceptions don't have a \"cause\"\n", + "// constructor. In these cases you must use\n", + "// initCause(), available in all\n", + "// Throwable subclasses.\n", + " DynamicFieldsException dfe =\n", + " new DynamicFieldsException();\n", + " dfe.initCause(new NullPointerException());\n", + " throw dfe;\n", + " }\n", + " int fieldNumber = hasField(id);\n", + " if(fieldNumber == -1)\n", + " fieldNumber = makeField(id);\n", + " Object result = null;\n", + " try {\n", + " result = getField(id); // Get old value\n", + " } catch(NoSuchFieldException e) {\n", + "// Use constructor that takes \"cause\":\n", + " throw new RuntimeException(e);\n", + " }\n", + " fields[fieldNumber][1] = value;\n", + " return result;\n", + " }\n", + " public static void main(String[] args) {\n", + " DynamicFields df = new DynamicFields(3);\n", + " System.out.println(df);\n", + " try {\n", + " df.setField(\"d\", \"A value for d\");\n", + " df.setField(\"number\", 47);\n", + " df.setField(\"number2\", 48);\n", + " System.out.println(df);\n", + " df.setField(\"d\", \"A new value for d\");\n", + " df.setField(\"number3\", 11);\n", + " System.out.println(\"df: \" + df);\n", + " System.out.println(\"df.getField(\\\"d\\\") : \"\n", + " + df.getField(\"d\"));\n", + " Object field =\n", + " df.setField(\"d\", null); // Exception\n", + " } catch(NoSuchFieldException |\n", + " DynamicFieldsException e) {\n", + " e.printStackTrace(System.out);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "null: null\n", + "null: null\n", + "null: null\n", + "d: A value for d\n", + "number: 47\n", + "number2: 48\n", + "df: d: A new value for d\n", + "number: 47\n", + "number2: 48\n", + "number3: 11\n", + "df.getField(\"d\") : A new value for d\n", + "DynamicFieldsException\n", + "at\n", + "DynamicFields.setField(DynamicFields.java:65)\n", + "at DynamicFields.main(DynamicFields.java:97)\n", + "Caused by: java.lang.NullPointerException\n", + "at\n", + "DynamicFields.setField(DynamicFields.java:67)\n", + "... 1 more" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个 DynamicFields 对象都含有一个数组,其元素是“成对的对象”。第一个对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。当创建对象的时候,要合理估计一下需要多少字段。当调用 setField() 方法的时候,它将试图通过标识修改已有字段值,否则就建一个新的字段,并把值放入。如果空间不够了,将建立一个更长的数组,并把原来数组的元素复制进去。如果你试图为字段设置一个空值,将抛出一个 DynamicFieldsException 异常,它是通过使用 initCause() 方法把 NullPointerException 对象插入而建立的。\n", + "\n", + "至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField0 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。\n", + "\n", + "你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。\n", + "\n", + "主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,并结合“或(|)”符号。此 Java 7 功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。\n", + "\n", + "\n", + "\n", + "## Java 标准异常\n", + "\n", + "Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从 Throwable 继承而得到的类型):Error 用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception。要想对异常有全面的了解,最好去浏览一下 HTML 格式的 Java 文档(可以从 java.sun.com 下载)。为了对不同的异常有个感性的认识,这么做是值得的。但很快你就会发现,这些异常除了名称外其实都差不多。同时,Java 中异常的数目在持续增加,所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说,关键是理解概念以及如何使用。\n", + "\n", + "异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从 java.io.IOException 继承而来的。\n", + "\n", + "### 特例:RuntimeException\n", + "\n", + "在本章的第一个例子中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(t == null)\n", + " throw new NullPointerException();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果必须对传递给方法的每个引用都检查其是否为 null(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。\n", + "\n", + "属于运行时异常的类型有很多,它们会自动被 java 虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。\n", + "\n", + "RuntimeException 代表的是编程错误:\n", + "\n", + "1. 无法预料的错误。比如从你控制范围之外传递进来的 null 引用。\n", + "2. 作为程序员,应该在代码中进行检查的错误。(比如对于 ArrayIndexOutOfBoundsException,就得注意一下数组的大小了。)在一个地方发生的异常,常常会在另一个地方导致错误。\n", + "\n", + "在这些情况下使用异常很有好处,它们能给调试带来便利。\n", + "\n", + "如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException 类型的异常也许会穿越所有的执行路径直达 main() 方法,而不会被捕获。要明白到底发生了什么,可以试试下面的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/NeverCaught.java\n", + "// Ignoring RuntimeExceptions\n", + "// {ThrowsException}\n", + "public class NeverCaught {\n", + " static void f() {\n", + " throw new RuntimeException(\"From f()\");\n", + " }\n", + " static void g() {\n", + " f();\n", + " }\n", + " public static void main(String[] args) {\n", + " g();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "___[ Error Output ]___\n", + "Exception in thread \"main\" java.lang.RuntimeException:\n", + "From f()\n", + "at NeverCaught.f(NeverCaught.java:7)\n", + "at NeverCaught.g(NeverCaught.java:10)\n", + "at NeverCaught.main(NeverCaught.java:13)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果 RuntimeException 没有被捕获而直达 main(),那么在程序退出前将调用异常的 printStackTrace() 方法。\n", + "\n", + "你会发现,RuntimeException(或任何从它继承的异常)是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了 System.err。\n", + "\n", + "请务必记住:只能在代码中忽略 RuntimeException(及其子类)类型的异常,因为所有受检查类型异常的处理都是由编译器强制实施的。\n", + "\n", + "值得注意的是:不应把 Java 的异常处理机制当成是单一用途的工具。是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于发现某些编译器无法检测到的编程错误,也是非常重要的。\n", + "\n", + "\n", + "\n", + "## 使用 finally 进行清理\n", + "\n", + "有一些代码片段,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成),为了达到这个效果,可以在异常处理程序后面加上 finally 子句。完整的异常处理程序看起来像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + "// The guarded region: Dangerous activities\n", + "// that might throw A, B, or C\n", + "} catch(A a1) {\n", + "// Handler for situation A\n", + "} catch(B b1) {\n", + "// Handler for situation B\n", + "} catch(C c1) {\n", + "// Handler for situation C\n", + "} finally {\n", + "// Activities that happen every time\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了证明 finally 子句总能运行,可以试试下面这个程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/FinallyWorks.java\n", + "// The finally clause is always executed\n", + "class ThreeException extends Exception {}\n", + "public class FinallyWorks {\n", + " static int count = 0;\n", + " public static void main(String[] args) {\n", + " while(true) {\n", + " try {\n", + "\t\t\t\t// Post-increment is zero first time:\n", + " if(count++ == 0)\n", + " throw new ThreeException();\n", + " System.out.println(\"No exception\");\n", + " } catch(ThreeException e) {\n", + " System.out.println(\"ThreeException\");\n", + " } finally {\n", + " System.out.println(\"In finally clause\");\n", + " if(count == 2) break; // out of \"while\"\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ThreeException\n", + "In finally clause\n", + "No exception\n", + "In finally clause" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以从输出中发现,无论异常是否被抛出,finally 子句总能被执行。这个程序也给了我们一些思路,当 Java 中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把 try 块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个 static 类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。\n", + "\n", + "### finally 用来做什么?\n", + "\n", + "对于没有垃圾回收和析构函数自动调用机制的语言来说,finally 非常重要。它能使程序员保证:无论 try 块里发生了什么,内存总能得到释放。但 Java 有垃圾回收机制,所以内存释放不再是问题。而且,Java 也没有析构函数可供调用。那么,Java 在什么情况下才能用到 finally 呢?\n", + "\n", + "当要把除内存之外的资源恢复到它们的初始状态时,就要用到 finally 子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关,如下面例子所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/Switch.java\n", + "public class Switch {\n", + " private boolean state = false;\n", + " public boolean read() { return state; }\n", + " public void on() {\n", + " state = true;\n", + " System.out.println(this);\n", + " }\n", + " public void off() {\n", + " state = false;\n", + " System.out.println(this);\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return state ? \"on\" : \"off\";\n", + " }\n", + "}\n", + "// exceptions/OnOffException1.java\n", + "public class OnOffException1 extends Exception {}\n", + "// exceptions/OnOffException2.java\n", + "public class OnOffException2 extends Exception {}\n", + "// exceptions/OnOffSwitch.java\n", + "// Why use finally?\n", + "public class OnOffSwitch {\n", + " private static Switch sw = new Switch();\n", + " public static void f()\n", + " throws OnOffException1, OnOffException2 {}\n", + " public static void main(String[] args) {\n", + " try {\n", + " sw.on();\n", + "\t\t\t// Code that can throw exceptions...\n", + " f();\n", + " sw.off();\n", + " } catch(OnOffException1 e) {\n", + " System.out.println(\"OnOffException1\");\n", + " sw.off();\n", + " } catch(OnOffException2 e) {\n", + " System.out.println(\"OnOffException2\");\n", + " sw.off();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "on\n", + "off" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "程序的目的是要确保 main() 结束的时候开关必须是关闭的,所以在每个 try 块和异常处理程序的末尾都加入了对 sw.off() 方法的调用。但也可能有这种情况:异常被抛出,但没被处理程序捕获,这时 sw.off() 就得不到调用。但是有了 finally,只要把 try 块中的清理代码移放在一处即可:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/WithFinally.java\n", + "// Finally Guarantees cleanup\n", + "public class WithFinally {\n", + " static Switch sw = new Switch();\n", + " public static void main(String[] args) {\n", + " try {\n", + " sw.on();\n", + "\t\t\t// Code that can throw exceptions...\n", + " OnOffSwitch.f();\n", + " } catch(OnOffException1 e) {\n", + " System.out.println(\"OnOffException1\");\n", + " } catch(OnOffException2 e) {\n", + " System.out.println(\"OnOffException2\");\n", + " } finally {\n", + " sw.off();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "on\n", + "off" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里 sw.off() 被移到一处,并且保证在任何情况下都能得到执行。\n", + "\n", + "甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行 finally 子句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/AlwaysFinally.java\n", + "// Finally is always executed\n", + "class FourException extends Exception {}\n", + "public class AlwaysFinally {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Entering first try block\");\n", + " try {\n", + " System.out.println(\"Entering second try block\");\n", + " try {\n", + " throw new FourException();\n", + " } finally {\n", + " System.out.println(\"finally in 2nd try block\");\n", + " }\n", + " } catch(FourException e) {\n", + " System.out.println(\n", + " \"Caught FourException in 1st try block\");\n", + " } finally {\n", + " System.out.println(\"finally in 1st try block\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Entering first try block\n", + "Entering second try block\n", + "finally in 2nd try block\n", + "Caught FourException in 1st try block\n", + "finally in 1st try block" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当涉及 break 和 continue 语句的时候,finally 子句也会得到执行。请注意,如果把 finally 子句和带标签的 break 及 continue 配合使用,在 Java 里就没必要使用 goto 语句了。\n", + "\n", + "### 在 return 中使用 finally\n", + "\n", + "因为 finally 子句总是会执行,所以可以从一个方法内的多个点返回,仍然能保证重要的清理工作会执行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/MultipleReturns.java\n", + "public class MultipleReturns {\n", + " public static void f(int i) {\n", + " System.out.println(\n", + " \"Initialization that requires cleanup\");\n", + " try {\n", + " System.out.println(\"Point 1\");\n", + " if(i == 1) return;\n", + " System.out.println(\"Point 2\");\n", + " if(i == 2) return;\n", + " System.out.println(\"Point 3\");\n", + " if(i == 3) return;\n", + " System.out.println(\"End\");\n", + " return;\n", + " } finally {\n", + " System.out.println(\"Performing cleanup\");\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " for(int i = 1; i <= 4; i++)\n", + " f(i);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Initialization that requires cleanup\n", + "Point 1\n", + "Performing cleanup\n", + "Initialization that requires cleanup\n", + "Point 1\n", + "Point 2\n", + "Performing cleanup\n", + "Initialization that requires cleanup\n", + "Point 1\n", + "Point 2\n", + "Point 3\n", + "Performing cleanup\n", + "Initialization that requires cleanup\n", + "Point 1\n", + "Point 2\n", + "Point 3\n", + "End\n", + "Performing cleanup" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从输出中可以看出,从何处返回无关紧要,finally 子句永远会执行。\n", + "\n", + "### 缺憾:异常丢失\n", + "\n", + "遗憾的是,Java 的异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但它还是有可能被轻易地忽略。用某些特殊的方式使用 finally 子句,就会发生这种情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/LostMessage.java\n", + "// How an exception can be lost\n", + "class VeryImportantException extends Exception {\n", + " @Override\n", + " public String toString() {\n", + " return \"A very important exception!\";\n", + " }\n", + "}\n", + "class HoHumException extends Exception {\n", + " @Override\n", + " public String toString() {\n", + " return \"A trivial exception\";\n", + " }\n", + "}\n", + "public class LostMessage {\n", + " void f() throws VeryImportantException {\n", + " throw new VeryImportantException();\n", + " }\n", + " void dispose() throws HoHumException {\n", + " throw new HoHumException();\n", + " }\n", + " public static void main(String[] args) {\n", + " try {\n", + " LostMessage lm = new LostMessage();\n", + " try {\n", + " lm.f();\n", + " } finally {\n", + " lm.dispose();\n", + " }\n", + " } catch(VeryImportantException | HoHumException e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "A trivial exception" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从输出中可以看到,VeryImportantException 不见了,它被 finally 子句里的 HoHumException 所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察觉的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上例中的 dispose() 方法,全部打包放到 try-catch 子句里面)。\n", + "\n", + "一种更加简单的丢失异常的方式是从 finally 子句中返回:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/ExceptionSilencer.java\n", + "public class ExceptionSilencer {\n", + " public static void main(String[] args) {\n", + " try {\n", + " throw new RuntimeException();\n", + " } finally {\n", + " // Using 'return' inside the finally block\n", + " // will silence any thrown exception.\n", + " return;\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果运行这个程序,就会看到即使方法里抛出了异常,它也不会产生任何输出。\n", + "\n", + "\n", + "\n", + "## 异常限制\n", + "\n", + "当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,若当基类使用的代码应用到其派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。\n", + "\n", + "下面例子演示了这种(在编译时)施加在异常上面的限制:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/StormyInning.java\n", + "// Overridden methods can throw only the exceptions\n", + "// specified in their base-class versions, or exceptions\n", + "// derived from the base-class exceptions\n", + "class BaseballException extends Exception {}\n", + "class Foul extends BaseballException {}\n", + "class Strike extends BaseballException {}\n", + "abstract class Inning {\n", + " Inning() throws BaseballException {}\n", + " public void event() throws BaseballException {\n", + "// Doesn't actually have to throw anything\n", + " }\n", + " public abstract void atBat() throws Strike, Foul;\n", + " public void walk() {} // Throws no checked exceptions\n", + "}\n", + "class StormException extends Exception {}\n", + "class RainedOut extends StormException {}\n", + "class PopFoul extends Foul {}\n", + "interface Storm {\n", + " void event() throws RainedOut;\n", + " void rainHard() throws RainedOut;\n", + "}\n", + "public class StormyInning extends Inning implements Storm {\n", + " // OK to add new exceptions for constructors, but you\n", + "// must deal with the base constructor exceptions:\n", + " public StormyInning()\n", + " throws RainedOut, BaseballException {}\n", + " public StormyInning(String s)\n", + " throws BaseballException {}\n", + " // Regular methods must conform to base class:\n", + "//- void walk() throws PopFoul {} //Compile error\n", + "// Interface CANNOT add exceptions to existing\n", + "// methods from the base class:\n", + "//- public void event() throws RainedOut {}\n", + "// If the method doesn't already exist in the\n", + "// base class, the exception is OK:\n", + " @Override\n", + " public void rainHard() throws RainedOut {}\n", + " // You can choose to not throw any exceptions,\n", + "// even if the base version does:\n", + " @Override\n", + " public void event() {}\n", + " // Overridden methods can throw inherited exceptions:\n", + " @Override\n", + " public void atBat() throws PopFoul {}\n", + " public static void main(String[] args) {\n", + " try {\n", + " StormyInning si = new StormyInning();\n", + " si.atBat();\n", + " } catch(PopFoul e) {\n", + " System.out.println(\"Pop foul\");\n", + " } catch(RainedOut e) {\n", + " System.out.println(\"Rained out\");\n", + " } catch(BaseballException e) {\n", + " System.out.println(\"Generic baseball exception\");\n", + " }\n", + "// Strike not thrown in derived version.\n", + " try {\n", + "// What happens if you upcast?\n", + " Inning i = new StormyInning();\n", + " i.atBat();\n", + "// You must catch the exceptions from the\n", + "// base-class version of the method:\n", + " } catch(Strike e) {\n", + " System.out.println(\"Strike\");\n", + " } catch(Foul e) {\n", + " System.out.println(\"Foul\");\n", + " } catch(RainedOut e) {\n", + " System.out.println(\"Rained out\");\n", + " } catch(BaseballException e) {\n", + " System.out.println(\"Generic baseball exception\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。\n", + "\n", + "接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。\n", + "\n", + "异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。\n", + "\n", + "派生类构造器不能捕获基类构造器抛出的异常。\n", + "\n", + "StormyInning.walk() 不能通过编译是因为它抛出了异常,而 Inning.walk() 并没有声明此异常。如果编译器允许这么做的话,就可以在调用 Inning.walk() 的时候不用做异常处理了,而且当把它替换成 Inning 的派生类的对象时,这个方法就有可能会抛出异常,于是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。\n", + "\n", + "覆盖后的 event() 方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在 atBat() 身上,它抛出的是 PopFoul,这个异常是继承自“会被基类的 atBat() 抛出”的 Foul,这样,如果你写的代码是同 Inning 打交道,并且调用了它的 atBat() 的话,那么肯定能捕获 Foul,而 PopFoul 是由 Foul 派生出来的,因此异常处理程序也能捕获 PopFoul。\n", + "\n", + "最后一个值得注意的地方是 main()。这里可以看到,如果处理的刚好是 Stormylnning 对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。\n", + "\n", + "尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。\n", + "\n", + "\n", + "\n", + "## 构造器\n", + "\n", + "有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?\"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。\n", + "\n", + "你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finaly 子句中却是要被清理的。\n", + "\n", + "在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类(将在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中讨论),这些类的基本用法很简单,你应该很容易明白:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/InputFile.java\n", + "// Paying attention to exceptions in constructors\n", + "import java.io.*;\n", + "public class InputFile {\n", + " private BufferedReader in;\n", + " public InputFile(String fname) throws Exception {\n", + " try {\n", + " in = new BufferedReader(new FileReader(fname));\n", + " // Other code that might throw exceptions\n", + " } catch(FileNotFoundException e) {\n", + " System.out.println(\"Could not open \" + fname);\n", + " // Wasn't open, so don't close it\n", + " throw e;\n", + " } catch(Exception e) {\n", + " // All other exceptions must close it\n", + " try {\n", + " in.close();\n", + " } catch(IOException e2) {\n", + " System.out.println(\"in.close() unsuccessful\");\n", + " }\n", + " throw e; // Rethrow\n", + " } finally {\n", + " // Don't close it here!!!\n", + " }\n", + " }\n", + " public String getLine() {\n", + " String s;\n", + " try {\n", + " s = in.readLine();\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(\"readLine() failed\");\n", + " }\n", + " return s;\n", + " }\n", + " public void dispose() {\n", + " try {\n", + " in.close();\n", + " System.out.println(\"dispose() successful\");\n", + " } catch(IOException e2) {\n", + " throw new RuntimeException(\"in.close() failed\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在 try 块中,会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大,但可以用它来建立 BufferedReader 对象。注意,使用 InputFile 的好处之一是把两步操作合而为一。\n", + "\n", + "如果 FileReader 的构造器失败了,将抛出 FileNotFoundException 异常。对于这个异常,并不需要关闭文件,因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件,因为在它们捕获到异常之时,文件已经打开了(当然,如果还有其他方法能抛出 FileNotFoundException,这个方法就显得有些投机取巧了。这时,通常必须把这些方法分别放到各自的 try 块里),close() 方法也可能会抛出异常,所以尽管它已经在另一个 catch 子句块里了,还是要再用一层 try-catch,这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后,异常被重新抛出,对于构造器而言这么做是很合适的,因为你总不希望去误导调用方,让他认为“这个对象已经创建完毕,可以使用了”。\n", + "\n", + "在本例中,由于 finally 会在每次完成构造器之后都执行一遍,因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。\n", + "\n", + "getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine(),但是这个异常已经在方法内得到处理,因此 getLine() 不会抛出任何异常。在设计异常时有一个问题:应该把异常全部放在这一层处理;还是先处理一部分,然后再向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。如果用法恰当的话,直接向上层抛出的确能简化编程。在这里,getLine() 方法将异常转换为 RuntimeException,表示一个编程错误。\n", + "\n", + "用户在不再需要 InputFile 对象时,就必须调用 dispose() 方法,这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源(比如文件句柄),在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面,但我在 [封装](./Housekeeping.md) 讲过,你不知道 finalize() 会不会被调用(即使能确定它将被调用,也不知道在什么时候调用),这也是 Java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端程序员,这是他们的责任。\n", + "\n", + "对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的 try 子句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/Cleanup.java\n", + "// Guaranteeing proper cleanup of a resource\n", + "public class Cleanup {\n", + " public static void main(String[] args) {\n", + " try {\n", + " InputFile in = new InputFile(\"Cleanup.java\");\n", + " try {\n", + " String s;\n", + " int i = 1;\n", + " while((s = in.getLine()) != null)\n", + " ; // Perform line-by-line processing here...\n", + " } catch(Exception e) {\n", + " System.out.println(\"Caught Exception in main\");\n", + " e.printStackTrace(System.out);\n", + " } finally {\n", + " in.dispose();\n", + " }\n", + " } catch(Exception e) {\n", + " System.out.println(\n", + " \"InputFile construction failed\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dispose() successful" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请仔细观察这里的逻辑:对 InputFile 对象的构造在其自己的 try 语句块中有效,如果构造失败,将进入外部的 catch 子句,而 dispose() 方法不会被调用。但是,如果构造成功,我们肯定想确保对象能够被清理,因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中,finally 子句在构造失败时是不会执行的,而在构造成功时将总是执行。\n", + "\n", + "这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个 try-finally 语句块:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/CleanupIdiom.java\n", + "// Disposable objects must be followed by a try-finally\n", + "class NeedsCleanup { // Construction can't fail\n", + " private static long counter = 1;\n", + " private final long id = counter++;\n", + " public void dispose() {\n", + " System.out.println(\n", + " \"NeedsCleanup \" + id + \" disposed\");\n", + " }\n", + "}\n", + "class ConstructionException extends Exception {}\n", + "class NeedsCleanup2 extends NeedsCleanup {\n", + " // Construction can fail:\n", + " NeedsCleanup2() throws ConstructionException {}\n", + "}\n", + "public class CleanupIdiom {\n", + " public static void main(String[] args) {\n", + " // [1]:\n", + " NeedsCleanup nc1 = new NeedsCleanup();\n", + " try {\n", + " // ...\n", + " } finally {\n", + " nc1.dispose();\n", + " }\n", + " // [2]:\n", + " // If construction cannot fail,\n", + " // you can group objects:\n", + " NeedsCleanup nc2 = new NeedsCleanup();\n", + " NeedsCleanup nc3 = new NeedsCleanup();\n", + " try {\n", + " // ...\n", + " } finally {\n", + " nc3.dispose(); // Reverse order of construction\n", + " nc2.dispose();\n", + " }\n", + " // [3]:\n", + " // If construction can fail you must guard each one:\n", + " try {\n", + " NeedsCleanup2 nc4 = new NeedsCleanup2();\n", + " try {\n", + " NeedsCleanup2 nc5 = new NeedsCleanup2();\n", + " try {\n", + " // ...\n", + " } finally {\n", + " nc5.dispose();\n", + " }\n", + " } catch(ConstructionException e) { // nc5 const.\n", + " System.out.println(e);\n", + " } finally {\n", + " nc4.dispose();\n", + " }\n", + " } catch(ConstructionException e) { // nc4 const.\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NeedsCleanup 1 disposed\n", + "NeedsCleanup 3 disposed\n", + "NeedsCleanup 2 disposed\n", + "NeedsCleanup 5 disposed\n", + "NeedsCleanup 4 disposed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- [1] 相当简单,遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败,就不需要任何 catch。\n", + "- [2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。\n", + "- [3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。\n", + "\n", + "本例中的异常处理的棘手程度,对于应该创建不能失败的构造器是一个有力的论据,尽管这么做并非总是可行。\n", + "\n", + "注意,如果 dispose() 可以抛出异常,那么你可能需要额外的 try 语句块。基本上,你应该仔细考虑所有的可能性,并确保正确处理每一种情况。\n", + "\n", + "\n", + "\n", + "## Try-With-Resources 用法\n", + "\n", + "上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。\n", + "\n", + "InputFile.java 是一个特别棘手的情况,因为文件被打开(包含所有可能的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常,因此可以调用 dispose() 方法。这是一个很好的例子,因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码(当然,你经常会遇到这种你无法选择的代码设计的情况,因此你必须仍然理解它)。\n", + "\n", + "InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/InputFile2.java\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "public class InputFile2 {\n", + " private String fname;\n", + "\n", + " public InputFile2(String fname) {\n", + " this.fname = fname;\n", + " }\n", + "\n", + " public Stream getLines() throws IOException {\n", + " return Files.lines(Paths.get(fname));\n", + " }\n", + "\n", + " public static void\n", + " main(String[] args) throws IOException {\n", + " new InputFile2(\"InputFile2.java\").getLines()\n", + " .skip(15)\n", + " .limit(1)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "main(String[] args) throws IOException {" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,getLines() 全权负责打开文件并创建 Stream。\n", + "\n", + "你不能总是轻易地回避这个问题。有时会有以下问题:\n", + "\n", + "1. 需要资源清理\n", + "2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。\n", + "\n", + "一个常见的例子是 jav.io.FileInputstream(将会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中提到)。要正确使用它,你必须编写一些棘手的样板代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/MessyExceptions.java\n", + "import java.io.*;\n", + "public class MessyExceptions {\n", + " public static void main(String[] args) {\n", + " InputStream in = null;\n", + " try {\n", + " in = new FileInputStream(\n", + " new File(\"MessyExceptions.java\"));\n", + " int contents = in.read();\n", + " // Process contents\n", + " } catch(IOException e) {\n", + " // Handle the error\n", + " } finally {\n", + " if(in != null) {\n", + " try {\n", + " in.close();\n", + " } catch(IOException e) {\n", + " // Handle the close() error\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当 finally 子句有自己的 try 块时,感觉事情变得过于复杂。\n", + "\n", + "幸运的是,Java 7 引入了 try-with-resources 语法,它可以非常清楚地简化上面的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/TryWithResources.java\n", + "import java.io.*;\n", + "public class TryWithResources {\n", + " public static void main(String[] args) {\n", + " try(\n", + " InputStream in = new FileInputStream(\n", + " new File(\"TryWithResources.java\"))\n", + " ) {\n", + " int contents = in.read();\n", + " // Process contents\n", + " } catch(IOException e) {\n", + " // Handle the error\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Java 7 之前,try 总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(正常或异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。\n", + "\n", + "它是如何工作的?在 try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.AutoCloseable 接口,这个接口有一个方法:close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/StreamsAreAutoCloseable.java\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "public class StreamsAreAutoCloseable {\n", + " public static void\n", + " main(String[] args) throws IOException{\n", + " try(\n", + " Stream in = Files.lines(\n", + " Paths.get(\"StreamsAreAutoCloseable.java\"));\n", + " PrintWriter outfile = new PrintWriter(\n", + " \"Results.txt\"); // [1]\n", + " ) {\n", + " in.skip(5)\n", + " .limit(1)\n", + " .map(String::toLowerCase)\n", + " .forEachOrdered(outfile::println);\n", + " } // [2]\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- [1] 你在这里可以看到其他的特性:资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的)。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。\n", + "- [2] try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里,IOException 被 main() 方法抛出,所以这里并不需要在 try 后面跟着一个 catch 语句块。\n", + "\n", + "Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。\n", + "\n", + "### 揭示细节\n", + "\n", + "为了研究 try-with-resources 的基本机制,我们将创建自己的 AutoCloseable 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/AutoCloseableDetails.java\n", + "class Reporter implements AutoCloseable {\n", + " String name = getClass().getSimpleName();\n", + " Reporter() {\n", + " System.out.println(\"Creating \" + name);\n", + " }\n", + " public void close() {\n", + " System.out.println(\"Closing \" + name);\n", + " }\n", + "}\n", + "class First extends Reporter {}\n", + "class Second extends Reporter {}\n", + "public class AutoCloseableDetails {\n", + " public static void main(String[] args) {\n", + " try(\n", + " First f = new First();\n", + " Second s = new Second()\n", + " ) {\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Creating First\n", + "Creating Second\n", + "Closing Second\n", + "Closing First" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在此配置中,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。\n", + "\n", + "假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/TryAnything.java\n", + "// {WillNotCompile}\n", + "class Anything {}\n", + "public class TryAnything {\n", + " public static void main(String[] args) {\n", + " try(\n", + " Anything a = new Anything()\n", + " ) {\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正如我们所希望和期望的那样,Java 不会让我们这样做,并且出现编译时错误。\n", + "\n", + "如果其中一个构造函数抛出异常怎么办?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/ConstructorException.java\n", + "class CE extends Exception {}\n", + "class SecondExcept extends Reporter {\n", + " SecondExcept() throws CE {\n", + " super();\n", + " throw new CE();\n", + " }\n", + "}\n", + "public class ConstructorException {\n", + " public static void main(String[] args) {\n", + " try(\n", + " First f = new First();\n", + " SecondExcept s = new SecondExcept();\n", + " Second s2 = new Second()\n", + " ) {\n", + " System.out.println(\"In body\");\n", + " } catch(CE e) {\n", + " System.out.println(\"Caught: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Creating First\n", + "Creating SecondExcept\n", + "Closing First\n", + "Caught: CE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在资源规范头中定义了 3 个对象,中间的对象抛出异常。因此,编译器强制我们使用 catch 子句来捕获构造函数异常。这意味着资源规范头实际上被 try 块包围。\n", + "\n", + "正如预期的那样,First 创建时没有发生意外,SecondExcept 在创建期间抛出异常。请注意,不会为 SecondExcept 调用 close(),因为如果构造函数失败,则无法假设你可以安全地对该对象执行任何操作,包括关闭它。由于 SecondExcept 的异常,Second 对象实例 s2 不会被创建,因此也不会有清除事件发生。\n", + "\n", + "如果没有构造函数抛出异常,但你可能会在 try 的主体中获取它们,则再次强制你实现 catch 子句:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/BodyException.java\n", + "class Third extends Reporter {}\n", + "public class BodyException {\n", + " public static void main(String[] args) {\n", + " try(\n", + " First f = new First();\n", + " Second s2 = new Second()\n", + " ) {\n", + " System.out.println(\"In body\");\n", + " Third t = new Third();\n", + " new SecondExcept();\n", + " System.out.println(\"End of body\");\n", + " } catch(CE e) {\n", + " System.out.println(\"Caught: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Creating First\n", + "Creating Second\n", + "In body\n", + "Creating Third\n", + "Creating SecondExcept\n", + "Closing Second\n", + "Closing First\n", + "Caught: CE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意,第 3 个对象永远不会被清除。那是因为它不是在资源规范头中创建的,所以它没有被保护。这很重要,因为 Java 在这里没有以警告或错误的形式提供指导,因此像这样的错误很容易漏掉。实际上,如果依赖某些集成开发环境来自动重写代码,以使用 try-with-resources 特性,那么它们(在撰写本文时)通常只会保护它们遇到的第一个对象,而忽略其余的对象。\n", + "\n", + "最后,让我们看一下抛出异常的 close() 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/CloseExceptions.java\n", + "class CloseException extends Exception {}\n", + "class Reporter2 implements AutoCloseable {\n", + " String name = getClass().getSimpleName();\n", + " Reporter2() {\n", + " System.out.println(\"Creating \" + name);\n", + " }\n", + " public void close() throws CloseException {\n", + " System.out.println(\"Closing \" + name);\n", + " }\n", + "}\n", + "class Closer extends Reporter2 {\n", + " @Override\n", + " public void close() throws CloseException {\n", + " super.close();\n", + " throw new CloseException();\n", + " }\n", + "}\n", + "public class CloseExceptions {\n", + " public static void main(String[] args) {\n", + " try(\n", + " First f = new First();\n", + " Closer c = new Closer();\n", + " Second s = new Second()\n", + " ) {\n", + " System.out.println(\"In body\");\n", + " } catch(CloseException e) {\n", + " System.out.println(\"Caught: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Creating First\n", + "Creating Closer\n", + "Creating Second\n", + "In body\n", + "Closing Second\n", + "Closing Closer\n", + "Closing First\n", + "Caught: CloseException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从技术上讲,我们并没有被迫在这里提供一个 catch 子句;你可以通过 **main() throws CloseException** 的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。\n", + "\n", + "请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer 也是如此。 close() 抛出异常。当你想到它时,这就是你想要发生的事情,但是如果你必须自己编写所有这些逻辑,那么你可能会错过一些错误。想象一下所有代码都在那里,程序员没有考虑清理的所有含义,并且做错了。因此,应始终尽可能使用 try-with-resources。它有助于实现该功能,使得生成的代码更清晰,更易于理解。\n", + "\n", + "\n", + "\n", + "## 异常匹配\n", + "\n", + "抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。\n", + "\n", + "查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/Human.java\n", + "// Catching exception hierarchies\n", + "class Annoyance extends Exception {}\n", + "class Sneeze extends Annoyance {}\n", + "public class Human {\n", + " public static void main(String[] args) {\n", + " // Catch the exact type:\n", + " try {\n", + " throw new Sneeze();\n", + " } catch(Sneeze s) {\n", + " System.out.println(\"Caught Sneeze\");\n", + " } catch(Annoyance a) {\n", + " System.out.println(\"Caught Annoyance\");\n", + " }\n", + " // Catch the base type:\n", + " try {\n", + " throw new Sneeze();\n", + " } catch(Annoyance a) {\n", + " System.out.println(\"Caught Annoyance\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Caught Sneeze\n", + "Caught Annoyance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sneeze 异常会被第一个匹配的 catch 子句捕获,也就是程序里的第一个。然而如果将这个 catch 子句删掉,只留下 Annoyance 的 catch 子句,该程序仍然能运行,因为这次捕获的是 Sneeze 的基类。换句话说,catch(Annoyance a)会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。\n", + "\n", + "如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + " throw new Sneeze();\n", + "} catch(Annoyance a) {\n", + " // ...\n", + "} catch(Sneeze s) {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此时,编译器会发现 Sneeze 的 catch 子句永远得不到执行,因此它会向你报告错误。\n", + "\n", + "\n", + "\n", + "## 其他可选方式\n", + "\n", + "异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”\n", + "发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门\"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。\n", + "\n", + "异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常\"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。\n", + "\n", + "“被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句,这就导致了吞食则有害(harmful if swallowed)的问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + " // ... to do something useful\n", + "} catch(ObligatoryException e) {} // Gulp!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "程序员们只做最简单的事情(包括我自己,在本书第 1 版中也有这个问题),常常是无意中\"吞食”了异常,然而一旦这么做,虽然能通过编译,但除非你记得复查并改正代码,否则异常将会丢失。异常确实发生了,但“吞食”后它却完全消失了。因为编译器强迫你立刻写代码来处理异常,所以这种看起来最简单的方法,却可能是最糟糕的做法。\n", + "\n", + "当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第 2 版中,我在处理程序里通过打印栈轨迹的方法“修补”了这个问题(本章中的很多例子还是使用了这种方法,看起来还是比较合适的),虽然这样可以跟踪异常的行为,但是仍旧不知道该如何处理异常。这一节,我们来研究一下“被检查的异常”及其并发症,以及采用什么方法来解决这些问题。\n", + "\n", + "这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。\n", + "\n", + "### 历史\n", + "\n", + "异常处理起源于 PL/1 和 Mesa 之类的系统中,后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 Ruby 和 C# 中。Java 的设计和 C++ 很相似,只是 Java 的设计者去掉了一些他们认为 C++设计得不好的东西。\n", + "\n", + "为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架,异常处理机制很晚才被加入 C++ 标准化过程中,这是由 C++ 的设计者 Bjarne Stroustrup 所倡议。C++ 的异常模型主要借鉴了 CLU 的做法。然而,当时其他语言已经支持异常处理了:包括 Ada、Smalltalk(两者都有异常处理,但是都没有异常说明),以及 Modula-3(它既有异常处理也有异常说明)。\n", + "\n", + "Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文中指出,用瞬时风格(transient fashion)报告错误的语言(如 C 中)有一个主要缺陷,那就是:\n", + "\n", + "> “....每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常。”\n", + "\n", + "因此,异常处理的初衷是要消除这种限制,但是我们又从 Java 的“被检查的异常”中看到了这种代码。他们继续写道:\n", + "\n", + "> “....要求程序员把异常处理程序的代码文本附接到会引发异常的调用上,这会降低程序的可读性,使得程序的正常思路被异常处理给破坏了。”\n", + "\n", + "C++ 中异常的设计参考了 CLU 方式。Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的,因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是,忽略所有的错误,然后使用调试器来跟踪错误。这些程序员知道,使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此,要把他们拉到“使用错误处理”的正轨上,“多出来的”代码决不能太多。我认为,评价 Java 的“被检查的异常”的时候,这一点是很重要的。\n", + "\n", + "C++ 从 CLU 那里还带来另一种思想:异常说明。这样,就可以用编程的方式在方法签名中声明这个方法将会抛出异常。异常说明有两个目的:一个是“我的代码会产生这种异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理的机制和语法的时候,我们一直在关注“你来处理”部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。\n", + "\n", + "C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++ 不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++ 会调用标准类库的 unexpected() 函数。\n", + "\n", + "值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于泛型用于异常说明的方式存在着一些限制。\n", + "\n", + "### 观点\n", + "\n", + "首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。\n", + "\n", + "其次,仅从示意性的例子和小程序来看,“被检查的异常”的好处很明显。但是当程序开始变大的时候,就会带来一些微妙的问题。当然,程序不是一下就变大的,这有个过程。如果把不适用于大项目的语言用于小项目,当这些项目不断膨胀时,突然有一天你会发现,原来可以管理的东西,现在已经变得无法管理了。这就是我所说的过多的类型检查,特别是“被检查的异常\"所造成的问题。\n", + "\n", + "看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C# 的设计人员发现:\n", + "\n", + "> “仅从小程序来看,会认为异常说明能增加开发人员的效率,并提高代码的质量;但考察大项目的时候,结论就不同了-开发效率下降了,而代码质量只有微不足道的提高,甚至毫无提高”。\n", + "\n", + "谈到未被捕获的异常的时候,CLU 的设计师们认为:\n", + "\n", + "> “我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序,是不现实的。”\n", + "\n", + "在解释为什么“函数没有异常说明就表示可以抛出任何异常”的时候,Stroustrup 这样认为:\n", + "\n", + "> “但是,这样一来几乎所有的函数都得提供异常说明了,也就都得重新编译,而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束,他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感。”\n", + ">\n", + "\n", + "我们已经看到这种破坏异常机制的行为了-就在 Java 的“被检查的异常”里。\n", + "\n", + "Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者)给我写了下面这段话:\n", + "\n", + "> “...总体来说,我觉得异常很不错,但是 Java 的”被检查的异常“带来的麻烦比好处要多。”\n", + "\n", + "过去,我曾坚定地认为“被检查的异常”和强静态类型检查对开发健壮的程序是非常必要的。但是,我看到的以及我使用一些动态(类型检查)语言的亲身经历告诉我,这些好处实际上是来自于:\n", + "\n", + "1. 不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型。\n", + "2. 不在于什么时候进行检查,而是一定要有类型检查。也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那倒没关系。\n", + "\n", + "此外,减少编译时施加的约束能显著提高程序员的编程效率。事实上,反射和泛型就是用来补偿静态类型检查所带来的过多限制,在本书很多例子中都会见到这种情形。\n", + "\n", + "我已经听到有人在指责了,他们认为这种言论会令我名誉扫地,会让文明堕落,会导致更高比例的项目失败。他们的信念是应该在编译时指出所有错误,这样才能挽救项目,这种信念可以说是无比坚定的;其实更重要的是要理解编译器的能力限制。在 http://MindView.net/Books/BetterJava 上的补充材料中,我强调了自动构建过程和单元测试的重要性,比起把所有的东西都说成是语法错误,它们的效果可以说是事半功倍。下面这段话是至理名言:\n", + "\n", + "> 好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出坏程序。\n", + "\n", + "不管怎么说,要让 Java 把“被检查的异常”从语言中去除,这种可能性看来非常渺茫。对语言来说,这个变化可能太激进了点,况且 Sun 的支持者们也非常强大。Sun 有完全向后兼容的历史和策略,实际上所有 Sun 的软件都能在 Sun 的硬件上运行,无论它们有多么古老。然而,如果发现有些“被检查的异常”挡住了路,尤其是发现你不得不去对付那些不知道该如何处理的异常,还是有些办法的。\n", + "\n", + "### 把异常传递给控制台\n", + "\n", + "对于简单的程序,比如本书中的许多例子,最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从 main() 传递到控制台。例如,为了读取信息而打开一个文件(在第 12 章将详细介绍),必须对 FilelnputStream 进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做(本书中很多地方采用了这种方法):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/MainException.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "public class MainException {\n", + " // Pass exceptions to the console:\n", + " public static void main(String[] args) throws Exception {\n", + " // Open the file:\n", + " List lines = Files.readAllLines(\n", + " Paths.get(\"MainException.java\"));\n", + " // Use the file ...\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,main() 作为一个方法也可以有异常说明,这里异常的类型是 Exception,它也是所有“被检查的异常”的基类。通过把它传递到控制台,就不必在 main() 里写 try-catch 子句了。(不过,实际的文件输人输出操作比这个例子要复杂得多。你将会在 [文件](./Files.md) 和 [附录:I/O 流](./Appendix-IO-Streams.md) 章节中学到更多)\n", + "\n", + "### 把“被检查的异常”转换为“不检查的异常”\n", + "\n", + "在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的,但这不是通用的方法。\n", + "\n", + "问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或者打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "try {\n", + " // ... to do something useful\n", + "} catch(IDontKnowWhatToDoWithThisCheckedException e) {\n", + " throw new RuntimeException(e);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果想把“被检查的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。\n", + "\n", + "这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// exceptions/TurnOffChecking.java\n", + "// \"Turning off\" Checked exceptions\n", + "import java.io.*;\n", + "class WrapCheckedException {\n", + " void throwRuntimeException(int type) {\n", + " try {\n", + " switch(type) {\n", + " case 0: throw new FileNotFoundException();\n", + " case 1: throw new IOException();\n", + " case 2: throw new\n", + " RuntimeException(\"Where am I?\");\n", + " default: return;\n", + " }\n", + " } catch(IOException | RuntimeException e) {\n", + " // Adapt to unchecked:\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}\n", + "class SomeOtherException extends Exception {}\n", + "public class TurnOffChecking {\n", + " public static void main(String[] args) {\n", + " WrapCheckedException wce =\n", + " new WrapCheckedException();\n", + " // You can call throwRuntimeException() without\n", + " // a try block, and let RuntimeExceptions\n", + " // leave the method:\n", + " wce.throwRuntimeException(3);\n", + " // Or you can choose to catch exceptions:\n", + " for(int i = 0; i < 4; i++)\n", + " try {\n", + " if(i < 3)\n", + " wce.throwRuntimeException(i);\n", + " else\n", + " throw new SomeOtherException();\n", + " } catch(SomeOtherException e) {\n", + " System.out.println(\n", + " \"SomeOtherException: \" + e);\n", + " } catch(RuntimeException re) {\n", + " try {\n", + " throw re.getCause();\n", + " } catch(FileNotFoundException e) {\n", + " System.out.println(\n", + " \"FileNotFoundException: \" + e);\n", + " } catch(IOException e) {\n", + " System.out.println(\"IOException: \" + e);\n", + " } catch(Throwable e) {\n", + " System.out.println(\"Throwable: \" + e);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "FileNotFoundException: java.io.FileNotFoundException\n", + "IOException: java.io.IOException\n", + "Throwable: java.lang.RuntimeException: Where am I?\n", + "SomeOtherException: SomeOtherException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型的异常。这些异常被捕获并包装进了 RuntimeException 对象,所以它们成了这些运行时异常的\"cause\"了。\n", + "\n", + "在 TurnOfChecking 里,可以不用 try 块就调用 throwRuntimeException(),因为它没有抛出“被检查的异常”。但是,当你准备好去捕获异常的时候,还是可以用 try 块来捕获任何你想捕获的异常的。应该捕获 try 块肯定会抛出的异常,这里就是 SomeOtherException,RuntimeException 要放到最后去捕获。然后把 getCause() 的结果(也就是被包装的那个原始异常)抛出来。这样就把原先的那个异常给提取出来了,然后就可以用它们自己的 catch 子句进行处理。\n", + "\n", + "本书余下部分将会在合适的时候使用这种“用 RuntimeException 来包装,被检查的异常”的技术。另一种解决方案是创建自己的 RuntimeException 的子类。在这种方式中,不必捕获它,但是希望得到它的其他代码都可以捕获它。\n", + "\n", + "\n", + "\n", + "## 异常指南\n", + "\n", + "应该在下列情况下使用异常:\n", + "\n", + "1. 尽可能使用 try-with-resource。\n", + "2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)\n", + "3. 解决问题并且重新调用产生异常的方法。\n", + "4. 进行少许修补,然后绕过异常发生的地方继续执行。\n", + "5. 用别的数据进行计算,以代替方法预计会返回的值。\n", + "6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。\n", + "7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。\n", + "8. 终止程序。\n", + "9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)\n", + "10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。\n", + "\n", + "异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。\n", + "\n", + "就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。\n", + "\n", + "## 后记:Exception Bizarro World\n", + "\n", + "(来自于 2011 年的一篇博文)\n", + "\n", + "我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,并且不断被检查的异常所挫败。他向我指出 Howard Lewis Ship 的帖子“[被检查的例外的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”。特别是。James 对他必须跳过去做一些应该简单的事情的所有环感到沮丧。即使在 finally 块中,他也不得不放入更多的 try-catch 子句,因为关闭连接也会导致异常。它在哪里结束?为了简单起见,你必须在环之后跳过环(请注意,try-with-resources语句可以显著改善这种情况)。\n", + "\n", + "我们开始讨论 Go 编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。\n", + "\n", + "我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。\n", + "\n", + "他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。\n", + "\n", + "替代方案非常简单,起初它几乎看起来像 C 一样。因为 Go 从一开始就包含了元组,所以你可以轻松地从函数调用中返回两个对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "go" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "result, err := functionCall()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "( := 告诉 Go 语言这里定义 result 和 err,并且推断他们的数据类型)\n", + "\n", + "就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。\n", + "\n", + "起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?\n", + "\n", + "它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。\n", + "\n", + "James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二者)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。\n", + "\n", + "关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。\n", + "\n", + "跳入奇异的世界会给 James 带来问题,它为所有程序员增加了更多的工作:因为你无法知道什么时候会发生什么事(你可以随时进入奇怪的世界),你必须添加一些 try 块来确保没有任何东西从裂缝中滑落。您最终必须进行额外的编程以补偿异常机制(它似乎类似于补偿共享内存并发所需的额外工作)。\n", + "\n", + "Go 团队采取了大胆的举动,质疑所有这些,并说,“让我们毫无例外地尝试它,看看会发生什么。”是的,这意味着你通常会在发生错误的地方处理错误,而不是最后将它们聚集在一起 try 块。但这也意味着关于一件事的代码是本地化的,也许这并不是那么糟糕。这也可能意味着您无法轻松组合常见的错误处理代码(除非您确定了常用代码并将其放入函数中,也不是那么糟糕)。但这绝对意味着您不必担心有多个可能的执行路径而且所有这些都需要。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/16-Validating-Your-Code.ipynb b/jupyter/16-Validating-Your-Code.ipynb new file mode 100644 index 00000000..f4cb2c3a --- /dev/null +++ b/jupyter/16-Validating-Your-Code.ipynb @@ -0,0 +1,2480 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第十六章 代码校验\n", + "\n", + "### 你永远不能保证你的代码是正确的,你只能证明它是错的。\n", + "\n", + "让我们先暂停编程语言特性的学习,看看一些代码基础知识。特别是能让你的代码更加健壮的知识。\n", + "\n", + "\n", + "\n", + "## 测试\n", + "\n", + "### 如果没有测试过,它就是不能工作的。\n", + "\n", + "Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性感到过于舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试,只是说明编译器接受你代码中的语法和基本类型规则,并不意味着你的代码达到程序的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过这些目标。迈向代码校验的第一步就是创建测试,针对你的目标检查代码的行为。\n", + "\n", + "#### 单元测试\n", + "\n", + "这个过程是将集成测试构建到你创建的所有代码中,并在每次构建系统时运行这些测试。这样,构建过程不仅能检查语法的错误,同时也能检查语义的错误。\n", + "\n", + "“单元”是指测试一小部分代码 。通常,每个类都有测试来检查它所有方法的行为。“系统”测试则是不同的,它检查的是整个程序是否满足要求。\n", + "\n", + "C 风格的语言,尤其是 C++,通常会认为性能比安全更重要。用 Java 编程比 C++(一般认为大概快两倍)快的原因是 Java 的安全性保障:比如垃圾回收以及改良的类型检测等特性。通过将单元测试集成到构建过程中,你扩大了这个安全保障,因而有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆地重构你的代码。\n", + "\n", + "我自己的测试经历开始于我意识到要确保书中代码的正确性,书中的所有程序必须能够通过合适的构建系统自动提取、编译。这本书所使用的构建系统是 Gradle。 你只要在安装 JDK 后输入 **gradlew compileJava**,就能编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目,(在我看来)这会很快成为任何编程书籍的必备条件——你怎么能相信没有编译的代码呢? 我还发现我可以使用搜索和替换在整本书进行大范围的修改,如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统中发现了一个严重的漏洞。编译程序毫无疑问是重要的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书会发现书中代码的错误)。但是,我收到了来自读者反馈代码中存在语义问题。当然,这些问题可以通过运行代码发现。我在早期实现一个自动化执行测试系统时尝试了一些不太有效的方式,但迫于出版压力,我明白我的程序绝对有问题,并会以 bug 报告的方式让我自食恶果。我也经常收到读者的抱怨说,我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者一开始就不正确)。为了解决这个问题,我利用 Python 创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证过。\n", + "\n", + "#### JUnit\n", + "\n", + "最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为 [注解](./Annotations.md) 一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本\n", + "\n", + "在 JUnit 最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit 将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。\n", + "\n", + "让我们尝试一个简单的例子。**CountedList** 继承 **ArrayList** ,添加信息来追踪有多少个 **CountedLists** 被创建:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/CountedList.java\n", + "// Keeps track of how many of itself are created.\n", + "package validating;\n", + "import java.util.*;\t\n", + "public class CountedList extends ArrayList {\n", + " private static int counter = 0;\n", + " private int id = counter++;\n", + " public CountedList() {\n", + " System.out.println(\"CountedList #\" + id);\n", + " }\n", + "\tpublic int getId() { return id; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "标准做法是将测试放在它们自己的子目录中。测试还必须放在包中,以便 JUnit 能发现它们:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/tests/CountedListTest.java\n", + "// Simple use of JUnit to test CountedList.\n", + "package validating;\n", + "import java.util.*;\n", + "import org.junit.jupiter.api.*;\n", + "import static org.junit.jupiter.api.Assertions.*;\n", + "public class CountedListTest {\n", + "private CountedList list;\n", + "\t@BeforeAll\n", + " static void beforeAllMsg() {\n", + " System.out.println(\">>> Starting CountedListTest\");\n", + " }\n", + " \n", + " @AfterAll\n", + " static void afterAllMsg() {\n", + " System.out.println(\">>> Finished CountedListTest\");\n", + " }\n", + " \n", + " @BeforeEach\n", + " public void initialize() {\n", + " \tlist = new CountedList();\n", + " \tSystem.out.println(\"Set up for \" + list.getId());\n", + " for(int i = 0; i < 3; i++)\n", + " list.add(Integer.toString(i));\n", + " }\n", + " \n", + " @AfterEach\n", + " public void cleanup() {\n", + " \tSystem.out.println(\"Cleaning up \" + list.getId());\n", + " }\n", + " \n", + " @Test\n", + " public void insert() {\n", + " System.out.println(\"Running testInsert()\");\n", + " assertEquals(list.size(), 3);\n", + " list.add(1, \"Insert\");\n", + " assertEquals(list.size(), 4);\n", + " assertEquals(list.get(1), \"Insert\");\n", + " }\n", + " \n", + " @Test\n", + " public void replace() {\n", + " \tSystem.out.println(\"Running testReplace()\");\n", + " \tassertEquals(list.size(), 3);\n", + " \tlist.set(1, \"Replace\");\n", + " \t\tassertEquals(list.size(), 3);\n", + " \tassertEquals(list.get(1), \"Replace\");\n", + " }\n", + " \t\n", + " // A helper method to simplify the code. As\n", + " // long as it's not annotated with @Test, it will\n", + " // not be automatically executed by JUnit.\n", + " private void compare(List lst, String[] strs) {\n", + " assertArrayEquals(lst.toArray(new String[0]), strs);\n", + " }\n", + " \n", + " @Test\n", + " public void order() {\n", + " System.out.println(\"Running testOrder()\");\n", + " compare(list, new String[] { \"0\", \"1\", \"2\" });\n", + " }\n", + " \n", + " @Test\n", + " public void remove() {\n", + " \tSystem.out.println(\"Running testRemove()\");\n", + " \tassertEquals(list.size(), 3);\n", + " \tlist.remove(1);\n", + " \tassertEquals(list.size(), 2);\n", + " \tcompare(list, new String[] { \"0\", \"2\" });\n", + " }\n", + " \n", + " @Test\n", + " public void addAll() {\n", + " \tSystem.out.println(\"Running testAddAll()\");\n", + " \tlist.addAll(Arrays.asList(new String[] {\n", + " \t\"An\", \"African\", \"Swallow\"}));\n", + " \tassertEquals(list.size(), 6);\n", + " \tcompare(list, new String[] { \"0\", \"1\", \"2\",\n", + " \t\"An\", \"African\", \"Swallow\" });\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + ">>> Starting CountedListTest\n", + "CountedList #0\n", + "Set up for 0\n", + "Running testRemove()\n", + "Cleaning up 0\n", + "CountedList #1\n", + "Set up for 1\n", + "Running testReplace()\n", + "Cleaning up 1\n", + "CountedList #2\n", + "Set up for 2\n", + "Running testAddAll()\n", + "Cleaning up 2\n", + "CountedList #3\n", + "Set up for 3\n", + "Running testInsert()\n", + "Cleaning up 3\n", + "CountedList #4\n", + "Set up for 4\n", + "Running testOrder()\n", + "Cleaning up 4\n", + ">>> Finished CountedListTest\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。\n", + "\n", + "**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在测试类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。\n", + "\n", + "如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**。\n", + "\n", + "每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是 list 被赋值为一个新的用字符串“0”、“1” 和 “2” 初始化的 **CountedList** 对象。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。\n", + "\n", + "**insert()** 和 **replace()** 演示了典型的测试方法。JUnit 使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(以\"assert\"开头)验证测试的正确性(更全面的\"assert\"说明可以在 Junit 文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个 JUnit 断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。\n", + "\n", + "断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。\n", + "\n", + "**compare()** 是“helper方法”的一个例子,它不是由 JUnit 执行的,而是被类中的其他测试使用。只要没有 **@Test** 注解,JUnit 就不会运行它,也不需要特定的签名。在这里,**compare()** 是私有方法 ,表示仅在测试类中使用,但他同样可以是 **public** 。其余的测试方法通过将其重构为 **compare()** 方法来消除重复的代码。\n", + "\n", + "本书使用 **build.gradle** 控制测试,运行本章节的测试,使用命令:`gradlew validating:test`,Gradle 不会运行已经运行过的测试,所以如果你没有得到测试结果,得先运行:`gradlew validating:clean`。\n", + "\n", + "可以用下面这个命令运行本书的所有测试:\n", + "\n", + "**gradlew test**\n", + "\n", + "尽管可以用最简单的方法,如 **CountedListTest.java** 所示那样,JUnit 还包括大量的测试结构,你可以到[官网](junit.org)上学习它们。\n", + "\n", + "JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。\n", + "\n", + "#### 测试覆盖率的幻觉\n", + "\n", + "测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage)\n", + "\n", + "计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)。\n", + "\n", + "对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为 100% 的测试覆盖是唯一可接受的值。这有一个问题,因为 100% 并不意味着是对测试有效性的良好测量。你可以测试所有需要它的东西,但是只需要 65% 的覆盖率。如果需要 100% 的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。\n", + "\n", + "当分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来得到测试质量的任何信息。\n", + "\n", + "\n", + "\n", + "## 前置条件\n", + "\n", + "前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从 Java 的断言机制开始来介绍 DBC,最后使用谷歌的 Guava 库作为前置条件。\n", + "\n", + "#### 断言(Assertions)\n", + "\n", + "断言通过验证在程序执行期间满足某些条件,从而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段表示日历上的月份。这个数字总是介于 1-12 之间。断言可以检查这个数字,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。\n", + "\n", + "#### Java 断言语法\n", + "\n", + "你可以通过其它程序设计架构来模拟断言的效果,因此,在 Java 中包含断言的意义在于它们易于编写。断言语句有两种形式 : \n", + "\n", + "assert boolean-expression;\n", + "\n", + "assert boolean-expression: information-expression;\n", + "\n", + "两者似乎告诉我们 **“我断言这个布尔表达式会产生 true”**, 否则,将抛出 **AssertionError** 异常。\n", + "\n", + "**AssertionError** 是 **Throwable** 的派生类,因此不需要异常说明。\n", + "\n", + "不幸的是,第一种断言形式的异常不会生成包含布尔表达式的任何信息(与大多数其他语言的断言机制相反)。\n", + "\n", + "下面是第一种形式的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Assert1.java\n", + "\n", + "// Non-informative style of assert\n", + "// Must run using -ea flag:\n", + "// {java -ea Assert1}\n", + "// {ThrowsException}\n", + "public class Assert1 {\n", + " public static void main(String[] args) {\n", + " assert false;\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"main\" java.lang.AssertionError\n", + "at Assert1.main(Assert1.java:9)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** 标志, 它也可以表示为: **-enableassertion**, 这将运行程序并执行任何断言语句。\n", + "\n", + "输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Assert2.java\n", + "// Assert with an information-expression\n", + "// {java Assert2 -ea}\n", + "// {ThrowsException}\n", + "\n", + "public class Assert2 {\n", + " public static void main(String[] args) {\n", + " assert false:\n", + " \"Here's a message saying what happened\";\n", + " }\n", + "}\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"main\" java.lang.AssertionError:\n", + "Here's a message saying what happened\n", + "at Assert2.main(Assert2.java:8)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它包含失败的断言。\n", + "\n", + "你还可以基于类名或包名打开或关闭断言;也就是说,你可以对整个包启用或禁用断言。实现这一点的详细信息在 JDK 的断言文档中。此特性对于使用断言的大型项目来说很有用当你想打开或关闭某些断言时。但是,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。\n", + "\n", + "你还可以通过编程的方式通过链接到类加载器对象(**ClassLoader**)来控制断言。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以像下面这样悄悄地开启断言:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/LoaderAssertions.java\n", + "// Using the class loader to enable assertions\n", + "// {ThrowsException}\n", + "public class LoaderAssertions {\n", + "public static void main(String[] args) {\n", + "\n", + "\tClassLoader.getSystemClassLoader().\n", + " setDefaultAssertionStatus(true);\n", + "\t\tnew Loaded().go();\n", + "\t}\n", + "}\n", + "\n", + "class Loaded {\n", + " public void go() {\n", + " assert false: \"Loaded.go()\";\n", + " }\n", + "}\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"main\" java.lang.AssertionError:\n", + "Loaded.go()\n", + "at Loaded.go(LoaderAssertions.java:15)\n", + "at\n", + "LoaderAssertions.main(LoaderAssertions.java:9)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数,这么做是有意义的。然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "static {\n", + " boolean assertionsEnabled = false;\n", + " // Note intentional side effect of assignment:\n", + " assert assertionsEnabled = true;\n", + " if(!assertionsEnabled)\n", + " throw new RuntimeException(\"Assertions disabled\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果启用断言,然后执行 **assert** 语句,**assertionsEnabled** 变为 **true** 。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert** 语句不执行,**assertionsEnabled** 保持false,将导致异常。\n", + "\n", + "#### Guava断言\n", + "\n", + "因为启用 Java 本地断言很麻烦,Guava 团队添加一个始终启用的用来替换断言的 **Verify** 类。他们建议静态导入 **Verify** 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/GuavaAssertions.java\n", + "// Assertions that are always enabled.\n", + "\n", + "import com.google.common.base.*;\n", + "import static com.google.common.base.Verify.*;\n", + "public class GuavaAssertions {\n", + " public static void main(String[] args) {\n", + " \tverify(2 + 2 == 4);\n", + " \ttry {\n", + " \t\tverify(1 + 2 == 4);\n", + " \t \t} catch(VerifyException e) {\n", + " \t\tSystem.out.println(e);\n", + " \t}\n", + " \n", + "\t\ttry {\n", + "\t\t\tverify(1 + 2 == 4, \"Bad math\");\n", + "\t\t} catch(VerifyException e) {\n", + "\t\t\tSystem.out.println(e.getMessage());\n", + "\t\t}\n", + " \n", + "\t\ttry {\n", + "\t\t\tverify(1 + 2 == 4, \"Bad math: %s\", \"not 4\");\n", + "\t\t} catch(VerifyException e) {\n", + " \tSystem.out.println(e.getMessage());\n", + " }\n", + " \n", + " String s = \"\";\n", + " s = verifyNotNull(s);\n", + " s = null;\n", + " try {\n", + " verifyNotNull(s);\n", + " } catch(VerifyException e) {\n", + " \tSystem.out.println(e.getMessage());\n", + " }\n", + " \n", + " try {\n", + " \tverifyNotNull(\n", + " \t\ts, \"Shouldn't be null: %s\", \"arg s\");\n", + " } catch(VerifyException e) {\n", + " \tSystem.out.println(e.getMessage());\n", + " }\n", + "\t}\n", + "}\n", + "/* Output:\n", + "com.google.common.base.VerifyException\n", + "Bad math\n", + "Bad math: not 4\n", + "expected a non-null reference\n", + "Shouldn't be null: arg s\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里有两个方法,使用变量 **verify()** 和 **verifyNotNull()** 来支持有用的错误消息。注意,**verifyNotNull()** 内置的错误消息通常就足够了,而 **verify()** 太一般,没有有用的默认错误消息。\n", + "\n", + "#### 使用断言进行契约式设计\n", + "\n", + "*契约式设计(DbC)*是 Eiffel 语言的发明者 Bertrand Meyer 提出的一个概念,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel 语言也是如此),但是它们创建了一种非正式的 DbC 编程风格。DbC 假定服务供应者与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应者和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。\n", + "\n", + "**Meyer** 认为:\n", + "\n", + "1.应该明确指定行为,就好像它是一个契约一样。\n", + "\n", + "2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。\n", + "\n", + "不管你是否同意,第一条总是对的,在大多数情况下,DbC 确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类 DbC 约束的表达式;如果无法指定约束,则你可能对要构建的内容了解得不够。\n", + "\n", + "#### 检查指令\n", + "\n", + "详细研究 DbC 之前,思考最简单使用断言的办法,**Meyer** 称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。\n", + "\n", + "在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是复杂反应的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。\n", + "\n", + "检查指令是对你的代码进行补充,当你可以测试并阐明对象或程序的状态时,应该使用它。\n", + "\n", + "#### 前置条件\n", + "\n", + "前置条件确保客户端(调用此方法的代码)履行其部分契约。这意味着在方法调用开始时几乎总是会检查参数(在你用那个方法做任何操作之前)以此保证它们的调用在方法中是合适的。因为你永远无法知道客户端会传递给你什么,前置条件是确保检查的一个好做法。\n", + "\n", + "#### 后置条件\n", + "\n", + "后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在 **return** 语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。\n", + "\n", + "#### 不变性\n", + "\n", + "不变性保证了必须在方法调用之间维护的对象的状态。但是,它并不会阻止方法在执行过程中暂时偏离这些保证,它只是在说对象的状态信息应该总是遵守状态规则:\n", + "\n", + "**1**. 在进入该方法时。\n", + "\n", + "**2**. 在离开方法之前。\n", + "\n", + "此外,不变性是构造后对于对象状态的保证。\n", + "\n", + "根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 **invariant()** ,它在构造之后以及每个方法的开始和结束时调用。方法以如下方式调用:\n", + "\n", + "assert invariant();\n", + "\n", + "这样,如果出于性能原因禁用断言,就不会产生开销。\n", + "\n", + "#### 放松 DbC 检查或非严格的 DbC\n", + "\n", + "尽管 Meyer 强调了前置条件、后置条件和不变性的价值以及在开发过程中使用它们的重要性,他承认在一个产品中包含所有 DbC 代码并不总是实际的。你可以基于对特定位置的代码的信任程度放松 DbC 检查。以下是放松检查的顺序,最安全到最不安全:\n", + "\n", + "**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。\n", + "\n", + "**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是当对自己的代码有信心时。\n", + "\n", + "**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。\n", + "\n", + "**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,某些情况下对性能要求很高,通过分析得到前置条件造成了这个瓶颈,而且你有某种合理的保证客户端不会违反前置条件(比如自己编写客户端的情况下),那么禁用前置条件检查是可接受的。\n", + "\n", + "你不应该直接删除检查的代码,而只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。\n", + "\n", + "#### DbC + 单元测试\n", + "\n", + "下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。\n", + "\n", + "我们可以对这个队列做一些契约定义:\n", + "\n", + "**1**. 前置条件(用于put()):不允许将空元素添加到队列中。\n", + "\n", + "**2**. 前置条件(用于put()):将元素放入完整队列是非法的。\n", + "\n", + "**3**. 前置条件(用于get()):试图从空队列中获取元素是非法的。\n", + "\n", + "**4**. 后置条件用于get()):不能从数组中生成空元素。\n", + "\n", + "**5**. 不变性:包含对象的区域不能包含任何空元素。\n", + "\n", + "**6**. 不变性:不包含对象的区域必须只有空值。\n", + "\n", + "下面是实现这些规则的一种方式,为每个 DbC 元素类型使用显式的方法调用。\n", + "\n", + "首先,我们创建一个专用的 **Exception**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " // validating/CircularQueueException.java\n", + " package validating;\n", + " public class CircularQueueException extends RuntimeException {\n", + " public CircularQueueException(String why) {\n", + " super(why);\n", + " }\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它用来报告 **CircularQueue** 中出现的错误:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " // validating/CircularQueue.java\n", + " // Demonstration of Design by Contract (DbC)\n", + " package validating;\n", + " import java.util.*;\n", + " public class CircularQueue {\n", + " private Object[] data;\n", + " private int in = 0, // Next available storage space \n", + " out = 0; // Next gettable object\n", + " // Has it wrapped around the circular queue?\n", + " private boolean wrapped = false;\n", + " public CircularQueue(int size) {\n", + " data = new Object[size];\n", + " // Must be true after construction:\n", + " assert invariant();\n", + " }\n", + " \n", + " public boolean empty() {\n", + " return !wrapped && in == out;\n", + " }\n", + " \n", + " public boolean full() {\n", + " \t return wrapped && in == out;\n", + " }\n", + " \n", + " \t public boolean isWrapped() { return wrapped; }\n", + " \n", + " public void put(Object item) {\n", + " \t precondition(item != null, \"put() null item\");\n", + " \t precondition(!full(),\n", + " \t \"put() into full CircularQueue\");\n", + " \t assert invariant();\n", + " \t data[in++] = item;\n", + " \t if(in >= data.length) {\n", + " in = 0;\n", + " wrapped = true;\n", + " \t }\n", + " \t\t assert invariant();\n", + " \t }\n", + " \n", + " public Object get() {\n", + " \t precondition(!empty(),\n", + " \t \"get() from empty CircularQueue\");\n", + " \t assert invariant();\n", + " \t Object returnVal = data[out];\n", + " \t data[out] = null;\n", + " \t out++;\n", + " if(out >= data.length) {\n", + " out = 0;\n", + " wrapped = false;\n", + " }\n", + " assert postcondition(\n", + " returnVal != null,\n", + " \"Null item in CircularQueue\");\n", + " assert invariant();\n", + " return returnVal;\n", + " }\n", + " \n", + " \t // Design-by-contract support methods:\n", + " private static void precondition(boolean cond, String msg) {\n", + " if(!cond) throw new CircularQueueException(msg);\n", + " }\n", + " \n", + " private static boolean postcondition(boolean cond, String msg) {\n", + " if(!cond) throw new CircularQueueException(msg);\n", + " \t return true;\n", + " }\n", + " \n", + " private boolean invariant() {\n", + " // Guarantee that no null values are in the\n", + " // region of 'data' that holds objects:\n", + " for(int i = out; i != in; i = (i + 1) % data.length)\n", + " if(data[i] == null)\n", + " throw new CircularQueueException(\"null in CircularQueue\");\n", + " // Guarantee that only null values are outside the\n", + " // region of 'data' that holds objects:\n", + " if(full()) return true;\n", + " for(int i = in; i != out; i = (i + 1) % data.length)\n", + " if(data[i] != null)\n", + " throw new CircularQueueException(\n", + " \"non-null outside of CircularQueue range: \" + dump());\n", + " return true;\n", + " }\n", + " \n", + " public String dump() {\n", + " return \"in = \" + in +\n", + " \", out = \" + out +\n", + " \", full() = \" + full() +\n", + " \", empty() = \" + empty() +\n", + " \", CircularQueue = \" + Arrays.asList(data);\n", + " }\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。\n", + "\n", + "**put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。\n", + "\n", + "注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。\n", + "\n", + "**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。\n", + "\n", + "**dump()** 帮助方法返回一个包含所有数据的字符串,而不是直接打印数据。这允许我们用这部分信息做更多事。 \n", + "\n", + "现在我们可以为类创建 JUnit 测试:\n", + "\n", + " ```java\n", + " // validating/tests/CircularQueueTest.java\n", + " package validating;\n", + " import org.junit.jupiter.api.*;\n", + " import static org.junit.jupiter.api.Assertions.*;\n", + " public class CircularQueueTest {\n", + " private CircularQueue queue = new CircularQueue(10);\n", + " private int i = 0;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@BeforeEach\n", + "public void initialize() {\n", + " while(i < 5) // Pre-load with some data\n", + " queue.put(Integer.toString(i++));\n", + " \t }\n", + "\n", + "// Support methods:\n", + "private void showFullness() {\n", + " assertTrue(queue.full());\n", + " assertFalse(queue.empty());\n", + " System.out.println(queue.dump());\n", + "}\n", + "\n", + "private void showEmptiness() {\n", + " assertFalse(queue.full());\n", + " assertTrue(queue.empty());\n", + " System.out.println(queue.dump());\n", + "}\n", + "\n", + "@Test\n", + "public void full() {\n", + " System.out.println(\"testFull\");\n", + " System.out.println(queue.dump());\n", + " System.out.println(queue.get());\n", + " System.out.println(queue.get());\n", + " while(!queue.full())\n", + " queue.put(Integer.toString(i++));\n", + " String msg = \"\";\n", + " try {\n", + " \t queue.put(\"\");\n", + " } catch(CircularQueueException e) {\n", + " \t msg = e.getMessage();\n", + " \t ∂System.out.println(msg);\n", + " }\n", + " assertEquals(msg, \"put() into full CircularQueue\");\n", + " showFullness();\n", + "}\n", + "\n", + "@Test\n", + "public void empty() {\n", + " System.out.println(\"testEmpty\");\n", + " while(!queue.empty())\n", + "\t\t System.out.println(queue.get());\n", + "\t\t String msg = \"\";\n", + " try {\n", + " queue.get();\n", + " } catch(CircularQueueException e) {\n", + " msg = e.getMessage();\n", + " System.out.println(msg);\n", + " }\n", + " assertEquals(msg, \"get() from empty CircularQueue\");\n", + " showEmptiness();\n", + "}\n", + "@Test\n", + "public void nullPut() {\n", + " System.out.println(\"testNullPut\");\n", + " String msg = \"\";\n", + " try {\n", + " \t queue.put(null);\n", + " } catch(CircularQueueException e) {\n", + " msg = e.getMessage();\n", + " System.out.println(msg);\n", + " }\n", + " assertEquals(msg, \"put() null item\");\n", + "}\n", + "\n", + "@Test\n", + "public void circularity() {\n", + "\t System.out.println(\"testCircularity\");\n", + "\t while(!queue.full())\n", + "\t\t queue.put(Integer.toString(i++));\n", + "\t\t showFullness();\n", + "\t\t assertTrue(queue.isWrapped());\n", + " \n", + " while(!queue.empty())\n", + " \t System.out.println(queue.get());\n", + " \t showEmptiness();\n", + " \n", + " while(!queue.full())\n", + " \t queue.put(Integer.toString(i++));\n", + " \t showFullness();\n", + " \n", + " while(!queue.empty())\n", + " \t System.out.println(queue.get());\n", + " \t showEmptiness();\n", + " }\n", + " }\n", + " /* Output:\n", + " testNullPut\n", + " put() null item\n", + " testCircularity\n", + " in = 0, out = 0, full() = true, empty() = false,\n", + " CircularQueue =\n", + " [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + " 0\n", + " 1\n", + " 2\n", + " 3\n", + " 4\n", + " 5\n", + " 6\n", + " 7\n", + " 8\n", + " 9\n", + " in = 0, out = 0, full() = false, empty() = true,\n", + " CircularQueue =\n", + " [null, null, null, null, null, null, null, null, null,\n", + " null]\n", + " in = 0, out = 0, full() = true, empty() = false,\n", + " CircularQueue =\n", + " [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", + " 10\n", + " 11\n", + " 12\n", + " 13\n", + " 14\n", + " 15\n", + " 16\n", + " 17\n", + " 18\n", + " 19\n", + " in = 0, out = 0, full() = false, empty() = true,\n", + " CircularQueue =\n", + " [null, null, null, null, null, null, null, null, null,\n", + " null]\n", + " testFull\n", + " in = 5, out = 0, full() = false, empty() = false,\n", + " CircularQueue =\n", + " [0, 1, 2, 3, 4, null, null, null, null, null]\n", + " 0\n", + " 1\n", + " put() into full CircularQueue\n", + " in = 2, out = 2, full() = true, empty() = false,\n", + " CircularQueue =\n", + " [10, 11, 2, 3, 4, 5, 6, 7, 8, 9]\n", + " testEmpty\n", + " 0\n", + " 1\n", + " 2\n", + " 3\n", + " 4\n", + " get() from empty CircularQueue\n", + " in = 5, out = 5, full() = false, empty() = true,\n", + " CircularQueue =\n", + " [null, null, null, null, null, null, null, null, null,\n", + " null]\n", + " */\n", + " ```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**initialize()** 添加了一些数据,因此每个测试的 **CircularQueue** 都是部分满的。**showFullness()** 和 **showempty()** 表明 **CircularQueue** 是满的还是空的,这四种测试方法中的每一种都确保了 **CircularQueue** 功能在不同的地方正确运行。\n", + "\n", + "通过将 Dbc 和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些 Dbc 测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。\n", + "\n", + "#### 使用Guava前置条件\n", + "\n", + "在非严格的 DbC 中,前置条件是 DbC 中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为 Java 在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。\n", + "\n", + "谷歌的 Guava 库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/GuavaPreconditions.java\n", + "// Demonstrating Guava Preconditions\n", + "import java.util.function.*;\n", + "import static com.google.common.base.Preconditions.*;\n", + "public class GuavaPreconditions {\n", + " static void test(Consumer c, String s) {\n", + " try {\n", + " System.out.println(s);\n", + " c.accept(s);\n", + " System.out.println(\"Success\");\n", + " } catch(Exception e) {\n", + " String type = e.getClass().getSimpleName();\n", + " String msg = e.getMessage();\n", + " System.out.println(type +\n", + " (msg == null ? \"\" : \": \" + msg));\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " test(s -> s = checkNotNull(s), \"X\");\n", + " test(s -> s = checkNotNull(s), null);\n", + " test(s -> s = checkNotNull(s, \"s was null\"), null);\n", + " test(s -> s = checkNotNull(\n", + " s, \"s was null, %s %s\", \"arg2\", \"arg3\"), null);\n", + " test(s -> checkArgument(s == \"Fozzie\"), \"Fozzie\");\n", + " test(s -> checkArgument(s == \"Fozzie\"), \"X\");\n", + " test(s -> checkArgument(s == \"Fozzie\"), null);\n", + " test(s -> checkArgument(\n", + " s == \"Fozzie\", \"Bear Left!\"), null);\n", + " test(s -> checkArgument(\n", + " s == \"Fozzie\", \"Bear Left! %s Right!\", \"Frog\"),\n", + " null);\n", + " test(s -> checkState(s.length() > 6), \"Mortimer\");\n", + " test(s -> checkState(s.length() > 6), \"Mort\");\n", + " test(s -> checkState(s.length() > 6), null);\n", + " test(s ->\n", + " checkElementIndex(6, s.length()), \"Robert\");\n", + " test(s ->\n", + " checkElementIndex(6, s.length()), \"Bob\");\n", + " test(s ->\n", + " checkElementIndex(6, s.length()), null);\n", + " test(s ->\n", + " checkPositionIndex(6, s.length()), \"Robert\");\n", + " test(s ->\n", + " checkPositionIndex(6, s.length()), \"Bob\");\n", + " test(s ->\n", + " checkPositionIndex(6, s.length()), null);\n", + " test(s -> checkPositionIndexes(\n", + " 0, 6, s.length()), \"Hieronymus\");\n", + " test(s -> checkPositionIndexes(\n", + " 0, 10, s.length()), \"Hieronymus\");\n", + " test(s -> checkPositionIndexes(\n", + " 0, 11, s.length()), \"Hieronymus\");\n", + " test(s -> checkPositionIndexes(\n", + " -1, 6, s.length()), \"Hieronymus\");\n", + " test(s -> checkPositionIndexes(\n", + " 7, 6, s.length()), \"Hieronymus\");\n", + " test(s -> checkPositionIndexes(\n", + " 0, 6, s.length()), null);\n", + " }\n", + "}\n", + "/* Output:\n", + "X\n", + "Success\n", + "null\n", + "NullPointerException\n", + "null\n", + "NullPointerException: s was null\n", + "null\n", + "NullPointerException: s was null, arg2 arg3\n", + "Fozzie\n", + "Success\n", + "X\n", + "IllegalArgumentException\n", + "null\n", + "IllegalArgumentException\n", + "null\n", + "IllegalArgumentException: Bear Left!\n", + "null\n", + "IllegalArgumentException: Bear Left! Frog Right!\n", + "Mortimer\n", + "Success\n", + "Mort\n", + "IllegalStateException\n", + "null\n", + "NullPointerException\n", + "Robert\n", + "IndexOutOfBoundsException: index (6) must be less than\n", + "size (6)\n", + "Bob\n", + "IndexOutOfBoundsException: index (6) must be less than\n", + "size (3)\n", + "null\n", + "NullPointerException\n", + "Robert\n", + "Success\n", + "Bob\n", + "IndexOutOfBoundsException: index (6) must not be\n", + "greater than size (3)\n", + "null\n", + "NullPointerException\n", + "Hieronymus\n", + "Success\n", + "Hieronymus\n", + "Success\n", + "Hieronymus\n", + "IndexOutOfBoundsException: end index (11) must not be\n", + "greater than size (10)\n", + "Hieronymus\n", + "IndexOutOfBoundsException: start index (-1) must not be\n", + "negative\n", + "Hieronymus\n", + "IndexOutOfBoundsException: end index (6) must not be\t\n", + "less than start index (7)\n", + "null\n", + "NullPointerException\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然 Guava 的前置条件适用于所有类型,但我这里只演示 **字符串(String)** 类型。**test()** 方法需要一个Consumer,因此我们可以传递一个 lambda 表达式作为第一个参数,传递给 lambda 表达式的字符串作为第二个参数。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给 lambda 表达式。try 块中的第二个 **println**() 仅在 lambda 表达式成功时才显示; 否则 catch 块将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。\n", + "\n", + "每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以你可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null** 值的对象构造:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "/ validating/NonNullConstruction.java\n", + "import static com.google.common.base.Preconditions.*;\n", + "public class NonNullConstruction {\n", + " private Integer n;\n", + " private String s;\n", + " NonNullConstruction(Integer n, String s) {\n", + " this.n = checkNotNull(n);\t\n", + " this.s = checkNotNull(s);\n", + " }\n", + " public static void main(String[] args) {\n", + " NonNullConstruction nnc =\n", + " new NonNullConstruction(3, \"Trousers\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()** 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。\n", + "\n", + "最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()** 确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。 **checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。\n", + "\n", + "所有的 Guava 前置条件对于基本类型和对象都有必要的重载。\n", + "\n", + "\n", + "\n", + "## 测试驱动开发\n", + "\n", + "之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码,而且还可以得到更好的代码设计。 一般情况下这个说法都是正确的。 一旦我想到“我将如何测试我的代码?”,这个想法将使我的代码产生变化,并且往往是从“可测试”转变为“可用”。\n", + "\n", + "纯粹的 TDD 主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 **String** 中字符的大小写。 让我们随意添加一些约束:**String** 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。\n", + "\n", + "此示例与标准 TDD 不同,因为它的作用在于接收 **StringInverter** 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 为了满足这个要求,将 **StringInverter** 作为接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/StringInverter.java\n", + "package validating;\n", + "\n", + "interface StringInverter {\n", + "\tString invert(String str);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了 JUnit5 中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。\n", + "\n", + "JUnit5 提供了几种动态生成测试的方法,但这里使用的方法可能是最复杂的。 **DynamicTest.stream() **方法采用了:\n", + "\n", + "- 对象集合上的迭代器 (versions) ,这个迭代器在不同组的测试中是不同的。 迭代器生成的对象可以是任何类型,但是只能有一种对象生成,因此对于存在多个不同的对象类型时,必须人为地将它们打包成单个类型。\n", + "- **Function**,它从迭代器获取对象并生成描述测试的 **String** 。\n", + "- **Consumer**,它从迭代器获取对象并包含基于该对象的测试代码。\n", + "\n", + "在此示例中,所有代码将在 **testVersions()** 中进行组合以防止代码重复。 迭代器生成的对象是对 **DynamicTest** 的不同实现,这些对象体现了对接口不同版本的实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/tests/DynamicStringInverterTests.java\n", + "package validating;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import org.junit.jupiter.api.*;\n", + "import static org.junit.jupiter.api.Assertions.*;\n", + "import static org.junit.jupiter.api.DynamicTest.*;\n", + "\n", + "class DynamicStringInverterTests {\n", + "\t// Combine operations to prevent code duplication:\n", + "\tStream testVersions(String id,\n", + "\t\tFunction test) {\n", + "\t\tList versions = Arrays.asList(\n", + "\t\t\tnew Inverter1(), new Inverter2(),\n", + "\t\t\tnew Inverter3(), new Inverter4());\n", + "\t\treturn DynamicTest.stream(\n", + "\t\t\tversions.iterator(),\n", + "\t\t\tinverter -> inverter.getClass().getSimpleName(),\n", + "\t\t\tinverter -> {\n", + "\t\t\t\tSystem.out.println(\n", + "\t\t\t\t\tinverter.getClass().getSimpleName() +\n", + "\t\t\t\t\t\t\": \" + id);\n", + "\t\t\t\ttry {\n", + "\t\t\t\t\tif(test.apply(inverter) != \"fail\")\n", + "\t\t\t\t\t\tSystem.out.println(\"Success\");\n", + "\t\t\t\t} catch(Exception | Error e) {\n", + "\t\t\t\t\tSystem.out.println(\n", + "\t\t\t\t\t\t\"Exception: \" + e.getMessage());\n", + "\t\t\t\t}\n", + "\t\t\t}\n", + "\t\t);\n", + "\t}\n", + " String isEqual(String lval, String rval) {\n", + "\t\tif(lval.equals(rval))\n", + "\t\t\treturn \"success\";\n", + "\t\tSystem.out.println(\"FAIL: \" + lval + \" != \" + rval);\n", + "\t\treturn \"fail\";\n", + "\t}\n", + " @BeforeAll\n", + "\tstatic void startMsg() {\n", + "\t\tSystem.out.println(\n", + "\t\t\t\">>> Starting DynamicStringInverterTests <<<\");\n", + "\t}\n", + " @AfterAll\n", + "\tstatic void endMsg() {\n", + "\t\tSystem.out.println(\n", + "\t\t\t\">>> Finished DynamicStringInverterTests <<<\");\n", + "\t}\n", + "\t@TestFactory\n", + "\tStream basicInversion1() {\n", + "\t\tString in = \"Exit, Pursued by a Bear.\";\n", + "\t\tString out = \"eXIT, pURSUED BY A bEAR.\";\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Basic inversion (should succeed)\",\n", + "\t\t\tinverter -> isEqual(inverter.invert(in), out)\n", + "\t\t);\n", + "\t}\n", + "\t@TestFactory\n", + "\tStream basicInversion2() {\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Basic inversion (should fail)\",\n", + "\t\t\tinverter -> isEqual(inverter.invert(\"X\"), \"X\"));\n", + "\t}\n", + "\t@TestFactory\n", + "\tStream disallowedCharacters() {\n", + "\t\tString disallowed = \";-_()*&^%$#@!~`0123456789\";\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Disallowed characters\",\n", + "\t\t\tinverter -> {\n", + "\t\t\t\tString result = disallowed.chars()\n", + "\t\t\t\t\t.mapToObj(c -> {\n", + "\t\t\t\t\t\tString cc = Character.toString((char)c);\n", + "\t\t\t\t\t\ttry {\n", + "\t\t\t\t\t\t\tinverter.invert(cc);\n", + "\t\t\t\t\t\t\treturn \"\";\n", + "\t\t\t\t\t\t} catch(RuntimeException e) {\n", + "\t\t\t\t\t\t\treturn cc;\n", + "\t\t\t\t\t\t}\n", + "\t\t\t\t\t}).collect(Collectors.joining(\"\"));\n", + "\t\t\t\tif(result.length() == 0)\n", + "\t\t\t\t\treturn \"success\";\n", + "\t\t\t\tSystem.out.println(\"Bad characters: \" + result);\n", + "\t\t\t\treturn \"fail\";\n", + "\t\t\t}\n", + "\t\t);\n", + "\t}\n", + " @TestFactory\n", + "\tStream allowedCharacters() {\n", + "\t\tString lowcase = \"abcdefghijklmnopqrstuvwxyz ,.\";\n", + "\t\tString upcase = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.\";\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Allowed characters (should succeed)\",\n", + " inverter -> {\n", + "\t\t\t\tassertEquals(inverter.invert(lowcase), upcase);\n", + "\t\t\t\tassertEquals(inverter.invert(upcase), lowcase);\n", + "\t\t\t\treturn \"success\";\n", + "\t\t\t}\n", + "\t\t);\n", + "\t}\n", + "\t@TestFactory\n", + "\tStream lengthNoGreaterThan30() {\n", + "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", + "\t\tassertTrue(str.length() > 30);\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Length must be less than 31 (throws exception)\",\n", + "\t\t\tinverter -> inverter.invert(str)\n", + "\t\t);\n", + "\t}\n", + "\t@TestFactory\n", + "\tStream lengthLessThan31() {\n", + "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", + "\t\tassertTrue(str.length() < 31);\n", + "\t\treturn testVersions(\n", + "\t\t\t\"Length must be less than 31 (should succeed)\",\n", + "\t\t\tinverter -> inverter.invert(str)\n", + "\t\t);\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在一般的测试中,你可能认为在进行一个结果为失败的测试时应该停止代码构建。 但是在这里,我们只希望系统报告问题,但仍然继续运行,以便你可以看到不同版本的 **StringInverter** 的效果。\n", + "\n", + "每个使用 **@TestFactory** 注释的方法都会生成一个 **DynamicTest** 对象的 **Stream**(通过 **testVersions()** ),每个 JUnit 都像常规的 **@Test** 方法一样执行。\n", + "\n", + "现在测试都已经准备好了,我们就可以开始实现 **StringInverter **了。 我们从一个仅返回其参数的假的实现类开始:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Inverter1.java\n", + "package validating;\n", + "public class Inverter1 implements StringInverter {\n", + "\tpublic String invert(String str) { return str; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来我们实现反转操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Inverter2.java\n", + "package validating;\n", + "import static java.lang.Character.*;\n", + "public class Inverter2 implements StringInverter {\n", + "\tpublic String invert(String str) {\n", + "\t\tString result = \"\";\n", + "\t\tfor(int i = 0; i < str.length(); i++) {\n", + "\t\t\tchar c = str.charAt(i);\n", + "\t\t\tresult += isUpperCase(c) ?\n", + "\t\t\t\t\t toLowerCase(c) :\n", + "\t\t\t\t\t toUpperCase(c);\n", + "\t\t}\n", + "\t\treturn result;\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在添加代码以确保输入不超过30个字符:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Inverter3.java\n", + "package validating;\n", + "import static java.lang.Character.*;\n", + "public class Inverter3 implements StringInverter {\n", + "\tpublic String invert(String str) {\n", + "\t\tif(str.length() > 30)\n", + "\t\t\tthrow new RuntimeException(\"argument too long!\");\n", + "\t\tString result = \"\";\n", + "\t\tfor(int i = 0; i < str.length(); i++) {\n", + "\t\t\tchar c = str.charAt(i);\n", + "\t\t\tresult += isUpperCase(c) ?\n", + "\t\t\t\t\t toLowerCase(c) :\n", + "\t\t\t\t\t toUpperCase(c);\n", + " }\n", + "\t\treturn result;\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最后,我们排除了不允许的字符:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/Inverter4.java\n", + "package validating;\n", + "import static java.lang.Character.*;\n", + "public class Inverter4 implements StringInverter {\n", + "\tstatic final String ALLOWED =\n", + "\t\t\"abcdefghijklmnopqrstuvwxyz ,.\" +\n", + "\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n", + "\tpublic String invert(String str) {\n", + "\t\tif(str.length() > 30)\n", + "\t\t\tthrow new RuntimeException(\"argument too long!\");\n", + "\t\tString result = \"\";\n", + "\t\tfor(int i = 0; i < str.length(); i++) {\n", + "\t\t\tchar c = str.charAt(i);\n", + "\t\t\tif(ALLOWED.indexOf(c) == -1)\n", + "\t\t\t\tthrow new RuntimeException(c + \" Not allowed\");\n", + "\t\t\tresult += isUpperCase(c) ?\n", + "\t\t\t\t\t toLowerCase(c) :\n", + "\t\t\t\t\t toUpperCase(c);\n", + "\t\t}\n", + "\t\treturn result;\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你将从测试输出中看到,每个版本的 **Inverter** 都几乎能通过所有测试。 当你在进行测试优先的开发时会有相同的体验。\n", + "\n", + "**DynamicStringInverterTests.java** 仅是为了显示 TDD 过程中不同 **StringInverter** 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 **StringInverter** 类直到它满足所有测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/tests/StringInverterTests.java\n", + "package validating;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import org.junit.jupiter.api.*;\n", + "import static org.junit.jupiter.api.Assertions.*;\n", + "\n", + "public class StringInverterTests {\n", + "\tStringInverter inverter = new Inverter4();\n", + "\t@BeforeAll\n", + "\tstatic void startMsg() {\n", + "\t\tSystem.out.println(\">>> StringInverterTests <<<\");\n", + "\t}\n", + " @Test\n", + "\tvoid basicInversion1() {\n", + "\t\tString in = \"Exit, Pursued by a Bear.\";\n", + "\t\tString out = \"eXIT, pURSUED BY A bEAR.\";\n", + "\t\tassertEquals(inverter.invert(in), out);\n", + "\t}\n", + "\t@Test\n", + "\tvoid basicInversion2() {\n", + "\t\texpectThrows(Error.class, () -> {\n", + "\t\t\tassertEquals(inverter.invert(\"X\"), \"X\");\n", + "\t\t});\n", + "\t}\n", + "\t@Test\n", + "\tvoid disallowedCharacters() {\n", + "\t\tString disallowed = \";-_()*&^%$#@!~`0123456789\";\n", + "\t\tString result = disallowed.chars()\n", + "\t\t\t.mapToObj(c -> {\n", + "\t\t\t\tString cc = Character.toString((char)c);\n", + "\t\t\t\ttry {\n", + "\t\t\t\t\tinverter.invert(cc);\n", + "\t\t\t\t\treturn \"\";\n", + "\t\t\t\t} catch(RuntimeException e) {\n", + "\t\t\t\t\treturn cc;\n", + "\t\t\t\t}\n", + "\t\t\t}).collect(Collectors.joining(\"\"));\n", + "\t\tassertEquals(result, disallowed);\n", + "\t}\n", + "\t@Test\n", + "\tvoid allowedCharacters() {\n", + "\t\tString lowcase = \"abcdefghijklmnopqrstuvwxyz ,.\";\n", + "\t\tString upcase = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.\";\n", + "\t\tassertEquals(inverter.invert(lowcase), upcase);\n", + "\t\tassertEquals(inverter.invert(upcase), lowcase);\n", + "\t}\n", + "\t@Test\n", + "\tvoid lengthNoGreaterThan30() {\n", + "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", + "\t\tassertTrue(str.length() > 30);\n", + "\t\texpectThrows(RuntimeException.class, () -> {\n", + "\t\t\tinverter.invert(str);\n", + "\t\t});\n", + "\t}\n", + " @Test\n", + "\tvoid lengthLessThan31() {\n", + "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", + "\t\tassertTrue(str.length() < 31);\n", + "\t\tinverter.invert(str);\n", + "\t}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以通过这种方式进行开发:一开始在测试中建立你期望程序应有的所有特性,然后你就能在实现中一步步添加功能,直到所有测试通过。 完成后,你还可以在将来通过这些测试来得知(或让其他任何人得知)当修复错误或添加功能时,代码是否被破坏了。 TDD的目标是产生更好,更周全的测试,因为在完全实现之后尝试实现完整的测试覆盖通常会产生匆忙或无意义的测试。\n", + "\n", + "### 测试驱动 vs. 测试优先\n", + "\n", + "虽然我自己还没有达到测试优先的意识水平,但我最感兴趣的是来自测试优先中的“测试失败的书签”这一概念。 当你离开你的工作一段时间后,重新回到工作进展中,甚至找到你离开时工作到的地方有时会很有挑战性。 然而,以失败的测试为书签能让你找到之前停止的地方。 这似乎让你能更轻松地暂时离开你的工作,因为不用担心找不到工作进展的位置。\n", + "\n", + "纯粹的测试优先编程的主要问题是它假设你事先了解了你正在解决的问题。 根据我自己的经验,我通常是从实验开始,而只有当我处理问题一段时间后,我对它的理解才会达到能给它编写测试的程度。 当然,偶尔会有一些问题在你开始之前就已经完全定义,但我个人并不常遇到这些问题。 实际上,可能用“*面向测试的开发* ( *Test-Oriented Development* )”这个短语来描述编写测试良好的代码或许更好。\n", + "\n", + "\n", + "\n", + "## 日志\n", + "\n", + "### 日志会给出正在运行的程序的各种信息。\n", + "\n", + "在调试程序中,日志可以是普通状态数据,用于显示程序运行过程(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。\n", + "\n", + "在调试期间,日志也能带来好处。 如果没有日志,你可能会尝试通过插入 **println()** 语句来打印出程序的行为。 本书中的一些例子使用了这种技术,并且在没有调试器的情况下(下文中很快就会介绍这样一个主题),它就是你唯一的工具。 但是,一旦你确定程序正常运行,你可能会将 **println()** 语句注释或者删除。 然而,如果你遇到更多错误,你可能又需要运行它们。因此,如果能够只在需要时轻松启用输出程序状态就好多了。\n", + "\n", + "程序员在日志包可供使用之前,都只能依赖 Java 编译器移除未调用的代码。 如果 **debug** 是一个 **static final boolean **,你就可以这么写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(debug) {\n", + "\tSystem.out.println(\"Debug info\");\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后,当 **debug **为 **false **时,编译器将移除大括号内的代码。 因此,未调用的代码不会对运行时产生影响。 使用这种方法,你可以在整个程序中放置跟踪代码,并轻松启用和关闭它。 但是,该技术的一个缺点是你必须重新编译代码才能启用和关闭跟踪语句。因此,通过更改配置文件来修改日志属性,从而起到启用跟踪语句但不用重新编译程序会更方便。\n", + "\n", + "业内普遍认为标准 Java 发行版本中的日志包 **(java.util.logging)** 的设计相当糟糕。 大多数人会选择其他的替代日志包。如 *Simple Logging Facade for Java(SLF4J)* ,它为多个日志框架提供了一个封装好的调用方式,这些日志框架包括 **java.util.logging** , **logback** 和 **log4j **。 SLF4J 允许用户在部署时插入所需的日志框架。\n", + "\n", + "SLF4J 提供了一个复杂的工具来报告程序的信息,它的效率与前面示例中的技术几乎相同。 对于非常简单的信息日志记录,你可以执行以下操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/SLF4JLogging.java\n", + "import org.slf4j.*;\n", + "public class SLF4JLogging {\n", + "\tprivate static Logger log =\n", + "\t\tLoggerFactory.getLogger(SLF4JLogging.class);\n", + "\tpublic static void main(String[] args) {\n", + "\t\tlog.info(\"hello logging\");\n", + "\t}\n", + "}\n", + "/* Output:\n", + "2017-05-09T06:07:53.418\n", + "[main] INFO SLF4JLogging - hello logging\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "日志输出中的格式和信息,甚至输出是否正常或“错误”都取决于 SLF4J 所连接的后端程序包是怎样实现的。 在上面的示例中,它连接到的是 **logback** 库(通过本书的 **build.gradle** 文件),并显示为标准输出。\n", + "\n", + "如果我们修改 **build.gradle** 从而使用内置在 JDK 中的日志包作为后端,则输出显示为错误输出,如下所示:\n", + "\n", + "**Aug 16, 2016 5:40:31 PM InfoLogging main**\n", + "**INFO: hello logging**\n", + "\n", + "日志系统会检测日志消息处所在的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。\n", + "\n", + "### 日志等级\n", + "\n", + "SLF4J 提供了多个等级的日志消息。下面这个例子以“严重性”的递增顺序对它们作出演示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/SLF4JLevels.java\n", + "import org.slf4j.*;\n", + "public class SLF4JLevels {\n", + "\tprivate static Logger log =\n", + "\t\tLoggerFactory.getLogger(SLF4JLevels.class);\n", + "\tpublic static void main(String[] args) {\n", + "\t\tlog.trace(\"Hello\");\n", + "\t\tlog.debug(\"Logging\");\n", + "\t\tlog.info(\"Using\");\n", + "\t\tlog.warn(\"the SLF4J\");\n", + "\t\tlog.error(\"Facade\");\n", + "\t}\n", + "}\n", + "/* Output:\n", + "2017-05-09T06:07:52.846\n", + "[main] TRACE SLF4JLevels - Hello\n", + "2017-05-09T06:07:52.849\n", + "[main] DEBUG SLF4JLevels - Logging\n", + "2017-05-09T06:07:52.849\n", + "[main] INFO SLF4JLevels - Using\n", + "2017-05-09T06:07:52.850\n", + "[main] WARN SLF4JLevels - the SLF4J\n", + "2017-05-09T06:07:52.851\n", + "[main] ERROR SLF4JLevels - Facade\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以按等级来查找消息。 级别通常设置在单独的配置文件中,因此你可以重新配置而无需重新编译。 配置文件格式取决于你使用的后端日志包实现。 如 **logback** 使用 XML :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "xml" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\n", + "\n", + "\n", + "\t\n", + "\t\t\n", + "\t\t\t\n", + "%d{yyyy-MM-dd'T'HH:mm:ss.SSS}\n", + "[%thread] %-5level %logger - %msg%n\n", + "\t\t\t\n", + "\t\t\n", + "\t\n", + "\t\n", + "\t\t\n", + "\t\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以尝试将 ** **行更改为其他级别,然后重新运行该程序查看日志输出的更改情况。 如果你没有写 **logback.xml** 文件,日志系统将采取默认配置。\n", + "\n", + "这只是 SLF4J 最简单的介绍和一般的日志消息,但也足以作为使用日志的基础 - 你可以沿着这个进行更长久的学习和实践。你可以查阅 [SLF4J 文档](http://www.slf4j.org/manual.html)来获得更深入的信息。\n", + "\n", + "\n", + "\n", + "## 调试\n", + "\n", + "尽管聪明地使用 **System.out** 或日志信息能给我们带来对程序行为的有效见解,但对于困难问题来说,这种方式就显得笨拙且耗时了。\n", + "\n", + "你也可能需要更加深入地理解程序,仅依靠打印日志做不到。此时你需要调试器。除了比打印语句更快更轻易地展示信息以外,调试器还可以设置断点,并在程序运行到这些断点处暂停程序。\n", + "\n", + "使用调试器,可以展示任何时刻的程序状态,查看变量的值,一步一步运行程序,连接远程运行的程序等等。特别是当你构建较大规模的系统(bug 容易被掩埋)时,熟练使用调试器是值得的。\n", + "\n", + "### 使用 JDB 调试\n", + "\n", + "Java 调试器(JDB)是 JDK 内置的命令行工具。从调试的指令和命令行接口两方面看的话,JDB 至少从概念上是 GNU 调试器(GDB,受 Unix DB 的影响)的继承者。JDB 对于学习调试和执行简单的调试任务来说是有用的,而且知道只要安装了 JDK 就可以使用 JDB 是有帮助的。然而,对于大型项目来说,你可能想要一个图形化的调试器,这在后面会描述。\n", + "\n", + "假设你写了如下程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/SimpleDebugging.java\n", + "// {ThrowsException}\n", + "public class SimpleDebugging {\n", + " private static void foo1() {\n", + " System.out.println(\"In foo1\");\n", + " foo2();\n", + " }\n", + " \n", + " private static void foo2() {\n", + " System.out.println(\"In foo2\");\n", + " foo3();\n", + " }\n", + " \n", + " private static void foo3() {\n", + " System.out.println(\"In foo3\");\n", + " int j = 1;\n", + " j--;\n", + " int i = 5 / j;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " foo1();\n", + " }\n", + "}\n", + "/* Output\n", + "In foo1\n", + "In foo2\n", + "In foo3\n", + "__[Error Output]__\n", + "Exception in thread \"main\"\n", + "java.lang.ArithmeticException: /by zero \n", + "at \n", + "SimpleDebugging.foo3(SimpleDebugging.java:17)\n", + "at \n", + "SimpleDebugging.foo2(SimpleDebugging.java:11)\n", + "at\n", + "SimpleDebugging.foo1(SimpleDebugging.java:7)\n", + "at\n", + "SimpleDebugging.main(SimpleDebugging.java:20)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先看方法 `foo3()`,问题很明显:除数是 0。但是假如这段代码被埋没在大型程序中(像这里的调用序列暗示的那样)而且你不知道从哪儿开始查找问题。结果呢,异常会给出足够的信息让你定位问题。然而,假设事情更加复杂,你必须更加深入程序中来获得比异常提供的更多的信息。\n", + "\n", + "为了运行 JDB,你需要在编译 **SimpleDebugging.java** 时加上 **-g** 标记,从而告诉编译器生成编译信息。然后使用如下命令开始调试程序:\n", + "\n", + "**jdb SimpleDebugging**\n", + "\n", + "接着 JDB 就会运行,出现命令行提示。你可以输入 **?** 查看可用的 JDB 命令。\n", + "\n", + "这里展示了如何使用交互式追踪一个问题的调试历程:\n", + "\n", + "**Initializing jdb...**\n", + "\n", + "**> catch Exception**\n", + "\n", + "`>` 表明 JDB 在等待输入命令。命令 **catch Exception** 在任何抛出异常的地方设置断点(然而,即使你不显式地设置断点,调试器也会停止— JDB 中好像是默认在异常抛出处设置了异常)。接着命令行会给出如下响应:\n", + "\n", + "**Deferring exception catch Exception.**\n", + "\n", + "**It will be set after the class is loaded.**\n", + "\n", + "继续输入:\n", + "\n", + "**> run**\n", + "\n", + "现在程序将运行到下个断点处,在这个例子中就是异常发生的地方。下面是运行 **run** 命令的结果:\n", + "\n", + "**run SimpleDebugging**\n", + "\n", + "**Set uncaught java.lang.Throwable**\n", + "\n", + "**Set deferred uncaught java.lang.Throwable**\n", + "\n", + "**>**\n", + "\n", + "**VM Started: In foo1**\n", + "\n", + "**In foo2**\n", + "\n", + "**In foo3**\n", + "\n", + "**Exception occurred: java.lang.ArithmeticException**\n", + "\n", + "**(uncaught)\"thread=main\",**\n", + "\n", + "**SimpleDebugging.foo3(),line=16 bci=15**\n", + "\n", + "**16 int i = 5 / j**\n", + "\n", + "程序运行到第16行时发生异常,但是 JDB 在异常发生时就不复存在。调试器还展示了是哪一行导致了异常。你可以使用 **list** 将导致程序终止的执行点列出来:\n", + "\n", + "**main[1] list**\n", + "\n", + "**12 private static void foo3() {**\n", + "\n", + "**13 System.out.println(\"In foo3\");**\n", + "\n", + "**14 int j = 1;**\n", + "\n", + "**15 j--;**\n", + "\n", + "**16 => int i = 5 / j;**\n", + "\n", + "**17 }**\n", + "\n", + "**18 public static void main(String[] args) {**\n", + "\n", + "**19 foo1();**\n", + "\n", + "**20 }**\n", + "\n", + "**21 }**\n", + "\n", + "**/* Output:**\n", + "\n", + "上述 `=>` 展示了程序将继续运行的执行点。你可以使用命令 **cont**(continue) 继续运行,但是会导致 JDB 在异常发生时退出并打印出栈轨迹信息。\n", + "\n", + "命令 **locals** 能转储所有的局部变量值:\n", + "\n", + "**main[1] locals**\n", + "\n", + "**Method arguments:**\n", + "\n", + "**Local variables:**\n", + "\n", + "**j = 0**\n", + "\n", + "命令 **wherei** 打印进入当前线程的方法栈中的栈帧信息:\n", + "\n", + "**main[1] wherei**\n", + "\n", + "**[1] SimpleDebugging.foo3(SimpleDebugging.java:16), pc =15**\n", + "\n", + "**[2] SimpleDebugging.foo2(SimpleDebugging.java:10), pc = 8**\n", + "\n", + "**[3] SimpleDebugging.foo1(SimpleDebugging.java:6), pc = 8**\n", + "\n", + "**[4] SimpleDebugging.main(SimpleDebugging.java:19), pc = 10**\n", + "\n", + "**wherei** 后的每一行代表一个方法调用和调用返回点(由程序计数器显示数值)。这里的调用序列是 **main()**, **foo1()**, **foo2()** 和 **foo3()**。\n", + "\n", + "因为命令 **list** 展示了执行停止的地方,所以你通常有足够的信息得知发生了什么并修复它。命令 **help** 将会告诉你更多关于 **jdb** 的用法,但是在花更多的时间学习它之前必须明白命令行调试器往往需要花费更多的精力得到结果。使用 **jdb** 学习调试的基础部分,然后转而学习图形界面调试器。\n", + "\n", + "### 图形化调试器\n", + "\n", + "使用类似 JDB 的命令行调试器是不方便的。它需要显式的命令去查看变量的状态(**locals**, **dump**),列出源代码中的执行点(**list**),查找系统中的线程(**threads**),设置断点(**stop in**, **stop at**)等等。使用图形化调试器只需要点击几下,不需要使用显式的命令就能使用这些特性,而且能查看被调试程序的最新细节。\n", + "\n", + "因此,尽管你可能一开始用 JDB 尝试调试,但是你将发现使用图形化调试器能更加高效、更快速地追踪 bug。IBM 的 Eclipse,Oracle 的 NetBeans 和 JetBrains 的 IntelliJ 这些集成开发环境都含有面向 Java 语言的好用的图形化调试器。\n", + "\n", + "\n", + "\n", + "## 基准测试\n", + "\n", + "> 我们应该忘掉微小的效率提升,说的就是这些 97% 的时间做的事:过早的优化是万恶之源。\n", + ">\n", + "> ​ —— Donald Knuth\n", + "\n", + "如果你发现自己正在过早优化的滑坡上,你可能浪费了几个月的时间(如果你雄心勃勃的话)。通常,一个简单直接的编码方法就足够好了。如果你进行了不必要的优化,就会使你的代码变得无谓的复杂和难以理解。\n", + "\n", + "基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。\n", + "\n", + "可以简单地对一个代码片段的执行计时吗?在像 C 这样直接的编程语言中,这个方法的确可行。在像 Java 这样拥有复杂的运行时系统的编程语言中,基准测试变得更有挑战性。为了生成可靠的数据,环境设置必须控制诸如 CPU 频率,节能特性,其他运行在相同机器上的进程,优化器选项等等。\n", + "\n", + "### 微基准测试\n", + "\n", + "写一个计时工具类从而比较不同代码块的执行速度是具有吸引力的。看上去这会产生一些有用的数据。比如,这里有一个简单的 **Timer** 类,可以用以下两种方式使用它:\n", + "\n", + "1. 创建一个 **Timer** 对象,执行一些操作然后调用 **Timer** 的 **duration()** 方法产生以毫秒为单位的运行时间。\n", + "2. 向静态的 **duration()** 方法中传入 **Runnable**。任何符合 **Runnable** 接口的类都有一个函数式方法 **run()**,该方法没有入参,且没有返回。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Timer.java\n", + "package onjava;\n", + "import static java.util.concurrent.TimeUnit.*;\n", + "\n", + "public class Timer {\n", + " private long start = System.nanoTime();\n", + " \n", + " public long duration() {\n", + " return NANOSECONDS.toMillis(System.nanoTime() - start);\n", + " }\n", + " \n", + " public static long duration(Runnable test) {\n", + " Timer timer = new Timer();\n", + " test.run();\n", + " return timer.duration();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个很直接的计时方式。难道我们不能只运行一些代码然后看它的运行时长吗?\n", + "\n", + "有许多因素会影响你的结果,即使是生成提示符也会造成计时的混乱。这里举一个看上去天真的例子,它使用了 标准的 Java **Arrays** 库(后面会详细介绍):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/BadMicroBenchmark.java\n", + "// {ExcludeFromTravisCI}\n", + "import java.util.*;\n", + "import onjava.Timer;\n", + "\n", + "public class BadMicroBenchmark {\n", + " static final int SIZE = 250_000_000;\n", + " \n", + " public static void main(String[] args) {\n", + " try { // For machines with insufficient memory\n", + " long[] la = new long[SIZE];\n", + " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> n)));\n", + " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> n)));\n", + " } catch (OutOfMemoryError e) {\n", + " System.out.println(\"Insufficient memory\");\n", + " System.exit(0);\n", + " }\n", + " }\n", + " \n", + "}\n", + "/* Output\n", + "setAll: 272\n", + "parallelSetAll: 301" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**main()** 方法的主体包含在 **try** 语句块中,因为一台机器用光内存后会导致构建停止。\n", + "\n", + "对于一个长度为 250,000,000 的 **long** 型(仅仅差一点就会让大部分机器内存溢出)数组,我们比较了 **Arrays.setAll()** 和 **Arrays.parallelSetAll()** 的性能。这个并行的版本会尝试使用多个处理器加快完成任务(尽管我在这一节谈到了一些并行的概念,但是在 [并发编程](./24-Concurrent-Programming.md) 章节我们才会详细讨论这些 )。然而非并行的版本似乎运行得更快,尽管在不同的机器上结果可能不同。\n", + "\n", + "**BadMicroBenchmark.java** 中的每一步操作都是独立的,但是如果你的操作依赖于同一资源,那么并行版本运行的速度会骤降,因为不同的进程会竞争相同的那个资源。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/BadMicroBenchmark2.java\n", + "// Relying on a common resource\n", + "\n", + "import java.util.*;\n", + "import onjava.Timer;\n", + "\n", + "public class BadMicroBenchmark2 {\n", + " static final int SIZE = 5_000_000;\n", + " \n", + " public static void main(String[] args) {\n", + " long[] la = new long[SIZE];\n", + " Random r = new Random();\n", + " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> r.nextLong())));\n", + " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> r.nextLong())));\n", + " SplittableRandom sr = new SplittableRandom();\n", + " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> sr.nextLong())));\n", + " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> sr.nextLong())));\n", + " }\n", + "}\n", + "/* Output\n", + "parallelSetAll: 1147\n", + "setAll: 174\n", + "parallelSetAll: 86\n", + "setAll: 39" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SplittableRandom** 是为并行算法设计的,它当然看起来比普通的 **Random** 在 **parallelSetAll()** 中运行得更快。 但是看上去还是比非并发的 **setAll()** 运行时间更长,有点难以置信(也许是真的,但我们不能通过一个坏的微基准测试得到这个结论)。\n", + "\n", + "这只考虑了微基准测试的问题。Java 虚拟机 Hotspot 也非常影响性能。如果你在测试前没有通过运行代码给 JVM 预热,那么你就会得到“冷”的结果,不能反映出代码在 JVM 预热之后的运行速度(假如你运行的应用没有在预热的 JVM 上运行,你就可能得不到所预期的性能,甚至可能减缓速度)。\n", + "\n", + "优化器有时可以检测出你创建了没有使用的东西,或者是部分代码的运行结果对程序没有影响。如果它优化掉你的测试,那么你可能得到不好的结果。\n", + "\n", + "一个良好的微基准测试系统能自动地弥补像这样的问题(和很多其他的问题)从而产生合理的结果,但是创建这么一套系统是非常棘手,需要深入的知识。\n", + "\n", + "### JMH 的引入\n", + "\n", + "截止目前为止,唯一能产生像样结果的 Java 微基准测试系统就是 Java Microbenchmarking Harness,简称 JMH。本书的 **build.gradle** 自动引入了 JMH 的设置,所以你可以轻松地使用它。\n", + "\n", + "你可以在命令行编写 JMH 代码并运行它,但是推荐的方式是让 JMH 系统为你运行测试;**build.gradle** 文件已经配置成只需要一条命令就能运行 JMH 测试。\n", + "\n", + "JMH 尝试使基准测试变得尽可能简单。例如,我们将使用 JMH 重新编写 **BadMicroBenchmark.java**。这里只有 **@State** 和 **@Benchmark** 这两个注解是必要的。其余的注解要么是为了产生更多易懂的输出,要么是加快基准测试的运行速度(JMH 基准测试通常需要运行很长时间):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/jmh/JMH1.java\n", + "package validating.jmh;\n", + "import java.util.*;\n", + "import org.openjdk.jmh.annotations.*;\n", + "import java.util.concurrent.TimeUnit;\n", + "\n", + "@State(Scope.Thread)\n", + "@BenchmarkMode(Mode.AverageTime)\n", + "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", + "// Increase these three for more accuracy:\n", + "@Warmup(iterations = 5)\n", + "@Measurement(iterations = 5)\n", + "@Fork(1)\n", + "public class JMH1 {\n", + " private long[] la;\n", + " \n", + " @Setup\n", + " public void setup() {\n", + " la = new long[250_000_000];\n", + " }\n", + " \n", + " @Benchmark\n", + " public void setAll() {\n", + " Arrays.setAll(la, n -> n);\n", + " }\n", + " \n", + " public void parallelSetAll() {\n", + " Arrays.parallelSetAll(la, n -> n);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "“forks” 的默认值是 10,意味着每个测试都运行 10 次。为了减少运行时间,这里使用了 **@Fork** 注解来减少这个次数到 1。我还使用了 **@Warmup** 和 **@Measurement** 注解将它们默认的运行次数从 20 减少到 5 次。尽管这降低了整体的准确率,但是结果几乎与使用默认值相同。可以尝试将 **@Warmup**、**@Measurement** 和 **@Fork** 都注释掉然后看使用它们的默认值,结果会有多大显著的差异;一般来说,你应该只能看到长期运行的测试使错误因素减少,而结果没有多大变化。\n", + "\n", + "需要使用显式的 gradle 命令才能运行基准测试(在示例代码的根目录处运行)。这能防止耗时的基准测试运行其他的 **gradlew** 命令:\n", + "\n", + "**gradlew validating:jmh**\n", + "\n", + "这会花费几分钟的时间,取决于你的机器(如果没有注解上的调整,可能需要几个小时)。控制台会显示 **results.txt** 文件的路径,这个文件统计了运行结果。注意,**results.txt** 包含这一章所有 **jmh** 测试的结果:**JMH1.java**,**JMH2.java** 和 **JMH3.java**。\n", + "\n", + "因为输出是绝对时间,所以在不同的机器和操作系统上结果各不相同。重要的因素不是绝对时间,我们真正观察的是一个算法和另一个算法的比较,尤其是哪一个运行得更快,快多少。如果你在自己的机器上运行测试,你将看到不同的结果却有着相同的模式。\n", + "\n", + "我在大量的机器上运行了这些测试,尽管不同的机器上得到的绝对值结果不同,但是相对值保持着合理的稳定性。我只列出了 **results.txt** 中适当的片段并加以编辑使输出更加易懂,而且内容大小适合页面。所有测试中的 **Mode** 都以 **avgt** 展示,代表 “平均时长”。**Cnt**(测试的数目)的值是 200,尽管这里的一个例子中配置的 **Cnt** 值是 5。**Units** 是 **us/op**,是 “Microseconds per operation” 的缩写,因此,这个值越小代表性能越高。\n", + "\n", + "我同样也展示了使用 warmups、measurements 和 forks 默认值的输出。我删除了示例中相应的注解,就是为了获取更加准确的测试结果(这将花费数小时)。结果中数字的模式应该仍然看起来相同,不论你如何运行测试。\n", + "\n", + "下面是 **JMH1.java** 的运行结果:\n", + "\n", + "**Benchmark Score**\n", + "\n", + "**JMH1.setAll 196280.2**\n", + "\n", + "**JMH1.parallelSetAll 195412.9**\n", + "\n", + "即使像 JMH 这么高级的基准测试工具,基准测试的过程也不容易,练习时需要倍加小心。这里测试产生了反直觉的结果:并行的版本 **parallelSetAll()** 花费了与非并行版本的 **setAll()** 相同的时间,两者似乎都运行了相当长的时间。\n", + "\n", + "当创建这个示例时,我假设如果我们要测试数组初始化的话,那么使用非常大的数组是有意义的。所以我选择了尽可能大的数组;如果你实验的话会发现一旦数组的大小超过 2亿5000万,你就开始会得到内存溢出的异常。然而,在这么大的数组上执行大量的操作从而震荡内存系统,产生无法预料的结果是有可能的。不管这个假设是否正确,看上去我们正在测试的并非是我们想测试的内容。\n", + "\n", + "考虑其他的因素:\n", + "\n", + "C:客户端执行操作的线程数量\n", + "\n", + "P:并行算法使用的并行数量\n", + "\n", + "N:数组的大小:**10^(2*k)**,通常来说,**k=1..7** 足够来练习不同的缓存占用。\n", + "\n", + "Q:setter 的操作成本\n", + "\n", + "这个 C/P/N/Q 模型在早期 JDK 8 的 Lambda 开发期间浮出水面,大多数并行的 Stream 操作(**parallelSetAll()** 也基本相似)都满足这些结论:**N*Q**(主要工作量)对于并发性能尤为重要。并行算法在工作量较少时可能实际运行得更慢。\n", + "\n", + "在一些情况下操作竞争如此激烈使得并行毫无帮助,而不管 **N*Q** 有多大。当 **C** 很大时,**P** 就变得不太相关(内部并行在大量的外部并行面前显得多余)。此外,在一些情况下,并行分解会让相同的 **C** 个客户端运行得比它们顺序运行代码更慢。\n", + "\n", + "基于这些信息,我们重新运行测试,并在这些测试中使用不同大小的数组(改变 **N**):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/jmh/JMH2.java\n", + "package validating.jmh;\n", + "import java.util.*;\n", + "import org.openjdk.jmh.annotations.*;\n", + "import java.util.concurrent.TimeUnit;\n", + "\n", + "@State(Scope.Thread)\n", + "@BenchmarkMode(Mode.AverageTime)\n", + "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", + "@Warmup(iterations = 5)\n", + "@Measurement(iterations = 5)\n", + "@Fork(1)\n", + "public class JMH2 {\n", + "\n", + " private long[] la;\n", + "\n", + " @Param({\n", + " \"1\",\n", + " \"10\",\n", + " \"100\",\n", + " \"1000\",\n", + " \"10000\",\n", + " \"100000\",\n", + " \"1000000\",\n", + " \"10000000\",\n", + " \"100000000\",\n", + " \"250000000\"\n", + " })\n", + " int size;\n", + "\n", + " @Setup\n", + " public void setup() {\n", + " la = new long[size];\n", + " }\n", + "\n", + " @Benchmark\n", + " public void setAll() {\n", + " Arrays.setAll(la, n -> n);\n", + " }\n", + "\n", + " @Benchmark\n", + " public void parallelSetAll() {\n", + " Arrays.parallelSetAll(la, n -> n);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@Param** 会自动地将其自身的值注入到变量中。其自身的值必须是字符串类型,并可以转化为适当的类型,在这个例子中是 **int** 类型。\n", + "\n", + "下面是已经编辑过的结果,包含精确计算出的加速数值:\n", + "\n", + "| JMH2 Benchmark | Size | Score % | Speedup |\n", + "| ------------------ | --------- | ---------- | ------- |\n", + "| **setAll** | 1 | 0.001 | |\n", + "| **parallelSetAll** | 1 | 0.036 | 0.028 |\n", + "| **setAll** | 10 | 0.005 | |\n", + "| **parallelSetAll** | 10 | 3.965 | 0.001 |\n", + "| **setAll** | 100 | 0.031 | |\n", + "| **parallelSetAll** | 100 | 3.145 | 0.010 |\n", + "| **setAll** | 1000 | 0.302 | |\n", + "| **parallelSetAll** | 1000 | 3.285 | 0.092 |\n", + "| **setAll** | 10000 | 3.152 | |\n", + "| **parallelSetAll** | 10000 | 9.669 | 0.326 |\n", + "| **setAll** | 100000 | 34.971 | |\n", + "| **parallelSetAll** | 100000 | 20.153 | 1.735 |\n", + "| **setAll** | 1000000 | 420.581 | |\n", + "| **parallelSetAll** | 1000000 | 165.388 | 2.543 |\n", + "| **setAll** | 10000000 | 8160.054 | |\n", + "| **parallelSetAll** | 10000000 | 7610.190 | 1.072 |\n", + "| **setAll** | 100000000 | 79128.752 | |\n", + "| **parallelSetAll** | 100000000 | 76734.671 | 1.031 |\n", + "| **setAll** | 250000000 | 199552.121 | |\n", + "| **parallelSetAll** | 250000000 | 191791.927 | 1.040 |\n", + "可以看到当数组大小达到 10 万左右时,**parallelSetAll()** 开始反超,而后趋于与非并行的运行速度相同。即使它运行速度上胜了,看起来也不足以证明由于并行的存在而使速度变快。\n", + "\n", + "**setAll()/parallelSetAll()** 中工作的计算量起很大影响吗?在前面的例子中,我们所做的只有对数组的赋值操作,这可能是最简单的任务。所以即使 **N** 值变大,**N*Q** 也仍然没有达到巨大,所以看起来像是我们没有为并行提供足够的机会(JMH 提供了一种模拟变量 Q 的途径;如果想了解更多的话,可搜索 **Blackhole.consumeCPU**)。\n", + "\n", + "我们通过使方法 **f()** 中的任务变得更加复杂,从而产生更多的并行机会:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// validating/jmh/JMH3.java\n", + "package validating.jmh;\n", + "import java.util.*;\n", + "import org.openjdk.jmh.annotations.*;\n", + "import java.util.concurrent.TimeUnit;\n", + "\n", + "@State(Scope.Thread)\n", + "@BenchmarkMode(Mode.AverageTime)\n", + "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", + "@Warmup(iterations = 5)\n", + "@Measurement(iterations = 5)\n", + "@Fork(1)\n", + "public class JMH3 {\n", + " private long[] la;\n", + "\n", + " @Param({\n", + " \"1\",\n", + " \"10\",\n", + " \"100\",\n", + " \"1000\",\n", + " \"10000\",\n", + " \"100000\",\n", + " \"1000000\",\n", + " \"10000000\",\n", + " \"100000000\",\n", + " \"250000000\"\n", + " })\n", + " int size;\n", + "\n", + " @Setup\n", + " public void setup() {\n", + " la = new long[size];\n", + " }\n", + "\n", + " public static long f(long x) {\n", + " long quadratic = 42 * x * x + 19 * x + 47;\n", + " return Long.divideUnsigned(quadratic, x + 1);\n", + " }\n", + "\n", + " @Benchmark\n", + " public void setAll() {\n", + " Arrays.setAll(la, n -> f(n));\n", + " }\n", + "\n", + " @Benchmark\n", + " public void parallelSetAll() {\n", + " Arrays.parallelSetAll(la, n -> f(n));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**f()** 方法提供了更加复杂且耗时的操作。现在除了简单的给数组赋值外,**setAll()** 和 **parallelSetAll()** 都有更多的工作去做,这肯定会影响结果。\n", + "\n", + "| JMH2 Benchmark | Size | Score % | Speedup |\n", + "| ------------------ | --------- | ----------- | ------- |\n", + "| **setAll** | 1 | 0.012 | |\n", + "| **parallelSetAll** | 1 | 0.047 | 0.255 |\n", + "| **setAll** | 10 | 0.107 | |\n", + "| **parallelSetAll** | 10 | 3.894 | 0.027 |\n", + "| **setAll** | 100 | 0.990 | |\n", + "| **parallelSetAll** | 100 | 3.708 | 0.267 |\n", + "| **setAll** | 1000 | 133.814 | |\n", + "| **parallelSetAll** | 1000 | 11.747 | 11.391 |\n", + "| **setAll** | 10000 | 97.954 | |\n", + "| **parallelSetAll** | 10000 | 37.259 | 2.629 |\n", + "| **setAll** | 100000 | 988.475 | |\n", + "| **parallelSetAll** | 100000 | 276.264 | 3.578 |\n", + "| **setAll** | 1000000 | 9203.103 | |\n", + "| **parallelSetAll** | 1000000 | 2826.974 | 3.255 |\n", + "| **setAll** | 10000000 | 92144.951 | |\n", + "| **parallelSetAll** | 10000000 | 28126.202 | 3.276 |\n", + "| **setAll** | 100000000 | 921701.863 | |\n", + "| **parallelSetAll** | 100000000 | 266750.543 | 3.455 |\n", + "| **setAll** | 250000000 | 2299127.273 | |\n", + "| **parallelSetAll** | 250000000 | 538173.425 | 4.272 |\n", + "\n", + "可以看到当数组的大小达到 1000 左右时,**parallelSetAll()** 的运行速度反超了 **setAll()**。看来 **parallelSetAll()** 严重依赖数组中计算的复杂度。这正是基准测试的价值所在,因为我们已经得到了关于 **setAll()** 和 **parallelSetAll()** 间微妙的信息,知道在何时使用它们。\n", + "\n", + "这显然不是从阅读 Javadocs 就能得到的。\n", + "\n", + "大多数时候,JMH 的简单应用会产生好的结果(正如你将在本书后面例子中所见),但是我们从这里知道,你不能一直假定 JMH 会产生好的结果。 JMH 网站上的范例可以帮助你开始。\n", + "\n", + "\n", + "\n", + "## 剖析和优化\n", + "\n", + "有时你必须检测程序运行时间花在哪儿,从而看是否可以优化那一块的性能。剖析器可以找到这些导致程序慢的地方,因而你可以找到最轻松,最明显的方式加快程序运行速度。\n", + "\n", + "剖析器收集的信息能显示程序哪一部分消耗内存,哪个方法最耗时。一些剖析器甚至能关闭垃圾回收,从而帮助限定内存分配的模式。\n", + "\n", + "剖析器还可以帮助检测程序中的线程死锁。注意剖析和基准测试的区别。剖析关注的是已经运行在真实数据上的整个程序,而基准测试关注的是程序中隔离的片段,通常是去优化算法。\n", + "\n", + "安装 Java 开发工具包(JDK)时会顺带安装一个虚拟的剖析器,叫做 **VisualVM**。它会被自动安装在与 **javac** 相同的目录下,你的执行路径应该已经包含该目录。启动 VisualVM 的控制台命令是:\n", + "\n", + "**> jvisualvm**\n", + "\n", + "运行该命令后会弹出一个窗口,其中包括一些指向帮助信息的链接。\n", + "\n", + "### 优化准则\n", + "\n", + "- 避免为了性能牺牲代码的可读性。\n", + "- 不要独立地看待性能。衡量与带来的收益相比所需投入的工作量。\n", + "- 程序的大小很重要。性能优化通常只对运行了长时间的大型项目有价值。性能通常不是小项目的关注点。\n", + "- 运行起来程序比一心钻研它的性能具有更高的优先级。一旦你已经有了可工作的程序,如有必要的话,你可以使用剖析器提高它的效率。只有当性能是关键因素时,才需要在设计/开发阶段考虑性能。\n", + "- 不要猜测瓶颈发生在哪。运行剖析器,让剖析器告诉你。\n", + "- 无论何时有可能的话,显式地设置实例为 null 表明你不再用它。这对垃圾收集器来说是个有用的暗示。\n", + "- **static final** 修饰的变量会被 JVM 优化从而提高程序的运行速度。因而程序中的常量应该声明 **static final**。\n", + "\n", + "\n", + "\n", + "## 风格检测\n", + "\n", + "当你在一个团队中工作时(包括尤其是开源项目),让每个人遵循相同的代码风格是非常有帮助的。这样阅读项目的代码时,不会因为风格的不同产生思维上的中断。然而,如果你习惯了某种不同的代码风格,那么记住项目中所有的风格准则对你来说可能是困难的。幸运的是,存在可以指出你代码中不符合风格准则的工具。\n", + "\n", + "一个流行的风格检测器是 **Checkstyle**。查看本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **gradle.build** 和 **checkstyle.xml** 文件中配置代码风格的方式。checkstyle.xml 是一个常用检测的集合,其中一些检测被注释掉了以允许使用本书中的代码风格。\n", + "\n", + "运行所有风格检测的命令是:\n", + "\n", + "**gradlew checkstyleMain**\n", + "\n", + "一些文件仍然产生了风格检测警告,通常是因为这些例子展示了你在生产代码中不会使用的样例。\n", + "\n", + "你还可以针对一个具体的章节运行代码检测。例如,下面命令会运行 [Annotations](./23-Annotations.md) 章节的风格检测:\n", + "\n", + "**gradlew annotations:checkstyleMain**\n", + "\n", + "\n", + "\n", + "## 静态错误分析\n", + "\n", + "尽管 Java 的静态类型检测可以发现基本的语法错误,其他的分析工具可以发现躲避 **javac** 检测的更加复杂的bug。一个这样的工具叫做 **Findbugs**。本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **build.gradle** 文件包含了 Findbugs 的配置,所以你可以输入如下命令:\n", + "\n", + "**gradlew findbugsMain**\n", + "\n", + "这会为每一章生成一个名为 **main.html** 的报告,报告中会说明代码中潜在的问题。Gradle 命令的输出会告诉你每个报告在何处。\n", + "\n", + "当你查看报告时,你将会看到很多 false positive 的情况,即代码没问题却报告了问题。我在一些文件中展示了不要做一些事的代码确实是正确的。\n", + "\n", + "当我最初看到本书的 Findbugs 报告时,我发现了一些不是技术错误的地方,但能使我改善代码。如果你正在寻找 bug,那么在调试之前运行 Findbugs 是值得的,因为这将可能节省你数小时的时间找到问题。\n", + "\n", + "\n", + "\n", + "## 代码重审\n", + "\n", + "单元测试能找到明显重要的 bug 类型,风格检测和 Findbugs 能自动执行代码重审,从而发现额外的问题。最终你走到了必须人为参与进来的地步。代码重审是一个或一群人的一段代码被另一个或一群人阅读和评估的众多方式之一。这最初看起来会使人不安,而且需要情感信任,但它的目的肯定不是羞辱任何人。它的目标是找到程序中的错误,代码重审是最成功的能做到这点的途径之一。可惜的是,它们也经常被认为是“过于昂贵的”(有时这会成为程序员避免代码被重审时感到尴尬的借口)。\n", + "\n", + "代码重审可以作为结对编程的一部分,作为代码签入过程的一部分(另一个程序员自动安排上审查新代码的任务)或使用群组预排的方式,即每个人阅读代码并讨论之。后一种方式对于分享知识和营造代码文化是极其有益的。\n", + "\n", + "\n", + "\n", + "## 结对编程\n", + "\n", + "结对编程是指两个程序员一起编程的实践活动。通常来说,一个人“驱动”(敲击键盘,输入代码),另一人(观察者或指引者)重审和分析代码,同时也要思考策略。这产生了一种实时的代码重审。通常程序员会定期地互换角色。\n", + "\n", + "结对编程有很多好处,但最显著的是分享知识和防止阻塞。最佳传递信息的方式之一就是一起解决问题,我已经在很多次研讨会使用了结对编程,都取得了很好的效果(同时,研讨会上的众人可以通过这种方式互相了解对方)。而且两个人一起工作时,可以更容易地推进开发的进展,而只有一个程序员的话,可能被轻易地卡住。结对编程的程序员通常可以从工作中感到更高的满足感。有时很难向管理人员们推行结对编程,因为他们可能觉得两个程序员解决同一个问题的效率比他们分开解决不同问题的效率低。尽管短期内是这样,但是结对编程能带来更高的代码质量;除了结对编程的其他益处,如果你眼光长远的话,这会产生更高的生产力。\n", + "\n", + "维基百科上这篇 [结对编程的文章](https://en.wikipedia.org/wiki/Pair_programming) 可以作为你深入了解结对编程的开始。\n", + "\n", + "\n", + "\n", + "## 重构\n", + "\n", + "技术负债是指迭代发展的软件中为了应急而生的丑陋解决方案从而导致设计难以理解,代码难以阅读的部分。特别是当你必须修改和增加新特性的时候,这会造成麻烦。\n", + "\n", + "重构可以矫正技术负债。重构的关键是它能改善代码设计,结构和可读性(因而减少代码负债),但是它不能改变代码的行为。\n", + "\n", + "很难向管理人员推行重构:“我们将投入很多工作不是增加新的特性,当我们完成时,外界无感知变化。但是相信我们,事情会变得更加美好”。不幸的是,管理人员意识到重构的价值时都为时已晚了:当他们提出增加新的特性时,你不得不告诉他们做不到,因为代码基底已经埋藏了太多的问题,试图增加新特性可能会使软件崩溃,即使你能想出怎么做。\n", + "\n", + "### 重构基石\n", + "\n", + "在开始重构代码之前,你需要有以下三个系统的支撑:\n", + "\n", + "1. 测试(通常,JUnit 测试作为最小的根基),因此你能确保重构不会改变代码的行为。\n", + "2. 自动构建,因而你能轻松地构建代码,运行所有的测试。通过这种方式做些小修改并确保修改不会破坏任何事物是毫不费力的。本书使用的是 Gradle 构建系统,你可以在 [代码示例](https://github.com/BruceEckel/OnJava8-Examples) 的 **build.gradle** 文件中查看示例。\n", + "3. 版本控制,以便你能回退到可工作的代码版本,能够一直记录重构的每一步。\n", + "\n", + "本书的代码托管在 [Github](https://github.com/BruceEckel/OnJava8-Examples) 上,使用的是 **git** 版本控制系统。\n", + "\n", + "没有这三个系统的支持,重构几乎是不可能的。确实,没有这些系统,起初维护和增加代码是一个巨大的挑战。令人意外的是,有很多成功的公司竟然在没有这三个系统的情况下在相当长的时间里勉强过得去。然而,对于这样的公司来说,在他们遇到严重的问题之前,这只是个时间问题。\n", + "\n", + "维基百科上的 [重构文章](https://en.wikipedia.org/wiki/Code_refactoring) 提供了更多的细节。\n", + "\n", + "\n", + "\n", + "## 持续集成\n", + "\n", + "在软件开发的早期,人们只能一次处理一步,所以他们坚信他们总是在经历快乐之旅,每个开发阶段无缝进入下一个。这种错觉经常被称为软件开发中的“瀑布流模型”。很多人告诉我瀑布流是他们的选择方法,好像这是一个选择工具,而不仅是一厢情愿。\n", + "\n", + "在这片童话的土地上,每一步都按照指定的预计时间准时完美结束,然后下一步开始。当最后一步结束时,所有的部件都可以无缝地滑在一起,瞧,一个装载产品诞生了!\n", + "\n", + "当然,现实中没有事能按计划或预计时间运作。相信它应该,然后当它不能时更加相信,只会使整件事变得更糟。否认证据不会产生好的结果。\n", + "\n", + "除此之外,产品本身经常也不是对客户有价值的事物。有时一大堆的特性完全是浪费时间,因为创造出这些特性需求的人不是客户而是其他人。\n", + "\n", + "因为受流水工作线的思路影响,所以每个开发阶段都有自己的团队。上游团队的延期传递到下游团队,当到了需要进行测试和集成的时候,这些团队被指望赶上预期时间,当他们必然做不到时,就认为他们是“差劲的团队成员”。不可能的时间安排和负相关的结合产生了自实现的预期:只有最绝望的开发者才会乐意做这些工作。\n", + "\n", + "另外,商学院培养出的管理人员仍然被训练成只在已有的流程上做一些改动——这些流程都是基于工业时代制造业的想法上。注重培养创造力而不是墨守成规的商学院仍然很稀有。终于一些编程领域的人们再也忍受不了这种情况并开始进行实验。最初一些实验叫做“极限编程”,因为它们与工业时代的思想完全不同。随着实验展示的结果,这些思想开始看起来像是常识。这些实验逐渐形成了如今显而易见的观点——尽管非常小——即把生产可运作的产品交到客户手中,询问他们 (A) 是否想要它 (B) 是否喜欢它工作的方式 (C) 还希望有什么其他有用的功能特性。然后这些信息反馈给开发,从而继续产出一个新版本。版本不断迭代,项目最终演变成为客户带来真正价值的事物。\n", + "\n", + "这完全颠倒了瀑布流开发的方式。你停止假设你要处理产品测试和把部署\"作为最后一步\"这类的事情。相反,每件事从开始到结束必须都在进行——即使一开始产品几乎没有任何特性。这么做对于在开发周期的早期发现更多问题有巨大的益处。此外,不是做大量宏大超前的计划和花费时间金钱在许多无用的特性上,而是一直都能从顾客那得到反馈。当客户不再需要其他特性时,你就完成了。这节省了大量的时间和金钱,并提高了顾客的满意度。\n", + "\n", + "有许多不同的想法导向这种方式,但是目前首要的术语叫持续集成(CI)。CI 与导向 CI 的想法之间的不同之处在于 CI 是一种独特的机械式的过程,过程中涵盖了这些想法;它是一种定义好的做事方式。事实上,它定义得如此明确以至于整个过程是自动化的。\n", + "\n", + "当前 CI 技术的高峰是持续集成服务器。这是一台独立的机器或虚拟机,通常是由第三方公司托管的完全独立的服务。这些公司通常免费提供基本服务,如果你需要额外的特性如更多的处理器或内存或者专门的工具或系统,你需要付费。CI 服务器起初是完全空白状态,即只是可用的操作系统的最小配置。这很重要因为你可能之前在你的开发机器上安装过一些程序,却没有在你的构建和部署系统中包含它。正如重构一样,持续集成需要分布式版本管理,自动构建和自动测试系统作为基础。通常来说,CI 服务器会绑定到你的版本控制仓库上。当 CI 服务器发现仓库中有改变时,就会拉取最新版本的代码,并按照 CI 脚本中的过程处理。这包括安装所有必要的工具和类库(记住,CI 服务器起初只有一个干净、基本的操作系统),所以如果过程中出现任何问题,你都可以发现它们。接着它会执行脚本中定义的构建和测试操作;通常脚本中使用的命令与人们在安装和测试中使用的命令完全相同。如果执行成功或失败,CI 服务器会有许多种方式汇报给你,包括在你的代码仓库上显示一个简单的标记。\n", + "\n", + "使用持续集成,每次你合进仓库时,这些改变都会被从头到尾验证。通过这种方式,一旦出现问题你能立即发现。甚至当你准备交付一个产品的新版本时,都不会有延迟或其他必要的额外步骤(在任何时刻都可以交付叫做持续交付)。\n", + "\n", + "本书的示例代码都是在 Travis-CI(基于 Linux 的系统) 和 AppVeyor(Windows) 上自动测试的。你可以在 [Gihub仓库](https://github.com/BruceEckel/OnJava8-Examples) 上的 Readme 看到通过/失败的标记。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "\"它在我的机器上正常工作了。\" \"我们不会运载你的机器!\"\n", + "\n", + "代码校验不是单一的过程或技术。每种方法只能发现特定类型的 bug,作为程序员的你在开发过程中会明白每个额外的技术都能增加代码的可靠性和鲁棒性。校验不仅能在开发过程中,还能在为应用添加新功能的整个项目期间帮你发现更多的错误。现代化开发意味着比仅仅编写代码更多的内容,每种你在开发过程中融入的测试技术—— 包括而且尤其是你创建的能适应特定应用的自定义工具——都会带来更好、更快和更加愉悦的开发过程,同时也能为客户提供更高的价值和满意度体验。\n", + "\n", + "" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/17-Files.ipynb b/jupyter/17-Files.ipynb new file mode 100644 index 00000000..37f26133 --- /dev/null +++ b/jupyter/17-Files.ipynb @@ -0,0 +1,1123 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第十七章 文件\n", + ">在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。\n", + "\n", + "这种\"困难方式\"的全部细节都在 [Appendix: I/O Streams](./Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于\n", + "很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。\n", + "\n", + "好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 **java.nio.file** 包下面,过去人们通常把 **nio** 中的 **n** 理解为 **new** 即新的 **io**,现在更应该当成是 **non-blocking** 非阻塞 **io**(**io**就是*input/output输入/输出*)。**java.nio.file** 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件:\n", + "\n", + "1. 文件或者目录的路径;\n", + "2. 文件本身。\n", + "\n", + "\n", + "## 文件和目录路径\n", + "\n", + "一个 **Path** 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。**java.nio.file.Paths** 类包含一个重载方法 **static get()**,该方法接受一系列 **String** 字符串或一个*统一资源标识符*(URI)作为参数,并且进行转换返回一个 **Path** 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/PathInfo.java\n", + "import java.nio.file.*;\n", + "import java.net.URI;\n", + "import java.io.File;\n", + "import java.io.IOException;\n", + "\n", + "public class PathInfo {\n", + " static void show(String id, Object p) {\n", + " System.out.println(id + \": \" + p);\n", + " }\n", + "\n", + " static void info(Path p) {\n", + " show(\"toString\", p);\n", + " show(\"Exists\", Files.exists(p));\n", + " show(\"RegularFile\", Files.isRegularFile(p));\n", + " show(\"Directory\", Files.isDirectory(p));\n", + " show(\"Absolute\", p.isAbsolute());\n", + " show(\"FileName\", p.getFileName());\n", + " show(\"Parent\", p.getParent());\n", + " show(\"Root\", p.getRoot());\n", + " System.out.println(\"******************\");\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(System.getProperty(\"os.name\"));\n", + " info(Paths.get(\"C:\", \"path\", \"to\", \"nowhere\", \"NoFile.txt\"));\n", + " Path p = Paths.get(\"PathInfo.java\");\n", + " info(p);\n", + " Path ap = p.toAbsolutePath();\n", + " info(ap);\n", + " info(ap.getParent());\n", + " try {\n", + " info(p.toRealPath());\n", + " } catch(IOException e) {\n", + " System.out.println(e);\n", + " }\n", + " URI u = p.toUri();\n", + " System.out.println(\"URI: \" + u);\n", + " Path puri = Paths.get(u);\n", + " System.out.println(Files.exists(puri));\n", + " File f = ap.toFile(); // Don't be fooled\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + "Windows 10\n", + "toString: C:\\path\\to\\nowhere\\NoFile.txt\n", + "Exists: false\n", + "RegularFile: false\n", + "Directory: false\n", + "Absolute: true\n", + "FileName: NoFile.txt\n", + "Parent: C:\\path\\to\\nowhere\n", + "Root: C:\\\n", + "******************\n", + "toString: PathInfo.java\n", + "Exists: true\n", + "RegularFile: true\n", + "Directory: false\n", + "Absolute: false\n", + "FileName: PathInfo.java\n", + "Parent: null\n", + "Root: null\n", + "******************\n", + "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\\PathInfo.java\n", + "Exists: true\n", + "RegularFile: true\n", + "Directory: false\n", + "Absolute: true\n", + "FileName: PathInfo.java\n", + "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "Root: C:\\\n", + "******************\n", + "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "Exists: true\n", + "RegularFile: false\n", + "Directory: true\n", + "Absolute: true\n", + "FileName: files\n", + "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\n", + "Root: C:\\\n", + "******************\n", + "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\\PathInfo.java\n", + "Exists: true\n", + "RegularFile: true\n", + "Directory: false\n", + "Absolute: true\n", + "FileName: PathInfo.java\n", + "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "Root: C:\\\n", + "******************\n", + "URI: file:///C:/Users/Bruce/Documents/GitHub/onjava/\n", + "ExtractedExamples/files/PathInfo.java\n", + "true\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10 上的程序输出。\n", + "\n", + "当 **toString()** 方法生成完整形式的路径,你可以看到 **getFileName()** 方法总是返回当前文件名。\n", + "通过使用 **Files** 工具类(我们接下来将会更多地使用它),可以测试一个文件是否存在,测试是否是一个\"普通\"文件还是一个目录等等。\"Nofile.txt\"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。\"PathInfo.java\"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为\"存在\"。一旦我们将其转换为绝对路径,我们将会得到一个从\"C:\"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的 **Path**,并且还会删除任何冗余元素。\n", + "\n", + "这里你会看到 **URI** 看起来只能用于描述文件,实际上 **URI** 可以用于描述更多的东西;通过 [维基百科](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) 可以了解更多细节。现在我们成功地将 **URI** 转为一个 **Path** 对象。\n", + "\n", + "最后,你会在 **Path** 中看到一些有点欺骗的东西,这就是调用 **toFile()** 方法会生成一个 **File** 对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为 **File** ),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为\"路径\",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于 **java.nio.file** 的存在我们可以安全地忽略它的存在。\n", + "\n", + "### 选取路径部分片段\n", + "**Path** 对象可以非常容易地生成路径的某一部分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/PartsOfPaths.java\n", + "import java.nio.file.*;\n", + "\n", + "public class PartsOfPaths {\n", + " public static void main(String[] args) {\n", + " System.out.println(System.getProperty(\"os.name\"));\n", + " Path p = Paths.get(\"PartsOfPaths.java\").toAbsolutePath();\n", + " for(int i = 0; i < p.getNameCount(); i++)\n", + " System.out.println(p.getName(i));\n", + " System.out.println(\"ends with '.java': \" +\n", + " p.endsWith(\".java\"));\n", + " for(Path pp : p) {\n", + " System.out.print(pp + \": \");\n", + " System.out.print(p.startsWith(pp) + \" : \");\n", + " System.out.println(p.endsWith(pp));\n", + " }\n", + " System.out.println(\"Starts with \" + p.getRoot() + \" \" + p.startsWith(p.getRoot()));\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + "Windows 10\n", + "Users\n", + "Bruce\n", + "Documents\n", + "GitHub\n", + "on-java\n", + "ExtractedExamples\n", + "files\n", + "PartsOfPaths.java\n", + "ends with '.java': false\n", + "Users: false : false\n", + "Bruce: false : false\n", + "Documents: false : false\n", + "GitHub: false : false\n", + "on-java: false : false\n", + "ExtractedExamples: false : false\n", + "files: false : false\n", + "PartsOfPaths.java: false : true\n", + "Starts with C:\\ true\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以通过 **getName()** 来索引 **Path** 的各个部分,直到达到上限 **getNameCount()**。**Path** 也实现了 **Iterable** 接口,因此我们也可以通过增强的 for-each 进行遍历。请注意,即使路径以 **.java** 结尾,使用 **endsWith()** 方法也会返回 **false**。这是因为使用 **endsWith()** 比较的是整个路径部分,而不会包含文件路径的后缀。通过使用 **startsWith()** 和 **endsWith()** 也可以完成路径的遍历。但是我们可以看到,遍历 **Path** 对象并不包含根路径,只有使用 **startsWith()** 检测根路径时才会返回 **true**。\n", + "\n", + "### 路径分析\n", + "**Files** 工具类包含一系列完整的方法用于获得 **Path** 相关的信息。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/PathAnalysis.java\n", + "import java.nio.file.*;\n", + "import java.io.IOException;\n", + "\n", + "public class PathAnalysis {\n", + " static void say(String id, Object result) {\n", + " System.out.print(id + \": \");\n", + " System.out.println(result);\n", + " }\n", + " \n", + " public static void main(String[] args) throws IOException {\n", + " System.out.println(System.getProperty(\"os.name\"));\n", + " Path p = Paths.get(\"PathAnalysis.java\").toAbsolutePath();\n", + " say(\"Exists\", Files.exists(p));\n", + " say(\"Directory\", Files.isDirectory(p));\n", + " say(\"Executable\", Files.isExecutable(p));\n", + " say(\"Readable\", Files.isReadable(p));\n", + " say(\"RegularFile\", Files.isRegularFile(p));\n", + " say(\"Writable\", Files.isWritable(p));\n", + " say(\"notExists\", Files.notExists(p));\n", + " say(\"Hidden\", Files.isHidden(p));\n", + " say(\"size\", Files.size(p));\n", + " say(\"FileStore\", Files.getFileStore(p));\n", + " say(\"LastModified: \", Files.getLastModifiedTime(p));\n", + " say(\"Owner\", Files.getOwner(p));\n", + " say(\"ContentType\", Files.probeContentType(p));\n", + " say(\"SymbolicLink\", Files.isSymbolicLink(p));\n", + " if(Files.isSymbolicLink(p))\n", + " say(\"SymbolicLink\", Files.readSymbolicLink(p));\n", + " if(FileSystems.getDefault().supportedFileAttributeViews().contains(\"posix\"))\n", + " say(\"PosixFilePermissions\",\n", + " Files.getPosixFilePermissions(p));\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + "Windows 10\n", + "Exists: true\n", + "Directory: false\n", + "Executable: true\n", + "Readable: true\n", + "RegularFile: true\n", + "Writable: true\n", + "notExists: false\n", + "Hidden: false\n", + "size: 1631\n", + "FileStore: SSD (C:)\n", + "LastModified: : 2017-05-09T12:07:00.428366Z\n", + "Owner: MINDVIEWTOSHIBA\\Bruce (User)\n", + "ContentType: null\n", + "SymbolicLink: false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在调用最后一个测试方法 **getPosixFilePermissions()** 之前我们需要确认一下当前文件系统是否支持 **Posix** 接口,否则会抛出运行时异常。\n", + "\n", + "### **Paths**的增减修改\n", + "我们必须能通过对 **Path** 对象增加或者删除一部分来构造一个新的 **Path** 对象。我们使用 **relativize()** 移除 **Path** 的根路径,使用 **resolve()** 添加 **Path** 的尾路径(不一定是“可发现”的名称)。\n", + "\n", + "对于下面代码中的示例,我使用 **relativize()** 方法从所有的输出中移除根路径,部分原因是为了示范,部分原因是为了简化输出结果,这说明你可以使用该方法将绝对路径转为相对路径。\n", + "这个版本的代码中包含 **id**,以便于跟踪输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/AddAndSubtractPaths.java\n", + "import java.nio.file.*;\n", + "import java.io.IOException;\n", + "\n", + "public class AddAndSubtractPaths {\n", + " static Path base = Paths.get(\"..\", \"..\", \"..\").toAbsolutePath().normalize();\n", + " \n", + " static void show(int id, Path result) {\n", + " if(result.isAbsolute())\n", + " System.out.println(\"(\" + id + \")r \" + base.relativize(result));\n", + " else\n", + " System.out.println(\"(\" + id + \") \" + result);\n", + " try {\n", + " System.out.println(\"RealPath: \" + result.toRealPath());\n", + " } catch(IOException e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " System.out.println(System.getProperty(\"os.name\"));\n", + " System.out.println(base);\n", + " Path p = Paths.get(\"AddAndSubtractPaths.java\").toAbsolutePath();\n", + " show(1, p);\n", + " Path convoluted = p.getParent().getParent()\n", + " .resolve(\"strings\").resolve(\"..\")\n", + " .resolve(p.getParent().getFileName());\n", + " show(2, convoluted);\n", + " show(3, convoluted.normalize());\n", + " Path p2 = Paths.get(\"..\", \"..\");\n", + " show(4, p2);\n", + " show(5, p2.normalize());\n", + " show(6, p2.toAbsolutePath().normalize());\n", + " Path p3 = Paths.get(\".\").toAbsolutePath();\n", + " Path p4 = p3.resolve(p2);\n", + " show(7, p4);\n", + " show(8, p4.normalize());\n", + " Path p5 = Paths.get(\"\").toAbsolutePath();\n", + " show(9, p5);\n", + " show(10, p5.resolveSibling(\"strings\"));\n", + " show(11, Paths.get(\"nonexistent\"));\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + "Windows 10\n", + "C:\\Users\\Bruce\\Documents\\GitHub\n", + "(1)r onjava\\\n", + "ExtractedExamples\\files\\AddAndSubtractPaths.java\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\\AddAndSubtractPaths.java\n", + "(2)r on-java\\ExtractedExamples\\strings\\..\\files\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "(3)r on-java\\ExtractedExamples\\files\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "(4) ..\\..\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", + "(5) ..\\..\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", + "(6)r on-java\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", + "(7)r on-java\\ExtractedExamples\\files\\.\\..\\..\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", + "(8)r on-java\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", + "(9)r on-java\\ExtractedExamples\\files\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\n", + "(10)r on-java\\ExtractedExamples\\strings\n", + "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\strings\n", + "(11) nonexistent\n", + "java.nio.file.NoSuchFileException:\n", + "C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", + "ExtractedExamples\\files\\nonexistent\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我还为 **toRealPath()** 添加了更多的测试,这是为了扩展和规则化,防止路径不存在时抛出运行时异常。\n", + "\n", + "\n", + "\n", + "## 目录\n", + "**Files** 工具类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,因此我们将实现并将其添加到 **onjava** 库中。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/RmDir.java\n", + "package onjava;\n", + "\n", + "import java.nio.file.*;\n", + "import java.nio.file.attribute.BasicFileAttributes;\n", + "import java.io.IOException;\n", + "\n", + "public class RmDir {\n", + " public static void rmdir(Path dir) throws IOException {\n", + " Files.walkFileTree(dir, new SimpleFileVisitor() {\n", + " @Override\n", + " public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n", + " Files.delete(file);\n", + " return FileVisitResult.CONTINUE;\n", + " }\n", + " \n", + " @Override\n", + " public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n", + " Files.delete(dir);\n", + " return FileVisitResult.CONTINUE;\n", + " }\n", + " });\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "删除目录树的方法实现依赖于 **Files.walkFileTree()**,\"walking\" 目录树意味着遍历每个子目录和文件。*Visitor* 设计模式提供了一种标准机制来访问集合中的每个对象,然后你需要提供在每个对象上执行的操作。\n", + "此操作的定义取决于实现的 **FileVisitor** 的四个抽象方法,包括:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1. **preVisitDirectory()**:在访问目录中条目之前在目录上运行。 \n", + "2. **visitFile()**:运行目录中的每一个文件。 \n", + "3. **visitFileFailed()**:调用无法访问的文件。 \n", + "4. **postVisitDirectory()**:在访问目录中条目之后在目录上运行,包括所有的子目录。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了简化,**java.nio.file.SimpleFileVisitor** 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:**visitFile()** 和 **postVisitDirectory()** 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。\n", + "作为探索目录操作的一部分,现在我们可以有条件地删除已存在的目录。在以下例子中,**makeVariant()** 接受基本目录测试,并通过旋转部件列表生成不同的子目录路径。这些旋转与路径分隔符 **sep** 使用 **String.join()** 贴在一起,然后返回一个 **Path** 对象。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/Directories.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "import onjava.RmDir;\n", + "\n", + "public class Directories {\n", + " static Path test = Paths.get(\"test\");\n", + " static String sep = FileSystems.getDefault().getSeparator();\n", + " static List parts = Arrays.asList(\"foo\", \"bar\", \"baz\", \"bag\");\n", + " \n", + " static Path makeVariant() {\n", + " Collections.rotate(parts, 1);\n", + " return Paths.get(\"test\", String.join(sep, parts));\n", + " }\n", + " \n", + " static void refreshTestDir() throws Exception {\n", + " if(Files.exists(test))\n", + " RmDir.rmdir(test);\n", + " if(!Files.exists(test))\n", + " Files.createDirectory(test);\n", + " }\n", + " \n", + " public static void main(String[] args) throws Exception {\n", + " refreshTestDir();\n", + " Files.createFile(test.resolve(\"Hello.txt\"));\n", + " Path variant = makeVariant();\n", + " // Throws exception (too many levels):\n", + " try {\n", + " Files.createDirectory(variant);\n", + " } catch(Exception e) {\n", + " System.out.println(\"Nope, that doesn't work.\");\n", + " }\n", + " populateTestDir();\n", + " Path tempdir = Files.createTempDirectory(test, \"DIR_\");\n", + " Files.createTempFile(tempdir, \"pre\", \".non\");\n", + " Files.newDirectoryStream(test).forEach(System.out::println);\n", + " System.out.println(\"*********\");\n", + " Files.walk(test).forEach(System.out::println);\n", + " }\n", + " \n", + " static void populateTestDir() throws Exception {\n", + " for(int i = 0; i < parts.size(); i++) {\n", + " Path variant = makeVariant();\n", + " if(!Files.exists(variant)) {\n", + " Files.createDirectories(variant);\n", + " Files.copy(Paths.get(\"Directories.java\"),\n", + " variant.resolve(\"File.txt\"));\n", + " Files.createTempFile(variant, null, null);\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + "Nope, that doesn't work.\n", + "test\\bag\n", + "test\\bar\n", + "test\\baz\n", + "test\\DIR_5142667942049986036\n", + "test\\foo\n", + "test\\Hello.txt\n", + "*********\n", + "test\n", + "test\\bag\n", + "test\\bag\\foo\n", + "test\\bag\\foo\\bar\n", + "test\\bag\\foo\\bar\\baz\n", + "test\\bag\\foo\\bar\\baz\\8279660869874696036.tmp\n", + "test\\bag\\foo\\bar\\baz\\File.txt\n", + "test\\bar\n", + "test\\bar\\baz\n", + "test\\bar\\baz\\bag\n", + "test\\bar\\baz\\bag\\foo\n", + "test\\bar\\baz\\bag\\foo\\1274043134240426261.tmp\n", + "test\\bar\\baz\\bag\\foo\\File.txt\n", + "test\\baz\n", + "test\\baz\\bag\n", + "test\\baz\\bag\\foo\n", + "test\\baz\\bag\\foo\\bar\n", + "test\\baz\\bag\\foo\\bar\\6130572530014544105.tmp\n", + "test\\baz\\bag\\foo\\bar\\File.txt\n", + "test\\DIR_5142667942049986036\n", + "test\\DIR_5142667942049986036\\pre7704286843227113253.non\n", + "test\\foo\n", + "test\\foo\\bar\n", + "test\\foo\\bar\\baz\n", + "test\\foo\\bar\\baz\\bag\n", + "test\\foo\\bar\\baz\\bag\\5412864507741775436.tmp\n", + "test\\foo\\bar\\baz\\bag\\File.txt\n", + "test\\Hello.txt\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先,**refreshTestDir()** 用于检测 **test** 目录是否已经存在。若存在,则使用我们新工具类 **rmdir()** 删除其整个目录。检查是否 **exists** 是多余的,但我想说明一点,因为如果你对于已经存在的目录调用 **createDirectory()** 将会抛出异常。**createFile()** 使用参数 **Path** 创建一个空文件; **resolve()** 将文件名添加到 **test Path** 的末尾。\n", + "\n", + "我们尝试使用 **createDirectory()** 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路径。我已经将 **populateTestDir()** 作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量 **variant**,我们都能使用 **createDirectories()** 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用 **createTempFile()** 生成一个临时文件。\n", + "\n", + "在调用 **populateTestDir()** 之后,我们在 **test** 目录下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与 **createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用\".tmp\"作为后缀。\n", + "\n", + "为了展示结果,我们首次使用看起来很有希望的 **newDirectoryStream()**,但事实证明这个方法只是返回 **test** 目录内容的 Stream 流,并没有更多的内容。要获取目录树的全部内容的流,请使用 **Files.walk()**。\n", + "\n", + "\n", + "\n", + "## 文件系统\n", + "为了完整起见,我们需要一种方法查找文件系统相关的其他信息。在这里,我们使用静态的 **FileSystems** 工具类获取\"默认\"的文件系统,但你同样也可以在 **Path** 对象上调用 **getFileSystem()** 以获取创建该 **Path** 的文件系统。你可以获得给定 *URI* 的文件系统,还可以构建新的文件系统(对于支持它的操作系统)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/FileSystemDemo.java\n", + "import java.nio.file.*;\n", + "\n", + "public class FileSystemDemo {\n", + " static void show(String id, Object o) {\n", + " System.out.println(id + \": \" + o);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " System.out.println(System.getProperty(\"os.name\"));\n", + " FileSystem fsys = FileSystems.getDefault();\n", + " for(FileStore fs : fsys.getFileStores())\n", + " show(\"File Store\", fs);\n", + " for(Path rd : fsys.getRootDirectories())\n", + " show(\"Root Directory\", rd);\n", + " show(\"Separator\", fsys.getSeparator());\n", + " show(\"UserPrincipalLookupService\",\n", + " fsys.getUserPrincipalLookupService());\n", + " show(\"isOpen\", fsys.isOpen());\n", + " show(\"isReadOnly\", fsys.isReadOnly());\n", + " show(\"FileSystemProvider\", fsys.provider());\n", + " show(\"File Attribute Views\",\n", + " fsys.supportedFileAttributeViews());\n", + " }\n", + "}\n", + "/* 输出:\n", + "Windows 10\n", + "File Store: SSD (C:)\n", + "Root Directory: C:\\\n", + "Root Directory: D:\\\n", + "Separator: \\\n", + "UserPrincipalLookupService:\n", + "sun.nio.fs.WindowsFileSystem$LookupService$1@15db9742\n", + "isOpen: true\n", + "isReadOnly: false\n", + "FileSystemProvider:\n", + "sun.nio.fs.WindowsFileSystemProvider@6d06d69c\n", + "File Attribute Views: [owner, dos, acl, basic, user]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一个 **FileSystem** 对象也能生成 **WatchService** 和 **PathMatcher** 对象,将会在接下来两章中详细讲解。\n", + "\n", + "\n", + "\n", + "## 路径监听\n", + "通过 **WatchService** 可以设置一个进程对目录中的更改做出响应。在这个例子中,**delTxtFiles()** 作为一个单独的任务执行,该任务将遍历整个目录并删除以 **.txt** 结尾的所有文件,**WatchService** 会对文件删除操作做出反应:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/PathWatcher.java\n", + "// {ExcludeFromGradle}\n", + "import java.io.IOException;\n", + "import java.nio.file.*;\n", + "import static java.nio.file.StandardWatchEventKinds.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class PathWatcher {\n", + " static Path test = Paths.get(\"test\");\n", + " \n", + " static void delTxtFiles() {\n", + " try {\n", + " Files.walk(test)\n", + " .filter(f ->\n", + " f.toString()\n", + " .endsWith(\".txt\"))\n", + " .forEach(f -> {\n", + " try {\n", + " System.out.println(\"deleting \" + f);\n", + " Files.delete(f);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " });\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) throws Exception {\n", + " Directories.refreshTestDir();\n", + " Directories.populateTestDir();\n", + " Files.createFile(test.resolve(\"Hello.txt\"));\n", + " WatchService watcher = FileSystems.getDefault().newWatchService();\n", + " test.register(watcher, ENTRY_DELETE);\n", + " Executors.newSingleThreadScheduledExecutor()\n", + " .schedule(PathWatcher::delTxtFiles,\n", + " 250, TimeUnit.MILLISECONDS);\n", + " WatchKey key = watcher.take();\n", + " for(WatchEvent evt : key.pollEvents()) {\n", + " System.out.println(\"evt.context(): \" + evt.context() +\n", + " \"\\nevt.count(): \" + evt.count() +\n", + " \"\\nevt.kind(): \" + evt.kind());\n", + " System.exit(0);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "deleting test\\bag\\foo\\bar\\baz\\File.txt\n", + "deleting test\\bar\\baz\\bag\\foo\\File.txt\n", + "deleting test\\baz\\bag\\foo\\bar\\File.txt\n", + "deleting test\\foo\\bar\\baz\\bag\\File.txt\n", + "deleting test\\Hello.txt\n", + "evt.context(): Hello.txt\n", + "evt.count(): 1\n", + "evt.kind(): ENTRY_DELETE\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**delTxtFiles()** 中的 **try** 代码块看起来有些多余,因为它们捕获的是同一种类型的异常,外部的 **try** 语句似乎已经足够了。然而出于某种原因,Java 要求两者都必须存在(这也可能是一个 bug)。还要注意的是在 **filter()** 中,我们必须显式地使用 **f.toString()** 转为字符串,否则我们调用 **endsWith()** 将会与整个 **Path** 对象进行比较,而不是路径名称字符串的一部分进行比较。\n", + "\n", + "一旦我们从 **FileSystem** 中得到了 **WatchService** 对象,我们将其注册到 **test** 路径以及我们感兴趣的项目的变量参数列表中,可以选择 **ENTRY_CREATE**,**ENTRY_DELETE** 或 **ENTRY_MODIFY**(其中创建和删除不属于修改)。\n", + "\n", + "因为接下来对 **watcher.take()** 的调用会在发生某些事情之前停止所有操作,所以我们希望 **deltxtfiles()** 能够并行运行以便生成我们感兴趣的事件。为了实现这个目的,我通过调用 **Executors.newSingleThreadScheduledExecutor()** 产生一个 **ScheduledExecutorService** 对象,然后调用 **schedule()** 方法传递所需函数的方法引用,并且设置在运行之前应该等待的时间。\n", + "\n", + "此时,**watcher.take()** 将等待并阻塞在这里。当目标事件发生时,会返回一个包含 **WatchEvent** 的 **Watchkey** 对象。展示的这三种方法是能对 **WatchEvent** 执行的全部操作。\n", + "\n", + "查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在 **Hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说\"监视这个目录\",自然会包含整个目录和下面子目录,但实际上:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 **Watchservice**。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/TreeWatcher.java\n", + "// {ExcludeFromGradle}\n", + "import java.io.IOException;\n", + "import java.nio.file.*;\n", + "import static java.nio.file.StandardWatchEventKinds.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class TreeWatcher {\n", + "\n", + " static void watchDir(Path dir) {\n", + " try {\n", + " WatchService watcher =\n", + " FileSystems.getDefault().newWatchService();\n", + " dir.register(watcher, ENTRY_DELETE);\n", + " Executors.newSingleThreadExecutor().submit(() -> {\n", + " try {\n", + " WatchKey key = watcher.take();\n", + " for(WatchEvent evt : key.pollEvents()) {\n", + " System.out.println(\n", + " \"evt.context(): \" + evt.context() +\n", + " \"\\nevt.count(): \" + evt.count() +\n", + " \"\\nevt.kind(): \" + evt.kind());\n", + " System.exit(0);\n", + " }\n", + " } catch(InterruptedException e) {\n", + " return;\n", + " }\n", + " });\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) throws Exception {\n", + " Directories.refreshTestDir();\n", + " Directories.populateTestDir();\n", + " Files.walk(Paths.get(\"test\"))\n", + " .filter(Files::isDirectory)\n", + " .forEach(TreeWatcher::watchDir);\n", + " PathWatcher.delTxtFiles();\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + "deleting test\\bag\\foo\\bar\\baz\\File.txt\n", + "deleting test\\bar\\baz\\bag\\foo\\File.txt\n", + "evt.context(): File.txt\n", + "evt.count(): 1\n", + "evt.kind(): ENTRY_DELETE\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **watchDir()** 方法中给 **WatchSevice** 提供参数 **ENTRY_DELETE**,并启动一个独立的线程来监视该**Watchservice**。这里我们没有使用 **schedule()** 进行启动,而是使用 **submit()** 启动线程。我们遍历整个目录树,并将 **watchDir()** 应用于每个子目录。现在,当我们运行 **deltxtfiles()** 时,其中一个 **Watchservice** 会检测到每一次文件删除。\n", + "\n", + "\n", + "\n", + "## 文件查找\n", + "到目前为止,为了找到文件,我们一直使用相当粗糙的方法,在 `path` 上调用 `toString()`,然后使用 `string` 操作查看结果。事实证明,`java.nio.file` 有更好的解决方案:通过在 `FileSystem` 对象上调用 `getPathMatcher()` 获得一个 `PathMatcher`,然后传入您感兴趣的模式。模式有两个选项:`glob` 和 `regex`。`glob` 比较简单,实际上功能非常强大,因此您可以使用 `glob` 解决许多问题。如果您的问题更复杂,可以使用 `regex`,这将在接下来的 `Strings` 一章中解释。\n", + "\n", + "在这里,我们使用 `glob` 查找以 `.tmp` 或 `.txt` 结尾的所有 `Path`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/Find.java\n", + "// {ExcludeFromGradle}\n", + "import java.nio.file.*;\n", + "\n", + "public class Find {\n", + " public static void main(String[] args) throws Exception {\n", + " Path test = Paths.get(\"test\");\n", + " Directories.refreshTestDir();\n", + " Directories.populateTestDir();\n", + " // Creating a *directory*, not a file:\n", + " Files.createDirectory(test.resolve(\"dir.tmp\"));\n", + "\n", + " PathMatcher matcher = FileSystems.getDefault()\n", + " .getPathMatcher(\"glob:**/*.{tmp,txt}\");\n", + " Files.walk(test)\n", + " .filter(matcher::matches)\n", + " .forEach(System.out::println);\n", + " System.out.println(\"***************\");\n", + "\n", + " PathMatcher matcher2 = FileSystems.getDefault()\n", + " .getPathMatcher(\"glob:*.tmp\");\n", + " Files.walk(test)\n", + " .map(Path::getFileName)\n", + " .filter(matcher2::matches)\n", + " .forEach(System.out::println);\n", + " System.out.println(\"***************\");\n", + "\n", + " Files.walk(test) // Only look for files\n", + " .filter(Files::isRegularFile)\n", + " .map(Path::getFileName)\n", + " .filter(matcher2::matches)\n", + " .forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "test\\bag\\foo\\bar\\baz\\5208762845883213974.tmp\n", + "test\\bag\\foo\\bar\\baz\\File.txt\n", + "test\\bar\\baz\\bag\\foo\\7918367201207778677.tmp\n", + "test\\bar\\baz\\bag\\foo\\File.txt\n", + "test\\baz\\bag\\foo\\bar\\8016595521026696632.tmp\n", + "test\\baz\\bag\\foo\\bar\\File.txt\n", + "test\\dir.tmp\n", + "test\\foo\\bar\\baz\\bag\\5832319279813617280.tmp\n", + "test\\foo\\bar\\baz\\bag\\File.txt\n", + "***************\n", + "5208762845883213974.tmp\n", + "7918367201207778677.tmp\n", + "8016595521026696632.tmp\n", + "dir.tmp\n", + "5832319279813617280.tmp\n", + "***************\n", + "5208762845883213974.tmp\n", + "7918367201207778677.tmp\n", + "8016595521026696632.tmp\n", + "5832319279813617280.tmp\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `matcher` 中,`glob` 表达式开头的 `**/` 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 `Path` 时非常有用。单 `*` 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性---我们正在寻找以 `.tmp` 或 `.txt` 结尾的东西。您可以在 `getPathMatcher()` 文档中找到更多详细信息。\n", + "\n", + "`matcher2` 只使用 `*.tmp`,通常不匹配任何内容,但是添加 `map()` 操作会将完整路径减少到末尾的名称。\n", + "\n", + "注意,在这两种情况下,输出中都会出现 `dir.tmp`,即使它是一个目录而不是一个文件。要只查找文件,必须像在最后 `files.walk()` 中那样对其进行筛选。\n", + "\n", + "\n", + "## 文件读写\n", + "此时,我们可以对路径和目录做任何事情。 现在让我们看一下操纵文件本身的内容。\n", + "\n", + "如果一个文件很“小”,也就是说“它运行得足够快且占用内存小”,那么 `java.nio.file.Files` 类中的实用程序将帮助你轻松读写文本和二进制文件。\n", + "\n", + "`Files.readAllLines()` 一次读取整个文件(因此,“小”文件很有必要),产生一个`List`。 对于示例文件,我们将重用`streams/Cheese.dat`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/ListOfLines.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "\n", + "public class ListOfLines {\n", + " public static void main(String[] args) throws Exception {\n", + " Files.readAllLines(\n", + " Paths.get(\"../streams/Cheese.dat\"))\n", + " .stream()\n", + " .filter(line -> !line.startsWith(\"//\"))\n", + " .map(line ->\n", + " line.substring(0, line.length()/2))\n", + " .forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "Not much of a cheese\n", + "Finest in the\n", + "And what leads you\n", + "Well, it's\n", + "It's certainly uncon\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "跳过注释行,其余的内容每行只打印一半。 这实现起来很简单:你只需将 `Path` 传递给 `readAllLines()` (以前的 java 实现这个功能很复杂)。`readAllLines()` 有一个重载版本,包含一个 `Charset` 参数来存储文件的 Unicode 编码。\n", + "\n", + "`Files.write()` 被重载以写入 `byte` 数组或任何 `Iterable` 对象(它也有 `Charset` 选项):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/Writing.java\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "\n", + "public class Writing {\n", + " static Random rand = new Random(47);\n", + " static final int SIZE = 1000;\n", + " \n", + " public static void main(String[] args) throws Exception {\n", + " // Write bytes to a file:\n", + " byte[] bytes = new byte[SIZE];\n", + " rand.nextBytes(bytes);\n", + " Files.write(Paths.get(\"bytes.dat\"), bytes);\n", + " System.out.println(\"bytes.dat: \" + Files.size(Paths.get(\"bytes.dat\")));\n", + "\n", + " // Write an iterable to a file:\n", + " List lines = Files.readAllLines(\n", + " Paths.get(\"../streams/Cheese.dat\"));\n", + " Files.write(Paths.get(\"Cheese.txt\"), lines);\n", + " System.out.println(\"Cheese.txt: \" + Files.size(Paths.get(\"Cheese.txt\")));\n", + " }\n", + "}\n", + "/* Output:\n", + "bytes.dat: 1000\n", + "Cheese.txt: 199\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们使用 `Random` 来创建一个随机的 `byte` 数组; 你可以看到生成的文件大小是 1000。\n", + "\n", + "一个 `List` 被写入文件,任何 `Iterable` 对象也可以这么做。\n", + "\n", + "如果文件大小有问题怎么办? 比如说:\n", + "\n", + "1. 文件太大,如果你一次性读完整个文件,你可能会耗尽内存。\n", + "\n", + "2. 您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。\n", + "\n", + "`Files.lines()` 方便地将文件转换为行的 `Stream`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/ReadLineStream.java\n", + "import java.nio.file.*;\n", + "\n", + "public class ReadLineStream {\n", + " public static void main(String[] args) throws Exception {\n", + " Files.lines(Paths.get(\"PathInfo.java\"))\n", + " .skip(13)\n", + " .findFirst()\n", + " .ifPresent(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + " show(\"RegularFile\", Files.isRegularFile(p));\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这对本章中第一个示例代码做了流式处理,跳过 13 行,然后选择下一行并将其打印出来。\n", + "\n", + "`Files.lines()` 对于把文件处理行的传入流时非常有用,但是如果你想在 `Stream` 中读取,处理或写入怎么办?这就需要稍微复杂的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// files/StreamInAndOut.java\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class StreamInAndOut {\n", + " public static void main(String[] args) {\n", + " try(\n", + " Stream input =\n", + " Files.lines(Paths.get(\"StreamInAndOut.java\"));\n", + " PrintWriter output =\n", + " new PrintWriter(\"StreamInAndOut.txt\")\n", + " ) {\n", + " input.map(String::toUpperCase)\n", + " .forEachOrdered(output::println);\n", + " } catch(Exception e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为我们在同一个块中执行所有操作,所以这两个文件都可以在相同的 try-with-resources 语句中打开。`PrintWriter` 是一个旧式的 `java.io` 类,允许你“打印”到一个文件,所以它是这个应用的理想选择。如果你看一下 `StreamInAndOut.txt`,你会发现它里面的内容确实是大写的。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "虽然本章对文件和目录操作做了相当全面的介绍,但是仍然有没被介绍的类库中的功能——一定要研究 `java.nio.file` 的 Javadocs,尤其是 `java.nio.file.Files` 这个类。\n", + "\n", + "Java 7 和 8 对于处理文件和目录的类库做了大量改进。如果您刚刚开始使用 Java,那么您很幸运。在过去,它令人非常不愉快,我确信 Java 设计者以前对于文件操作不够重视才没做简化。对于初学者来说这是一件很棒的事,对于教学者来说也一样。我不明白为什么花了这么长时间来解决这个明显的问题,但不管怎么说它被解决了,我很高兴。使用文件现在很简单,甚至很有趣,这是你以前永远想不到的。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/18-Strings.ipynb b/jupyter/18-Strings.ipynb new file mode 100644 index 00000000..3a6e124f --- /dev/null +++ b/jupyter/18-Strings.ipynb @@ -0,0 +1,2481 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第十八章 字符串\n", + ">字符串操作毫无疑问是计算机程序设计中最常见的行为之一。\n", + "\n", + "在 Java 大展拳脚的 Web 系统中更是如此。在本章中,我们将深入学习在 Java 语言中应用最广泛的 `String` 类,并研究与之相关的类及工具。\n", + "\n", + "\n", + "\n", + "## 字符串的不可变\n", + "`String` 对象是不可变的。查看 JDK 文档你就会发现,`String` 类中每一个看起来会修改 `String` 值的方法,实际上都是创建了一个全新的 `String` 对象,以包含修改后的字符串内容。而最初的 `String` 对象则丝毫未动。\n", + "\n", + "看看下面的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Immutable.java\n", + "public class Immutable { \n", + " public static String upcase(String s) { \n", + " return s.toUpperCase(); \n", + " } \n", + " public static void main(String[] args) { \n", + " String q = \"howdy\";\n", + " System.out.println(q); // howdy \n", + " String qq = upcase(q); \n", + " System.out.println(qq); // HOWDY \n", + " System.out.println(q); // howdy \n", + " } \n", + "} \n", + "/* Output: \n", + "howdy\n", + "HOWDY \n", + "howdy\n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当把 `q` 传递给 `upcase()` 方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。\n", + "\n", + "回到 `upcase()` 的定义,传入其中的引用有了名字 `s`,只有 `upcase()` 运行的时候,局部引用 `s` 才存在。一旦 `upcase()` 运行结束,`s` 就消失了。当然了,`upcase()` 的返回值,其实是最终结果的引用。这足以说明,`upcase()` 返回的引用已经指向了一个新的对象,而 `q` 仍然在原来的位置。\n", + "\n", + "`String` 的这种行为正是我们想要的。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "String s = \"asdf\";\n", + "String x = Immutable.upcase(s);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "难道你真的希望 `upcase()` 方法改变其参数吗?对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。\n", + "\n", + "\n", + "\n", + "## `+` 的重载与 `StringBuilder`\n", + "`String` 对象是不可变的,你可以给一个 `String` 对象添加任意多的别名。因为 `String` 是只读的,所以指向它的任何引用都不可能修改它的值,因此,也就不会影响到其他引用。\n", + "\n", + "不可变性会带来一定的效率问题。为 `String` 对象重载的 `+` 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于 `String` 的 `+` 与 `+=` 是 Java 中仅有的两个重载过的操作符,Java 不允许程序员重载任何其他的操作符 [^1])。\n", + "\n", + "操作符 `+` 可以用来连接 `String`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Concatenation.java\n", + "\n", + "public class Concatenation {\n", + " public static void main(String[] args) { \n", + " String mango = \"mango\"; \n", + " String s = \"abc\" + mango + \"def\" + 47; \n", + " System.out.println(s);\n", + " } \n", + "}\n", + "/* Output:\n", + "abcmangodef47 \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以想象一下,这段代码是这样工作的:`String` 可能有一个 `append()` 方法,它会生成一个新的 `String` 对象,以包含“abc”与 `mango` 连接后的字符串。该对象会再创建另一个新的 `String` 对象,然后与“def”相连,生成另一个新的对象,依此类推。\n", + "\n", + "这种方式当然是可行的,但是为了生成最终的 `String` 对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。\n", + "\n", + "想看看以上代码到底是如何工作的吗?可以用 JDK 自带的 `javap` 工具来反编译以上代码。命令如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "javap -c Concatenation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里的 `-c` 标志表示将生成 JVM 字节码。我们剔除不感兴趣的部分,然后做细微的修改,于是有了以下的字节码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "x86asm" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public static void main(java.lang.String[]); \n", + " Code:\n", + " Stack=2, Locals=3, Args_size=1\n", + " 0: ldc #2; //String mango \n", + " 2: astore_1 \n", + " 3: new #3; //class StringBuilder \n", + " 6: dup \n", + " 7: invokespecial #4; //StringBuilder.\"\":() \n", + " 10: ldc #5; //String abc \n", + " 12: invokevirtual #6; //StringBuilder.append:(String) \n", + " 15: aload_1 \n", + " 16: invokevirtual #6; //StringBuilder.append:(String) \n", + " 19: ldc #7; //String def \n", + " 21: invokevirtual #6; //StringBuilder.append:(String) \n", + " 24: bipush 47 \n", + " 26: invokevirtual #8; //StringBuilder.append:(I) \n", + " 29: invokevirtual #9; //StringBuilder.toString:() \n", + " 32: astore_2 \n", + " 33: getstatic #10; //Field System.out:PrintStream;\n", + " 36: aload_2 \n", + " 37: invokevirtual #11; //PrintStream.println:(String) \n", + " 40: return" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你有汇编语言的经验,以上代码应该很眼熟(其中的 `dup` 和 `invokevirtual` 语句相当于Java虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心)。需要重点注意的是:编译器自动引入了 `java.lang.StringBuilder` 类。虽然源代码中并没有使用 `StringBuilder` 类,但是编译器却自作主张地使用了它,就因为它更高效。\n", + "\n", + "在这里,编译器创建了一个 `StringBuilder` 对象,用于构建最终的 `String`,并对每个字符串调用了一次 `append()` 方法,共计 4 次。最后调用 `toString()` 生成结果,并存为 `s` (使用的命令为 `astore_2`)。\n", + "\n", + "现在,也许你会觉得可以随意使用 `String` 对象,反正编译器会自动为你做性能优化。可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。下面的例子采用两种方式生成一个 `String`:方法一使用了多个 `String` 对象;方法二在代码中使用了 `StringBuilder`。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/WhitherStringBuilder.java\n", + "\n", + "public class WhitherStringBuilder { \n", + " public String implicit(String[] fields) { \n", + " String result = \"\"; \n", + " for(String field : fields) { \n", + " result += field;\n", + " }\n", + " return result; \n", + " }\n", + " public String explicit(String[] fields) { \n", + " StringBuilder result = new StringBuilder(); \n", + " for(String field : fields) { \n", + " result.append(field); \n", + " } \n", + " return result.toString(); \n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在运行 `javap -c WitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是 `implicit()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "x86asm" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public java.lang.String implicit(java.lang.String[]); \n", + "0: ldc #2 // String \n", + "2: astore_2\n", + "3: aload_1 \n", + "4: astore_3 \n", + "5: aload_3 \n", + "6: arraylength \n", + "7: istore 4 \n", + "9: iconst_0 \n", + "10: istore 5 \n", + "12: iload 5 \n", + "14: iload 4 \n", + "16: if_icmpge 51 \n", + "19: aload_3 \n", + "20: iload 5 \n", + "22: aaload \n", + "23: astore 6 \n", + "25: new #3 // StringBuilder \n", + "28: dup \n", + "29: invokespecial #4 // StringBuilder.\"\"\n", + "32: aload_2 \n", + "33: invokevirtual #5 // StringBuilder.append:(String) \n", + "36: aload 6 \n", + "38: invokevirtual #5 // StringBuilder.append:(String;) \n", + "41: invokevirtual #6 // StringBuilder.toString:() \n", + "44: astore_2 \n", + "45: iinc 5, 1 \n", + "48: goto 12 \n", + "51: aload_2 \n", + "52: areturn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意从第 16 行到第 48 行构成了一个循环体。第 16 行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第 51 行。第 48 行:重新回到循环体的起始位置(第 12 行)。注意:`StringBuilder` 是在循环内构造的,这意味着每进行一次循环,会创建一个新的 `StringBuilder` 对象。\n", + "\n", + "下面是 `explicit()` 方法对应的字节码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "x86asm" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public java.lang.String explicit(java.lang.String[]); \n", + "0: new #3 // StringBuilder \n", + "3: dup\n", + "4: invokespecial #4 // StringBuilder.\"\" \n", + "7: astore_2 \n", + "8: aload_1 \n", + "9: astore_3 \n", + "10: aload_3 \n", + "11: arraylength \n", + "12: istore 4 \n", + "14: iconst_0 \n", + "15: istore 5 \n", + "17: iload 5 \n", + "19: iload 4 \n", + "21: if_icmpge 43 \n", + "24: aload_3 \n", + "25: iload 5 \n", + "27: aaload \n", + "28: astore 6 \n", + "30: aload_2 \n", + "31: aload 6 \n", + "33: invokevirtual #5 // StringBuilder.append:(String) \n", + "36: pop\n", + "37: iinc 5, 1 \n", + "40: goto 17 \n", + "43: aload_2 \n", + "44: invokevirtual #6 // StringBuilder.toString:() \n", + "47: areturn" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个 `StringBuilder` 对象。显式地创建 `StringBuilder` 还允许你预先为其指定大小。如果你已经知道最终字符串的大概长度,那预先指定 `StringBuilder` 的大小可以避免频繁地重新分配缓冲。\n", + "\n", + "因此,当你为一个类编写 `toString()` 方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在 `toString()` 方法中使用循环,且可能有性能问题,那么最好自己创建一个 `StringBuilder` 对象,用它来构建最终结果。请参考以下示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/UsingStringBuilder.java \n", + "\n", + "import java.util.*; \n", + "import java.util.stream.*; \n", + "public class UsingStringBuilder { \n", + " public static String string1() { \n", + " Random rand = new Random(47);\n", + " StringBuilder result = new StringBuilder(\"[\"); \n", + " for(int i = 0; i < 25; i++) { \n", + " result.append(rand.nextInt(100)); \n", + " result.append(\", \"); \n", + " } \n", + " result.delete(result.length()-2, result.length()); \n", + " result.append(\"]\");\n", + " return result.toString(); \n", + " } \n", + " public static String string2() { \n", + " String result = new Random(47)\n", + " .ints(25, 0, 100)\n", + " .mapToObj(Integer::toString)\n", + " .collect(Collectors.joining(\", \"));\n", + " return \"[\" + result + \"]\"; \n", + " } \n", + " public static void main(String[] args) { \n", + " System.out.println(string1()); \n", + " System.out.println(string2()); \n", + " }\n", + "} \n", + "/* Output: \n", + "[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, \n", + "9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] \n", + "[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89,\n", + "9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在方法 `string1()` 中,最终结果是用 `append()` 语句拼接起来的。如果你想走捷径,例如:`append(a + \": \" + c)`,编译器就会掉入陷阱,从而为你另外创建一个 `StringBuilder` 对象处理括号内的字符串操作。如果拿不准该用哪种方式,随时可以用 `javap` 来分析你的程序。\n", + "\n", + "`StringBuilder` 提供了丰富而全面的方法,包括 `insert()`、`replace()`、`substring()`,甚至还有`reverse()`,但是最常用的还是 `append()` 和 `toString()`。还有 `delete()`,上面的例子中我们用它删除最后一个逗号和空格,以便添加右括号。\n", + "\n", + "`string2()` 使用了 `Stream`,这样代码更加简洁美观。可以证明,`Collectors.joining()` 内部也是使用的 `StringBuilder`,这种写法不会影响性能!\n", + "\n", + "`StringBuilder `是 Java SE5 引入的,在这之前用的是 `StringBuffer`。后者是线程安全的(参见[并发编程](./24-Concurrent-Programming.md)),因此开销也会大些。使用 `StringBuilder` 进行字符串操作更快一点。\n", + "\n", + "\n", + "\n", + "## 意外递归\n", + "Java 中的每个类从根本上都是继承自 `Object`,标准集合类也是如此,它们都有 `toString()` 方法,并且覆盖了该方法,使得它生成的 `String` 结果能够表达集合自身,以及集合包含的对象。例如 `ArrayList.toString()`,它会遍历 `ArrayList` 中包含的所有对象,调用每个元素上的 `toString()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ArrayListDisplay.java \n", + "import java.util.*;\n", + "import java.util.stream.*; \n", + "import generics.coffee.*;\n", + "public class ArrayListDisplay { \n", + " public static void main(String[] args) {\n", + " List coffees = \n", + " Stream.generate(new CoffeeSupplier())\n", + " .limit(10)\n", + " .collect(Collectors.toList()); \n", + " System.out.println(coffees); \n", + " } \n", + "}\n", + "/* Output: \n", + "[Americano 0, Latte 1, Americano 2, Mocha 3, Mocha 4, \n", + "Breve 5, Americano 6, Latte 7, Cappuccino 8, Cappuccino 9] \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你希望 `toString()` 打印出类的内存地址,也许你会考虑使用 `this` 关键字:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/InfiniteRecursion.java \n", + "// Accidental recursion \n", + "// {ThrowsException} \n", + "// {VisuallyInspectOutput} Throws very long exception\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class InfiniteRecursion { \n", + " @Override \n", + " public String toString() { \n", + " return \" InfiniteRecursion address: \" + this + \"\\n\"\n", + " } \n", + " public static void main(String[] args) { \n", + " Stream.generate(InfiniteRecursion::new) \n", + " .limit(10) \n", + " .forEach(System.out::println); \n", + " } \n", + "} " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当你创建了 `InfiniteRecursion` 对象,并将其打印出来的时候,你会得到一串很长的异常信息。如果你将该 `InfiniteRecursion` 对象存入一个 `ArrayList` 中,然后打印该 `ArrayList`,同样也会抛出异常。其实,当运行到如下代码时:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\"InfiniteRecursion address: \" + this " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里发生了自动类型转换,由 `InfiniteRecursion` 类型转换为 `String` 类型。因为编译器发现一个 `String` 对象后面跟着一个 “+”,而 “+” 后面的对象不是 `String`,于是编译器试着将 `this` 转换成一个 `String`。它怎么转换呢?正是通过调用 `this` 上的 `toString()` 方法,于是就发生了递归调用。\n", + "\n", + "如果你真的想要打印对象的内存地址,应该调用 `Object.toString()` 方法,这才是负责此任务的方法。所以,不要使用 `this`,而是应该调用 `super.toString()` 方法。\n", + "\n", + "\n", + "\n", + "## 字符串操作\n", + "以下是 `String` 对象具备的一些基本方法。重载的方法归纳在同一行中:\n", + "\n", + "| 方法 | 参数,重载版本 | 作用 |\n", + "| ---- | ---- | ---- |\n", + "| 构造方法 | 默认版本,`String`,`StringBuilder`,`StringBuffer`,`char`数组,`byte`数组 | 创建`String`对象 |\n", + "| `length()` | | `String`中字符的个数 |\n", + "| `charAt()` | `int`索引|获取`String`中索引位置上的`char` |\n", + "| `getChars()`,`getBytes()` | 待复制部分的开始和结束索引,复制的目标数组,目标数组的开始索引 | 复制`char`或`byte`到一个目标数组中 |\n", + "| `toCharArray()` | | 生成一个`char[]`,包含`String`中的所有字符 |\n", + "| `equals()`,`equalsIgnoreCase()` | 与之进行比较的`String` | 比较两个`String`的内容是否相同。如果相同,结果为`true` |\n", + "| `compareTo()`,`compareToIgnoreCase()` | 与之进行比较的`String` | 按词典顺序比较`String`的内容,比较结果为负数、零或正数。注意,大小写不等价 |\n", + "| `contains()` | 要搜索的`CharSequence` | 如果该`String`对象包含参数的内容,则返回`true` |\n", + "| `contentEquals()` | 与之进行比较的`CharSequence`或`StringBuffer` | 如果该`String`对象与参数的内容完全一致,则返回`true` |\n", + "| `isEmpty()` | | 返回`boolean`结果,以表明`String`对象的长度是否为0 |\n", + "| `regionMatches()` | 该`String`的索引偏移量,另一个`String`及其索引偏移量,要比较的长度。重载版本增加了“忽略大小写”功能|返回`boolean`结果,以表明所比较区域是否相等 |\n", + "| `startsWith()` | 可能的起始`String`。重载版本在参数中增加了偏移量 | 返回`boolean`结果,以表明该`String`是否以传入参数开始 |\n", + "| `endsWith()` | 该`String`可能的后缀`String` | 返回`boolean`结果,以表明此参数是否是该字符串的后缀 |\n", + "| `indexOf()`,`lastIndexOf()` | 重载版本包括:`char`,`char`与起始索引,`String`,`String`与起始索引 | 如果该`String`并不包含此参数,就返回-1;否则返回此参数在`String`中的起始索引。`lastIndexOf`()是从后往前搜索 |\n", + "| `matches()` | 一个正则表达式 | 返回`boolean`结果,以表明该`String`和给出的正则表达式是否匹配 |\n", + "| `split()` | 一个正则表达式。可选参数为需要拆分的最大数量 | 按照正则表达式拆分`String`,返回一个结果数组 |\n", + "| `join()`(Java8引入的)|分隔符,待拼字符序列。用分隔符将字符序列拼接成一个新的`String` |用分隔符拼接字符片段,产生一个新的`String` |\n", + "| `substring()`(即`subSequence()`)| 重载版本:起始索引;起始索引+终止索引 | 返回一个新的`String`对象,以包含参数指定的子串 |\n", + "| `concat()` | 要连接的`String` |返回一个新的`String`对象,内容为原始`String`连接上参数`String` |\n", + "| `replace()` | 要替换的字符,用来进行替换的新字符。也可以用一个`CharSequence`替换另一个`CharSequence` |返回替换字符后的新`String`对象。如果没有替换发生,则返回原始的`String`对象 |\n", + "| `replaceFirst()` |要替换的正则表达式,用来进行替换的`String` | 返回替换首个目标字符串后的`String`对象 |\n", + "| `replaceAll()` | 要替换的正则表达式,用来进行替换的`String` | 返回替换所有目标字符串后的`String`对象 |\n", + "| `toLowerCase()`,`toUpperCase()` | | 将字符的大小写改变后,返回一个新的`String`对象。如果没有任何改变,则返回原始的`String`对象|\n", + "| `trim()` | |将`String`两端的空白符删除后,返回一个新的`String`对象。如果没有任何改变,则返回原始的`String`对象|\n", + "| `valueOf()`(`static`)|重载版本:`Object`;`char[]`;`char[]`,偏移量,与字符个数;`boolean`;`char`;`int`;`long`;`float`;`double` | 返回一个表示参数内容的`String` |\n", + "| `intern()` | | 为每个唯一的字符序列生成一个且仅生成一个`String`引用 |\n", + "| `format()` | 要格式化的字符串,要替换到格式化字符串的参数 | 返回格式化结果`String` |\n", + "\n", + "从这个表可以看出,当需要改变字符串的内容时,`String` 类的方法都会返回一个新的 `String` 对象。同时,如果内容不改变,`String` 方法只是返回原始对象的一个引用而已。这可以节约存储空间以及避免额外的开销。\n", + "\n", + "本章稍后还将介绍正则表达式在 `String` 方法中的应用。\n", + "\n", + "\n", + "\n", + "## 格式化输出\n", + "在长久的等待之后,Java SE5 终于推出了 C 语言中 `printf()` 风格的格式化输出这一功能。这不仅使得控制输出的代码更加简单,同时也给与Java开发者对于输出格式与排列更强大的控制能力。\n", + "### `printf()`\n", + "C 语言的 `printf()` 并不像 Java 那样连接字符串,它使用一个简单的格式化字符串,加上要插入其中的值,然后将其格式化输出。 `printf()` 并不使用重载的 `+` 操作符(C语言没有重载)来连接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "System.out.printf(\"Row 1: [%d %f]%n\", x, y);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这一行代码在运行的时候,首先将 `x` 的值插入到 `%d_` 的位置,然后将 `y` 的值插入到 `%f` 的位置。这些占位符叫做*格式修饰符*,它们不仅指明了插入数据的位置,同时还指明了将会插入什么类型的变量,以及如何格式化。在这个例子中 `%d` 表示 `x` 是一个整数,`%f` 表示 `y` 是一个浮点数(`float` 或者 `double`)。\n", + "### `System.out.format()`\n", + "Java SE5 引入了 `format()` 方法,可用于 `PrintStream` 或者 `PrintWriter` 对象(你可以在 [附录:流式 I/O](./Appendix-IO-Streams.md) 了解更多内容),其中也包括 `System.out` 对象。`format()` 方法模仿了 C 语言的 `printf()`。如果你比较怀旧的话,也可以使用 `printf()`。以下是一个简单的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/SimpleFormat.java \n", + "\n", + "public class SimpleFormat { \n", + " public static void main(String[] args) { \n", + " int x = 5; \n", + " double y = 5.332542; \n", + " // The old way: \n", + " System.out.println(\"Row 1: [\" + x + \" \" + y + \"]\"); \n", + " // The new way: \n", + " System.out.format(\"Row 1: [%d %f]%n\", x, y); \n", + " // or \n", + " System.out.printf(\"Row 1: [%d %f]%n\", x, y); \n", + " } \n", + "} \n", + "/* Output: \n", + "Row 1: [5 5.332542] \n", + "Row 1: [5 5.332542] \n", + "Row 1: [5 5.332542] \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,`format()` 和 `printf()` 是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。\n", + "\n", + "`String` 类也有一个 `static format()` 方法,可以格式化字符串。\n", + "\n", + "### `Formatter` 类\n", + "在 Java 中,所有的格式化功能都是由 `java.util.Formatter` 类处理的。可以将 `Formatter` 看做一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个 `Formatter` 对象时,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Turtle.java \n", + "import java.io.*;\n", + "import java.util.*;\n", + "\n", + "public class Turtle { \n", + " private String name; \n", + " private Formatter f; \n", + " public Turtle(String name, Formatter f) {\n", + " this.name = name; \n", + " this.f = f; \n", + " } \n", + " public void move(int x, int y) { \n", + " f.format(\"%s The Turtle is at (%d,%d)%n\", \n", + " name, x, y); \n", + " }\n", + " public static void main(String[] args) { \n", + " PrintStream outAlias = System.out; \n", + " Turtle tommy = new Turtle(\"Tommy\",\n", + " new Formatter(System.out)); \n", + " Turtle terry = new Turtle(\"Terry\", \n", + " new Formatter(outAlias)); \n", + " tommy.move(0,0); \n", + " terry.move(4,8); \n", + " tommy.move(3,4); \n", + " terry.move(2,5); \n", + " tommy.move(3,3); \n", + " terry.move(3,3); \n", + " } \n", + "} \n", + "/* Output: \n", + "Tommy The Turtle is at (0,0) \n", + "Terry The Turtle is at (4,8) \n", + "Tommy The Turtle is at (3,4) \n", + "Terry The Turtle is at (2,5) \n", + "Tommy The Turtle is at (3,3) \n", + "Terry The Turtle is at (3,3) \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "格式化修饰符 `%s` 表明这里需要 `String` 参数。\n", + "\n", + "所有的 `tommy` 都将输出到 `System.out`,而所有的 `terry` 则都输出到 `System.out` 的一个别名中。`Formatter` 的重载构造器支持输出到多个路径,不过最常用的还是 `PrintStream()`(如上例)、`OutputStream` 和 `File`。你可以在 [附录:流式 I/O](././Appendix-IO-Streams.md) 中了解更多信息。\n", + "### 格式化修饰符\n", + "在插入数据时,如果想要优化空格与对齐,你需要更精细复杂的格式修饰符。以下是其通用语法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "%[argument_index$][flags][width][.precision]conversion " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最常见的应用是控制一个字段的最小长度,这可以通过指定 *width* 来实现。`Formatter `对象通过在必要时添加空格,来确保一个字段至少达到设定长度。默认情况下,数据是右对齐的,不过可以通过使用 `-` 标志来改变对齐方向。\n", + "\n", + "与 *width* 相对的是 *precision*,用于指定最大长度。*width* 可以应用于各种类型的数据转换,并且其行为方式都一样。*precision* 则不然,当应用于不同类型的数据转换时,*precision* 的意义也不同。在将 *precision* 应用于 `String` 时,它表示打印 `string` 时输出字符的最大数量。而在将 *precision* 应用于浮点数时,它表示小数部分要显示出来的位数(默认是 6 位小数),如果小数位数过多则舍入,太少则在尾部补零。由于整数没有小数部分,所以 *precision* 无法应用于整数,如果你对整数应用 *precision*,则会触发异常。\n", + "\n", + "下面的程序应用格式修饰符来打印一个购物收据。这是 *Builder* 设计模式的一个简单实现,即先创建一个初始对象,然后逐渐添加新东西,最后调用 `build()` 方法完成构建:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ReceiptBuilder.java \n", + "import java.util.*; \n", + "\n", + "public class ReceiptBuilder { \n", + " private double total = 0; \n", + " private Formatter f = \n", + " new Formatter(new StringBuilder()); \n", + " public ReceiptBuilder() { \n", + " f.format( \n", + " \"%-15s %5s %10s%n\", \"Item\", \"Qty\", \"Price\"); \n", + " f.format( \n", + " \"%-15s %5s %10s%n\", \"----\", \"---\", \"-----\"); \n", + " } \n", + " public void add(String name, int qty, double price) { \n", + " f.format(\"%-15.15s %5d %10.2f%n\", name, qty, price); \n", + " total += price * qty; \n", + " } \n", + " public String build() { \n", + " f.format(\"%-15s %5s %10.2f%n\", \"Tax\", \"\", \n", + " total * 0.06); \n", + " f.format(\"%-15s %5s %10s%n\", \"\", \"\", \"-----\"); \n", + " f.format(\"%-15s %5s %10.2f%n\", \"Total\", \"\", \n", + " total * 1.06); \n", + " return f.toString(); \n", + " } \n", + " public static void main(String[] args) { \n", + " ReceiptBuilder receiptBuilder = \n", + " new ReceiptBuilder(); \n", + " receiptBuilder.add(\"Jack's Magic Beans\", 4, 4.25); \n", + " receiptBuilder.add(\"Princess Peas\", 3, 5.1); \n", + " receiptBuilder.add( \n", + " \"Three Bears Porridge\", 1, 14.29); \n", + " System.out.println(receiptBuilder.build()); \n", + " } \n", + "} \n", + "/* Output: \n", + "Item Qty Price \n", + "---- --- ----- \n", + "Jack's Magic Be 4 4.25 \n", + "Princess Peas 3 5.10 \n", + "Three Bears Por 1 14.29 \n", + "Tax 2.80 \n", + " ----- \n", + "Total 49.39 \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过传入一个 `StringBuilder` 对象到 `Formatter` 的构造器,我们指定了一个容器来构建目标 `String`。你也可以通过不同的构造器参数,把结果输出到标准输出,甚至是一个文件里。\n", + "\n", + "正如你所见,通过相当简洁的语法,`Formatter ` 提供了对空格与对齐的强大控制能力。在该程序中,为了恰当地控制间隔,格式化字符串被重复利用了多遍。\n", + "### `Formatter` 转换\n", + "下面的表格展示了最常用的类型转换:\n", + "\n", + "| 类型 | 含义 |\n", + "| :----: | :---- |\n", + "| `d` | 整型(十进制) |\n", + "| `c` | Unicode字符 |\n", + "| `b` | Boolean值 |\n", + "| `s` | String |\n", + "| `f` | 浮点数(十进制) |\n", + "| `e` | 浮点数(科学计数) |\n", + "| `x` | 整型(十六进制) |\n", + "| `h` | 散列码(十六进制) |\n", + "| `%` | 字面值“%” |\n", + "\n", + "下面的程序演示了这些转换是如何工作的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Conversion.java \n", + "import java.math.*;\n", + "import java.util.*; \n", + "\n", + "public class Conversion { \n", + " public static void main(String[] args) { \n", + " Formatter f = new Formatter(System.out); \n", + "\n", + " char u = 'a'; \n", + " System.out.println(\"u = 'a'\"); \n", + " f.format(\"s: %s%n\", u); \n", + " // f.format(\"d: %d%n\", u); \n", + " f.format(\"c: %c%n\", u); \n", + " f.format(\"b: %b%n\", u); \n", + " // f.format(\"f: %f%n\", u); \n", + " // f.format(\"e: %e%n\", u); \n", + " // f.format(\"x: %x%n\", u); \n", + " f.format(\"h: %h%n\", u); \n", + "\n", + " int v = 121; \n", + " System.out.println(\"v = 121\"); \n", + " f.format(\"d: %d%n\", v); \n", + " f.format(\"c: %c%n\", v); \n", + " f.format(\"b: %b%n\", v); \n", + " f.format(\"s: %s%n\", v); \n", + " // f.format(\"f: %f%n\", v); \n", + " // f.format(\"e: %e%n\", v); \n", + " f.format(\"x: %x%n\", v); \n", + " f.format(\"h: %h%n\", v); \n", + "\n", + " BigInteger w = new BigInteger(\"50000000000000\"); \n", + " System.out.println( \n", + " \"w = new BigInteger(\\\"50000000000000\\\")\"); \n", + " f.format(\"d: %d%n\", w); \n", + " // f.format(\"c: %c%n\", w); \n", + " f.format(\"b: %b%n\", w); \n", + " f.format(\"s: %s%n\", w); \n", + " // f.format(\"f: %f%n\", w); \n", + " // f.format(\"e: %e%n\", w); \n", + " f.format(\"x: %x%n\", w); \n", + " f.format(\"h: %h%n\", w); \n", + "\n", + " double x = 179.543; \n", + " System.out.println(\"x = 179.543\"); \n", + " // f.format(\"d: %d%n\", x); \n", + " // f.format(\"c: %c%n\", x); \n", + " f.format(\"b: %b%n\", x); \n", + " f.format(\"s: %s%n\", x); \n", + " f.format(\"f: %f%n\", x); \n", + " f.format(\"e: %e%n\", x); \n", + " // f.format(\"x: %x%n\", x); \n", + " f.format(\"h: %h%n\", x); \n", + "\n", + " Conversion y = new Conversion(); \n", + " System.out.println(\"y = new Conversion()\"); \n", + "\n", + " // f.format(\"d: %d%n\", y); \n", + " // f.format(\"c: %c%n\", y); \n", + " f.format(\"b: %b%n\", y); \n", + " f.format(\"s: %s%n\", y); \n", + " // f.format(\"f: %f%n\", y); \n", + " // f.format(\"e: %e%n\", y); \n", + " // f.format(\"x: %x%n\", y); \n", + " f.format(\"h: %h%n\", y); \n", + "\n", + " boolean z = false; \n", + " System.out.println(\"z = false\"); \n", + " // f.format(\"d: %d%n\", z); \n", + " // f.format(\"c: %c%n\", z); \n", + " f.format(\"b: %b%n\", z); \n", + " f.format(\"s: %s%n\", z); \n", + " // f.format(\"f: %f%n\", z); \n", + " // f.format(\"e: %e%n\", z); \n", + " // f.format(\"x: %x%n\", z); \n", + " f.format(\"h: %h%n\", z); \n", + " } \n", + "} \n", + "/* Output: \n", + "u = 'a' \n", + "s: a \n", + "c: a \n", + "b: true \n", + "h: 61 \n", + "v = 121 \n", + "d: 121 \n", + "c: y \n", + "b: true \n", + "s: 121 \n", + "x: 79 \n", + "h: 79 \n", + "w = new BigInteger(\"50000000000000\") \n", + "d: 50000000000000 \n", + "b: true \n", + "s: 50000000000000 \n", + "x: 2d79883d2000 \n", + "h: 8842a1a7 \n", + "x = 179.543 \n", + "b: true \n", + "s: 179.543 \n", + "f: 179.543000 \n", + "e: 1.795430e+02 \n", + "h: 1ef462c \n", + "y = new Conversion() \n", + "b: true \n", + "s: Conversion@15db9742 \n", + "h: 15db9742 \n", + "z = false \n", + "b: false \n", + "s: false\n", + "h: 4d5 \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "被注释的代码表示,针对相应类型的变量,这些转换是无效的。如果执行这些转换,则会触发异常。\n", + "\n", + "注意,程序中的每个变量都用到了 `b` 转换。虽然它对各种类型都是合法的,但其行为却不一定与你想象的一致。对于 `boolean` 基本类型或 `Boolean` 对象,其转换结果是对应的 `true` 或 `false`。但是,对其他类型的参数,只要该参数不为 `null`,其转换结果永远都是 `true`。即使是数字 0,转换结果依然为 `true`,而这在其他语言中(包括C),往往转换为 `false`。所以,将 `b` 应用于非布尔类型的对象时请格外小心。\n", + "\n", + "还有许多不常用的类型转换与格式修饰符选项,你可以在 JDK 文档中的 `Formatter` 类部分找到它们。\n", + "### `String.format()`\n", + "Java SE5 也参考了 C 中的 `sprintf()` 方法,以生成格式化的 `String` 对象。`String.format()` 是一个 `static` 方法,它接受与 `Formatter.format()` 方法一样的参数,但返回一个 `String` 对象。当你只需使用一次 `format()` 方法的时候,`String.format()` 用起来很方便。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/DatabaseException.java \n", + "\n", + "public class DatabaseException extends Exception { \n", + " public DatabaseException(int transactionID, \n", + " int queryID, String message) { \n", + " super(String.format(\"(t%d, q%d) %s\", transactionID, \n", + " queryID, message)); \n", + " } \n", + " public static void main(String[] args) { \n", + " try { \n", + " throw new DatabaseException(3, 7, \"Write failed\"); \n", + " } catch(Exception e) { \n", + " System.out.println(e); \n", + " } \n", + " } \n", + "} \n", + "/* \n", + "Output: \n", + "DatabaseException: (t3, q7) Write failed \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其实在 `String.format()` 内部,它也是创建了一个 `Formatter` 对象,然后将你传入的参数转给 `Formatter`。不过,与其自己做这些事情,不如使用便捷的 `String.format()` 方法,何况这样的代码更清晰易读。\n", + "#### 一个十六进制转储(dump)工具\n", + "在第二个例子中,我们把二进制文件转换为十六进制格式。下面的小工具使用了 `String.format()` 方法,以可读的十六进制格式将字节数组打印出来:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Hex.java \n", + "// {java onjava.Hex} \n", + "package onjava;\n", + "import java.io.*; \n", + "import java.nio.file.*; \n", + "\n", + "public class Hex { \n", + " public static String format(byte[] data) { \n", + " StringBuilder result = new StringBuilder(); \n", + " int n = 0; \n", + " for(byte b : data) { \n", + " if(n % 16 == 0) \n", + " result.append(String.format(\"%05X: \", n)); \n", + " result.append(String.format(\"%02X \", b)); \n", + " n++; \n", + " if(n % 16 == 0) result.append(\"\\n\"); \n", + " } \n", + " result.append(\"\\n\"); \n", + " return result.toString(); \n", + " } \n", + " public static void main(String[] args) throws Exception { \n", + " if(args.length == 0) \n", + " // Test by displaying this class file: \n", + " System.out.println(format( \n", + " Files.readAllBytes(Paths.get( \n", + " \"build/classes/main/onjava/Hex.class\")))); \n", + " else \n", + " System.out.println(format( \n", + " Files.readAllBytes(Paths.get(args[0])))); \n", + " } \n", + "} \n", + "/* Output: (First 6 Lines) \n", + "00000: CA FE BA BE 00 00 00 34 00 61 0A 00 05 00 31 07 \n", + "00010: 00 32 0A 00 02 00 31 08 00 33 07 00 34 0A 00 35 \n", + "00020: 00 36 0A 00 0F 00 37 0A 00 02 00 38 08 00 39 0A \n", + "00030: 00 3A 00 3B 08 00 3C 0A 00 02 00 3D 09 00 3E 00 \n", + "00040: 3F 08 00 40 07 00 41 0A 00 42 00 43 0A 00 44 00 \n", + "00050: 45 0A 00 14 00 46 0A 00 47 00 48 07 00 49 01 00\n", + " ... \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了打开及读入二进制文件,我们用到了另一个工具 `Files.readAllBytes()`,这已经在 [Files章节](./17-Files.md) 介绍过了。这里的 `readAllBytes()` 方法将整个文件以 `byte` 数组的形式返回。\n", + "\n", + "\n", + "\n", + "## 正则表达式\n", + "很久之前,*正则表达式*就已经整合到标准 Unix 工具集之中,例如 sed、awk 和程序语言之中了,如 Python 和Perl(有些人认为正是正则表达式促成了 Perl 的成功)。而在 Java 中,字符串操作还主要集中于`String`、`StringBuffer` 和 `StringTokenizer` 类。与正则表达式相比较,它们只能提供相当简单的功能。\n", + "\n", + "正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入 `String` 进行搜索。一旦找到了匹配这些模式的部分,你就能随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。正则表达式提供了一种完全通用的方式,能够解决各种 `String` 处理相关的问题:匹配、选择、编辑以及验证。\n", + "### 基础\n", + "一般来说,正则表达式就是以某种方式来描述字符串,因此你可以说:“如果一个字符串含有这些东西,那么它就是我正在找的东西。”例如,要找一个数字,它可能有一个负号在最前面,那你就写一个负号加上一个问号,就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "-?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用 `\\d` 表示一位数字。如果在其他语言中使用过正则表达式,那你可能就能发现 Java 对反斜线 \\ 的不同处理方式。在其他语言中,`\\\\` 表示“我想要在正则表达式中插入一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”而在Java中,`\\\\` 的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式应该是 `\\\\d`。如果你想插入一个普通的反斜线,应该这样写 `\\\\\\`。不过换行符和制表符之类的东西只需要使用单反斜线:`\\n\\t`。 [^2]\n", + "\n", + "要表示“一个或多个之前的表达式”,应该使用 `+`。所以,如果要表示“可能有一个负号,后面跟着一位或多位数字”,可以这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "-?\\\\d+ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "应用正则表达式最简单的途径,就是利用 `String` 类内建的功能。例如,你可以检查一个 `String` 是否匹配如上所述的正则表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/IntegerMatch.java \n", + "\n", + "public class IntegerMatch { \n", + " public static void main(String[] args) { \n", + " System.out.println(\"-1234\".matches(\"-?\\\\d+\")); \n", + " System.out.println(\"5678\".matches(\"-?\\\\d+\")); \n", + " System.out.println(\"+911\".matches(\"-?\\\\d+\")); \n", + " System.out.println(\"+911\".matches(\"(-|\\\\+)?\\\\d+\")); \n", + " }\n", + "}\n", + "/* Output: \n", + "true \n", + "true \n", + "false \n", + "true \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "前两个字符串都满足对应的正则表达式,匹配成功。第三个字符串以 `+` 开头,这也是一个合法的符号,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,用括号将表达式进行分组,用竖线 ` | ` 表示或操作。也就是:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "(-|\\\\+)? " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个正则表达式表示字符串的起始字符可能是一个 `-` 或 `+`,或者二者都没有(因为后面跟着 `?` 修饰符)。因为字符 `+` 在正则表达式中有特殊的意义,所以必须使用 `\\\\` 将其转义,使之成为表达式中的一个普通字符。\n", + "\n", + "`String`类还自带了一个非常有用的正则表达式工具——`split()` 方法,其功能是“将字符串从正则表达式匹配的地方切开。”" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Splitting.java import java.util.*; \n", + "\n", + "public class Splitting {\n", + " public static String knights = \n", + " \"Then, when you have found the shrubbery, \" +\n", + " \"you must cut down the mightiest tree in the \" +\n", + " \"forest...with... a herring!\";\n", + " public static void split(String regex) {\n", + " System.out.println(\n", + " Arrays.toString(knights.split(regex)));\n", + " }\n", + " public static void main(String[] args) {\n", + " split(\" \"); // Doesn't have to contain regex chars\n", + " split(\"\\\\W+\"); // Non-word characters\n", + " split(\"n\\\\W+\"); // 'n' followed by non-words\n", + " }\n", + "}\n", + "/* Output:\n", + "[Then,, when, you, have, found, the, shrubbery,, you,\n", + "must, cut, down, the, mightiest, tree, in, the,\n", + "forest...with..., a, herring!]\n", + "[Then, when, you, have, found, the, shrubbery, you,\n", + "must, cut, down, the, mightiest, tree, in, the, forest,\n", + "with, a, herring]\n", + "[The, whe, you have found the shrubbery, you must cut\n", + "dow, the mightiest tree i, the forest...with... a\n", + "herring!]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中并不包含任何特殊字符。因此第一个 `split()` 只是按空格来划分字符串。\n", + "\n", + "第二个和第三个 `split()` 都用到了 `\\\\W`,它的意思是一个非单词字符(如果 W 小写,`\\\\w`,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个 `split()` 表示“字母 `n` 后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。\n", + "\n", + "`String.split()` 还有一个重载的版本,它允许你限制字符串分割的次数。\n", + "\n", + "用正则表达式进行替换操作时,你可以只替换第一处匹配,也可以替换所有的匹配:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Replacing.java \n", + "\n", + "public class Replacing {\n", + " static String s = Splitting.knights; \n", + " public static void main(String[] args) {\n", + " System.out.println(\n", + " s.replaceFirst(\"f\\\\w+\", \"located\"));\n", + " System.out.println( \n", + " s.replaceAll(\"shrubbery|tree|herring\",\"banana\")); \n", + " } \n", + "}\n", + "/* Output: \n", + "Then, when you have located the shrubbery, you must cut \n", + "down the mightiest tree in the forest...with... a \n", + "herring! \n", + "Then, when you have found the banana, you must cut down\n", + "the mightiest banana in the forest...with... a banana! \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一个表达式要匹配的是,以字母 `f` 开头,后面跟一个或多个字母(注意这里的 `w` 是小写的)。并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。\n", + "\n", + "第二个表达式要匹配的是三个单词中的任意一个,因为它们以竖线分割表示“或”,并且替换所有匹配的部分。\n", + "\n", + "稍后你会看到,`String` 之外的正则表达式还有更强大的替换工具,例如,可以通过方法调用执行替换。而且,如果正则表达式不是只使用一次的话,非 `String` 对象的正则表达式明显具备更佳的性能。\n", + "### 创建正则表达式\n", + "我们首先从正则表达式可能存在的构造集中选取一个很有用的子集,以此开始学习正则表达式。正则表达式的完整构造子列表,请参考JDK文档 `java.util.regex` 包中的 `Pattern`类。\n", + "\n", + "| 表达式 | 含义 |\n", + "| :---- | :---- |\n", + "| `B` | 指定字符`B` |\n", + "| `\\xhh` | 十六进制值为`0xhh`的字符 |\n", + "| `\\uhhhh` | 十六进制表现为`0xhhhh`的Unicode字符 |\n", + "| `\\t` | 制表符Tab |\n", + "| `\\n` | 换行符 |\n", + "| `\\r` | 回车 |\n", + "| `\\f` | 换页 |\n", + "| `\\e` | 转义(Escape) |\n", + "\n", + "当你学会了使用字符类(character classes)之后,正则表达式的威力才能真正显现出来。以下是一些创建字符类的典型方式,以及一些预定义的类:\n", + "\n", + "| 表达式 | 含义 |\n", + "| :---- | :---- |\n", + "| `.` | 任意字符 |\n", + "| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a|b|c`作用相同)|\n", + "| `[^abc]` | 除`a`、`b`和`c`之外的任何字符(否定) |\n", + "| `[a-zA-Z]` | 从`a`到`z`或从`A`到`Z`的任何字符(范围) |\n", + "| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a|b|c|h|i|j`作用相同)(合并) |\n", + "| `[a-z&&[hij]]` | 任意`h`、`i`或`j`(交) |\n", + "| `\\s` | 空白符(空格、tab、换行、换页、回车) |\n", + "| `\\S` | 非空白符(`[^\\s]`) |\n", + "| `\\d` | 数字(`[0-9]`) |\n", + "| `\\D` | 非数字(`[^0-9]`) |\n", + "| `\\w` | 词字符(`[a-zA-Z_0-9]`) |\n", + "| `\\W` | 非词字符(`[^\\w]`) |\n", + "\n", + "这里只列出了部分常用的表达式,你应该将JDK文档中 `java.util.regex.Pattern` 那一页加入浏览器书签中,以便在需要的时候方便查询。\n", + "\n", + "| 逻辑操作符 | 含义 |\n", + "| :----: | :---- |\n", + "| `XY` | `Y`跟在`X`后面 |\n", + "| `X|Y` | `X`或`Y` |\n", + "| `(X)` | 捕获组(capturing group)。可以在表达式中用`\\i`引用第i个捕获组 |\n", + "\n", + "下面是不同的边界匹配符:\n", + "\n", + "| 边界匹配符 | 含义 |\n", + "| :----: | :---- |\n", + "| `^` | 一行的开始 |\n", + "| `$` | 一行的结束 |\n", + "| `\\b` | 词的边界 |\n", + "| `\\B` | 非词的边界 |\n", + "| `\\G` | 前一个匹配的结束 |\n", + "\n", + "作为演示,下面的每一个正则表达式都能成功匹配字符序列“Rudolph”:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Rudolph.java \n", + "\n", + "public class Rudolph { \n", + " public static void main(String[] args) { \n", + " for(String pattern : new String[]{ \n", + " \"Rudolph\", \n", + " \"[rR]udolph\", \n", + " \"[rR][aeiou][a-z]ol.*\", \n", + " \"R.*\" }) \n", + " System.out.println(\"Rudolph\".matches(pattern)); \n", + " } \n", + "} \n", + "/* Output: \n", + "true \n", + "true \n", + "true \n", + "true \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们的目的并不是编写最难理解的正则表达式,而是尽量编写能够完成任务的、最简单以及最必要的正则表达式。一旦真正开始使用正则表达式了,你就会发现,在编写新的表达式之前,你通常会参考代码中已经用到的正则表达式。\n", + "### 量词\n", + "量词描述了一个模式捕获输入文本的方式:\n", + "\n", + "+ **贪婪型**:\n", + "量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。\n", + "\n", + "+ **勉强型**:\n", + "用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也被称作懒惰的、最少匹配的、非贪婪的或不贪婪的。\n", + "\n", + "+ **占有型**:\n", + "目前,这种类型的量词只有在 Java 语言中才可用(在其他语言中不可用),并且也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于 `String` 时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更高效。\n", + "\n", + "| 贪婪型 | 勉强型 | 占有型 | 如何匹配 |\n", + "| ---- | ---- | ---- | ---- |\n", + "| `X?` | `X??` | `X?+` | 一个或零个`X` |\n", + "| `X*` | `X*?` | `X*+` | 零个或多个`X` |\n", + "| `X+` | `X+?` | `X++` | 一个或多个`X` |\n", + "| `X{n}` | `X{n}?` | `X{n}+` | 恰好`n`次`X` |\n", + "| `X{n,}` | `X{n,}?` | `X{n,}+` | 至少`n`次`X` |\n", + "| `X{n,m}` | `X{n,m}?` | `X{n,m}+` | `X`至少`n`次,但不超过`m`次 |\n", + "\n", + "应该非常清楚地意识到,表达式 `X` 通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "abc+" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "看起来它似乎应该匹配1个或多个`abc`序列,如果我们把它应用于输入字符串`abcabcabc`,则实际上会获得3个匹配。然而,这个表达式实际上表示的是:匹配`ab`,后面跟随1个或多个`c`。要表明匹配1个或多个完整的字符串`abc`,我们必须这样表示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "(abc)+" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你会发现,在使用正则表达式时很容易混淆,因为它是一种在 Java 之上的新语言。\n", + "### `CharSequence`\n", + "接口 `CharSequence` 从 `CharBuffer`、`String`、`StringBuffer`、`StringBuilder` 类中抽象出了字符序列的一般化定义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "interface CharSequence { \n", + " char charAt(int i); \n", + " int length();\n", + " CharSequence subSequence(int start, int end);\n", + " String toString(); \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,这些类都实现了该接口。多数正则表达式操作都接受 `CharSequence` 类型参数。\n", + "### `Pattern` 和 `Matcher`\n", + "通常,比起功能有限的 `String` 类,我们更愿意构造功能强大的正则表达式对象。只需导入 `java.util.regex`包,然后用 `static Pattern.compile()` 方法来编译你的正则表达式即可。它会根据你的 `String` 类型的正则表达式生成一个 `Pattern` 对象。接下来,把你想要检索的字符串传入 `Pattern` 对象的 `matcher()` 方法。`matcher()` 方法会生成一个 `Matcher` 对象,它有很多功能可用(可以参考 `java.util.regext.Matcher` 的 JDK 文档)。例如,它的 `replaceAll()` 方法能将所有匹配的部分都替换成你传入的参数。\n", + "\n", + "作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串。第一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输入的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须用引号括起来。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。[^3]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/TestRegularExpression.java \n", + "// Simple regular expression demonstration \n", + "// {java TestRegularExpression \n", + "// abcabcabcdefabc \"abc+\" \"(abc)+\" } \n", + "import java.util.regex.*; \n", + "\n", + "public class TestRegularExpression {\n", + " public static void main(String[] args) { \n", + " if(args.length < 2) { \n", + " System.out.println( \n", + " \"Usage:\\njava TestRegularExpression \" + \n", + " \"characterSequence regularExpression+\"); \n", + " System.exit(0); \n", + " }\n", + " System.out.println(\"Input: \\\"\" + args[0] + \"\\\"\"); \n", + " for(String arg : args) { \n", + " System.out.println( \n", + " \"Regular expression: \\\"\" + arg + \"\\\"\"); \n", + " Pattern p = Pattern.compile(arg); \n", + " Matcher m = p.matcher(args[0]); \n", + " while(m.find()) { \n", + " System.out.println( \n", + " \"Match \\\"\" + m.group() + \"\\\" at positions \" + \n", + " m.start() + \"-\" + (m.end() - 1)); \n", + " } \n", + " } \n", + " }\n", + "}\n", + "/* Output: \n", + "Input: \"abcabcabcdefabc\" \n", + "Regular expression: \"abcabcabcdefabc\" \n", + "Match \"abcabcabcdefabc\" at positions 0-14 \n", + "Regular expression: \"abc+\" \n", + "Match \"abc\" at positions 0-2 \n", + "Match \"abc\" at positions 3-5 \n", + "Match \"abc\" at positions 6-8 \n", + "Match \"abc\" at positions 12-14 \n", + "Regular expression: \"(abc)+\"\n", + "Match \"abcabcabc\" at positions 0-8 \n", + "Match \"abc\" at positions 12-14 \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "还可以在控制台参数中加入`“(abc){2,}”`,看看执行结果。\n", + "\n", + "`Pattern` 对象表示编译后的正则表达式。从这个例子可以看到,我们使用已编译的 `Pattern` 对象上的 `matcher()` 方法,加上一个输入字符串,从而共同构造了一个 `Matcher` 对象。同时,`Pattern` 类还提供了一个`static`方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "static boolean matches(String regex, CharSequence input)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "该方法用以检查 `regex` 是否匹配整个 `CharSequence` 类型的 `input` 参数。编译后的 `Pattern` 对象还提供了 `split()` 方法,它从匹配了 `regex` 的地方分割输入字符串,返回分割后的子字符串 `String` 数组。\n", + "\n", + "通过调用 `Pattern.matcher()` 方法,并传入一个字符串参数,我们得到了一个 `Matcher` 对象。使用 `Matcher` 上的方法,我们将能够判断各种不同类型的匹配是否成功:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "boolean matches() \n", + "boolean lookingAt() \n", + "boolean find() \n", + "boolean find(int start)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中的 `matches()` 方法用来判断整个输入字符串是否匹配正则表达式模式,而 `lookingAt()` 则用来判断该字符串(不必是整个字符串)的起始部分是否能够匹配模式。\n", + "### `find()`\n", + "`Matcher.find()` 方法可用来在 `CharSequence` 中查找多个匹配。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Finding.java \n", + "import java.util.regex.*; \n", + "\n", + "public class Finding { \n", + " public static void main(String[] args) { \n", + " Matcher m = Pattern.compile(\"\\\\w+\") \n", + " .matcher( \n", + " \"Evening is full of the linnet's wings\"); \n", + " while(m.find()) \n", + " System.out.print(m.group() + \" \"); \n", + " System.out.println(); \n", + " int i = 0; \n", + " while(m.find(i)) { \n", + " System.out.print(m.group() + \" \"); \n", + " i++; \n", + " } \n", + " }\n", + "}\n", + "/* Output: \n", + "Evening is full of the linnet s wings\n", + "Evening vening ening ning ing ng g is is s full full \n", + "ull ll l of of f the the he e linnet linnet innet nnet \n", + "net et t s s wings wings ings ngs gs s \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "模式 `\\\\w+` 将字符串划分为词。`find()` 方法像迭代器那样向前遍历输入字符串。而第二个重载的 `find()` 接收一个整型参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果可以看出,后一个版本的 `find()` 方法能够根据其参数的值,不断重新设定搜索的起始位置。\n", + "### 组(Groups)\n", + "组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示整个表达式,组号 1 表示被第一对括号括起来的组,以此类推。因此,下面这个表达式," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "A(B(C))D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "中有三个组:组 0 是 `ABCD`,组 1 是 `BC`,组 2 是 `C`。\n", + "\n", + "`Matcher` 对象提供了一系列方法,用以获取与组相关的信息:\n", + "\n", + "+ `public int groupCount()` 返回该匹配器的模式中的分组数目,组 0 不包括在内。\n", + "+ `public String group()` 返回前一次匹配操作(例如 `find()`)的第 0 组(整个匹配)。\n", + "+ `public String group(int i)` 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回 `null`。\n", + "+ `public int start(int group)` 返回在前一次匹配操作中寻找到的组的起始索引。\n", + "+ `public int end(int group)` 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。\n", + "\n", + "下面是正则表达式组的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Groups.java\n", + "import java.util.regex.*; \n", + "\n", + "public class Groups { \n", + " public static final String POEM = \n", + " \"Twas brillig, and the slithy toves\\n\" + \n", + " \"Did gyre and gimble in the wabe.\\n\" + \n", + " \"All mimsy were the borogoves,\\n\" + \n", + " \"And the mome raths outgrabe.\\n\\n\" + \n", + " \"Beware the Jabberwock, my son,\\n\" + \n", + " \"The jaws that bite, the claws that catch.\\n\" + \n", + " \"Beware the Jubjub bird, and shun\\n\" + \n", + " \"The frumious Bandersnatch.\"; \n", + " public static void main(String[] args) { \n", + " Matcher m = Pattern.compile(\n", + " \"(?m)(\\\\S+)\\\\s+((\\\\S+)\\\\s+(\\\\S+))$\") \n", + " .matcher(POEM); \n", + " while(m.find()) { \n", + " for(int j = 0; j <= m.groupCount(); j++) \n", + " System.out.print(\"[\" + m.group(j) + \"]\"); \n", + " System.out.println(); \n", + " } \n", + " } \n", + "}\n", + "/* Output: \n", + "[the slithy toves][the][slithy toves][slithy][toves] \n", + "[in the wabe.][in][the wabe.][the][wabe.] \n", + "[were the borogoves,][were][the \n", + "borogoves,][the][borogoves,] \n", + "[mome raths outgrabe.][mome][raths \n", + "outgrabe.][raths][outgrabe.] \n", + "[Jabberwock, my son,][Jabberwock,][my son,][my][son,] \n", + "[claws that catch.][claws][that catch.][that][catch.] \n", + "[bird, and shun][bird,][and shun][and][shun] \n", + "[The frumious Bandersnatch.][The][frumious \n", + "Bandersnatch.][frumious][Bandersnatch.] \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这首诗来自于 Lewis Carroll 所写的 *Through the Looking Glass* 中的 “Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(`\\\\S+`)及随后的任意数目的空白符(`\\\\s+`)所组成。目的是捕获每行的最后3个词,每行最后以 `\\$` 结束。不过,在正常情况下是将 `\\$` 与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记 `(?m)` 来完成(模式标记马上就会介绍)。\n", + "### `start()` 和 `end()`\n", + "在匹配操作成功之后,`start()` 返回先前匹配的起始位置的索引,而 `end()` 返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用 `start()` 或 `end()` 将会产生 `IllegalStateException`。下面的示例还同时展示了 `matches()` 和 `lookingAt()` 的用法 [^4]:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/StartEnd.java \n", + "import java.util.regex.*; \n", + "\n", + "public class StartEnd {\n", + " public static String input =\n", + " \"As long as there is injustice, whenever a\\n\" + \n", + " \"Targathian baby cries out, wherever a distress\\n\" + \n", + " \"signal sounds among the stars \" + \n", + " \"... We'll be there.\\n\"+ \n", + " \"This fine ship, and this fine crew ...\\n\" + \n", + " \"Never give up! Never surrender!\"; \n", + " private static class Display {\n", + " private boolean regexPrinted = false; \n", + " private String regex;\n", + " Display(String regex) { this.regex = regex; } \n", + " \n", + " void display(String message) { \n", + " if(!regexPrinted) { \n", + " System.out.println(regex); \n", + " regexPrinted = true; \n", + " } \n", + " System.out.println(message); \n", + " } \n", + " } \n", + " \n", + " static void examine(String s, String regex) { \n", + " Display d = new Display(regex); \n", + " Pattern p = Pattern.compile(regex); \n", + " Matcher m = p.matcher(s); \n", + " while(m.find()) \n", + " d.display(\"find() '\" + m.group() + \n", + " \"' start = \"+ m.start() + \" end = \" + m.end()); \n", + " if(m.lookingAt()) // No reset() necessary \n", + " d.display(\"lookingAt() start = \" \n", + " + m.start() + \" end = \" + m.end()); \n", + " if(m.matches()) // No reset() necessary \n", + " d.display(\"matches() start = \" \n", + " + m.start() + \" end = \" + m.end()); \n", + " }\n", + " \n", + " public static void main(String[] args) { \n", + " for(String in : input.split(\"\\n\")) { \n", + " System.out.println(\"input : \" + in); \n", + " for(String regex : new String[]{\"\\\\w*ere\\\\w*\", \n", + " \"\\\\w*ever\", \"T\\\\w+\", \"Never.*?!\"}) \n", + " examine(in, regex); \n", + " } \n", + " } \n", + "} \n", + "/* Output: \n", + "input : As long as there is injustice, whenever a \n", + "\\w*ere\\w* \n", + "find() 'there' start = 11 end = 16 \n", + "\\w*ever \n", + "find() 'whenever' start = 31 end = 39 \n", + "input : Targathian baby cries out, wherever a distress \n", + "\\w*ere\\w* \n", + "find() 'wherever' start = 27 end = 35 \n", + "\\w*ever \n", + "find() 'wherever' start = 27 end = 35 \n", + "T\\w+ find() 'Targathian' start = 0 end = 10 \n", + "lookingAt() start = 0 end = 10 \n", + "input : signal sounds among the stars ... We'll be \n", + "there. \n", + "\\w*ere\\w* \n", + "find() 'there' start = 43 end = 48 \n", + "input : This fine ship, and this fine crew ... \n", + "T\\w+ find() 'This' start = 0 end = 4\n", + "lookingAt() start = 0 end = 4 \n", + "input : Never give up! Never surrender! \n", + "\\w*ever \n", + "find() 'Never' start = 0 end = 5 \n", + "find() 'Never' start = 15 end = 20 \n", + "lookingAt() start = 0 end = 5 \n", + "Never.*?! \n", + "find() 'Never give up!' start = 0 end = 14 \n", + "find() 'Never surrender!' start = 15 end = 31 \n", + "lookingAt() start = 0 end = 14 \n", + "matches() start = 0 end = 31 \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,`find()` 可以在输入的任意位置定位正则表达式,而 `lookingAt()` 和 `matches()` 只有在正则表达式与输入的最开始处就开始匹配时才会成功。`matches()` 只有在整个输入都匹配正则表达式时才会成功,而 `lookingAt()` [^5] 只要输入的第一部分匹配就会成功。\n", + "### `Pattern` 标记\n", + "`Pattern` 类的 `compile()` 方法还有另一个版本,它接受一个标记参数,以调整匹配行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Pattern Pattern.compile(String regex, int flag)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中的 `flag` 来自以下 `Pattern` 类中的常量\n", + "\n", + "| 编译标记 | 效果 |\n", + "| ---- |---- |\n", + "| `Pattern.CANON_EQ` | 当且仅当两个字符的完全规范分解相匹配时,才认为它们是匹配的。例如,如果我们指定这个标记,表达式`\\u003F`就会匹配字符串`?`。默认情况下,匹配不考虑规范的等价性 |\n", + "| `Pattern.CASE_INSENSITIVE(?i)` | 默认情况下,大小写不敏感的匹配假定只有US-ASCII字符集中的字符才能进行。这个标记允许模式匹配不考虑大小写(大写或小写)。通过指定`UNICODE_CASE`标记及结合此标记。基于Unicode的大小写不敏感的匹配就可以开启了 |\n", + "| `Pattern.COMMENTS(?x)` | 在这种模式下,空格符将被忽略掉,并且以`#`开始直到行末的注释也会被忽略掉。通过嵌入的标记表达式也可以开启Unix的行模式 |\n", + "| `Pattern.DOTALL(?s)` | 在dotall模式下,表达式`.`匹配所有字符,包括行终止符。默认情况下,`.`不会匹配行终止符 |\n", + "| `Pattern.MULTILINE(?m)` | 在多行模式下,表达式`^`和`$`分别匹配一行的开始和结束。`^`还匹配输入字符串的开始,而`$`还匹配输入字符串的结尾。默认情况下,这些表达式仅匹配输入的完整字符串的开始和结束 |\n", + "| `Pattern.UNICODE_CASE(?u)` | 当指定这个标记,并且开启`CASE_INSENSITIVE`时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行 |\n", + "| `Pattern.UNIX_LINES(?d)` | 在这种模式下,在`.`、`^`和`$`的行为中,只识别行终止符`\\n` |\n", + "\n", + "在这些标记中,`Pattern.CASE_INSENSITIVE`、`Pattern.MULTILINE` 以及 `Pattern.COMMENTS`(对声明或文档有用)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。\n", + "\n", + "你还可以通过“或”(`|`)操作符组合多个标记的功能:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ReFlags.java \n", + "import java.util.regex.*; \n", + "\n", + "public class ReFlags { \n", + " public static void main(String[] args) { \n", + " Pattern p = Pattern.compile(\"^java\", \n", + " Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); \n", + " Matcher m = p.matcher( \n", + " \"java has regex\\nJava has regex\\n\" + \n", + " \"JAVA has pretty good regular expressions\\n\" + \n", + " \"Regular expressions are in Java\"); \n", + " while(m.find()) \n", + " System.out.println(m.group()); \n", + " } \n", + "}\n", + "/* Output: \n", + "java \n", + "Java \n", + "JAVA \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一行(从字符序列的第一个字符开始,至每一个行终止符)都进行匹配。注意,`group()` 方法只返回已匹配的部分。\n", + "### `split()`\n", + "`split()`方法将输入 `String` 断开成 `String` 对象数组,断开边界由正则表达式确定:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "String[] split(CharSequence input) \n", + "String[] split(CharSequence input, int limit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个快速而方便的方法,可以按照通用边界断开输入文本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/SplitDemo.java \n", + "import java.util.regex.*; \n", + "import java.util.*; \n", + "\n", + "public class SplitDemo { \n", + " public static void main(String[] args) { \n", + " String input = \n", + " \"This!!unusual use!!of exclamation!!points\"; \n", + " System.out.println(Arrays.toString( \n", + " Pattern.compile(\"!!\").split(input))); \n", + " // Only do the first three: \n", + " System.out.println(Arrays.toString( \n", + " Pattern.compile(\"!!\").split(input, 3))); \n", + " }\n", + "}\n", + "/* Output: \n", + "[This, unusual use, of exclamation, points] \n", + "[This, unusual use, of exclamation!!points]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第二种形式的 `split()` 方法可以限制将输入分割成字符串的数量。\n", + "### 替换操作\n", + "正则表达式在进行文本替换时特别方便,它提供了许多方法:\n", + "+ `replaceFirst(String replacement)` 以参数字符串 `replacement` 替换掉第一个匹配成功的部分。\n", + "+ `replaceAll(String replacement)` 以参数字符串 `replacement` 替换所有匹配成功的部分。\n", + "+ `appendReplacement(StringBuffer sbuf, String replacement)` 执行渐进式的替换,而不是像 `replaceFirst()` 和 `replaceAll()` 那样只替换第一个匹配或全部匹配。这是一个非常重要的方法。它允许你调用其他方法来生成或处理 `replacement`(`replaceFirst()` 和 `replaceAll()` 则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。\n", + "+ `appendTail(StringBuffer sbuf)` 在执行了一次或多次 `appendReplacement()` 之后,调用此方法可以将输入字符串余下的部分复制到 `sbuf` 中。\n", + "\n", + "下面的程序演示了如何使用这些替换方法。开头部分注释掉的文本,就是正则表达式要处理的输入字符串:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/TheReplacements.java \n", + "import java.util.regex.*; \n", + "import java.nio.file.*; \n", + "import java.util.stream.*;\n", + "\n", + "/*! Here's a block of text to use as input to \n", + " the regular expression matcher. Note that we \n", + " first extract the block of text by looking for \n", + " the special delimiters, then process the \n", + " extracted block. !*/\n", + "\n", + "public class TheReplacements { \n", + " public static void main(String[] args) throws Exception { \n", + " String s = Files.lines( \n", + " Paths.get(\"TheReplacements.java\")) \n", + " .collect(Collectors.joining(\"\\n\")); \n", + " // Match specially commented block of text above: \n", + " Matcher mInput = Pattern.compile( \n", + " \"/\\\\*!(.*)!\\\\*/\", Pattern.DOTALL).matcher(s); \n", + " if(mInput.find()) \n", + " s = mInput.group(1); // Captured by parentheses \n", + " // Replace two or more spaces with a single space: \n", + " s = s.replaceAll(\" {2,}\", \" \"); \n", + " // Replace 1+ spaces at the beginning of each \n", + " // line with no spaces. Must enable MULTILINE mode: \n", + " s = s.replaceAll(\"(?m)^ +\", \"\"); \n", + " System.out.println(s); \n", + " s = s.replaceFirst(\"[aeiou]\", \"(VOWEL1)\"); \n", + " StringBuffer sbuf = new StringBuffer(); \n", + " Pattern p = Pattern.compile(\"[aeiou]\"); \n", + " Matcher m = p.matcher(s); \n", + " // Process the find information as you \n", + " // perform the replacements: \n", + " while(m.find()) \n", + " m.appendReplacement(sbuf, m.group().toUpperCase()); \n", + " // Put in the remainder of the text: \n", + " m.appendTail(sbuf); \n", + " System.out.println(sbuf);\n", + " } \n", + "}\n", + "/* Output: \n", + "Here's a block of text to use as input to \n", + "the regular expression matcher. Note that we \n", + "first extract the block of text by looking for \n", + "the special delimiters, then process the \n", + "extracted block. \n", + "H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO \n", + "thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE \n", + "fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr \n", + "thE spEcIAl dElImItErs, thEn prOcEss thE \n", + "ExtrActEd blOck. \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此处使用上一章介绍过的 [`Files`](./17-Files.md) 类打开并读入文件。`Files.lines()` 返回一个 `Stream` 对象,包含读入的所有行,`Collectors.joining()` 在每一行的结尾追加参数字符序列,最终拼接成一个 `String` 对象。\n", + "\n", + "`mInput` 匹配 `/*!` 和 `!*/` 之间的所有文字(注意分组的括号)。接下来,将存在两个或两个以上空格的地方,缩减为一个空格,并且删除每行开头部分的所有空格(为了使每一行都达到这个效果,而不仅仅是删除文本开头部分的空格,这里特意开启了多行模式)。这两个替换操作所使用的的 `replaceAll()` 是 `String` 对象自带的方法,在这里,使用此方法更方便。注意,因为这两个替换操作都只使用了一次 `replaceAll()`,所以,与其编译为 `Pattern`,不如直接使用 `String` 的 `replaceAll()` 方法,而且开销也更小些。\n", + "\n", + "`replaceFirst()` 只对找到的第一个匹配进行替换。此外,`replaceFirst()` 和 `replaceAll()` 方法用来替换的只是普通字符串,所以,如果想对这些替换字符串进行某些特殊处理,这两个方法时无法胜任的。如果你想要那么做,就应该使用 `appendReplacement()` 方法。该方法允许你在执行替换的过程中,操作用来替换的字符串。在这个例子中,先构造了 `sbuf` 用来保存最终结果,然后用 `group()` 选择一个组,并对其进行处理,将正则表达式找到的元音字母替换成大些字母。一般情况下,你应该遍历执行所有的替换操作,然后再调用 `appendTail()` 方法,但是,如果你想模拟 `replaceFirst()`(或替换n次)的行为,那就只需要执行一次替换,然后调用 `appendTail()` 方法,将剩余未处理的部分存入 `sbuf` 即可。\n", + "\n", + "同时,`appendReplacement()` 方法还允许你通过 `\\$g` 直接找到匹配的某个组,这里的 `g` 就是组号。然而,它只能应付一些简单的处理,无法实现类似前面这个例子中的功能。\n", + "### `reset()`\n", + "通过 `reset()` 方法,可以将现有的 `Matcher` 对象应用于一个新的字符序列:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/Resetting.java \n", + "import java.util.regex.*; \n", + "\n", + "public class Resetting { \n", + " public static void main(String[] args) throws Exception { \n", + " Matcher m = Pattern.compile(\"[frb][aiu][gx]\") \n", + " .matcher(\"fix the rug with bags\"); \n", + " while(m.find()) \n", + " System.out.print(m.group() + \" \"); \n", + " System.out.println(); \n", + " m.reset(\"fix the rig with rags\"); \n", + " while(m.find()) \n", + " System.out.print(m.group() + \" \"); \n", + " } \n", + "} \n", + "/* Output: \n", + "fix rug bag \n", + "fix rig rag \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用不带参数的 `reset()` 方法,可以将 `Matcher` 对象重新设置到当前字符序列的起始位置。\n", + "### 正则表达式与 Java I/O\n", + "到目前为止,我们看到的例子都是将正则表达式用于静态的字符串。下面的例子将向你演示,如何应用正则表达式在一个文件中进行搜索匹配操作。`JGrep.java` 的灵感源自于 Unix 上的 *grep*。它有两个参数:文件名以及要匹配的正则表达式。输出的是每行有匹配的部分以及匹配部分在行中的位置。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/JGrep.java \n", + "// A very simple version of the \"grep\" program \n", + "// {java JGrep \n", + "// WhitherStringBuilder.java 'return|for|String'} \n", + "import java.util.regex.*; \n", + "import java.nio.file.*; \n", + "import java.util.stream.*;\n", + "\n", + "public class JGrep {  \n", + " public static void main(String[] args) throws Exception {    \n", + " if(args.length < 2) {      \n", + " System.out.println(        \n", + " \"Usage: java JGrep file regex\");      \n", + " System.exit(0);   \n", + " }    \n", + " Pattern p = Pattern.compile(args[1]);    \n", + " // Iterate through the lines of the input file:    \n", + " int index = 0;    \n", + " Matcher m = p.matcher(\"\");    \n", + " for(String line: Files.readAllLines(Paths.get(args[0]))) {      \n", + " m.reset(line);      \n", + " while(m.find())        \n", + " System.out.println(index++ + \": \" +          \n", + " m.group() + \": \" + m.start());   \n", + " } \n", + " } \n", + "} \n", + "/* Output: \n", + "0: for: 4 \n", + "1: for: 4 \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Files.readAllLines()` 返回一个 `List` 对象,这意味着可以用 *for-in* 进行遍历。虽然可以在 `for` 循环内部创建一个新的 `Matcher` 对象,但是,在循环体外创建一个空的 `Matcher` 对象,然后用 `reset()` 方法每次为 `Matcher` 加载一行输入,这种处理会有一定的性能优化。最后用 `find()` 搜索结果。\n", + "\n", + "这里读入的测试参数是 `JGrep.java` 文件,然后搜索以 `[Ssct]` 开头的单词。\n", + "\n", + "如果想要更深入地学习正则表达式,你可以阅读 Jeffrey E. F. Friedl 的《精通正则表达式(第2版)》。网络上也有很多正则表达式的介绍,你还可以从 Perl 和 Python 等其他语言的文档中找到有用的信息。\n", + "\n", + "\n", + "\n", + "## 扫描输入\n", + "到目前为止,从文件或标准输入读取数据还是一件相当痛苦的事情。一般的解决办法就是读入一行文本,对其进行分词,然后使用 `Integer`、`Double` 等类的各种解析方法来解析数据:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/SimpleRead.java \n", + "import java.io.*;\n", + "\n", + "public class SimpleRead {  \n", + " public static BufferedReader input =    \n", + " new BufferedReader(new StringReader(    \n", + " \"Sir Robin of Camelot\\n22 1.61803\"));  \n", + " public static void main(String[] args) {    \n", + " try {      \n", + " System.out.println(\"What is your name?\");      \n", + " String name = input.readLine();      \n", + " System.out.println(name);      \n", + " System.out.println(\"How old are you? \" +        \n", + " \"What is your favorite double?\");      \n", + " System.out.println(\"(input: )\");      \n", + " String numbers = input.readLine();      \n", + " System.out.println(numbers);      \n", + " String[] numArray = numbers.split(\" \");      \n", + " int age = Integer.parseInt(numArray[0]);      \n", + " double favorite = Double.parseDouble(numArray[1]);      \n", + " System.out.format(\"Hi %s.%n\", name);      \n", + " System.out.format(\"In 5 years you will be %d.%n\", age + 5);      \n", + " System.out.format(\"My favorite double is %f.\", favorite / 2);   \n", + " } catch(IOException e) {      \n", + " System.err.println(\"I/O exception\");   \n", + " } \n", + " } \n", + "}\n", + "/* Output: \n", + "What is your name? \n", + "Sir Robin of Camelot \n", + "How old are you? What is your favorite double? \n", + "(input: ) \n", + "22 1.61803\n", + "Hi Sir Robin of Camelot. \n", + "In 5 years you will be 27. \n", + "My favorite double is 0.809015. \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`input` 字段使用的类来自 `java.io`,[附录:流式 I/O](./Appendix-IO-Streams.md) 详细介绍了相关内容。`StringReader` 将 `String` 转化为可读的流对象,然后用这个对象来构造 `BufferedReader` 对象,因为我们要使用 `BufferedReader` 的 `readLine()` 方法。最终,我们可以使用 `input` 对象一次读取一行文本,就像从控制台读入标准输入一样。\n", + "\n", + "`readLine()` 方法将一行输入转为 `String` 对象。如果每一行数据正好对应一个输入值,那这个方法也还可行。但是,如果两个输入值在同一行中,事情就不好办了,我们必须分解这个行,才能分别解析所需的输入值。在这个例子中,分解的操作发生在创建 `numArray`时。\n", + "\n", + "终于,Java SE5 新增了 `Scanner` 类,它可以大大减轻扫描输入的工作负担:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/BetterRead.java \n", + "import java.util.*; \n", + "\n", + "public class BetterRead {\n", + " public static void main(String[] args) {\n", + " Scanner stdin = new Scanner(SimpleRead.input);\n", + " System.out.println(\"What is your name?\");\n", + " String name = stdin.nextLine();\n", + " System.out.println(name);\n", + " System.out.println(\n", + " \"How old are you? What is your favorite double?\");\n", + " System.out.println(\"(input: )\");\n", + " int age = stdin.nextInt();\n", + " double favorite = stdin.nextDouble();\n", + " System.out.println(age);\n", + " System.out.println(favorite);\n", + " System.out.format(\"Hi %s.%n\", name);\n", + " System.out.format(\"In 5 years you will be %d.%n\",\n", + " age + 5);\n", + " System.out.format(\"My favorite double is %f.\",\n", + " favorite / 2);\n", + " }\n", + "}\n", + "/* Output: \n", + "What is your name? \n", + "Sir Robin of Camelot \n", + "How old are you? What is your favorite double? \n", + "(input: ) \n", + "22 \n", + "1.61803 \n", + "Hi Sir Robin of Camelot. \n", + "In 5 years you will be 27. \n", + "My favorite double is 0.809015. \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Scanner` 的构造器可以接收任意类型的输入对象,包括 `File`、`InputStream`、`String` 或者像此例中的`Readable` 实现类。`Readable` 是 Java SE5 中新加入的一个接口,表示“具有 `read()` 方法的某种东西”。上一个例子中的 `BufferedReader` 也归于这一类。\n", + "\n", + "有了 `Scanner`,所有的输入、分词、以及解析的操作都隐藏在不同类型的 `next` 方法中。普通的 `next()` 方法返回下一个 `String`。所有的基本类型(除 `char` 之外)都有对应的 `next` 方法,包括 `BigDecimal` 和 `BigInteger`。所有的 next 方法,只有在找到一个完整的分词之后才会返回。`Scanner` 还有相应的 `hasNext` 方法,用以判断下一个输入分词是否是所需的类型,如果是则返回 `true`。\n", + "\n", + "在 `BetterRead.java` 中没有用 `try` 区块捕获`IOException`。因为,`Scanner` 有一个假设,在输入结束时会抛出 `IOException`,所以 `Scanner` 会把 `IOException` 吞掉。不过,通过 `ioException()` 方法,你可以找到最近发生的异常,因此,你可以在必要时检查它。\n", + "### `Scanner` 分隔符\n", + "默认情况下,`Scanner` 根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的分隔符:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ScannerDelimiter.java \n", + "import java.util.*;\n", + "public class ScannerDelimiter {  \n", + " public static void main(String[] args) {    \n", + " Scanner scanner = new Scanner(\"12, 42, 78, 99, 42\");    \n", + " scanner.useDelimiter(\"\\\\s*,\\\\s*\");    \n", + " while(scanner.hasNextInt())    \n", + " System.out.println(scanner.nextInt()); \n", + " } \n", + "}\n", + "/* Output: \n", + "12 \n", + "42 \n", + "78 \n", + "99 \n", + "42 \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个例子使用逗号(包括逗号前后任意的空白字符)作为分隔符,同样的技术也可以用来读取逗号分隔的文件。我们可以用 `useDelimiter()` 来设置分隔符,同时,还有一个 `delimiter()` 方法,用来返回当前正在作为分隔符使用的 `Pattern` 对象。\n", + "### 用正则表达式扫描\n", + "除了能够扫描基本类型之外,你还可以使用自定义的正则表达式进行扫描,这在扫描复杂数据时非常有用。下面的例子将扫描一个防火墙日志文件中的威胁数据:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ThreatAnalyzer.java \n", + "import java.util.regex.*; \n", + "import java.util.*;\n", + "public class ThreatAnalyzer { \n", + " static String threatData =    \n", + " \"58.27.82.161@08/10/2015\\n\" +   \n", + " \"204.45.234.40@08/11/2015\\n\" +    \n", + " \"58.27.82.161@08/11/2015\\n\" +    \n", + " \"58.27.82.161@08/12/2015\\n\" +    \n", + " \"58.27.82.161@08/12/2015\\n\" +\n", + "     \"[Next log section with different data format]\";  \n", + " public static void main(String[] args) { \n", + " Scanner scanner = new Scanner(threatData);    \n", + " String pattern = \"(\\\\d+[.]\\\\d+[.]\\\\d+[.]\\\\d+)@\" +      \n", + " \"(\\\\d{2}/\\\\d{2}/\\\\d{4})\";    \n", + " while(scanner.hasNext(pattern)) {      \n", + " scanner.next(pattern);      \n", + " MatchResult match = scanner.match();      \n", + " String ip = match.group(1);      \n", + " String date = match.group(2);      \n", + " System.out.format(        \n", + " \"Threat on %s from %s%n\", date,ip);   \n", + " } \n", + " } \n", + "} \n", + "/* Output: \n", + "Threat on 08/10/2015 from 58.27.82.161 \n", + "Threat on 08/11/2015 from 204.45.234.40 \n", + "Threat on 08/11/2015 from 58.27.82.161 \n", + "Threat on 08/12/2015 from 58.27.82.161 \n", + "Threat on 08/12/2015 from 58.27.82.161 \n", + "*/ " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当 `next()` 方法配合指定的正则表达式使用时,将找到下一个匹配该模式的输入部分,调用 `match()` 方法就可以获得匹配的结果。如上所示,它的工作方式与之前看到的正则表达式匹配相似。\n", + "\n", + "在配合正则表达式使用扫描时,有一点需要注意:它仅仅针对下一个输入分词进行匹配,如果你的正则表达式中含有分隔符,那永远不可能匹配成功。\n", + "\n", + "\n", + "\n", + "## StringTokenizer类\n", + "在 Java 引入正则表达式(J2SE1.4)和 `Scanner` 类(Java SE5)之前,分割字符串的唯一方法是使用 `StringTokenizer` 来分词。不过,现在有了正则表达式和 `Scanner`,我们可以使用更加简单、更加简洁的方式来完成同样的工作了。下面的例子中,我们将 `StringTokenizer` 与另外两种技术简单地做了一个比较:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// strings/ReplacingStringTokenizer.java \n", + "import java.util.*; \n", + "\n", + "public class ReplacingStringTokenizer { \n", + " public static void main(String[] args) { \n", + " String input = \n", + " \"But I'm not dead yet! I feel happy!\"; \n", + " StringTokenizer stoke = new StringTokenizer(input); \n", + " while(stoke.hasMoreElements()) \n", + " System.out.print(stoke.nextToken() + \" \"); \n", + " System.out.println(); \n", + " System.out.println(Arrays.toString(input.split(\" \"))); \n", + " Scanner scanner = new Scanner(input); \n", + " while(scanner.hasNext()) \n", + " System.out.print(scanner.next() + \" \"); \n", + " }\n", + "} \n", + "/* Output: \n", + "But I'm not dead yet! I feel happy! \n", + "[But, I'm, not, dead, yet!, I, feel, happy!] \n", + "But I'm not dead yet! I feel happy! \n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用正则表达式或 `Scanner` 对象,我们能够以更加复杂的模式来分割一个字符串,而这对于 `StringTokenizer` 来说就很困难了。基本上,我们可以放心地说,`StringTokenizer` 已经可以废弃不用了。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "过去,Java 对于字符串操作的技术相当不完善。不过随着近几个版本的升级,我们可以看到,Java 已经从其他语言中吸取了许多成熟的经验。到目前为止,它对字符串操作的支持已经很完善了。不过,有时你还要在细节上注意效率问题,例如恰当地使用 `StringBuilder` 等。\n", + "\n", + "\n", + "[^1]: C++允许编程人员任意重载操作符。这通常是很复杂的过程(参见Prentice Hall于2000年编写的《Thinking in C++(第2版)》第10章),因此Java设计者认为这是很糟糕的功能,不应该纳入到Java中。起始重载操作符并没有糟糕到只能自己去重载的地步,但具有讽刺意味的是,与C++相比,在Java中使用操作符重载要容易得多。这一点可以在Python(参见[www.Python.org](http://www.python.org))和C#中看到,它们都有垃圾回收机制,操作符重载也简单易懂。\n", + "\n", + "\n", + "[^2]: Java并非在一开始就支持正则表达式,因此这个令人费解的语法是硬塞进来的。\n", + "\n", + "\n", + "[^3]: 网上还有很多实用并且成熟的正则表达式工具。\n", + "\n", + "\n", + "[^4]: input来自于[Galaxy Quest](https://en.wikipedia.org/wiki/Galaxy_Quest)中Taggart司令的一篇演讲。\n", + "\n", + "\n", + "[^5]: 我不知道他们是如何想出这个方法名的,或者它到底指的什么。这只是代码审查很重要的原因之一。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/19-Type-Information.ipynb b/jupyter/19-Type-Information.ipynb new file mode 100644 index 00000000..5c13de9a --- /dev/null +++ b/jupyter/19-Type-Information.ipynb @@ -0,0 +1,3701 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第十九章 类型信息\n", + "\n", + "> RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息\n", + "\n", + "RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。对 RTTI 的需要,揭示了面向对象设计中许多有趣(并且复杂)的特性,同时也带来了关于如何组织程序的基本问题。\n", + "\n", + "本章将讨论 Java 是如何在运行时识别对象和类信息的。主要有两种方式:\n", + "\n", + "1. “传统的” RTTI:假定我们在编译时已经知道了所有的类型;\n", + "2. “反射”机制:允许我们在运行时发现和使用类的信息。\n", + "\n", + "\n", + "\n", + "## 为什么需要 RTTI\n", + "\n", + "下面看一下我们已经很熟悉的一个例子,它使用了多态的类层次结构。基类 `Shape` 是泛化的类型,从它派生出了三个具体类: `Circle` 、`Square` 和 `Triangle`(见下图所示)。\n", + "\n", + "![多态例子Shape的类层次结构图](../images/image-20190409114913825-4781754.png)\n", + "\n", + "这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从 `Shape` 派生出 `Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape` 接口中动态绑定了 `draw()` 方法,这样做的目的就是让客户端程序员可以使用泛化的 `Shape` 引用来调用 `draw()`。`draw()` 方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以即使通过 `Shape` 引用来调用它,也能产生恰当的行为,这就是多态。\n", + "\n", + "因此,我们通常会创建一个具体的对象(`Circle`、`Square` 或者 `Triangle`),把它向上转型成 `Shape` (忽略对象的具体类型),并且在后面的程序中使用 `Shape` 引用来调用在具体对象中被重载的方法(如 `draw()`)。\n", + "\n", + "代码如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Shapes.java\n", + "import java.util.stream.*;\n", + "\n", + "abstract class Shape {\n", + " void draw() { System.out.println(this + \".draw()\"); }\n", + " @Override\n", + " public abstract String toString();\n", + "}\n", + "\n", + "class Circle extends Shape {\n", + " @Override\n", + " public String toString() { return \"Circle\"; }\n", + "}\n", + "\n", + "class Square extends Shape {\n", + " @Override\n", + " public String toString() { return \"Square\"; }\n", + "}\n", + "\n", + "class Triangle extends Shape {\n", + " @Override\n", + " public String toString() { return \"Triangle\"; }\n", + "}\n", + "\n", + "public class Shapes {\n", + " public static void main(String[] args) {\n", + " Stream.of(\n", + " new Circle(), new Square(), new Triangle())\n", + " .forEach(Shape::draw);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Circle.draw()\n", + "Square.draw()\n", + "Triangle.draw()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "基类中包含 `draw()` 方法,它通过传递 `this` 参数传递给 `System.out.println()`,间接地使用 `toString()` 打印类标识符(注意:这里将 `toString()` 声明为 `abstract`,以此强制继承者覆盖该方法,并防止对 `Shape` 的实例化)。如果某个对象出现在字符串表达式中(涉及\"+\"和字符串对象的表达式),`toString()` 方法就会被自动调用,以生成表示该对象的 `String`。每个派生类都要覆盖(从 `Object` 继承来的)`toString()` 方法,这样 `draw()` 在不同情况下就打印出不同的消息(多态)。\n", + "\n", + "这个例子中,在把 `Shape` 对象放入 `Stream` 中时就会进行向上转型(隐式),但在向上转型的时候也丢失了这些对象的具体类型。对 `stream` 而言,它们只是 `Shape` 对象。\n", + "\n", + "严格来说,`Stream` 实际上是把放入其中的所有对象都当做 `Object` 对象来持有,只是取元素时会自动将其类型转为 `Shape`。这也是 RTTI 最基本的使用形式,因为在 Java 中,所有类型转换的正确性检查都是在运行时进行的。这也正是 RTTI 的含义所在:在运行时,识别一个对象的类型。\n", + "\n", + "另外在这个例子中,类型转换并不彻底:`Object` 被转型为 `Shape` ,而不是 `Circle`、`Square` 或者 `Triangle`。这是因为目前我们只能确保这个 `Stream` 保存的都是 `Shape`:\n", + "\n", + "- 编译期,`stream` 和 Java 泛型系统确保放入 `stream` 的都是 `Shape` 对象(`Shape` 子类的对象也可视为 `Shape` 的对象),否则编译器会报错;\n", + "- 运行时,自动类型转换确保了从 `stream` 中取出的对象都是 `Shape` 类型。\n", + "\n", + "接下来就是多态机制的事了,`Shape` 对象实际执行什么样的代码,是由引用所指向的具体对象(`Circle`、`Square` 或者 `Triangle`)决定的。这也符合我们编写代码的一般需求,通常,我们希望大部分代码尽可能少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(本例中即为 `Shape`)。这样,代码会更容易写,更易读和维护;设计也更容易实现,更易于理解和修改。所以多态是面向对象的基本目标。\n", + "\n", + "但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而 Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。\n", + "\n", + "\n", + "## `Class` 对象\n", + "\n", + "要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有\"常规\"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用 RTTI 的其它方式。\n", + "\n", + "类是程序的一部分,每个类都有一个 `Class` 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 `Class` 对象(更恰当的说,是被保存在一个同名的 `.class` 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用\"类加载器\"子系统把这个类加载到内存中。\n", + "\n", + "类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是 JVM 实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。\n", + "\n", + "所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。\n", + "\n", + "> 其实构造器也是类的静态方法,虽然构造器前面并没有 `static` 关键字。所以,使用 `new` 操作符创建类的新对象,这个操作也算作对类的静态成员引用。\n", + "\n", + "因此,Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得 Java 具有一些静态加载语言(如 C++)很难或者根本不可能实现的特性。\n", + "\n", + "类加载器首先会检查这个类的 `Class` 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 `.class` 文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM 会对其进行验证,确保它没有损坏,并且不包含不良的 Java 代码(这是 Java 安全防范的一种措施)。\n", + "\n", + "一旦某个类的 `Class` 对象被载入内存,它就可以用来创建这个类的所有对象。下面的示范程序可以证明这点:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/SweetShop.java\n", + "// 检查类加载器工作方式\n", + "class Cookie {\n", + " static { System.out.println(\"Loading Cookie\"); }\n", + "}\n", + "\n", + "class Gum {\n", + " static { System.out.println(\"Loading Gum\"); }\n", + "}\n", + "\n", + "class Candy {\n", + " static { System.out.println(\"Loading Candy\"); }\n", + "}\n", + "\n", + "public class SweetShop {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"inside main\");\n", + " new Candy();\n", + " System.out.println(\"After creating Candy\");\n", + " try {\n", + " Class.forName(\"Gum\");\n", + " } catch(ClassNotFoundException e) {\n", + " System.out.println(\"Couldn't find Gum\");\n", + " }\n", + " System.out.println(\"After Class.forName(\\\"Gum\\\")\");\n", + " new Cookie();\n", + " System.out.println(\"After creating Cookie\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inside main\n", + "Loading Candy\n", + "After creating Candy\n", + "Loading Gum\n", + "After Class.forName(\"Gum\")\n", + "Loading Cookie\n", + "After creating Cookie" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上面的代码中,`Candy`、`Gum` 和 `Cookie` 这几个类都有一个 `static{...}` 静态初始化块,这些静态初始化块在类第一次被加载的时候就会执行。也就是说,静态初始化块会打印出相应的信息,告诉我们这些类分别是什么时候被加载了。而在主方法里边,创建对象的代码都放在了 `print()` 语句之间,以帮助我们判断类加载的时间点。\n", + "\n", + "从输出中可以看到,`Class` 对象仅在需要的时候才会被加载,`static` 初始化是在类加载时进行的。\n", + "\n", + "代码里面还有特别有趣的一行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Class.forName(\"Gum\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所有 `Class` 对象都属于 `Class` 类,而且它跟其他普通对象一样,我们可以获取和操控它的引用(这也是类加载器的工作)。`forName()` 是 `Class` 类的一个静态方法,我们可以使用 `forName()` 根据目标类的类名(`String`)得到该类的 `Class` 对象。上面的代码忽略了 `forName()` 的返回值,因为那个调用是为了得到它产生的“副作用”。从结果可以看出,`forName()` 执行的副作用是如果 `Gum` 类没有被加载就加载它,而在加载的过程中,`Gum` 的 `static` 初始化块被执行了。\n", + "\n", + "还需要注意的是,如果 `Class.forName()` 找不到要加载的类,它就会抛出异常 `ClassNotFoundException`。上面的例子中我们只是简单地报告了问题,但在更严密的程序里,就要考虑在异常处理程序中把问题解决掉(具体例子详见[设计模式](./25-Patterns)章节)。\n", + "\n", + "无论何时,只要你想在运行时使用类型信息,就必须先得到那个 `Class` 对象的引用。`Class.forName()` 就是实现这个功能的一个便捷途径,因为使用该方法你不需要先持有这个类型 的对象。但是,如果你已经拥有了目标类的对象,那就可以通过调用 `getClass()` 方法来获取 `Class` 引用了,这个方法来自根类 `Object`,它将返回表示该对象实际类型的 `Class` 对象的引用。`Class` 包含很多有用的方法,下面代码展示了其中的一部分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/toys/ToyTest.java\n", + "// 测试 Class 类\n", + "// {java typeinfo.toys.ToyTest}\n", + "package typeinfo.toys;\n", + "\n", + "interface HasBatteries {}\n", + "interface Waterproof {}\n", + "interface Shoots {}\n", + "\n", + "class Toy {\n", + " // 注释下面的无参数构造器会引起 NoSuchMethodError 错误\n", + " Toy() {}\n", + " Toy(int i) {}\n", + "}\n", + "\n", + "class FancyToy extends Toy\n", + "implements HasBatteries, Waterproof, Shoots {\n", + " FancyToy() { super(1); }\n", + "}\n", + "\n", + "public class ToyTest {\n", + " static void printInfo(Class cc) {\n", + " System.out.println(\"Class name: \" + cc.getName() +\n", + " \" is interface? [\" + cc.isInterface() + \"]\");\n", + " System.out.println(\n", + " \"Simple name: \" + cc.getSimpleName());\n", + " System.out.println(\n", + " \"Canonical name : \" + cc.getCanonicalName());\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Class c = null;\n", + " try {\n", + " c = Class.forName(\"typeinfo.toys.FancyToy\");\n", + " } catch(ClassNotFoundException e) {\n", + " System.out.println(\"Can't find FancyToy\");\n", + " System.exit(1);\n", + " }\n", + "\n", + " printInfo(c);\n", + " for(Class face : c.getInterfaces())\n", + " printInfo(face);\n", + "\n", + " Class up = c.getSuperclass();\n", + " Object obj = null;\n", + "\n", + " try {\n", + " // Requires no-arg constructor:\n", + " obj = up.newInstance();\n", + " } catch(InstantiationException e) {\n", + " System.out.println(\"Cannot instantiate\");\n", + " System.exit(1);\n", + " } catch(IllegalAccessException e) {\n", + " System.out.println(\"Cannot access\");\n", + " System.exit(1);\n", + " }\n", + "\n", + " printInfo(obj.getClass());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Class name: typeinfo.toys.FancyToy is interface?\n", + "[false]\n", + "Simple name: FancyToy\n", + "Canonical name : typeinfo.toys.FancyToy\n", + "Class name: typeinfo.toys.HasBatteries is interface?\n", + "[true]\n", + "Simple name: HasBatteries\n", + "Canonical name : typeinfo.toys.HasBatteries\n", + "Class name: typeinfo.toys.Waterproof is interface?\n", + "[true]\n", + "Simple name: Waterproof\n", + "Canonical name : typeinfo.toys.Waterproof\n", + "Class name: typeinfo.toys.Shoots is interface? [true]\n", + "Simple name: Shoots\n", + "Canonical name : typeinfo.toys.Shoots\n", + "Class name: typeinfo.toys.Toy is interface? [false]\n", + "Simple name: Toy\n", + "Canonical name : typeinfo.toys.Toy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`FancyToy` 继承自 `Toy` 并实现了 `HasBatteries`、`Waterproof` 和 `Shoots` 接口。在 `main` 方法中,我们创建了一个 `Class` 引用,然后在 `try` 语句里边用 `forName()` 方法创建了一个 `FancyToy` 的类对象并赋值给该引用。需要注意的是,传递给 `forName()` 的字符串必须使用类的全限定名(包含包名)。\n", + "\n", + "`printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。\n", + "\n", + "在主方法中调用的 `Class.getInterfaces()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。\n", + "\n", + "另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。\n", + "\n", + "`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态地创建类的对象。\n", + "\n", + "### 类字面常量\n", + "\n", + "Java 还提供了另一种方法来生成类对象的引用:**类字面常量**。对上述程序来说,就像这样:`FancyToy.class;`。这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不必放在 `try` 语句块中)。并且它根除了对 `forName()` 方法的调用,所以效率更高。\n", + "\n", + "类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段 `TYPE`。`TYPE` 字段是一个引用,指向对应的基本数据类型的 `Class` 对象,如下所示:\n", + "\n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
...等价于...
boolean.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classShort.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.TYPE
\n", + "
\n", + "\n", + "我的建议是使用 `.class` 的形式,以保持与普通类的一致性。\n", + "\n", + "注意,有一点很有趣:当使用 `.class` 来创建对 `Class` 对象的引用时,不会自动地初始化该 `Class` 对象。为了使用类而做的准备工作实际包含三个步骤:\n", + "\n", + "1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。\n", + "\n", + "2. **链接**。在链接阶段将验证类中的字节码,为 `static` 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。\n", + "\n", + "3. **初始化**。如果该类具有超类,则先初始化超类,执行 `static` 初始化器和 `static` 初始化块。\n", + "\n", + "直到第一次引用一个 `static` 方法(构造器隐式地是 `static`)或者非常量的 `static` 字段,才会进行类初始化。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/ClassInitialization.java\n", + "import java.util.*;\n", + "\n", + "class Initable {\n", + " static final int STATIC_FINAL = 47;\n", + " static final int STATIC_FINAL2 =\n", + " ClassInitialization.rand.nextInt(1000);\n", + " static {\n", + " System.out.println(\"Initializing Initable\");\n", + " }\n", + "}\n", + "\n", + "class Initable2 {\n", + " static int staticNonFinal = 147;\n", + " static {\n", + " System.out.println(\"Initializing Initable2\");\n", + " }\n", + "}\n", + "\n", + "class Initable3 {\n", + " static int staticNonFinal = 74;\n", + " static {\n", + " System.out.println(\"Initializing Initable3\");\n", + " }\n", + "}\n", + "\n", + "public class ClassInitialization {\n", + " public static Random rand = new Random(47);\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " Class initable = Initable.class;\n", + " System.out.println(\"After creating Initable ref\");\n", + " // Does not trigger initialization:\n", + " System.out.println(Initable.STATIC_FINAL);\n", + " // Does trigger initialization:\n", + " System.out.println(Initable.STATIC_FINAL2);\n", + " // Does trigger initialization:\n", + " System.out.println(Initable2.staticNonFinal);\n", + " Class initable3 = Class.forName(\"Initable3\");\n", + " System.out.println(\"After creating Initable3 ref\");\n", + " System.out.println(Initable3.staticNonFinal);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "After creating Initable ref\n", + "47\n", + "Initializing Initable\n", + "258\n", + "Initializing Initable2\n", + "147\n", + "Initializing Initable3\n", + "After creating Initable3 ref\n", + "74" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "初始化有效地实现了尽可能的“惰性”,从对 `initable` 引用的创建中可以看到,仅使用 `.class` 语法来获得对类对象的引用不会引发初始化。但与此相反,使用 `Class.forName()` 来产生 `Class` 引用会立即就进行初始化,如 `initable3`。\n", + "\n", + "如果一个 `static final` 值是“编译期常量”(如 `Initable.staticFinal`),那么这个值不需要对 `Initable` 类进行初始化就可以被读取。但是,如果只是将一个字段设置成为 `static` 和 `final`,还不足以确保这种行为。例如,对 `Initable.staticFinal2` 的访问将强制进行类的初始化,因为它不是一个编译期常量。\n", + "\n", + "如果一个 `static` 字段不是 `final` 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间),就像在对 `Initable2.staticNonFinal` 的访问中所看到的那样。\n", + "\n", + "### 泛化的 `Class` 引用\n", + "\n", + "`Class` 引用总是指向某个 `Class` 对象,而 `Class` 对象可以用于产生类的实例,并且包含可作用于这些实例的所有方法代码。它还包含该类的 `static` 成员,因此 `Class` 引用表明了它所指向对象的确切类型,而该对象便是 `Class` 类的一个对象。\n", + "\n", + "\n", + "\n", + "但是,Java 设计者看准机会,将它的类型变得更具体了一些。Java 引入泛型语法之后,我们可以使用泛型对 `Class` 引用所指向的 `Class` 对象的类型进行限定。在下面的实例中,两种语法都是正确的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/GenericClassReferences.java\n", + "\n", + "public class GenericClassReferences {\n", + " public static void main(String[] args) {\n", + " Class intClass = int.class;\n", + " Class genericIntClass = int.class;\n", + " genericIntClass = Integer.class; // 同一个东西\n", + " intClass = double.class;\n", + " // genericIntClass = double.class; // 非法\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "普通的类引用不会产生警告信息。你可以看到,普通的类引用可以重新赋值指向任何其他的 `Class` 对象,但是使用泛型限定的类引用只能指向其声明的类型。通过使用泛型语法,我们可以让编译器强制执行额外的类型检查。\n", + "\n", + "那如果我们希望稍微放松一些限制,应该怎么办呢?乍一看,下面的操作好像是可以的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Class geenericNumberClass = int.class;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这看起来似乎是起作用的,因为 `Integer` 继承自 `Number`。但事实却是不行,因为 `Integer` 的 `Class` 对象并不是 `Number`的 `Class` 对象的子类(这看起来可能有点诡异,我们将在[泛型](./20-Generics)这一章详细讨论)。\n", + "\n", + "为了在使用 `Class` 引用时放松限制,我们使用了通配符,它是 Java 泛型中的一部分。通配符就是 `?`,表示“任何事物”。因此,我们可以在上例的普通 `Class` 引用中添加通配符,并产生相同的结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/WildcardClassReferences.java\n", + "\n", + "public class WildcardClassReferences {\n", + " public static void main(String[] args) {\n", + " Class intClass = int.class;\n", + " intClass = double.class;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 `Class` 比单纯使用 `Class` 要好,虽然它们是等价的,并且单纯使用 `Class` 不会产生编译器警告信息。使用 `Class` 的好处是它表示你并非是碰巧或者由于疏忽才使用了一个非具体的类引用,而是特意为之。\n", + "\n", + "为了创建一个限定指向某种类型或其子类的 `Class` 引用,我们需要将通配符与 `extends` 关键字配合使用,创建一个范围限定。这与仅仅声明 `Class` 不同,现在做如下声明:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/BoundedClassReferences.java\n", + "\n", + "public class BoundedClassReferences {\n", + " public static void main(String[] args) {\n", + " Class bounded = int.class;\n", + " bounded = double.class;\n", + " bounded = Number.class;\n", + " // Or anything else derived from Number.\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "向 `Class` 引用添加泛型语法的原因只是为了提供编译期类型检查,因此如果你操作有误,稍后就会发现这点。使用普通的 `Class` 引用你要确保自己不会犯错,因为一旦你犯了错误,就要等到运行时才能发现它,很不方便。\n", + "\n", + "下面的示例使用了泛型语法,它保存了一个类引用,稍后又用 `newInstance()` 方法产生类的对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/DynamicSupplier.java\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "class CountedInteger {\n", + " private static long counter;\n", + " private final long id = counter++;\n", + " @Override\n", + " public String toString() { return Long.toString(id); }\n", + "}\n", + "\n", + "public class DynamicSupplier implements Supplier {\n", + " private Class type;\n", + " public DynamicSupplier(Class type) {\n", + " this.type = type;\n", + " }\n", + " public T get() {\n", + " try {\n", + " return type.newInstance();\n", + " } catch(InstantiationException |\n", + " IllegalAccessException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " Stream.generate(\n", + " new DynamicSupplier<>(CountedInteger.class))\n", + " .skip(10)\n", + " .limit(5)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "10\n", + "11\n", + "12\n", + "13\n", + "14" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否则运行时会抛出异常。编译期对该程序不会产生任何警告信息。\n", + "\n", + "当你将泛型语法用于 `Class` 对象时,`newInstance()` 将返回该对象的确切类型,而不仅仅只是在 `ToyTest.java` 中看到的基类 `Object`。然而,这在某种程度上有些受限:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/toys/GenericToyTest.java\n", + "// 测试 Class 类\n", + "// {java typeinfo.toys.GenericToyTest}\n", + "package typeinfo.toys;\n", + "\n", + "public class GenericToyTest {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " Class ftClass = FancyToy.class;\n", + " // Produces exact type:\n", + " FancyToy fancyToy = ftClass.newInstance();\n", + " Class up =\n", + " ftClass.getSuperclass();\n", + " // This won't compile:\n", + " // Class up2 = ftClass.getSuperclass();\n", + " // Only produces Object:\n", + " Object obj = up.newInstance();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是\"某个类\"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。\n", + "\n", + "### `cast()` 方法\n", + "\n", + "Java 中还有用于 `Class` 引用的转型语法,即 `cast()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/ClassCasts.java\n", + "\n", + "class Building {}\n", + "class House extends Building {}\n", + "\n", + "public class ClassCasts {\n", + " public static void main(String[] args) {\n", + " Building b = new House();\n", + " Class houseType = House.class;\n", + " House h = houseType.cast(b);\n", + " h = (House)b; // ... 或者这样做.\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`cast()` 方法接受参数对象,并将其类型转换为 `Class` 引用的类型。但是,如果观察上面的代码,你就会发现,与实现了相同功能的 `main` 方法中最后一行相比,这种转型好像做了很多额外的工作。\n", + "\n", + "`cast()` 在无法使用普通类型转换的情况下会显得非常有用,在你编写泛型代码(你将在[泛型](./20-Generics)这一章学习到)时,如果你保存了 `Class` 引用,并希望以后通过这个引用来执行转型,你就需要用到 `cast()`。但事实却是这种情况非常少见,我发现整个 Java 类库中,只有一处使用了 `cast()`(在 `com.sun.mirror.util.DeclarationFilter` 中)。\n", + "\n", + "Java 类库中另一个没有任何用处的特性就是 `Class.asSubclass()`,该方法允许你将一个 `Class` 对象转型为更加具体的类型。\n", + "\n", + "## 类型转换检测\n", + "\n", + "直到现在,我们已知的 RTTI 类型包括:\n", + "\n", + "1. 传统的类型转换,如 “`(Shape)`”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 `ClassCastException` 异常。\n", + "\n", + "2. 代表对象类型的 `Class` 对象. 通过查询 `Class` 对象可以获取运行时所需的信息.\n", + "\n", + "在 C++ 中,经典的类型转换 “`(Shape)`” 并不使用 RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显式的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式地进行类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际不是待转型类型的子类类型上)。\n", + "\n", + "RTTI 在 Java 中还有第三种形式,那就是关键字 `instanceof`。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "if(x instanceof Dog)\n", + " ((Dog)x).bark();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在将 `x` 的类型转换为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。\n", + "\n", + "一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 `instanceof` 来度量所有对象。举个例子,假如你有一个类的继承体系,描述了 `Pet`(以及它们的主人,在后面一个例子中会用到这个特性)。在这个继承体系中的每个 `Individual` 都有一个 `id` 和一个可选的名字。尽管下面的类都继承自 `Individual`,但是 `Individual` 类复杂性较高,因此其代码将放在[附录:容器](./Appendix-Collection-Topics)中进行解释说明。正如你所看到的,此处并不需要去了解 `Individual` 的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 `Individual` 都有一个 `id()` 方法,如果你没有为 `Individual` 提供名字,`toString()` 方法只产生类型名。\n", + "\n", + "下面是继承自 `Individual` 的类的继承体系:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Person.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Person extends Individual {\n", + " public Person(String name) { super(name); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Pet.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Pet extends Individual {\n", + " public Pet(String name) { super(name); }\n", + " public Pet() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Dog.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Dog extends Pet {\n", + " public Dog(String name) { super(name); }\n", + " public Dog() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Mutt.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Mutt extends Dog {\n", + " public Mutt(String name) { super(name); }\n", + " public Mutt() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Pug.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Pug extends Dog {\n", + " public Pug(String name) { super(name); }\n", + " public Pug() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Cat.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Cat extends Pet {\n", + " public Cat(String name) { super(name); }\n", + " public Cat() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/EgyptianMau.java\n", + "package typeinfo.pets;\n", + "\n", + "public class EgyptianMau extends Cat {\n", + " public EgyptianMau(String name) { super(name); }\n", + " public EgyptianMau() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Manx.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Manx extends Cat {\n", + " public Manx(String name) { super(name); }\n", + " public Manx() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Cymric.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Cymric extends Manx {\n", + " public Cymric(String name) { super(name); }\n", + " public Cymric() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Rodent.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Rodent extends Pet {\n", + " public Rodent(String name) { super(name); }\n", + " public Rodent() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Rat.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Rat extends Rodent {\n", + " public Rat(String name) { super(name); }\n", + " public Rat() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Mouse.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Mouse extends Rodent {\n", + " public Mouse(String name) { super(name); }\n", + " public Mouse() { super(); }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Hamster.java\n", + "package typeinfo.pets;\n", + "\n", + "public class Hamster extends Rodent {\n", + " public Hamster(String name) { super(name); }\n", + " public Hamster() { super(); }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们必须显式地为每一个子类编写无参构造器。因为我们有一个带一个参数的构造器,所以编译器不会自动地为我们加上无参构造器。\n", + "\n", + "接下来,我们需要一个类,它可以随机地创建不同类型的宠物,同时,它还可以创建宠物数组和持有宠物的 `List`。为了使这个类更加普遍适用,我们将其定义为抽象类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/PetCreator.java\n", + "// Creates random sequences of Pets\n", + "package typeinfo.pets;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "public abstract class PetCreator implements Supplier {\n", + " private Random rand = new Random(47);\n", + "\n", + " // The List of the different types of Pet to create:\n", + " public abstract List> types();\n", + "\n", + " public Pet get() { // Create one random Pet\n", + " int n = rand.nextInt(types().size());\n", + " try {\n", + " return types().get(n).newInstance();\n", + " } catch (InstantiationException |\n", + " IllegalAccessException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "抽象的 `types()` 方法需要子类来实现,以此来获取 `Class` 对象构成的 `List`(这是模板方法设计模式的一种变体)。注意,其中类的类型被定义为“任何从 `Pet` 导出的类型”,因此 `newInstance()` 不需要转型就可以产生 `Pet`。`get()` 随机的选取出一个 `Class` 对象,然后可以通过 `Class.newInstance()` 来生成该类的新实例。\n", + "\n", + "在调用 `newInstance()` 时,可能会出现两种异常。在紧跟 `try` 语句块后面的 `catch` 子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(`IllegalAccessException` 违反了 Java 安全机制,在本例中,表示默认构造器为 `private` 的情况)。\n", + "\n", + "当你创建 `PetCreator` 的子类时,你需要为 `get()` 方法提供 `Pet` 类型的 `List`。`types()` 方法会简单地返回一个静态 `List` 的引用。下面是使用 `forName()` 的一个具体实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/ForNameCreator.java\n", + "package typeinfo.pets;\n", + "import java.util.*;\n", + "\n", + "public class ForNameCreator extends PetCreator {\n", + " private static List> types =\n", + " new ArrayList<>();\n", + " // 需要随机生成的类型名:\n", + " private static String[] typeNames = {\n", + " \"typeinfo.pets.Mutt\",\n", + " \"typeinfo.pets.Pug\",\n", + " \"typeinfo.pets.EgyptianMau\",\n", + " \"typeinfo.pets.Manx\",\n", + " \"typeinfo.pets.Cymric\",\n", + " \"typeinfo.pets.Rat\",\n", + " \"typeinfo.pets.Mouse\",\n", + " \"typeinfo.pets.Hamster\"\n", + " };\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " private static void loader() {\n", + " try {\n", + " for (String name : typeNames)\n", + " types.add(\n", + " (Class) Class.forName(name));\n", + " } catch (ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " static {\n", + " loader();\n", + " }\n", + "\n", + " @Override\n", + " public List> types() {\n", + " return types;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException` 异常,因为你传入的是一个 `String` 类型的参数,它不能在编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。\n", + "\n", + "为了使得 `List` 装入的是具体的 `Class` 对象,类型转换是必须的,它会产生一个编译时警告。`loader()` 方法是分开编写的,然后它被放入到一个静态代码块里,因为 `@SuppressWarning` 注解不能够直接放置在静态代码块之上。\n", + "\n", + "为了对 `Pet` 进行计数,我们需要一个能跟踪不同类型的 `Pet` 的工具。`Map` 是这个需求的首选,我们将 `Pet` 类型名作为键,将保存 `Pet` 数量的 `Integer` 作为值。通过这种方式,你就可以询问:“有多少个 `Hamster` 对象?”我们可以使用 `instanceof` 来对 `Pet` 进行计数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/PetCount.java\n", + "// 使用 instanceof\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class PetCount {\n", + " static class Counter extends HashMap {\n", + " public void count(String type) {\n", + " Integer quantity = get(type);\n", + " if (quantity == null)\n", + " put(type, 1);\n", + " else\n", + " put(type, quantity + 1);\n", + " }\n", + " }\n", + "\n", + " public static void\n", + " countPets(PetCreator creator) {\n", + " Counter counter = new Counter();\n", + " for (Pet pet : Pets.array(20)) {\n", + " // List each individual pet:\n", + " System.out.print(\n", + " pet.getClass().getSimpleName() + \" \");\n", + " if (pet instanceof Pet)\n", + " counter.count(\"Pet\");\n", + " if (pet instanceof Dog)\n", + " counter.count(\"Dog\");\n", + " if (pet instanceof Mutt)\n", + " counter.count(\"Mutt\");\n", + " if (pet instanceof Pug)\n", + " counter.count(\"Pug\");\n", + " if (pet instanceof Cat)\n", + " counter.count(\"Cat\");\n", + " if (pet instanceof EgyptianMau)\n", + " counter.count(\"EgyptianMau\");\n", + " if (pet instanceof Manx)\n", + " counter.count(\"Manx\");\n", + " if (pet instanceof Cymric)\n", + " counter.count(\"Cymric\");\n", + " if (pet instanceof Rodent)\n", + " counter.count(\"Rodent\");\n", + " if (pet instanceof Rat)\n", + " counter.count(\"Rat\");\n", + " if (pet instanceof Mouse)\n", + " counter.count(\"Mouse\");\n", + " if (pet instanceof Hamster)\n", + " counter.count(\"Hamster\");\n", + " }\n", + " // Show the counts:\n", + " System.out.println();\n", + " System.out.println(counter);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " countPets(new ForNameCreator());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", + "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", + "Pug Mouse Cymric\n", + "{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,\n", + "Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `countPets()` 中,一个简短的静态方法 `Pets.array()` 生产出了一个随机动物的集合。每个 `Pet` 都被 `instanceof` 检测到并计算了一遍。\n", + "\n", + "`instanceof` 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 `Class` 对象作比较。在前面的例子中,你可能会觉得写出一大堆 `instanceof` 表达式很乏味,事实也是如此。但是,也没有办法让 `instanceof` 聪明起来,让它能够自动地创建一个 `Class` 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 `instanceof`,那就说明你的设计可能存在瑕疵。\n", + "\n", + "### 使用类字面量\n", + "\n", + "如果我们使用类字面量重新实现 `PetCreator` 类的话,其结果在很多方面都会更清晰:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/LiteralPetCreator.java\n", + "// 使用类字面量\n", + "// {java typeinfo.pets.LiteralPetCreator}\n", + "package typeinfo.pets;\n", + "import java.util.*;\n", + "\n", + "public class LiteralPetCreator extends PetCreator {\n", + " // try 代码块不再需要\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static final List> ALL_TYPES =\n", + " Collections.unmodifiableList(Arrays.asList(\n", + " Pet.class, Dog.class, Cat.class, Rodent.class,\n", + " Mutt.class, Pug.class, EgyptianMau.class,\n", + " Manx.class, Cymric.class, Rat.class,\n", + " Mouse.class, Hamster.class));\n", + " // 用于随机创建的类型:\n", + " private static final List> TYPES =\n", + " ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class),\n", + " ALL_TYPES.size());\n", + "\n", + " @Override\n", + " public List> types() {\n", + " return TYPES;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(TYPES);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[class typeinfo.pets.Mutt, class typeinfo.pets.Pug,\n", + "class typeinfo.pets.EgyptianMau, class\n", + "typeinfo.pets.Manx, class typeinfo.pets.Cymric, class\n", + "typeinfo.pets.Rat, class typeinfo.pets.Mouse, class\n", + "typeinfo.pets.Hamster]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在即将到来的 `PetCount3.java` 示例中,我们用所有 `Pet` 类型预先加载一个 `Map`(不仅仅是随机生成的),因此 `ALL_TYPES` 类型的列表是必要的。`types` 列表是 `ALL_TYPES` 类型(使用 `List.subList()` 创建)的一部分,它包含精确的宠物类型,因此用于随机生成 `Pet`。\n", + "\n", + "这次,`types` 的创建没有被 `try` 块包围,因为它是在编译时计算的,因此不会引发任何异常,不像 `Class.forName()`。\n", + "\n", + "我们现在在 `typeinfo.pets` 库中有两个 `PetCreator` 的实现。为了提供第二个作为默认实现,我们可以创建一个使用 `LiteralPetCreator` 的 *外观模式*:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Pets.java\n", + "// Facade to produce a default PetCreator\n", + "package typeinfo.pets;\n", + "\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class Pets {\n", + " public static final PetCreator CREATOR = new LiteralPetCreator();\n", + "\n", + " public static Pet get() {\n", + " return CREATOR.get();\n", + " }\n", + "\n", + " public static Pet[] array(int size) {\n", + " Pet[] result = new Pet[size];\n", + " for (int i = 0; i < size; i++)\n", + " result[i] = CREATOR.get();\n", + " return result;\n", + " }\n", + "\n", + " public static List list(int size) {\n", + " List result = new ArrayList<>();\n", + " Collections.addAll(result, array(size));\n", + " return result;\n", + " }\n", + "\n", + " public static Stream stream() {\n", + " return Stream.generate(CREATOR);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这还提供了对 `get()`、`array()` 和 `list()` 的间接调用,以及生成 `Stream` 的新方法。\n", + "\n", + "因为 `PetCount.countPets()` 采用了 `PetCreator` 参数,所以我们可以很容易地测试 `LiteralPetCreator`(通过上面的外观模式):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/PetCount2.java\n", + "import typeinfo.pets.*;\n", + "\n", + "public class PetCount2 {\n", + " public static void main(String[] args) {\n", + " PetCount.countPets(Pets.CREATOR);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", + "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", + "Pug Mouse Cymric\n", + "{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,\n", + "Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出与 `PetCount.java` 的输出相同。\n", + "\n", + "### 一个动态 `instanceof` 函数\n", + "\n", + "`Class.isInstance()` 方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 `instanceof` 语句都可以从 `PetCount.java` 中删除:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/PetCount3.java\n", + "// 使用 isInstance() 方法\n", + "\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "import onjava.*;\n", + "import typeinfo.pets.*;\n", + "\n", + "public class PetCount3 {\n", + " static class Counter extends\n", + " LinkedHashMap, Integer> {\n", + " Counter() {\n", + " super(LiteralPetCreator.ALL_TYPES.stream()\n", + " .map(lpc -> Pair.make(lpc, 0))\n", + " .collect(\n", + " Collectors.toMap(Pair::key, Pair::value)));\n", + " }\n", + "\n", + " public void count(Pet pet) {\n", + " // Class.isInstance() 替换 instanceof:\n", + " entrySet().stream()\n", + " .filter(pair -> pair.getKey().isInstance(pet))\n", + " .forEach(pair ->\n", + " put(pair.getKey(), pair.getValue() + 1));\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " String result = entrySet().stream()\n", + " .map(pair -> String.format(\"%s=%s\",\n", + " pair.getKey().getSimpleName(),\n", + " pair.getValue()))\n", + " .collect(Collectors.joining(\", \"));\n", + " return \"{\" + result + \"}\";\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Counter petCount = new Counter();\n", + " Pets.stream()\n", + " .limit(20)\n", + " .peek(petCount::count)\n", + " .forEach(p -> System.out.print(\n", + " p.getClass().getSimpleName() + \" \"));\n", + " System.out.println(\"n\" + petCount);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", + "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", + "Pug Mouse Cymric\n", + "{Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5,\n", + "EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了计算所有不同类型的 `Pet`,`Counter Map` 预先加载了来自 `LiteralPetCreator.ALL_TYPES` 的类型。如果不预先加载 `Map`,将只计数随机生成的类型,而不是像 `Pet` 和 `Cat` 这样的基本类型。\n", + "\n", + "`isInstance()` 方法消除了对 `instanceof` 表达式的需要。此外,这意味着你可以通过更改 `LiteralPetCreator.types` 数组来添加新类型的 `Pet`;程序的其余部分不需要修改(就像使用 `instanceof` 表达式时那样)。\n", + "\n", + "`toString()` 方法被重载,以便更容易读取输出,该输出仍与打印 `Map` 时看到的典型输出匹配。\n", + "\n", + "### 递归计数\n", + "\n", + "`PetCount3.Counter` 中的 `Map` 预先加载了所有不同的 `Pet` 类。我们可以使用 `Class.isAssignableFrom()` 而不是预加载 `Map` ,并创建一个不限于计数 `Pet` 的通用工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/TypeCounter.java\n", + "// 计算类型家族的实例数\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class TypeCounter extends HashMap, Integer> {\n", + " private Class baseType;\n", + "\n", + " public TypeCounter(Class baseType) {\n", + " this.baseType = baseType;\n", + " }\n", + "\n", + " public void count(Object obj) {\n", + " Class type = obj.getClass();\n", + " if(!baseType.isAssignableFrom(type))\n", + " throw new RuntimeException(\n", + " obj + \" incorrect type: \" + type +\n", + " \", should be type or subtype of \" + baseType);\n", + " countClass(type);\n", + " }\n", + "\n", + " private void countClass(Class type) {\n", + " Integer quantity = get(type);\n", + " put(type, quantity == null ? 1 : quantity + 1);\n", + " Class superClass = type.getSuperclass();\n", + " if(superClass != null &&\n", + " baseType.isAssignableFrom(superClass))\n", + " countClass(superClass);\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " String result = entrySet().stream()\n", + " .map(pair -> String.format(\"%s=%s\",\n", + " pair.getKey().getSimpleName(),\n", + " pair.getValue()))\n", + " .collect(Collectors.joining(\", \"));\n", + " return \"{\" + result + \"}\";\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`count()` 方法获取其参数的 `Class`,并使用 `isAssignableFrom()` 进行运行时检查,以验证传递的对象实际上属于感兴趣的层次结构。`countClass()` 首先计算类的确切类型。然后,如果 `baseType` 可以从超类赋值,则在超类上递归调用 `countClass()`。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/PetCount4.java\n", + "import typeinfo.pets.*;\n", + "import onjava.*;\n", + "\n", + "public class PetCount4 {\n", + " public static void main(String[] args) {\n", + " TypeCounter counter = new TypeCounter(Pet.class);\n", + " Pets.stream()\n", + " .limit(20)\n", + " .peek(counter::count)\n", + " .forEach(p -> System.out.print(\n", + " p.getClass().getSimpleName() + \" \"));\n", + " System.out.println(\"n\" + counter);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", + "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", + "Pug Mouse Cymric\n", + "{Dog=6, Manx=7, Cat=9, Rodent=5, Hamster=1, Rat=2,\n", + "Pug=3, Mutt=3, Cymric=5, EgyptianMau=2, Pet=20,\n", + "Mouse=2}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出表明两个基类型以及精确类型都被计数了。\n", + "\n", + "\n", + "\n", + "## 注册工厂\n", + "\n", + "从 `Pet` 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 `Pet` 时,必须记住将其添加到 `LiteralPetCreator.java` 的条目中。在一个定期添加更多类的系统中,这可能会成为问题。\n", + "\n", + "你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。\n", + "\n", + "基本上,你必须自己手工创建列表(除非你编写了一个工具来搜索和分析源代码,然后创建和编译列表)。所以你能做的最好的事情就是把列表集中放在一个明显的地方。层次结构的基类可能是最好的地方。\n", + "\n", + "我们在这里所做的另一个更改是使用*工厂方法*设计模式将对象的创建推迟到类本身。工厂方法可以以多态方式调用,并为你创建适当类型的对象。事实证明,`java.util.function.Supplier` 用 `T get()` 描述了原型工厂方法。协变返回类型允许 `get()` 为 `Supplier` 的每个子类实现返回不同的类型。\n", + "\n", + "在本例中,基类 `Part` 包含一个工厂对象的静态列表,列表成员类型为 `Supplier`。对于应该由 `get()` 方法生成的类型的工厂,通过将它们添加到 `prototypes` 列表向基类“注册”。奇怪的是,这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的*原型*:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/RegisteredFactories.java\n", + "// 注册工厂到基础类\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "class Part implements Supplier {\n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName();\n", + " }\n", + "\n", + " static List> prototypes =\n", + " Arrays.asList(\n", + " new FuelFilter(),\n", + " new AirFilter(),\n", + " new CabinAirFilter(),\n", + " new OilFilter(),\n", + " new FanBelt(),\n", + " new PowerSteeringBelt(),\n", + " new GeneratorBelt()\n", + " );\n", + "\n", + " private static Random rand = new Random(47);\n", + " public Part get() {\n", + " int n = rand.nextInt(prototypes.size());\n", + " return prototypes.get(n).get();\n", + " }\n", + "}\n", + "\n", + "class Filter extends Part {}\n", + "\n", + "class FuelFilter extends Filter {\n", + " @Override\n", + " public FuelFilter get() {\n", + " return new FuelFilter();\n", + " }\n", + "}\n", + "\n", + "class AirFilter extends Filter {\n", + " @Override\n", + " public AirFilter get() {\n", + " return new AirFilter();\n", + " }\n", + "}\n", + "\n", + "class CabinAirFilter extends Filter {\n", + " @Override\n", + " public CabinAirFilter get() {\n", + " return new CabinAirFilter();\n", + " }\n", + "}\n", + "\n", + "class OilFilter extends Filter {\n", + " @Override\n", + " public OilFilter get() {\n", + " return new OilFilter();\n", + " }\n", + "}\n", + "\n", + "class Belt extends Part {}\n", + "\n", + "class FanBelt extends Belt {\n", + " @Override\n", + " public FanBelt get() {\n", + " return new FanBelt();\n", + " }\n", + "}\n", + "\n", + "class GeneratorBelt extends Belt {\n", + " @Override\n", + " public GeneratorBelt get() {\n", + " return new GeneratorBelt();\n", + " }\n", + "}\n", + "\n", + "class PowerSteeringBelt extends Belt {\n", + " @Override\n", + " public PowerSteeringBelt get() {\n", + " return new PowerSteeringBelt();\n", + " }\n", + "}\n", + "\n", + "public class RegisteredFactories {\n", + " public static void main(String[] args) {\n", + " Stream.generate(new Part())\n", + " .limit(10)\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "GeneratorBelt\n", + "CabinAirFilter\n", + "GeneratorBelt\n", + "AirFilter\n", + "PowerSteeringBelt\n", + "CabinAirFilter\n", + "FuelFilter\n", + "PowerSteeringBelt\n", + "PowerSteeringBelt\n", + "FuelFilter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "并非层次结构中的所有类都应实例化;这里的 `Filter` 和 `Belt` 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 `Part` 基类的行为)。\n", + "\n", + "因为 `Part implements Supplier`,`Part` 通过其 `get()` 方法供应其他 `Part`。如果为基类 `Part` 调用 `get()`(或者如果 `generate()` 调用 `get()`),它将创建随机特定的 `Part` 子类型,每个子类型最终都从 `Part` 继承,并重写相应的 `get()` 以生成它们中的一个。\n", + "\n", + "\n", + "\n", + "## 类的等价比较\n", + "\n", + "当你查询类型信息时,需要注意:instanceof 的形式(即 `instanceof` 或 `isInstance()` ,这两者产生的结果相同) 和 与 Class 对象直接比较 这两者间存在重要区别。下面的例子展示了这种区别:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/FamilyVsExactType.java\n", + "// instanceof 与 class 的差别\n", + "// {java typeinfo.FamilyVsExactType}\n", + "package typeinfo;\n", + "\n", + "class Base {}\n", + "class Derived extends Base {}\n", + "\n", + "public class FamilyVsExactType {\n", + " static void test(Object x) {\n", + " System.out.println(\n", + " \"Testing x of type \" + x.getClass());\n", + " System.out.println(\n", + " \"x instanceof Base \" + (x instanceof Base));\n", + " System.out.println(\n", + " \"x instanceof Derived \" + (x instanceof Derived));\n", + " System.out.println(\n", + " \"Base.isInstance(x) \" + Base.class.isInstance(x));\n", + " System.out.println(\n", + " \"Derived.isInstance(x) \" +\n", + " Derived.class.isInstance(x));\n", + " System.out.println(\n", + " \"x.getClass() == Base.class \" +\n", + " (x.getClass() == Base.class));\n", + " System.out.println(\n", + " \"x.getClass() == Derived.class \" +\n", + " (x.getClass() == Derived.class));\n", + " System.out.println(\n", + " \"x.getClass().equals(Base.class)) \"+\n", + " (x.getClass().equals(Base.class)));\n", + " System.out.println(\n", + " \"x.getClass().equals(Derived.class)) \" +\n", + " (x.getClass().equals(Derived.class)));\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " test(new Base());\n", + " test(new Derived());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Testing x of type class typeinfo.Base\n", + "x instanceof Base true\n", + "x instanceof Derived false\n", + "Base.isInstance(x) true\n", + "Derived.isInstance(x) false\n", + "x.getClass() == Base.class true\n", + "x.getClass() == Derived.class false\n", + "x.getClass().equals(Base.class)) true\n", + "x.getClass().equals(Derived.class)) false\n", + "Testing x of type class typeinfo.Derived\n", + "x instanceof Base true\n", + "x instanceof Derived true\n", + "Base.isInstance(x) true\n", + "Derived.isInstance(x) true\n", + "x.getClass() == Base.class false\n", + "x.getClass() == Derived.class true\n", + "x.getClass().equals(Base.class)) false\n", + "x.getClass().equals(Derived.class)) true" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果相同, `equals()` 和 `==` 产生的结果也相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 `==` 比较实际的`Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。\n", + "\n", + "\n", + "## 反射:运行时类信息\n", + "\n", + "如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。\n", + "\n", + "起初,这看起来并没有那么大的限制,但是假设你引用了一个不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类?\n", + "\n", + "在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在编程时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆写这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。\n", + "\n", + "在运行时发现类信息的另一个令人信服的动机是提供跨网络在远程平台上创建和执行对象的能力。这称为*远程方法调用*(RMI),它使 Java 程序的对象分布在许多机器上。这种分布有多种原因。如果你想加速一个计算密集型的任务,你可以把它分解成小块放到空闲的机器上。或者你可以将处理特定类型任务的代码(例如,多层次客户机/服务器体系结构中的“业务规则”)放在特定的机器上,这样机器就成为描述这些操作的公共存储库,并且可以很容易地更改它以影响系统中的每个人。分布式计算还支持专门的硬件,这些硬件可能擅长于某个特定的任务——例如矩阵转换——但对于通用编程来说不合适或过于昂贵。\n", + "\n", + "类 `Class` 支持*反射*的概念, `java.lang.reflect` 库中包含类 `Field`、`Method` 和 `Constructor`(每一个都实现了 `Member` 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 `Constructor` 创建新对象,`get()` 和 `set()` 方法读取和修改与 `Field` 对象关联的字段,`invoke()` 方法调用与 `Method` 对象关联的方法。此外,还可以调用便利方法 `getFields()`、`getMethods()`、`getConstructors()` 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 `Class` 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。\n", + "\n", + "重要的是要意识到反射没有什么魔力。当你使用反射与未知类型的对象交互时,JVM 将查看该对象,并看到它属于特定的类(就像普通的 RTTI)。在对其执行任何操作之前,必须加载 `Class` 对象。因此,该特定类型的 `.class` 文件必须在本地计算机上或通过网络对 JVM 仍然可用。因此,RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 `.class` 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,`.class` 文件在编译时不可用;它由运行时环境打开并检查。\n", + "\n", + "### 类方法提取器\n", + "\n", + "通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](https://lingcoder.github.io/OnJava8/#/book/Appendix-Object-Serialization))。但是,有时动态提取有关类的信息很有用。\n", + "\n", + "考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/ShowMethods.java\n", + "// 使用反射展示一个类的所有方法,甚至包括定义在基类中方法\n", + "// {java ShowMethods ShowMethods}\n", + "import java.lang.reflect.*;\n", + "import java.util.regex.*;\n", + "\n", + "public class ShowMethods {\n", + " private static String usage =\n", + " \"usage:\\n\" +\n", + " \"ShowMethods qualified.class.name\\n\" +\n", + " \"To show all methods in class or:\\n\" +\n", + " \"ShowMethods qualified.class.name word\\n\" +\n", + " \"To search for methods involving 'word'\";\n", + " private static Pattern p = Pattern.compile(\"\\\\w+\\\\.\");\n", + "\n", + " public static void main(String[] args) {\n", + " if (args.length < 1) {\n", + " System.out.println(usage);\n", + " System.exit(0);\n", + " }\n", + " int lines = 0;\n", + " try {\n", + " Class c = Class.forName(args[0]);\n", + " Method[] methods = c.getMethods();\n", + " Constructor[] ctors = c.getConstructors();\n", + " if (args.length == 1) {\n", + " for (Method method : methods)\n", + " System.out.println(\n", + " p.matcher(\n", + " method.toString()).replaceAll(\"\"));\n", + " for (Constructor ctor : ctors)\n", + " System.out.println(\n", + " p.matcher(ctor.toString()).replaceAll(\"\"));\n", + " lines = methods.length + ctors.length;\n", + " } else {\n", + " for (Method method : methods)\n", + " if (method.toString().contains(args[1])) {\n", + " System.out.println(p.matcher(\n", + " method.toString()).replaceAll(\"\"));\n", + " lines++;\n", + " }\n", + " for (Constructor ctor : ctors)\n", + " if (ctor.toString().contains(args[1])) {\n", + " System.out.println(p.matcher(\n", + " ctor.toString()).replaceAll(\"\"));\n", + " lines++;\n", + " }\n", + " }\n", + " } catch (ClassNotFoundException e) {\n", + " System.out.println(\"No such class: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "public static void main(String[])\n", + "public final void wait() throws InterruptedException\n", + "public final void wait(long,int) throws\n", + "InterruptedException\n", + "public final native void wait(long) throws\n", + "InterruptedException\n", + "public boolean equals(Object)\n", + "public String toString()\n", + "public native int hashCode()\n", + "public final native Class getClass()\n", + "public final native void notify()\n", + "public final native void notifyAll()\n", + "public ShowMethods()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Class` 方法 `getmethods()` 和 `getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。\n", + "\n", + "编译时无法知道 `Class.forName()` 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用(本书后面有这样的例子)。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。\n", + "\n", + "上面的输出来自命令行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "java ShowMethods ShowMethods" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出包含一个 `public` 无参数构造函数,即使未定义构造函数。你看到的构造函数是由编译器自动合成的。如果将 `ShowMethods` 设置为非 `public` 类(即只有包级访问权),则合成的无参数构造函数将不再显示在输出中。自动为合成的无参数构造函数授予与类相同的访问权。\n", + "\n", + "尝试运行 `java ShowMethods java.lang.String`,并附加一个 `char`、`int`、`String` 等参数。\n", + "\n", + "编程时,当你不记得某个类是否有特定的方法,并且不想在 JDK 文档中搜索索引或类层次结构时,或者如果你不知道该类是否可以对 `Color` 对象执行任何操作时,该工具能节省不少时间。\n", + "\n", + "\n", + "\n", + "## 动态代理\n", + "\n", + "*代理*是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/SimpleProxyDemo.java\n", + "\n", + "interface Interface {\n", + " void doSomething();\n", + "\n", + " void somethingElse(String arg);\n", + "}\n", + "\n", + "class RealObject implements Interface {\n", + " @Override\n", + " public void doSomething() {\n", + " System.out.println(\"doSomething\");\n", + " }\n", + "\n", + " @Override\n", + " public void somethingElse(String arg) {\n", + " System.out.println(\"somethingElse \" + arg);\n", + " }\n", + "}\n", + "\n", + "class SimpleProxy implements Interface {\n", + " private Interface proxied;\n", + "\n", + " SimpleProxy(Interface proxied) {\n", + " this.proxied = proxied;\n", + " }\n", + "\n", + " @Override\n", + " public void doSomething() {\n", + " System.out.println(\"SimpleProxy doSomething\");\n", + " proxied.doSomething();\n", + " }\n", + "\n", + " @Override\n", + " public void somethingElse(String arg) {\n", + " System.out.println(\n", + " \"SimpleProxy somethingElse \" + arg);\n", + " proxied.somethingElse(arg);\n", + " }\n", + "}\n", + "\n", + "class SimpleProxyDemo {\n", + " public static void consumer(Interface iface) {\n", + " iface.doSomething();\n", + " iface.somethingElse(\"bonobo\");\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " consumer(new RealObject());\n", + " consumer(new SimpleProxy(new RealObject()));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "doSomething\n", + "somethingElse bonobo\n", + "SimpleProxy doSomething\n", + "doSomething\n", + "SimpleProxy somethingElse bonobo\n", + "somethingElse bonobo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 `consumer()` 接受 `Interface`,所以它不知道获得的是 `RealObject` 还是 `SimpleProxy`,因为两者都实现了 `Interface`。\n", + "但是,在客户端和 `RealObject` 之间插入的 `SimpleProxy` 执行操作,然后在 `RealObject` 上调用相同的方法。\n", + "\n", + "当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对 `RealObject` 中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。\n", + "\n", + "Java 的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是 `SimpleProxyDemo.java` 使用动态代理重写的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/SimpleDynamicProxy.java\n", + "\n", + "import java.lang.reflect.*;\n", + "\n", + "class DynamicProxyHandler implements InvocationHandler {\n", + " private Object proxied;\n", + "\n", + " DynamicProxyHandler(Object proxied) {\n", + " this.proxied = proxied;\n", + " }\n", + "\n", + " @Override\n", + " public Object\n", + " invoke(Object proxy, Method method, Object[] args)\n", + " throws Throwable {\n", + " System.out.println(\n", + " \"**** proxy: \" + proxy.getClass() +\n", + " \", method: \" + method + \", args: \" + args);\n", + " if (args != null)\n", + " for (Object arg : args)\n", + " System.out.println(\" \" + arg);\n", + " return method.invoke(proxied, args);\n", + " }\n", + "}\n", + "\n", + "class SimpleDynamicProxy {\n", + " public static void consumer(Interface iface) {\n", + " iface.doSomething();\n", + " iface.somethingElse(\"bonobo\");\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " RealObject real = new RealObject();\n", + " consumer(real);\n", + " // Insert a proxy and call again:\n", + " Interface proxy = (Interface) Proxy.newProxyInstance(\n", + " Interface.class.getClassLoader(),\n", + " new Class[]{Interface.class},\n", + " new DynamicProxyHandler(real));\n", + " consumer(proxy);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "doSomething\n", + "somethingElse bonobo\n", + "**** proxy: class $Proxy0, method: public abstract void\n", + "Interface.doSomething(), args: null\n", + "doSomething\n", + "**** proxy: class $Proxy0, method: public abstract void\n", + "Interface.somethingElse(java.lang.String), args:\n", + "[Ljava.lang.Object;@6bc7c054\n", + " bonobo\n", + "somethingElse bonobo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以通过调用静态方法 `Proxy.newProxyInstance()` 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 `InvocationHandler` 的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。\n", + "\n", + "`invoke()` 方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在 `invoke()` 内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。\n", + "\n", + "通常执行代理操作,然后使用 `Method.invoke()` 将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/SelectingMethods.java\n", + "// Looking for particular methods in a dynamic proxy\n", + "\n", + "import java.lang.reflect.*;\n", + "\n", + "class MethodSelector implements InvocationHandler {\n", + " private Object proxied;\n", + "\n", + " MethodSelector(Object proxied) {\n", + " this.proxied = proxied;\n", + " }\n", + "\n", + " @Override\n", + " public Object\n", + " invoke(Object proxy, Method method, Object[] args)\n", + " throws Throwable {\n", + " if (method.getName().equals(\"interesting\"))\n", + " System.out.println(\n", + " \"Proxy detected the interesting method\");\n", + " return method.invoke(proxied, args);\n", + " }\n", + "}\n", + "\n", + "interface SomeMethods {\n", + " void boring1();\n", + "\n", + " void boring2();\n", + "\n", + " void interesting(String arg);\n", + "\n", + " void boring3();\n", + "}\n", + "\n", + "class Implementation implements SomeMethods {\n", + " @Override\n", + " public void boring1() {\n", + " System.out.println(\"boring1\");\n", + " }\n", + "\n", + " @Override\n", + " public void boring2() {\n", + " System.out.println(\"boring2\");\n", + " }\n", + "\n", + " @Override\n", + " public void interesting(String arg) {\n", + " System.out.println(\"interesting \" + arg);\n", + " }\n", + "\n", + " @Override\n", + " public void boring3() {\n", + " System.out.println(\"boring3\");\n", + " }\n", + "}\n", + "\n", + "class SelectingMethods {\n", + " public static void main(String[] args) {\n", + " SomeMethods proxy =\n", + " (SomeMethods) Proxy.newProxyInstance(\n", + " SomeMethods.class.getClassLoader(),\n", + " new Class[]{ SomeMethods.class },\n", + " new MethodSelector(new Implementation()));\n", + " proxy.boring1();\n", + " proxy.boring2();\n", + " proxy.interesting(\"bonobo\");\n", + " proxy.boring3();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "boring1\n", + "boring2\n", + "Proxy detected the interesting method\n", + "interesting bonobo\n", + "boring3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。\n", + "\n", + "动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](./25-Patterns.md)一章。\n", + "\n", + "\n", + "\n", + "## Optional类\n", + "\n", + "如果你使用内置的 `null` 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 `null`,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 `null` 没什么自己的行为,只会在你想用它执行任何操作的时候产生 `NullPointException`。`java.util.Optional`(首次出现是在[函数式编程](docs/book/13-Functional-Programming.md)这章)为 `null` 值提供了一个轻量级代理,`Optional` 对象可以防止你的代码直接抛出 `NullPointException`。\n", + "\n", + "虽然 `Optional` 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。\n", + "\n", + "实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理地假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Person.java\n", + "// Using Optional with regular classes\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.*;\n", + "\n", + "class Person {\n", + " public final Optional first;\n", + " public final Optional last;\n", + " public final Optional address;\n", + " // etc.\n", + " public final Boolean empty;\n", + "\n", + " Person(String first, String last, String address) {\n", + " this.first = Optional.ofNullable(first);\n", + " this.last = Optional.ofNullable(last);\n", + " this.address = Optional.ofNullable(address);\n", + " empty = !this.first.isPresent()\n", + " && !this.last.isPresent()\n", + " && !this.address.isPresent();\n", + " }\n", + "\n", + " Person(String first, String last) {\n", + " this(first, last, null);\n", + " }\n", + "\n", + " Person(String last) {\n", + " this(null, last, null);\n", + " }\n", + "\n", + " Person() {\n", + " this(null, null, null);\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " if (empty)\n", + " return \"\";\n", + " return (first.orElse(\"\") +\n", + " \" \" + last.orElse(\"\") +\n", + " \" \" + address.orElse(\"\")).trim();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(new Person());\n", + " System.out.println(new Person(\"Smith\"));\n", + " System.out.println(new Person(\"Bob\", \"Smith\"));\n", + " System.out.println(new Person(\"Bob\", \"Smith\",\n", + " \"11 Degree Lane, Frostbite Falls, MN\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "Smith\n", + "Bob Smith\n", + "Bob Smith 11 Degree Lane, Frostbite Falls, MN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 字段在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。\n", + "\n", + "如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 字段,这样就不会意外触发 `NullPointException` 了。\n", + "\n", + "现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 `Person Optional` 对象放在每个 `Position` 上:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Position.java\n", + "\n", + "import java.util.*;\n", + "\n", + "class EmptyTitleException extends RuntimeException {\n", + "}\n", + "\n", + "class Position {\n", + " private String title;\n", + " private Person person;\n", + "\n", + " Position(String jobTitle, Person employee) {\n", + " setTitle(jobTitle);\n", + " setPerson(employee);\n", + " }\n", + "\n", + " Position(String jobTitle) {\n", + " this(jobTitle, null);\n", + " }\n", + "\n", + " public String getTitle() {\n", + " return title;\n", + " }\n", + "\n", + " public void setTitle(String newTitle) {\n", + " // Throws EmptyTitleException if newTitle is null:\n", + " title = Optional.ofNullable(newTitle)\n", + " .orElseThrow(EmptyTitleException::new);\n", + " }\n", + "\n", + " public Person getPerson() {\n", + " return person;\n", + " }\n", + "\n", + " public void setPerson(Person newPerson) {\n", + " // Uses empty Person if newPerson is null:\n", + " person = Optional.ofNullable(newPerson)\n", + " .orElse(new Person());\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"Position: \" + title +\n", + " \", Employee: \" + person;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(new Position(\"CEO\"));\n", + " System.out.println(new Position(\"Programmer\",\n", + " new Person(\"Arthur\", \"Fonzarelli\")));\n", + " try {\n", + " new Position(null);\n", + " } catch (Exception e) {\n", + " System.out.println(\"caught \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Position: CEO, Employee: \n", + "Position: Programmer, Employee: Arthur Fonzarelli\n", + "caught EmptyTitleException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通字段,不受 `Optional` 的保护。但是,修改这些字段的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对字段进行了严格的限制。\n", + "\n", + "同时,我们想保证 `title` 字段永远不会变成 `null` 值。为此,我们可以自己在 `setTitle()` 方法里边检查参数 `newTitle` 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 `ofNullable()` 把 `newTitle` 转换一个 `Optional`(如果传入的值为 `null`,`ofNullable()` 返回的将是 `Optional.empty()`)。紧接着我们调用了 `orElseThrow()` 方法,所以如果 `newTitle` 的值是 `null`,你将会得到一个异常。这里我们并没有把 `title` 保存成 `Optional`,但通过应用 `Optional` 的功能,我们仍然如愿以偿地对这个字段施加了约束。\n", + "\n", + "`EmptyTitleException` 是一个 `RuntimeException`,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 `setTitle()` 传 `null` 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 `EmptyTitleException` 还有助于定位 BUG。\n", + "\n", + "`Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Optional`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。\n", + "\n", + "在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段的 `Person` 对象为空,就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。\n", + "\n", + "请注意,虽然你清楚你使用了 `Optional`,可以免受 `NullPointerExceptions` 的困扰,但是 `Staff` 类却对此毫不知情。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Staff.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class Staff extends ArrayList {\n", + " public void add(String title, Person person) {\n", + " add(new Position(title, person));\n", + " }\n", + "\n", + " public void add(String... titles) {\n", + " for (String title : titles)\n", + " add(new Position(title));\n", + " }\n", + "\n", + " public Staff(String... titles) {\n", + " add(titles);\n", + " }\n", + "\n", + " public Boolean positionAvailable(String title) {\n", + " for (Position position : this)\n", + " if (position.getTitle().equals(title) &&\n", + " position.getPerson().empty)\n", + " return true;\n", + " return false;\n", + " }\n", + "\n", + " public void fillPosition(String title, Person hire) {\n", + " for (Position position : this)\n", + " if (position.getTitle().equals(title) &&\n", + " position.getPerson().empty) {\n", + " position.setPerson(hire);\n", + " return;\n", + " }\n", + " throw new RuntimeException(\n", + " \"Position \" + title + \" not available\");\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Staff staff = new Staff(\"President\", \"CTO\",\n", + " \"Marketing Manager\", \"Product Manager\",\n", + " \"Project Lead\", \"Software Engineer\",\n", + " \"Software Engineer\", \"Software Engineer\",\n", + " \"Software Engineer\", \"Test Engineer\",\n", + " \"Technical Writer\");\n", + " staff.fillPosition(\"President\",\n", + " new Person(\"Me\", \"Last\", \"The Top, Lonely At\"));\n", + " staff.fillPosition(\"Project Lead\",\n", + " new Person(\"Janet\", \"Planner\", \"The Burbs\"));\n", + " if (staff.positionAvailable(\"Software Engineer\"))\n", + " staff.fillPosition(\"Software Engineer\",\n", + " new Person(\n", + " \"Bob\", \"Coder\", \"Bright Light City\"));\n", + " System.out.println(staff);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[Position: President, Employee: Me Last The Top, Lonely\n", + "At, Position: CTO, Employee: , Position:\n", + "Marketing Manager, Employee: , Position: Product\n", + "Manager, Employee: , Position: Project Lead,\n", + "Employee: Janet Planner The Burbs, Position: Software\n", + "Engineer, Employee: Bob Coder Bright Light City,\n", + "Position: Software Engineer, Employee: ,\n", + "Position: Software Engineer, Employee: ,\n", + "Position: Software Engineer, Employee: ,\n", + "Position: Test Engineer, Employee: , Position:\n", + "Technical Writer, Employee: ]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,在有些地方你可能还是要测试引用是不是 `Optional`,这跟检查是否为 `null` 没什么不同。但是在其它地方(例如本例中的 `toString()` 转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。\n", + "\n", + "### 标记接口\n", + "\n", + "有时候使用一个**标记接口**来表示空值会更方便。标记接口里边什么都没有,你只要把它的名字当做标签来用就可以。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Null.java\n", + "package onjava;\n", + "public interface Null {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你用接口取代具体类,那么就可以使用 `DynamicProxy` 来自动地创建 `Null` 对象。假设我们有一个 `Robot` 接口,它定义了一个名字、一个模型和一个描述 `Robot` 行为能力的 `List`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Robot.java\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.*;\n", + "\n", + "public interface Robot {\n", + " String name();\n", + "\n", + " String model();\n", + "\n", + " List operations();\n", + "\n", + " static void test(Robot r) {\n", + " if (r instanceof Null)\n", + " System.out.println(\"[Null Robot]\");\n", + " System.out.println(\"Robot name: \" + r.name());\n", + " System.out.println(\"Robot model: \" + r.model());\n", + " for (Operation operation : r.operations()) {\n", + " System.out.println(operation.description.get());\n", + " operation.command.run();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以通过调用 `operations()` 来访问 `Robot` 的服务。`Robot` 里边还有一个 `static` 方法来执行测试。\n", + "\n", + "`Operation` 包含一个描述和一个命令(这用到了**命令模式**)。它们被定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 `Operation` 的构造器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/Operation.java\n", + "\n", + "import java.util.function.*;\n", + "\n", + "public class Operation {\n", + " public final Supplier description;\n", + " public final Runnable command;\n", + "\n", + " public Operation(Supplier descr, Runnable cmd) {\n", + " description = descr;\n", + " command = cmd;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在我们可以创建一个扫雪 `Robot`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/SnowRemovalRobot.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class SnowRemovalRobot implements Robot {\n", + " private String name;\n", + "\n", + " public SnowRemovalRobot(String name) {\n", + " this.name = name;\n", + " }\n", + "\n", + " @Override\n", + " public String name() {\n", + " return name;\n", + " }\n", + "\n", + " @Override\n", + " public String model() {\n", + " return \"SnowBot Series 11\";\n", + " }\n", + "\n", + " private List ops = Arrays.asList(\n", + " new Operation(\n", + " () -> name + \" can shovel snow\",\n", + " () -> System.out.println(\n", + " name + \" shoveling snow\")),\n", + " new Operation(\n", + " () -> name + \" can chip ice\",\n", + " () -> System.out.println(name + \" chipping ice\")),\n", + " new Operation(\n", + " () -> name + \" can clear the roof\",\n", + " () -> System.out.println(\n", + " name + \" clearing roof\")));\n", + "\n", + " public List operations() {\n", + " return ops;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Robot.test(new SnowRemovalRobot(\"Slusher\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Robot name: Slusher\n", + "Robot model: SnowBot Series 11\n", + "Slusher can shovel snow\n", + "Slusher shoveling snow\n", + "Slusher can chip ice\n", + "Slusher chipping ice\n", + "Slusher can clear the roof\n", + "Slusher clearing roof" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "假设存在许多不同类型的 `Robot`,我们想让每种 `Robot` 都创建一个 `Null` 对象来执行一些特殊的操作——在本例中,即提供 `Null` 对象所代表 `Robot` 的确切类型信息。这些信息是通过动态代理捕获的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/NullRobot.java\n", + "// Using a dynamic proxy to create an Optional\n", + "\n", + "import java.lang.reflect.*;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "import onjava.*;\n", + "\n", + "class NullRobotProxyHandler\n", + " implements InvocationHandler {\n", + " private String nullName;\n", + " private Robot proxied = new NRobot();\n", + "\n", + " NullRobotProxyHandler(Class type) {\n", + " nullName = type.getSimpleName() + \" NullRobot\";\n", + " }\n", + "\n", + " private class NRobot implements Null, Robot {\n", + " @Override\n", + " public String name() {\n", + " return nullName;\n", + " }\n", + "\n", + " @Override\n", + " public String model() {\n", + " return nullName;\n", + " }\n", + "\n", + " @Override\n", + " public List operations() {\n", + " return Collections.emptyList();\n", + " }\n", + " }\n", + "\n", + " @Override\n", + " public Object\n", + " invoke(Object proxy, Method method, Object[] args)\n", + " throws Throwable {\n", + " return method.invoke(proxied, args);\n", + " }\n", + "}\n", + "\n", + "public class NullRobot {\n", + " public static Robot\n", + " newNullRobot(Class type) {\n", + " return (Robot) Proxy.newProxyInstance(\n", + " NullRobot.class.getClassLoader(),\n", + " new Class[] { Null.class, Robot.class },\n", + " new NullRobotProxyHandler(type));\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Stream.of(\n", + " new SnowRemovalRobot(\"SnowBee\"),\n", + " newNullRobot(SnowRemovalRobot.class)\n", + " ).forEach(Robot::test);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Robot name: SnowBee\n", + "Robot model: SnowBot Series 11\n", + "SnowBee can shovel snow\n", + "SnowBee shoveling snow\n", + "SnowBee can chip ice\n", + "SnowBee chipping ice\n", + "SnowBee can clear the roof\n", + "SnowBee clearing roof\n", + "[Null Robot]\n", + "Robot name: SnowRemovalRobot NullRobot\n", + "Robot model: SnowRemovalRobot NullRobot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "无论何时,如果你需要一个空 `Robot` 对象,只需要调用 `newNullRobot()`,并传递需要代理的 `Robot` 的类型。这个代理满足了 `Robot` 和 `Null` 接口的需要,并提供了它所代理的类型的确切名字。\n", + "\n", + "### Mock 对象和桩\n", + "\n", + "**Mock 对象**和 **桩(Stub)**在逻辑上都是 `Optional` 的变体。他们都是最终程序中所使用的“实际”对象的代理。不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 `Optional` 那样把包含潜在 `null` 值的对象隐藏。\n", + "\n", + "Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。\n", + "\n", + "\n", + "## 接口和类型\n", + "\n", + "`interface` 关键字的一个重要目标就是允许程序员隔离组件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/interfacea/A.java\n", + "package typeinfo.interfacea;\n", + "\n", + "public interface A {\n", + " void f();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后实现这个接口,你可以看到其代码是怎么从实际类型开始顺藤摸瓜的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/InterfaceViolation.java\n", + "// Sneaking around an interface\n", + "\n", + "import typeinfo.interfacea.*;\n", + "\n", + "class B implements A {\n", + " public void f() {\n", + " }\n", + "\n", + " public void g() {\n", + " }\n", + "}\n", + "\n", + "public class InterfaceViolation {\n", + " public static void main(String[] args) {\n", + " A a = new B();\n", + " a.f();\n", + " // a.g(); // Compile error\n", + " System.out.println(a.getClass().getName());\n", + " if (a instanceof B) {\n", + " B b = (B) a;\n", + " b.g();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "B" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过使用 RTTI,我们发现 `a` 是用 `B` 实现的。通过将其转型为 `B`,我们可以调用不在 `A` 中的方法。\n", + "\n", + "这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 `interface` 关键字正在保护你,但其实并没有。另外,在本例中使用 `B` 来实现 `A` 这种情况是有公开案例可查的[^3]。\n", + "\n", + "一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是可行的,但“可能”还不够,你或许希望能有一些更严格的控制方式。\n", + "\n", + "最简单的方式是让实现类只具有包访问权限,这样在包外部的客户端就看不到它了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/packageaccess/HiddenC.java\n", + "package typeinfo.packageaccess;\n", + "\n", + "import typeinfo.interfacea.*;\n", + "\n", + "class C implements A {\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"public C.f()\");\n", + " }\n", + "\n", + " public void g() {\n", + " System.out.println(\"public C.g()\");\n", + " }\n", + "\n", + " void u() {\n", + " System.out.println(\"package C.u()\");\n", + " }\n", + "\n", + " protected void v() {\n", + " System.out.println(\"protected C.v()\");\n", + " }\n", + "\n", + " private void w() {\n", + " System.out.println(\"private C.w()\");\n", + " }\n", + "}\n", + "\n", + "public class HiddenC {\n", + " public static A makeA() {\n", + " return new C();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个包中唯一 `public` 的部分就是 `HiddenC`,在被调用时将产生 `A`接口类型的对象。这里有趣之处在于:即使你从 `makeA()` 返回的是 `C` 类型,你在包的外部仍旧不能使用 `A` 之外的任何方法,因为你不能在包的外部命名 `C`。\n", + "\n", + "现在如果你试着将其向下转型为 `C`,则将被禁止,因为在包的外部没有任何 `C` 类型可用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/HiddenImplementation.java\n", + "// Sneaking around package hiding\n", + "\n", + "import typeinfo.interfacea.*;\n", + "import typeinfo.packageaccess.*;\n", + "\n", + "import java.lang.reflect.*;\n", + "\n", + "public class HiddenImplementation {\n", + " public static void main(String[] args) throws Exception {\n", + " A a = HiddenC.makeA();\n", + " a.f();\n", + " System.out.println(a.getClass().getName());\n", + " // Compile error: cannot find symbol 'C':\n", + " /* if(a instanceof C) {\n", + " C c = (C)a;\n", + " c.g();\n", + " } */\n", + " // Oops! Reflection still allows us to call g():\n", + " callHiddenMethod(a, \"g\");\n", + " // And even less accessible methods!\n", + " callHiddenMethod(a, \"u\");\n", + " callHiddenMethod(a, \"v\");\n", + " callHiddenMethod(a, \"w\");\n", + " }\n", + "\n", + " static void callHiddenMethod(Object a, String methodName) throws Exception {\n", + " Method g = a.getClass().getDeclaredMethod(methodName);\n", + " g.setAccessible(true);\n", + " g.invoke(a);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "public C.f()\n", + "typeinfo.packageaccess.C\n", + "public C.g()\n", + "package C.u()\n", + "protected C.v()\n", + "private C.w()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正如你所看到的,通过使用反射,仍然可以调用所有方法,甚至是 `private` 方法!如果知道方法名,你就可以在其 `Method` 对象上调用 `setAccessible(true)`,就像在 `callHiddenMethod()` 中看到的那样。\n", + "\n", + "你可能觉得,可以通过只发布编译后的代码来阻止这种情况,但其实这并不能解决问题。因为只需要运行 `javap`(一个随 JDK 发布的反编译器)即可突破这一限制。下面是一个使用 `javap` 的命令行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "javap -private C" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`-private` 标志表示所有的成员都应该显示,甚至包括私有成员。下面是输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class typeinfo.packageaccess.C extends\n", + "java.lang.Object implements typeinfo.interfacea.A {\n", + " typeinfo.packageaccess.C();\n", + " public void f();\n", + " public void g();\n", + " void u();\n", + " protected void v();\n", + " private void w();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此,任何人都可以获取你最私有的方法的名字和签名,然后调用它们。\n", + "\n", + "那如果把接口实现为一个私有内部类,又会怎么样呢?下面展示了这种情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/InnerImplementation.java\n", + "// Private inner classes can't hide from reflection\n", + "\n", + "import typeinfo.interfacea.*;\n", + "\n", + "class InnerA {\n", + " private static class C implements A {\n", + " public void f() {\n", + " System.out.println(\"public C.f()\");\n", + " }\n", + "\n", + " public void g() {\n", + " System.out.println(\"public C.g()\");\n", + " }\n", + "\n", + " void u() {\n", + " System.out.println(\"package C.u()\");\n", + " }\n", + "\n", + " protected void v() {\n", + " System.out.println(\"protected C.v()\");\n", + " }\n", + "\n", + " private void w() {\n", + " System.out.println(\"private C.w()\");\n", + " }\n", + " }\n", + "\n", + " public static A makeA() {\n", + " return new C();\n", + " }\n", + "}\n", + "\n", + "public class InnerImplementation {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " A a = InnerA.makeA();\n", + " a.f();\n", + " System.out.println(a.getClass().getName());\n", + " // Reflection still gets into the private class:\n", + " HiddenImplementation.callHiddenMethod(a, \"g\");\n", + " HiddenImplementation.callHiddenMethod(a, \"u\");\n", + " HiddenImplementation.callHiddenMethod(a, \"v\");\n", + " HiddenImplementation.callHiddenMethod(a, \"w\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "public C.f()\n", + "InnerA$C\n", + "public C.g()\n", + "package C.u()\n", + "protected C.v()\n", + "private C.w()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里对反射仍然没有任何东西可以隐藏。那么如果是匿名类呢?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/AnonymousImplementation.java\n", + "// Anonymous inner classes can't hide from reflection\n", + "\n", + "import typeinfo.interfacea.*;\n", + "\n", + "class AnonymousA {\n", + " public static A makeA() {\n", + " return new A() {\n", + " public void f() {\n", + " System.out.println(\"public C.f()\");\n", + " }\n", + "\n", + " public void g() {\n", + " System.out.println(\"public C.g()\");\n", + " }\n", + "\n", + " void u() {\n", + " System.out.println(\"package C.u()\");\n", + " }\n", + "\n", + " protected void v() {\n", + " System.out.println(\"protected C.v()\");\n", + " }\n", + "\n", + " private void w() {\n", + " System.out.println(\"private C.w()\");\n", + " }\n", + " };\n", + " }\n", + "}\n", + "\n", + "public class AnonymousImplementation {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " A a = AnonymousA.makeA();\n", + " a.f();\n", + " System.out.println(a.getClass().getName());\n", + " // Reflection still gets into the anonymous class:\n", + " HiddenImplementation.callHiddenMethod(a, \"g\");\n", + " HiddenImplementation.callHiddenMethod(a, \"u\");\n", + " HiddenImplementation.callHiddenMethod(a, \"v\");\n", + " HiddenImplementation.callHiddenMethod(a, \"w\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "public C.f()\n", + "AnonymousA$1\n", + "public C.g()\n", + "package C.u()\n", + "protected C.v()\n", + "private C.w()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于字段来说也是这样,即便是 `private` 字段:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/ModifyingPrivateFields.java\n", + "\n", + "import java.lang.reflect.*;\n", + "\n", + "class WithPrivateFinalField {\n", + " private int i = 1;\n", + " private final String s = \"I'm totally safe\";\n", + " private String s2 = \"Am I safe?\";\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"i = \" + i + \", \" + s + \", \" + s2;\n", + " }\n", + "}\n", + "\n", + "public class ModifyingPrivateFields {\n", + " public static void main(String[] args) throws Exception {\n", + " WithPrivateFinalField pf =\n", + " new WithPrivateFinalField();\n", + " System.out.println(pf);\n", + " Field f = pf.getClass().getDeclaredField(\"i\");\n", + " f.setAccessible(true);\n", + " System.out.println(\n", + " \"f.getInt(pf): \" + f.getInt(pf));\n", + " f.setInt(pf, 47);\n", + " System.out.println(pf);\n", + " f = pf.getClass().getDeclaredField(\"s\");\n", + " f.setAccessible(true);\n", + " System.out.println(\"f.get(pf): \" + f.get(pf));\n", + " f.set(pf, \"No, you're not!\");\n", + " System.out.println(pf);\n", + " f = pf.getClass().getDeclaredField(\"s2\");\n", + " f.setAccessible(true);\n", + " System.out.println(\"f.get(pf): \" + f.get(pf));\n", + " f.set(pf, \"No, you're not!\");\n", + " System.out.println(pf);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 1, I'm totally safe, Am I safe?\n", + "f.getInt(pf): 1\n", + "i = 47, I'm totally safe, Am I safe?\n", + "f.get(pf): I'm totally safe\n", + "i = 47, I'm totally safe, Am I safe?\n", + "f.get(pf): Am I safe?\n", + "i = 47, I'm totally safe, No, you're not!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但实际上 `final` 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。\n", + "\n", + "通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,反射给我们带来了很多好处。\n", + "\n", + "程序员往往对编程语言提供的访问控制过于自信,甚至认为 Java 在安全性上比其它提供了(明显)更宽松的访问控制的语言要优越[^4]。然而,正如你所看到的,事实并不是这样。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch` 语句,你可以用 RTTI 和 `switch` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能地使用多态机制,只在非用不可的时候才使用 RTTI。\n", + "\n", + "然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 `switch` 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。\n", + "\n", + "如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 `Instrument` 的类层次结构。假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 `Instrument` 中放入 `clearSpitValve()` 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 `Percussion`、弦乐器 `Stringed` 和电子乐器 `Electronic` 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 `clearSpitValve()` 放在某个合适的类中,在这个例子中是管乐器 `Wind`。不过,在这里你可能会发现还有更好的解决方法,就是将 `prepareInstrument()` 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。\n", + "\n", + "最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早地关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)[^5]。\n", + "\n", + "我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想得更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只要你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。\n", + "\n", + "[^1]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。\n", + "\n", + "[^2]: 这是极限编程(XP,Extreme Programming)的原则之一:“Try the simplest thing that could possibly work,实现尽最大可能的简单。”\n", + "\n", + "[^3]: 最著名的例子是 Windows 操作系统,Windows 为开发者提供了公开的 API,但是开发者还可以找到一些非公开但是可以调用的函数。为了解决问题,很多程序员使用了隐藏的 API 函数。这就迫使微软公司要像维护公开 API 一样维护这些隐藏的 API,消耗了巨大的成本和精力。\n", + "\n", + "[^4]: 比如,Python 中在元素前面添加双下划线 `__`,就表示你想隐藏这个元素。如果你在类或者包外面调用了这个元素,运行环境就会报错。\n", + "\n", + "[^5]: 译者注:Java Profiler 是一种 Java 性能分析工具,用于在 JVM 级别监视 Java 字节码的构造和执行。主流的 Profiler 有 JProfiler、YourKit 和 Java VisualVM 等。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/20-Generics.ipynb b/jupyter/20-Generics.ipynb new file mode 100644 index 00000000..17efcaf7 --- /dev/null +++ b/jupyter/20-Generics.ipynb @@ -0,0 +1,7433 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第二十章 泛型\n", + "\n", + "> 普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。\n", + "\n", + "多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了 `final` 类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。\n", + "\n", + "拘泥于单一的继承体系太过局限,因为只有继承体系中的对象才能适用基类作为参数的方法中。如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。这给予调用方一种选项,通过调整现有的类来实现接口,满足方法参数要求。接口可以突破继承体系的限制。\n", + "\n", + "即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。\n", + "\n", + "这就是泛型的概念,是 Java 5 的重大变化之一。泛型实现了*参数化类型*,这样你编写的组件(通常是集合)可以适用于多种类型。“泛型”这个术语的含义是“适用于很多类型”。编程语言中泛型出现的初衷是通过解耦类或方法与所使用的类型之间的约束,使得类或方法具备最宽泛的表达力。随后你会发现 Java 中泛型的实现并没有那么“泛”,你可能会质疑“泛型”这个词是否合适用来描述这一功能。\n", + "\n", + "如果你从未接触过参数化类型机制,你会发现泛型对 Java 语言确实是个很有益的补充。在你实例化一个类型参数时,编译器会负责转型并确保类型的正确性。这是一大进步。\n", + "\n", + "然而,如果你了解其他语言(例如 C++ )的参数化机制,你会发现,Java 泛型并不能满足所有的预期。使用别人创建好的泛型相对容易,但是创建自己的泛型时,就会遇到很多意料之外的麻烦。\n", + "\n", + "这并不是说 Java 泛型毫无用处。在很多情况下,它可以使代码更直接更优雅。不过,如果你见识过那种实现了更纯粹的泛型的编程语言,那么,Java 可能会令你失望。本章会介绍 Java 泛型的优点与局限。我会解释 Java 的泛型是如何发展成现在这样的,希望能够帮助你更有效地使用这个特性。[^1]\n", + "\n", + "### 与 C++ 的比较\n", + "\n", + "Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。\n", + "\n", + "但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里)。\n", + "\n", + "第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。\n", + "\n", + "因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。\n", + "\n", + "\n", + "\n", + "## 简单泛型\n", + "\n", + "促成泛型出现的最主要的动机之一是为了创建*集合类*,参见 [集合](book/12-Collections.md) 章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。\n", + "\n", + "我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Holder1.java\n", + "\n", + "class Automobile {}\n", + "\n", + "public class Holder1 {\n", + " private Automobile a;\n", + " public Holder1(Automobile a) { this.a = a; }\n", + " Automobile get() { return a; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类。\n", + "\n", + "在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ObjectHolder.java\n", + "\n", + "public class ObjectHolder {\n", + " private Object a;\n", + " public ObjectHolder(Object a) { this.a = a; }\n", + " public void set(Object a) { this.a = a; }\n", + " public Object get() { return a; }\n", + " \n", + " public static void main(String[] args) {\n", + " ObjectHolder h2 = new ObjectHolder(new Automobile());\n", + " Automobile a = (Automobile)h2.get();\n", + " h2.set(\"Not an Automobile\");\n", + " String s = (String)h2.get();\n", + " h2.set(1); // 自动装箱为 Integer\n", + " Integer x = (Integer)h2.get();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,`ObjectHolder` 可以持有任何类型的对象,在上面的示例中,一个 `ObjectHolder` 先后持有了三种不同类型的对象。\n", + "\n", + "一个集合中存储多种不同类型的对象的情况很少见,通常而言,我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。\n", + "\n", + "因此,与其使用 `Object` ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用*类型参数*,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中,`T` 就是类型参数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericHolder.java\n", + "\n", + "public class GenericHolder {\n", + " private T a;\n", + " public GenericHolder() {}\n", + " public void set(T a) { this.a = a; }\n", + " public T get() { return a; }\n", + " \n", + " public static void main(String[] args) {\n", + " GenericHolder h3 = new GenericHolder();\n", + " h3.set(new Automobile()); // 此处有类型校验\n", + " Automobile a = h3.get(); // 无需类型转换\n", + " //- h3.set(\"Not an Automobile\"); // 报错\n", + " //- h3.set(1); // 报错\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。\n", + "\n", + "这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。\n", + "\n", + "你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Diamond.java\n", + "\n", + "class Bob {}\n", + "\n", + "public class Diamond {\n", + " public static void main(String[] args) {\n", + " GenericHolder h3 = new GenericHolder<>();\n", + " h3.set(new Bob());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。\n", + "\n", + "一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定它们的名称和类型参数列表即可。\n", + "\n", + "### 一个元组类库\n", + "\n", + "有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。\n", + "\n", + "这个概念称为*元组*,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象* 或 *信使* )。\n", + "\n", + "通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Tuple2.java\n", + "package onjava;\n", + "\n", + "public class Tuple2 {\n", + " public final A a1;\n", + " public final B a2;\n", + " public Tuple2(A a, B b) { a1 = a; a2 = b; }\n", + " public String rep() { return a1 + \", \" + a2; }\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return \"(\" + rep() + \")\";\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "构造函数传入要存储的对象。这个元组隐式地保持了其中元素的次序。\n", + "\n", + "初次阅读上面的代码时,你可能认为这违反了 Java 编程的封装原则。`a1` 和 `a2` 应该声明为 **private**,然后提供 `getFirst()` 和 `getSecond()` 取值方法才对呀?考虑下这样做能提供的“安全性”是什么:元组的使用程序可以读取 `a1` 和 `a2` 然后对它们执行任何操作,但无法对 `a1` 和 `a2` 重新赋值。例子中的 `final` 可以实现同样的效果,并且更为简洁明了。\n", + "\n", + "另一种设计思路是允许元组的用户给 `a1` 和 `a2` 重新赋值。然而,采用上例中的形式无疑更加安全,如果用户想存储不同的元素,就会强制他们创建新的 `Tuple2` 对象。\n", + "\n", + "我们可以利用继承机制实现长度更长的元组。添加更多的类型参数就行了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Tuple3.java\n", + "package onjava;\n", + "\n", + "public class Tuple3 extends Tuple2 {\n", + " public final C a3;\n", + " public Tuple3(A a, B b, C c) {\n", + " super(a, b);\n", + " a3 = c;\n", + " }\n", + " \n", + " @Override\n", + " public String rep() {\n", + " return super.rep() + \", \" + a3;\n", + " }\n", + "}\n", + "\n", + "// onjava/Tuple4.java\n", + "package onjava;\n", + "\n", + "public class Tuple4\n", + " extends Tuple3 {\n", + " public final D a4;\n", + " public Tuple4(A a, B b, C c, D d) {\n", + " super(a, b, c);\n", + " a4 = d;\n", + " }\n", + " \n", + " @Override\n", + " public String rep() {\n", + " return super.rep() + \", \" + a4;\n", + " }\n", + "}\n", + "\n", + "// onjava/Tuple5.java\n", + "package onjava;\n", + "\n", + "public class Tuple5\n", + " extends Tuple4 {\n", + " public final E a5;\n", + " public Tuple5(A a, B b, C c, D d, E e) {\n", + " super(a, b, c, d);\n", + " a5 = e;\n", + " }\n", + " \n", + " @Override\n", + " public String rep() {\n", + " return super.rep() + \", \" + a5;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "演示需要,再定义两个类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Amphibian.java\n", + "public class Amphibian {}\n", + "\n", + "// generics/Vehicle.java\n", + "public class Vehicle {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用元组时,你只需要定义一个长度适合的元组,将其作为返回值即可。注意下面例子中方法的返回类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/TupleTest.java\n", + "import onjava.*;\n", + "\n", + "public class TupleTest {\n", + " static Tuple2 f() {\n", + " // 47 自动装箱为 Integer\n", + " return new Tuple2<>(\"hi\", 47);\n", + " }\n", + " \n", + " static Tuple3 g() {\n", + " return new Tuple3<>(new Amphibian(), \"hi\", 47);\n", + " }\n", + " \n", + " static Tuple4 h() {\n", + " return new Tuple4<>(new Vehicle(), new Amphibian(), \"hi\", 47);\n", + " }\n", + " \n", + " static Tuple5 k() {\n", + " return new Tuple5<>(new Vehicle(), new Amphibian(), \"hi\", 47, 11.1);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Tuple2 ttsi = f();\n", + " System.out.println(ttsi);\n", + " // ttsi.a1 = \"there\"; // 编译错误,因为 final 不能重新赋值\n", + " System.out.println(g());\n", + " System.out.println(h());\n", + " System.out.println(k());\n", + " }\n", + "}\n", + "\n", + "/* 输出:\n", + " (hi, 47)\n", + " (Amphibian@1540e19d, hi, 47)\n", + " (Vehicle@7f31245a, Amphibian@6d6f6e28, hi, 47)\n", + " (Vehicle@330bedb4, Amphibian@2503dbd3, hi, 47, 11.1)\n", + " */" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有了泛型,你可以很容易地创建元组,令其返回一组任意类型的对象。\n", + "\n", + "通过 `ttsi.a1 = \"there\"` 语句的报错,我们可以看出,**final** 声明确实可以确保 **public** 字段在对象被构造出来之后就不能重新赋值了。\n", + "\n", + "在上面的程序中,`new` 表达式有些啰嗦。本章稍后会介绍,如何利用 *泛型方法* 简化它们。\n", + "\n", + "### 一个堆栈类\n", + "\n", + "接下来我们看一个稍微复杂一点的例子:堆栈。在 [集合](book/12-Collections.md) 一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。\n", + "\n", + "这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/LinkedStack.java\n", + "// 用链式结构实现的堆栈\n", + "\n", + "public class LinkedStack {\n", + " private static class Node {\n", + " U item;\n", + " Node next;\n", + " \n", + " Node() { item = null; next = null; }\n", + " \n", + " Node(U item, Node next) {\n", + " this.item = item;\n", + " this.next = next;\n", + " }\n", + " \n", + " boolean end() {\n", + " return item == null && next == null;\n", + " }\n", + " }\n", + " \n", + " private Node top = new Node<>(); // 栈顶\n", + " \n", + " public void push(T item) {\n", + " top = new Node<>(item, top);\n", + " }\n", + " \n", + " public T pop() {\n", + " T result = top.item;\n", + " if (!top.end()) {\n", + " top = top.next;\n", + " }\n", + " return result;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " LinkedStack lss = new LinkedStack<>();\n", + " for (String s : \"Phasers on stun!\".split(\" \")) {\n", + " lss.push(s);\n", + " }\n", + " String s;\n", + " while ((s = lss.pop()) != null) {\n", + " System.out.println(s);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "stun!\n", + "on\n", + "Phasers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "内部类 `Node` 也是一个泛型,它拥有自己的类型参数。\n", + "\n", + "这个例子使用了一个 *末端标识* (end sentinel) 来判断栈何时为空。这个末端标识是在构造 `LinkedStack` 时创建的。然后,每次调用 `push()` 就会创建一个 `Node` 对象,并将其链接到前一个 `Node` 对象。当你调用 `pop()` 方法时,总是返回 `top.item`,然后丢弃当前 `top` 所指向的 `Node`,并将 `top` 指向下一个 `Node`,除非到达末端标识,这时就不能再移动 `top` 了。如果已经到达末端,程序还继续调用 `pop()` 方法,它只能得到 `null`,说明栈已经空了。\n", + "\n", + "### RandomList\n", + "\n", + "作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用它的 `select()` 方法时都随机返回一个元素。如果希望这种列表可以适用于各种类型,就需要使用泛型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/RandomList.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class RandomList extends ArrayList {\n", + " private Random rand = new Random(47);\n", + " \n", + " public T select() {\n", + " return get(rand.nextInt(size()));\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " RandomList rs = new RandomList<>();\n", + " Arrays.stream(\"The quick brown fox jumped over the lazy brown dog\".split(\" \")).forEach(rs::add);\n", + " IntStream.range(0, 11).forEach(i -> \n", + " System.out.print(rs.select() + \" \"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "brown over fox quick quick dog brown The brown lazy brown" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。\n", + "\n", + "\n", + "\n", + "## 泛型接口\n", + "\n", + "泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。\n", + "\n", + "一般而言,一个生成器只定义一个方法,用于创建对象。例如 `java.util.function` 类库中的 `Supplier` 就是一个生成器,调用其 `get()` 获取对象。`get()` 是泛型方法,返回值为类型参数 `T`。\n", + "\n", + "为了演示 `Supplier`,我们需要定义几个类。下面是个咖啡相关的继承体系:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/coffee/Coffee.java\n", + "package generics.coffee;\n", + "\n", + "public class Coffee {\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + " \n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName() + \" \" + id;\n", + " }\n", + "}\n", + "\n", + "\n", + "// generics/coffee/Latte.java\n", + "package generics.coffee;\n", + "public class Latte extends Coffee {}\n", + "\n", + "\n", + "// generics/coffee/Mocha.java\n", + "package generics.coffee;\n", + "public class Mocha extends Coffee {}\n", + "\n", + "\n", + "// generics/coffee/Cappuccino.java\n", + "package generics.coffee;\n", + "public class Cappuccino extends Coffee {}\n", + "\n", + "\n", + "// generics/coffee/Americano.java\n", + "package generics.coffee;\n", + "public class Americano extends Coffee {}\n", + "\n", + "\n", + "// generics/coffee/Breve.java\n", + "package generics.coffee;\n", + "public class Breve extends Coffee {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,我们可以编写一个类,实现 `Supplier` 接口,它能够随机生成不同类型的 `Coffee` 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/coffee/CoffeeSupplier.java\n", + "// {java generics.coffee.CoffeeSupplier}\n", + "package generics.coffee;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class CoffeeSupplier\n", + "implements Supplier, Iterable {\n", + " private Class[] types = { Latte.class, Mocha.class, \n", + " Cappuccino.class, Americano.class, Breve.class };\n", + " private static Random rand = new Random(47);\n", + " \n", + " public CoffeeSupplier() {}\n", + " // For iteration:\n", + " private int size = 0;\n", + " public CoffeeSupplier(int sz) { size = sz; }\n", + " \n", + " @Override\n", + " public Coffee get() {\n", + " try {\n", + " return (Coffee) types[rand.nextInt(types.length)].newInstance();\n", + " } catch (InstantiationException | IllegalAccessException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " \n", + " class CoffeeIterator implements Iterator {\n", + " int count = size;\n", + " @Override\n", + " public boolean hasNext() { return count > 0; }\n", + " @Override\n", + " public Coffee next() {\n", + " count--;\n", + " return CoffeeSupplier.this.get();\n", + " }\n", + " @Override\n", + " public void remove() {\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " }\n", + " \n", + " @Override\n", + " public Iterator iterator() {\n", + " return new CoffeeIterator();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Stream.generate(new CoffeeSupplier())\n", + " .limit(5)\n", + " .forEach(System.out::println);\n", + " for (Coffee c : new CoffeeSupplier(5)) {\n", + " System.out.println(c);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Americano 0\n", + "Latte 1\n", + "Americano 2\n", + "Mocha 3\n", + "Mocha 4\n", + "Breve 5\n", + "Americano 6\n", + "Latte 7\n", + "Cappuccino 8\n", + "Cappuccino 9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "参数化的 `Supplier` 接口确保 `get()` 返回值是参数的类型。`CoffeeSupplier` 同时还实现了 `Iterable` 接口,所以能用于 *for-in* 语句。不过,它还需要知道何时终止循环,这正是第二个构造函数的作用。\n", + "\n", + "下面是另一个实现 `Supplier` 接口的例子,它负责生成 Fibonacci 数列:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Fibonacci.java\n", + "// Generate a Fibonacci sequence\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class Fibonacci implements Supplier {\n", + " private int count = 0;\n", + " @Override\n", + " public Integer get() { return fib(count++); }\n", + " \n", + " private int fib(int n) {\n", + " if(n < 2) return 1;\n", + " return fib(n-2) + fib(n-1);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Stream.generate(new Fibonacci())\n", + " .limit(18)\n", + " .map(n -> n + \" \")\n", + " .forEach(System.out::print);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。\n", + "\n", + "如果还想更进一步,编写一个实现了 `Iterable` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterable` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。\n", + "\n", + "有多种方法可以实现适配器。例如,可以通过继承来创建适配器类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/IterableFibonacci.java\n", + "// Adapt the Fibonacci class to make it Iterable\n", + "import java.util.*;\n", + "\n", + "public class IterableFibonacci\n", + "extends Fibonacci implements Iterable {\n", + " private int n;\n", + " public IterableFibonacci(int count) { n = count; }\n", + " \n", + " @Override\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " @Override\n", + " public boolean hasNext() { return n > 0; }\n", + " @Override\n", + " public Integer next() {\n", + " n--;\n", + " return IterableFibonacci.this.get();\n", + " }\n", + " @Override\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " for(int i : new IterableFibonacci(18))\n", + " System.out.print(i + \" \");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 *for-in* 语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。\n", + "\n", + "\n", + "\n", + "## 泛型方法\n", + "\n", + "到目前为止,我们已经研究了参数化整个类。其实还可以参数化类中的方法。类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型的并没有什么关系。\n", + "\n", + "泛型方法独立于类而改变方法。作为准则,请“尽可能”使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。\n", + "\n", + "如果方法是 **static** 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。\n", + "\n", + "要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericMethods.java\n", + "\n", + "public class GenericMethods {\n", + " public void f(T x) {\n", + " System.out.println(x.getClass().getName());\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " GenericMethods gm = new GenericMethods();\n", + " gm.f(\"\");\n", + " gm.f(1);\n", + " gm.f(1.0);\n", + " gm.f(1.0F);\n", + " gm.f('c');\n", + " gm.f(gm);\n", + " }\n", + "}\n", + "/* Output:\n", + "java.lang.String\n", + "java.lang.Integer\n", + "java.lang.Double\n", + "java.lang.Float\n", + "java.lang.Character\n", + "GenericMethods\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管可以同时对类及其方法进行参数化,但这里未将 **GenericMethods** 类参数化。只有方法 `f()` 具有类型参数,该参数由方法返回类型之前的参数列表指示。\n", + "\n", + "对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对 `f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。\n", + "\n", + "如果使用基本类型调用 `f()` ,自动装箱就开始起作用,自动将基本类型包装在它们对应的包装类型中。\n", + "\n", + "\n", + "### 变长参数和泛型方法\n", + "\n", + "泛型方法和变长参数列表可以很好地共存:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericVarargs.java\n", + "\n", + "import java.util.ArrayList;\n", + "import java.util.List;\n", + "\n", + "public class GenericVarargs {\n", + " @SafeVarargs\n", + " public static List makeList(T... args) {\n", + " List result = new ArrayList<>();\n", + " for (T item : args)\n", + " result.add(item);\n", + " return result;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " List ls = makeList(\"A\");\n", + " System.out.println(ls);\n", + " ls = makeList(\"A\", \"B\", \"C\");\n", + " System.out.println(ls);\n", + " ls = makeList(\n", + " \"ABCDEFFHIJKLMNOPQRSTUVWXYZ\".split(\"\"));\n", + " System.out.println(ls);\n", + " }\n", + "}\n", + "/* Output:\n", + "[A]\n", + "[A, B, C]\n", + "[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R,\n", + "S, T, U, V, W, X, Y, Z]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此处显示的 `makeList()` 方法产生的功能与标准库的 `java.util.Arrays.asList()` 方法相同。\n", + "\n", + "`@SafeVarargs` 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。\n", + "\n", + "\n", + "### 一个泛型的 Supplier\n", + "\n", + "这是一个为任意具有无参构造方法的类生成 **Supplier** 的类。为了减少键入,它还包括一个用于生成 **BasicSupplier** 的泛型方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/BasicSupplier.java\n", + "// Supplier from a class with a no-arg constructor\n", + "package onjava;\n", + "\n", + "import java.util.function.Supplier;\n", + "\n", + "public class BasicSupplier implements Supplier {\n", + " private Class type;\n", + "\n", + " public BasicSupplier(Class type) {\n", + " this.type = type;\n", + " }\n", + "\n", + " @Override\n", + " public T get() {\n", + " try {\n", + " // Assumes type is a public class:\n", + " return type.newInstance();\n", + " } catch (InstantiationException |\n", + " IllegalAccessException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " // Produce a default Supplier from a type token:\n", + " public static Supplier create(Class type) {\n", + " return new BasicSupplier<>(type);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "此类提供了产生以下对象的基本实现:\n", + "\n", + "1. 是 **public** 的。 因为 **BasicSupplier** 在单独的包中,所以相关的类必须具有 **public** 权限,而不仅仅是包级访问权限。\n", + "\n", + "2. 具有无参构造方法。要创建一个这样的 **BasicSupplier** 对象,请调用 `create()` 方法,并将要生成类型的类型令牌传递给它。通用的 `create()` 方法提供了 `BasicSupplier.create(MyType.class)` 这种较简洁的语法来代替较笨拙的 `new BasicSupplier (MyType.class)`。\n", + "\n", + "例如,这是一个具有无参构造方法的简单类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CountedObject.java\n", + "\n", + "public class CountedObject {\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + "\n", + " public long id() {\n", + " return id;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"CountedObject \" + id;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**CountedObject** 类可以跟踪自身创建了多少个实例,并通过 `toString()` 报告这些实例的数量。 **BasicSupplier** 可以轻松地为 **CountedObject** 创建 **Supplier**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + " // generics/BasicSupplierDemo.java\n", + "\n", + "import onjava.BasicSupplier;\n", + "\n", + "import java.util.stream.Stream;\n", + "\n", + "public class BasicSupplierDemo {\n", + " public static void main(String[] args) {\n", + " Stream.generate(\n", + " BasicSupplier.create(CountedObject.class))\n", + " .limit(5)\n", + " .forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "CountedObject 0\n", + "CountedObject 1\n", + "CountedObject 2\n", + "CountedObject 3\n", + "CountedObject 4\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "泛型方法减少了产生 **Supplier** 对象所需的代码量。 Java 泛型强制传递 **Class** 对象,以便在 `create()` 方法中将其用于类型推断。\n", + "\n", + "\n", + "### 简化元组的使用\n", + "\n", + "使用类型参数推断和静态导入,我们将把早期的元组重写为更通用的库。在这里,我们使用重载的静态方法创建元组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Tuple.java\n", + "// Tuple library using type argument inference\n", + "package onjava;\n", + "\n", + "public class Tuple {\n", + " public static Tuple2 tuple(A a, B b) {\n", + " return new Tuple2<>(a, b);\n", + " }\n", + "\n", + " public static Tuple3\n", + " tuple(A a, B b, C c) {\n", + " return new Tuple3<>(a, b, c);\n", + " }\n", + "\n", + " public static Tuple4\n", + " tuple(A a, B b, C c, D d) {\n", + " return new Tuple4<>(a, b, c, d);\n", + " }\n", + "\n", + " public static \n", + " Tuple5 tuple(A a, B b, C c, D d, E e) {\n", + " return new Tuple5<>(a, b, c, d, e);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们修改 **TupleTest.java** 来测试 **Tuple.java** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/TupleTest2.java\n", + "\n", + "import onjava.Tuple2;\n", + "import onjava.Tuple3;\n", + "import onjava.Tuple4;\n", + "import onjava.Tuple5;\n", + "\n", + "import static onjava.Tuple.tuple;\n", + "\n", + "public class TupleTest2 {\n", + " static Tuple2 f() {\n", + " return tuple(\"hi\", 47);\n", + " }\n", + "\n", + " static Tuple2 f2() {\n", + " return tuple(\"hi\", 47);\n", + " }\n", + "\n", + " static Tuple3 g() {\n", + " return tuple(new Amphibian(), \"hi\", 47);\n", + " }\n", + "\n", + " static Tuple4 h() {\n", + " return tuple(\n", + " new Vehicle(), new Amphibian(), \"hi\", 47);\n", + " }\n", + "\n", + " static Tuple5 k() {\n", + " return tuple(new Vehicle(), new Amphibian(),\n", + " \"hi\", 47, 11.1);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Tuple2 ttsi = f();\n", + " System.out.println(ttsi);\n", + " System.out.println(f2());\n", + " System.out.println(g());\n", + " System.out.println(h());\n", + " System.out.println(k());\n", + " }\n", + "}\n", + "/* Output:\n", + "(hi, 47)\n", + "(hi, 47)\n", + "(Amphibian@14ae5a5, hi, 47)\n", + "(Vehicle@135fbaa4, Amphibian@45ee12a7, hi, 47)\n", + "(Vehicle@4b67cf4d, Amphibian@7ea987ac, hi, 47, 11.1)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。\n", + "\n", + "\n", + "### 一个 Set 工具\n", + "\n", + "对于泛型方法的另一个示例,请考虑由 **Set** 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Sets.java\n", + "\n", + "package onjava;\n", + "\n", + "import java.util.HashSet;\n", + "import java.util.Set;\n", + "\n", + "public class Sets {\n", + " public static Set union(Set a, Set b) {\n", + " Set result = new HashSet<>(a);\n", + " result.addAll(b);\n", + " return result;\n", + " }\n", + "\n", + " public static \n", + " Set intersection(Set a, Set b) {\n", + " Set result = new HashSet<>(a);\n", + " result.retainAll(b);\n", + " return result;\n", + " }\n", + "\n", + " // Subtract subset from superset:\n", + " public static Set\n", + " difference(Set superset, Set subset) {\n", + " Set result = new HashSet<>(superset);\n", + " result.removeAll(subset);\n", + " return result;\n", + " }\n", + "\n", + " // Reflexive--everything not in the intersection:\n", + " public static Set complement(Set a, Set b) {\n", + " return difference(union(a, b), intersection(a, b));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "前三个方法通过将第一个参数的引用复制到新的 **HashSet** 对象中来复制第一个参数,因此不会直接修改参数集合。因此,返回值是一个新的 **Set** 对象。\n", + "\n", + "这四种方法代表数学集合操作: `union()` 返回一个包含两个参数并集的 **Set** , `intersection()` 返回一个包含两个参数集合交集的 **Set** , `difference()` 从 **superset** 中减去 **subset** 的元素 ,而 `complement()` 返回所有不在交集中的元素的 **Set**。作为显示这些方法效果的简单示例的一部分,下面是一个包含不同水彩名称的 **enum** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/watercolors/Watercolors.java\n", + "\n", + "package watercolors;\n", + "\n", + "public enum Watercolors {\n", + " ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW,\n", + " ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA,\n", + " ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE,\n", + " PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE,\n", + " PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN,\n", + " YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,\n", + " BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/WatercolorSets.java\n", + "\n", + "import watercolors.*;\n", + "\n", + "import java.util.EnumSet;\n", + "import java.util.Set;\n", + "\n", + "import static watercolors.Watercolors.*;\n", + "import static onjava.Sets.*;\n", + "\n", + "public class WatercolorSets {\n", + " public static void main(String[] args) {\n", + " Set set1 =\n", + " EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE);\n", + " Set set2 =\n", + " EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER);\n", + " System.out.println(\"set1: \" + set1);\n", + " System.out.println(\"set2: \" + set2);\n", + " System.out.println(\n", + " \"union(set1, set2): \" + union(set1, set2));\n", + " Set subset = intersection(set1, set2);\n", + " System.out.println(\n", + " \"intersection(set1, set2): \" + subset);\n", + " System.out.println(\"difference(set1, subset): \" +\n", + " difference(set1, subset));\n", + " System.out.println(\"difference(set2, subset): \" +\n", + " difference(set2, subset));\n", + " System.out.println(\"complement(set1, set2): \" +\n", + " complement(set1, set2));\n", + " }\n", + "}\n", + "/* Output:\n", + "set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER,\n", + "VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,\n", + "COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE]\n", + "set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,\n", + "COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE,\n", + "SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,\n", + "BURNT_UMBER]\n", + "union(set1, set2): [BURNT_SIENNA, BRILLIANT_RED,\n", + "YELLOW_OCHRE, MAGENTA, SAP_GREEN, CERULEAN_BLUE_HUE,\n", + "ULTRAMARINE, VIRIDIAN_HUE, VIOLET, RAW_UMBER,\n", + "ROSE_MADDER, PERMANENT_GREEN, BURNT_UMBER,\n", + "PHTHALO_BLUE, CRIMSON, COBALT_BLUE_HUE]\n", + "intersection(set1, set2): [PERMANENT_GREEN,\n", + "CERULEAN_BLUE_HUE, ULTRAMARINE, VIRIDIAN_HUE,\n", + "PHTHALO_BLUE, COBALT_BLUE_HUE]\n", + "difference(set1, subset): [BRILLIANT_RED, MAGENTA,\n", + "VIOLET, CRIMSON, ROSE_MADDER]\n", + "difference(set2, subset): [BURNT_SIENNA, YELLOW_OCHRE,\n", + "BURNT_UMBER, SAP_GREEN, RAW_UMBER]\n", + "complement(set1, set2): [BURNT_SIENNA, BRILLIANT_RED,\n", + "YELLOW_OCHRE, MAGENTA, SAP_GREEN, VIOLET, RAW_UMBER,\n", + "ROSE_MADDER, BURNT_UMBER, CRIMSON]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来的例子使用 `Sets.difference()` 方法来展示 **java.util** 包中各种 **Collection** 和 **Map** 类之间的方法差异:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/CollectionMethodDifferences.java\n", + "// {java onjava.CollectionMethodDifferences}\n", + "\n", + "package onjava;\n", + "\n", + "import java.lang.reflect.Method;\n", + "import java.util.*;\n", + "import java.util.stream.Collectors;\n", + "\n", + "public class CollectionMethodDifferences {\n", + " static Set methodSet(Class type) {\n", + " return Arrays.stream(type.getMethods())\n", + " .map(Method::getName)\n", + " .collect(Collectors.toCollection(TreeSet::new));\n", + " }\n", + "\n", + " static void interfaces(Class type) {\n", + " System.out.print(\"Interfaces in \" +\n", + " type.getSimpleName() + \": \");\n", + " System.out.println(\n", + " Arrays.stream(type.getInterfaces())\n", + " .map(Class::getSimpleName)\n", + " .collect(Collectors.toList()));\n", + " }\n", + "\n", + " static Set object = methodSet(Object.class);\n", + "\n", + " static {\n", + " object.add(\"clone\");\n", + " }\n", + "\n", + " static void\n", + " difference(Class superset, Class subset) {\n", + " System.out.print(superset.getSimpleName() +\n", + " \" extends \" + subset.getSimpleName() +\n", + " \", adds: \");\n", + " Set comp = Sets.difference(\n", + " methodSet(superset), methodSet(subset));\n", + " comp.removeAll(object); // Ignore 'Object' methods\n", + " System.out.println(comp);\n", + " interfaces(superset);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Collection: \" +\n", + " methodSet(Collection.class));\n", + " interfaces(Collection.class);\n", + " difference(Set.class, Collection.class);\n", + " difference(HashSet.class, Set.class);\n", + " difference(LinkedHashSet.class, HashSet.class);\n", + " difference(TreeSet.class, Set.class);\n", + " difference(List.class, Collection.class);\n", + " difference(ArrayList.class, List.class);\n", + " difference(LinkedList.class, List.class);\n", + " difference(Queue.class, Collection.class);\n", + " difference(PriorityQueue.class, Queue.class);\n", + " System.out.println(\"Map: \" + methodSet(Map.class));\n", + " difference(HashMap.class, Map.class);\n", + " difference(LinkedHashMap.class, HashMap.class);\n", + " difference(SortedMap.class, Map.class);\n", + " difference(TreeMap.class, Map.class);\n", + " }\n", + "}\n", + "/* Output:\n", + "Collection: [add, addAll, clear, contains, containsAll,\n", + "equals, forEach, hashCode, isEmpty, iterator,\n", + "parallelStream, remove, removeAll, removeIf, retainAll,\n", + "size, spliterator, stream, toArray]\n", + "Interfaces in Collection: [Iterable]\n", + "Set extends Collection, adds: []\n", + "Interfaces in Set: [Collection]\n", + "HashSet extends Set, adds: []\n", + "Interfaces in HashSet: [Set, Cloneable, Serializable]\n", + "LinkedHashSet extends HashSet, adds: []\n", + "Interfaces in LinkedHashSet: [Set, Cloneable,\n", + "Serializable]\n", + "TreeSet extends Set, adds: [headSet,\n", + "descendingIterator, descendingSet, pollLast, subSet,\n", + "floor, tailSet, ceiling, last, lower, comparator,\n", + "pollFirst, first, higher]\n", + "Interfaces in TreeSet: [NavigableSet, Cloneable,\n", + "Serializable]\n", + "List extends Collection, adds: [replaceAll, get,\n", + "indexOf, subList, set, sort, lastIndexOf, listIterator]\n", + "Interfaces in List: [Collection]\n", + "ArrayList extends List, adds: [trimToSize,\n", + "ensureCapacity]\n", + "Interfaces in ArrayList: [List, RandomAccess,\n", + "Cloneable, Serializable]\n", + "LinkedList extends List, adds: [offerFirst, poll,\n", + "getLast, offer, getFirst, removeFirst, element,\n", + "removeLastOccurrence, peekFirst, peekLast, push,\n", + "pollFirst, removeFirstOccurrence, descendingIterator,\n", + "pollLast, removeLast, pop, addLast, peek, offerLast,\n", + "addFirst]\n", + "Interfaces in LinkedList: [List, Deque, Cloneable,\n", + "Serializable]\n", + "Queue extends Collection, adds: [poll, peek, offer,\n", + "element]\n", + "Interfaces in Queue: [Collection]\n", + "PriorityQueue extends Queue, adds: [comparator]\n", + "Interfaces in PriorityQueue: [Serializable]\n", + "Map: [clear, compute, computeIfAbsent,\n", + "computeIfPresent, containsKey, containsValue, entrySet,\n", + "equals, forEach, get, getOrDefault, hashCode, isEmpty,\n", + "keySet, merge, put, putAll, putIfAbsent, remove,\n", + "replace, replaceAll, size, values]\n", + "HashMap extends Map, adds: []\n", + "Interfaces in HashMap: [Map, Cloneable, Serializable]\n", + "LinkedHashMap extends HashMap, adds: []\n", + "Interfaces in LinkedHashMap: [Map]\n", + "SortedMap extends Map, adds: [lastKey, subMap,\n", + "comparator, firstKey, headMap, tailMap]\n", + "Interfaces in SortedMap: [Map]\n", + "TreeMap extends Map, adds: [descendingKeySet,\n", + "navigableKeySet, higherEntry, higherKey, floorKey,\n", + "subMap, ceilingKey, pollLastEntry, firstKey, lowerKey,\n", + "headMap, tailMap, lowerEntry, ceilingEntry,\n", + "descendingMap, pollFirstEntry, lastKey, firstEntry,\n", + "floorEntry, comparator, lastEntry]\n", + "Interfaces in TreeMap: [NavigableMap, Cloneable,\n", + "Serializable]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在第十二章 [集合的本章小结](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。\n", + "\n", + "\n", + "\n", + "## 构建复杂模型\n", + "\n", + "泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以轻松地创建一个元组列表:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/TupleList.java\n", + "// Combining generic types to make complex generic types\n", + "\n", + "import onjava.Tuple4;\n", + "\n", + "import java.util.ArrayList;\n", + "\n", + "public class TupleList\n", + " extends ArrayList> {\n", + " public static void main(String[] args) {\n", + " TupleList tl =\n", + " new TupleList<>();\n", + " tl.add(TupleTest2.h());\n", + " tl.add(TupleTest2.h());\n", + " tl.forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "(Vehicle@7cca494b, Amphibian@7ba4f24f, hi, 47)\n", + "(Vehicle@3b9a45b3, Amphibian@7699a589, hi, 47)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这将产生一个功能强大的数据结构,而无需太多代码。\n", + "\n", + "下面是第二个例子。每个类都是组成块,总体包含很多个块。在这里,该模型是一个具有过道,货架和产品的零售商店:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Store.java\n", + "// Building a complex model using generic collections\n", + "\n", + "import onjava.Suppliers;\n", + "\n", + "import java.util.ArrayList;\n", + "import java.util.Random;\n", + "import java.util.function.Supplier;\n", + "\n", + "class Product {\n", + " private final int id;\n", + " private String description;\n", + " private double price;\n", + "\n", + " Product(int idNumber, String descr, double price) {\n", + " id = idNumber;\n", + " description = descr;\n", + " this.price = price;\n", + " System.out.println(toString());\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return id + \": \" + description +\n", + " \", price: $\" + price;\n", + " }\n", + "\n", + " public void priceChange(double change) {\n", + " price += change;\n", + " }\n", + "\n", + " public static Supplier generator =\n", + " new Supplier() {\n", + " private Random rand = new Random(47);\n", + "\n", + " @Override\n", + " public Product get() {\n", + " return new Product(rand.nextInt(1000), \"Test\",\n", + " Math.round(\n", + " rand.nextDouble() * 1000.0) + 0.99);\n", + " }\n", + " };\n", + "}\n", + "\n", + "class Shelf extends ArrayList {\n", + " Shelf(int nProducts) {\n", + " Suppliers.fill(this, Product.generator, nProducts);\n", + " }\n", + "}\n", + "\n", + "class Aisle extends ArrayList {\n", + " Aisle(int nShelves, int nProducts) {\n", + " for (int i = 0; i < nShelves; i++)\n", + " add(new Shelf(nProducts));\n", + " }\n", + "}\n", + "\n", + "class CheckoutStand {\n", + "}\n", + "\n", + "class Office {\n", + "}\n", + "\n", + "public class Store extends ArrayList {\n", + " private ArrayList checkouts =\n", + " new ArrayList<>();\n", + " private Office office = new Office();\n", + "\n", + " public Store(\n", + " int nAisles, int nShelves, int nProducts) {\n", + " for (int i = 0; i < nAisles; i++)\n", + " add(new Aisle(nShelves, nProducts));\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " StringBuilder result = new StringBuilder();\n", + " for (Aisle a : this)\n", + " for (Shelf s : a)\n", + " for (Product p : s) {\n", + " result.append(p);\n", + " result.append(\"\\n\");\n", + " }\n", + " return result.toString();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(new Store(5, 4, 3));\n", + " }\n", + "}\n", + "/* Output: (First 8 Lines)\n", + "258: Test, price: $400.99\n", + "861: Test, price: $160.99\n", + "868: Test, price: $417.99\n", + "207: Test, price: $268.99\n", + "551: Test, price: $114.99\n", + "278: Test, price: $804.99\n", + "520: Test, price: $554.99\n", + "140: Test, price: $530.99\n", + " ...\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Store.toString()` 显示了结果:尽管有复杂的层次结构,但多层的集合仍然是类型安全的和可管理的。令人印象深刻的是,组装这样的模型并不需要耗费过多精力。\n", + "\n", + "**Shelf** 使用 `Suppliers.fill()` 这个实用程序,该实用程序接受 **Collection** (第一个参数),并使用 **Supplier** (第二个参数),以元素的数量为 **n** (第三个参数)来填充它。 **Suppliers** 类将会在本章末尾定义,其中的方法都是在执行某种填充操作,并在本章的其他示例中使用。\n", + "\n", + "\n", + "## 泛型擦除\n", + "\n", + "当你开始更深入地钻研泛型时,会发现有大量的东西初看起来是没有意义的。例如,尽管可以说 `ArrayList.class`,但不能说成 `ArrayList.class`。考虑下面的情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ErasedTypeEquivalence.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class ErasedTypeEquivalence {\n", + " \n", + " public static void main(String[] args) {\n", + " Class c1 = new ArrayList().getClass();\n", + " Class c2 = new ArrayList().getClass();\n", + " System.out.println(c1 == c2);\n", + " }\n", + " \n", + "}\n", + "/* Output:\n", + "true\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`ArrayList` 和 `ArrayList` 应该是不同的类型。不同的类型会有不同的行为。例如,如果尝试向 `ArrayList` 中放入一个 `Integer`,所得到的行为(失败)和向 `ArrayList` 中放入一个 `Integer` 所得到的行为(成功)完全不同。然而上面的程序认为它们是相同的类型。\n", + "\n", + "下面的例子是对该谜题的补充:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/LostInformation.java\n", + "\n", + "import java.util.*;\n", + "\n", + "class Frob {}\n", + "class Fnorkle {}\n", + "class Quark {}\n", + "\n", + "class Particle {}\n", + "\n", + "public class LostInformation {\n", + "\n", + " public static void main(String[] args) {\n", + " List list = new ArrayList<>();\n", + " Map map = new HashMap<>();\n", + " Quark quark = new Quark<>();\n", + " Particle p = new Particle<>();\n", + " System.out.println(Arrays.toString(list.getClass().getTypeParameters()));\n", + " System.out.println(Arrays.toString(map.getClass().getTypeParameters()));\n", + " System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));\n", + " System.out.println(Arrays.toString(p.getClass().getTypeParameters()));\n", + " }\n", + "\n", + "}\n", + "/* Output:\n", + "[E]\n", + "[K,V]\n", + "[Q]\n", + "[POSITION,MOMENTUM]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "根据 JDK 文档,**Class.getTypeParameters()** “返回一个 **TypeVariable** 对象数组,表示泛型声明中声明的类型参数...” 这暗示你可以发现这些参数类型。但是正如上例中输出所示,你只能看到用作参数占位符的标识符,这并非有用的信息。\n", + "\n", + "残酷的现实是:\n", + "\n", + "在泛型代码内部,无法获取任何有关泛型参数类型的信息。\n", + "\n", + "因此,你可以知道如类型参数标识符和泛型边界这些信息,但无法得知实际的类型参数从而用来创建特定的实例。如果你曾是 C++ 程序员,那么这个事实会让你很沮丧,在使用 Java 泛型工作时,它是必须处理的最基本的问题。\n", + "\n", + "Java 泛型是使用擦除实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此,`List` 和 `List` 在运行时实际上是相同的类型。它们都被擦除成原生类型 `List`。\n", + "\n", + "理解擦除并知道如何处理它,是你在学习 Java 泛型时面临的最大障碍之一。这也是本节将要探讨的内容。\n", + "\n", + "### C++ 的方式\n", + "\n", + "下面是使用模版的 C++ 示例。你会看到类型参数的语法十分相似,因为 Java 是受 C++ 启发的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Templates.cpp\n", + "\n", + "#include \n", + "using namespace std;\n", + "\n", + "template class Manipulator {\n", + " T obj;\n", + "public:\n", + " Manipulator(T x) { obj = x; }\n", + " void manipulate() { obj.f(); }\n", + "};\n", + "\n", + "class HasF {\n", + "public:\n", + " void f() { cout << \"HasF::f()\" << endl; }\n", + "};\n", + "\n", + "int main() {\n", + " HasF hf;\n", + " Manipulator manipulator(hf);\n", + " manipulator.manipulate();\n", + "}\n", + "/* Output:\n", + "HasF::f()\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Manipulator** 类存储了一个 **T** 类型的对象。`manipulate()` 方法会调用 **obj** 上的 `f()` 方法。它是如何知道类型参数 **T** 中存在 `f()` 方法的呢?C++ 编译器会在你实例化模版时进行检查,所以在 `Manipulator` 实例化的那一刻,它看到 **HasF** 中含有一个方法 `f()`。如果情况并非如此,你就会得到一个编译期错误,保持类型安全。\n", + "\n", + "用 C++ 编写这种代码很简单,因为当模版被实例化时,模版代码就知道模版参数的类型。Java 泛型就不同了。下面是 **HasF** 的 Java 版本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/HasF.java\n", + "\n", + "public class HasF {\n", + " public void f() {\n", + " System.out.println(\"HasF.f()\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们将示例的其余代码用 Java 实现,就不会通过编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Manipulation.java\n", + "// {WillNotCompile}\n", + "\n", + "class Manipulator {\n", + " private T obj;\n", + " \n", + " Manipulator(T x) {\n", + " obj = x;\n", + " }\n", + " \n", + " // Error: cannot find symbol: method f():\n", + " public void manipulate() {\n", + " obj.f();\n", + " }\n", + "}\n", + "\n", + "public class Manipulation {\n", + "\tpublic static void main(String[] args) {\n", + " HasF hf = new HasF();\n", + " Manipulator manipulator = new Manipulator<>(hf);\n", + " manipulator.manipulate();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为擦除,Java 编译器无法将 `manipulate()` 方法必须能调用 **obj** 的 `f()` 方法这一需求映射到 HasF 具有 `f()` 方法这个事实上。为了调用 `f()`,我们必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了 **extends** 关键字。由于有了边界,下面的代码就能通过编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public class Manipulator2 {\n", + " private T obj;\n", + "\n", + " Manipulator2(T x) {\n", + " obj = x;\n", + " }\n", + "\n", + " public void manipulate() {\n", + " obj.f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "边界 `` 声明 T 必须是 HasF 类型或其子类。如果情况确实如此,就可以安全地在 **obj** 上调用 `f()` 方法。\n", + "\n", + "我们说泛型类型参数会擦除到它的第一个边界(可能有多个边界,稍后你将看到)。我们还提到了类型参数的擦除。编译器实际上会把类型参数替换为它的擦除,就像上面的示例,**T** 擦除到了 **HasF**,就像在类的声明中用 **HasF** 替换了 **T** 一样。\n", + "\n", + "你可能正确地观察到了泛型在 **Manipulator2.java** 中没有贡献任何事。你可以很轻松地自己去执行擦除,生成没有泛型的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Manipulator3.java\n", + "\n", + "class Manipulator3 {\n", + " private HasF obj;\n", + " \n", + " Manipulator3(HasF x) {\n", + " obj = x;\n", + " }\n", + " \n", + " public void manipulate() {\n", + " obj.f();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这提出了很重要的一点:泛型只有在类型参数比某个具体类型(以及其子类)更加“泛化”——代码能跨多个类工作时才有用。因此,类型参数和它们在有用的泛型代码中的应用,通常比简单的类替换更加复杂。但是,不能因此认为使用 `` 形式就是有缺陷的。例如,如果某个类有一个返回 **T** 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ReturnGenericType.java\n", + "\n", + "public class ReturnGenericType {\n", + " private T obj;\n", + " \n", + " ReturnGenericType(T x) {\n", + " obj = x;\n", + " }\n", + " \n", + " public T get() {\n", + " return obj;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你必须查看所有的代码,从而确定代码是否复杂到必须使用泛型的程度。\n", + "\n", + "我们将在本章稍后看到有关边界的更多细节。\n", + "\n", + "### 迁移兼容性\n", + "\n", + "为了减少潜在的关于擦除的困惑,你必须清楚地认识到这不是一个语言特性。它是 Java 实现泛型的一种妥协,因为泛型不是 Java 语言出现时就有的,所以就有了这种妥协。它会使你痛苦,因此你需要尽早习惯它并了解为什么它会这样。\n", + "\n", + "如果 Java 1.0 就含有泛型的话,那么这个特性就不会使用擦除来实现——它会使用具体化,保持参数类型为第一类实体,因此你就能在类型参数上执行基于类型的语言操作和反射操作。本章稍后你会看到,擦除减少了泛型的泛化性。泛型在 Java 中仍然是有用的,只是不如它们本来设想的那么有用,而原因就是擦除。\n", + "\n", + "在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文使用泛型类型。泛型类型只有在静态类型检测期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如, `List` 这样的类型注解会被擦除为 **List**,普通的类型变量在未指定边界的情况下会被擦除为 **Object**。\n", + "\n", + "擦除的核心动机是你可以在泛化的客户端上使用非泛型的类库,反之亦然。这经常被称为“迁移兼容性”。在理想情况下,所有事物将在指定的某天被泛化。在现实中,即使程序员只编写泛型代码,他们也必须处理 Java 5 之前编写的非泛型类库。这些类库的作者可能从没想过要泛化他们的代码,或许他们可能刚刚开始接触泛型。\n", + "\n", + "因此 Java 泛型不仅必须支持向后兼容性——现有的代码和类文件仍然合法,继续保持之前的含义——而且还必须支持迁移兼容性,使得类库能按照它们自己的步调变为泛型,当某个类库变为泛型时,不会破坏依赖于它的代码和应用。在确定了这个目标后,Java 设计者们和从事此问题相关工作的各个团队决策认为擦除是唯一可行的解决方案。擦除使得这种向泛型的迁移成为可能,允许非泛型的代码和泛型代码共存。\n", + "\n", + "例如,假设一个应用使用了两个类库 **X** 和 **Y**,**Y** 使用了类库 **Z**。随着 Java 5 的出现,这个应用和这些类库的创建者最终可能希望迁移到泛型上。但是当进行迁移时,它们有着不同的动机和限制。为了实现迁移兼容性,每个类库与应用必须与其他所有的部分是否使用泛型无关。因此,它们不能探测其他类库是否使用了泛型。因此,某个特定的类库使用了泛型这样的证据必须被”擦除“。\n", + "\n", + "如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到 Java 泛型上的开发者们说再见了。类库毫无争议是编程语言的一部分,对生产效率有着极大的影响,所以这种代码无法接受。擦除是否是最佳的或唯一的迁移途径,还待时间来证明。\n", + "\n", + "### 擦除的问题\n", + "\n", + "因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下将泛型融入到语言中。擦除允许你继续使用现有的非泛型客户端代码,直至客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为它不会骤然破坏所有现有的代码。\n", + "\n", + "擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、**instanceof** 操作和 **new** 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。\n", + "\n", + "考虑如下的代码段:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class Foo {\n", + " T var;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "看上去当你创建一个 **Foo** 实例时:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Foo f = new Foo<>();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**class** **Foo** 中的代码应该知道现在工作于 **Cat** 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 **Object**“。\n", + "\n", + "另外,擦除和迁移兼容性意味着,使用泛型并不是强制的,尽管你可能希望这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ErasureAndInheritance.java\n", + "\n", + "class GenericBase {\n", + " private T element;\n", + " \n", + " public void set(T arg) {\n", + " element = arg;\n", + " }\n", + " \n", + " public T get() {\n", + " return element;\n", + " }\n", + "}\n", + "\n", + "class Derived1 extends GenericBase {}\n", + "\n", + "class Derived2 extends GenericBase {} // No warning\n", + "\n", + "// class Derived3 extends GenericBase {}\n", + "// Strange error:\n", + "// unexpected type\n", + "// required: class or interface without bounds\n", + "public class ErasureAndInteritance {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " Derived2 d2 = new Derived2();\n", + " Object obj = d2.get();\n", + " d2.set(obj); // Warning here!\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Derived2** 继承自 **GenericBase**,但是没有任何类型参数,编译器没有发出任何警告。直到调用 `set()` 方法时才出现警告。\n", + "\n", + "为了关闭警告,Java 提供了一个注解,我们可以在列表中看到它:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@SuppressWarnings(\"unchecked\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个注解放置在产生警告的方法上,而不是整个类上。当你要关闭警告时,最好尽可能地“聚焦”,这样就不会因为过于宽泛地关闭警告,而导致意外地遮蔽掉真正的问题。\n", + "\n", + "可以推断,**Derived3** 产生的错误意味着编译器期望得到一个原生基类。\n", + "\n", + "当你希望将类型参数不仅仅当作 Object 处理时,就需要付出额外努力来管理边界,并且与在 C++、Ada 和 Eiffel 这样的语言中获得参数化类型相比,你需要付出多得多的努力来获得少得多的回报。这并不是说,对于大多数的编程问题而言,这些语言通常都会比 Java 更得心应手,只是说它们的参数化类型机制相比 Java 更灵活、更强大。\n", + "\n", + "### 边界处的动作\n", + "\n", + "因为擦除,我发现了泛型最令人困惑的方面是可以表示没有任何意义的事物。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ArrayMaker.java\n", + "\n", + "import java.lang.reflect.*;\n", + "import java.util.*;\n", + "\n", + "public class ArrayMaker {\n", + " private Class kind;\n", + "\n", + " public ArrayMaker(Class kind) {\n", + " this.kind = kind;\n", + " }\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " T[] create(int size) {\n", + " return (T[]) Array.newInstance(kind, size);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " ArrayMaker stringMaker = new ArrayMaker<>(String.class);\n", + " String[] stringArray = stringMaker.create(9);\n", + " System.out.println(Arrays.toString(stringArray));\n", + " }\n", + "}\n", + "/* Output\n", + "[null,null,null,null,null,null,null,null,null]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "即使 **kind** 被存储为 `Class`,擦除也意味着它实际被存储为没有任何参数的 **Class**。因此,当你在使用它时,例如创建数组,`Array.newInstance()` 实际上并未拥有 **kind** 所蕴含的类型信息。所以它不会产生具体的结果,因而必须转型,这会产生一条令你无法满意的警告。\n", + "\n", + "注意,对于在泛型中创建数组,使用 `Array.newInstance()` 是推荐的方式。\n", + "\n", + "如果我们创建一个集合而不是数组,情况就不同了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ListMaker.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class ListMaker {\n", + " List create() {\n", + " return new ArrayList<>();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " ListMaker stringMaker = new ListMaker<>();\n", + " List stringList = stringMaker.create();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器不会给出任何警告,尽管我们知道(从擦除中)在 `create()` 内部的 `new ArrayList<>()` 中的 `` 被移除了——在运行时,类内部没有任何 ``,因此这看起来毫无意义。但是如果你遵从这种思路,并将这个表达式改为 `new ArrayList()`,编译器就会发出警告。\n", + "\n", + "本例中这么做真的毫无意义吗?如果在创建 **List** 的同时向其中放入一些对象呢,像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/FilledList.java\n", + "\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import onjava.*;\n", + "\n", + "public class FilledList extends ArrayList {\n", + " FilledList(Supplier gen, int size) {\n", + " Suppliers.fill(this, gen, size);\n", + " }\n", + " \n", + " public FilledList(T t, int size) {\n", + " for (int i = 0; i < size; i++) {\n", + " this.add(t);\n", + " }\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " List list = new FilledList<>(\"Hello\", 4);\n", + " System.out.println(list);\n", + " // Supplier version:\n", + " List ilist = new FilledList<>(() -> 47, 4);\n", + " System.out.println(ilist);\n", + " }\n", + "}\n", + "/* Output:\n", + "[Hello,Hello,Hello,Hello]\n", + "[47,47,47,47]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "即使编译器无法得知 `add()` 中的 **T** 的任何信息,但它仍可以在编译期确保你放入 **FilledList** 中的对象是 **T** 类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以确保方法或类中使用的类型的内部一致性。\n", + "\n", + "因为擦除移除了方法体中的类型信息,所以在运行时的问题就是*边界*:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。\n", + "\n", + "考虑如下这段非泛型示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SimpleHolder.java\n", + "\n", + "public class SimpleHolder {\n", + " private Object obj;\n", + " \n", + " public void set(Object obj) {\n", + " this.obj = obj;\n", + " }\n", + " \n", + " public Object get() {\n", + " return obj;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " SimpleHolder holder = new SimpleHolder();\n", + " holder.set(\"Item\");\n", + " String s = (String) holder.get();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果用 **javap -c SimpleHolder** 反编译这个类,会得到如下内容(经过编辑):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public void set(java.lang.Object);\n", + " 0: aload_0\n", + " 1: aload_1\n", + " 2: putfield #2; // Field obj:Object;\n", + " 5: return\n", + " \n", + "public java.lang.Object get();\n", + " 0: aload_0\n", + " 1: getfield #2; // Field obj:Object;\n", + " 4: areturn\n", + " \n", + "public static void main(java.lang.String[]);\n", + " 0: new #3; // class SimpleHolder\n", + " 3: dup\n", + " 4: invokespecial #4; // Method \"\":()V\n", + " 7: astore_1\n", + " 8: aload_1\n", + " 9: ldc #5; // String Item\n", + " 11: invokevirtual #6; // Method set:(Object;)V\n", + " 14: aload_1\n", + " 15: invokevirtual #7; // Method get:()Object;\n", + " 18: checkcast #8; // class java/lang/String\n", + " 21: astore_2\n", + " 22: return" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`set()` 和 `get()` 方法存储和产生值,转型在调用 `get()` 时接受检查。\n", + "\n", + "现在将泛型融入上例代码中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericHolder2.java\n", + "\n", + "public class GenericHolder2 {\n", + " private T obj;\n", + "\n", + " public void set(T obj) {\n", + " this.obj = obj;\n", + " }\n", + "\n", + " public T get() {\n", + " return obj;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " GenericHolder2 holder = new GenericHolder2<>();\n", + " holder.set(\"Item\");\n", + " String s = holder.get();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从 `get()` 返回后的转型消失了,但是我们还知道传递给 `set()` 的值在编译期会被检查。下面是相关的字节码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public void set(java.lang.Object);\n", + " 0: aload_0\n", + " 1: aload_1\n", + " 2: putfield #2; // Field obj:Object;\n", + " 5: return\n", + " \n", + "public java.lang.Object get();\n", + " 0: aload_0\n", + " 1: getfield #2; // Field obj:Object;\n", + " 4: areturn\n", + " \n", + "public static void main(java.lang.String[]);\n", + " 0: new #3; // class GenericHolder2\n", + " 3: dup\n", + " 4: invokespecial #4; // Method \"\":()V\n", + " 7: astore_1\n", + " 8: aload_1\n", + " 9: ldc #5; // String Item\n", + " 11: invokevirtual #6; // Method set:(Object;)V\n", + " 14: aload_1\n", + " 15: invokevirtual #7; // Method get:()Object;\n", + " 18: checkcast #8; // class java/lang/String\n", + " 21: astore_2\n", + " 22: return" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所产生的字节码是相同的。对进入 `set()` 的类型进行检查是不需要的,因为这将由编译器执行。而对 `get()` 返回的值进行转型仍然是需要的,只不过不需要你来操作,它由编译器自动插入,这样你就不用编写(阅读)杂乱的代码。\n", + "\n", + "`get()` 和 `set()` 产生了相同的字节码,这就告诉我们泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。\n", + "\n", + "\n", + "\n", + "## 补偿擦除\n", + "\n", + "因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Erased.java\n", + "// {WillNotCompile}\n", + "\n", + "public class Erased {\n", + " private final int SIZE = 100;\n", + "\n", + " public void f(Object arg) {\n", + " // error: illegal generic type for instanceof\n", + " if (arg instanceof T) {\n", + " }\n", + " // error: unexpected type\n", + " T var = new T();\n", + " // error: generic array creation\n", + " T[] array = new T[SIZE];\n", + " // warning: [unchecked] unchecked cast\n", + " T[] array = (T[]) new Object[SIZE];\n", + "\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 **Class** 对象,以在类型表达式中使用它。\n", + "\n", + "例如,由于擦除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ClassTypeCapture.java\n", + "\n", + "class Building {\n", + "}\n", + "\n", + "class House extends Building {\n", + "}\n", + "\n", + "public class ClassTypeCapture {\n", + " Class kind;\n", + "\n", + " public ClassTypeCapture(Class kind) {\n", + " this.kind = kind;\n", + " }\n", + "\n", + " public boolean f(Object arg) {\n", + " return kind.isInstance(arg);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " ClassTypeCapture ctt1 =\n", + " new ClassTypeCapture<>(Building.class);\n", + " System.out.println(ctt1.f(new Building()));\n", + " System.out.println(ctt1.f(new House()));\n", + " ClassTypeCapture ctt2 =\n", + " new ClassTypeCapture<>(House.class);\n", + " System.out.println(ctt2.f(new Building()));\n", + " System.out.println(ctt2.f(new House()));\n", + " }\n", + "}\n", + "/* Output:\n", + "true\n", + "true\n", + "false\n", + "true\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器来保证类型标签与泛型参数相匹配。\n", + "\n", + "\n", + "### 创建类型的实例\n", + "\n", + "试图在 **Erased.java** 中 `new T()` 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 **T** 是否具有默认(无参)构造函数。但是在 C++ 中,此操作自然,直接且安全(在编译时检查):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "C++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/InstantiateGenericType.cpp\n", + "// C++, not Java!\n", + "\n", + "template class Foo {\n", + " T x; // Create a field of type T\n", + " T* y; // Pointer to T\n", + "public:\n", + " // Initialize the pointer:\n", + " Foo() { y = new T(); }\n", + "};\n", + "\n", + "class Bar {};\n", + "\n", + "int main() {\n", + " Foo fb;\n", + " Foo fi; // ... and it works with primitives\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Java 中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是 **Class** 对象,因此,如果使用类型标记,则可以使用 `newInstance()` 创建该类型的新对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/InstantiateGenericType.java\n", + "\n", + "import java.util.function.Supplier;\n", + "\n", + "class ClassAsFactory implements Supplier {\n", + " Class kind;\n", + "\n", + " ClassAsFactory(Class kind) {\n", + " this.kind = kind;\n", + " }\n", + "\n", + " @Override\n", + " public T get() {\n", + " try {\n", + " return kind.newInstance();\n", + " } catch (InstantiationException |\n", + " IllegalAccessException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}\n", + "\n", + "class Employee {\n", + " @Override\n", + " public String toString() {\n", + " return \"Employee\";\n", + " }\n", + "}\n", + "\n", + "public class InstantiateGenericType {\n", + " public static void main(String[] args) {\n", + " ClassAsFactory fe =\n", + " new ClassAsFactory<>(Employee.class);\n", + " System.out.println(fe.get());\n", + " ClassAsFactory fi =\n", + " new ClassAsFactory<>(Integer.class);\n", + " try {\n", + " System.out.println(fi.get());\n", + " } catch (Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "Employee\n", + "java.lang.InstantiationException: java.lang.Integer\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这样可以编译,但对于 `ClassAsFactory` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/FactoryConstraint.java\n", + "\n", + "import onjava.Suppliers;\n", + "\n", + "import java.util.ArrayList;\n", + "import java.util.List;\n", + "import java.util.function.Supplier;\n", + "\n", + "class IntegerFactory implements Supplier {\n", + " private int i = 0;\n", + "\n", + " @Override\n", + " public Integer get() {\n", + " return ++i;\n", + " }\n", + "}\n", + "\n", + "class Widget {\n", + " private int id;\n", + "\n", + " Widget(int n) {\n", + " id = n;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"Widget \" + id;\n", + " }\n", + "\n", + " public static\n", + " class Factory implements Supplier {\n", + " private int i = 0;\n", + "\n", + " @Override\n", + " public Widget get() {\n", + " return new Widget(++i);\n", + " }\n", + " }\n", + "}\n", + "\n", + "class Fudge {\n", + " private static int count = 1;\n", + " private int n = count++;\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"Fudge \" + n;\n", + " }\n", + "}\n", + "\n", + "class Foo2 {\n", + " private List x = new ArrayList<>();\n", + "\n", + " Foo2(Supplier factory) {\n", + " Suppliers.fill(x, factory, 5);\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return x.toString();\n", + " }\n", + "}\n", + "\n", + "public class FactoryConstraint {\n", + " public static void main(String[] args) {\n", + " System.out.println(\n", + " new Foo2<>(new IntegerFactory()));\n", + " System.out.println(\n", + " new Foo2<>(new Widget.Factory()));\n", + " System.out.println(\n", + " new Foo2<>(Fudge::new));\n", + " }\n", + "}\n", + "/* Output:\n", + "[1, 2, 3, 4, 5]\n", + "[Widget 1, Widget 2, Widget 3, Widget 4, Widget 5]\n", + "[Fudge 1, Fudge 2, Fudge 3, Fudge 4, Fudge 5]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**IntegerFactory** 本身就是通过实现 `Supplier` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。\n", + "\n", + "另一种方法是模板方法设计模式。在以下示例中,`create()` 是模板方法,在子类中被重写以生成该类型的对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CreatorGeneric.java\n", + "\n", + "abstract class GenericWithCreate {\n", + " final T element;\n", + "\n", + " GenericWithCreate() {\n", + " element = create();\n", + " }\n", + "\n", + " abstract T create();\n", + "}\n", + "\n", + "class X {\n", + "}\n", + "\n", + "class XCreator extends GenericWithCreate {\n", + " @Override\n", + " X create() {\n", + " return new X();\n", + " }\n", + "\n", + " void f() {\n", + " System.out.println(\n", + " element.getClass().getSimpleName());\n", + " }\n", + "}\n", + "\n", + "public class CreatorGeneric {\n", + " public static void main(String[] args) {\n", + " XCreator xc = new XCreator();\n", + " xc.f();\n", + " }\n", + "}\n", + "/* Output:\n", + "X\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**GenericWithCreate** 包含 `element` 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 `create()` 方法。这种创建方式可以在子类中定义,同时建立 **T** 的类型。\n", + "\n", + "\n", + "\n", + "### 泛型数组\n", + "\n", + "正如在 **Erased.java** 中所看到的,我们无法创建泛型数组。通用解决方案是在试图创建泛型数组的时候使用 **ArrayList** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ListOfGenerics.java\n", + "\n", + "import java.util.ArrayList;\n", + "import java.util.List;\n", + "\n", + "public class ListOfGenerics {\n", + " private List array = new ArrayList<>();\n", + "\n", + " public void add(T item) {\n", + " array.add(item);\n", + " }\n", + "\n", + " public T get(int index) {\n", + " return array.get(index);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这样做可以获得数组的行为,并且还具有泛型提供的编译时类型安全性。\n", + "\n", + "有时,仍然会创建泛型类型的数组(例如, **ArrayList** 在内部使用数组)。可以通过使编译器满意的方式定义对数组的通用引用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ArrayOfGenericReference.java\n", + "\n", + "class Generic {\n", + "}\n", + "\n", + "public class ArrayOfGenericReference {\n", + " static Generic[] gia;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器接受此操作而不产生警告。但是我们永远无法创建具有该确切类型(包括类型参数)的数组,因此有点令人困惑。由于所有数组,无论它们持有什么类型,都具有相同的结构(每个数组插槽的大小和数组布局),因此似乎可以创建一个 **Object** 数组并将其转换为所需的数组类型。实际上,这确实可以编译,但是会产生 **ClassCastException** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ArrayOfGeneric.java\n", + "\n", + "public class ArrayOfGeneric {\n", + " static final int SIZE = 100;\n", + " static Generic[] gia;\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " try {\n", + " gia = (Generic[]) new Object[SIZE];\n", + " } catch (ClassCastException e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " // Runtime type is the raw (erased) type:\n", + " gia = (Generic[]) new Generic[SIZE];\n", + " System.out.println(gia.getClass().getSimpleName());\n", + " gia[0] = new Generic<>();\n", + " //- gia[1] = new Object(); // Compile-time error\n", + " // Discovers type mismatch at compile time:\n", + " //- gia[2] = new Generic();\n", + " }\n", + "}\n", + "/* Output:\n", + "[Ljava.lang.Object; cannot be cast to [LGeneric;\n", + "Generic[]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个 **Object** 数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。\n", + "\n", + "让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericArray.java\n", + "\n", + "public class GenericArray {\n", + " private T[] array;\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " public GenericArray(int sz) {\n", + " array = (T[]) new Object[sz];\n", + " }\n", + "\n", + " public void put(int index, T item) {\n", + " array[index] = item;\n", + " }\n", + "\n", + " public T get(int index) {\n", + " return array[index];\n", + " }\n", + "\n", + " // Method that exposes the underlying representation:\n", + " public T[] rep() {\n", + " return array;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " GenericArray gai = new GenericArray<>(10);\n", + " try {\n", + " Integer[] ia = gai.rep();\n", + " } catch (ClassCastException e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " // This is OK:\n", + " Object[] oa = gai.rep();\n", + " }\n", + "}\n", + "/* Output:\n", + "[Ljava.lang.Object; cannot be cast to\n", + "[Ljava.lang.Integer;\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "和以前一样,我们不能说 `T[] array = new T[sz]` ,所以我们创建了一个 **Object** 数组并将其强制转换。\n", + "\n", + "`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]`,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。\n", + "\n", + "如果再注释掉 **@SuppressWarnings** 注解后编译 **GenericArray.java** ,则编译器会产生警告:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "GenericArray.java uses unchecked or unsafe operations.\n", + "Recompile with -Xlint:unchecked for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里,我们收到了一个警告,我们认为这是有关强制转换的。\n", + "\n", + "但是要真正确定,请使用 `-Xlint:unchecked` 进行编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "确实是在抱怨那个强制转换。由于警告会变成噪音,因此,一旦我们确认预期会出现特定警告,我们可以做的最好的办法就是使用 **@SuppressWarnings** 将其关闭。这样,当警告确实出现时,我们将进行实际调查。\n", + "\n", + "由于擦除,数组的运行时类型只能是 `Object[]` 。 如果我们立即将其转换为 `T[]` ,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用 `Object[]` ,并在使用数组元素时向 **T** 添加强制类型转换。让我们来看看在 **GenericArray.java** 示例中会是怎么样的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericArray2.java\n", + "\n", + "public class GenericArray2 {\n", + " private Object[] array;\n", + "\n", + " public GenericArray2(int sz) {\n", + " array = new Object[sz];\n", + " }\n", + "\n", + " public void put(int index, T item) {\n", + " array[index] = item;\n", + " }\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " public T get(int index) {\n", + " return (T) array[index];\n", + " }\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " public T[] rep() {\n", + " return (T[]) array; // Unchecked cast\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " GenericArray2 gai =\n", + " new GenericArray2<>(10);\n", + " for (int i = 0; i < 10; i++)\n", + " gai.put(i, i);\n", + " for (int i = 0; i < 10; i++)\n", + " System.out.print(gai.get(i) + \" \");\n", + " System.out.println();\n", + " try {\n", + " Integer[] ia = gai.rep();\n", + " } catch (Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "0 1 2 3 4 5 6 7 8 9\n", + "java.lang.ClassCastException: [Ljava.lang.Object;\n", + "cannot be cast to [Ljava.lang.Integer;\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最初,看起来并没有太大不同,只是转换的位置移动了。没有 **@SuppressWarnings** 注解,仍然会收到“unchecked”警告。但是,内部表示现在是 `Object[]` 而不是 `T[]` 。 调用 `get()` 时,它将对象强制转换为 **T** ,实际上这是正确的类型,因此很安全。但是,如果调用 `rep()` ,它将再次尝试将 `Object[]` 强制转换为 `T[]` ,但仍然不正确,并在编译时生成警告,并在运行时生成异常。因此,无法破坏基础数组的类型,该基础数组只能是 `Object[]` 。在内部将数组视为 `Object[]` 而不是 `T[]` 的优点是,我们不太可能会忘记数组的运行时类型并意外地引入了bug,尽管大多数(也许是全部)此类错误会在运行时被迅速检测到。\n", + "\n", + "对于新代码,请传入类型标记。在这种情况下,**GenericArray** 如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericArrayWithTypeToken.java\n", + "\n", + "import java.lang.reflect.Array;\n", + "\n", + "public class GenericArrayWithTypeToken {\n", + " private T[] array;\n", + "\n", + " @SuppressWarnings(\"unchecked\")\n", + " public GenericArrayWithTypeToken(Class type, int sz) {\n", + " array = (T[]) Array.newInstance(type, sz);\n", + " }\n", + "\n", + " public void put(int index, T item) {\n", + " array[index] = item;\n", + " }\n", + "\n", + " public T get(int index) {\n", + " return array[index];\n", + " }\n", + "\n", + " // Expose the underlying representation:\n", + " public T[] rep() {\n", + " return array;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " GenericArrayWithTypeToken gai =\n", + " new GenericArrayWithTypeToken<>(\n", + " Integer.class, 10);\n", + " // This now works:\n", + " Integer[] ia = gai.rep();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类型标记 **Class\\** 被传递到构造函数中以从擦除中恢复,因此尽管必须使用 **@SuppressWarnings** 关闭来自强制类型转换的警告,但我们仍可以创建所需的实际数组类型。一旦获得了实际的类型,就可以返回它并产生所需的结果,如在主方法中看到的那样。数组的运行时类型是确切的类型 `T[]` 。\n", + "\n", + "不幸的是,如果查看 Java 标准库中的源代码,你会发现到处都有从 **Object** 数组到参数化类型的转换。例如,这是**ArrayList** 中,复制一个 **Collection** 的构造函数,这里为了简化,去除了源码中对此不重要的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "public ArrayList(Collection c) {\n", + " size = c.size();\n", + " elementData = (E[])new Object[size];\n", + " c.toArray(elementData);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你浏览 **ArrayList.java** 的代码,将会发现很多此类强制转换。当我们编译它时会发生什么?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Note: ArrayList.java uses unchecked or unsafe operations\n", + "Note: Recompile with -Xlint:unchecked for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。\n", + "\n", + "Neal Gafter(Java 5 的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。\n", + "\n", + "请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。\n", + "\n", + "\n", + "\n", + "## 边界\n", + "\n", + "*边界*(bounds)在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在绑定的类型中调用方法。\n", + "\n", + "由于擦除会删除类型信息,因此唯一可用于无限制泛型参数的方法是那些 **Object** 可用的方法。但是,如果将该参数限制为某类型的子集,则可以调用该子集中的方法。为了应用约束,Java 泛型使用了 `extends` 关键字。\n", + "\n", + "重要的是要理解,当用于限定泛型类型时,`extends` 的含义与通常的意义截然不同。此示例展示边界的基础应用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/BasicBounds.java\n", + "\n", + "interface HasColor {\n", + " java.awt.Color getColor();\n", + "}\n", + "\n", + "class WithColor {\n", + " T item;\n", + "\n", + " WithColor(T item) {\n", + " this.item = item;\n", + " }\n", + "\n", + " T getItem() {\n", + " return item;\n", + " }\n", + "\n", + " // The bound allows you to call a method:\n", + " java.awt.Color color() {\n", + " return item.getColor();\n", + " }\n", + "}\n", + "\n", + "class Coord {\n", + " public int x, y, z;\n", + "}\n", + "\n", + "// This fails. Class must be first, then interfaces:\n", + "// class WithColorCoord {\n", + "\n", + "// Multiple bounds:\n", + "class WithColorCoord {\n", + " T item;\n", + "\n", + " WithColorCoord(T item) {\n", + " this.item = item;\n", + " }\n", + "\n", + " T getItem() {\n", + " return item;\n", + " }\n", + "\n", + " java.awt.Color color() {\n", + " return item.getColor();\n", + " }\n", + "\n", + " int getX() {\n", + " return item.x;\n", + " }\n", + "\n", + " int getY() {\n", + " return item.y;\n", + " }\n", + "\n", + " int getZ() {\n", + " return item.z;\n", + " }\n", + "}\n", + "\n", + "interface Weight {\n", + " int weight();\n", + "}\n", + "\n", + "// As with inheritance, you can have only one\n", + "// concrete class but multiple interfaces:\n", + "class Solid {\n", + " T item;\n", + "\n", + " Solid(T item) {\n", + " this.item = item;\n", + " }\n", + "\n", + " T getItem() {\n", + " return item;\n", + " }\n", + "\n", + " java.awt.Color color() {\n", + " return item.getColor();\n", + " }\n", + "\n", + " int getX() {\n", + " return item.x;\n", + " }\n", + "\n", + " int getY() {\n", + " return item.y;\n", + " }\n", + "\n", + " int getZ() {\n", + " return item.z;\n", + " }\n", + "\n", + " int weight() {\n", + " return item.weight();\n", + " }\n", + "}\n", + "\n", + "class Bounded\n", + " extends Coord implements HasColor, Weight {\n", + " @Override\n", + " public java.awt.Color getColor() {\n", + " return null;\n", + " }\n", + "\n", + " @Override\n", + " public int weight() {\n", + " return 0;\n", + " }\n", + "}\n", + "\n", + "public class BasicBounds {\n", + " public static void main(String[] args) {\n", + " Solid solid =\n", + " new Solid<>(new Bounded());\n", + " solid.color();\n", + " solid.getY();\n", + " solid.weight();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可能会观察到 **BasicBounds.java** 中似乎包含一些冗余,它们可以通过继承来消除。在这里,每个继承级别还添加了边界约束:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/InheritBounds.java\n", + "\n", + "class HoldItem {\n", + " T item;\n", + "\n", + " HoldItem(T item) {\n", + " this.item = item;\n", + " }\n", + "\n", + " T getItem() {\n", + " return item;\n", + " }\n", + "}\n", + "\n", + "class WithColor2\n", + " extends HoldItem {\n", + " WithColor2(T item) {\n", + " super(item);\n", + " }\n", + "\n", + " java.awt.Color color() {\n", + " return item.getColor();\n", + " }\n", + "}\n", + "\n", + "class WithColorCoord2\n", + " extends WithColor2 {\n", + " WithColorCoord2(T item) {\n", + " super(item);\n", + " }\n", + "\n", + " int getX() {\n", + " return item.x;\n", + " }\n", + "\n", + " int getY() {\n", + " return item.y;\n", + " }\n", + "\n", + " int getZ() {\n", + " return item.z;\n", + " }\n", + "}\n", + "\n", + "class Solid2\n", + " extends WithColorCoord2 {\n", + " Solid2(T item) {\n", + " super(item);\n", + " }\n", + "\n", + " int weight() {\n", + " return item.weight();\n", + " }\n", + "}\n", + "\n", + "public class InheritBounds {\n", + " public static void main(String[] args) {\n", + " Solid2 solid2 =\n", + " new Solid2<>(new Bounded());\n", + " solid2.color();\n", + " solid2.getY();\n", + " solid2.weight();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**HoldItem** 拥有一个对象,因此此行为将继承到 **WithColor2** 中,这也需要其参数符合 **HasColor**。 **WithColorCoord2** 和 **Solid2** 进一步扩展了层次结构,并在每个级别添加了边界。现在,这些方法已被继承,并且在每个类中不再重复。\n", + "\n", + "这是一个具有更多层次的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/EpicBattle.java\n", + "// Bounds in Java generics\n", + "\n", + "import java.util.List;\n", + "\n", + "interface SuperPower {\n", + "}\n", + "\n", + "interface XRayVision extends SuperPower {\n", + " void seeThroughWalls();\n", + "}\n", + "\n", + "interface SuperHearing extends SuperPower {\n", + " void hearSubtleNoises();\n", + "}\n", + "\n", + "interface SuperSmell extends SuperPower {\n", + " void trackBySmell();\n", + "}\n", + "\n", + "class SuperHero {\n", + " POWER power;\n", + "\n", + " SuperHero(POWER power) {\n", + " this.power = power;\n", + " }\n", + "\n", + " POWER getPower() {\n", + " return power;\n", + " }\n", + "}\n", + "\n", + "class SuperSleuth\n", + " extends SuperHero {\n", + " SuperSleuth(POWER power) {\n", + " super(power);\n", + " }\n", + "\n", + " void see() {\n", + " power.seeThroughWalls();\n", + " }\n", + "}\n", + "\n", + "class\n", + "CanineHero\n", + " extends SuperHero {\n", + " CanineHero(POWER power) {\n", + " super(power);\n", + " }\n", + "\n", + " void hear() {\n", + " power.hearSubtleNoises();\n", + " }\n", + "\n", + " void smell() {\n", + " power.trackBySmell();\n", + " }\n", + "}\n", + "\n", + "class SuperHearSmell\n", + " implements SuperHearing, SuperSmell {\n", + " @Override\n", + " public void hearSubtleNoises() {\n", + " }\n", + "\n", + " @Override\n", + " public void trackBySmell() {\n", + " }\n", + "}\n", + "\n", + "class DogPerson extends CanineHero {\n", + " DogPerson() {\n", + " super(new SuperHearSmell());\n", + " }\n", + "}\n", + "\n", + "public class EpicBattle {\n", + " // Bounds in generic methods:\n", + " static \n", + " void useSuperHearing(SuperHero hero) {\n", + " hero.getPower().hearSubtleNoises();\n", + " }\n", + "\n", + " static \n", + " void superFind(SuperHero hero) {\n", + " hero.getPower().hearSubtleNoises();\n", + " hero.getPower().trackBySmell();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " DogPerson dogPerson = new DogPerson();\n", + " useSuperHearing(dogPerson);\n", + " superFind(dogPerson);\n", + " // You can do this:\n", + " List audioPeople;\n", + " // But you can't do this:\n", + " // List dogPs;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来将要研究的通配符将会把范围限制在单个类型。\n", + "\n", + "\n", + "\n", + "## 通配符\n", + "\n", + "你已经在 [集合](book/12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](book/19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。\n", + "\n", + "我们的起始示例要展示数组的一种特殊行为:你可以将派生类的数组赋值给基类的引用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CovariantArrays.java\n", + "\n", + "class Fruit {}\n", + "\n", + "class Apple extends Fruit {}\n", + "\n", + "class Jonathan extends Apple {}\n", + "\n", + "class Orange extends Fruit {}\n", + "\n", + "public class CovariantArrays {\n", + " \n", + " public static void main(String[] args) {\n", + " Fruit[] fruit = new Apple[10];\n", + " fruit[0] = new Apple(); // OK\n", + " fruit[1] = new Jonathan(); // OK\n", + " // Runtime type is Apple[], not Fruit[] or Orange[]:\n", + " try {\n", + " // Compiler allows you to add Fruit:\n", + " fruit[0] = new Fruit(); // ArrayStoreException\n", + " } catch (Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " try {\n", + " // Compiler allows you to add Oranges:\n", + " fruit[0] = new Orange(); // ArrayStoreException\n", + " } catch (Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "java.lang.ArrayStoreException: Fruit\n", + "java.lang.ArrayStoreException: Orange" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`main()` 中的第一行创建了 **Apple** 数组,并赋值给一个 **Fruit** 数组引用。这是有意义的,因为 **Apple** 也是一种 **Fruit**,因此 **Apple** 数组应该也是一个 **Fruit** 数组。\n", + "\n", + "但是,如果实际的数组类型是 **Apple[]**,你可以在其中放置 **Apple** 或 **Apple** 的子类型,这在编译期和运行时都可以工作。但是你也可以在数组中放置 **Fruit** 对象。这对编译器来说是有意义的,因为它有一个 **Fruit[]** 引用——它有什么理由不允许将 **Fruit** 对象或任何从 **Fruit** 继承出来的对象(比如 **Orange**),放置到这个数组中呢?因此在编译期,这是允许的。然而,运行时的数组机制知道它处理的是 **Apple[]**,因此会在向数组中放置异构类型时抛出异常。\n", + "\n", + "向上转型用在这里不合适。你真正在做的是将一个数组赋值给另一个数组。数组的行为是持有其他对象,这里只是因为我们能够向上转型而已,所以很明显,数组对象可以保留有关它们包含的对象类型的规则。看起来就像数组对它们持有的对象是有意识的,因此在编译期检查和运行时检查之间,你不能滥用它们。\n", + "\n", + "数组的这种赋值并不是那么可怕,因为在运行时你可以发现插入了错误的类型。但是泛型的主要目标之一是将这种错误检测移到编译期。所以当我们试图使用泛型集合代替数组时,会发生什么呢?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/NonCovariantGenerics.java\n", + "// {WillNotCompile}\n", + "\n", + "import java.util.*;\n", + "\n", + "public class NonCovariantGenerics {\n", + " // Compile Error: incompatible types:\n", + " List flist = new ArrayList();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尽管你在首次阅读这段代码时会认为“不能将一个 **Apple** 集合赋值给一个 **Fruit** 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 **Apple** 的泛型赋值给一个涉及 **Fruit** 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 将持有 **Apple** 和 **Apple** 的子类型,**Fruit** 的 **List** 将持有任何类型的 **Fruit**。是的,这包括 **Apple**,但是它不是一个 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在类型上不等价于 **Fruit** 的 **List**,即使 **Apple** 是一种 **Fruit** 类型。\n", + "\n", + "真正的问题是我们在讨论的集合类型,而不是集合持有对象的类型。与数组不同,泛型没有内建的协变类型。这是因为数组是完全在语言中定义的,因此可以具有编译期和运行时的内建检查,但是在使用泛型时,编译器和运行时系统不知道你想用类型做什么,以及应该采用什么规则。\n", + "\n", + "但是,有时你想在两个类型间建立某种向上转型关系。通配符可以产生这种关系。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericsAndCovariance.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class GenericsAndCovariance {\n", + " \n", + " public static void main(String[] args) {\n", + " // Wildcards allow covariance:\n", + " List flist = new ArrayList<>();\n", + " // Compile Error: can't add any type of object:\n", + " // flist.add(new Apple());\n", + " // flist.add(new Fruit());\n", + " // flist.add(new Object());\n", + " flist.add(null); // Legal but uninteresting\n", + " // We know it returns at least Fruit:\n", + " Fruit f = flist.get(0);\n", + " }\n", + " \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**flist** 的类型现在是 `List`,你可以读作“一个具有任何从 **Fruit** 继承的类型的列表”。然而,这实际上并不意味着这个 **List** 将持有任何类型的 **Fruit**。通配符引用的是明确的类型,因此它意味着“某种 **flist** 引用没有指定的具体类型”。因此这个被赋值的 **List** 必须持有诸如 **Fruit** 或 **Apple** 这样的指定类型,但是为了向上转型为 **Fruit**,这个类型是什么没人在意。\n", + "\n", + "**List** 必须持有一种具体的 **Fruit** 或 **Fruit** 的子类型,但是如果你不关心具体的类型是什么,那么你能对这样的 **List** 做什么呢?如果不知道 **List** 中持有的对象是什么类型,你怎能保证安全地向其中添加对象呢?就像在 **CovariantArrays.java** 中向上转型一样,你不能,除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。\n", + "\n", + "你可能认为事情开始变得有点走极端了,因为现在你甚至不能向刚刚声明过将持有 **Apple** 对象的 **List** 中放入一个 **Apple** 对象。是的,但编译器并不知道这一点。`List` 可能合法地指向一个 `List`。一旦执行这种类型的向上转型,你就丢失了向其中传递任何对象的能力,甚至传递 **Object** 也不行。\n", + "\n", + "另一方面,如果你调用了一个返回 **Fruit** 的方法,则是安全的,因为你知道这个 **List** 中的任何对象至少具有 **Fruit** 类型,因此编译器允许这么做。\n", + "\n", + "### 编译器有多聪明\n", + "\n", + "现在你可能会猜想自己不能去调用任何接受参数的方法,但是考虑下面的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CompilerIntelligence.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class CompilerIntelligence {\n", + " \n", + " public static void main(String[] args) {\n", + " List flist = Arrays.asList(new Apple());\n", + " Apple a = (Apple) flist.get(0); // No warning\n", + " flist.contains(new Apple()); // Argument is 'Object'\n", + " flist.indexOf(new Apple()); // Argument is 'Object'\n", + " }\n", + " \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里对 `contains()` 和 `indexOf()` 的调用接受 **Apple** 对象作为参数,执行没问题。这是否意味着编译器实际上会检查代码,以查看是否有某个特定的方法修改了它的对象?\n", + "\n", + "通过查看 **ArrayList** 的文档,我们发现编译器没有那么聪明。尽管 `add()` 接受一个泛型参数类型的参数,但 `contains()` 和 `indexOf()` 接受的参数类型是 **Object**。因此当你指定一个 `ArrayList` 时,`add()` 的参数就变成了\"**? extends Fruit**\"。从这个描述中,编译器无法得知这里需要 **Fruit** 的哪个具体子类型,因此它不会接受任何类型的 **Fruit**。如果你先把 **Apple** 向上转型为 **Fruit**,也没有关系——编译器仅仅会拒绝调用像 `add()` 这样参数列表中涉及通配符的方法。\n", + "\n", + "`contains()` 和 `indexOf()` 的参数类型是 **Object**,不涉及通配符,所以编译器允许调用它们。这意味着将由泛型类的设计者来决定哪些调用是“安全的”,并使用 **Object** 类作为它们的参数类型。为了禁止对类型中使用了通配符的方法调用,需要在参数列表中使用类型参数。\n", + "\n", + "下面展示一个简单的 **Holder** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Holder.java\n", + "\n", + "public class Holder {\n", + "\n", + " private T value;\n", + "\n", + " public Holder() {}\n", + "\n", + " public Holder(T val) {\n", + " value = val;\n", + " }\n", + "\n", + " public void set(T val) {\n", + " value = val;\n", + " }\n", + "\n", + " public T get() {\n", + " return value;\n", + " }\n", + "\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof Holder && Objects.equals(value, ((Holder) o).value);\n", + " }\n", + "\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hashCode(value);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Holder apple = new Holder<>(new Apple());\n", + " Apple d = apple.get();\n", + " apple.set(d);\n", + " // Holder fruit = apple; // Cannot upcast\n", + " Holder fruit = apple; // OK\n", + " Fruit p = fruit.get();\n", + " d = (Apple) fruit.get();\n", + " try {\n", + " Orange c = (Orange) fruit.get(); // No warning\n", + " } catch (Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " // fruit.set(new Apple()); // Cannot call set()\n", + " // fruit.set(new Fruit()); // Cannot call set()\n", + " System.out.println(fruit.equals(d)); // OK\n", + " }\n", + "}\n", + "/* Output\n", + "java.lang.ClassCastException: Apple cannot be cast to Orange\n", + "false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是\"**? extends Fruit**\",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。\n", + "\n", + "但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。\n", + "\n", + "Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](book/Appendix-Understanding-equals-and-hashCode) 一章。\n", + "\n", + "### 逆变\n", + "\n", + "还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,或者甚至使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SuperTypeWildcards.java\n", + "import java.util.*;\n", + "public class SuperTypeWildcards {\n", + " static void writeTo(List apples) {\n", + " apples.add(new Apple());\n", + " apples.add(new Jonathan());\n", + " // apples.add(new Fruit()); // Error\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "参数 **apples** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是因为 **Apple** 是下界,所以你知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。\n", + "下面的示例复习了一下逆变和通配符的的使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericReading.java\n", + "import java.util.*;\n", + "\n", + "public class GenericReading {\n", + " static List apples = Arrays.asList(new Apple());\n", + " static List fruit = Arrays.asList(new Fruit());\n", + " \n", + " static T readExact(List list) {\n", + " return list.get(0);\n", + " }\n", + " \n", + " // A static method adapts to each call:\n", + " static void f1() {\n", + " Apple a = readExact(apples);\n", + " Fruit f = readExact(fruit);\n", + " f = readExact(apples);\n", + " }\n", + " \n", + " // A class type is established\n", + " // when the class is instantiated:\n", + " static class Reader {\n", + " T readExact(List list) { \n", + " return list.get(0); \n", + " }\n", + " }\n", + " \n", + " static void f2() {\n", + " Reader fruitReader = new Reader<>();\n", + " Fruit f = fruitReader.readExact(fruit);\n", + " //- Fruit a = fruitReader.readExact(apples);\n", + " // error: incompatible types: List\n", + " // cannot be converted to List\n", + " }\n", + " \n", + " static class CovariantReader {\n", + " T readCovariant(List list) {\n", + " return list.get(0);\n", + " }\n", + " }\n", + " \n", + " static void f3() {\n", + " CovariantReader fruitReader = new CovariantReader<>();\n", + " Fruit f = fruitReader.readCovariant(fruit);\n", + " Fruit a = fruitReader.readCovariant(apples);\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " f1(); \n", + " f2(); \n", + " f3();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`readExact()` 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么在读取时就不需要协变类型了。\n", + "然而对于泛型类来说,当你创建这个类的实例时,就要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 也应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。\n", + "为了修正这个问题,`CovariantReader.readCovariant()` 方法将接受 `List<?extends T>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从 T 导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。\n", + "\n", + "### 无界通配符\n", + "\n", + "无界通配符 `` 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/UnboundedWildcards1.java\n", + "import java.util.*;\n", + "\n", + "public class UnboundedWildcards1 {\n", + " static List list1;\n", + " static List list2;\n", + " static List list3;\n", + " \n", + " static void assign1(List list) {\n", + " list1 = list;\n", + " list2 = list;\n", + " //- list3 = list;\n", + " // warning: [unchecked] unchecked conversion\n", + " // list3 = list;\n", + " // ^\n", + " // required: List\n", + " // found: List\n", + " }\n", + " \n", + " static void assign2(List list) {\n", + " list1 = list;\n", + " list2 = list;\n", + " list3 = list;\n", + " }\n", + " \n", + " static void assign3(List list) {\n", + " list1 = list;\n", + " list2 = list;\n", + " list3 = list;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " assign1(new ArrayList());\n", + " assign2(new ArrayList());\n", + " //- assign3(new ArrayList());\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method assign3 in class UnboundedWildcards1\n", + " // is applied to given types\n", + " // assign3(new ArrayList());\n", + " // ^\n", + " // required: List\n", + " // found: ArrayList\n", + " // warning: [unchecked] unchecked conversion\n", + " // assign3(new ArrayList());\n", + " // ^\n", + " // required: List\n", + " // found: ArrayList\n", + " // 2 warnings\n", + " assign1(new ArrayList<>());\n", + " assign2(new ArrayList<>());\n", + " assign3(new ArrayList<>());\n", + " // Both forms are acceptable as List:\n", + " List wildList = new ArrayList();\n", + " wildList = new ArrayList<>();\n", + " assign1(wildList);\n", + " assign2(wildList);\n", + " assign3(wildList);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上它是在声明:“我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。”\n", + "第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/UnboundedWildcards2.java\n", + "import java.util.*;\n", + "\n", + "public class UnboundedWildcards2 {\n", + " static Map map1;\n", + " static Map map2;\n", + " static Map map3;\n", + " \n", + " static void assign1(Map map) { \n", + " map1 = map; \n", + " }\n", + " \n", + " static void assign2(Map map) { \n", + " map2 = map; \n", + " }\n", + " \n", + " static void assign3(Map map) { \n", + " map3 = map; \n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " assign1(new HashMap());\n", + " assign2(new HashMap());\n", + " //- assign3(new HashMap());\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method assign3 in class UnboundedWildcards2\n", + " // is applied to given types\n", + " // assign3(new HashMap());\n", + " // ^\n", + " // required: Map\n", + " // found: HashMap\n", + " // warning: [unchecked] unchecked conversion\n", + " // assign3(new HashMap());\n", + " // ^\n", + " // required: Map\n", + " // found: HashMap\n", + " // 2 warnings\n", + " assign1(new HashMap<>());\n", + " assign2(new HashMap<>());\n", + " assign3(new HashMap<>());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards1.java** 展示了编译器处理 `List` 和 `List` 是不同的。\n", + "令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。”\n", + "编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Wildcards.java\n", + "// Exploring the meaning of wildcards\n", + "\n", + "public class Wildcards {\n", + " // Raw argument:\n", + " static void rawArgs(Holder holder, Object arg) {\n", + " //- holder.set(arg);\n", + " // warning: [unchecked] unchecked call to set(T)\n", + " // as a member of the raw type Holder\n", + " // holder.set(arg);\n", + " // ^\n", + " // where T is a type-variable:\n", + " // T extends Object declared in class Holder\n", + " // 1 warning\n", + "\n", + " // Can't do this; don't have any 'T':\n", + " // T t = holder.get();\n", + "\n", + " // OK, but type information is lost:\n", + " Object obj = holder.get();\n", + " }\n", + " \n", + " // Like rawArgs(), but errors instead of warnings:\n", + " static void unboundedArg(Holder holder, Object arg) {\n", + " //- holder.set(arg);\n", + " // error: method set in class Holder\n", + " // cannot be applied to given types;\n", + " // holder.set(arg);\n", + " // ^\n", + " // required: CAP#1\n", + " // found: Object\n", + " // reason: argument mismatch;\n", + " // Object cannot be converted to CAP#1\n", + " // where T is a type-variable:\n", + " // T extends Object declared in class Holder\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Object from capture of ?\n", + " // 1 error\n", + "\n", + " // Can't do this; don't have any 'T':\n", + " // T t = holder.get();\n", + "\n", + " // OK, but type information is lost:\n", + " Object obj = holder.get();\n", + " }\n", + " \n", + " static T exact1(Holder holder) {\n", + " return holder.get();\n", + " }\n", + " \n", + " static T exact2(Holder holder, T arg) {\n", + " holder.set(arg);\n", + " return holder.get();\n", + " }\n", + " \n", + " static T wildSubtype(Holder holder, T arg) {\n", + " //- holder.set(arg);\n", + " // error: method set in class Holder\n", + " // cannot be applied to given types;\n", + " // holder.set(arg);\n", + " // ^\n", + " // required: CAP#1\n", + " // found: T#1\n", + " // reason: argument mismatch;\n", + " // T#1 cannot be converted to CAP#1\n", + " // where T#1,T#2 are type-variables:\n", + " // T#1 extends Object declared in method\n", + " // wildSubtype(Holder,T#1)\n", + " // T#2 extends Object declared in class Holder\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends T#1 from\n", + " // capture of ? extends T#1\n", + " // 1 error\n", + " return holder.get();\n", + " }\n", + " \n", + " static void wildSupertype(Holder holder, T arg) {\n", + " holder.set(arg);\n", + " //- T t = holder.get();\n", + " // error: incompatible types:\n", + " // CAP#1 cannot be converted to T\n", + " // T t = holder.get();\n", + " // ^\n", + " // where T is a type-variable:\n", + " // T extends Object declared in method\n", + " // wildSupertype(Holder,T)\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Object super:\n", + " // T from capture of ? super T\n", + " // 1 error\n", + "\n", + " // OK, but type information is lost:\n", + " Object obj = holder.get();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " Holder raw = new Holder<>();\n", + " // Or:\n", + " raw = new Holder();\n", + " Holder qualified = new Holder<>();\n", + " Holder unbounded = new Holder<>();\n", + " Holder bounded = new Holder<>();\n", + " Long lng = 1L;\n", + "\n", + " rawArgs(raw, lng);\n", + " rawArgs(qualified, lng);\n", + " rawArgs(unbounded, lng);\n", + " rawArgs(bounded, lng);\n", + "\n", + " unboundedArg(raw, lng);\n", + " unboundedArg(qualified, lng);\n", + " unboundedArg(unbounded, lng);\n", + " unboundedArg(bounded, lng);\n", + "\n", + " //- Object r1 = exact1(raw);\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method exact1 in class Wildcards is applied\n", + " // to given types\n", + " // Object r1 = exact1(raw);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact1(Holder)\n", + " // warning: [unchecked] unchecked conversion\n", + " // Object r1 = exact1(raw);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact1(Holder)\n", + " // 2 warnings\n", + "\n", + " Long r2 = exact1(qualified);\n", + " Object r3 = exact1(unbounded); // Must return Object\n", + " Long r4 = exact1(bounded);\n", + "\n", + " //- Long r5 = exact2(raw, lng);\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method exact2 in class Wildcards is\n", + " // applied to given types\n", + " // Long r5 = exact2(raw, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact2(Holder,T)\n", + " // warning: [unchecked] unchecked conversion\n", + " // Long r5 = exact2(raw, lng);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact2(Holder,T)\n", + " // 2 warnings\n", + "\n", + " Long r6 = exact2(qualified, lng);\n", + "\n", + " //- Long r7 = exact2(unbounded, lng);\n", + " // error: method exact2 in class Wildcards\n", + " // cannot be applied to given types;\n", + " // Long r7 = exact2(unbounded, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // reason: inference variable T has\n", + " // incompatible bounds\n", + " // equality constraints: CAP#1\n", + " // lower bounds: Long\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact2(Holder,T)\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Object from capture of ?\n", + " // 1 error\n", + "\n", + " //- Long r8 = exact2(bounded, lng);\n", + " // error: method exact2 in class Wildcards\n", + " // cannot be applied to given types;\n", + " // Long r8 = exact2(bounded, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // reason: inference variable T\n", + " // has incompatible bounds\n", + " // equality constraints: CAP#1\n", + " // lower bounds: Long\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method exact2(Holder,T)\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Long from\n", + " // capture of ? extends Long\n", + " // 1 error\n", + "\n", + " //- Long r9 = wildSubtype(raw, lng);\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method wildSubtype in class Wildcards\n", + " // is applied to given types\n", + " // Long r9 = wildSubtype(raw, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSubtype(Holder,T)\n", + " // warning: [unchecked] unchecked conversion\n", + " // Long r9 = wildSubtype(raw, lng);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSubtype(Holder,T)\n", + " // 2 warnings\n", + "\n", + " Long r10 = wildSubtype(qualified, lng);\n", + " // OK, but can only return Object:\n", + " Object r11 = wildSubtype(unbounded, lng);\n", + " Long r12 = wildSubtype(bounded, lng);\n", + "\n", + " //- wildSupertype(raw, lng);\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method wildSupertype in class Wildcards\n", + " // is applied to given types\n", + " // wildSupertype(raw, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSupertype(Holder,T)\n", + " // warning: [unchecked] unchecked conversion\n", + " // wildSupertype(raw, lng);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSupertype(Holder,T)\n", + " // 2 warnings\n", + "\n", + " wildSupertype(qualified, lng);\n", + "\n", + " //- wildSupertype(unbounded, lng);\n", + " // error: method wildSupertype in class Wildcards\n", + " // cannot be applied to given types;\n", + " // wildSupertype(unbounded, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // reason: cannot infer type-variable(s) T\n", + " // (argument mismatch; Holder\n", + " // cannot be converted to Holder)\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSupertype(Holder,T)\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Object from capture of ?\n", + " // 1 error\n", + "\n", + " //- wildSupertype(bounded, lng);\n", + " // error: method wildSupertype in class Wildcards\n", + " // cannot be applied to given types;\n", + " // wildSupertype(bounded, lng);\n", + " // ^\n", + " // required: Holder,T\n", + " // found: Holder,Long\n", + " // reason: cannot infer type-variable(s) T\n", + " // (argument mismatch; Holder\n", + " // cannot be converted to Holder)\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method wildSupertype(Holder,T)\n", + " // where CAP#1 is a fresh type-variable:\n", + " // CAP#1 extends Long from capture of\n", + " // ? extends Long\n", + " // 1 error\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此无论何时,只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object**。\n", + "人们很自然地会开始考虑原生 `Holder` 与 `Holder` 是大致相同的事物。但是 `unboundedArg()` 强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生 **Holder** 将持有任何类型的组合,而 `Holder` 将持有具有某种具体类型的同构集合,因此不能只是向其中传递 **Object** 。\n", + "在 `exact1()` 和 `exact2()` 中,你可以看到使用了确切的泛型参数——没有任何通配符。你将看到,`exact2()`与 `exact1()` 具有不同的限制,因为它有额外的参数。\n", + "在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果 T 是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。\n", + "`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何 T 的基类型的容器。因此, `set()` 可以接受 **T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。\n", + "这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制:因为你没有 **T**,所以你不能将 `set()` 或 `get()` 作用于 **T** 上。\n", + "\n", + "在 `main()` 方法中你看到了某些方法在接受某些类型的参数时没有报错和警告。为了迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。\n", + "\n", + "如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生 **Holder** 引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向 `exact1()` 传递一个无界引用,就不会有任何可以确定返回类型的类型信息。\n", + "可以看到,`exact2()` 具有最多的限制,因为它希望精确地得到一个 `Holder` ,以及一个具有类型 **T** 的参数,正由于此,它将产生错误或警告,除非提供确切的参数。有时,这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 `wildSubtype()` 中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在 `wildSupertype()` 中看到的那样)。\n", + "因此,使用确切类型来替代通配符类型的好处是,可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。\n", + "\n", + "### 捕获转换\n", + "\n", + "有一种特殊情况需要使用 `` 而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CaptureConversion.java\n", + "\n", + "public class CaptureConversion {\n", + " static void f1(Holder holder) {\n", + " T t = holder.get();\n", + " System.out.println(t.getClass().getSimpleName());\n", + " }\n", + " \n", + " static void f2(Holder holder) {\n", + " f1(holder); // Call with captured type\n", + " }\n", + " \n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " Holder raw = new Holder<>(1);\n", + " f1(raw);\n", + " // warning: [unchecked] unchecked method invocation:\n", + " // method f1 in class CaptureConversion\n", + " // is applied to given types\n", + " // f1(raw);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method f1(Holder)\n", + " // warning: [unchecked] unchecked conversion\n", + " // f1(raw);\n", + " // ^\n", + " // required: Holder\n", + " // found: Holder\n", + " // where T is a type-variable:\n", + " // T extends Object declared in\n", + " // method f1(Holder)\n", + " // 2 warnings\n", + " f2(raw); // No warnings\n", + " \n", + " Holder rawBasic = new Holder();\n", + " rawBasic.set(new Object());\n", + " // warning: [unchecked] unchecked call to set(T)\n", + " // as a member of the raw type Holder\n", + " // rawBasic.set(new Object());\n", + " // ^\n", + " // where T is a type-variable:\n", + " // T extends Object declared in class Holder\n", + " // 1 warning\n", + " f2(rawBasic); // No warnings\n", + " \n", + " // Upcast to Holder, still figures it out:\n", + " Holder wildcarded = new Holder<>(1.0);\n", + " f2(wildcarded);\n", + " }\n", + "}\n", + "/* Output:\n", + "Integer\n", + "Integer\n", + "Object\n", + "Double\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中调用了 `f1()`,而 `f1()` 需要一个已知参数。这里所发生的是:在调用 `f2()` 的过程中捕获了参数类型,并在调用 `f1()` 时使用了这种类型。\n", + "你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。\n", + "\n", + "\n", + "\n", + "## 问题\n", + "\n", + "本节将阐述在使用 Java 泛型时会出现的各类问题。\n", + "\n", + "### 任何基本类型都不能作为类型参数\n", + "\n", + "正如本章早先提到的,Java 泛型的限制之一是不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。\n", + "解决方法是使用基本类型的包装器类以及自动装箱机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个集合,那么你将发现自动装箱机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList` 一样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ListOfInt.java\n", + "// Autoboxing compensates for the inability\n", + "// to use primitives in generics\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class ListOfInt {\n", + " public static void main(String[] args) {\n", + " List li = IntStream.range(38, 48)\n", + " .boxed() // Converts ints to Integers\n", + " .collect(Collectors.toList());\n", + " System.out.println(li);\n", + " }\n", + "}\n", + "/* Output:\n", + "[38, 39, 40, 41, 42, 43, 44, 45, 46, 47]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通常,这种解决方案工作得很好——能够成功地存储和读取 **int**,自动装箱隐藏了转换的过程。但是如果性能成为问题的话,就需要使用专门为基本类型适配的特殊版本的集合;一个开源版本的实现是 **org.apache.commons.collections.primitives**。\n", + "下面是另外一种方式,它可以创建持有 **Byte** 的 **Set**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ByteSet.java\n", + "import java.util.*;\n", + "\n", + "public class ByteSet {\n", + " Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };\n", + " Set mySet = new HashSet<>(Arrays.asList(possibles));\n", + " // But you can't do this:\n", + " // Set mySet2 = new HashSet<>(\n", + " // Arrays.asList(1,2,3,4,5,6,7,8,9));\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "自动装箱机制解决了一些问题,但并没有解决所有问题。\n", + "\n", + "在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的)。**Supplier** 实现来自 [数组](book/21-Arrays.md) 一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用对象填充了数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/PrimitiveGenericTest.java\n", + "import onjava.*;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "// Fill an array using a generator:\n", + "interface FillArray {\n", + " static T[] fill(T[] a, Supplier gen) {\n", + " Arrays.setAll(a, n -> gen.get());\n", + " return a;\n", + " }\n", + " \n", + " static int[] fill(int[] a, IntSupplier gen) {\n", + " Arrays.setAll(a, n -> gen.getAsInt());\n", + " return a;\n", + " }\n", + " \n", + " static long[] fill(long[] a, LongSupplier gen) {\n", + " Arrays.setAll(a, n -> gen.getAsLong());\n", + " return a;\n", + " }\n", + " \n", + " static double[] fill(double[] a, DoubleSupplier gen) {\n", + " Arrays.setAll(a, n -> gen.getAsDouble());\n", + " return a;\n", + " }\n", + "}\n", + "\n", + "public class PrimitiveGenericTest {\n", + " public static void main(String[] args) {\n", + " String[] strings = FillArray.fill(\n", + " new String[5], new Rand.String(9));\n", + " System.out.println(Arrays.toString(strings));\n", + " int[] integers = FillArray.fill(\n", + " new int[9], new Rand.Pint());\n", + " System.out.println(Arrays.toString(integers));\n", + " }\n", + "}\n", + "/* Output:\n", + "[btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl]\n", + "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768, 4948]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用一点,因为它返回填充的数组。\n", + "\n", + "### 实现参数化接口\n", + "\n", + "一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/MultipleInterfaceVariants.java\n", + "// {WillNotCompile}\n", + "package generics;\n", + "\n", + "interface Payable {}\n", + "\n", + "class Employee implements Payable {}\n", + "\n", + "class Hourly extends Employee implements Payable {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Hourly** 不能编译,因为擦除会将 `Payable` 和 `Payable` 简化为相同的类 **Payable**,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 **Payable** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。\n", + "\n", + "在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后看到的那样。\n", + "\n", + "### 转型和警告\n", + "\n", + "使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的集合在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericCast.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "class FixedSizeStack {\n", + " private final int size;\n", + " private Object[] storage;\n", + " private int index = 0;\n", + " \n", + " FixedSizeStack(int size) {\n", + " this.size = size;\n", + " storage = new Object[size];\n", + " }\n", + " \n", + " public void push(T item) {\n", + " if(index < size)\n", + " storage[index++] = item;\n", + " }\n", + " \n", + " @SuppressWarnings(\"unchecked\")\n", + " public T pop() {\n", + " return index == 0 ? null : (T)storage[--index];\n", + " }\n", + " \n", + " @SuppressWarnings(\"unchecked\")\n", + " Stream stream() {\n", + " return (Stream)Arrays.stream(storage);\n", + " }\n", + "}\n", + "\n", + "public class GenericCast {\n", + " static String[] letters = \"ABCDEFGHIJKLMNOPQRS\".split(\"\");\n", + " \n", + " public static void main(String[] args) {\n", + " FixedSizeStack strings =\n", + " new FixedSizeStack<>(letters.length);\n", + " Arrays.stream(\"ABCDEFGHIJKLMNOPQRS\".split(\"\"))\n", + " .forEach(strings::push);\n", + " System.out.println(strings.pop());\n", + " strings.stream()\n", + " .map(s -> s + \" \")\n", + " .forEach(System.out::print);\n", + " }\n", + "}\n", + "/* Output:\n", + "S\n", + "A B C D E F G H I J K L M N O P Q R S\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有 **@SuppressWarnings** 注解,编译器将对 `pop()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop()` 方法实际上并没有执行任何转型。\n", + "这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。\n", + "有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/NeedCasting.java\n", + "import java.io.*;\n", + "import java.util.*;\n", + "\n", + "public class NeedCasting {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public void f(String[] args) throws Exception {\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(args[0]));\n", + " List shapes = (List)in.readObject();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正如你将在 [附录:对象序列化](book/Appendix-Object-Serialization.md) 中学到的那样,`readObject()` 无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解并编译这个程序时,就会得到下面的警告。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NeedCasting.java uses unchecked or unsafe operations.\n", + "Recompile with -Xlint:unchecked for details.\n", + "\n", + "And if you follow the instructions and recompile with -\n", + "Xlint:unchecked :(如果遵循这条指示,使用-Xlint:unchecked来重新编译:)\n", + "\n", + "NeedCasting.java:10: warning: [unchecked] unchecked cast\n", + " List shapes = (List)in.readObject();\n", + " required: List\n", + " found: Object\n", + "1 warning" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,既通过泛型类来转型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ClassCasting.java\n", + "import java.io.*;\n", + "import java.util.*;\n", + "\n", + "public class ClassCasting {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public void f(String[] args) throws Exception {\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(args[0]));\n", + " // Won't Compile:\n", + " // List lw1 =\n", + " // List<>.class.cast(in.readObject());\n", + " List lw2 = List.class.cast(in.readObject());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是,不能转型到实际类型( `List` )。也就是说,不能声明:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "List.class.cast(in.readobject())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "甚至当你添加一个像下面这样的另一个转型时:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(List)List.class.cast(in.readobject())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "仍旧会得到一个警告。\n", + "\n", + "### 重载\n", + "\n", + "下面的程序是不能编译的,即使它看起来是合理的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/UseList.java\n", + "// {WillNotCompile}\n", + "import java.util.*;\n", + "\n", + "public class UseList {\n", + " void f(List v) {}\n", + " void f(List v) {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为擦除,所以重载方法产生了相同的类型签名。\n", + "\n", + "因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/UseList2.java\n", + "\n", + "import java.util.*;\n", + "\n", + "public class UseList2 {\n", + " void f1(List v) {}\n", + " void f2(List v) {}\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "幸运的是,编译器可以检测到这类问题。\n", + "\n", + "### 基类劫持接口\n", + "\n", + "假设你有一个实现了 **Comparable** 接口的 **Pet** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ComparablePet.java\n", + "\n", + "public class ComparablePet implements Comparable {\n", + " @Override\n", + " public int compareTo(ComparablePet o) {\n", + " return 0;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "尝试缩小 **ComparablePet** 子类的比较类型是有意义的。例如,**Cat** 类可以与其他的 **Cat** 比较:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/HijackedInterface.java\n", + "// {WillNotCompile}\n", + "\n", + "class Cat extends ComparablePet implements Comparable {\n", + " // error: Comparable cannot be inherited with\n", + " // different arguments: and \n", + " // class Cat\n", + " // ^\n", + " // 1 error\n", + " public int compareTo(Cat arg) {\n", + " return 0;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "不幸的是,这不能工作。一旦 **Comparable** 的类型参数设置为 **ComparablePet**,其他的实现类只能比较 **ComparablePet**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/RestrictedComparablePets.java\n", + "\n", + "public class Hamster extends ComparablePet implements Comparable {\n", + "\n", + " @Override\n", + " public int compareTo(ComparablePet arg) {\n", + " return 0;\n", + " }\n", + "}\n", + "// Or just:\n", + "class Gecko extends ComparablePet {\n", + " public int compareTo(ComparablePet arg) {\n", + " return 0;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Hamster** 显示了重新实现 **ComparableSet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。\n", + "\n", + "\n", + "\n", + "## 自限定的类型\n", + "\n", + "在 Java 泛型中,有一个似乎经常性出现的惯用法,它相当令人费解:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class SelfBounded> { // ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而 **T** 由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。\n", + "\n", + "当你首次看到它时,很难去解析它,它强调的是当 **extends** 关键字用于边界与用来创建子类明显是不同的。\n", + "\n", + "### 古怪的循环泛型\n", + "\n", + "为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。\n", + "\n", + "不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CuriouslyRecurringGeneric.java\n", + "\n", + "class GenericType {}\n", + "\n", + "public class CuriouslyRecurringGeneric\n", + " extends GenericType {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这可以按照 Jim Coplien 在 C++ 中的*古怪的循环模版模式*的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。\n", + "为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java 中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,尽管这些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/BasicHolder.java\n", + "\n", + "public class BasicHolder {\n", + " T element;\n", + " void set(T arg) { element = arg; }\n", + " T get() { return element; }\n", + " void f() {\n", + " System.out.println(element.getClass().getSimpleName());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。\n", + "我们可以在一个古怪的循环泛型中使用 **BasicHolder**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CRGWithBasicHolder.java\n", + "\n", + "class Subtype extends BasicHolder {}\n", + "\n", + "public class CRGWithBasicHolder {\n", + " public static void main(String[] args) {\n", + " Subtype st1 = new Subtype(), st2 = new Subtype();\n", + " st1.set(st2);\n", + " Subtype st3 = st1.get();\n", + " st1.f();\n", + " }\n", + "}\n", + "/* Output:\n", + "Subtype\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype**。\n", + "\n", + "### 自限定\n", + "\n", + "**BasicHolder** 可以使用任何类型作为其泛型参数,就像下面看到的那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Unconstrained.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "\n", + "class Other {}\n", + "class BasicOther extends BasicHolder {}\n", + "\n", + "public class Unconstrained {\n", + " public static void main(String[] args) {\n", + " BasicOther b = new BasicOther();\n", + " BasicOther b2 = new BasicOther();\n", + " b.set(new Other());\n", + " Other other = b.get();\n", + " b.f();\n", + " }\n", + "}\n", + "/* Output:\n", + "Other\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "限定将采取额外的步骤,强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SelfBounding.java\n", + "\n", + "class SelfBounded> {\n", + " T element;\n", + " SelfBounded set(T arg) {\n", + " element = arg;\n", + " return this;\n", + " }\n", + " T get() { return element; }\n", + "}\n", + "\n", + "class A extends SelfBounded {}\n", + "class B extends SelfBounded {} // Also OK\n", + "\n", + "class C extends SelfBounded {\n", + " C setAndGet(C arg) { \n", + " set(arg); \n", + " return get();\n", + " }\n", + "}\n", + "\n", + "class D {}\n", + "// Can't do this:\n", + "// class E extends SelfBounded {}\n", + "// Compile error:\n", + "// Type parameter D is not within its bound\n", + "\n", + "// Alas, you can do this, so you cannot force the idiom:\n", + "class F extends SelfBounded {}\n", + "\n", + "public class SelfBounding {\n", + " public static void main(String[] args) {\n", + " A a = new A();\n", + " a.set(new A());\n", + " a = a.set(new A()).get();\n", + " a = a.get();\n", + " C c = new C();\n", + " c = c.setAndGet(new C());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "自限定所做的,就是要求在继承关系中,像下面这样使用这个类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class A extends SelfBounded{}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这会强制要求将正在定义的类当作参数传递给基类。\n", + "\n", + "自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的 **SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。\n", + "遗憾的是, **F** 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。\n", + "注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 **E** 也会因此而变得可编译:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/NotSelfBounded.java\n", + "\n", + "public class NotSelfBounded {\n", + " T element;\n", + " NotSelfBounded set(T arg) {\n", + " element = arg;\n", + " return this;\n", + " }\n", + " T get() { return element; }\n", + "} \n", + "\n", + "class A2 extends NotSelfBounded {}\n", + "class B2 extends NotSelfBounded {}\n", + "\n", + "class C2 extends NotSelfBounded {\n", + " C2 setAndGet(C2 arg) { \n", + " set(arg); \n", + " return get(); \n", + " }\n", + "}\n", + "\n", + "class D2 {}\n", + "// Now this is OK:\n", + "class E2 extends NotSelfBounded {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因此很明显,自限定限制只能强制作用于继承关系。如果使用自限定,就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基类型。这会强制要求使用这个类的每个人都要遵循这种形式。\n", + "还可以将自限定用于泛型方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SelfBoundingMethods.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "\n", + "public class SelfBoundingMethods {\n", + " static > T f(T arg) {\n", + " return arg.set(arg).get();\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " A a = f(new A());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这可以防止这个方法被应用于除上述形式的自限定参数之外的任何事物上。\n", + "\n", + "### 参数协变\n", + "\n", + "自限定类型的价值在于它们可以产生*协变参数类型*——方法参数类型会随子类而变化。\n", + "\n", + "尽管自限定类型还可以产生与子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java 5 引入:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CovariantReturnTypes.java\n", + "\n", + "class Base {}\n", + "class Derived extends Base {}\n", + "\n", + "interface OrdinaryGetter {\n", + " Base get();\n", + "}\n", + "\n", + "interface DerivedGetter extends OrdinaryGetter {\n", + " // Overridden method return type can vary:\n", + " @Override\n", + " Derived get();\n", + "}\n", + "\n", + "public class CovariantReturnTypes {\n", + " void test(DerivedGetter d) {\n", + " Derived d2 = d.get();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**DerivedGetter** 中的 `get()` 方法覆盖了 **OrdinaryGetter** 中的 `get()` ,并返回了一个从 `OrdinaryGetter.get()` 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情(导出类方法应该能够返回比它覆盖的基类方法更具体的类型)但是这在早先的 Java 版本中是不合法的。\n", + "\n", + "自限定泛型事实上将产生确切的导出类型作为其返回值,就像在 `get()` 中所看到的一样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/GenericsAndReturnTypes.java\n", + "\n", + "interface GenericGetter> {\n", + " T get();\n", + "}\n", + "\n", + "interface Getter extends GenericGetter {}\n", + "\n", + "public class GenericsAndReturnTypes {\n", + " void test(Getter g) {\n", + " Getter result = g.get();\n", + " GenericGetter gg = g.get(); // Also the base type\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java 5。\n", + "\n", + "然而,在非泛型代码中,参数类型不能随子类型发生变化:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/OrdinaryArguments.java\n", + "\n", + "class OrdinarySetter {\n", + " void set(Base base) {\n", + " System.out.println(\"OrdinarySetter.set(Base)\");\n", + " }\n", + "}\n", + "\n", + "class DerivedSetter extends OrdinarySetter {\n", + " void set(Derived derived) {\n", + " System.out.println(\"DerivedSetter.set(Derived)\");\n", + " }\n", + "}\n", + "\n", + "public class OrdinaryArguments {\n", + " public static void main(String[] args) {\n", + " Base base = new Base();\n", + " Derived derived = new Derived();\n", + " DerivedSetter ds = new DerivedSetter();\n", + " ds.set(derived);\n", + " // Compiles--overloaded, not overridden!:\n", + " ds.set(base);\n", + " }\n", + "}\n", + "/* Output:\n", + "DerivedSetter.set(Derived)\n", + "OrdinarySetter.set(Base)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`set(derived)` 和 `set(base)` 都是合法的,因此 `DerivedSetter.set()` 没有覆盖 `OrdinarySetter.set()` ,而是重载了这个方法。从输出中可以看到,在 **DerivedSetter** 中有两个方法,因此基类版本仍旧是可用的,因此可以证明它被重载过。\n", + "但是,在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型为参数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SelfBoundingAndCovariantArguments.java\n", + "\n", + "interface SelfBoundSetter> {\n", + " void set(T arg);\n", + "}\n", + "\n", + "interface Setter extends SelfBoundSetter {}\n", + "\n", + "public class SelfBoundingAndCovariantArguments {\n", + " void\n", + " testA(Setter s1, Setter s2, SelfBoundSetter sbs) {\n", + " s1.set(s2);\n", + " //- s1.set(sbs);\n", + " // error: method set in interface SelfBoundSetter\n", + " // cannot be applied to given types;\n", + " // s1.set(sbs);\n", + " // ^\n", + " // required: Setter\n", + " // found: SelfBoundSetter\n", + " // reason: argument mismatch;\n", + " // SelfBoundSetter cannot be converted to Setter\n", + " // where T is a type-variable:\n", + " // T extends SelfBoundSetter declared in\n", + " // interface SelfBoundSetter\n", + " // 1 error\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器不能识别将基类型当作参数传递给 `set()` 的尝试,因为没有任何方法具有这样的签名。实际上,这个参数已经被覆盖。\n", + "如果不使用自限定类型,普通的继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/PlainGenericInheritance.java\n", + "\n", + "class GenericSetter { // Not self-bounded\n", + " void set(T arg) {\n", + " System.out.println(\"GenericSetter.set(Base)\");\n", + " }\n", + "}\n", + "\n", + "class DerivedGS extends GenericSetter {\n", + " void set(Derived derived) {\n", + " System.out.println(\"DerivedGS.set(Derived)\");\n", + " }\n", + "}\n", + "\n", + "public class PlainGenericInheritance {\n", + " public static void main(String[] args) {\n", + " Base base = new Base();\n", + " Derived derived = new Derived();\n", + " DerivedGS dgs = new DerivedGS();\n", + " dgs.set(derived);\n", + " dgs.set(base); // Overloaded, not overridden!\n", + " }\n", + "}\n", + "/* Output:\n", + "DerivedGS.set(Derived)\n", + "GenericSetter.set(Base)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这段代码在模仿 **OrdinaryArguments.java**;在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArguments.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得方法的一个版本,它将接受确切的参数类型。\n", + "\n", + "\n", + "\n", + "## 动态类型安全\n", + "\n", + "因为可以向 Java 5 之前的代码传递泛型集合,所以旧式代码仍旧有可能会破坏你的集合。Java 5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法 `checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的集合当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。\n", + "\n", + "受检查的集合在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的集合,就可以发现谁在试图插入不良对象。\n", + "让我们用受检查的集合来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/CheckedList.java\n", + "// Using Collection.checkedList()\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "\n", + "public class CheckedList {\n", + " @SuppressWarnings(\"unchecked\")\n", + " static void oldStyleMethod(List probablyDogs) {\n", + " probablyDogs.add(new Cat());\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " List dogs1 = new ArrayList<>();\n", + " oldStyleMethod(dogs1); // Quietly accepts a Cat\n", + " List dogs2 = Collections.checkedList(\n", + " new ArrayList<>(), Dog.class);\n", + " try {\n", + " oldStyleMethod(dogs2); // Throws an exception\n", + " } catch(Exception e) {\n", + " System.out.println(\"Expected: \" + e);\n", + " }\n", + " // Derived types work fine:\n", + " List pets = Collections.checkedList(\n", + " new ArrayList<>(), Pet.class);\n", + " pets.add(new Dog());\n", + " pets.add(new Cat());\n", + " }\n", + "}\n", + "/* Output:\n", + "Expected: java.lang.ClassCastException: Attempt to\n", + "insert class typeinfo.pets.Cat element into collection\n", + "with element type class typeinfo.pets.Dog\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "运行这个程序时,你会发现插入一个 **Cat** 对于 **dogs1** 来说没有任何问题,而 **dogs2** 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。\n", + "\n", + "\n", + "\n", + "## 泛型异常\n", + "\n", + "由于擦除的原因,**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。\n", + "但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常类型变化的泛型代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ThrowGenericException.java\n", + "\n", + "import java.util.*;\n", + "\n", + "interface Processor {\n", + " void process(List resultCollector) throws E;\n", + "}\n", + "\n", + "class ProcessRunner\n", + "extends ArrayList> {\n", + " List processAll() throws E {\n", + " List resultCollector = new ArrayList<>();\n", + " for(Processor processor : this)\n", + " processor.process(resultCollector);\n", + " return resultCollector;\n", + " }\n", + "}\n", + "\n", + "class Failure1 extends Exception {}\n", + "\n", + "class Processor1\n", + "implements Processor {\n", + " static int count = 3;\n", + " @Override\n", + " public void process(List resultCollector)\n", + " throws Failure1 {\n", + " if(count-- > 1)\n", + " resultCollector.add(\"Hep!\");\n", + " else\n", + " resultCollector.add(\"Ho!\");\n", + " if(count < 0)\n", + " throw new Failure1();\n", + " }\n", + "}\n", + "\n", + "class Failure2 extends Exception {}\n", + "\n", + "class Processor2\n", + "implements Processor {\n", + " static int count = 2;\n", + " @Override\n", + " public void process(List resultCollector)\n", + " throws Failure2 {\n", + " if(count-- == 0)\n", + " resultCollector.add(47);\n", + " else {\n", + " resultCollector.add(11);\n", + " }\n", + " if(count < 0)\n", + " throw new Failure2();\n", + " }\n", + "}\n", + "\n", + "public class ThrowGenericException {\n", + " public static void main(String[] args) {\n", + " ProcessRunner runner =\n", + " new ProcessRunner<>();\n", + " for(int i = 0; i < 3; i++)\n", + " runner.add(new Processor1());\n", + " try {\n", + " System.out.println(runner.processAll());\n", + " } catch(Failure1 e) {\n", + " System.out.println(e);\n", + " }\n", + "\n", + " ProcessRunner runner2 =\n", + " new ProcessRunner<>();\n", + " for(int i = 0; i < 3; i++)\n", + " runner2.add(new Processor2());\n", + " try {\n", + " System.out.println(runner2.processAll());\n", + " } catch(Failure2 e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "[Hep!, Hep!, Ho!]\n", + "Failure2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Processor** 执行 `process()` 方法,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它会在所持有的每个 **Process** 对象执行,并返回 **resultCollector** 。\n", + "如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。\n", + "\n", + "\n", + "\n", + "## 混型\n", + "\n", + "术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。\n", + "混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而切面经常被建议用来解决混型问题。\n", + "\n", + "### C++ 中的混型\n", + "\n", + "在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。\n", + "下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Mixins.cpp\n", + "\n", + "#include \n", + "#include \n", + "#include \n", + "using namespace std;\n", + "\n", + "template class TimeStamped : public T {\n", + " long timeStamp;\n", + "public:\n", + " TimeStamped() { timeStamp = time(0); }\n", + " long getStamp() { return timeStamp; }\n", + "};\n", + "\n", + "template class SerialNumbered : public T {\n", + " long serialNumber;\n", + " static long counter;\n", + "public:\n", + " SerialNumbered() { serialNumber = counter++; }\n", + " long getSerialNumber() { return serialNumber; }\n", + "};\n", + "\n", + "// Define and initialize the static storage:\n", + "template long SerialNumbered::counter = 1;\n", + "\n", + "class Basic {\n", + " string value;\n", + "public:\n", + " void set(string val) { value = val; }\n", + " string get() { return value; }\n", + "};\n", + "\n", + "int main() {\n", + " TimeStamped> mixin1, mixin2;\n", + " mixin1.set(\"test string 1\");\n", + " mixin2.set(\"test string 2\");\n", + " cout << mixin1.get() << \" \" << mixin1.getStamp() <<\n", + " \" \" << mixin1.getSerialNumber() << endl;\n", + " cout << mixin2.get() << \" \" << mixin2.getStamp() <<\n", + " \" \" << mixin2.getSerialNumber() << endl;\n", + "}\n", + "/* Output:\n", + "test string 1 1452987605 1\n", + "test string 2 1452987605 2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么的轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "TimeStamped> mixin1,mixin2;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "遗憾的是,Java 泛型不允许这样。擦除会忘记基类类型,因此\n", + "\n", + "> 泛型类不能直接继承自一个泛型参数\n", + "\n", + "这突显了许多我在 Java 语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您会发现自己做不到。\n", + "\n", + "### 与接口混合\n", + "\n", + "一种更常见的推荐解决方案是使用接口来产生混型效果,就像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Mixins.java\n", + "\n", + "import java.util.*;\n", + "\n", + "interface TimeStamped { long getStamp(); }\n", + "\n", + "class TimeStampedImp implements TimeStamped {\n", + " private final long timeStamp;\n", + " TimeStampedImp() {\n", + " timeStamp = new Date().getTime();\n", + " }\n", + " @Override\n", + " public long getStamp() { return timeStamp; }\n", + "}\n", + "\n", + "interface SerialNumbered { long getSerialNumber(); }\n", + "\n", + "class SerialNumberedImp implements SerialNumbered {\n", + " private static long counter = 1;\n", + " private final long serialNumber = counter++;\n", + " @Override\n", + " public long getSerialNumber() { return serialNumber; }\n", + "}\n", + "\n", + "interface Basic {\n", + " void set(String val);\n", + " String get();\n", + "}\n", + "\n", + "class BasicImp implements Basic {\n", + " private String value;\n", + " @Override\n", + " public void set(String val) { value = val; }\n", + " @Override\n", + " public String get() { return value; }\n", + "}\n", + "\n", + "class Mixin extends BasicImp\n", + "implements TimeStamped, SerialNumbered {\n", + " private TimeStamped timeStamp = new TimeStampedImp();\n", + " private SerialNumbered serialNumber =\n", + " new SerialNumberedImp();\n", + " @Override\n", + " public long getStamp() {\n", + " return timeStamp.getStamp();\n", + " }\n", + " @Override\n", + " public long getSerialNumber() {\n", + " return serialNumber.getSerialNumber();\n", + " }\n", + "}\n", + "\n", + "public class Mixins {\n", + " public static void main(String[] args) {\n", + " Mixin mixin1 = new Mixin(), mixin2 = new Mixin();\n", + " mixin1.set(\"test string 1\");\n", + " mixin2.set(\"test string 2\");\n", + " System.out.println(mixin1.get() + \" \" +\n", + " mixin1.getStamp() + \" \" + mixin1.getSerialNumber());\n", + " System.out.println(mixin2.get() + \" \" +\n", + " mixin2.getStamp() + \" \" + mixin2.getSerialNumber());\n", + " }\n", + "}\n", + "/* Output:\n", + "test string 1 1494331663026 1\n", + "test string 2 1494331663027 2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。\n", + "\n", + "### 使用装饰器模式\n", + "\n", + "当你观察混型的使用方式时,就会发现混型概念好像与*装饰器*设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。\n", + "装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。\n", + "装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。\n", + "前面的示例可以被改写为使用装饰器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/decorator/Decoration.java\n", + "\n", + "// {java generics.decorator.Decoration}\n", + "package generics.decorator;\n", + "import java.util.*;\n", + "\n", + "class Basic {\n", + " private String value;\n", + " public void set(String val) { value = val; }\n", + " public String get() { return value; }\n", + "}\n", + "\n", + "class Decorator extends Basic {\n", + " protected Basic basic;\n", + " Decorator(Basic basic) { this.basic = basic; }\n", + " @Override\n", + " public void set(String val) { basic.set(val); }\n", + " @Override\n", + " public String get() { return basic.get(); }\n", + "}\n", + "\n", + "class TimeStamped extends Decorator {\n", + " private final long timeStamp;\n", + " TimeStamped(Basic basic) {\n", + " super(basic);\n", + " timeStamp = new Date().getTime();\n", + " }\n", + " public long getStamp() { return timeStamp; }\n", + "}\n", + "\n", + "class SerialNumbered extends Decorator {\n", + " private static long counter = 1;\n", + " private final long serialNumber = counter++;\n", + " SerialNumbered(Basic basic) { super(basic); }\n", + " public long getSerialNumber() { return serialNumber; }\n", + "}\n", + "\n", + "public class Decoration {\n", + " public static void main(String[] args) {\n", + " TimeStamped t = new TimeStamped(new Basic());\n", + " TimeStamped t2 = new TimeStamped(\n", + " new SerialNumbered(new Basic()));\n", + " //- t2.getSerialNumber(); // Not available\n", + " SerialNumbered s = new SerialNumbered(new Basic());\n", + " SerialNumbered s2 = new SerialNumbered(\n", + " new TimeStamped(new Basic()));\n", + " //- s2.getStamp(); // Not available\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "产生自泛型的类包含所有感兴趣的方法,但是由使用装饰器所产生的对象类型是最后被装饰的类型。也就是说,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,而混型的类型是所有被混合到一起的类型。因此对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。\n", + "\n", + "### 与动态代理混合\n", + "\n", + "可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看 [类型信息](book/19-Type-Information.md) 一章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。\n", + "由于动态代理的限制,每个被混入的类都必须是某个接口的实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/DynamicProxyMixin.java\n", + "\n", + "import java.lang.reflect.*;\n", + "import java.util.*;\n", + "import onjava.*;\n", + "import static onjava.Tuple.*;\n", + "\n", + "class MixinProxy implements InvocationHandler {\n", + " Map delegatesByMethod;\n", + " @SuppressWarnings(\"unchecked\")\n", + " MixinProxy(Tuple2>... pairs) {\n", + " delegatesByMethod = new HashMap<>();\n", + " for(Tuple2> pair : pairs) {\n", + " for(Method method : pair.a2.getMethods()) {\n", + " String methodName = method.getName();\n", + " // The first interface in the map\n", + " // implements the method.\n", + " if(!delegatesByMethod.containsKey(methodName))\n", + " delegatesByMethod.put(methodName, pair.a1);\n", + " }\n", + " }\n", + " }\n", + " @Override\n", + " public Object invoke(Object proxy, Method method,\n", + " Object[] args) throws Throwable {\n", + " String methodName = method.getName();\n", + " Object delegate = delegatesByMethod.get(methodName);\n", + " return method.invoke(delegate, args);\n", + " }\n", + " \n", + " @SuppressWarnings(\"unchecked\")\n", + " public static Object newInstance(Tuple2... pairs) {\n", + " Class[] interfaces = new Class[pairs.length];\n", + " for(int i = 0; i < pairs.length; i++) {\n", + " interfaces[i] = (Class)pairs[i].a2;\n", + " }\n", + " ClassLoader cl = pairs[0].a1.getClass().getClassLoader();\n", + " return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));\n", + " }\n", + "}\n", + "\n", + "public class DynamicProxyMixin {\n", + " public static void main(String[] args) {\n", + " Object mixin = MixinProxy.newInstance(\n", + " tuple(new BasicImp(), Basic.class),\n", + " tuple(new TimeStampedImp(), TimeStamped.class),\n", + " tuple(new SerialNumberedImp(), SerialNumbered.class));\n", + " Basic b = (Basic)mixin;\n", + " TimeStamped t = (TimeStamped)mixin;\n", + " SerialNumbered s = (SerialNumbered)mixin;\n", + " b.set(\"Hello\");\n", + " System.out.println(b.get());\n", + " System.out.println(t.getStamp());\n", + " System.out.println(s.getSerialNumber());\n", + " }\n", + "}\n", + "/* Output:\n", + "Hello\n", + "1494331653339\n", + "1\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。\n", + "为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。\n", + "\n", + "\n", + "\n", + "## 潜在类型机制\n", + "\n", + "在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。\n", + "\n", + "Java 泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动装箱机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。\n", + "\n", + "还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。\n", + "\n", + "某些编程语言提供的一种解决方案称为*潜在类型机制*或*结构化类型机制*,而更古怪的术语称为*鸭子类型机制*,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。\n", + "\n", + "泛型代码典型地只能在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。\n", + "\n", + "潜在类型机制是一种代码组织和复用机制。有了它,编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。\n", + "\n", + "支持潜在类型机制的语言包括 Python(可以从 www.Python.org 免费下载)、C++、Ruby、SmallTalk 和 Go。Python 是动态类型语言(几乎所有的类型检查都发生在运行时),而 C++ 和 Go 是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。\n", + "\n", + "### pyhton 中的潜在类型\n", + "\n", + "如果我们将上面的描述用 Python 来表示,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# generics/DogsAndRobots.py\n", + "\n", + "class Dog:\n", + " def speak(self):\n", + " print(\"Arf!\")\n", + " def sit(self):\n", + " print(\"Sitting\")\n", + " def reproduce(self):\n", + " pass\n", + "\n", + "class Robot:\n", + " def speak(self):\n", + " print(\"Click!\")\n", + " def sit(self):\n", + " print(\"Clank!\")\n", + " def oilChange(self):\n", + " pass\n", + "\n", + "def perform(anything):\n", + " anything.speak()\n", + " anything.sit()\n", + "\n", + "a = Dog()\n", + "b = Robot()\n", + "perform(a)\n", + "perform(b)\n", + "\n", + "output = \"\"\"\n", + "Arf!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许普通(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)` 中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()` 方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。\n", + "\n", + "输出规定使用三重引号创建带有内嵌换行符的字符串。\n", + "\n", + "### C++ 中的潜在类型\n", + "\n", + "我们可以用 C++ 产生相同的效果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/DogsAndRobots.cpp\n", + "\n", + "#include \n", + "using namespace std;\n", + "\n", + "class Dog {\n", + "public:\n", + " void speak() { cout << \"Arf!\" << endl; }\n", + " void sit() { cout << \"Sitting\" << endl; }\n", + " void reproduce() {}\n", + "};\n", + "\n", + "class Robot {\n", + "public:\n", + " void speak() { cout << \"Click!\" << endl; }\n", + " void sit() { cout << \"Clank!\" << endl; }\n", + " void oilChange() {}\n", + "};\n", + "\n", + "template void perform(T anything) {\n", + " anything.speak();\n", + " anything.sit();\n", + "}\n", + "\n", + "int main() {\n", + " Dog d;\n", + " Robot r;\n", + " perform(d);\n", + " perform(r);\n", + "}\n", + "/* Output:\n", + "Arf!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。\n", + "C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。\n", + "\n", + "### Go 中的潜在类型\n", + "\n", + "这里用 Go 语言编写相同的程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "go" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/dogsandrobots.go\n", + "\n", + "package main\n", + "import \"fmt\"\n", + "\n", + "type Dog struct {}\n", + "func (this Dog) speak() { fmt.Printf(\"Arf!\\n\")}\n", + "func (this Dog) sit() { fmt.Printf(\"Sitting\\n\")}\n", + "func (this Dog) reproduce() {}\n", + "\n", + "type Robot struct {}\n", + "func (this Robot) speak() { fmt.Printf(\"Click!\\n\") }\n", + "func (this Robot) sit() { fmt.Printf(\"Clank!\\n\") }\n", + "func (this Robot) oilChange() {}\n", + "\n", + "func perform(speaker interface { speak(); sit() }) {\n", + " speaker.speak();\n", + " speaker.sit();\n", + "}\n", + "\n", + "func main() {\n", + " perform(Dog{})\n", + " perform(Robot{})\n", + "}\n", + "/* Output:\n", + "Arf!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。\n", + "\n", + "Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要原因。 但是,Go 的组成很简单。\n", + "\n", + "`perform()` 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 `speak()` 和 `sit()` 方法即可。 该接口在此处匿名定义,内联,如 `perform()` 的参数列表所示。\n", + "\n", + "`main()` 证明 `perform()` 确实对其参数的确切类型不在乎,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。\n", + "\n", + "语法 **Dog {}** 和 **Robot {}** 创建匿名的 **Dog** 和 **Robot** 结构。\n", + "\n", + "### java中的直接潜在类型\n", + "\n", + "因为泛型是在这场竞赛的后期才添加到 Java 中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用擦除来实现 Java 泛型的实现有时称为第二类泛型类型)例如,在 Java 8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Performs.java\n", + "\n", + "public interface Performs {\n", + " void speak();\n", + " void sit();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/DogsAndRobots.java\n", + "// No (direct) latent typing in Java\n", + "import typeinfo.pets.*;\n", + "\n", + "class PerformingDog extends Dog implements Performs {\n", + " @Override\n", + " public void speak() { System.out.println(\"Woof!\"); }\n", + " @Override\n", + " public void sit() { System.out.println(\"Sitting\"); }\n", + " public void reproduce() {}\n", + "}\n", + "\n", + "class Robot implements Performs {\n", + " public void speak() { System.out.println(\"Click!\"); }\n", + " public void sit() { System.out.println(\"Clank!\"); }\n", + " public void oilChange() {}\n", + "}\n", + "\n", + "class Communicate {\n", + " public static \n", + " void perform(T performer) {\n", + " performer.speak();\n", + " performer.sit();\n", + " }\n", + "}\n", + "\n", + "public class DogsAndRobots {\n", + " public static void main(String[] args) {\n", + " Communicate.perform(new PerformingDog());\n", + " Communicate.perform(new Robot());\n", + " }\n", + "}\n", + "/* Output:\n", + "Woof!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是要注意,`perform()` 不需要使用泛型来工作,它可以被简单地指定为接受一个 **Performs** 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SimpleDogsAndRobots.java\n", + "// Removing the generic; code still works\n", + "\n", + "class CommunicateSimply {\n", + " static void perform(Performs performer) {\n", + " performer.speak();\n", + " performer.sit();\n", + " }\n", + "}\n", + "\n", + "public class SimpleDogsAndRobots {\n", + " public static void main(String[] args) {\n", + " CommunicateSimply.perform(new PerformingDog());\n", + " CommunicateSimply.perform(new Robot());\n", + " }\n", + "}\n", + "/* Output:\n", + "Woof!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在本例中,泛型不是必需的,因为这些类已经被强制要求实现 **Performs** 接口。\n", + "\n", + "\n", + "\n", + "## 对缺乏潜在类型机制的补偿\n", + "\n", + "尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。\n", + "\n", + "### 反射\n", + "\n", + "可以使用的一种方式是反射,下面的 `perform()` 方法就是用了潜在类型机制:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/LatentReflection.java\n", + "// Using reflection for latent typing\n", + "import java.lang.reflect.*;\n", + "\n", + "// Does not implement Performs:\n", + "class Mime {\n", + " public void walkAgainstTheWind() {}\n", + " public void sit() {\n", + " System.out.println(\"Pretending to sit\");\n", + " }\n", + " public void pushInvisibleWalls() {}\n", + " @Override\n", + " public String toString() { return \"Mime\"; }\n", + "}\n", + "\n", + "// Does not implement Performs:\n", + "class SmartDog {\n", + " public void speak() { System.out.println(\"Woof!\"); }\n", + " public void sit() { System.out.println(\"Sitting\"); }\n", + " public void reproduce() {}\n", + "}\n", + "\n", + "class CommunicateReflectively {\n", + " public static void perform(Object speaker) {\n", + " Class spkr = speaker.getClass();\n", + " try {\n", + " try {\n", + " Method speak = spkr.getMethod(\"speak\");\n", + " speak.invoke(speaker);\n", + " } catch(NoSuchMethodException e) {\n", + " System.out.println(speaker + \" cannot speak\");\n", + " }\n", + " try {\n", + " Method sit = spkr.getMethod(\"sit\");\n", + " sit.invoke(speaker);\n", + " } catch(NoSuchMethodException e) {\n", + " System.out.println(speaker + \" cannot sit\");\n", + " }\n", + " } catch(SecurityException |\n", + " IllegalAccessException |\n", + " IllegalArgumentException |\n", + " InvocationTargetException e) {\n", + " throw new RuntimeException(speaker.toString(), e);\n", + " }\n", + " }\n", + "}\n", + "\n", + "public class LatentReflection {\n", + " public static void main(String[] args) {\n", + " CommunicateReflectively.perform(new SmartDog());\n", + " CommunicateReflectively.perform(new Robot());\n", + " CommunicateReflectively.perform(new Mime());\n", + " }\n", + "}\n", + "/* Output:\n", + "Woof!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "Mime cannot speak\n", + "Pretending to sit\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上例中,这些类完全是彼此分离的,没有任何公共基类(除了 **Object** )或接口。通过反射, `CommunicateReflectively.perform()` 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 **Mime** 只具有一个必需的方法这一事实,并能够部分实现其目标。\n", + "\n", + "### 将一个方法应用于序列\n", + "\n", + "反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗?\n", + "\n", + "让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢?\n", + "\n", + "最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Apply.java\n", + "\n", + "import java.lang.reflect.*;\n", + "import java.util.*;\n", + "\n", + "public class Apply {\n", + " public static >\n", + " void apply(S seq, Method f, Object... args) {\n", + " try {\n", + " for(T t: seq)\n", + " f.invoke(t, args);\n", + " } catch(IllegalAccessException |\n", + " IllegalArgumentException |\n", + " InvocationTargetException e) {\n", + " // Failures are programmer errors\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **Apply.java** 中,异常被转换为 **RuntimeException** ,因为没有多少办法可以从这种异常中恢复——在这种情况下,它们实际上代表着程序员的错误。\n", + "\n", + "为什么我们不只使用 Java 8 方法参考(稍后显示)而不是反射方法 **f** ? 注意,`invoke()` 和 `apply()` 的优点是它们可以接受任意数量的参数。 在某些情况下,灵活性可能至关重要。\n", + "\n", + "为了测试 **Apply** ,我们首先创建一个 **Shape** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Shape.java\n", + "\n", + "public class Shape {\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName() + \" \" + id;\n", + " }\n", + " public void rotate() {\n", + " System.out.println(this + \" rotate\");\n", + " }\n", + " public void resize(int newSize) {\n", + " System.out.println(this + \" resize \" + newSize);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "被一个子类 **Square** 继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/Square.java\n", + "\n", + "public class Square extends Shape {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过这些,我们可以测试 **Apply**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ApplyTest.java\n", + "\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import onjava.*;\n", + "\n", + "public class ApplyTest {\n", + " public static\n", + " void main(String[] args) throws Exception {\n", + " List shapes =\n", + " Suppliers.create(ArrayList::new, Shape::new, 3);\n", + " Apply.apply(shapes, Shape.class.getMethod(\"rotate\"));\n", + " Apply.apply(shapes, Shape.class.getMethod(\"resize\", int.class), 7);\n", + "\n", + " List squares =\n", + " Suppliers.create(ArrayList::new, Square::new, 3);\n", + " Apply.apply(squares, Shape.class.getMethod(\"rotate\"));\n", + " Apply.apply(squares, Shape.class.getMethod(\"resize\", int.class), 7);\n", + "\n", + " Apply.apply(new FilledList<>(Shape::new, 3),\n", + " Shape.class.getMethod(\"rotate\"));\n", + " Apply.apply(new FilledList<>(Square::new, 3),\n", + " Shape.class.getMethod(\"rotate\"));\n", + "\n", + " SimpleQueue shapeQ = Suppliers.fill(\n", + " new SimpleQueue<>(), SimpleQueue::add,\n", + " Shape::new, 3);\n", + " Suppliers.fill(shapeQ, SimpleQueue::add,\n", + " Square::new, 3);\n", + " Apply.apply(shapeQ, Shape.class.getMethod(\"rotate\"));\n", + " }\n", + "}\n", + "/* Output:\n", + "Shape 0 rotate\n", + "Shape 1 rotate\n", + "Shape 2 rotate\n", + "Shape 0 resize 7\n", + "Shape 1 resize 7\n", + "Shape 2 resize 7\n", + "Square 3 rotate\n", + "Square 4 rotate\n", + "Square 5 rotate\n", + "Square 3 resize 7\n", + "Square 4 resize 7\n", + "Square 5 resize 7\n", + "Shape 6 rotate\n", + "Shape 7 rotate\n", + "Shape 8 rotate\n", + "Square 9 rotate\n", + "Square 10 rotate\n", + "Square 11 rotate\n", + "Shape 12 rotate\n", + "Shape 13 rotate\n", + "Shape 14 rotate\n", + "Square 15 rotate\n", + "Square 16 rotate\n", + "Square 17 rotate\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的——例如,在 `main()` 中使用下面定义的 **SimpleQueue** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/SimpleQueue.java\n", + "\n", + "// A different kind of Iterable collection\n", + "import java.util.*;\n", + "\n", + "public class SimpleQueue implements Iterable {\n", + " private LinkedList storage = new LinkedList<>();\n", + " public void add(T t) { storage.offer(t); }\n", + " public T get() { return storage.poll(); }\n", + " @Override\n", + " public Iterator iterator() {\n", + " return storage.iterator();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。\n", + "\n", + "几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/ApplyFunctional.java\n", + "\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "import onjava.*;\n", + "\n", + "public class ApplyFunctional {\n", + " public static void main(String[] args) {\n", + " Stream.of(\n", + " Stream.generate(Shape::new).limit(2),\n", + " Stream.generate(Square::new).limit(2))\n", + " .flatMap(c -> c) // flatten into one stream\n", + " .peek(Shape::rotate)\n", + " .forEach(s -> s.resize(7));\n", + "\n", + " new FilledList<>(Shape::new, 2)\n", + " .forEach(Shape::rotate);\n", + " new FilledList<>(Square::new, 2)\n", + " .forEach(Shape::rotate);\n", + "\n", + " SimpleQueue shapeQ = Suppliers.fill(\n", + " new SimpleQueue<>(), SimpleQueue::add,\n", + " Shape::new, 2);\n", + " Suppliers.fill(shapeQ, SimpleQueue::add,\n", + " Square::new, 2);\n", + " shapeQ.forEach(Shape::rotate);\n", + " }\n", + "}\n", + "/* Output:\n", + "Shape 0 rotate\n", + "Shape 0 resize 7\n", + "Shape 1 rotate\n", + "Shape 1 resize 7\n", + "Square 2 rotate\n", + "Square 2 resize 7\n", + "Square 3 rotate\n", + "Square 3 resize 7\n", + "Shape 4 rotate\n", + "Shape 5 rotate\n", + "Square 6 rotate\n", + "Square 7 rotate\n", + "Shape 8 rotate\n", + "Shape 9 rotate\n", + "Square 10 rotate\n", + "Square 11 rotate\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于使用 Java 8,因此不需要 `Apply.apply()` 。\n", + "\n", + "我们首先生成两个 **Stream** : 一个是 **Shape** ,一个是 **Square** ,并将它们展平为单个流。 尽管 Java 缺少功能语言中经常出现的 `flatten()` ,但是我们可以使用 `flatMap(c-> c)` 产生相同的结果,后者使用身份映射将操作简化为“ **flatten** ”。\n", + "\n", + "我们使用 `peek()` 当做对 `rotate()` 的调用,因为 `peek()` 执行一个操作(此处是出于副作用),并在未更改的情况下传递对象。\n", + "\n", + "注意,使用 **FilledList** 和 **shapeQ** 调用 `forEach()` 比 `Apply.apply()` 代码整洁得多。 在代码简单性和可读性方面,结果比以前的方法好得多。 并且,现在也不可能从 `main()` 引发异常。\n", + "\n", + "\n", + "\n", + "## Java8 中的辅助潜在类型\n", + "\n", + "先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为 Java 最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。\n", + "\n", + "我在其他地方从没遇过这种技术,因此我将其称为辅助潜在类型。\n", + "\n", + "我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/DogsAndRobotMethodReferences.java\n", + "\n", + "// \"Assisted Latent Typing\"\n", + "import typeinfo.pets.*;\n", + "import java.util.function.*;\n", + "\n", + "class PerformingDogA extends Dog {\n", + " public void speak() { System.out.println(\"Woof!\"); }\n", + " public void sit() { System.out.println(\"Sitting\"); }\n", + " public void reproduce() {}\n", + "}\n", + "\n", + "class RobotA {\n", + " public void speak() { System.out.println(\"Click!\"); }\n", + " public void sit() { System.out.println(\"Clank!\"); }\n", + " public void oilChange() {}\n", + "}\n", + "\n", + "class CommunicateA {\n", + " public static

void perform(P performer,\n", + " Consumer

action1, Consumer

action2) {\n", + " action1.accept(performer);\n", + " action2.accept(performer);\n", + " }\n", + "}\n", + "\n", + "public class DogsAndRobotMethodReferences {\n", + " public static void main(String[] args) {\n", + " CommunicateA.perform(new PerformingDogA(),\n", + " PerformingDogA::speak, PerformingDogA::sit);\n", + " CommunicateA.perform(new RobotA(),\n", + " RobotA::speak, RobotA::sit);\n", + " CommunicateA.perform(new Mime(),\n", + " Mime::walkAgainstTheWind,\n", + " Mime::pushInvisibleWalls);\n", + " }\n", + "}\n", + "/* Output:\n", + "Woof!\n", + "Sitting\n", + "Click!\n", + "Clank!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**PerformingDogA** 和 **RobotA** 与 **DogsAndRobots.java** 中的相同,不同之处在于它们不继承通用接口 **Performs** ,因此它们没有通用性。\n", + "\n", + "`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。当您调用 **Consumer** 的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于 [函数式编程](book/13-Functional-Programming.md) 一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。\n", + "\n", + "之所以称其为“辅助”,是因为您必须显式地为 `perform()` 提供要使用的方法引用。 它不能只按名称调用方法。\n", + "\n", + "尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 `CommunicateA.perform()` ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。\n", + "\n", + "为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime**。\n", + "\n", + "### 使用**Suppliers**类的通用方法\n", + "\n", + "通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的工具方法。 泛化这些操作很有意义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Suppliers.java\n", + "\n", + "// A utility to use with Suppliers\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class Suppliers {\n", + " // Create a collection and fill it:\n", + " public static > C\n", + " create(Supplier factory, Supplier gen, int n) {\n", + " return Stream.generate(gen)\n", + " .limit(n)\n", + " .collect(factory, C::add, C::addAll);\n", + " }\n", + " \n", + " // Fill an existing collection:\n", + " public static >\n", + " C fill(C coll, Supplier gen, int n) {\n", + " Stream.generate(gen)\n", + " .limit(n)\n", + " .forEach(coll::add);\n", + " return coll;\n", + " }\n", + " \n", + " // Use an unbound method reference to\n", + " // produce a more general method:\n", + " public static H fill(H holder,\n", + " BiConsumer adder, Supplier gen, int n) {\n", + " Stream.generate(gen)\n", + " .limit(n)\n", + " .forEach(a -> adder.accept(holder, a));\n", + " return holder;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。\n", + "\n", + "前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。\n", + "\n", + "在一个稍作模拟的测试中对 **Suppliers** 工具程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// generics/BankTeller.java\n", + "\n", + "// A very simple bank teller simulation\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "class Customer {\n", + " private static long counter = 1;\n", + " private final long id = counter++;\n", + " @Override\n", + " public String toString() {\n", + " return \"Customer \" + id;\n", + " }\n", + "}\n", + "\n", + "class Teller {\n", + " private static long counter = 1;\n", + " private final long id = counter++;\n", + " @Override\n", + " public String toString() {\n", + " return \"Teller \" + id;\n", + " }\n", + "}\n", + "\n", + "class Bank {\n", + " private List tellers =\n", + " new ArrayList<>();\n", + " public void put(BankTeller bt) {\n", + " tellers.add(bt);\n", + " }\n", + "}\n", + "\n", + "public class BankTeller {\n", + " public static void serve(Teller t, Customer c) {\n", + " System.out.println(t + \" serves \" + c);\n", + " }\n", + " public static void main(String[] args) {\n", + " // Demonstrate create():\n", + " RandomList tellers =\n", + " Suppliers.create(\n", + " RandomList::new, Teller::new, 4);\n", + " // Demonstrate fill():\n", + " List customers = Suppliers.fill(\n", + " new ArrayList<>(), Customer::new, 12);\n", + " customers.forEach(c ->\n", + " serve(tellers.select(), c));\n", + " // Demonstrate assisted latent typing:\n", + " Bank bank = Suppliers.fill(\n", + " new Bank(), Bank::put, BankTeller::new, 3);\n", + " // Can also use second version of fill():\n", + " List customers2 = Suppliers.fill(\n", + " new ArrayList<>(),\n", + " List::add, Customer::new, 12);\n", + " }\n", + "}\n", + "/* Output:\n", + "Teller 3 serves Customer 1\n", + "Teller 2 serves Customer 2\n", + "Teller 3 serves Customer 3\n", + "Teller 1 serves Customer 4\n", + "Teller 1 serves Customer 5\n", + "Teller 3 serves Customer 6\n", + "Teller 1 serves Customer 7\n", + "Teller 2 serves Customer 8\n", + "Teller 3 serves Customer 9\n", + "Teller 3 serves Customer 10\n", + "Teller 2 serves Customer 11\n", + "Teller 4 serves Customer 12\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与无关的新类型 **Bank** 一起使用,还能与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。\n", + "\n", + "\n", + "\n", + "## 总结:类型转换真的如此之糟吗?\n", + "\n", + "自从 C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。\n", + "\n", + "这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用集合类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在 [集合](book/12-Collections.md) 和 [附录:集合主题](book/Appendix-Collection-Topics.md) 这两章所见。在 Java 5 之前,当你将一个对象放置到集合中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从集合中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在 [集合](book/12-Collections.md) 章节的开头展示过)。如果没有 Java 5 泛型版本的集合,你放到容集里和从集合中取回的都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。\n", + "\n", + "但是,泛型出现之前的 Java 并不会让你误用放入到集合中的对象。如果将一个 **Dog** 扔到 **Cat** 的集合中,并且试图将这个集合中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 集合中取回那个 **Dog** 引用,并试图将其转型为 **Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。\n", + "\n", + "在本书以前的版本中,我曾经说过:\n", + "\n", + "> 这不止令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向集合中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了集合中,那么必须发现这个不良插入到底是在何处发生的。\n", + ">\n", + "\n", + "但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个称为 **files** 的 list 示例,它包含 **String** 对象。在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的集合,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。\n", + "\n", + "有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在 [多线程编程](book/24-Concurrent-Programming.md) 章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗?\n", + "我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的集合。类型安全的集合是能够创建更通用代码这一能力所带来的副作用。\n", + "因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解这些代码的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。\n", + "\n", + "还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 **Map** ,在特定的方法 `containsKey(Object key) `和 `get(Object key)` 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 **Object** ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 **map** 中,键的类型总是在编译期检查的。\n", + "\n", + "有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。\n", + "某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语言将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。\n", + "\n", + "## 进阶阅读\n", + "\n", + "泛型的入门文档是 《Generics in the Java Programming Language》,作者是 Gilad Bracha,可以从 http://java.oracle.com 获取。\n", + "\n", + "Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html 获取。\n", + "\n", + "你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。\n", + "\n", + "Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到:http://www.infoq.com/articles/neal-gafter-on-java。\n", + "\n", + "[^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。\n", + "[^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html)\n", + "[^3]: 参见本章章末引文。\n", + "[^4]: 注意,一些编程环境,如 Eclipse 和 IntelliJ IDEA,将会自动生成委托代码。\n", + "[^5]: 因为可以使用转型,有效地禁止了类型系统,一些人就认为 C++ 是弱类型,但这太极端了。一种可能更好的说法是 C++ 是有一道暗门的强类型语言。\n", + "[^6]: 我再次从 Brian Goetz 那获得帮助。\n", + "\n", + "\n", + "\n", + "

" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/21-Arrays.ipynb b/jupyter/21-Arrays.ipynb new file mode 100644 index 00000000..1ac36a3a --- /dev/null +++ b/jupyter/21-Arrays.ipynb @@ -0,0 +1,3498 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第二十一章 数组\n", + "\n", + "\n", + "> 在 [初始化和清理](book/06-Housekeeping.md) 一章的最后,你已经学过如何定义和初始化一个数组。\n", + "\n", + "简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 **集合** (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。\n", + "\n", + "**注意:** 随着 Java Collection 和 Stream 类中高级功能的不断增加,日常编程中使用数组的需求也在变少,所以你暂且可以放心地略读甚至跳过这一章。但是,即使你自己避免使用数组,也总会有需要阅读别人数组代码的那一天。那时候,本章依然在这里等着你来翻阅。\n", + "\n", + "\n", + "\n", + "## 数组特性\n", + "\n", + "明明还有很多其他的办法来保存对象,那么是什么令数组如此特别?\n", + "\n", + "将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。\n", + "\n", + "速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个 **ArrayList** 的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。\n", + "\n", + "\n", + "数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 **RuntimeException** 的异常提醒,这表明你的程序中存在错误。\n", + "\n", + "\n", + "在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 **Object**,也就是 Java 中所有类的基类。而数组是优于 **预泛型** (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。\n", + "\n", + "\n", + "当然,不管在编译时还是运行时,Java都会阻止你犯向对象发送不正确消息的错误。然而不管怎样,使用数组都不会有更大的风险。比较好的地方在于,如果编译器报错,最终的用户更容易理解抛出异常的含义。\n", + "\n", + "\n", + "一个数组可以保存基本数据类型,而一个预泛型的集合不可以。然而对于泛型而言,集合可以指定和检查他们保存对象的类型,而通过 **自动装箱** (autoboxing)机制,集合表现地就像它们可以保存基本数据类型一样,因为这种转换是自动的。\n", + "\n", + "下面给出一例用于比较数组和泛型集合:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/CollectionComparison.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "import java.util.*;\n", + "import onjava.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "class BerylliumSphere {\n", + " private static long counter;\n", + " private final long id = counter++;\n", + " @Override\n", + " public String toString() {\n", + " return \"Sphere \" + id;\n", + " }\n", + "}\n", + "\n", + "public class CollectionComparison {\n", + " public static void main(String[] args) {\n", + " BerylliumSphere[] spheres =\n", + " new BerylliumSphere[10];\n", + " for(int i = 0; i < 5; i++)\n", + " spheres[i] = new BerylliumSphere();\n", + " show(spheres);\n", + " System.out.println(spheres[4]);\n", + "\n", + " List sphereList = Suppliers.create(\n", + " ArrayList::new, BerylliumSphere::new, 5);\n", + " System.out.println(sphereList);\n", + " System.out.println(sphereList.get(4));\n", + "\n", + " int[] integers = { 0, 1, 2, 3, 4, 5 };\n", + " show(integers);\n", + " System.out.println(integers[4]);\n", + "\n", + " List intList = new ArrayList<>(\n", + " Arrays.asList(0, 1, 2, 3, 4, 5));\n", + " intList.add(97);\n", + " System.out.println(intList);\n", + " System.out.println(intList.get(4));\n", + " }\n", + "}\n", + "/* Output:\n", + "[Sphere 0, Sphere 1, Sphere 2, Sphere 3, Sphere 4,\n", + "null, null, null, null, null]\n", + "Sphere 4\n", + "[Sphere 5, Sphere 6, Sphere 7, Sphere 8, Sphere 9]\n", + "Sphere 9\n", + "[0, 1, 2, 3, 4, 5]\n", + "4\n", + "[0, 1, 2, 3, 4, 5, 97]\n", + "4\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Suppliers.create()** 方法在[泛型](book/20-Generics.md)一章中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用 **[ ]** 来随机存取元素,而一个 **List** 使用诸如 `add()` 和 `get()` 等方法。数组和 **ArrayList** 之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](book/12-Collections.md)中看到的,集合的功能明显多于数组。随着 Java 自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。\n", + "\n", + "\n", + "### 用于显示数组的实用程序\n", + "\n", + "在本章中,我们处处都要显示数组。Java 提供了 **Arrays.toString()** 来将数组转换为可读字符串,然后可以在控制台上显示。然而这种方式视觉上噪音太大,所以我们创建一个小的库来完成这项工作。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/ArrayShow.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "package onjava;\n", + "import java.util.*;\n", + "\n", + "public interface ArrayShow {\n", + " static void show(Object[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(boolean[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(byte[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(char[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(short[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(int[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(long[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(float[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " static void show(double[] a) {\n", + " System.out.println(Arrays.toString(a));\n", + " }\n", + " // Start with a description:\n", + " static void show(String info, Object[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, boolean[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, byte[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, char[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, short[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, int[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, long[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, float[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + " static void show(String info, double[] a) {\n", + " System.out.print(info + \": \");\n", + " show(a);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一个方法适用于对象数组,包括那些包装基本数据类型的数组。所有的方法重载对于不同的数据类型是必要的。\n", + "\n", + "第二组重载方法可以让你显示带有信息 **字符串** 前缀的数组。\n", + "\n", + "为了简单起见,你通常可以静态地导入它们。\n", + "\n", + "\n", + "\n", + "## 一等对象\n", + "\n", + "不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 **new** 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 **length** 成员函数,它能告诉你数组对象中可以存储多少元素。**[ ]** 语法是你访问数组对象的唯一方式。\n", + "\n", + "下面的例子总结了初始化数组的多种方式,并且展示了如何给不同的数组对象分配数组引用。同时也可以看出对象数组和基元数组在使用上是完全相同的。唯一的不同之处就是对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ArrayOptions.java\n", + "// Initialization & re-assignment of arrays\n", + "import java.util.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class ArrayOptions {\n", + " public static void main(String[] args) {\n", + " // Arrays of objects:\n", + " BerylliumSphere[] a; // Uninitialized local\n", + " BerylliumSphere[] b = new BerylliumSphere[5];\n", + "\n", + " // The references inside the array are\n", + " // automatically initialized to null:\n", + " show(\"b\", b);\n", + " BerylliumSphere[] c = new BerylliumSphere[4];\n", + " for(int i = 0; i < c.length; i++)\n", + " if(c[i] == null) // Can test for null reference\n", + " c[i] = new BerylliumSphere();\n", + "\n", + " // Aggregate initialization:\n", + " BerylliumSphere[] d = {\n", + " new BerylliumSphere(),\n", + " new BerylliumSphere(),\n", + " new BerylliumSphere()\n", + " };\n", + "\n", + " // Dynamic aggregate initialization:\n", + " a = new BerylliumSphere[]{\n", + " new BerylliumSphere(), new BerylliumSphere(),\n", + " };\n", + " // (Trailing comma is optional)\n", + "\n", + " System.out.println(\"a.length = \" + a.length);\n", + " System.out.println(\"b.length = \" + b.length);\n", + " System.out.println(\"c.length = \" + c.length);\n", + " System.out.println(\"d.length = \" + d.length);\n", + " a = d;\n", + " System.out.println(\"a.length = \" + a.length);\n", + "\n", + " // Arrays of primitives:\n", + " int[] e; // Null reference\n", + " int[] f = new int[5];\n", + "\n", + " // The primitives inside the array are\n", + " // automatically initialized to zero:\n", + " show(\"f\", f);\n", + " int[] g = new int[4];\n", + " for(int i = 0; i < g.length; i++)\n", + " g[i] = i*i;\n", + " int[] h = { 11, 47, 93 };\n", + "\n", + " // Compile error: variable e not initialized:\n", + " //- System.out.println(\"e.length = \" + e.length);\n", + " System.out.println(\"f.length = \" + f.length);\n", + " System.out.println(\"g.length = \" + g.length);\n", + " System.out.println(\"h.length = \" + h.length);\n", + " e = h;\n", + " System.out.println(\"e.length = \" + e.length);\n", + " e = new int[]{ 1, 2 };\n", + " System.out.println(\"e.length = \" + e.length);\n", + " }\n", + "}\n", + "/* Output:\n", + "b: [null, null, null, null, null]\n", + "a.length = 2\n", + "b.length = 5\n", + "c.length = 4\n", + "d.length = 3\n", + "a.length = 3\n", + "f: [0, 0, 0, 0, 0]\n", + "f.length = 5\n", + "g.length = 4\n", + "h.length = 3\n", + "e.length = 3\n", + "e.length = 2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "数组 **a** 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。数组 **b** 被初始化成一系列指向 **BerylliumSphere** 对象的引用,但是并没有真正的 **BerylliumSphere** 对象被存储在数组中。尽管你仍然可以获得这个数组的大小,因为 **b** 指向合法对象。这带来了一个小问题:你无法找出到底有多少元素存储在数组中,因为 **length** 只能告诉你数组可以存储多少元素;这就是说,数组对象的大小并不是真正存储在数组中对象的个数。然而,当你创建一个数组对象,其引用将自动初始化为 **null**,因此你可以通过检查特定数组元素中的引用是否为 **null** 来判断其中是否有对象。基元数组也有类似的机制,比如自动将数值类型初始化为 **0**,char 型初始化为 **(char)0**,布尔类型初始化为 **false**。\n", + "\n", + "数组 **c** 展示了创建数组对象后给数组中各元素分配 **BerylliumSphere** 对象。数组 **d** 展示了创建数组对象的聚合初始化语法(隐式地使用 **new** 在堆中创建对象,就像 **c** 一样)并且初始化成 **BeryliumSphere** 对象,这一切都在一条语句中完成。\n", + "\n", + "下一个数组初始化可以被看做是一个“动态聚合初始化”。 **d** 使用的聚合初始化必须在 **d** 定义处使用,但是使用第二种语法,你可以在任何地方创建和初始化数组对象。例如,假设 **hide()** 是一个需要使用一系列的 **BeryliumSphere**对象。你可以这样调用它:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "hide(d);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你也可以动态地创建你用作参数传递的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "hide(new BerylliumSphere[]{\n", + " new BerlliumSphere(),\n", + " new BerlliumSphere()\n", + "});" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "很多情况下这种语法写代码更加方便。\n", + "\n", + "表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "a = d;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显示了你如何获取指向一个数组对象的引用并将其分配给另一个数组对象。就像你可以处理其他类型的对象引用。现在 **a** 和 **d** 都指向了堆中的同一个数组对象。\n", + "\n", + "**ArrayOptions.java** 的第二部分展示了基元数组的语法就像对象数组一样,除了基元数组直接保存基本数据类型的值。\n", + "\n", + "\n", + "\n", + "## 返回数组\n", + "\n", + "假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对 C++/C 这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。\n", + "\n", + "而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。\n", + "\n", + "下面,我们返回一个 **字符串** 数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/IceCreamFlavors.java\n", + "// Returning arrays from methods\n", + "import java.util.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class IceCreamFlavors {\n", + " private static SplittableRandom rand =\n", + " new SplittableRandom(47);\n", + " static final String[] FLAVORS = {\n", + " \"Chocolate\", \"Strawberry\", \"Vanilla Fudge Swirl\",\n", + " \"Mint Chip\", \"Mocha Almond Fudge\", \"Rum Raisin\",\n", + " \"Praline Cream\", \"Mud Pie\"\n", + " };\n", + " public static String[] flavorSet(int n) {\n", + " if(n > FLAVORS.length)\n", + " throw new IllegalArgumentException(\"Set too big\");\n", + " String[] results = new String[n];\n", + " boolean[] picked = new boolean[FLAVORS.length];\n", + " for(int i = 0; i < n; i++) {\n", + " int t;\n", + " do\n", + " t = rand.nextInt(FLAVORS.length);\n", + " while(picked[t]);\n", + " results[i] = FLAVORS[t];\n", + " picked[t] = true;\n", + " }\n", + " return results;\n", + " }\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 7; i++)\n", + " show(flavorSet(3));\n", + " }\n", + "}\n", + "/* Output:\n", + "[Praline Cream, Mint Chip, Vanilla Fudge Swirl]\n", + "[Strawberry, Vanilla Fudge Swirl, Mud Pie]\n", + "[Chocolate, Strawberry, Vanilla Fudge Swirl]\n", + "[Rum Raisin, Praline Cream, Chocolate]\n", + "[Mint Chip, Rum Raisin, Mocha Almond Fudge]\n", + "[Mocha Almond Fudge, Mud Pie, Vanilla Fudge Swirl]\n", + "[Mocha Almond Fudge, Mud Pie, Mint Chip]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**flavorset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后从数组 **FLAVORS** 中随机选择 flavors 并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者是在其他什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。\n", + "\n", + "如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/20-Generics.md) 中介绍的 **元组** 。\n", + "\n", + "注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项没被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串\n", + "\n", + "比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flavorSet()** 每一次都是按照随机顺序选择 flavors。\n", + "\n", + "一直到现在,随机数都是通过 **java.util.Random** 类生成的,这个类从 Java 1.0 就有,甚至更新过以提供 Java 8 流。现在我们可以介绍 Java 8 中的 **SplittableRandom** ,它不仅能在并行操作使用(你最终会学到),而且提供了一个高质量的随机数。这本书的剩余部分都使用 **SplittableRandom** 。\n", + "\n", + "\n", + "\n", + "## 多维数组\n", + "\n", + "要创建多维的基元数组,你要用大括号来界定数组中的向量:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/MultidimensionalPrimitiveArray.java\n", + "import java.util.*;\n", + "\n", + "public class MultidimensionalPrimitiveArray {\n", + " public static void main(String[] args) {\n", + " int[][] a = {\n", + " { 1, 2, 3, },\n", + " { 4, 5, 6, },\n", + " };\n", + " System.out.println(Arrays.deepToString(a));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[1, 2, 3], [4, 5, 6]]\n", + "*/。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个嵌套的大括号都代表了数组的一个维度。\n", + "\n", + "这个例子使用 **Arrays.deepToString()** 方法,将多维数组转换成 **String** 类型,就像输出中显示的那样。\n", + "\n", + "你也可以使用 **new** 分配数组。这是一个使用 **new** 表达式分配的三维数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ThreeDWithNew.java\n", + "import java.util.*;\n", + "\n", + "public class ThreeDWithNew {\n", + " public static void main(String[] args) {\n", + " // 3-D array with fixed length:\n", + " int[][][] a = new int[2][2][4];\n", + " System.out.println(Arrays.deepToString(a));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0,\n", + "0]]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "倘若你不对基元数组进行显式的初始化,它的值会自动初始化。而对象数组将被初始化为 **null** 。\n", + "\n", + "组成矩阵的数组中每一个向量都可以是任意长度的(这叫做不规则数组):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/RaggedArray.java\n", + "import java.util.*;\n", + "\n", + "public class RaggedArray {\n", + " static int val = 1;\n", + " public static void main(String[] args) {\n", + " SplittableRandom rand = new SplittableRandom(47);\n", + " // 3-D array with varied-length vectors:\n", + " int[][][] a = new int[rand.nextInt(7)][][];\n", + " for(int i = 0; i < a.length; i++) {\n", + " a[i] = new int[rand.nextInt(5)][];\n", + " for(int j = 0; j < a[i].length; j++) {\n", + " a[i][j] = new int[rand.nextInt(5)];\n", + " Arrays.setAll(a[i][j], n -> val++); // [1]\n", + " }\n", + " }\n", + " System.out.println(Arrays.deepToString(a));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[[1], []], [[2, 3, 4, 5], [6]], [[7, 8, 9], [10, 11,\n", + "12], []]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个 **new** 在 for 循环中给数组填充了第二个元素,第三个 **new** 为数组的最后一个索引填充元素。\n", + "\n", + "* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数式接口 **IntUnaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的 lambda 表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。\n", + "\n", + "非基元的对象数组也可以定义为不规则数组。这里,我们收集了许多使用大括号的 **new** 表达式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/MultidimensionalObjectArrays.java\n", + "import java.util.*;\n", + "\n", + "public class MultidimensionalObjectArrays {\n", + " public static void main(String[] args) {\n", + " BerylliumSphere[][] spheres = {\n", + " { new BerylliumSphere(), new BerylliumSphere() },\n", + " { new BerylliumSphere(), new BerylliumSphere(),\n", + " new BerylliumSphere(), new BerylliumSphere() },\n", + " { new BerylliumSphere(), new BerylliumSphere(),\n", + " new BerylliumSphere(), new BerylliumSphere(),\n", + " new BerylliumSphere(), new BerylliumSphere(),\n", + " new BerylliumSphere(), new BerylliumSphere() },\n", + " };\n", + " System.out.println(Arrays.deepToString(spheres));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[Sphere 0, Sphere 1], [Sphere 2, Sphere 3, Sphere 4,\n", + "Sphere 5], [Sphere 6, Sphere 7, Sphere 8, Sphere 9,\n", + "Sphere 10, Sphere 11, Sphere 12, Sphere 13]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "数组初始化时使用自动装箱技术:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/AutoboxingArrays.java\n", + "import java.util.*;\n", + "\n", + "public class AutoboxingArrays {\n", + " public static void main(String[] args) {\n", + " Integer[][] a = { // Autoboxing:\n", + " { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },\n", + " { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },\n", + " { 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },\n", + " { 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 },\n", + " };\n", + " System.out.println(Arrays.deepToString(a));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [21, 22, 23, 24, 25,\n", + "26, 27, 28, 29, 30], [51, 52, 53, 54, 55, 56, 57, 58,\n", + "59, 60], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以下是如何逐个构建非基元的对象数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/AssemblingMultidimensionalArrays.java\n", + "// Creating multidimensional arrays\n", + "import java.util.*;\n", + "\n", + "public class AssemblingMultidimensionalArrays {\n", + " public static void main(String[] args) {\n", + " Integer[][] a;\n", + " a = new Integer[3][];\n", + " for(int i = 0; i < a.length; i++) {\n", + " a[i] = new Integer[3];\n", + " for(int j = 0; j < a[i].length; j++)\n", + " a[i][j] = i * j; // Autoboxing\n", + " }\n", + " System.out.println(Arrays.deepToString(a));\n", + " }\n", + "}\n", + "/* Output:\n", + "[[0, 0, 0], [0, 1, 2], [0, 2, 4]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。\n", + "\n", + "**Arrays.deepToString()** 方法同时适用于基元数组和对象数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/MultiDimWrapperArray.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Multidimensional arrays of \"wrapper\" objects\n", + "import java.util.*;\n", + "\n", + "public class MultiDimWrapperArray {\n", + " public static void main(String[] args) {\n", + " Integer[][] a1 = { // Autoboxing\n", + " { 1, 2, 3, },\n", + " { 4, 5, 6, },\n", + " };\n", + " Double[][][] a2 = { // Autoboxing\n", + " { { 1.1, 2.2 }, { 3.3, 4.4 } },\n", + " { { 5.5, 6.6 }, { 7.7, 8.8 } },\n", + " { { 9.9, 1.2 }, { 2.3, 3.4 } },\n", + " };\n", + " String[][] a3 = {\n", + " { \"The\", \"Quick\", \"Sly\", \"Fox\" },\n", + " { \"Jumped\", \"Over\" },\n", + " { \"The\", \"Lazy\", \"Brown\", \"Dog\", \"&\", \"friend\" },\n", + " };\n", + " System.out.println(\n", + " \"a1: \" + Arrays.deepToString(a1));\n", + " System.out.println(\n", + " \"a2: \" + Arrays.deepToString(a2));\n", + " System.out.println(\n", + " \"a3: \" + Arrays.deepToString(a3));\n", + " }\n", + "}\n", + "/* Output:\n", + "a1: [[1, 2, 3], [4, 5, 6]]\n", + "a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7,\n", + "8.8]], [[9.9, 1.2], [2.3, 3.4]]]\n", + "a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The,\n", + "Lazy, Brown, Dog, &, friend]]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "同样的,在 **Integer** 和 **Double** 数组中,自动装箱可为你创建包装器对象。\n", + "\n", + "\n", + "## 泛型数组\n", + "\n", + "一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Peel[] peels = new Peel[10]; // Illegal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类型擦除需要删除参数类型信息,而且数组必须知道它们所保存的确切类型,以强制保证类型安全。\n", + "\n", + "但是,可以参数化数组本身的类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ParameterizedArrayType.java\n", + "\n", + "class ClassParameter {\n", + " public T[] f(T[] arg) { return arg; }\n", + "}\n", + "\n", + "class MethodParameter {\n", + " public static T[] f(T[] arg) { return arg; }\n", + "}\n", + "\n", + "public class ParameterizedArrayType {\n", + " public static void main(String[] args) {\n", + " Integer[] ints = { 1, 2, 3, 4, 5 };\n", + " Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 };\n", + " Integer[] ints2 =\n", + " new ClassParameter().f(ints);\n", + " Double[] doubles2 =\n", + " new ClassParameter().f(doubles);\n", + " ints2 = MethodParameter.f(ints);\n", + " doubles2 = MethodParameter.f(doubles);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "比起使用参数化类,使用参数化方法很方便。您不必为应用它的每个不同类型都实例化一个带有参数的类,但是可以使它成为 **静态** 的。你不能总是选择使用参数化方法而不用参数化的类,但通常参数化方法是更好的选择。\n", + "\n", + "你不能创建泛型类型的数组,这种说法并不完全正确。是的,编译器不会让你 *实例化* 一个泛型的数组。但是,它将允许您创建对此类数组的引用。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "List[] ls;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "无可争议的,这可以通过编译。尽管不能创建包含泛型的实际数组对象,但是你可以创建一个非泛型的数组并对其进行强制类型转换:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ArrayOfGenerics.java\n", + "import java.util.*;\n", + "\n", + "public class ArrayOfGenerics {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " List[] ls;\n", + " List[] la = new List[10];\n", + " ls = (List[])la; // Unchecked cast\n", + " ls[0] = new ArrayList<>();\n", + "\n", + " //- ls[1] = new ArrayList();\n", + " // error: incompatible types: ArrayList\n", + " // cannot be converted to List\n", + " // ls[1] = new ArrayList();\n", + " // ^\n", + "\n", + " // The problem: List is a subtype of Object\n", + " Object[] objects = ls; // So assignment is OK\n", + " // Compiles and runs without complaint:\n", + " objects[1] = new ArrayList<>();\n", + "\n", + " // However, if your needs are straightforward it is\n", + " // possible to create an array of generics, albeit\n", + " // with an \"unchecked cast\" warning:\n", + " List[] spheres =\n", + " (List[])new List[10];\n", + " Arrays.setAll(spheres, n -> new ArrayList<>());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦你有了对 **List[]** 的引用 , 你会发现多了一些编译时检查。问题是数组是协变的,所以 **List[]** 也是一个 **Object[]** ,你可以用这来将 **ArrayList ** 分配进你的数组,在编译或者运行时都不会出错。\n", + "\n", + "如果你知道你不会进行向上类型转换,你的需求相对简单,那么可以创建一个泛型数组,它将提供基本的编译时类型检查。然而,一个泛型 **Collection** 实际上是一个比泛型数组更好的选择。\n", + "\n", + "一般来说,您会发现泛型在类或方法的边界上是有效的。在内部,擦除常常会使泛型不可使用。所以,就像下面的例子,不能创建泛型类型的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ArrayOfGenericType.java\n", + "\n", + "public class ArrayOfGenericType {\n", + " T[] array; // OK\n", + " @SuppressWarnings(\"unchecked\")\n", + " public ArrayOfGenericType(int size) {\n", + " // error: generic array creation:\n", + " //- array = new T[size];\n", + " array = (T[])new Object[size]; // unchecked cast\n", + " }\n", + " // error: generic array creation:\n", + " //- public U[] makeArray() { return new U[10]; }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "擦除再次从中作梗,这个例子试图创建已经擦除的类型数组,因此它们是未知的类型。你可以创建一个 **对象** 数组,然后对其进行强制类型转换,但如果没有 **@SuppressWarnings** 注释,你将会得到一个 \"unchecked\" 警告,因为数组实际上不真正支持而且将对类型 **T** 动态检查 。这就是说,如果我创建了一个 **String[]** , Java将在编译时和运行时强制执行,我只能在数组中放置字符串对象。然而,如果我创建一个 **Object[]** ,我可以把除了基元类型外的任何东西放入数组。\n", + "\n", + "\n", + "\n", + "\n", + "## Arrays的fill方法\n", + "\n", + "通常情况下,当对数组和程序进行实验时,能够很轻易地生成充满测试数据的数组是很有帮助的。 Java 标准库 **Arrays** 类包括一个普通的 **fill()** 方法,该方法将单个值复制到整个数组,或者在对象数组的情况下,将相同的引用复制到整个数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/FillingArrays.java\n", + "// Using Arrays.fill()\n", + "import java.util.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class FillingArrays {\n", + " public static void main(String[] args) {\n", + " int size = 6;\n", + " boolean[] a1 = new boolean[size];\n", + " byte[] a2 = new byte[size];\n", + " char[] a3 = new char[size];\n", + " short[] a4 = new short[size];\n", + " int[] a5 = new int[size];\n", + " long[] a6 = new long[size];\n", + " float[] a7 = new float[size];\n", + " double[] a8 = new double[size];\n", + " String[] a9 = new String[size];\n", + " Arrays.fill(a1, true);\n", + " show(\"a1\", a1);\n", + " Arrays.fill(a2, (byte)11);\n", + " show(\"a2\", a2);\n", + " Arrays.fill(a3, 'x');\n", + " show(\"a3\", a3);\n", + " Arrays.fill(a4, (short)17);\n", + " show(\"a4\", a4);\n", + " Arrays.fill(a5, 19);\n", + " show(\"a5\", a5);\n", + " Arrays.fill(a6, 23);\n", + " show(\"a6\", a6);\n", + " Arrays.fill(a7, 29);\n", + " show(\"a7\", a7);\n", + " Arrays.fill(a8, 47);\n", + " show(\"a8\", a8);\n", + " Arrays.fill(a9, \"Hello\");\n", + " show(\"a9\", a9);\n", + " // Manipulating ranges:\n", + " Arrays.fill(a9, 3, 5, \"World\");\n", + " show(\"a9\", a9);\n", + " }\n", + "}gedan\n", + "/* Output:\n", + "a1: [true, true, true, true, true, true]\n", + "a2: [11, 11, 11, 11, 11, 11]\n", + "a3: [x, x, x, x, x, x]\n", + "a4: [17, 17, 17, 17, 17, 17]\n", + "a5: [19, 19, 19, 19, 19, 19]\n", + "a6: [23, 23, 23, 23, 23, 23]\n", + "a7: [29.0, 29.0, 29.0, 29.0, 29.0, 29.0]\n", + "a8: [47.0, 47.0, 47.0, 47.0, 47.0, 47.0]\n", + "a9: [Hello, Hello, Hello, Hello, Hello, Hello]\n", + "a9: [Hello, Hello, Hello, World, World, Hello]\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你既可以填充整个数组,也可以像最后两个语句所示,填充一系列的元素。但是由于你只能使用单个值调用 **Arrays.fill()** ,因此结果并非特别有用。\n", + "\n", + "\n", + "\n", + "## Arrays的setAll方法\n", + "\n", + "在Java 8中, 在**RaggedArray.java** 中引入并在 **ArrayOfGenerics.java.Array.setAll()** 中重用。它使用一个生成器并生成不同的值,可以选择基于数组的索引元素(通过访问当前索引,生成器可以读取数组值并对其进行修改)。 **static Arrays.setAll()** 的重载签名为:\n", + "\n", + "* **void setAll(int[] a, IntUnaryOperator gen)**\n", + "* **void setAll(long[] a, IntToLongFunction gen)**\n", + "* **void setAll(double[] a, IntToDoubleFunctiongen)**\n", + "* ** void setAll(T[] a, IntFunction gen)**\n", + "\n", + "除了 **int** , **long** , **double** 有特殊的版本,其他的一切都由泛型版本处理。生成器不是 **Supplier** 因为它们不带参数,并且必须将 **int** 数组索引作为参数。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/SimpleSetAll.java\n", + "\n", + "import java.util.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "class Bob {\n", + " final int id;\n", + " Bob(int n) { id = n; }\n", + " @Override\n", + " public String toString() { return \"Bob\" + id; }\n", + "}\n", + "\n", + "public class SimpleSetAll {\n", + " public static final int SZ = 8;\n", + " static int val = 1;\n", + " static char[] chars = \"abcdefghijklmnopqrstuvwxyz\"\n", + " .toCharArray();\n", + " static char getChar(int n) { return chars[n]; }\n", + " public static void main(String[] args) {\n", + " int[] ia = new int[SZ];\n", + " long[] la = new long[SZ];\n", + " double[] da = new double[SZ];\n", + " Arrays.setAll(ia, n -> n); // [1]\n", + " Arrays.setAll(la, n -> n);\n", + " Arrays.setAll(da, n -> n);\n", + " show(ia);\n", + " show(la);\n", + " show(da);\n", + " Arrays.setAll(ia, n -> val++); // [2]\n", + " Arrays.setAll(la, n -> val++);\n", + " Arrays.setAll(da, n -> val++);\n", + " show(ia);\n", + " show(la);\n", + " show(da);\n", + "\n", + " Bob[] ba = new Bob[SZ];\n", + " Arrays.setAll(ba, Bob::new); // [3]\n", + " show(ba);\n", + "\n", + " Character[] ca = new Character[SZ];\n", + " Arrays.setAll(ca, SimpleSetAll::getChar); // [4]\n", + " show(ca);\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", + "[1, 2, 3, 4, 5, 6, 7, 8]\n", + "[9, 10, 11, 12, 13, 14, 15, 16]\n", + "[17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0]\n", + "[Bob0, Bob1, Bob2, Bob3, Bob4, Bob5, Bob6, Bob7]\n", + "[a, b, c, d, e, f, g, h]\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* **[1]** 这里,我们只是将数组索引作为值插入数组。这将自动转化为 **long** 和 **double** 版本。\n", + "* **[2]** 这个函数只需要接受索引就能产生正确结果。这个,我们忽略索引值并且使用 **val** 生成结果。\n", + "* **[3]** 方法引用有效,因为 **Bob** 的构造器接收一个 **int** 参数。只要我们传递的函数接收一个 **int** 参数且能产生正确的结果,就认为它完成了工作。\n", + "* **[4]** 为了处理除了 **int** ,**long** ,**double** 之外的基元类型,请为基元创建包装类的数组。然后使用 **setAll()** 的泛型版本。请注意,**getChar()** 生成基元类型,因此这是自动装箱到 **Character** 。\n", + "\n", + "\n", + "\n", + "## 增量生成\n", + "\n", + "这是一个方法库,用于为不同类型生成增量值。\n", + "\n", + "这些被作为内部类来生成容易记住的名字;比如,为了使用 **Integer** 工具你可以用 **new Conut.Interger()** , 如果你想要使用基本数据类型 **int** 工具,你可以用 **new Count.Pint()** (基本类型的名字不能被直接使用,所以它们都在前面添加一个 **P** 来表示基本数据类型'primitive', 我们的第一选择是使用基本类型名字后面跟着下划线,比如 **int_** 和 **double_** ,但是这种方式违背Java的命名习惯)。每个包装类的生成器都使用 **get()** 方法实现了它的 **Supplier** 。要使用**Array.setAll()** ,一个重载的 **get(int n)** 方法要接受(并忽略)其参数,以便接受 **setAll()** 传递的索引值。\n", + "\n", + "注意,通过使用包装类的名称作为内部类名,我们必须调用 **java.lang** 包来保证我们可以使用实际包装类的名字:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Count.java\n", + "// Generate incremental values of different types\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import static onjava.ConvertTo.*;\n", + "\n", + "public interface Count {\n", + " class Boolean\n", + " implements Supplier {\n", + " private boolean b = true;\n", + " @Override\n", + " public java.lang.Boolean get() {\n", + " b = !b;\n", + " return java.lang.Boolean.valueOf(b);\n", + " }\n", + " public java.lang.Boolean get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Boolean[] array(int sz) {\n", + " java.lang.Boolean[] result =\n", + " new java.lang.Boolean[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pboolean {\n", + " private boolean b = true;\n", + " public boolean get() {\n", + " b = !b;\n", + " return b;\n", + " }\n", + " public boolean get(int n) { return get(); }\n", + " public boolean[] array(int sz) {\n", + " return primitive(new Boolean().array(sz));\n", + " }\n", + " }\n", + " class Byte\n", + " implements Supplier {\n", + " private byte b;\n", + " @Override\n", + " public java.lang.Byte get() { return b++; }\n", + " public java.lang.Byte get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Byte[] array(int sz) {\n", + " java.lang.Byte[] result =\n", + " new java.lang.Byte[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pbyte {\n", + " private byte b;\n", + " public byte get() { return b++; }\n", + " public byte get(int n) { return get(); }\n", + " public byte[] array(int sz) {\n", + " return primitive(new Byte().array(sz));\n", + " }\n", + " }\n", + " char[] CHARS =\n", + " \"abcdefghijklmnopqrstuvwxyz\".toCharArray();\n", + " class Character\n", + " implements Supplier {\n", + " private int i;\n", + " @Override\n", + " public java.lang.Character get() {\n", + " i = (i + 1) % CHARS.length;\n", + " return CHARS[i];\n", + " }\n", + " public java.lang.Character get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Character[] array(int sz) {\n", + " java.lang.Character[] result =\n", + " new java.lang.Character[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pchar {\n", + " private int i;\n", + " public char get() {\n", + " i = (i + 1) % CHARS.length;\n", + " return CHARS[i];\n", + " }\n", + " public char get(int n) { return get(); }\n", + " public char[] array(int sz) {\n", + " return primitive(new Character().array(sz));\n", + " }\n", + " }\n", + " class Short\n", + " implements Supplier {\n", + " short s;\n", + " @Override\n", + " public java.lang.Short get() { return s++; }\n", + " public java.lang.Short get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Short[] array(int sz) {\n", + " java.lang.Short[] result =\n", + " new java.lang.Short[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pshort {\n", + " short s;\n", + " public short get() { return s++; }\n", + " public short get(int n) { return get(); }\n", + " public short[] array(int sz) {\n", + " return primitive(new Short().array(sz));\n", + " }\n", + " }\n", + " class Integer\n", + " implements Supplier {\n", + " int i;\n", + " @Override\n", + " public java.lang.Integer get() { return i++; }\n", + " public java.lang.Integer get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Integer[] array(int sz) {\n", + " java.lang.Integer[] result =\n", + " new java.lang.Integer[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pint implements IntSupplier {\n", + " int i;\n", + " public int get() { return i++; }\n", + " public int get(int n) { return get(); }\n", + " @Override\n", + " public int getAsInt() { return get(); }\n", + " public int[] array(int sz) {\n", + " return primitive(new Integer().array(sz));\n", + " }\n", + " }\n", + " class Long\n", + " implements Supplier {\n", + " private long l;\n", + " @Override\n", + " public java.lang.Long get() { return l++; }\n", + " public java.lang.Long get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Long[] array(int sz) {\n", + " java.lang.Long[] result =\n", + " new java.lang.Long[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Plong implements LongSupplier {\n", + " private long l;\n", + " public long get() { return l++; }\n", + " public long get(int n) { return get(); }\n", + " @Override\n", + " public long getAsLong() { return get(); }\n", + " public long[] array(int sz) {\n", + " return primitive(new Long().array(sz));\n", + " }\n", + " }\n", + " class Float\n", + " implements Supplier {\n", + " private int i;\n", + " @Override\n", + " public java.lang.Float get() {\n", + " return java.lang.Float.valueOf(i++);\n", + " }\n", + " public java.lang.Float get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Float[] array(int sz) {\n", + " java.lang.Float[] result =\n", + " new java.lang.Float[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pfloat {\n", + " private int i;\n", + " public float get() { return i++; }\n", + " public float get(int n) { return get(); }\n", + " public float[] array(int sz) {\n", + " return primitive(new Float().array(sz));\n", + " }\n", + " }\n", + " class Double\n", + " implements Supplier {\n", + " private int i;\n", + " @Override\n", + " public java.lang.Double get() {\n", + " return java.lang.Double.valueOf(i++);\n", + " }\n", + " public java.lang.Double get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Double[] array(int sz) {\n", + " java.lang.Double[] result =\n", + " new java.lang.Double[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pdouble implements DoubleSupplier {\n", + " private int i;\n", + " public double get() { return i++; }\n", + " public double get(int n) { return get(); }\n", + " @Override\n", + " public double getAsDouble() { return get(0); }\n", + " public double[] array(int sz) {\n", + " return primitive(new Double().array(sz));\n", + " }\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于 **int** ,**long** ,**double** 这三个有特殊 **Supplier** 接口的原始数据类型来说,**Pint** , **Plong** 和 **Pdouble** 实现了这些接口。\n", + "\n", + "这里是对 **Count** 的测试,这同样给我们提供了如何使用它的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/TestCount.java\n", + "// Test counting generators\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class TestCount {\n", + " static final int SZ = 5;\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Boolean\");\n", + " Boolean[] a1 = new Boolean[SZ];\n", + " Arrays.setAll(a1, new Count.Boolean()::get);\n", + " show(a1);\n", + " a1 = Stream.generate(new Count.Boolean())\n", + " .limit(SZ + 1).toArray(Boolean[]::new);\n", + " show(a1);\n", + " a1 = new Count.Boolean().array(SZ + 2);\n", + " show(a1);\n", + " boolean[] a1b =\n", + " new Count.Pboolean().array(SZ + 3);\n", + " show(a1b);\n", + "\n", + " System.out.println(\"Byte\");\n", + " Byte[] a2 = new Byte[SZ];\n", + " Arrays.setAll(a2, new Count.Byte()::get);\n", + " show(a2);\n", + " a2 = Stream.generate(new Count.Byte())\n", + " .limit(SZ + 1).toArray(Byte[]::new);\n", + " show(a2);\n", + " a2 = new Count.Byte().array(SZ + 2);\n", + " show(a2);\n", + " byte[] a2b = new Count.Pbyte().array(SZ + 3);\n", + " show(a2b);\n", + "\n", + " System.out.println(\"Character\");\n", + " Character[] a3 = new Character[SZ];\n", + " Arrays.setAll(a3, new Count.Character()::get);\n", + " show(a3);\n", + " a3 = Stream.generate(new Count.Character())\n", + " .limit(SZ + 1).toArray(Character[]::new);\n", + " show(a3);\n", + " a3 = new Count.Character().array(SZ + 2);\n", + " show(a3);\n", + " char[] a3b = new Count.Pchar().array(SZ + 3);\n", + " show(a3b);\n", + "\n", + " System.out.println(\"Short\");\n", + " Short[] a4 = new Short[SZ];\n", + " Arrays.setAll(a4, new Count.Short()::get);\n", + " show(a4);\n", + " a4 = Stream.generate(new Count.Short())\n", + " .limit(SZ + 1).toArray(Short[]::new);\n", + " show(a4);\n", + " a4 = new Count.Short().array(SZ + 2);\n", + " show(a4);\n", + " short[] a4b = new Count.Pshort().array(SZ + 3);\n", + " show(a4b);\n", + "\n", + " System.out.println(\"Integer\");\n", + " int[] a5 = new int[SZ];\n", + " Arrays.setAll(a5, new Count.Integer()::get);\n", + " show(a5);\n", + " Integer[] a5b =\n", + " Stream.generate(new Count.Integer())\n", + " .limit(SZ + 1).toArray(Integer[]::new);\n", + " show(a5b);\n", + " a5b = new Count.Integer().array(SZ + 2);\n", + " show(a5b);\n", + " a5 = IntStream.generate(new Count.Pint())\n", + " .limit(SZ + 1).toArray();\n", + " show(a5);\n", + " a5 = new Count.Pint().array(SZ + 3);\n", + " show(a5);\n", + "\n", + " System.out.println(\"Long\");\n", + " long[] a6 = new long[SZ];\n", + " Arrays.setAll(a6, new Count.Long()::get);\n", + " show(a6);\n", + " Long[] a6b = Stream.generate(new Count.Long())\n", + " .limit(SZ + 1).toArray(Long[]::new);\n", + " show(a6b);\n", + " a6b = new Count.Long().array(SZ + 2);\n", + " show(a6b);\n", + " a6 = LongStream.generate(new Count.Plong())\n", + " .limit(SZ + 1).toArray();\n", + " show(a6);\n", + " a6 = new Count.Plong().array(SZ + 3);\n", + " show(a6);\n", + "\n", + " System.out.println(\"Float\");\n", + " Float[] a7 = new Float[SZ];\n", + " Arrays.setAll(a7, new Count.Float()::get);\n", + " show(a7);\n", + " a7 = Stream.generate(new Count.Float())\n", + " .limit(SZ + 1).toArray(Float[]::new);\n", + " show(a7);\n", + " a7 = new Count.Float().array(SZ + 2);\n", + " show(a7);\n", + " float[] a7b = new Count.Pfloat().array(SZ + 3);\n", + " show(a7b);\n", + "\n", + " System.out.println(\"Double\");\n", + " double[] a8 = new double[SZ];\n", + " Arrays.setAll(a8, new Count.Double()::get);\n", + " show(a8);\n", + " Double[] a8b =\n", + " Stream.generate(new Count.Double())\n", + " .limit(SZ + 1).toArray(Double[]::new);\n", + " show(a8b);\n", + " a8b = new Count.Double().array(SZ + 2);\n", + " show(a8b);\n", + " a8 = DoubleStream.generate(new Count.Pdouble())\n", + " .limit(SZ + 1).toArray();\n", + " show(a8);\n", + " a8 = new Count.Pdouble().array(SZ + 3);\n", + " show(a8);\n", + " }\n", + "}\n", + "/* Output:\n", + "Boolean\n", + "[false, true, false, true, false]\n", + "[false, true, false, true, false, true]\n", + "[false, true, false, true, false, true, false]\n", + "[false, true, false, true, false, true, false, true]\n", + "Byte\n", + "[0, 1, 2, 3, 4]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6]\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "Character\n", + "[b, c, d, e, f]\n", + "[b, c, d, e, f, g]\n", + "[b, c, d, e, f, g, h]\n", + "[b, c, d, e, f, g, h, i]\n", + "Short\n", + "[0, 1, 2, 3, 4]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6]\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "Integer\n", + "[0, 1, 2, 3, 4]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "Long\n", + "[0, 1, 2, 3, 4]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6]\n", + "[0, 1, 2, 3, 4, 5]\n", + "[0, 1, 2, 3, 4, 5, 6, 7]\n", + "Float\n", + "[0.0, 1.0, 2.0, 3.0, 4.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", + "Double\n", + "[0.0, 1.0, 2.0, 3.0, 4.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", + "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意到原始数组类型 **int[]** ,**long[]** ,**double[]** 可以直接被 **Arrays.setAll()** 填充,但是其他的原始类型都要求用包装器类型的数组。\n", + "\n", + "通过 **Stream.generate()** 创建的包装数组显示了 **toArray()** 的重载用法,在这里你应该提供给它要创建的数组类型的构造器。\n", + "\n", + "\n", + "## 随机生成\n", + "\n", + "我们可以按照 **Count.java** 的结构创建一个生成随机值的工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Rand.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Generate random values of different types\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import static onjava.ConvertTo.*;\n", + "\n", + "public interface Rand {\n", + " int MOD = 10_000;\n", + " class Boolean\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Boolean get() {\n", + " return r.nextBoolean();\n", + " }\n", + " public java.lang.Boolean get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Boolean[] array(int sz) {\n", + " java.lang.Boolean[] result =\n", + " new java.lang.Boolean[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pboolean {\n", + " public boolean[] array(int sz) {\n", + " return primitive(new Boolean().array(sz));\n", + " }\n", + " }\n", + " class Byte\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Byte get() {\n", + " return (byte)r.nextInt(MOD);\n", + " }\n", + " public java.lang.Byte get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Byte[] array(int sz) {\n", + " java.lang.Byte[] result =\n", + " new java.lang.Byte[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pbyte {\n", + " public byte[] array(int sz) {\n", + " return primitive(new Byte().array(sz));\n", + " }\n", + " }\n", + " class Character\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Character get() {\n", + " return (char)r.nextInt('a', 'z' + 1);\n", + " }\n", + " public java.lang.Character get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Character[] array(int sz) {\n", + " java.lang.Character[] result =\n", + " new java.lang.Character[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pchar {\n", + " public char[] array(int sz) {\n", + " return primitive(new Character().array(sz));\n", + " }\n", + " }\n", + " class Short\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Short get() {\n", + " return (short)r.nextInt(MOD);\n", + " }\n", + " public java.lang.Short get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Short[] array(int sz) {\n", + " java.lang.Short[] result =\n", + " new java.lang.Short[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pshort {\n", + " public short[] array(int sz) {\n", + " return primitive(new Short().array(sz));\n", + " }\n", + " }\n", + " class Integer\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Integer get() {\n", + " return r.nextInt(MOD);\n", + " }\n", + " public java.lang.Integer get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Integer[] array(int sz) {\n", + " int[] primitive = new Pint().array(sz);\n", + " java.lang.Integer[] result =\n", + " new java.lang.Integer[sz];\n", + " for(int i = 0; i < sz; i++)\n", + " result[i] = primitive[i];\n", + " return result;\n", + " }\n", + " }\n", + " class Pint implements IntSupplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public int getAsInt() {\n", + " return r.nextInt(MOD);\n", + " }\n", + " public int get(int n) { return getAsInt(); }\n", + " public int[] array(int sz) {\n", + " return r.ints(sz, 0, MOD).toArray();\n", + " }\n", + " }\n", + " class Long\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Long get() {\n", + " return r.nextLong(MOD);\n", + " }\n", + " public java.lang.Long get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Long[] array(int sz) {\n", + " long[] primitive = new Plong().array(sz);\n", + " java.lang.Long[] result =\n", + " new java.lang.Long[sz];\n", + " for(int i = 0; i < sz; i++)\n", + " result[i] = primitive[i];\n", + " return result;\n", + " }\n", + " }\n", + " class Plong implements LongSupplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public long getAsLong() {\n", + " return r.nextLong(MOD);\n", + " }\n", + " public long get(int n) { return getAsLong(); }\n", + " public long[] array(int sz) {\n", + " return r.longs(sz, 0, MOD).toArray();\n", + " }\n", + " }\n", + " class Float\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Float get() {\n", + " return (float)trim(r.nextDouble());\n", + " }\n", + " public java.lang.Float get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Float[] array(int sz) {\n", + " java.lang.Float[] result =\n", + " new java.lang.Float[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + " class Pfloat {\n", + " public float[] array(int sz) {\n", + " return primitive(new Float().array(sz));\n", + " }\n", + " }\n", + " static double trim(double d) {\n", + " return\n", + " ((double)Math.round(d * 1000.0)) / 100.0;\n", + " }\n", + " class Double\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public java.lang.Double get() {\n", + " return trim(r.nextDouble());\n", + " }\n", + " public java.lang.Double get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.Double[] array(int sz) {\n", + " double[] primitive =\n", + " new Rand.Pdouble().array(sz);\n", + " java.lang.Double[] result =\n", + " new java.lang.Double[sz];\n", + " for(int i = 0; i < sz; i++)\n", + " result[i] = primitive[i];\n", + " return result;\n", + " }\n", + " }\n", + " class Pdouble implements DoubleSupplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " @Override\n", + " public double getAsDouble() {\n", + " return trim(r.nextDouble());\n", + " }\n", + " public double get(int n) {\n", + " return getAsDouble();\n", + " }\n", + " public double[] array(int sz) {\n", + " double[] result = r.doubles(sz).toArray();\n", + " Arrays.setAll(result,\n", + " n -> result[n] = trim(result[n]));\n", + " return result;\n", + " }\n", + " }\n", + " class String\n", + " implements Supplier {\n", + " SplittableRandom r = new SplittableRandom(47);\n", + " private int strlen = 7; // Default length\n", + " public String() {}\n", + " public String(int strLength) {\n", + " strlen = strLength;\n", + " }\n", + " @Override\n", + " public java.lang.String get() {\n", + " return r.ints(strlen, 'a', 'z' + 1)\n", + " .collect(StringBuilder::new,\n", + " StringBuilder::appendCodePoint,\n", + " StringBuilder::append).toString();\n", + " }\n", + " public java.lang.String get(int n) {\n", + " return get();\n", + " }\n", + " public java.lang.String[] array(int sz) {\n", + " java.lang.String[] result =\n", + " new java.lang.String[sz];\n", + " Arrays.setAll(result, n -> get());\n", + " return result;\n", + " }\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于除了 **int** 、 **long** 和 **double** 之外的所有基本类型元素生成器,只生成数组,而不是 Count 中看到的完整操作集。这只是一个设计选择,因为本书不需要额外的功能。\n", + "\n", + "下面是对所有 **Rand** 工具的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/TestRand.java\n", + "// Test random generators\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class TestRand {\n", + " static final int SZ = 5;\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Boolean\");\n", + " Boolean[] a1 = new Boolean[SZ];\n", + " Arrays.setAll(a1, new Rand.Boolean()::get);\n", + " show(a1);\n", + " a1 = Stream.generate(new Rand.Boolean())\n", + " .limit(SZ + 1).toArray(Boolean[]::new);\n", + " show(a1);\n", + " a1 = new Rand.Boolean().array(SZ + 2);\n", + " show(a1);\n", + " boolean[] a1b =\n", + " new Rand.Pboolean().array(SZ + 3);\n", + " show(a1b);\n", + "\n", + " System.out.println(\"Byte\");\n", + " Byte[] a2 = new Byte[SZ];\n", + " Arrays.setAll(a2, new Rand.Byte()::get);\n", + " show(a2);\n", + " a2 = Stream.generate(new Rand.Byte())\n", + " .limit(SZ + 1).toArray(Byte[]::new);\n", + " show(a2);\n", + " a2 = new Rand.Byte().array(SZ + 2);\n", + " show(a2);\n", + " byte[] a2b = new Rand.Pbyte().array(SZ + 3);\n", + " show(a2b);\n", + "\n", + " System.out.println(\"Character\");\n", + " Character[] a3 = new Character[SZ];\n", + " Arrays.setAll(a3, new Rand.Character()::get);\n", + " show(a3);\n", + " a3 = Stream.generate(new Rand.Character())\n", + " .limit(SZ + 1).toArray(Character[]::new);\n", + " show(a3);\n", + " a3 = new Rand.Character().array(SZ + 2);\n", + " show(a3);\n", + " char[] a3b = new Rand.Pchar().array(SZ + 3);\n", + " show(a3b);\n", + "\n", + " System.out.println(\"Short\");\n", + " Short[] a4 = new Short[SZ];\n", + " Arrays.setAll(a4, new Rand.Short()::get);\n", + " show(a4);\n", + " a4 = Stream.generate(new Rand.Short())\n", + " .limit(SZ + 1).toArray(Short[]::new);\n", + " show(a4);\n", + " a4 = new Rand.Short().array(SZ + 2);\n", + " show(a4);\n", + " short[] a4b = new Rand.Pshort().array(SZ + 3);\n", + " show(a4b);\n", + "\n", + " System.out.println(\"Integer\");\n", + " int[] a5 = new int[SZ];\n", + " Arrays.setAll(a5, new Rand.Integer()::get);\n", + " show(a5);\n", + " Integer[] a5b =\n", + " Stream.generate(new Rand.Integer())\n", + " .limit(SZ + 1).toArray(Integer[]::new);\n", + " show(a5b);\n", + " a5b = new Rand.Integer().array(SZ + 2);\n", + " show(a5b);\n", + " a5 = IntStream.generate(new Rand.Pint())\n", + " .limit(SZ + 1).toArray();\n", + " show(a5);\n", + " a5 = new Rand.Pint().array(SZ + 3);\n", + " show(a5);\n", + "\n", + " System.out.println(\"Long\");\n", + " long[] a6 = new long[SZ];\n", + " Arrays.setAll(a6, new Rand.Long()::get);\n", + " show(a6);\n", + " Long[] a6b = Stream.generate(new Rand.Long())\n", + " .limit(SZ + 1).toArray(Long[]::new);\n", + " show(a6b);\n", + " a6b = new Rand.Long().array(SZ + 2);\n", + " show(a6b);\n", + " a6 = LongStream.generate(new Rand.Plong())\n", + " .limit(SZ + 1).toArray();\n", + " show(a6);\n", + " a6 = new Rand.Plong().array(SZ + 3);\n", + " show(a6);\n", + "\n", + " System.out.println(\"Float\");\n", + " Float[] a7 = new Float[SZ];\n", + " Arrays.setAll(a7, new Rand.Float()::get);\n", + " show(a7);\n", + " a7 = Stream.generate(new Rand.Float())\n", + " .limit(SZ + 1).toArray(Float[]::new);\n", + " show(a7);\n", + " a7 = new Rand.Float().array(SZ + 2);\n", + " show(a7);\n", + " float[] a7b = new Rand.Pfloat().array(SZ + 3);\n", + " show(a7b);\n", + "\n", + " System.out.println(\"Double\");\n", + " double[] a8 = new double[SZ];\n", + " Arrays.setAll(a8, new Rand.Double()::get);\n", + " show(a8);\n", + " Double[] a8b =\n", + " Stream.generate(new Rand.Double())\n", + " .limit(SZ + 1).toArray(Double[]::new);\n", + " show(a8b);\n", + " a8b = new Rand.Double().array(SZ + 2);\n", + " show(a8b);\n", + " a8 = DoubleStream.generate(new Rand.Pdouble())\n", + " .limit(SZ + 1).toArray();\n", + " show(a8);\n", + " a8 = new Rand.Pdouble().array(SZ + 3);\n", + " show(a8);\n", + "\n", + " System.out.println(\"String\");\n", + " String[] s = new String[SZ - 1];\n", + " Arrays.setAll(s, new Rand.String()::get);\n", + " show(s);\n", + " s = Stream.generate(new Rand.String())\n", + " .limit(SZ).toArray(String[]::new);\n", + " show(s);\n", + " s = new Rand.String().array(SZ + 1);\n", + " show(s);\n", + "\n", + " Arrays.setAll(s, new Rand.String(4)::get);\n", + " show(s);\n", + " s = Stream.generate(new Rand.String(4))\n", + " .limit(SZ).toArray(String[]::new);\n", + " show(s);\n", + " s = new Rand.String(4).array(SZ + 1);\n", + " show(s);\n", + " }\n", + "}\n", + "/* Output:\n", + "Boolean\n", + "[true, false, true, true, true]\n", + "[true, false, true, true, true, false]\n", + "[true, false, true, true, true, false, false]\n", + "[true, false, true, true, true, false, false, true]\n", + "Byte\n", + "[123, 33, 101, 112, 33]\n", + "[123, 33, 101, 112, 33, 31]\n", + "[123, 33, 101, 112, 33, 31, 0]\n", + "[123, 33, 101, 112, 33, 31, 0, -72]\n", + "Character\n", + "[b, t, p, e, n]\n", + "[b, t, p, e, n, p]\n", + "[b, t, p, e, n, p, c]\n", + "[b, t, p, e, n, p, c, c]\n", + "Short\n", + "[635, 8737, 3941, 4720, 6177]\n", + "[635, 8737, 3941, 4720, 6177, 8479]\n", + "[635, 8737, 3941, 4720, 6177, 8479, 6656]\n", + "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768]\n", + "Integer\n", + "[635, 8737, 3941, 4720, 6177]\n", + "[635, 8737, 3941, 4720, 6177, 8479]\n", + "[635, 8737, 3941, 4720, 6177, 8479, 6656]\n", + "[635, 8737, 3941, 4720, 6177, 8479]\n", + "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768]\n", + "Long\n", + "[6882, 3765, 692, 9575, 4439]\n", + "[6882, 3765, 692, 9575, 4439, 2638]\n", + "[6882, 3765, 692, 9575, 4439, 2638, 4011]\n", + "[6882, 3765, 692, 9575, 4439, 2638]\n", + "[6882, 3765, 692, 9575, 4439, 2638, 4011, 9610]\n", + "Float\n", + "[4.83, 2.89, 2.9, 1.97, 3.01]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28]\n", + "Double\n", + "[4.83, 2.89, 2.9, 1.97, 3.01]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28]\n", + "String\n", + "[btpenpc, cuxszgv, gmeinne, eloztdv]\n", + "[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc]\n", + "[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc, ygpoalk]\n", + "[btpe, npcc, uxsz, gvgm, einn, eelo]\n", + "[btpe, npcc, uxsz, gvgm, einn]\n", + "[btpe, npcc, uxsz, gvgm, einn, eelo]\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意(除了 **String** 部分之外),这段代码与 **TestCount.java** 中的代码相同,**Count** 被 **Rand** 替换。\n", + "\n", + "\n", + "## 泛型和基本数组\n", + "在本章的前面,我们被提醒,泛型不能和基元一起工作。在这种情况下,我们必须从基元数组转换为包装类型的数组,并且还必须从另一个方向转换。下面是一个转换器可以同时对所有类型的数据执行操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/ConvertTo.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "package onjava;\n", + "\n", + "public interface ConvertTo {\n", + " static boolean[] primitive(Boolean[] in) {\n", + " boolean[] result = new boolean[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i]; // Autounboxing\n", + " return result;\n", + " }\n", + " static char[] primitive(Character[] in) {\n", + " char[] result = new char[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static byte[] primitive(Byte[] in) {\n", + " byte[] result = new byte[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static short[] primitive(Short[] in) {\n", + " short[] result = new short[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static int[] primitive(Integer[] in) {\n", + " int[] result = new int[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static long[] primitive(Long[] in) {\n", + " long[] result = new long[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static float[] primitive(Float[] in) {\n", + " float[] result = new float[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static double[] primitive(Double[] in) {\n", + " double[] result = new double[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " // Convert from primitive array to wrapped array:\n", + " static Boolean[] boxed(boolean[] in) {\n", + " Boolean[] result = new Boolean[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i]; // Autoboxing\n", + " return result;\n", + " }\n", + " static Character[] boxed(char[] in) {\n", + " Character[] result = new Character[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Byte[] boxed(byte[] in) {\n", + " Byte[] result = new Byte[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Short[] boxed(short[] in) {\n", + " Short[] result = new Short[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Integer[] boxed(int[] in) {\n", + " Integer[] result = new Integer[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Long[] boxed(long[] in) {\n", + " Long[] result = new Long[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Float[] boxed(float[] in) {\n", + " Float[] result = new Float[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + " static Double[] boxed(double[] in) {\n", + " Double[] result = new Double[in.length];\n", + " for(int i = 0; i < in.length; i++)\n", + " result[i] = in[i];\n", + " return result;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**primitive()** 的每个版本都创建一个准确长度的适当基元数组,然后从包装类的 **in** 数组中复制元素。如果任何包装的数组元素是 **null** ,你将得到一个异常(这是合理的—否则无法选择有意义的值进行替换)。注意在这个任务中自动装箱如何发生。\n", + "\n", + "下面是对 **ConvertTo** 中所有方法的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/TestConvertTo.java\n", + "import java.util.*;\n", + "import onjava.*;\n", + "import static onjava.ArrayShow.*;\n", + "import static onjava.ConvertTo.*;\n", + "\n", + "public class TestConvertTo {\n", + " static final int SIZE = 6;\n", + " public static void main(String[] args) {\n", + " Boolean[] a1 = new Boolean[SIZE];\n", + " Arrays.setAll(a1, new Rand.Boolean()::get);\n", + " boolean[] a1p = primitive(a1);\n", + " show(\"a1p\", a1p);\n", + " Boolean[] a1b = boxed(a1p);\n", + " show(\"a1b\", a1b);\n", + "\n", + " Byte[] a2 = new Byte[SIZE];\n", + " Arrays.setAll(a2, new Rand.Byte()::get);\n", + " byte[] a2p = primitive(a2);\n", + " show(\"a2p\", a2p);\n", + " Byte[] a2b = boxed(a2p);\n", + " show(\"a2b\", a2b);\n", + "\n", + " Character[] a3 = new Character[SIZE];\n", + " Arrays.setAll(a3, new Rand.Character()::get);\n", + " char[] a3p = primitive(a3);\n", + " show(\"a3p\", a3p);\n", + " Character[] a3b = boxed(a3p);\n", + " show(\"a3b\", a3b);\n", + "\n", + " Short[] a4 = new Short[SIZE];\n", + " Arrays.setAll(a4, new Rand.Short()::get);\n", + " short[] a4p = primitive(a4);\n", + " show(\"a4p\", a4p);\n", + " Short[] a4b = boxed(a4p);\n", + " show(\"a4b\", a4b);\n", + "\n", + " Integer[] a5 = new Integer[SIZE];\n", + " Arrays.setAll(a5, new Rand.Integer()::get);\n", + " int[] a5p = primitive(a5);\n", + " show(\"a5p\", a5p);\n", + " Integer[] a5b = boxed(a5p);\n", + " show(\"a5b\", a5b);\n", + "\n", + " Long[] a6 = new Long[SIZE];\n", + " Arrays.setAll(a6, new Rand.Long()::get);\n", + " long[] a6p = primitive(a6);\n", + " show(\"a6p\", a6p);\n", + " Long[] a6b = boxed(a6p);\n", + " show(\"a6b\", a6b);\n", + "\n", + " Float[] a7 = new Float[SIZE];\n", + " Arrays.setAll(a7, new Rand.Float()::get);\n", + " float[] a7p = primitive(a7);\n", + " show(\"a7p\", a7p);\n", + " Float[] a7b = boxed(a7p);\n", + " show(\"a7b\", a7b);\n", + "\n", + " Double[] a8 = new Double[SIZE];\n", + " Arrays.setAll(a8, new Rand.Double()::get);\n", + " double[] a8p = primitive(a8);\n", + " show(\"a8p\", a8p);\n", + " Double[] a8b = boxed(a8p);\n", + " show(\"a8b\", a8b);\n", + " }\n", + "}\n", + "/* Output:\n", + "a1p: [true, false, true, true, true, false]\n", + "a1b: [true, false, true, true, true, false]\n", + "a2p: [123, 33, 101, 112, 33, 31]\n", + "a2b: [123, 33, 101, 112, 33, 31]\n", + "a3p: [b, t, p, e, n, p]\n", + "a3b: [b, t, p, e, n, p]\n", + "a4p: [635, 8737, 3941, 4720, 6177, 8479]\n", + "a4b: [635, 8737, 3941, 4720, 6177, 8479]\n", + "a5p: [635, 8737, 3941, 4720, 6177, 8479]\n", + "a5b: [635, 8737, 3941, 4720, 6177, 8479]\n", + "a6p: [6882, 3765, 692, 9575, 4439, 2638]\n", + "a6b: [6882, 3765, 692, 9575, 4439, 2638]\n", + "a7p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "a7b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "a8p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "a8b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在每种情况下,原始数组都是为包装类型创建的,并使用 **Arrays.setAll()** 填充,正如我们在 **TestCouner.java** 中所做的那样(这也验证了 **Arrays.setAll()** 是否能同 **Integer** ,**Long** ,和 **Double** )。然后 **ConvertTo.primitive()** 将包装器数组转换为对应的基元数组,**ConverTo.boxed()** 将其转换回来。\n", + "\n", + "\n", + "## 数组元素修改\n", + "\n", + "传递给 **Arrays.setAll()** 的生成器函数可以使用它接收到的数组索引修改现有的数组元素:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ModifyExisting.java\n", + "\n", + "import java.util.*;\n", + "import onjava.*;\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class ModifyExisting {\n", + " public static void main(String[] args) {\n", + " double[] da = new double[7];\n", + " Arrays.setAll(da, new Rand.Double()::get);\n", + " show(da);\n", + " Arrays.setAll(da, n -> da[n] / 100); // [1]\n", + " show(da);\n", + "\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", + "[0.0483, 0.028900000000000002, 0.028999999999999998,\n", + "0.0197, 0.0301, 0.0018, 0.009899999999999999]\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[1] Lambdas在这里特别有用,因为数组总是在lambda表达式的范围内。\n", + "\n", + "\n", + "\n", + "## 数组并行\n", + "\n", + "我们很快就不得不面对并行的主题。例如,“并行”一词在许多Java库方法中使用。您可能听说过类似“并行程序运行得更快”这样的说法,这是有道理的—当您可以有多个处理器时,为什么只有一个处理器在您的程序上工作呢? 如果您认为您应该利用其中的“并行”,这是很容易被原谅的。\n", + "要是这么简单就好了。不幸的是,通过采用这种方法,您可以很容易地编写比非并行版本运行速度更慢的代码。在你深刻理解所有的问题之前,并行编程看起来更像是一门艺术而非科学。\n", + "以下是简短的版本:用简单的方法编写代码。不要开始处理并行性,除非它成为一个问题。您仍然会遇到并行性。在本章中,我们将介绍一些为并行执行而编写的Java库方法。因此,您必须对它有足够的了解,以便进行基本的讨论,并避免出现错误。\n", + "\n", + "在阅读并发编程这一章之后,您将更深入地理解它(但是,唉,这还远远不够。只是这些的话,充分理解这个主题是不可能的)。\n", + "在某些情况下,即使您只有一个处理器,无论您是否显式地尝试并行,并行实现是惟一的、最佳的或最符合逻辑的选择。它是一个可以一直使用的工具,所以您必须了解它的相关问题。\n", + "\n", + "最好从数据的角度来考虑并行性。对于大量数据(以及可用的额外处理器),并行可能会有所帮助。但您也可能使事情变得更糟。\n", + "\n", + "在本书的其余部分,我们将遇到不同的情况:\n", + "\n", + "- 1、所提供的惟一选项是并行的。这很简单,因为我们别无选择,只能使用它。这种情况是比较罕见的。\n", + "\n", + "- 2、有多个选项,但是并行版本(通常是最新的版本)被设计成在任何地方都可以使用(甚至在那些不关心并行性的代码中),如案例#1。我们将按预期使用并行版本。\n", + "\n", + "- 3、案例1和案例2并不经常发生。相反,您将遇到某些算法的两个版本,一个用于并行使用,另一个用于正常使用。我将描述并行的一个,但不会在普通代码中使用它,因为它也许会产生所有可能的问题。\n", + "\n", + "我建议您在自己的代码中采用这种方法。\n", + "\n", + "[http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](要进一步了解为什么这是一个难题,请参阅Doug Lea的文章。)\n", + "\n", + "**parallelSetAll()**\n", + "\n", + "流式编程产生优雅的代码。例如,假设我们想要创建一个数值由从零开始填充的长数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/CountUpward.java\n", + "\n", + "import java.util.stream.LongStream;\n", + "\n", + "public class CountUpward {\n", + " static long[] fillCounted(int size) {\n", + " return LongStream.iterate(0, i -> i + 1).limit(size).toArray();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " long[] l1 = fillCounted(20); // No problem\n", + " show(l1);\n", + " // On my machine, this runs out of heap space:\n", + " // - long[] l2 = fillCounted(10_000_000);\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", + "16, 17, 18, 19]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**流** 实际上可以存储到将近1000万,但是之后就会耗尽堆空间。常规的 **setAll()** 是有效的,但是如果我们能更快地处理如此大量的数字,那就更好了。\n", + "我们可以使用 **setAll()** 初始化更大的数组。如果速度成为一个问题,**Arrays.parallelSetAll()** 将(可能)更快地执行初始化(请记住并行性中描述的问题)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "\n", + "// arrays/ParallelSetAll.java\n", + "\n", + "import onjava.*;\n", + "import java.util.Arrays;\n", + "\n", + "public class ParallelSetAll {\n", + " static final int SIZE = 10_000_000;\n", + "\n", + " static void intArray() {\n", + " int[] ia = new int[SIZE];\n", + " Arrays.setAll(ia, new Rand.Pint()::get);\n", + " Arrays.parallelSetAll(ia, new Rand.Pint()::get);\n", + " }\n", + "\n", + " static void longArray() {\n", + " long[] la = new long[SIZE];\n", + " Arrays.setAll(la, new Rand.Plong()::get);\n", + " Arrays.parallelSetAll(la, new Rand.Plong()::get);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " intArray();\n", + " longArray();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "数组分配和初始化是在单独的方法中执行的,因为如果两个数组都在 **main()** 中分配,它会耗尽内存(至少在我的机器上是这样。还有一些方法可以告诉Java在启动时分配更多的内存)。\n", + "\n", + "\n", + "\n", + "\n", + "## Arrays工具类\n", + "\n", + "您已经看到了 **java.util.Arrays** 中的 **fill()** 和 **setAll()/parallelSetAll()** 。该类包含许多其他有用的 **静态** 程序方法,我们将对此进行研究。\n", + "\n", + "概述:\n", + "\n", + "- **asList()**: 获取任何序列或数组,并将其转换为一个 **列表集合** (集合章节介绍了此方法)。\n", + "\n", + "- **copyOf()**:以新的长度创建现有数组的新副本。\n", + "\n", + "- **copyOfRange()**:创建现有数组的一部分的新副本。\n", + "\n", + "- **equals()**:比较两个数组是否相等。\n", + "\n", + "- **deepEquals()**:多维数组的相等性比较。\n", + "\n", + "- **stream()**:生成数组元素的流。\n", + "\n", + "- **hashCode()**:生成数组的哈希值(您将在附录中了解这意味着什么:理解equals()和hashCode())。\n", + "\n", + "- **deepHashCode()**: 多维数组的哈希值。\n", + "\n", + "- **sort()**:排序数组\n", + "\n", + "- **parallelSort()**:对数组进行并行排序,以提高速度。\n", + "\n", + "- **binarySearch()**:在已排序的数组中查找元素。\n", + "\n", + "- **parallelPrefix()**:使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。\n", + "\n", + "- **spliterator()**:从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。\n", + "\n", + "- **toString()**:为数组生成一个字符串表示。你在整个章节中经常看到这种用法。\n", + "\n", + "- **deepToString()**:为多维数组生成一个字符串。你在整个章节中经常看到这种用法。对于所有基本类型和对象,所有这些方法都是重载的。\n", + "\n", + "\n", + "## 数组拷贝\n", + "\n", + "与使用for循环手工执行复制相比,**copyOf()** 和 **copyOfRange()** 复制数组要快得多。这些方法被重载以处理所有类型。\n", + "\n", + "我们从复制 **int** 和 **Integer** 数组开始:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ArrayCopying.java\n", + "// Demonstrate Arrays.copy() and Arrays.copyOf()\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "class Sup {\n", + " // Superclass\n", + " private int id;\n", + "\n", + " Sup(int n) {\n", + " id = n;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName() + id;\n", + " }\n", + "}\n", + "\n", + "class Sub extends Sup { // Subclass\n", + "\n", + " Sub(int n) {\n", + " super(n);\n", + " }\n", + "}\n", + "\n", + "public class ArrayCopying {\n", + " public static final int SZ = 15;\n", + "\n", + " public static void main(String[] args) {\n", + " int[] a1 = new int[SZ];\n", + " Arrays.setAll(a1, new Count.Integer()::get);\n", + " show(\"a1\", a1);\n", + " int[] a2 = Arrays.copyOf(a1, a1.length); // [1]\n", + " // Prove they are distinct arrays:\n", + " Arrays.fill(a1, 1);\n", + " show(\"a1\", a1);\n", + " show(\"a2\", a2);\n", + " // Create a shorter result:\n", + " a2 = Arrays.copyOf(a2, a2.length / 2); // [2]\n", + " show(\"a2\", a2);\n", + " // Allocate more space:\n", + " a2 = Arrays.copyOf(a2, a2.length + 5);\n", + " show(\"a2\", a2);\n", + " // Also copies wrapped arrays:\n", + " Integer[] a3 = new Integer[SZ]; // [3]\n", + " Arrays.setAll(a3, new Count.Integer()::get);\n", + " Integer[] a4 = Arrays.copyOfRange(a3, 4, 12);\n", + " show(\"a4\", a4);\n", + " Sub[] d = new Sub[SZ / 2];\n", + " Arrays.setAll(d, Sub::new); // Produce Sup[] from Sub[]:\n", + " Sup[] b = Arrays.copyOf(d, d.length, Sup[].class); // [4]\n", + " show(b); // This \"downcast\" works fine:\n", + " Sub[] d2 = Arrays.copyOf(b, b.length, Sub[].class); // [5]\n", + " show(d2); // Bad \"downcast\" compiles but throws exception:\n", + " Sup[] b2 = new Sup[SZ / 2];\n", + " Arrays.setAll(b2, Sup::new);\n", + " try {\n", + " Sub[] d3 = Arrays.copyOf(b2, b2.length, Sub[].class); // [6]\n", + " } catch (Exception e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}\n", + "/* Output: a1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] a1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", + " a2:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]a2:[0, 1, 2, 3, 4, 5, 6]a2:[\n", + " 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]a4:[4, 5, 6, 7, 8, 9, 10, 11][Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6][\n", + " Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6]java.lang.ArrayStoreException */\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[1] 这是复制的基本方法;只需给出返回的复制数组的大小。这对于编写需要调整存储大小的算法很有帮助。复制之后,我们把a1的所有元素都设为1,以证明a1的变化不会影响a2中的任何东西。\n", + "\n", + "[2] 通过更改最后一个参数,我们可以缩短或延长返回的复制数组。\n", + "\n", + "[3] **copyOf()** 和 **copyOfRange()** 也可以使用包装类型。**copyOfRange()** 需要一个开始和结束索引。\n", + "\n", + "[4] **copyOf()** 和 **copyOfRange()** 都有一个版本,该版本通过在方法调用的末尾添加目标类型来创建不同类型的数组。我首先想到的是,这可能是一种从原生数组生成包装数组的方法,反之亦然。\n", + "但这没用。它的实际用途是“向上转换”和“向下转换”数组。也就是说,如果您有一个子类型(派生类型)的数组,而您想要一个基类型的数组,那么这些方法将生成所需的数组。\n", + "\n", + "[5] 您甚至可以成功地“向下强制转换”,并从超类型的数组生成子类型的数组。这个版本运行良好,因为我们只是“upcast”。\n", + "\n", + "[6] 这个“数组转换”将编译,但是如果类型不兼容,您将得到一个运行时异常。在这里,强制将基类型转换为派生类型是非法的,因为派生对象中可能有基对象中没有的属性和方法。\n", + "\n", + "实例表明,原生数组和对象数组都可以被复制。但是,如果复制对象的数组,那么只复制引用—不复制对象本身。这称为浅拷贝(有关更多细节,请参阅附录:传递和返回对象)。\n", + "\n", + "还有一个方法 **System.arraycopy()** ,它将一个数组复制到另一个已经分配的数组中。这将不会执行自动装箱或自动卸载—两个数组必须是完全相同的类型。\n", + "\n", + "\n", + "## 数组比较\n", + "\n", + "**数组** 提供了 **equals()** 来比较一维数组,以及 **deepEquals()** 来比较多维数组。对于所有原生类型和对象,这些方法都是重载的。\n", + "\n", + "数组相等的含义:数组必须有相同数量的元素,并且每个元素必须与另一个数组中的对应元素相等,对每个元素使用 **equals()**(对于原生类型,使用原生类型的包装类的 **equals()** 方法;例如,int的Integer.equals()。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ComparingArrays.java\n", + "// Using Arrays.equals()\n", + "\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class ComparingArrays {\n", + " public static final int SZ = 15;\n", + "\n", + " static String[][] twoDArray() {\n", + " String[][] md = new String[5][];\n", + " Arrays.setAll(md, n -> new String[n]);\n", + " for (int i = 0; i < md.length; i++) Arrays.setAll(md[i], new Rand.String()::get);\n", + " return md;\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " int[] a1 = new int[SZ], a2 = new int[SZ];\n", + " Arrays.setAll(a1, new Count.Integer()::get);\n", + " Arrays.setAll(a2, new Count.Integer()::get);\n", + " System.out.println(\"a1 == a2: \" + Arrays.equals(a1, a2));\n", + " a2[3] = 11;\n", + " System.out.println(\"a1 == a2: \" + Arrays.equals(a1, a2));\n", + " Integer[] a1w = new Integer[SZ], a2w = new Integer[SZ];\n", + " Arrays.setAll(a1w, new Count.Integer()::get);\n", + " Arrays.setAll(a2w, new Count.Integer()::get);\n", + " System.out.println(\"a1w == a2w: \" + Arrays.equals(a1w, a2w));\n", + " a2w[3] = 11;\n", + " System.out.println(\"a1w == a2w: \" + Arrays.equals(a1w, a2w));\n", + " String[][] md1 = twoDArray(), md2 = twoDArray();\n", + " System.out.println(Arrays.deepToString(md1));\n", + " System.out.println(\"deepEquals(md1, md2): \" + Arrays.deepEquals(md1, md2));\n", + " System.out.println(\"md1 == md2: \" + Arrays.equals(md1, md2));\n", + " md1[4][1] = \"#$#$#$#\";\n", + " System.out.println(Arrays.deepToString(md1));\n", + " System.out.println(\"deepEquals(md1, md2): \" + Arrays.deepEquals(md1, md2));\n", + " }\n", + "}\n", + "\n", + "/* Output:\n", + "a1 == a2: true\n", + "a1 == a2: false\n", + "a1w == a2w: true\n", + "a1w == a2w: false\n", + "[[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv,\n", + " gmeinne], [btpenpc, cuxszgv, gmeinne, eloztdv]]\n", + " deepEquals(md1, md2): true\n", + " md1 == md2: false\n", + " [[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv,\n", + " gmeinne], [btpenpc, #$#$#$#, gmeinne, eloztdv]]\n", + " deepEquals(md1, md2): false\n", + " */" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最初,a1和a2是完全相等的,所以输出是true,但是之后其中一个元素改变了,这使得结果为false。a1w和a2w是对一个封装类型数组重复该练习。\n", + "\n", + "**md1** 和 **md2** 是通过 **twoDArray()** 以相同方式初始化的多维字符串数组。注意,**deepEquals()** 返回 **true**,因为它执行了适当的比较,而普通的 **equals()** 错误地返回 **false**。如果我们更改数组中的一个元素,**deepEquals()** 将检测它。\n", + "\n", + "\n", + "## 流和数组\n", + "\n", + "**stream()** 方法很容易从某些类型的数组中生成元素流。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/StreamFromArray.java\n", + "\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class StreamFromArray {\n", + " public static void main(String[] args) {\n", + " String[] s = new Rand.String().array(10);\n", + " Arrays.stream(s).skip(3).limit(5).map(ss -> ss + \"!\").forEach(System.out::println);\n", + " int[] ia = new Rand.Pint().array(10);\n", + " Arrays.stream(ia).skip(3).limit(5)\n", + " .map(i -> i * 10).forEach(System.out::println);\n", + " Arrays.stream(new long[10]);\n", + " Arrays.stream(new double[10]);\n", + " // Only int, long and double work:\n", + " // - Arrays.stream(new boolean[10]);\n", + " // - Arrays.stream(new byte[10]);\n", + " // - Arrays.stream(new char[10]);\n", + " // - Arrays.stream(new short[10]);\n", + " // - Arrays.stream(new float[10]);\n", + " // For the other types you must use wrapped arrays:\n", + " float[] fa = new Rand.Pfloat().array(10);\n", + " Arrays.stream(ConvertTo.boxed(fa));\n", + " Arrays.stream(new Rand.Float().array(10));\n", + " }\n", + "}\n", + "/* Output:\n", + " eloztdv!\n", + " ewcippc!\n", + " ygpoalk!\n", + " ljlbynx!\n", + " taprwxz!\n", + " 47200\n", + " 61770\n", + " 84790\n", + " 66560\n", + " 37680\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只有“原生类型” **int**、**long** 和 **double** 可以与 **Arrays.stream()** 一起使用;对于其他的,您必须以某种方式获得一个包装类型的数组。\n", + "\n", + "通常,将数组转换为流来生成所需的结果要比直接操作数组容易得多。请注意,即使流已经“用完”(您不能重复使用它),您仍然拥有该数组,因此您可以以其他方式使用它----包括生成另一个流。\n", + "\n", + "\n", + "## 数组排序\n", + "\n", + "根据对象的实际类型执行比较排序。一种方法是为不同的类型编写对应的排序方法,但是这样的代码不能复用。\n", + "\n", + "编程设计的一个主要目标是“将易变的元素与稳定的元素分开”,在这里,保持不变的代码是一般的排序算法,但是变化的是对象的比较方式。因此,使用策略设计模式而不是将比较代码放入许多不同的排序源码中。使用策略模式时,变化的代码部分被封装在一个单独的类(策略对象)中。\n", + "\n", + "您将一个策略对象交给相同的代码,该代码使用策略模式来实现其算法。通过这种方式,您将使用相同的排序代码,使不同的对象表达不同的比较方式。\n", + "\n", + "Java有两种方式提供比较功能。第一种方法是通过实现 **java.lang.Comparable** 接口的原生方法。这是一个简单的接口,只含有一个方法 **compareTo()**。该方法接受另一个与参数类型相同的对象作为参数,如果当前对象小于参数,则产生一个负值;如果参数相等,则产生零值;如果当前对象大于参数,则产生一个正值。\n", + "\n", + "这里有一个类,它实现了 **Comparable** 接口并演示了可比性,而且使用Java标准库方法 **Arrays.sort()**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/CompType.java\n", + "// Implementing Comparable in a class\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "import java.util.SplittableRandom;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class CompType implements Comparable {\n", + " private static int count = 1;\n", + " private static SplittableRandom r = new SplittableRandom(47);\n", + " int i;\n", + " int j;\n", + "\n", + " public CompType(int n1, int n2) {\n", + " i = n1;\n", + " j = n2;\n", + " }\n", + "\n", + " public static CompType get() {\n", + " return new CompType(r.nextInt(100), r.nextInt(100));\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " CompType[] a = new CompType[12];\n", + " Arrays.setAll(a, n -> get());\n", + " show(\"Before sorting\", a);\n", + " Arrays.sort(a);\n", + " show(\"After sorting\", a);\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " String result = \"[i = \" + i + \", j = \" + j + \"]\";\n", + " if (count++ % 3 == 0) result += \"\\n\";\n", + " return result;\n", + " }\n", + "\n", + " @Override\n", + " public int compareTo(CompType rv) {\n", + " return (i < rv.i ? -1 : (i == rv.i ? 0 : 1));\n", + " }\n", + "}\n", + "/* Output:\n", + "Before sorting: [[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] ,\n", + " [i = 56, j = 68], [i = 48, j = 93],\n", + " [i = 70, j = 7] , [i = 0, j = 25],\n", + " [i = 62, j = 34], [i = 50, j = 82] ,\n", + " [i = 31, j = 67], [i = 66, j = 54],\n", + " [i = 21, j = 6] ]\n", + "After sorting: [[i = 0, j = 25], [i = 21, j = 6], [i = 31, j = 67] ,\n", + " [i = 35, j = 37], [i = 41, j = 20], [i = 48, j = 93] ,\n", + " [i = 50, j = 82], [i = 56, j = 68], [i = 62, j = 34] ,\n", + " [i = 66, j = 54], [i = 70, j = 7], [i = 77, j = 79] ]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当您定义比较方法时,您有责任决定将一个对象与另一个对象进行比较意味着什么。这里,在比较中只使用i值和j值\n", + "将被忽略。\n", + "\n", + "**get()** 方法通过使用随机值初始化CompType对象来构建它们。在 **main()** 中,**get()** 与 **Arrays.setAll()** 一起使用,以填充一个 **CompType类型** 数组,然后对其排序。如果没有实现 **Comparable接口**,那么当您试图调用 **sort()** 时,您将在运行时获得一个 **ClassCastException** 。这是因为 **sort()** 将其参数转换为 **Comparable类型**。\n", + "\n", + "现在假设有人给了你一个没有实现 **Comparable接口** 的类,或者给了你一个实现 **Comparable接口** 的类,但是你不喜欢它的工作方式而愿意有一个不同的对于此类型的比较方法。为了解决这个问题,创建一个实现 **Comparator** 接口的单独的类(在集合一章中简要介绍)。它有两个方法,**compare()** 和 **equals()**。但是,除了特殊的性能需求外,您不需要实现 **equals()**,因为无论何时创建一个类,它都是隐式地继承自 **Object**,**Object** 有一个equals()。您可以只使用默认的 **Object equals()** 来满足接口的规范。\n", + "\n", + "集合类(注意复数;我们将在下一章节讨论它) 包含一个方法 **reverseOrder()**,它生成一个来 **Comparator**(比较器)反转自然排序顺序。这可以应用到比较对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/Reverse.java\n", + "// The Collections.reverseOrder() Comparator\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "import java.util.Collections;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class Reverse {\n", + " public static void main(String[] args) {\n", + " CompType[] a = new CompType[12];\n", + " Arrays.setAll(a, n -> CompType.get());\n", + " show(\"Before sorting\", a);\n", + " Arrays.sort(a, Collections.reverseOrder());\n", + " show(\"After sorting\", a);\n", + " }\n", + "}\n", + "/* Output:\n", + "Before sorting: [[i = 35, j = 37], [i = 41, j = 20],\n", + " [i = 77, j = 79] , [i = 56, j = 68],\n", + " [i = 48, j = 93], [i = 70, j = 7],\n", + " [i = 0, j = 25], [i = 62, j = 34],\n", + " [i = 50, j = 82] , [i = 31, j = 67],\n", + " [i = 66, j = 54], [i = 21, j = 6] ]\n", + "After sorting: [[i = 77, j = 79], [i = 70, j = 7],\n", + " [i = 66, j = 54] , [i = 62, j = 34],\n", + " [i = 56, j = 68], [i = 50, j = 82] ,\n", + " [i = 48, j = 93], [i = 41, j = 20],\n", + " [i = 35, j = 37] , [i = 31, j = 67],\n", + " [i = 21, j = 6], [i = 0, j = 25] ]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "您还可以编写自己的比较器。这个比较CompType对象基于它们的j值而不是它们的i值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ComparatorTest.java\n", + "// Implementing a Comparator for a class\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "import java.util.Comparator;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "class CompTypeComparator implements Comparator {\n", + " public int compare(CompType o1, CompType o2) {\n", + " return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1));\n", + " }\n", + "}\n", + "\n", + "public class ComparatorTest {\n", + " public static void main(String[] args) {\n", + " CompType[] a = new CompType[12];\n", + " Arrays.setAll(a, n -> CompType.get());\n", + " show(\"Before sorting\", a);\n", + " Arrays.sort(a, new CompTypeComparator());\n", + " show(\"After sorting\", a);\n", + " }\n", + "}\n", + "/* Output:\n", + "Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] ,\n", + " [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7] ,\n", + " [i = 0, j = 25], [i = 62, j = 34], [i = 50, j = 82],\n", + " [i = 31, j = 67], [i = 66, j = 54], [i = 21, j = 6] ]\n", + "After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] ,\n", + " [i = 0, j = 25], [i = 62, j = 34], [i = 35, j = 37] ,\n", + " [i = 66, j = 54], [i = 31, j = 67], [i = 56, j = 68] ,\n", + " [i = 77, j = 79], [i = 50, j = 82], [i = 48, j = 93] ]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Arrays.sort()的使用\n", + "\n", + "使用内置的排序方法,您可以对实现了 **Comparable** 接口或具有 **Comparator** 的任何对象数组 或 任何原生数组进行排序。这里我们生成一个随机字符串对象数组并对其排序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/StringSorting.java\n", + "// Sorting an array of Strings\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "import java.util.Collections;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class StringSorting {\n", + " public static void main(String[] args) {\n", + " String[] sa = new Rand.String().array(20);\n", + " show(\"Before sort\", sa);\n", + " Arrays.sort(sa);\n", + " show(\"After sort\", sa);\n", + " Arrays.sort(sa, Collections.reverseOrder());\n", + " show(\"Reverse sort\", sa);\n", + " Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);\n", + " show(\"Case-insensitive sort\", sa);\n", + " }\n", + "}\n", + "/* Output:\n", + "Before sort: [btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc,\n", + " ygpoalk, ljlbynx, taprwxz, bhmupju, cjwzmmr,\n", + " anmkkyh, fcjpthl, skddcat, jbvlgwc, mvducuj,\n", + " ydpulcq, zehpfmm, zrxmclh, qgekgly, hyoubzl]\n", + "\n", + "After sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv,\n", + " eloztdv, ewcippc, fcjpthl, gmeinne, hyoubzl,\n", + " jbvlgwc, ljlbynx, mvducuj, qgekgly, skddcat,\n", + " taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh]\n", + "\n", + "Reverse sort: [zrxmclh, zehpfmm, ygpoalk, ydpulcq,taprwxz,\n", + " skddcat, qgekgly, mvducuj, ljlbynx, jbvlgwc,\n", + " hyoubzl, gmeinne, fcjpthl, ewcippc, eloztdv,\n", + " cuxszgv, cjwzmmr, btpenpc, bhmupju, anmkkyh]\n", + "\n", + "Case-insensitive sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr,\n", + " cuxszgv, eloztdv, ewcippc, fcjpthl, gmeinne,\n", + " hyoubzl, jbvlgwc, ljlbynx, mvducuj, qgekgly,\n", + " skddcat, taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意字符串排序算法中的输出。它是字典式的,所以它把所有以大写字母开头的单词放在前面,然后是所有以小写字母开头的单词。(电话簿通常是这样分类的。)无论大小写,要将单词组合在一起,请使用 **String.CASE_INSENSITIVE_ORDER** ,如对sort()的最后一次调用所示。\n", + "\n", + "Java标准库中使用的排序算法被设计为最适合您正在排序的类型----原生类型的快速排序和对象的归并排序。\n", + "\n", + "\n", + "\n", + "## 并行排序\n", + "\n", + "如果排序性能是一个问题,那么可以使用 **Java 8 parallelSort()**,它为所有不可预见的情况(包括数组的排序区域或使用了比较器)提供了重载版本。为了查看相比于普通的sort(), **parallelSort()** 的优点,我们使用了用来验证代码时的 **JMH**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/jmh/ParallelSort.java\n", + "package arrays.jmh;\n", + "\n", + "import onjava.*;\n", + "import org.openjdk.jmh.annotations.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "@State(Scope.Thread)\n", + "public class ParallelSort {\n", + " private long[] la;\n", + "\n", + " @Setup\n", + " public void setup() {\n", + " la = new Rand.Plong().array(100_000);\n", + " }\n", + "\n", + " @Benchmark\n", + " public void sort() {\n", + " Arrays.sort(la);\n", + " }\n", + "\n", + " @Benchmark\n", + " public void parallelSort() {\n", + " Arrays.parallelSort(la);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**parallelSort()** 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 **Arrays .sort()** 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。\n", + "\n", + "您可能会看到不同的结果,但是在我的机器上,并行排序将速度提高了大约3倍。由于并行版本使用起来很简单,所以很容易考虑在任何地方使用它,而不是\n", + "**Arrays.sort ()**。当然,它可能不是那么简单—看看微基准测试。\n", + "\n", + "\n", + "\n", + "## binarySearch二分查找\n", + "\n", + "一旦数组被排序,您就可以通过使用 **Arrays.binarySearch()** 来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 **binarySearch()**,结果是不可预测的。下面的示例使用 **Rand.Pint** 类来创建一个填充随机整形值的数组,然后调用 **getAsInt()** (因为 **Rand.Pint** 是一个 **IntSupplier**)来产生搜索值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ArraySearching.java\n", + "// Using Arrays.binarySearch()\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class ArraySearching {\n", + " public static void main(String[] args) {\n", + " Rand.Pint rand = new Rand.Pint();\n", + " int[] a = new Rand.Pint().array(25);\n", + " Arrays.sort(a);\n", + " show(\"Sorted array\", a);\n", + " while (true) {\n", + " int r = rand.getAsInt();\n", + " int location = Arrays.binarySearch(a, r);\n", + " if (location >= 0) {\n", + " System.out.println(\"Location of \" + r + \" is \" + location + \", a[\" + location + \"] is \" + a[location]);\n", + " break; // Out of while loop\n", + " }\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "Sorted array: [125, 267, 635, 650, 1131, 1506, 1634, 2400, 2766,\n", + " 3063, 3768, 3941, 4720, 4762, 4948, 5070, 5682,\n", + " 5807, 6177, 6193, 6656, 7021, 8479, 8737, 9954]\n", + "Location of 635 is 2, a[2] is 635\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在while循环中,随机值作为搜索项生成,直到在数组中找到其中一个为止。\n", + "\n", + "如果找到了搜索项,**Arrays.binarySearch()** 将生成一个大于或等于零的值。否则,它将产生一个负值,表示如果手动维护已排序的数组,则应该插入元素的位置。产生的值是 -(插入点) - 1 。插入点是大于键的第一个元素的索引,如果数组中的所有元素都小于指定的键,则是 **a.size()** 。\n", + "\n", + "如果数组包含重复的元素,则无法保证找到其中的那些重复项。搜索算法不是为了支持重复的元素,而是为了容忍它们。如果需要没有重复元素的排序列表,可以使用 **TreeSet** (用于维持排序顺序)或 **LinkedHashSet** (用于维持插入顺序)。这些类自动为您处理所有的细节。只有在出现性能瓶颈的情况下,才应该使用手工维护的数组替换这些类中的一个。\n", + "\n", + "如果使用比较器(原语数组不允许使用比较器进行排序)对对象数组进行排序,那么在执行 **binarySearch()** (使用重载版本的binarySearch())时必须包含相同的比较器。例如,可以修改 **StringSorting.java** 来执行搜索:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/AlphabeticSearch.java\n", + "// Searching with a Comparator\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class AlphabeticSearch {\n", + " public static void main(String[] args) {\n", + " String[] sa = new Rand.String().array(30);\n", + " Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);\n", + " show(sa);\n", + " int index = Arrays.binarySearch(sa, sa[10], String.CASE_INSENSITIVE_ORDER);\n", + " System.out.println(\"Index: \" + index + \"\\n\" + sa[index]);\n", + " }\n", + "}\n", + "/* Output:\n", + "[anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv, eloztdv, ewcippc,\n", + "ezdeklu, fcjpthl, fqmlgsh, gmeinne, hyoubzl, jbvlgwc, jlxpqds,\n", + "ljlbynx, mvducuj, qgekgly, skddcat, taprwxz, uybypgp, vjsszkn,\n", + "vniyapk, vqqakbm, vwodhcf, ydpulcq, ygpoalk, yskvett, zehpfmm,\n", + "zofmmvm, zrxmclh]\n", + "Index: 10 gmeinne\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "比较器必须作为第三个参数传递给重载的 **binarySearch()** 。在本例中,成功是有保证的,因为搜索项是从数组本身中选择的。\n", + "\n", + "\n", + "## parallelPrefix并行前缀\n", + "\n", + "没有“prefix()”方法,只有 **parallelPrefix()**。这类似于 **Stream** 类中的 **reduce()** 方法:它对前一个元素和当前元素执行一个操作,并将结果放入当前元素位置:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ParallelPrefix1.java\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class ParallelPrefix1 {\n", + " public static void main(String[] args) {\n", + " int[] nums = new Count.Pint().array(10);\n", + " show(nums);\n", + " System.out.println(Arrays.stream(nums).reduce(Integer::sum).getAsInt());\n", + " Arrays.parallelPrefix(nums, Integer::sum);\n", + " show(nums);\n", + " System.out.println(Arrays.stream(new Count.Pint().array(6)).reduce(Integer::sum).getAsInt());\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "45\n", + "[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]\n", + "15\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里我们对数组应用Integer::sum。在位置0中,它将先前计算的值(因为没有先前的值)与原始数组位置0中的值组合在一起。在位置1中,它获取之前计算的值(它只是存储在位置0中),并将其与位置1中先前计算的值相结合。依次往复。\n", + "\n", + "使用 **Stream.reduce()**,您只能得到最终结果,而使用 **Arrays.parallelPrefix()**,您还可以得到所有中间计算,以确保它们是有用的。注意,第二个 **Stream.reduce()** 计算的结果已经在 **parallelPrefix()** 计算的数组中。\n", + "\n", + "使用字符串可能更清楚:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ParallelPrefix2.java\n", + "\n", + "import onjava.*;\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "import static onjava.ArrayShow.*;\n", + "\n", + "public class ParallelPrefix2 {\n", + " public static void main(String[] args) {\n", + " String[] strings = new Rand.String(1).array(8);\n", + " show(strings);\n", + " Arrays.parallelPrefix(strings, (a, b) -> a + b);\n", + " show(strings);\n", + " }\n", + "}\n", + "/* Output:\n", + "[b, t, p, e, n, p, c, c]\n", + "[b, bt, btp, btpe, btpen, btpenp, btpenpc, btpenpcc]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如前所述,使用流进行初始化非常优雅,但是对于大型数组,这种方法可能会耗尽堆空间。使用 **setAll()** 执行初始化更节省内存:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "JAVA" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// arrays/ParallelPrefix3.java\n", + "// {ExcludeFromTravisCI}\n", + "\n", + "import java.util.Arrays;\n", + "\n", + "public class ParallelPrefix3 {\n", + " static final int SIZE = 10_000_000;\n", + "\n", + " public static void main(String[] args) {\n", + " long[] nums = new long[SIZE];\n", + " Arrays.setAll(nums, n -> n);\n", + " Arrays.parallelPrefix(nums, Long::sum);\n", + " System.out.println(\"First 20: \" + nums[19]);\n", + " System.out.println(\"First 200: \" + nums[199]);\n", + " System.out.println(\"All: \" + nums[nums.length - 1]);\n", + " }\n", + "}\n", + "/* Output:\n", + "First 20: 190\n", + "First 200: 19900\n", + "All: 49999995000000\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为正确使用 **parallelPrefix()** 可能相当复杂,所以通常应该只在存在内存或速度问题(或两者都有)时使用。否则,**Stream.reduce()** 应该是您的首选。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "Java为固定大小的低级数组提供了合理的支持。这种数组强调的是性能而不是灵活性,就像C和c++数组模型一样。在Java的最初版本中,固定大小的低级数组是绝对必要的,这不仅是因为Java设计人员选择包含原生类型(也考虑到性能),还因为那个版本对集合的支持非常少。因此,在早期的Java版本中,选择数组总是合理的。\n", + "\n", + "在Java的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组,即使这样,集合的性能也得到了显著的改进。正如本书其他部分所述,无论如何,性能问题通常不会出现在您设想的地方。\n", + "\n", + "使用自动装箱和泛型,在集合中保存原生类型是毫不费力的,这进一步鼓励您用集合替换低级数组。由于泛型产生类型安全的集合,数组在这方面也不再有优势。\n", + "\n", + "如本章所述,当您尝试使用泛型时,您将看到泛型对数组是相当不友好的。通常,即使可以让泛型和数组以某种形式一起工作(在下一章中您将看到),在编译期间仍然会出现“unchecked”警告。\n", + "\n", + "有几次,当我们讨论特定的例子时,我直接从Java语言设计人员那里听到我应该使用集合而不是数组(我使用数组来演示特定的技术,所以我没有这个选项)。\n", + "\n", + "所有这些问题都表明,在使用Java的最新版本进行编程时,应该“优先选择集合而不是数组”。只有当您证明性能是一个问题(并且切换到一个数组实际上会有很大的不同)时,才应该重构到数组。这是一个相当大胆的声明,但是有些语言根本没有固定大小的低级数组。它们只有可调整大小的集合,而且比C/C++/java风格的数组功能多得多。例如,Python有一个使用基本数组语法的列表类型,但是具有更大的功能—您甚至可以从它继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Python" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "# arrays/PythonLists.py\n", + "\n", + "aList=[1,2,3,4,5]print(type(aList)) #\n", + "print(aList) # [1,2,3,4,5]\n", + " print(aList[4]) # 5Basic list indexing\n", + " aList.append(6) # lists can be resized\n", + " aList+=[7,8] # Add a list to a list\n", + " print(aList) # [1,2,3,4,5,6,7,8]\n", + " aSlice=aList[2:4]\n", + " print(aSlice) # [3,4]\n", + "\n", + "class MyList(list): # Inherit from list\n", + " # Define a method;'this'pointer is explicit:\n", + " def getReversed(self):\n", + " reversed=self[:] # Copy list using slices\n", + " reversed.reverse() # Built-in list method\n", + " return reversed\n", + " # No'new'necessary for object creation:\n", + " list2=MyList(aList)\n", + " print(type(list2)) #\n", + " print(list2.getReversed()) # [8,7,6,5,4,3,2,1]\n", + " output=\"\"\"\n", + " \n", + " [1, 2, 3, 4, 5]\n", + " 5\n", + " [1, 2, 3, 4, 5, 6, 7, 8]\n", + " [3, 4]\n", + " \n", + " [8, 7, 6, 5, 4, 3, 2, 1]\n", + " \"\"\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "前一章介绍了基本的Python语法。在这里,通过用方括号包围以逗号分隔的对象序列来创建列表。结果是一个运行时类型为list的对象(print语句的输出显示为同一行中的注释)。打印列表的结果与在Java中使用Arrays.toString()的结果相同。\n", + "通过将 : 操作符放在索引操作中,通过切片来创建列表的子序列。list类型有更多的内置操作,通常只需要序列类型。\n", + "MyList是一个类定义;基类放在括号内。在类内部,def语句生成方法,该方法的第一个参数在Java中自动与之等价,除了在Python中它是显式的,而且标识符self是按约定使用的(它不是关键字)。注意构造函数是如何自动继承的。\n", + "\n", + "虽然一切在Python中真的是一个对象(包括整数和浮点类型),你仍然有一个安全门,因为你可以优化性能关键型的部分代码编写扩展的C, c++或使用特殊的工具设计容易加速您的Python代码(有很多)。通过这种方式,可以在不影响性能改进的情况下保持对象的纯度。\n", + "\n", + "PHP甚至更进一步,它只有一个数组类型,既充当int索引数组,又充当关联数组(Map)。\n", + "\n", + "在经历了这么多年的Java发展之后,我们可以很有趣地推测,如果重新开始,设计人员是否会将原生类型和低级数组放在该语言中(同样在JVM上运行的Scala语言不包括这些)。如果不考虑这些,就有可能开发出一种真正纯粹的面向对象语言(尽管有这样的说法,Java并不是一种纯粹的面向对象语言,这正是因为它的底层缺陷)。关于效率的最初争论总是令人信服的,但是随着时间的推移,我们已经看到了从这个想法向更高层次的组件(如集合)的演进。此外,如果集合可以像在某些语言中一样构建到核心语言中,那么编译器就有更好的机会进行优化。\n", + "\n", + "撇开““Green-fields”的推测不谈,我们肯定会被数组所困扰,当你阅读代码时就会看到它们。然而,集合几乎总是更好的选择。\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/22-Enumerations.ipynb b/jupyter/22-Enumerations.ipynb new file mode 100644 index 00000000..feba10e5 --- /dev/null +++ b/jupyter/22-Enumerations.ipynb @@ -0,0 +1,3076 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第二十二章 枚举\n", + "\n", + "> 关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能\n", + "\n", + "在[初始化和清理 ]() 这章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对 Java 已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用 enum 可以做很多有趣的事情,同时,我们也会深入其他的 Java 特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式。\n", + "\n", + "\n", + "\n", + "## 基本 enum 特性\n", + "\n", + "我们已经在[初始化和清理 ]() 这章章看到,调用 enum 的 values() 方法,可以遍历 enum 实例 .values() 方法返回 enum 实例的数组,而且该数组中的元素严格保持其在 enum 中声明时的顺序,因此你可以在循环中使用 values() 返回的数组。\n", + "\n", + "创建 enum 时,编译器会为你生成一个相关的类,这个类继承自 Java.lang.Enum。下面的例子演示了 Enum 提供的一些功能:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/EnumClass.java\n", + "// Capabilities of the Enum class\n", + "enum Shrubbery { GROUND, CRAWLING, HANGING }\n", + "public class EnumClass {\n", + " public static void main(String[] args) {\n", + " for(Shrubbery s : Shrubbery.values()) {\n", + " System.out.println(\n", + " s + \" ordinal: \" + s.ordinal());\n", + " System.out.print(\n", + " s.compareTo(Shrubbery.CRAWLING) + \" \");\n", + " System.out.print(\n", + " s.equals(Shrubbery.CRAWLING) + \" \");\n", + " System.out.println(s == Shrubbery.CRAWLING);\n", + " System.out.println(s.getDeclaringClass());\n", + " System.out.println(s.name());\n", + " System.out.println(\"********************\");\n", + " }\n", + "// Produce an enum value from a String name:\n", + " for(String s :\n", + " \"HANGING CRAWLING GROUND\".split(\" \")) {\n", + " Shrubbery shrub =\n", + " Enum.valueOf(Shrubbery.class, s);\n", + " System.out.println(shrub);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "GROUND ordinal: 0\n", + "-1 false false\n", + "class Shrubbery\n", + "GROUND\n", + "********************\n", + "CRAWLING ordinal: 1\n", + "0 true true\n", + "class Shrubbery\n", + "CRAWLING\n", + "********************\n", + "HANGING ordinal: 2\n", + "1 false false\n", + "class Shrubbery\n", + "HANGING\n", + "********************\n", + "HANGING\n", + "CRAWLING\n", + "GROUND" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ordinal() 方法返回一个 int 值,这是每个 enum 实例在声明时的次序,从 0 开始。可以使用==来比较 enum 实例,编译器会自动为你提供 equals() 和 hashCode() 方法。Enum 类实现了 Comparable 接口,所以它具有 compareTo() 方法。同时,它还实现了 Serializable 接口。\n", + "\n", + "如果在 enum 实例上调用 getDeclaringClass() 方法,我们就能知道其所属的 enum 类。\n", + "\n", + "name() 方法返回 enum 实例声明时的名字,这与使用 toString() 方法效果相同。valueOf() 是在 Enum 中定义的 static 方法,它根据给定的名字返回相应的 enum 实例,如果不存在给定名字的实例,将会抛出异常。\n", + "\n", + "### 将静态类型导入用于 enum\n", + "\n", + "先看一看 [初始化和清理 ]() 这章中 Burrito.java 的另一个版本:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/SpicinessEnum.java\n", + "package enums;\n", + "public enum SpicinessEnum {\n", + " NOT, MILD, MEDIUM, HOT, FLAMING\n", + "}\n", + "// enums/Burrito2.java\n", + "// {java enums.Burrito2}\n", + "package enums;\n", + "import static enums.SpicinessEnum.*;\n", + "public class Burrito2 {\n", + " SpicinessEnum degree;\n", + " public Burrito2(SpicinessEnum degree) {\n", + " this.degree = degree;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"Burrito is \"+ degree;\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(new Burrito2(NOT));\n", + " System.out.println(new Burrito2(MEDIUM));\n", + " System.out.println(new Burrito2(HOT));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Burrito is NOT\n", + "Burrito is MEDIUM\n", + "Burrito is HOT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 static import 能够将 enum 实例的标识符带入当前的命名空间,所以无需再用 enum 类型来修饰 enum 实例。这是一个好的想法吗?或者还是显式地修饰 enum 实例更好?这要看代码的复杂程度了。编译器可以确保你使用的是正确的类型,所以唯一需要担心的是,使用静态导入会不会导致你的代码令人难以理解。多数情况下,使用 static import 还是有好处的,不过,程序员还是应该对具体情况进行具体分析。\n", + "\n", + "注意,在定义 enum 的同一个文件中,这种技巧无法使用,如果是在默认包中定义 enum,这种技巧也无法使用(在 Sun 内部对这一点显然也有不同意见)。\n", + "\n", + "\n", + "\n", + "## 方法添加\n", + "\n", + "除了不能继承自一个 enum 之外,我们基本上可以将 enum 看作一个常规的类。也就是说我们可以向 enum 中添加方法。enum 甚至可以有 main() 方法。\n", + "\n", + "一般来说,我们希望每个枚举实例能够返回对自身的描述,而不仅仅只是默认的 toString() 实现,这只能返回枚举实例的名字。为此,你可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。看一看下面的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/OzWitch.java\n", + "// The witches in the land of Oz\n", + "public enum OzWitch {\n", + " // Instances must be defined first, before methods:\n", + " WEST(\"Miss Gulch, aka the Wicked Witch of the West\"),\n", + " NORTH(\"Glinda, the Good Witch of the North\"),\n", + " EAST(\"Wicked Witch of the East, wearer of the Ruby \" +\n", + " \"Slippers, crushed by Dorothy's house\"),\n", + " SOUTH(\"Good by inference, but missing\");\n", + " private String description;\n", + " // Constructor must be package or private access:\n", + " private OzWitch(String description) {\n", + " this.description = description;\n", + " }\n", + " public String getDescription() { return description; }\n", + " public static void main(String[] args) {\n", + " for(OzWitch witch : OzWitch.values())\n", + " System.out.println(\n", + " witch + \": \" + witch.getDescription());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "WEST: Miss Gulch, aka the Wicked Witch of the West\n", + "NORTH: Glinda, the Good Witch of the North\n", + "EAST: Wicked Witch of the East, wearer of the Ruby\n", + "Slippers, crushed by Dorothy's house\n", + "SOUTH: Good by inference, but missing" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,如果你打算定义自己的方法,那么必须在 enum 实例序列的最后添加一个分号。同时,Java 要求你必须先定义 enum 实例。如果在定义 enum 实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。\n", + "\n", + "enum 中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum 就是一个普通的类。所以,我们可以使用 enum 做许多事情(虽然,我们一般只使用普通的枚举类型)\n", + "\n", + "在这个例子中,虽然我们有意识地将 enum 的构造器声明为 private,但对于它的可访问性而言,其实并没有什么变化,因为(即使不声明为 private)我们只能在 enum 定义的内部使用其构造器创建 enum 实例。一旦 enum 的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。\n", + "\n", + "### 覆盖 enum 的方法\n", + "\n", + "覆盖 toSring() 方法,给我们提供了另一种方式来为枚举实例生成不同的字符串描述信息。\n", + "在下面的示例中,我们使用的就是实例的名字,不过我们希望改变其格式。覆盖 enum 的 toSring() 方法与覆盖一般类的方法没有区别:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/SpaceShip.java\n", + "import java.util.stream.*;\n", + "public enum SpaceShip {\n", + " SCOUT, CARGO, TRANSPORT,\n", + " CRUISER, BATTLESHIP, MOTHERSHIP;\n", + " @Override\n", + " public String toString() {\n", + " String id = name();\n", + " String lower = id.substring(1).toLowerCase();\n", + " return id.charAt(0) + lower;\n", + " }\n", + " public static void main(String[] args) {\n", + " Stream.of(values())\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Scout\n", + "Cargo\n", + "Transport\n", + "Cruiser\n", + "Battleship\n", + "Mothership" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "toString() 方法通过调用 name() 方法取得 SpaceShip 的名字,然后将其修改为只有首字母大写的格式。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## switch 语句中的 enum\n", + "\n", + "在 switch 中使用 enum,是 enum 提供的一项非常便利的功能。一般来说,在 switch 中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过 ordinal() 方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在 switch 语句中使用 enum。\n", + "\n", + "虽然一般情况下我们必须使用 enum 类型来修饰一个 enum 实例,但是在 case 语句中却不必如此。下面的例子使用 enum 构造了一个小型状态机:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/TrafficLight.java\n", + "// Enums in switch statements\n", + "// Define an enum type:\n", + "enum Signal { GREEN, YELLOW, RED, }\n", + "\n", + "public class TrafficLight {\n", + " Signal color = Signal.RED;\n", + " public void change() {\n", + " switch(color) {\n", + " // Note you don't have to say Signal.RED\n", + " // in the case statement:\n", + " case RED: color = Signal.GREEN;\n", + " break;\n", + " case GREEN: color = Signal.YELLOW;\n", + " break;\n", + " case YELLOW: color = Signal.RED;\n", + " break;\n", + " }\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"The traffic light is \" + color;\n", + " }\n", + " public static void main(String[] args) {\n", + " TrafficLight t = new TrafficLight();\n", + " for(int i = 0; i < 7; i++) {\n", + " System.out.println(t);\n", + " t.change();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "The traffic light is RED\n", + "The traffic light is GREEN\n", + "The traffic light is YELLOW\n", + "The traffic light is RED\n", + "The traffic light is GREEN\n", + "The traffic light is YELLOW\n", + "The traffic light is RED" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "编译器并没有抱怨 switch 中没有 default 语句,但这并不是因为每一个 Signal 都有对应的 case 语句。如果你注释掉其中的某个 case 语句,编译器同样不会抱怨什么。这意味着,你必须确保自己覆盖了所有的分支。但是,如果在 case 语句中调用 return,那么编译器就会抱怨缺少 default 语句了。这与是否覆盖了 enum 的所有实例无关。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## values 方法的神秘之处\n", + "\n", + "前面已经提到,编译器为你创建的 enum 类都继承自 Enum 类。然而,如果你研究一下 Enum 类就会发现,它并没有 values() 方法。可我们明明已经用过该方法了,难道存在某种“隐藏的”方法吗?我们可以利用反射机制编写一个简单的程序,来查看其中的究竟:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/Reflection.java\n", + "// Analyzing enums using reflection\n", + "import java.lang.reflect.*;\n", + "import java.util.*;\n", + "import onjava.*;\n", + "enum Explore { HERE, THERE }\n", + "public class Reflection {\n", + " public static\n", + " Set analyze(Class enumClass) {\n", + " System.out.println(\n", + " \"_____ Analyzing \" + enumClass + \" _____\");\n", + " System.out.println(\"Interfaces:\");\n", + " for(Type t : enumClass.getGenericInterfaces())\n", + " System.out.println(t);\n", + " System.out.println(\n", + " \"Base: \" + enumClass.getSuperclass());\n", + " System.out.println(\"Methods: \");\n", + " Set methods = new TreeSet<>();\n", + " for(Method m : enumClass.getMethods())\n", + " methods.add(m.getName());\n", + " System.out.println(methods);\n", + " return methods;\n", + " }\n", + " public static void main(String[] args) {\n", + " Set exploreMethods =\n", + " analyze(Explore.class);\n", + " Set enumMethods = analyze(Enum.class);\n", + " System.out.println(\n", + " \"Explore.containsAll(Enum)? \" +\n", + " exploreMethods.containsAll(enumMethods));\n", + " System.out.print(\"Explore.removeAll(Enum): \");\n", + " exploreMethods.removeAll(enumMethods);\n", + " System.out.println(exploreMethods);\n", + "// Decompile the code for the enum:\n", + " OSExecute.command(\n", + " \"javap -cp build/classes/main Explore\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "_____ Analyzing class Explore _____\n", + "Interfaces:\n", + "Base: class java.lang.Enum\n", + "Methods:\n", + "[compareTo, equals, getClass, getDeclaringClass,\n", + "hashCode, name, notify, notifyAll, ordinal, toString,\n", + "valueOf, values, wait]\n", + "_____ Analyzing class java.lang.Enum _____\n", + "Interfaces:\n", + "java.lang.Comparable\n", + "interface java.io.Serializable\n", + "Base: class java.lang.Object\n", + "Methods:\n", + "[compareTo, equals, getClass, getDeclaringClass,\n", + "hashCode, name, notify, notifyAll, ordinal, toString,\n", + "valueOf, wait]\n", + "Explore.containsAll(Enum)? true\n", + "Explore.removeAll(Enum): [values]\n", + "Compiled from \"Reflection.java\"\n", + "final class Explore extends java.lang.Enum {\n", + " public static final Explore HERE;\n", + " public static final Explore THERE;\n", + " public static Explore[] values();\n", + " public static Explore valueOf(java.lang.String);\n", + " static {};\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "答案是,values() 是由编译器添加的 static 方法。可以看出,在创建 Explore 的过程中,编译器还为其添加了 valueOf() 方法。这可能有点令人迷惑,Enum 类不是已经有 valueOf() 方法了吗。\n", + "\n", + "不过 Enum 中的 valueOf() 方法需要两个参数,而这个新增的方法只需一个参数。由于这里使用的 Set 只存储方法的名字,而不考虑方法的签名,所以在调用 Explore.removeAll(Enum) 之后,就只剩下[values] 了。\n", + "\n", + "从最后的输出中可以看到,编译器将 Explore 标记为 final 类,所以无法继承自 enum,其中还有一个 static 的初始化子句,稍后我们将学习如何重定义该句。\n", + "\n", + "由于擦除效应(在[泛型 ]() 章节中介绍过),反编译无法得到 Enum 的完整信息,所以它展示的 Explore 的父类只是一个原始的 Enum,而非事实上的 Enum\\。\n", + "\n", + "由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants0 方法,所以即便 Enum 接口中没有 values0 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/UpcastEnum.java\n", + "// No values() method if you upcast an enum\n", + "enum Search { HITHER, YON }\n", + "public class UpcastEnum {\n", + " public static void main(String[] args) {\n", + " Search[] vals = Search.values();\n", + " Enum e = Search.HITHER; // Upcast\n", + "// e.values(); // No values() in Enum\n", + " for(Enum en : e.getClass().getEnumConstants())\n", + " System.out.println(en);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "HITHER\n", + "YON" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 getEnumConstants() 是 Class 上的方法,所以你甚至可以对不是枚举的类调用此方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/NonEnum.java\n", + "public class NonEnum {\n", + " public static void main(String[] args) {\n", + " Class intClass = Integer.class;\n", + " try {\n", + " for(Object en : intClass.getEnumConstants())\n", + " System.out.println(en);\n", + " } catch(Exception e) {\n", + " System.out.println(\"Expected: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Expected: java.lang.NullPointerException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只不过,此时该方法返回 null,所以当你试图使用其返回的结果时会发生异常。\n", + "\n", + "\n", + "\n", + "## 实现而非继承\n", + "\n", + "我们已经知道,所有的 enum 都继承自 Java.lang.Enum 类。由于 Java 不支持多重继承,所以你的 enum 不能再继承其他类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "enum NotPossible extends Pet { ... // Won't work" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然而,在我们创建一个新的 enum 时,可以同时实现一个或多个接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/cartoons/EnumImplementation.java\n", + "// An enum can implement an interface\n", + "// {java enums.cartoons.EnumImplementation}\n", + "package enums.cartoons;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "enum CartoonCharacter\n", + " implements Supplier {\n", + " SLAPPY, SPANKY, PUNCHY,\n", + " SILLY, BOUNCY, NUTTY, BOB;\n", + " private Random rand =\n", + " new Random(47);\n", + " @Override\n", + " public CartoonCharacter get() {\n", + " return values()[rand.nextInt(values().length)];\n", + " }\n", + "}\n", + "public class EnumImplementation {\n", + " public static void printNext(Supplier rg) {\n", + " System.out.print(rg.get() + \", \");\n", + " }\n", + " public static void main(String[] args) {\n", + "// Choose any instance:\n", + " CartoonCharacter cc = CartoonCharacter.BOB;\n", + " for(int i = 0; i < 10; i++)\n", + " printNext(cc);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY,\n", + "NUTTY, SLAPPY," + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个结果有点奇怪,不过你必须要有一个 enum 实例才能调用其上的方法。现在,在任何接受 Supplier 参数的方法中,例如 printNext(),都可以使用 CartoonCharacter。\n", + "\n", + "\n", + "\n", + "## 随机选择\n", + "\n", + "就像你在 CartoonCharacter.get() 中看到的那样,本章中的很多示例都需要从 enum 实例中进行随机选择。我们可以利用泛型,从而使得这个工作更一般化,并将其加入到我们的工具库中。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Enums.java\n", + "package onjava;\n", + "import java.util.*;\n", + "public class Enums {\n", + " private static Random rand = new Random(47);\n", + " \n", + " public static > T random(Class ec) {\n", + " return random(ec.getEnumConstants());\n", + " }\n", + " \n", + " public static T random(T[] values) {\n", + " return values[rand.nextInt(values.length)];\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "古怪的语法\\\\> 表示 T 是一个 enum 实例。而将 Class\\ 作为参数的话,我们就可以利用 Class 对象得到 enum 实例的数组了。重载后的 random() 方法只需使用 T[] 作为参数,因为它并不会调用 Enum 上的任何操作,它只需从数组中随机选择一个元素即可。这样,最终的返回类型正是 enum 的类型。\n", + "\n", + "下面是 random() 方法的一个简单示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RandomTest.java\n", + "import onjava.*;\n", + "enum Activity { SITTING, LYING, STANDING, HOPPING,\n", + " RUNNING, DODGING, JUMPING, FALLING, FLYING }\n", + " \n", + "public class RandomTest {\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 20; i++)\n", + " System.out.print(\n", + " Enums.random(Activity.class) + \" \");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "STANDING FLYING RUNNING STANDING RUNNING STANDING LYING\n", + "DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING\n", + "STANDING LYING FALLING RUNNING FLYING LYING" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "## 使用接口组织枚举\n", + "\n", + "无法从 enum 继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。\n", + "\n", + "在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。举例来说,假设你想用 enum 来表示不同类别的食物,同时还希望每个 enum 元素仍然保持 Food 类型。那可以这样实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/menu/Food.java\n", + "// Subcategorization of enums within interfaces\n", + "package enums.menu;\n", + "public interface Food {\n", + " enum Appetizer implements Food {\n", + " SALAD, SOUP, SPRING_ROLLS;\n", + " }\n", + " enum MainCourse implements Food {\n", + " LASAGNE, BURRITO, PAD_THAI,\n", + " LENTILS, HUMMOUS, VINDALOO;\n", + " }\n", + " enum Dessert implements Food {\n", + " TIRAMISU, GELATO, BLACK_FOREST_CAKE,\n", + " FRUIT, CREME_CARAMEL;\n", + " }\n", + " enum Coffee implements Food {\n", + " BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,\n", + " LATTE, CAPPUCCINO, TEA, HERB_TEA;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于 enum 而言,实现接口是使其子类化的唯一办法,所以嵌入在 Food 中的每个 enum 都实现了 Food 接口。现在,在下面的程序中,我们可以说“所有东西都是某种类型的 Food\"。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/menu/TypeOfFood.java\n", + "// {java enums.menu.TypeOfFood}\n", + "package enums.menu;\n", + "import static enums.menu.Food.*;\n", + "public class TypeOfFood {\n", + " public static void main(String[] args) {\n", + " Food food = Appetizer.SALAD;\n", + " food = MainCourse.LASAGNE;\n", + " food = Dessert.GELATO;\n", + " food = Coffee.CAPPUCCINO;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果 enum 类型实现了 Food 接口,那么我们就可以将其实例向上转型为 Food,所以上例中的所有东西都是 Food。\n", + "\n", + "然而,当你需要与一大堆类型打交道时,接口就不如 enum 好用了。例如,如果你想创建一个“校举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/menu/Course.java\n", + "package enums.menu;\n", + "import onjava.*;\n", + "public enum Course {\n", + " APPETIZER(Food.Appetizer.class),\n", + " MAINCOURSE(Food.MainCourse.class),\n", + " DESSERT(Food.Dessert.class),\n", + " COFFEE(Food.Coffee.class);\n", + " private Food[] values;\n", + " private Course(Class kind) {\n", + " values = kind.getEnumConstants();\n", + " }\n", + " public Food randomSelection() {\n", + " return Enums.random(values);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每一个 Course 的实例都将其对应的 Class 对象作为构造器的参数。通过 getEnumConstants0 方法,可以从该 Class 对象中取得某个 Food 子类的所有 enum 实例。这些实例在 randomSelection() 中被用到。因此,通过从每一个 Course 实例中随机地选择一个 Food,我们便能够生成一份菜单:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/menu/Meal.java\n", + "// {java enums.menu.Meal}\n", + "package enums.menu;\n", + "public class Meal {\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 5; i++) {\n", + " for(Course course : Course.values()) {\n", + " Food food = course.randomSelection();\n", + " System.out.println(food);\n", + " }\n", + " System.out.println(\"***\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SPRING_ROLLS\n", + "VINDALOO\n", + "FRUIT\n", + "DECAF_COFFEE\n", + "***\n", + "SOUP\n", + "VINDALOO\n", + "FRUIT\n", + "TEA\n", + "***\n", + "SALAD\n", + "BURRITO\n", + "FRUIT\n", + "TEA\n", + "***\n", + "SALAD\n", + "BURRITO\n", + "CREME_CARAMEL\n", + "LATTE\n", + "***\n", + "SOUP\n", + "BURRITO\n", + "TIRAMISU\n", + "ESPRESSO\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个例子中,我们通过遍历每一个 Course 实例来获得“枚举的枚举”的值。稍后,在 VendingMachine.java 中,我们会看到另一种组织枚举实例的方式,但其也有一些其他的限制。\n", + "\n", + "此外,还有一种更简洁的管理枚举的办法,就是将一个 enum 嵌套在另一个 enum 内。就像这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/SecurityCategory.java\n", + "// More succinct subcategorization of enums\n", + "import onjava.*;\n", + "enum SecurityCategory {\n", + " STOCK(Security.Stock.class),\n", + " BOND(Security.Bond.class);\n", + " Security[] values;\n", + " SecurityCategory(Class kind) {\n", + " values = kind.getEnumConstants();\n", + " }\n", + " interface Security {\n", + " enum Stock implements Security {\n", + " SHORT, LONG, MARGIN\n", + " }\n", + " enum Bond implements Security {\n", + " MUNICIPAL, JUNK\n", + " }\n", + " }\n", + " public Security randomSelection() {\n", + " return Enums.random(values);\n", + " }\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 10; i++) {\n", + " SecurityCategory category =\n", + " Enums.random(SecurityCategory.class);\n", + " System.out.println(category + \": \" +\n", + " category.randomSelection());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BOND: MUNICIPAL\n", + "BOND: MUNICIPAL\n", + "STOCK: MARGIN\n", + "STOCK: MARGIN\n", + "BOND: JUNK\n", + "STOCK: SHORT\n", + "STOCK: LONG\n", + "STOCK: LONG\n", + "BOND: MUNICIPAL\n", + "BOND: JUNK" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Security 接口的作用是将其所包含的 enum 组合成一个公共类型,这一点是有必要的。然后,SecurityCategory 才能将 Security 中的 enum 作为其构造器的参数使用,以起到组织的效果。\n", + "\n", + "如果我们将这种方式应用于 Food 的例子,结果应该这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/menu/Meal2.java\n", + "// {java enums.menu.Meal2}\n", + "package enums.menu;\n", + "import onjava.*;\n", + "public enum Meal2 {\n", + " APPETIZER(Food.Appetizer.class),\n", + " MAINCOURSE(Food.MainCourse.class),\n", + " DESSERT(Food.Dessert.class),\n", + " COFFEE(Food.Coffee.class);\n", + " private Food[] values;\n", + " private Meal2(Class kind) {\n", + " values = kind.getEnumConstants();\n", + " }\n", + " public interface Food {\n", + " enum Appetizer implements Food {\n", + " SALAD, SOUP, SPRING_ROLLS;\n", + " }\n", + " enum MainCourse implements Food {\n", + " LASAGNE, BURRITO, PAD_THAI,\n", + " LENTILS, HUMMOUS, VINDALOO;\n", + " }\n", + " enum Dessert implements Food {\n", + " TIRAMISU, GELATO, BLACK_FOREST_CAKE,\n", + " FRUIT, CREME_CARAMEL;\n", + " }\n", + " enum Coffee implements Food {\n", + " BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,\n", + " LATTE, CAPPUCCINO, TEA, HERB_TEA;\n", + " }\n", + " }\n", + " public Food randomSelection() {\n", + " return Enums.random(values);\n", + " }\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < 5; i++) {\n", + " for(Meal2 meal : Meal2.values()) {\n", + " Food food = meal.randomSelection();\n", + " System.out.println(food);\n", + " }\n", + " System.out.println(\"***\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SPRING_ROLLS\n", + "VINDALOO\n", + "FRUIT\n", + "DECAF_COFFEE\n", + "***\n", + "SOUP\n", + "VINDALOO\n", + "FRUIT\n", + "TEA\n", + "***\n", + "SALAD\n", + "BURRITO\n", + "FRUIT\n", + "TEA\n", + "***\n", + "SALAD\n", + "BURRITO\n", + "CREME_CARAMEL\n", + "LATTE\n", + "***\n", + "SOUP\n", + "BURRITO\n", + "TIRAMISU\n", + "ESPRESSO\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其实,这仅仅是重新组织了一下代码,不过多数情况下,这种方式使你的代码具有更清晰的结构。\n", + "\n", + "\n", + "\n", + "## 使用 EnumSet 替代 Flags\n", + "\n", + "Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enumi 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。\n", + "\n", + "EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。\n", + "\n", + "EnumSet 中的元素必须来自一个 enum。下面的 enum 表示在一座大楼中,警报传感器的安放位置:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/AlarmPoints.java\n", + "package enums;\n", + "public enum AlarmPoints {\n", + " STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,\n", + " OFFICE4, BATHROOM, UTILITY, KITCHEN\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后,我们用 EnumSet 来跟踪报警器的状态:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/EnumSets.java\n", + "// Operations on EnumSets\n", + "// {java enums.EnumSets}\n", + "package enums;\n", + "import java.util.*;\n", + "import static enums.AlarmPoints.*;\n", + "public class EnumSets {\n", + " public static void main(String[] args) {\n", + " EnumSet points =\n", + " EnumSet.noneOf(AlarmPoints.class); // Empty\n", + " points.add(BATHROOM);\n", + " System.out.println(points);\n", + " points.addAll(\n", + " EnumSet.of(STAIR1, STAIR2, KITCHEN));\n", + " System.out.println(points);\n", + " points = EnumSet.allOf(AlarmPoints.class);\n", + " points.removeAll(\n", + " EnumSet.of(STAIR1, STAIR2, KITCHEN));\n", + " System.out.println(points);\n", + " points.removeAll(\n", + " EnumSet.range(OFFICE1, OFFICE4));\n", + " System.out.println(points);\n", + " points = EnumSet.complementOf(points);\n", + " System.out.println(points);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[BATHROOM]\n", + "[STAIR1, STAIR2, BATHROOM, KITCHEN]\n", + "[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM,\n", + "UTILITY]\n", + "[LOBBY, BATHROOM, UTILITY]\n", + "[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4,\n", + "KITCHEN]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnumSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。\n", + "\n", + "EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在。\n", + "也就是说,在不超过一个 long 的表达能力的情况下,你的 EnumSet 可以应用于最多不超过 64 个元素的 enum。如果 enum 超过了 64 个元素会发生什么呢?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/BigEnumSet.java\n", + "import java.util.*;\n", + "public class BigEnumSet {\n", + " enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,\n", + " A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,\n", + " A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,\n", + " A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,\n", + " A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,\n", + " A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,\n", + " A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,\n", + " A70, A71, A72, A73, A74, A75 }\n", + " public static void main(String[] args) {\n", + " EnumSet bigEnumSet = EnumSet.allOf(Big.class);\n", + " System.out.println(bigEnumSet);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12,\n", + "A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23,\n", + "A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34,\n", + "A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45,\n", + "A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56,\n", + "A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67,\n", + "A68, A69, A70, A71, A72, A73, A74, A75]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显然,EnumSet 可以应用于多过 64 个元素的 enum,所以我猜测,Enum 会在必要的时候增加一个 long。\n", + "\n", + "\n", + "\n", + "## 使用 EnumMap\n", + "\n", + "EnumMap 是一种特殊的 Map,它要求其中的键(key)必须来自一个 enum,由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快,我们可以放心地使用 enum 实例在 EnumMap 中进行查找操作。不过,我们只能将 enum 的实例作为键来调用 put() 可方法,其他操作与使用一般的 Map 差不多。\n", + "\n", + "下面的例子演示了*命令设计模式*的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/EnumMaps.java\n", + "// Basics of EnumMaps\n", + "// {java enums.EnumMaps}\n", + "package enums;\n", + "import java.util.*;\n", + "import static enums.AlarmPoints.*;\n", + "interface Command { void action(); }\n", + "public class EnumMaps {\n", + " public static void main(String[] args) {\n", + " EnumMap em =\n", + " new EnumMap<>(AlarmPoints.class);\n", + " em.put(KITCHEN,\n", + " () -> System.out.println(\"Kitchen fire!\"));\n", + " em.put(BATHROOM,\n", + " () -> System.out.println(\"Bathroom alert!\"));\n", + " for(Map.Entry e:\n", + " em.entrySet()) {\n", + " System.out.print(e.getKey() + \": \");\n", + " e.getValue().action();\n", + " }\n", + " try { // If there's no value for a particular key:\n", + " em.get(UTILITY).action();\n", + " } catch(Exception e) {\n", + " System.out.println(\"Expected: \" + e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "BATHROOM: Bathroom alert!\n", + "KITCHEN: Kitchen fire!\n", + "Expected: java.lang.NullPointerException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "与 EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。\n", + "\n", + "main() 方法的最后部分说明,enum 的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用 put() 方法来存入相应的值的话,其对应的值就是 null。\n", + "\n", + "与常量相关的方法(constant-specific methods 将在下一节中介绍)相比,EnumMap 有一个优点,那 EnumMap 允许程序员改变值对象,而常量相关的方法在编译期就被固定了。稍后你会看到,在你有多种类型的 enum,而且它们之间存在互操作的情况下,我们可以用 EnumMap 实现多路分发(multiple dispatching)。\n", + "\n", + "\n", + "\n", + "## 常量特定方法\n", + "\n", + "Java 的 enum 有一个非常有趣的特性,即它允许程序员为 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。要实现常量相关的方法,你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。参考下面的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/ConstantSpecificMethod.java\n", + "import java.util.*;\n", + "import java.text.*;\n", + "public enum ConstantSpecificMethod {\n", + " DATE_TIME {\n", + " @Override\n", + " String getInfo() {\n", + " return\n", + " DateFormat.getDateInstance()\n", + " .format(new Date());\n", + " }\n", + " },\n", + " CLASSPATH {\n", + " @Override\n", + " String getInfo() {\n", + " return System.getenv(\"CLASSPATH\");\n", + " }\n", + " },\n", + " VERSION {\n", + " @Override\n", + " String getInfo() {\n", + " return System.getProperty(\"java.version\");\n", + " }\n", + " };\n", + " abstract String getInfo();\n", + " public static void main(String[] args) {\n", + " for(ConstantSpecificMethod csm : values())\n", + " System.out.println(csm.getInfo());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "May 9, 2017\n", + "C:\\Users\\Bruce\\Documents\\GitHub\\on-\n", + "java\\ExtractedExamples\\\\gradle\\wrapper\\gradle-\n", + "wrapper.jar\n", + "1.8.0_112" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过相应的 enum 实例,我们可以调用其上的方法。这通常也称为表驱动的代码(table-driven code,请注意它与前面提到的命令模式的相似之处)。\n", + "\n", + "在面向对象的程序设计中,不同的行为与不同的类关联。而通过常量相关的方法,每个 enum 实例可以具备自己独特的行为,这似乎说明每个 enum 实例就像一个独特的类。在上面的例子中,enum 实例似乎被当作其“超类”ConstantSpecificMethod 来使用,在调用 getInfo() 方法时,体现出多态的行为。\n", + "\n", + "然而,enum 实例与类的相似之处也仅限于此了。我们并不能真的将 enum 实例作为一个类型来使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/NotClasses.java\n", + "// {javap -c LikeClasses}\n", + "enum LikeClasses {\n", + " WINKEN {\n", + " @Override\n", + " void behavior() {\n", + " System.out.println(\"Behavior1\");\n", + " }\n", + " },\n", + " BLINKEN {\n", + " @Override\n", + " void behavior() {\n", + " System.out.println(\"Behavior2\");\n", + " }\n", + " },\n", + " NOD {\n", + " @Override\n", + " void behavior() {\n", + " System.out.println(\"Behavior3\");\n", + " }\n", + " };\n", + " abstract void behavior();\n", + "}\n", + "public class NotClasses {\n", + " // void f1(LikeClasses.WINKEN instance) {} // Nope\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为(前 12 行):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Compiled from \"NotClasses.java\"\n", + "abstract class LikeClasses extends\n", + "java.lang.Enum {\n", + "public static final LikeClasses WINKEN;\n", + "public static final LikeClasses BLINKEN;\n", + "public static final LikeClasses NOD;\n", + "public static LikeClasses[] values();\n", + "Code:\n", + "0: getstatic #2 // Field\n", + "$VALUES:[LLikeClasses;\n", + "3: invokevirtual #3 // Method\n", + "\"[LLikeClasses;\".clone:()Ljava/lang/Object;\n", + "..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在方法 f1() 中,编译器不允许我们将一个 enum 实例当作 class 类型。如果我们分析一下编译器生成的代码,就知道这种行为也是很正常的。因为每个 enum 元素都是一个 LikeClasses 类型的 static final 实例。\n", + "\n", + "同时,由于它们是 static 实例,无法访问外部类的非 static 元素或方法,所以对于内部的 enum 的实例而言,其行为与一般的内部类并不相同。\n", + "\n", + "再看一个更有趣的关于洗车的例子。每个顾客在洗车时,都有一个选择菜单,每个选择对应一个不同的动作。可以将一个常量相关的方法关联到一个选择上,再使用一个 EnumSet 来保存客户的选择:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/CarWash.java\n", + "import java.util.*;\n", + "public class CarWash {\n", + " public enum Cycle {\n", + " UNDERBODY {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Spraying the underbody\");\n", + " }\n", + " },\n", + " WHEELWASH {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Washing the wheels\");\n", + " }\n", + " },\n", + " PREWASH {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Loosening the dirt\");\n", + " }\n", + " },\n", + " BASIC {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"The basic wash\");\n", + " }\n", + " },\n", + " HOTWAX {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Applying hot wax\");\n", + " }\n", + " },\n", + " RINSE {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Rinsing\");\n", + " }\n", + " },\n", + " BLOWDRY {\n", + " @Override\n", + " void action() {\n", + " System.out.println(\"Blowing dry\");\n", + " }\n", + " };\n", + " abstract void action();\n", + " }\n", + " EnumSet cycles =\n", + " EnumSet.of(Cycle.BASIC, Cycle.RINSE);\n", + " public void add(Cycle cycle) {\n", + " cycles.add(cycle);\n", + " }\n", + " public void washCar() {\n", + " for(Cycle c : cycles)\n", + " c.action();\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return cycles.toString();\n", + " }\n", + " public static void main(String[] args) {\n", + " CarWash wash = new CarWash();\n", + " System.out.println(wash);\n", + " wash.washCar();\n", + "// Order of addition is unimportant:\n", + " wash.add(Cycle.BLOWDRY);\n", + " wash.add(Cycle.BLOWDRY); // Duplicates ignored\n", + " wash.add(Cycle.RINSE);\n", + " wash.add(Cycle.HOTWAX);\n", + " System.out.println(wash);\n", + " wash.washCar();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[BASIC, RINSE]\n", + "The basic wash\n", + "Rinsing\n", + "[BASIC, HOTWAX, RINSE, BLOWDRY]\n", + "The basic wash\n", + "Applying hot wax\n", + "Rinsing\n", + "Blowing dry" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "与使用匿名内部类相比较,定义常量相关方法的语法更高效、简洁。\n", + "\n", + "这个例子也展示了 EnumSet 了一些特性。因为它是一个集合,所以对于同一个元素而言,只能出现一次,因此对同一个参数重复地调用 add0 方法会被忽略掉(这是正确的行为,因为一个 bit 位开关只能“打开”一次),同样地,向 EnumSet 添加 enum 实例的顺序并不重要,因为其输出的次序决定于 enum 实例定义时的次序。\n", + "\n", + "除了实现 abstract 方法以外,程序员是否可以覆盖常量相关的方法呢?答案是肯定的,参考下面的程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/OverrideConstantSpecific.java\n", + "public enum OverrideConstantSpecific {\n", + " NUT, BOLT,\n", + " WASHER {\n", + " @Override\n", + " void f() {\n", + " System.out.println(\"Overridden method\");\n", + " }\n", + " };\n", + " void f() {\n", + " System.out.println(\"default behavior\");\n", + " }\n", + " public static void main(String[] args) {\n", + " for(OverrideConstantSpecific ocs : values()) {\n", + " System.out.print(ocs + \": \");\n", + " ocs.f();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NUT: default behavior\n", + "BOLT: default behavior\n", + "WASHER: Overridden method" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然 enum 有某些限制,但是一般而言,我们还是可以将其看作是类。\n", + "\n", + "### 使用 enum 的职责链\n", + "\n", + "在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。\n", + "\n", + "通过常量相关的方法,我们可以很容易地实现一个简单的职责链。我们以一个邮局的模型为例。邮局需要以尽可能通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件最终被确定为一封死信。其中的每一次尝试可以看作为一个策略(也是一个设计模式),而完整的处理方式列表就是一个职责链。\n", + "\n", + "我们先来描述一下邮件。邮件的每个关键特征都可以用 enum 来表示。程序将随机地生成 Mail 对象,如果要减小一封邮件的 GeneralDelivery 为 YES 的概率,那最简单的方法就是多创建几个不是 YES 的 enum 实例,所以 enum 的定义看起来有点古怪。\n", + "\n", + "我们看到 Mail 中有一个 randomMail() 方法,它负责随机地创建用于测试的邮件。而 generator() 方法生成一个 Iterable 对象,该对象在你调用 next() 方法时,在其内部使用 randomMail() 来创建 Mail 对象。这样的结构使程序员可以通过调用 Mail.generator() 方法,很容易地构造出一个 foreach 循环:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/PostOffice.java\n", + "// Modeling a post office\n", + "import java.util.*;\n", + "import onjava.*;\n", + "class Mail {\n", + " // The NO's reduce probability of random selection:\n", + " enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}\n", + " enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}\n", + " enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}\n", + " enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}\n", + " enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}\n", + " GeneralDelivery generalDelivery;\n", + " Scannability scannability;\n", + " Readability readability;\n", + " Address address;\n", + " ReturnAddress returnAddress;\n", + " static long counter = 0;\n", + " long id = counter++;\n", + " @Override\n", + " public String toString() { return \"Mail \" + id; }\n", + " public String details() {\n", + " return toString() +\n", + " \", General Delivery: \" + generalDelivery +\n", + " \", Address Scanability: \" + scannability +\n", + " \", Address Readability: \" + readability +\n", + " \", Address Address: \" + address +\n", + " \", Return address: \" + returnAddress;\n", + " }\n", + " // Generate test Mail:\n", + " public static Mail randomMail() {\n", + " Mail m = new Mail();\n", + " m.generalDelivery =\n", + " Enums.random(GeneralDelivery.class);\n", + " m.scannability =\n", + " Enums.random(Scannability.class);\n", + " m.readability =\n", + " Enums.random(Readability.class);\n", + " m.address = Enums.random(Address.class);\n", + " m.returnAddress =\n", + " Enums.random(ReturnAddress.class);\n", + " return m;\n", + " }\n", + " public static\n", + " Iterable generator(final int count) {\n", + " return new Iterable() {\n", + " int n = count;\n", + " @Override\n", + " public Iterator iterator() {\n", + " return new Iterator() {\n", + " @Override\n", + " public boolean hasNext() {\n", + " return n-- > 0;\n", + " }\n", + " @Override\n", + " public Mail next() {\n", + " return randomMail();\n", + " }\n", + " @Override\n", + " public void remove() { // Not implemented\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " };\n", + " }\n", + " };\n", + " }\n", + "}\n", + "public class PostOffice {\n", + " enum MailHandler {\n", + " GENERAL_DELIVERY {\n", + " @Override\n", + " boolean handle(Mail m) {\n", + " switch(m.generalDelivery) {\n", + " case YES:\n", + " System.out.println(\n", + " \"Using general delivery for \" + m);\n", + " return true;\n", + " default: return false;\n", + " }\n", + " }\n", + " },\n", + " MACHINE_SCAN {\n", + " @Override\n", + " boolean handle(Mail m) {\n", + " switch(m.scannability) {\n", + " case UNSCANNABLE: return false;\n", + " default:\n", + " switch(m.address) {\n", + " case INCORRECT: return false;\n", + " default:\n", + " System.out.println(\n", + " \"Delivering \"+ m + \" automatically\");\n", + " return true;\n", + " }\n", + " }\n", + " }\n", + " },\n", + " VISUAL_INSPECTION {\n", + " @Override\n", + " boolean handle(Mail m) {\n", + " switch(m.readability) {\n", + " case ILLEGIBLE: return false;\n", + " default:\n", + " switch(m.address) {\n", + " case INCORRECT: return false;\n", + " default:\n", + " System.out.println(\n", + " \"Delivering \" + m + \" normally\");\n", + " return true;\n", + " }\n", + " }\n", + " }\n", + " },\n", + " RETURN_TO_SENDER {\n", + " @Override\n", + " boolean handle(Mail m) {\n", + " switch(m.returnAddress) {\n", + " case MISSING: return false;\n", + " default:\n", + " System.out.println(\n", + " \"Returning \" + m + \" to sender\");\n", + " return true;\n", + " }\n", + " }\n", + " };\n", + " abstract boolean handle(Mail m);\n", + " }\n", + " static void handle(Mail m) {\n", + " for(MailHandler handler : MailHandler.values())\n", + " if(handler.handle(m))\n", + " return;\n", + " System.out.println(m + \" is a dead letter\");\n", + " }\n", + " public static void main(String[] args) {\n", + " for(Mail mail : Mail.generator(10)) {\n", + " System.out.println(mail.details());\n", + " handle(mail);\n", + " System.out.println(\"*****\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Mail 0, General Delivery: NO2, Address Scanability:\n", + "UNSCANNABLE, Address Readability: YES3, Address\n", + "Address: OK1, Return address: OK1\n", + "Delivering Mail 0 normally\n", + "*****\n", + "Mail 1, General Delivery: NO5, Address Scanability:\n", + "YES3, Address Readability: ILLEGIBLE, Address Address:\n", + "OK5, Return address: OK1\n", + "Delivering Mail 1 automatically\n", + "*****\n", + "Mail 2, General Delivery: YES, Address Scanability:\n", + "YES3, Address Readability: YES1, Address Address: OK1,\n", + "Return address: OK5\n", + "Using general delivery for Mail 2\n", + "*****\n", + "Mail 3, General Delivery: NO4, Address Scanability:\n", + "YES3, Address Readability: YES1, Address Address:\n", + "INCORRECT, Return address: OK4\n", + "Returning Mail 3 to sender\n", + "*****\n", + "Mail 4, General Delivery: NO4, Address Scanability:\n", + "UNSCANNABLE, Address Readability: YES1, Address\n", + "Address: INCORRECT, Return address: OK2\n", + "Returning Mail 4 to sender\n", + "*****\n", + "Mail 5, General Delivery: NO3, Address Scanability:\n", + "YES1, Address Readability: ILLEGIBLE, Address Address:\n", + "OK4, Return address: OK2\n", + "Delivering Mail 5 automatically\n", + "*****\n", + "Mail 6, General Delivery: YES, Address Scanability:\n", + "YES4, Address Readability: ILLEGIBLE, Address Address:\n", + "OK4, Return address: OK4\n", + "Using general delivery for Mail 6\n", + "*****\n", + "Mail 7, General Delivery: YES, Address Scanability:\n", + "YES3, Address Readability: YES4, Address Address: OK2,\n", + "Return address: MISSING\n", + "Using general delivery for Mail 7\n", + "*****\n", + "Mail 8, General Delivery: NO3, Address Scanability:\n", + "YES1, Address Readability: YES3, Address Address:\n", + "INCORRECT, Return address: MISSING\n", + "Mail 8 is a dead letter\n", + "*****\n", + "Mail 9, General Delivery: NO1, Address Scanability:\n", + "UNSCANNABLE, Address Readability: YES2, Address\n", + "Address: OK1, Return address: OK4\n", + "Delivering Mail 9 normally\n", + "*****" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "职责链由 enum MailHandler 实现,而 enum 定义的次序决定了各个解决策略在应用时的次序。对每一封邮件,都要按此顺序尝试每个解决策略,直到其中一个能够成功地处理该邮件,如果所有的策略都失败了,那么该邮件将被判定为一封死信。\n", + "\n", + "### 使用 enum 的状态机\n", + "\n", + "枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态(transient states),而一旦任务执行结束,状态机就会立刻离开瞬时状态。\n", + "\n", + "每个状态都具有某些可接受的输入,不同的输入会使状态机从当前状态转移到不同的新状态。由于 enum 对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。\n", + "\n", + "自动售贷机是一个很好的状态机的例子。首先,我们用一个 enum 定义各种输入:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/Input.java\n", + "import java.util.*;\n", + "public enum Input {\n", + " NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),\n", + " TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),\n", + " ABORT_TRANSACTION {\n", + " @Override\n", + " public int amount() { // Disallow\n", + " throw new RuntimeException(\"ABORT.amount()\");\n", + " }\n", + " },\n", + " STOP { // This must be the last instance.\n", + " @Override\n", + " public int amount() { // Disallow\n", + " throw new RuntimeException(\"SHUT_DOWN.amount()\");\n", + " }\n", + " };\n", + " int value; // In cents\n", + " Input(int value) { this.value = value; }\n", + " Input() {}\n", + " int amount() { return value; }; // In cents\n", + " static Random rand = new Random(47);\n", + " public static Input randomSelection() {\n", + " // Don't include STOP:\n", + " return values()[rand.nextInt(values().length - 1)];\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,除了两个特殊的 Input 实例之外,其他的 Input 都有相应的价格,因此在接口中定义了 amount(方法。然而,对那两个特殊 Input 实例而言,调用 amount(方法并不合适,所以如果程序员调用它们的 amount)方法就会有异常抛出(在接口内定义了一个方法,然后在你调用该方法的某个实现时就会抛出异常),这似乎有点奇怪,但由于 enum 的限制,我们不得不采用这种方式。\n", + "\n", + "VendingMachine 对输入的第一个反应是将其归类为 Category enum 中的某一个 enum 实例,这可以通过 switch 实现。下面的例子演示了 enum 是如何使代码变得更加清晰且易于管理的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/VendingMachine.java\n", + "// {java VendingMachine VendingMachineInput.txt}\n", + "import java.util.*;\n", + "import java.io.IOException;\n", + "import java.util.function.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "enum Category {\n", + " MONEY(Input.NICKEL, Input.DIME,\n", + " Input.QUARTER, Input.DOLLAR),\n", + " ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS,\n", + " Input.SODA, Input.SOAP),\n", + " QUIT_TRANSACTION(Input.ABORT_TRANSACTION),\n", + " SHUT_DOWN(Input.STOP);\n", + " private Input[] values;\n", + " Category(Input... types) { values = types; }\n", + " private static EnumMap categories =\n", + " new EnumMap<>(Input.class);\n", + " static {\n", + " for(Category c : Category.class.getEnumConstants())\n", + " for(Input type : c.values)\n", + " categories.put(type, c);\n", + " }\n", + " public static Category categorize(Input input) {\n", + " return categories.get(input);\n", + " }\n", + "}\n", + "\n", + "public class VendingMachine {\n", + " private static State state = State.RESTING;\n", + " private static int amount = 0;\n", + " private static Input selection = null;\n", + " enum StateDuration { TRANSIENT } // Tagging enum\n", + " enum State {\n", + " RESTING {\n", + " @Override\n", + " void next(Input input) {\n", + " switch(Category.categorize(input)) {\n", + " case MONEY:\n", + " amount += input.amount();\n", + " state = ADDING_MONEY;\n", + " break;\n", + " case SHUT_DOWN:\n", + " state = TERMINAL;\n", + " default:\n", + " }\n", + " }\n", + " },\n", + " ADDING_MONEY {\n", + " @Override\n", + " void next(Input input) {\n", + " switch(Category.categorize(input)) {\n", + " case MONEY:\n", + " amount += input.amount();\n", + " break;\n", + " case ITEM_SELECTION:\n", + " selection = input;\n", + " if(amount < selection.amount())\n", + " System.out.println(\n", + " \"Insufficient money for \" + selection);\n", + " else state = DISPENSING;\n", + " break;\n", + " case QUIT_TRANSACTION:\n", + " state = GIVING_CHANGE;\n", + " break;\n", + " case SHUT_DOWN:\n", + " state = TERMINAL;\n", + " default:\n", + " }\n", + " }\n", + " },\n", + " DISPENSING(StateDuration.TRANSIENT) {\n", + " @Override\n", + " void next() {\n", + " System.out.println(\"here is your \" + selection);\n", + " amount -= selection.amount();\n", + " state = GIVING_CHANGE;\n", + " }\n", + " },\n", + " GIVING_CHANGE(StateDuration.TRANSIENT) {\n", + " @Override\n", + " void next() {\n", + " if(amount > 0) {\n", + " System.out.println(\"Your change: \" + amount);\n", + " amount = 0;\n", + " }\n", + " state = RESTING;\n", + " }\n", + " },\n", + " TERMINAL {@Override\n", + " void output() { System.out.println(\"Halted\"); } };\n", + " private boolean isTransient = false;\n", + " State() {}\n", + " State(StateDuration trans) { isTransient = true; }\n", + " void next(Input input) {\n", + " throw new RuntimeException(\"Only call \" +\n", + " \"next(Input input) for non-transient states\");\n", + " }\n", + " void next() {\n", + " throw new RuntimeException(\n", + " \"Only call next() for \" +\n", + " \"StateDuration.TRANSIENT states\");\n", + " }\n", + " void output() { System.out.println(amount); }\n", + " }\n", + " static void run(Supplier gen) {\n", + " while(state != State.TERMINAL) {\n", + " state.next(gen.get());\n", + " while(state.isTransient)\n", + " state.next();\n", + " state.output();\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " Supplier gen = new RandomInputSupplier();\n", + " if(args.length == 1)\n", + " gen = new FileInputSupplier(args[0]);\n", + " run(gen);\n", + " }\n", + "}\n", + "\n", + "// For a basic sanity check:\n", + "class RandomInputSupplier implements Supplier {\n", + " @Override\n", + " public Input get() {\n", + " return Input.randomSelection();\n", + " }\n", + "}\n", + "\n", + "// Create Inputs from a file of ';'-separated strings:\n", + "class FileInputSupplier implements Supplier {\n", + " private Iterator input;\n", + " FileInputSupplier(String fileName) {\n", + " try {\n", + " input = Files.lines(Paths.get(fileName))\n", + " .skip(1) // Skip the comment line\n", + " .flatMap(s -> Arrays.stream(s.split(\";\")))\n", + " .map(String::trim)\n", + " .collect(Collectors.toList())\n", + " .iterator();\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " @Override\n", + " public Input get() {\n", + " if(!input.hasNext())\n", + " return null;\n", + " return Enum.valueOf(Input.class, input.next().trim());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "25\n", + "50\n", + "75\n", + "here is your CHIPS\n", + "0\n", + "100\n", + "200\n", + "here is your TOOTHPASTE\n", + "0\n", + "25\n", + "35\n", + "Your change: 35\n", + "0\n", + "25\n", + "35\n", + "Insufficient money for SODA\n", + "35\n", + "60\n", + "70\n", + "75\n", + "Insufficient money for SODA\n", + "75\n", + "Your change: 75\n", + "0\n", + "Halted" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于用 switch 语句从 enum 实例中进行选择是最常见的一种方式(请注意,为了使 enum 在 switch 语句中的使用变得简单,我们是需要付出其他代价的),所以,我们经常遇到这样的问题:将多个 enum 进行分类时,“我们希望在什么 enum 中使用 switch 语句?”我们通过 VendingMachine 的例子来研究一下这个问题。对于每一个 State,我们都需要在输入动作的基本分类中进行查找:用户塞入钞票,选择了某个货物,操作被取消,以及机器停止。然而,在这些基本分类之下,我们又可以塞人不同类型的钞票,可以选择不同的货物。Category enum 将不同类型的 Input 进行分组,因而,可以使用 categorize0 方法为 switch 语句生成恰当的 Cateroy 实例。并且,该方法使用的 EnumMap 确保了在其中进行查询时的效率与安全。\n", + "\n", + "如果读者仔细研究 VendingMachine 类,就会发现每种状态的不同之处,以及对于输入的不同响应,其中还有两个瞬时状态。在 run() 方法中,状态机等待着下一个 Input,并一直在各个状态中移动,直到它不再处于瞬时状态。\n", + "\n", + "通过两种不同的 Generator 对象,我们可以使用不同的 Supplier 对象来测试 VendingMachine,首先是 RandomInputSupplier,它会不停地生成除了 SHUT-DOWN 之外的各种输入。通过长时间地运行 RandomInputSupplier,可以起到健全测试(sanity test)的作用,能够确保该状态机不会进入一个错误状态。另一个是 FileInputSupplier,使用文件以文本的方式来描述输入,然后将它们转换成 enum 实例,并创建对应的 Input 对象。上面的程序使用的正是如下的文本文件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// enums/VendingMachineInput.txt\n", + "QUARTER; QUARTER; QUARTER; CHIPS;\n", + "DOLLAR; DOLLAR; TOOTHPASTE;\n", + "QUARTER; DIME; ABORT_TRANSACTION;\n", + "QUARTER; DIME; SODA;\n", + "QUARTER; DIME; NICKEL; SODA;\n", + "ABORT_TRANSACTION;\n", + "STOP;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "FileInputSupplier 构造函数将此文件转换为流,并跳过注释行。然后它使用 String.split() 以分号进行分割。这会生成一个 String 数组,并可以通过将其转换为 Stream,然后应用 flatMap() 来将其输入到流中。其输出结果将去除所有空格空格,并转换为 List\\,且从中获取 Iterator\\。\n", + "\n", + "这种设计有一个缺陷,它要求 enum State 实例访问的 VendingMachine 属性必须声明为 static,这意味着,你只能有一个 VendingMachine 实例。不过如果我们思考一下实际的(嵌入式 Java)应用,这也许并不是一个大问题,因为在一台机器上,我们可能只有一个应用程序。\n", + "\n", + "\n", + "\n", + "## 多路分发\n", + "\n", + "当你要处理多种交互类型时,程序可能会变得相当杂乱。举例来说,如果一个系统要分析和执行数学表达式。我们可能会声明 Number.plus(Number),Number.multiple(Number) 等等,其中 Number 是各种数字对象的超类。然而,当你声明 a.plus(b) 时,你并不知道 a 或 b 的确切类型,那你如何能让它们正确地交互呢?\n", + "\n", + "你可能从未思考过这个问题的答案.Java 只支持单路分发。也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么 Java 的动态绑定机制只能处理其中一个的类型。这就无法解决我们上面提到的问题。所以,你必须自己来判定其他的类型,从而实现自己的动态线定行为。\n", + "\n", + "解决上面问题的办法就是多路分发(在那个例子中,只有两个分发,一般称之为两路分发).多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知的类型。要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。一般而言,程序员需要有设定好的某种配置,以便一个方法调用能够引出更多的方法调用,从而能够在这个过程中处理多种类型。为了达到这种效果,我们需要与多个方法一同工作:因为每个分发都需要一个方法调用。在下面的例子中(实现了 “石头、剪刀、布”游戏,也称为 RoShamBo)对应的方法是 compete() 和 eval(),二者都是同一个类型的成员,它们可以产生三种 Outcome 实例中的一个作为结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/Outcome.java\n", + "package enums;\n", + "public enum Outcome { WIN, LOSE, DRAW }\n", + "// enums/RoShamBo1.java\n", + "// Demonstration of multiple dispatching\n", + "// {java enums.RoShamBo1}\n", + "package enums;\n", + " import java.util.*;\n", + " import static enums.Outcome.*;\n", + "interface Item {\n", + " Outcome compete(Item it);\n", + " Outcome eval(Paper p);\n", + " Outcome eval(Scissors s);\n", + " Outcome eval(Rock r);\n", + "}\n", + "class Paper implements Item {\n", + " @Override\n", + " public Outcome compete(Item it) {\n", + " return it.eval(this);\n", + " }\n", + " @Override\n", + " public Outcome eval(Paper p) { return DRAW; }\n", + " @Override\n", + " public Outcome eval(Scissors s) { return WIN; }\n", + " @Override\n", + " public Outcome eval(Rock r) { return LOSE; }\n", + " @Override\n", + " public String toString() { return \"Paper\"; }\n", + "}\n", + "class Scissors implements Item {\n", + " @Override\n", + " public Outcome compete(Item it) {\n", + " return it.eval(this);\n", + " }\n", + " @Override\n", + " public Outcome eval(Paper p) { return LOSE; }\n", + " @Override\n", + " public Outcome eval(Scissors s) { return DRAW; }\n", + " @Override\n", + " public Outcome eval(Rock r) { return WIN; }\n", + " @Override\n", + " public String toString() { return \"Scissors\"; }\n", + "}\n", + "class Rock implements Item {\n", + " @Override\n", + " public Outcome compete(Item it) {\n", + " return it.eval(this);\n", + " }\n", + " @Override\n", + " public Outcome eval(Paper p) { return WIN; }\n", + " @Override\n", + " public Outcome eval(Scissors s) { return LOSE; }\n", + " @Override\n", + " public Outcome eval(Rock r) { return DRAW; }\n", + " @Override\n", + " public String toString() { return \"Rock\"; }\n", + "}\n", + "public class RoShamBo1 {\n", + " static final int SIZE = 20;\n", + " private static Random rand = new Random(47);\n", + " public static Item newItem() {\n", + " switch(rand.nextInt(3)) {\n", + " default:\n", + " case 0: return new Scissors();\n", + " case 1: return new Paper();\n", + " case 2: return new Rock();\n", + " }\n", + " }\n", + " public static void match(Item a, Item b) {\n", + " System.out.println(\n", + " a + \" vs. \" + b + \": \" + a.compete(b));\n", + " }\n", + " public static void main(String[] args) {\n", + " for(int i = 0; i < SIZE; i++)\n", + " match(newItem(), newItem());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Rock vs. Rock: DRAW\n", + "Paper vs. Rock: WIN\n", + "Paper vs. Rock: WIN\n", + "Paper vs. Rock: WIN\n", + "Scissors vs. Paper: WIN\n", + "Scissors vs. Scissors: DRAW\n", + "Scissors vs. Paper: WIN\n", + "Rock vs. Paper: LOSE\n", + "Paper vs. Paper: DRAW\n", + "Rock vs. Paper: LOSE\n", + "Paper vs. Scissors: LOSE\n", + "Paper vs. Scissors: LOSE\n", + "Rock vs. Scissors: WIN\n", + "Rock vs. Paper: LOSE\n", + "Paper vs. Rock: WIN\n", + "Scissors vs. Paper: WIN\n", + "Paper vs. Scissors: LOSE\n", + "Paper vs. Scissors: LOSE\n", + "Paper vs. Scissors: LOSE\n", + "Paper vs. Scissors: LOSE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Item 是这几种类型的接口,将会被用作多路分发。RoShamBo1.match() 有两个 Item 参数,通过调用 Item.compete90) 方法开始两路分发。要判定 a 的类型,分发机制会在 a 的实际类型的 compete(内部起到分发的作用。compete() 方法通过调用 eval() 来为另一个类型实现第二次分法。\n", + "\n", + "将自身(this)作为参数调用 evalo,能够调用重载过的 eval() 方法,这能够保留第一次分发的类型信息。当第二次分发完成时,你就能够知道两个 Item 对象的具体类型了。\n", + "\n", + "要配置好多路分发需要很多的工序,不过要记住,它的好处在于方法调用时的优雅的话法,这避免了在一个方法中判定多个对象的类型的丑陋代码,你只需说,“嘿,你们两个,我不在乎你们是什么类型,请你们自己交流!”不过,在使用多路分发前,请先明确,这种优雅的代码对你确实有重要的意义。\n", + "\n", + "### 使用 enum 分发\n", + "\n", + "直接将 RoShamBol.java 翻译为基于 enum 的版本是有问题的,因为 enum 实例不是类型,不能将 enum 实例作为参数的类型,所以无法重载 eval() 方法。不过,还有很多方式可以实现多路分发,并从 enum 中获益。\n", + "\n", + "一种方式是使用构造器来初始化每个 enum 实例,并以“一组”结果作为参数。这二者放在一块,形成了类似查询表的结构:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RoShamBo2.java\n", + "// Switching one enum on another\n", + "// {java enums.RoShamBo2}\n", + "package enums;\n", + "import static enums.Outcome.*;\n", + "public enum RoShamBo2 implements Competitor {\n", + " PAPER(DRAW, LOSE, WIN),\n", + " SCISSORS(WIN, DRAW, LOSE),\n", + " ROCK(LOSE, WIN, DRAW);\n", + " private Outcome vPAPER, vSCISSORS, vROCK;\n", + " RoShamBo2(Outcome paper,\n", + " Outcome scissors, Outcome rock) {\n", + " this.vPAPER = paper;\n", + " this.vSCISSORS = scissors;\n", + " this.vROCK = rock;\n", + " }\n", + " @Override\n", + " public Outcome compete(RoShamBo2 it) {\n", + " switch(it) {\n", + " default:\n", + " case PAPER: return vPAPER;\n", + " case SCISSORS: return vSCISSORS;\n", + " case ROCK: return vROCK;\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " RoShamBo.play(RoShamBo2.class, 20);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ROCK vs. ROCK: DRAW\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "PAPER vs. PAPER: DRAW\n", + "PAPER vs. SCISSORS: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. SCISSORS: DRAW\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. PAPER: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 compete() 方法中,一旦两种类型都被确定了,那么唯一的操作就是返回结果 Outcome 然而,你可能还需要调用其他的方法,(例如)甚至是调用在构造器中指定的某个命令对象上的方法。\n", + "\n", + "RoShamBo2.javal 之前的例子短小得多,而且更直接,更易于理解。注意,我们仍然是使用两路分发来判定两个对象的类型。在 RoShamBol.java 中,两次分发都是通过实际的方法调用实现,而在这个例子中,只有第一次分发是实际的方法调用。第二个分发使用的是 switch,不过这样做是安全的,因为 enum 限制了 switch 语句的选择分支。\n", + "\n", + "在代码中,enum 被单独抽取出来,因此它可以应用在其他例子中。首先,Competitor 接口定义了一种类型,该类型的对象可以与另一个 Competitor 相竞争:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/Competitor.java\n", + "// Switching one enum on another\n", + "package enums;\n", + "public interface Competitor> {\n", + " Outcome compete(T competitor);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后,我们定义两个 static 方法(static 可以避免显式地指明参数类型),第一个是 match() 方法,它会为一个 Competitor 对象调用 compete() 方法,并与另一个 Competitor 对象作比较。在这个例子中,我们看到,match())方法的参数需要是 Competitor\\ 类型。但是在 play() 方法中,类型参数必须同时是 Enum\\ 类型(因为它将在 Enums.random() 中使用)和 Competitor\\ 类型因为它将被传递给 match() 方法):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RoShamBo.java\n", + "// Common tools for RoShamBo examples\n", + "package enums;\n", + "import onjava.*;\n", + "public class RoShamBo {\n", + " public static >\n", + " void match(T a, T b) {\n", + " System.out.println(\n", + " a + \" vs. \" + b + \": \" + a.compete(b));\n", + " }\n", + " public static & Competitor>\n", + " void play(Class rsbClass, int size) {\n", + " for(int i = 0; i < size; i++)\n", + " match(Enums.random(rsbClass),Enums.random(rsbClass));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "play() 方法没有将类型参数 T 作为返回值类型,因此,似乎我们应该在 Class\\ 中使用通配符来代替上面的参数声明。然而,通配符不能扩展多个基类,所以我们必须采用以上的表达式。\n", + "\n", + "### 使用常量相关的方法\n", + "\n", + "常量相关的方法允许我们为每个 enum 实例提供方法的不同实现,这使得常量相关的方法似乎是实现多路分发的完美解决方案。不过,通过这种方式,enum 实例虽然可以具有不同的行为,但它们仍然不是类型,不能将其作为方法签名中的参数类型来使用。最好的办法是将 enum 用在 switch 语句中,见下例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RoShamBo3.java\n", + "// Using constant-specific methods\n", + "// {java enums.RoShamBo3}\n", + "package enums;\n", + "import static enums.Outcome.*;\n", + "public enum RoShamBo3 implements Competitor {\n", + " PAPER {\n", + " @Override\n", + " public Outcome compete(RoShamBo3 it) {\n", + " switch(it) {\n", + " default: // To placate the compiler\n", + " case PAPER: return DRAW;\n", + " case SCISSORS: return LOSE;\n", + " case ROCK: return WIN;\n", + " }\n", + " }\n", + " },\n", + " SCISSORS {\n", + " @Override\n", + " public Outcome compete(RoShamBo3 it) {\n", + " switch(it) {\n", + " default:\n", + " case PAPER: return WIN;\n", + " case SCISSORS: return DRAW;\n", + " case ROCK: return LOSE;\n", + " }\n", + " }\n", + " },\n", + " ROCK {\n", + " @Override\n", + " public Outcome compete(RoShamBo3 it) {\n", + " switch(it) {\n", + " default:\n", + " case PAPER: return LOSE;\n", + " case SCISSORS: return WIN;\n", + " case ROCK: return DRAW;\n", + " }\n", + " }\n", + " };\n", + " @Override\n", + " public abstract Outcome compete(RoShamBo3 it);\n", + " public static void main(String[] args) {\n", + " RoShamBo.play(RoShamBo3.class, 20);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ROCK vs. ROCK: DRAW\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "PAPER vs. PAPER: DRAW\n", + "PAPER vs. SCISSORS: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. SCISSORS: DRAW\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. PAPER: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然这种方式可以工作,但是却不甚合理,如果采用 RoShamB02.java 的解决方案,那么在添加一个新的类型时,只需更少的代码,而且也更直接。\n", + "\n", + ":然而,RoShamBo3.java 还可以压缩简化一下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RoShamBo4.java\n", + "// {java enums.RoShamBo4}\n", + "package enums;\n", + "public enum RoShamBo4 implements Competitor {\n", + " ROCK {\n", + " @Override\n", + " public Outcome compete(RoShamBo4 opponent) {\n", + " return compete(SCISSORS, opponent);\n", + " }\n", + " },\n", + " SCISSORS {\n", + " @Override\n", + " public Outcome compete(RoShamBo4 opponent) {\n", + " return compete(PAPER, opponent);\n", + " }\n", + " },\n", + " PAPER {\n", + " @Override\n", + " public Outcome compete(RoShamBo4 opponent) {\n", + " return compete(ROCK, opponent);\n", + " }\n", + " };\n", + " Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {\n", + " return ((opponent == this) ? Outcome.DRAW\n", + " : ((opponent == loser) ? Outcome.WIN\n", + " : Outcome.LOSE));\n", + " }\n", + " public static void main(String[] args) {\n", + " RoShamBo.play(RoShamBo4.class, 20);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PAPER vs. PAPER: DRAW\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. SCISSORS: WIN\n", + "ROCK vs. ROCK: DRAW\n", + "ROCK vs. SCISSORS: WIN\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. SCISSORS: DRAW\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. ROCK: WIN\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中,具有两个参数的 compete() 方法执行第二个分发,该方法执行一系列的比较,其行为类似 switch 语句。这个版本的程序更简短,不过却比较难理解,对于一个大型系统而言,难以理解的代码将导致整个系统不够健壮。\n", + "\n", + "### 使用 EnumMap 进行分发\n", + "\n", + "使用 EnumMap 能够实现“真正的”两路分发。EnumMap 是为 enum 专门设计的一种性能非常好的特殊 Map。由于我们的目的是摸索出两种未知的类型,所以可以用一个 EnumMap 的 EnumMap 来实现两路分发:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// enums/RoShamBo5.java\n", + "// Multiple dispatching using an EnumMap of EnumMaps\n", + "// {java enums.RoShamBo5}\n", + "package enums;\n", + "import java.util.*;\n", + "import static enums.Outcome.*;\n", + "enum RoShamBo5 implements Competitor {\n", + " PAPER, SCISSORS, ROCK;\n", + " static EnumMap>\n", + " table = new EnumMap<>(RoShamBo5.class);\n", + " static {\n", + " for(RoShamBo5 it : RoShamBo5.values())\n", + " table.put(it, new EnumMap<>(RoShamBo5.class));\n", + " initRow(PAPER, DRAW, LOSE, WIN);\n", + " initRow(SCISSORS, WIN, DRAW, LOSE);\n", + " initRow(ROCK, LOSE, WIN, DRAW);\n", + " }\n", + " static void initRow(RoShamBo5 it,\n", + " Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {\n", + " EnumMap row =\n", + " RoShamBo5.table.get(it);\n", + " row.put(RoShamBo5.PAPER, vPAPER);\n", + " row.put(RoShamBo5.SCISSORS, vSCISSORS);\n", + " row.put(RoShamBo5.ROCK, vROCK);\n", + " }\n", + " @Override\n", + " public Outcome compete(RoShamBo5 it) {\n", + " return table.get(this).get(it);\n", + " }\n", + " public static void main(String[] args) {\n", + " RoShamBo.play(RoShamBo5.class, 20);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ROCK vs. ROCK: DRAW\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "PAPER vs. PAPER: DRAW\n", + "PAPER vs. SCISSORS: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. SCISSORS: DRAW\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. PAPER: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "该程序在一个 static 子句中初始化 EnumMap 对象,具体见表格似的 initRow() 方法调用。请注意 compete() 方法,您可以看到,在一行语句中发生了两次分发。\n", + "\n", + "### 使用二维数组\n", + "\n", + "我们还可以进一步简化实现两路分发的解决方案。我们注意到,每个 enum 实例都有一个固定的值(基于其声明的次序),并且可以通过 ordinal() 方法取得该值。因此我们可以使用二维数组,将竞争者映射到竞争结果。采用这种方式能够获得最简洁、最直接的解决方案(很可能也是最快速的,虽然我们知道 EnumMap 内部其实也是使用数组实现的)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "We can simplify the solution even more by noting that each enum instance has a fixed\n", + " value (based on its declaration order) and that ordinal() produces this value. A two-\n", + " dimensional array mapping the competitors onto the outcomes produces the smallest\n", + " and most straightforward solution (and possibly the fastest, although remember that\n", + " EnumMap uses an internal array):\n", + "// enums/RoShamBo6.java\n", + "// Enums using \"tables\" instead of multiple dispatch\n", + "// {java enums.RoShamBo6}\n", + " package enums;\n", + " import static enums.Outcome.*;\n", + "enum RoShamBo6 implements Competitor {\n", + " PAPER, SCISSORS, ROCK;\n", + " private static Outcome[][] table = {\n", + " { DRAW, LOSE, WIN }, // PAPER\n", + " { WIN, DRAW, LOSE }, // SCISSORS\n", + " { LOSE, WIN, DRAW }, // ROCK\n", + " };\n", + " @Override\n", + " public Outcome compete(RoShamBo6 other) {\n", + " return table[this.ordinal()][other.ordinal()];\n", + " }\n", + " public static void main(String[] args) {\n", + " RoShamBo.play(RoShamBo6.class, 20);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ROCK vs. ROCK: DRAW\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "PAPER vs. PAPER: DRAW\n", + "PAPER vs. SCISSORS: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. SCISSORS: DRAW\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "ROCK vs. PAPER: LOSE\n", + "ROCK vs. SCISSORS: WIN\n", + "SCISSORS vs. ROCK: LOSE\n", + "PAPER vs. SCISSORS: LOSE\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN\n", + "SCISSORS vs. PAPER: WIN" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "table 与前一个例子中 initRow() 方法的调用次序完全相同。\n", + "\n", + "与前面一个例子相比,这个程序代码虽然简短,但表达能力却更强,部分原因是其代码更易于理解与修改,而且也更直接。不过,由于它使用的是数组,所以这种方式不太“安全”。如果使用一个大型数组,可能会不小心使用了错误的尺寸,而且,如果你的测试不能覆盖所有的可能性,有些错误可能会从你眼前溜过。\n", + "\n", + "事实上,以上所有的解决方案只是各种不同类型的表罢了。不过,分析各种表的表现形式,找出最适合的那一种,还是很有价值的。注意,虽然上例是最简洁的一种解决方案,但它也是相当僵硬的方案,因为它只能针对给定的常量输入产生常量输出。然而,也没有什么特别的理由阻止你用 table 来生成功能对象。对于某类问题而言,“表驱动式编码”的概念具有非常强大的功能。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "虽然枚举类型本身并不是特别复杂,但我还是将本章安排在全书比较靠后的位置,这是因为,程序员可以将 enum 与 Java 语言的其他功能结合使用,例如多态、泛型和反射。\n", + "\n", + "虽然 Java 中的枚举比 C 或 C++中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。而本章正好说明了一个“小”功能所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。正如我在本书中一再强调的那样,优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。\n", + "\n", + "关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 iterator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/23-Annotations.ipynb b/jupyter/23-Annotations.ipynb new file mode 100644 index 00000000..e8e766ce --- /dev/null +++ b/jupyter/23-Annotations.ipynb @@ -0,0 +1,2737 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "# 第二十三章 注解\n", + "\n", + "注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方式,使我们可以在稍后的某个时刻更容易的使用这些数据。\n", + "\n", + "注解在一定程度上是把元数据和源代码文件结合在一起的趋势所激发的,而不是保存在外部文档。这同样是对像 C# 语言对于 Java 语言特性压力的一种回应。\n", + "\n", + "注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。\n", + "\n", + "注解的语法十分简单,主要是在现有语法中添加 @ 符号。Java 5 引入了前三种定义在 **java.lang** 包中的注解:\n", + "\n", + "- **@Override**:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。\n", + "- **@Deprecated**:如果使用该注解的元素被调用,编译器就会发出警告信息。\n", + "- **@SuppressWarnings**:关闭不当的编译器警告信息。\n", + "- **@SafeVarargs**:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。\n", + "- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口\n", + "\n", + "还有 5 种额外的注解类型用于创造新的注解。你将会在这一章学习它们。\n", + "\n", + "每当创建涉及重复工作的类或接口时,你通常可以使用注解来自动化和简化流程。例如在 Enterprise JavaBean(EJB)中的许多额外工作就是通过注解来消除的。\n", + "\n", + "注解的出现可以替代一些现有的系统,例如 XDoclet,它是一种独立的文档化工具,专门设计用来生成注解风格的文档。与之相比,注解是真正语言层级的概念,以前构造出来就享有编译器的类型检查保护。注解在源代码级别保存所有信息而不是通过注释文字,这使得代码更加整洁和便于维护。通过使用拓展的 annotation API 或稍后在本章节可以看到的外部的字节码工具类库,你会拥有对源代码及字节码强大的检查与操作能力。\n", + "\n", + "\n", + "\n", + "## 基本语法\n", + "\n", + "\n", + "\n", + "在下面的例子中,使用 `@Test` 对 `testExecute()` 进行注解。该注解本身不做任何事情,但是编译器要保证其类路径上有 `@Test` 注解的定义。你将在本章看到,我们通过注解创建了一个工具用于运行这个方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/Testable.java\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "public class Testable {\n", + " public void execute() {\n", + " System.out.println(\"Executing..\");\n", + " }\n", + " @Test\n", + " void testExecute() { execute(); }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。\n", + "\n", + "### 定义注解\n", + "\n", + "如下是一个注解的定义。注解的定义看起来很像接口的定义。事实上,它们和其他 Java 接口一样,也会被编译成 class 文件。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/Test.java\n", + "// The @Test tag\n", + "package onjava.atunit;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.METHOD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface Test {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annoation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。\n", + "\n", + "注解通常会包含一些表示特定值的元素。当分析处理注解的时候,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,但是可以为其指定默认值。\n", + "\n", + "不包含任何元素的注解称为标记注解(marker annotation),例如上例中的 `@Test` 就是标记注解。\n", + "\n", + "下面是一个简单的注解,我们可以用它来追踪项目中的用例。程序员可以使用该注解来标注满足特定用例的一个方法或者一组方法。于是,项目经理可以通过统计已经实现的用例来掌控项目的进展,而开发者在维护项目时可以轻松的找到用例用于更新,或者他们可以调试系统中业务逻辑。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/UseCase.java\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.METHOD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface UseCase {\n", + " int id();\n", + " String description() default \"no description\";\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意 **id** 和 **description** 与方法定义类似。由于编译器会对 **id** 进行类型检查,因此将跟踪数据库与用例文档和源代码相关联是可靠的方式。**description** 元素拥有一个 **default** 值,如果在注解某个方法时没有给出 **description** 的值。则该注解的处理器会使用此元素的默认值。\n", + "\n", + "在下面的类中,有三个方法被注解为用例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/PasswordUtils.java\n", + "import java.util.*;\n", + "public class PasswordUtils {\n", + " @UseCase(id = 47, description =\n", + " \"Passwords must contain at least one numeric\")\n", + " public boolean validatePassword(String passwd) {\n", + " return (passwd.matches(\"\\\\w*\\\\d\\\\w*\"));\n", + " }\n", + " @UseCase(id = 48)\n", + " public String encryptPassword(String passwd) {\n", + " return new StringBuilder(passwd)\n", + " .reverse().toString();\n", + " }\n", + " @UseCase(id = 49, description =\n", + " \"New passwords can't equal previously used ones\")\n", + " public boolean checkForNewPassword(\n", + " List prevPasswords, String passwd) {\n", + " return !prevPasswords.contains(passwd);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注解的元素在使用时表现为 名-值 对的形式,并且需要放置在 `@UseCase` 声明之后的括号内。在 `encryptPassword()` 方法的注解中,并没有给出 **description** 的值,所以在 **@interface UseCase** 的注解处理器分析处理这个类的时候会使用该元素的默认值。\n", + "\n", + "你应该能够想象到如何使用这套工具来“勾勒”出将要建造的系统,然后在建造的过程中逐渐实现系统的各项功能。\n", + "\n", + "### 元注解\n", + "\n", + "Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元注解。元注解用于注解其他的注解\n", + "\n", + "| 注解 | 解释 |\n", + "| ----------- | ------------------------------------------------------------ |\n", + "| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明
**FIELD**:字段声明(包括 enum 实例)
**LOCAL_VARIABLE**:局部变量声明
**METHOD**:方法声明
**PACKAGE**:包声明
**PARAMETER**:参数声明
**TYPE**:类、接口(包括注解类型)或者 enum 声明 |\n", + "| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |\n", + "| @Documented | 将此注解保存在 Javadoc 中 |\n", + "| @Inherited | 允许子类继承父类的注解 |\n", + "| @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 |\n", + "\n", + "大多数时候,程序员定义自己的注解,并编写自己的处理器来处理他们。\n", + "\n", + "## 编写注解处理器\n", + "\n", + "如果没有用于读取注解的工具,那么注解不会比注释更有用。使用注解中一个很重要的部分就是,创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解。\n", + "\n", + "下面是一个非常简单的注解处理器,我们用它来读取被注解的 **PasswordUtils** 类,并且使用反射机制来寻找 **@UseCase** 标记。给定一组 **id** 值,然后列出在 **PasswordUtils** 中找到的用例,以及缺失的用例。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/UseCaseTracker.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.lang.reflect.*;\n", + "public class UseCaseTracker {\n", + " public static void\n", + " trackUseCases(List useCases, Class cl) {\n", + " for(Method m : cl.getDeclaredMethods()) {\n", + " UseCase uc = m.getAnnotation(UseCase.class);\n", + " if(uc != null) {\n", + " System.out.println(\"Found Use Case \" +\n", + " uc.id() + \"\\n \" + uc.description());\n", + " useCases.remove(Integer.valueOf(uc.id()));\n", + " }\n", + " }\n", + " useCases.forEach(i ->\n", + " System.out.println(\"Missing use case \" + i));\n", + " }\n", + " public static void main(String[] args) {\n", + " List useCases = IntStream.range(47, 51)\n", + " .boxed().collect(Collectors.toList());\n", + " trackUseCases(useCases, PasswordUtils.class);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Found Use Case 48\n", + "no description\n", + "Found Use Case 47\n", + "Passwords must contain at least one numeric\n", + "Found Use Case 49\n", + "New passwords can't equal previously used ones\n", + "Missing use case 50" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个程序用了两个反射的方法:`getDeclaredMethods()` 和 `getAnnotation()`,它们都属于 **AnnotatedElement** 接口(**Class**,**Method** 与 **Field** 类都实现了该接口)。`getAnnotation()` 方法返回指定类型的注解对象,在本例中就是 “**UseCase**”。如果被注解的方法上没有该类型的注解,返回值就为 **null**。我们通过调用 `id()` 和 `description()` 方法来提取元素值。注意 `encryptPassword()` 方法在注解的时候没有指定 **description** 的值,因此处理器在处理它对应的注解时,通过 `description()` 取得的是默认值 “no description”。\n", + "\n", + "### 注解元素\n", + "\n", + "在 **UseCase.java** 中定义的 **@UseCase** 的标签包含 int 元素 **id** 和 String 元素 **description**。注解元素可用的类型如下所示:\n", + "\n", + "- 所有基本类型(int、float、boolean等)\n", + "- String\n", + "- Class\n", + "- enum\n", + "- Annotation\n", + "- 以上类型的数组\n", + "\n", + "如果你使用了其他类型,编译器就会报错。注意,也不允许使用任何包装类型,但是由于自动装箱的存在,这不算是什么限制。注解也可以作为元素的类型。稍后你会看到,注解嵌套是一个非常有用的技巧。\n", + "\n", + "### 默认值限制\n", + "\n", + "编译器对于元素的默认值有些过于挑剔。首先,元素不能有不确定的值。也就是说,元素要么有默认值,要么就在使用注解时提供元素的值。\n", + "\n", + "这里有另外一个限制:任何非基本类型的元素, 无论是在源代码声明时还是在注解接口中定义默认值时,都不能使用 null 作为其值。这个限制使得处理器很难表现一个元素的存在或者缺失的状态,因为在每个注解的声明中,所有的元素都存在,并且具有相应的值。为了绕开这个约束,可以自定义一些特殊的值,比如空字符串或者负数用于表达某个元素不存在。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/SimulatingNull.java\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.METHOD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface SimulatingNull {\n", + " int id() default -1;\n", + " String description() default \"\";\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个在定义注解的习惯用法。\n", + "\n", + "### 生成外部文件\n", + "\n", + "当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。\n", + "\n", + "假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。\n", + "\n", + "以下是一个注解的定义,它告诉注解处理器应该创建一个数据库表:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/DBTable.java\n", + "package annotations.database;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.TYPE) // Applies to classes only\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface DBTable {\n", + " String name() default \"\";\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `@Target` 注解中指定的每一个 **ElementType** 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。你可以指定 **enum ElementType** 中的一个值,或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 **ElementType**,那么可以省去 `@Target` 注解,但是这并不常见。\n", + "\n", + "注意 **@DBTable** 中有一个 `name()` 元素,该注解通过这个元素为处理器创建数据库时提供表的名字。\n", + "\n", + "如下是修饰字段的注解:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/Constraints.java\n", + "package annotations.database;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.FIELD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface Constraints {\n", + " boolean primaryKey() default false;\n", + " boolean allowNull() default true;\n", + " boolean unique() default false;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/SQLString.java\n", + "package annotations.database;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.FIELD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface SQLString {\n", + " int value() default 0;\n", + " String name() default \"\";\n", + " Constraints constraints() default @Constraints;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/SQLInteger.java\n", + "package annotations.database;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.FIELD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface SQLInteger {\n", + " String name() default \"\";\n", + " Constraints constraints() default @Constraints;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@Constraints** 注解允许处理器提供数据库表的元数据。**@Constraints** 代表了数据库通常提供的约束的一小部分,但是它所要表达的思想已经很清楚了。`primaryKey()`,`allowNull()` 和 `unique()` 元素明显的提供了默认值,从而使得在大多数情况下,该注解的使用者不需要输入太多东西。\n", + "\n", + "另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过为为示例,两个元素足够了。\n", + "\n", + "这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值是 **@Constraints**。由于在 **@Constraints** 注解类型之后,没有在括号中指明 **@Constraints** 元素的值,因此,**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的 **@Constraints** 注解中的 `unique()` 元素为 true,并作为 `constraints()` 元素的默认值,你可以像如下定义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/Uniqueness.java\n", + "// Sample of nested annotations\n", + "package annotations.database;\n", + "public @interface Uniqueness {\n", + " Constraints constraints()\n", + " default @Constraints(unique = true);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面是一个简单的,使用了如上注解的类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/Member.java\n", + "package annotations.database;\n", + "@DBTable(name = \"MEMBER\")\n", + "public class Member {\n", + " @SQLString(30) String firstName;\n", + " @SQLString(50) String lastName;\n", + " @SQLInteger Integer age;\n", + " @SQLString(value = 30,\n", + " constraints = @Constraints(primaryKey = true))\n", + " String reference;\n", + " static int memberCount;\n", + " public String getReference() { return reference; }\n", + " public String getFirstName() { return firstName; }\n", + " public String getLastName() { return lastName; }\n", + " @Override\n", + " public String toString() { return reference; }\n", + " public Integer getAge() { return age; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@SQLString(30)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "处理器将在创建表的时候使用该值设置 SQL 列的大小。\n", + "\n", + "默认值的语法虽然很灵巧,但是它很快就变的复杂起来。以 **reference** 字段的注解为例,上面拥有 **@SQLString** 注解,但是这个字段也将成为表的主键,因此在嵌入的 **@Constraint** 注解中设定 **primaryKey** 元素的值。这时事情就变的复杂了。你不得不为这个嵌入的注解使用很长的键—值对的形式,来指定元素名称和 **@interface** 的名称。同时,由于有特殊命名的 **value** 也不是唯一需要赋值的元素,因此不能再使用快捷方式特性。如你所见,最终结果不算清晰易懂。\n", + "\n", + "### 替代方案\n", + "\n", + "可以使用多种不同的方式来定义自己的注解用于上述任务。例如,你可以使用一个单一的注解类 **@TableColumn**,它拥有一个 **enum** 元素,元素值定义了 **STRING**,**INTEGER**,**FLOAT** 等类型。这消除了每个 SQL 类型都需要定义一个 **@interface** 的负担,不过也使得用额外信息修饰 SQL 类型变的不可能,这些额外的信息例如长度或精度等,都可能是非常有用的。\n", + "\n", + "你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解助力器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。\n", + "\n", + "第三种可行的方案是一起使用两个注解,**@Constraints** 和相应的 SQL 类型(例如,**@SQLInteger**)去注解同一个字段。这可能会让代码有些混乱,但是编译器允许你对同一个目标使用多个注解。在 Java 8,在使用多个注解的时候,你可以重复使用同一个注解。\n", + "\n", + "### 注解不支持继承\n", + "\n", + "你不能使用 **extends** 关键字来继承 **@interfaces**。这真是一个遗憾,如果可以定义 **@TableColumn** 注解(参考前面的建议),同时嵌套一个 **@SQLType** 类型的注解,将成为一个优雅的设计。按照这种方式,你可以通过继承 **@SQLType** 来创造各种 SQL 类型。例如 **@SQLInteger** 和 **@SQLString**。如果支持继承,就会大大减少打字的工作量并且使得语法更整洁。在 Java 的未来版本中,似乎没有任何关于让注解支持继承的提案,所以在当前情况下,上例中的解决方案可能已经是最佳方案了。\n", + "\n", + "### 实现处理器\n", + "\n", + "下面是一个注解处理器的例子,他将读取一个类文件,检查上面的数据库注解,并生成用于创建数据库的 SQL 命令:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/database/TableCreator.java\n", + "// Reflection-based annotation processor\n", + "// {java annotations.database.TableCreator\n", + "// annotations.database.Member}\n", + "package annotations.database;\n", + "\n", + "import java.lang.annotation.Annotation;\n", + "import java.lang.reflect.Field;\n", + "import java.util.ArrayList;\n", + "import java.util.List;\n", + "\n", + "public class TableCreator {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " if (args.length < 1) {\n", + " System.out.println(\n", + " \"arguments: annotated classes\");\n", + " System.exit(0);\n", + " }\n", + " for (String className : args) {\n", + " Class cl = Class.forName(className);\n", + " DBTable dbTable = cl.getAnnotation(DBTable.class);\n", + " if (dbTable == null) {\n", + " System.out.println(\n", + " \"No DBTable annotations in class \" +\n", + " className);\n", + " continue;\n", + " }\n", + " String tableName = dbTable.name();\n", + " // If the name is empty, use the Class name:\n", + " if (tableName.length() < 1)\n", + " tableName = cl.getName().toUpperCase();\n", + " List columnDefs = new ArrayList<>();\n", + " for (Field field : cl.getDeclaredFields()) {\n", + " String columnName = null;\n", + " Annotation[] anns =\n", + " field.getDeclaredAnnotations();\n", + " if (anns.length < 1)\n", + " continue; // Not a db table column\n", + " if (anns[0] instanceof SQLInteger) {\n", + " SQLInteger sInt = (SQLInteger) anns[0];\n", + " // Use field name if name not specified\n", + " if (sInt.name().length() < 1)\n", + " columnName = field.getName().toUpperCase();\n", + " else\n", + " columnName = sInt.name();\n", + " columnDefs.add(columnName + \" INT\" +\n", + " getConstraints(sInt.constraints()));\n", + " }\n", + " if (anns[0] instanceof SQLString) {\n", + " SQLString sString = (SQLString) anns[0];\n", + " // Use field name if name not specified.\n", + " if (sString.name().length() < 1)\n", + " columnName = field.getName().toUpperCase();\n", + " else\n", + " columnName = sString.name();\n", + " columnDefs.add(columnName + \" VARCHAR(\" +\n", + " sString.value() + \")\" +\n", + " getConstraints(sString.constraints()));\n", + " }\n", + " StringBuilder createCommand = new StringBuilder(\n", + " \"CREATE TABLE \" + tableName + \"(\");\n", + " for (String columnDef : columnDefs)\n", + " createCommand.append(\n", + " \"\\n \" + columnDef + \",\");\n", + " // Remove trailing comma\n", + " String tableCreate = createCommand.substring(\n", + " 0, createCommand.length() - 1) + \");\";\n", + " System.out.println(\"Table Creation SQL for \" +\n", + " className + \" is:\\n\" + tableCreate);\n", + " }\n", + " }\n", + " }\n", + "\n", + " private static String getConstraints(Constraints con) {\n", + " String constraints = \"\";\n", + " if (!con.allowNull())\n", + " constraints += \" NOT NULL\";\n", + " if (con.primaryKey())\n", + " constraints += \" PRIMARY KEY\";\n", + " if (con.unique())\n", + " constraints += \" UNIQUE\";\n", + " return constraints;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "sql" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Table Creation SQL for annotations.database.Member is:\n", + "CREATE TABLE MEMBER(\n", + " FIRSTNAME VARCHAR(30));\n", + "Table Creation SQL for annotations.database.Member is:\n", + "CREATE TABLE MEMBER(\n", + " FIRSTNAME VARCHAR(30),\n", + " LASTNAME VARCHAR(50));\n", + "Table Creation SQL for annotations.database.Member is:\n", + "CREATE TABLE MEMBER(\n", + " FIRSTNAME VARCHAR(30),\n", + " LASTNAME VARCHAR(50),\n", + " AGE INT);\n", + "Table Creation SQL for annotations.database.Member is:\n", + "CREATE TABLE MEMBER(\n", + " FIRSTNAME VARCHAR(30),\n", + " LASTNAME VARCHAR(50),\n", + " AGE INT,\n", + " REFERENCE VARCHAR(30) PRIMARY KEY);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "主方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载,并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在,将表名存储起来。然后读取这个类的所有字段,并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话,在对应的处理块中将构造出相应的数据库列的字符串片段。注意,由于注解没有继承机制,如果要获取近似多态的行为,使用 `getDeclaredAnnotations()` 似乎是唯一的方式。\n", + "\n", + "嵌套的 **@Constraint** 注解被传递给 `getConstraints()`方法,并用它来构造一个包含 SQL 约束的 String 对象。\n", + "\n", + "需要提醒的是,上面演示的技巧对于真实的对象/映射关系而言,是十分幼稚的。使用 **@DBTable** 的注解来获取表的名称,这使得如果要修改表的名字,则迫使你重新编译 Java 代码。这种效果并不理想。现在已经有了很多可用的框架,用于将对象映射到数据库中,并且越来越多的框架开始使用注解了。\n", + "\n", + "\n", + "\n", + "## 使用javac处理注解\n", + "\n", + "通过 **javac**,你可以通过创建编译时(compile-time)注解处理器在 Java 源文件上使用注解,而不是编译之后的 class 文件。但是这里有一个重大限制:你不能通过处理器来改变源代码。唯一影响输出的方式就是创建新的文件。\n", + "\n", + "如果你的注解处理器创建了新的源文件,在新一轮处理中注解会检查源文件本身。工具在检测一轮之后持续循环,直到不再有新的源文件产生。然后它编译所有的源文件。\n", + "\n", + "每一个你编写的注解都需要处理器,但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类,并且你可以添加监听器用于监听注解处理完成后接到通知。\n", + "\n", + "本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和StackOverflow 的准备。\n", + "\n", + "### 最简单的处理器\n", + "\n", + "让我们开始定义我们能想到的最简单的处理器,只是为了编译和测试。如下是注解的定义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/simplest/Simple.java\n", + "// A bare-bones annotation\n", + "package annotations.simplest;\n", + "import java.lang.annotation.Retention;\n", + "import java.lang.annotation.RetentionPolicy;\n", + "import java.lang.annotation.Target;\n", + "import java.lang.annotation.ElementType;\n", + "@Retention(RetentionPolicy.SOURCE)\n", + "@Target({ElementType.TYPE, ElementType.METHOD,\n", + " ElementType.CONSTRUCTOR,\n", + " ElementType.ANNOTATION_TYPE,\n", + " ElementType.PACKAGE, ElementType.FIELD,\n", + " ElementType.LOCAL_VARIABLE})\n", + "public @interface Simple {\n", + " String value() default \"-default-\";\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@Retention** 的参数现在为 **SOURCE**,这意味着注解不会再存留在编译后的代码。这在编译时处理注解是没有必要的,它只是指出,在这里,**javac** 是唯一有机会处理注解的代理。\n", + "\n", + "**@Target** 声明了几乎所有的目标类型(除了 **PACKAGE**) ,同样是为了演示。下面是一个测试示例。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/simplest/SimpleTest.java\n", + "// Test the \"Simple\" annotation\n", + "// {java annotations.simplest.SimpleTest}\n", + "package annotations.simplest;\n", + "@Simple\n", + "public class SimpleTest {\n", + " @Simple\n", + " int i;\n", + " @Simple\n", + " public SimpleTest() {}\n", + " @Simple\n", + " public void foo() {\n", + " System.out.println(\"SimpleTest.foo()\");\n", + " }\n", + " @Simple\n", + " public void bar(String s, int i, float f) {\n", + " System.out.println(\"SimpleTest.bar()\");\n", + " }\n", + " @Simple\n", + " public static void main(String[] args) {\n", + " @Simple\n", + " SimpleTest st = new SimpleTest();\n", + " st.foo();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "SimpleTest.foo()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里我们使用 **@Simple** 注解了所有 **@Target** 声明允许的地方。\n", + "\n", + "**SimpleTest.java** 只需要 **Simple.java** 就可以编译成功。当我们编译的时候什么都没有发生。\n", + "\n", + "**javac** 允许 **@Simple** 注解(只要它存在)在我们创建处理器并将其 hook 到编译器之前,不做任何事情。\n", + "\n", + "如下是一个十分简单的处理器,其所作的事情就是把注解相关的信息打印出来:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/simplest/SimpleProcessor.java\n", + "// A bare-bones annotation processor\n", + "package annotations.simplest;\n", + "import javax.annotation.processing.*;\n", + "import javax.lang.model.SourceVersion;\n", + "import javax.lang.model.element.*;\n", + "import java.util.*;\n", + "@SupportedAnnotationTypes(\n", + " \"annotations.simplest.Simple\")\n", + "@SupportedSourceVersion(SourceVersion.RELEASE_8)\n", + "public class SimpleProcessor\n", + " extends AbstractProcessor {\n", + " @Override\n", + " public boolean process(\n", + " Set annotations,\n", + " RoundEnvironment env) {\n", + " for(TypeElement t : annotations)\n", + " System.out.println(t);\n", + " for(Element el :\n", + " env.getElementsAnnotatedWith(Simple.class))\n", + " display(el);\n", + " return false;\n", + " }\n", + " private void display(Element el) {\n", + " System.out.println(\"==== \" + el + \" ====\");\n", + " System.out.println(el.getKind() +\n", + " \" : \" + el.getModifiers() +\n", + " \" : \" + el.getSimpleName() +\n", + " \" : \" + el.asType());\n", + " if(el.getKind().equals(ElementKind.CLASS)) {\n", + " TypeElement te = (TypeElement)el;\n", + " System.out.println(te.getQualifiedName());\n", + " System.out.println(te.getSuperclass());\n", + " System.out.println(te.getEnclosedElements());\n", + " }\n", + " if(el.getKind().equals(ElementKind.METHOD)) {\n", + " ExecutableElement ex = (ExecutableElement)el;\n", + " System.out.print(ex.getReturnType() + \" \");\n", + " System.out.print(ex.getSimpleName() + \"(\");\n", + " System.out.println(ex.getParameters() + \")\");\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的示例关于注解如何简化你的代码)。\n", + "\n", + "你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。\n", + "\n", + "**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。\n", + "\n", + "动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。\n", + "\n", + "如果只是通过平常的方式来编译 **SimpleTest.java**,你不会得到任何结果。为了得到注解输出,你必须增加一个 **processor** 标志并且连接注解处理器类" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "javac -processor annotations.simplest.SimpleProcessor SimpleTest.java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在编译器有了输出" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.simplest.Simple\n", + "==== annotations.simplest.SimpleTest ====\n", + "CLASS : [public] : SimpleTest : annotations.simplest.SimpleTest\n", + "annotations.simplest.SimpleTest\n", + "java.lang.Object\n", + "i,SimpleTest(),foo(),bar(java.lang.String,int,float),main(java.lang.String[])\n", + "==== i ====\n", + "FIELD : [] : i : int\n", + "==== SimpleTest() ====\n", + "CONSTRUCTOR : [public] : : ()void\n", + "==== foo() ====\n", + "METHOD : [public] : foo : ()void\n", + "void foo()\n", + "==== bar(java.lang.String,int,float) ====\n", + "METHOD : [public] : bar : (java.lang.String,int,float)void\n", + "void bar(s,i,f)\n", + "==== main(java.lang.String[]) ====\n", + "METHOD : [public, static] : main : (java.lang.String[])void\n", + "void main(args)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这给了你一些可以发现的东西,包括参数名和类型、返回值等。\n", + "\n", + "### 更复杂的处理器\n", + "\n", + "当你创建用于 javac 注解处理器时,你不能使用 Java 的反射特性,因为你处理的是源代码,而并非是编译后的 class 文件。各种 mirror[^3 ] 解决这个问题的方法是,通过允许你在未编译的源代码中查看方法、字段和类型。\n", + "\n", + "如下是一个用于提取类中方法的注解,所以它可以被抽取成为一个接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/ifx/ExtractInterface.java\n", + "// javac-based annotation processing\n", + "package annotations.ifx;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.TYPE)\n", + "@Retention(RetentionPolicy.SOURCE)\n", + "public @interface ExtractInterface {\n", + " String interfaceName() default \"-!!-\";\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**RetentionPolicy** 的值为 **SOURCE**,这是为了在提取类中的接口之后不再将注解信息保留在 class 文件中。接下来的测试类提供了一些公用方法,这些方法可以成为接口的一部分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/ifx/Multiplier.java\n", + "// javac-based annotation processing\n", + "// {java annotations.ifx.Multiplier}\n", + "package annotations.ifx;\n", + "@ExtractInterface(interfaceName=\"IMultiplier\")\n", + "public class Multiplier {\n", + " public boolean flag = false;\n", + " private int n = 0;\n", + " public int multiply(int x, int y) {\n", + " int total = 0;\n", + " for(int i = 0; i < x; i++)\n", + " total = add(total, y);\n", + " return total;\n", + " }\n", + " public int fortySeven() { return 47; }\n", + " private int add(int x, int y) {\n", + " return x + y;\n", + " }\n", + " public double timesTen(double arg) {\n", + " return arg * 10;\n", + " }\n", + " public static void main(String[] args) {\n", + " Multiplier m = new Multiplier();\n", + " System.out.println(\n", + " \"11 * 16 = \" + m.multiply(11, 16));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "11 * 16 = 176" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Multiplier** 类(只能处理正整数)拥有一个 `multiply()` 方法,这个方法会多次调用私有方法 `add()` 来模拟乘法操作。` add()` 是私有方法,因此不能成为接口的一部分。其他的方法提供了语法多样性。注解被赋予 **IMultiplier** 的 **InterfaceName** 作为要创建的接口的名称。\n", + "\n", + "这里有一个编译时处理器用于提取有趣的方法,并创建一个新的 interface 源代码文件(这个源文件将会在下一轮中被自动编译):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/ifx/IfaceExtractorProcessor.java\n", + "// javac-based annotation processing\n", + "package annotations.ifx;\n", + "import javax.annotation.processing.*;\n", + "import javax.lang.model.SourceVersion;\n", + "import javax.lang.model.element.*;\n", + "import javax.lang.model.util.*;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.io.*;\n", + "@SupportedAnnotationTypes(\n", + " \"annotations.ifx.ExtractInterface\")\n", + "@SupportedSourceVersion(SourceVersion.RELEASE_8)\n", + "public class IfaceExtractorProcessor\n", + " extends AbstractProcessor {\n", + " private ArrayList\n", + " interfaceMethods = new ArrayList<>();\n", + " Elements elementUtils;\n", + " private ProcessingEnvironment processingEnv;\n", + " @Override\n", + " public void init(\n", + " ProcessingEnvironment processingEnv) {\n", + " this.processingEnv = processingEnv;\n", + " elementUtils = processingEnv.getElementUtils();\n", + " }\n", + " @Override\n", + " public boolean process(\n", + " Set annotations,\n", + " RoundEnvironment env) {\n", + " for(Element elem:env.getElementsAnnotatedWith(\n", + " ExtractInterface.class)) {\n", + " String interfaceName = elem.getAnnotation(\n", + " ExtractInterface.class).interfaceName();\n", + " for(Element enclosed :\n", + " elem.getEnclosedElements()) {\n", + " if(enclosed.getKind()\n", + " .equals(ElementKind.METHOD) &&\n", + " enclosed.getModifiers()\n", + " .contains(Modifier.PUBLIC) &&\n", + " !enclosed.getModifiers()\n", + " .contains(Modifier.STATIC)) {\n", + " interfaceMethods.add(enclosed);\n", + " }\n", + " }\n", + " if(interfaceMethods.size() > 0)\n", + " writeInterfaceFile(interfaceName);\n", + " }\n", + " return false;\n", + " }\n", + " private void\n", + " writeInterfaceFile(String interfaceName) {\n", + " try(\n", + " Writer writer = processingEnv.getFiler()\n", + " .createSourceFile(interfaceName)\n", + " .openWriter()\n", + " ) {\n", + " String packageName = elementUtils\n", + " .getPackageOf(interfaceMethods\n", + " .get(0)).toString();\n", + " writer.write(\n", + " \"package \" + packageName + \";\\n\");\n", + " writer.write(\"public interface \" +\n", + " interfaceName + \" {\\n\");\n", + " for(Element elem : interfaceMethods) {\n", + " ExecutableElement method =\n", + " (ExecutableElement)elem;\n", + " String signature = \" public \";\n", + " signature += method.getReturnType() + \" \";\n", + " signature += method.getSimpleName();\n", + " signature += createArgList(\n", + " method.getParameters());\n", + " System.out.println(signature);\n", + " writer.write(signature + \";\\n\");\n", + " }\n", + " writer.write(\"}\");\n", + " } catch(Exception e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " private String createArgList(\n", + " List parameters) {\n", + " String args = parameters.stream()\n", + " .map(p -> p.asType() + \" \" + p.getSimpleName())\n", + " .collect(Collectors.joining(\", \"));\n", + " return \"(\" + args + \")\";\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Elements** 对象实例 **elementUtils** 是一组静态方法的工具;我们用它来寻找 **writeInterfaceFile()** 中含有的包名。\n", + "\n", + "`getEnclosedElements()`方法会通过指定的元素生成所有的“闭包”元素。在这里,这个类闭包了它的所有元素。通过使用 `getKind()` 我们会找到所有的 **public** 和 **static** 方法,并将其添加到 **interfaceMethods** 列表中。接下来 `writeInterfaceFile()` 使用 **interfaceMethods** 列表里面的值生成新的接口定义。注意,在 `writeInterfaceFile()` 使用了向下转型到 **ExecutableElement**,这使得我们可以获取所有的方法信息。**createArgList()** 是一个帮助方法,用于生成参数列表。\n", + "\n", + "**Filer**是 `getFiler()` 生成的,并且是 **PrintWriter** 的一种实例,可以用于创建新文件。我们使用 **Filer** 对象,而不是原生的 **PrintWriter** 原因是,这个对象可以运行 **javac** 追踪你创建的新文件,这使得它可以在新一轮中检查新文件中的注解并编译文件。\n", + "\n", + "如下是一个命令行,可以在编译的时候使用处理器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "javac -processor annotations.ifx.IfaceExtractorProcessor Multiplier.java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "新生成的 **IMultiplier.java** 的文件,正如你通过查看上面处理器的 `println()` 语句所猜测的那样,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "package annotations.ifx;\n", + "public interface IMultiplier {\n", + " public int multiply(int x, int y);\n", + " public int fortySeven();\n", + " public double timesTen(double arg);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个类同样会被 **javac** 编译(在某一轮中),所以你会在同一个目录中看到 **IMultiplier.class** 文件。\n", + "\n", + "\n", + "\n", + "## 基于注解的单元测试\n", + "\n", + "单元测试是对类中每个方法提供一个或者多个测试的一种事件,其目的是为了有规律的测试一个类中每个部分是否具备正确的行为。在 Java 中,最著名的单元测试工具就是 **JUnit**。**JUnit** 4 版本已经包含了注解。在注解版本之前的 JUnit 一个最主要的问题是,为了启动和运行 **JUnit** 测试,有大量的“仪式”需要标注。这种负担已经减轻了一些,**但是**注解使得测试更接近“可以工作的最简单的测试系统”。\n", + "\n", + "在注解版本之前的 JUnit,你必须创建一个单独的文件来保存单元测试。通过注解,我们可以将单元测试集成在需要被测试的类中,从而将单元测试的时间和麻烦降到了最低。这种方式有额外的好处,就是使得测试私有方法和公有方法变的一样容易。\n", + "\n", + "这个基于注解的测试框架叫做 **@Unit**。其最基本的测试形式,可能也是你使用的最多的一个注解是 **@Test**,我们使用 **@Test** 来标记测试方法。测试方法不带参数,并返回 **boolean** 结果来说明测试方法成功或者失败。你可以任意命名它的测试方法。同时 **@Unit** 测试方法可以是任意你喜欢的访问修饰方法,包括 **private**。\n", + "\n", + "要使用 **@Unit**,你必须导入 **onjava.atunit** 包,并且使用 **@Unit** 的测试标记为合适的方法和字段打上标签(在接下来的例子中你会学到),然后让你的构建系统对编译后的类运行 **@Unit**,下面是一个简单的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AtUnitExample1.java\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AtUnitExample1.class}\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AtUnitExample1 {\n", + " public String methodOne() {\n", + " return \"This is methodOne\";\n", + " }\n", + " public int methodTwo() {\n", + " System.out.println(\"This is methodTwo\");\n", + " return 2;\n", + " }\n", + " @Test\n", + " boolean methodOneTest() {\n", + " return methodOne().equals(\"This is methodOne\");\n", + " }\n", + " @Test\n", + " boolean m2() { return methodTwo() == 2; }\n", + " @Test\n", + " private boolean m3() { return true; }\n", + " // Shows output for failure:\n", + " @Test\n", + " boolean failureTest() { return false; }\n", + " @Test\n", + " boolean anotherDisappointment() {\n", + " return false;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AtUnitExample1\n", + ". m3\n", + ". methodOneTest\n", + ". m2 This is methodTwo\n", + ". failureTest (failed)\n", + ". anotherDisappointment (failed)\n", + "(5 tests)\n", + ">>> 2 FAILURES <<<\n", + "annotations.AtUnitExample1: failureTest\n", + "annotations.AtUnitExample1: anotherDisappointment" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 **@Unit** 进行测试的类必须定义在某个包中(即必须包括 **package** 声明)。\n", + "\n", + "**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 a`notherDisappointment()` 方法之前,它们告诉 **@Unit** 方法作为单元测试来运行。同时 **@Test** 确保这些方法没有任何参数并且返回值为 **boolean** 或者 **void**。当你填写单元测试时,唯一需要做的就是决定测试是成功还是失败,(对于返回值为 **boolean** 的方法)应该返回 **ture** 还是 **false**。\n", + "\n", + "如果你熟悉 **JUnit**,你还将注意到 **@Unit** 输出的信息更多。你会看到现在正在运行的测试的输出更有用,最后它会告诉你导致失败的类和测试。\n", + "\n", + "你并非必须将测试方法嵌入到原来的类中,有时候这种事情根本做不到。要生产一个非嵌入式的测试,最简单的方式就是继承:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AUExternalTest.java\n", + "// Creating non-embedded tests\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AUExternalTest.class}\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AUExternalTest extends AtUnitExample1 {\n", + " @Test\n", + " boolean _MethodOne() {\n", + " return methodOne().equals(\"This is methodOne\");\n", + " }\n", + " @Test\n", + " boolean _MethodTwo() {\n", + " return methodTwo() == 2;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AUExternalTest\n", + ". tMethodOne\n", + ". tMethodTwo This is methodTwo\n", + "OK (2 tests)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个示例还表现出灵活命名的价值。在这里,**@Test** 方法被命名为下划线前缀加上要测试的方法名称(我并不认为这是一种理想的命名形式,这只是表现一种可能性罢了)。\n", + "\n", + "你也可以使用组合来创建非嵌入式的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AUComposition.java\n", + "// Creating non-embedded tests\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AUComposition.class}\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AUComposition {\n", + " AtUnitExample1 testObject = new AtUnitExample1();\n", + " @Test\n", + " boolean tMethodOne() {\n", + " return testObject.methodOne()\n", + " .equals(\"This is methodOne\");\n", + " }\n", + " @Test\n", + " boolean tMethodTwo() {\n", + " return testObject.methodTwo() == 2;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AUComposition\n", + ". tMethodTwo This is methodTwo\n", + ". tMethodOne\n", + "OK (2 tests)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为在每一个测试里面都会创建 **AUComposition** 对象,所以创建新的成员变量 **testObject** 用于以后的每一个测试方法。\n", + "\n", + "因为 **@Unit** 中没有 **JUnit** 中特殊的 **assert** 方法,不过另一种形式的 **@Test** 方法仍然允许返回值为 **void**(如果你还想使用 **true** 或者 **false** 的话,也可以使用 **boolean** 作为方法返回值类型)。为了表示测试成功,可以使用 Java 的 **assert** 语句。Java 断言机制需要你在 java 命令行行加上 **-ea** 标志来开启,但是 **@Unit** 已经自动开启了该功能。要表示测试失败的话,你甚至可以使用异常。**@Unit** 的设计目标之一就是尽可能减少添加额外的语法,而 Java 的 **assert** 和异常对于报告错误而言,即已经足够了。一个失败的 **assert** 或者从方法从抛出的异常都被视为测试失败,但是 **@Unit** 不会在这个失败的测试上卡住,它会继续运行,直到所有测试完毕,下面是一个示例程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AtUnitExample2.java\n", + "// Assertions and exceptions can be used in @Tests\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AtUnitExample2.class}\n", + "package annotations;\n", + "import java.io.*;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AtUnitExample2 {\n", + " public String methodOne() {\n", + " return \"This is methodOne\";\n", + " }\n", + " public int methodTwo() {\n", + " System.out.println(\"This is methodTwo\");\n", + " return 2;\n", + " }\n", + " @Test\n", + " void assertExample() {\n", + " assert methodOne().equals(\"This is methodOne\");\n", + " }\n", + " @Test\n", + " void assertFailureExample() {\n", + " assert 1 == 2: \"What a surprise!\";\n", + " }\n", + " @Test\n", + " void exceptionExample() throws IOException {\n", + " try(FileInputStream fis =\n", + " new FileInputStream(\"nofile.txt\")) {} // Throws\n", + " }\n", + " @Test\n", + " boolean assertAndReturn() {\n", + " // Assertion with message:\n", + " assert methodTwo() == 2: \"methodTwo must equal 2\";\n", + " return methodOne().equals(\"This is methodOne\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AtUnitExample2\n", + ". exceptionExample java.io.FileNotFoundException:\n", + "nofile.txt (The system cannot find the file specified)\n", + "(failed)\n", + ". assertExample\n", + ". assertAndReturn This is methodTwo\n", + ". assertFailureExample java.lang.AssertionError: What\n", + "a surprise!\n", + "(failed)\n", + "(4 tests)\n", + ">>> 2 FAILURES <<<\n", + "annotations.AtUnitExample2: exceptionExample\n", + "annotations.AtUnitExample2: assertFailureExample" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如下是一个使用非嵌入式测试的例子,并且使用了断言,它将会对 **java.util.HashSet** 进行一些简单的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/HashSetTest.java\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/HashSetTest.class}\n", + "package annotations;\n", + "import java.util.*;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class HashSetTest {\n", + " HashSet testObject = new HashSet<>();\n", + " @Test\n", + " void initialization() {\n", + " assert testObject.isEmpty();\n", + " }\n", + " @Test\n", + " void _Contains() {\n", + " testObject.add(\"one\");\n", + " assert testObject.contains(\"one\");\n", + " }\n", + " @Test\n", + " void _Remove() {\n", + " testObject.add(\"one\");\n", + " testObject.remove(\"one\");\n", + " assert testObject.isEmpty();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "采用继承的方式可能会更简单,也没有一些其他的约束。\n", + "\n", + "对每一个单元测试而言,**@Unit** 都会使用默认的无参构造器,为该测试类所属的类创建出一个新的实例。并在此新创建的对象上运行测试,然后丢弃该对象,以免对其他测试产生副作用。如此创建对象导致我们依赖于类的默认构造器。如果你的类没有默认构造器,或者对象需要复杂的构造过程,那么你可以创建一个 **static** 方法专门负责构造对象,然后使用 **@TestObjectCreate** 注解标记该方法,例子如下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AtUnitExample3.java\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AtUnitExample3.class}\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AtUnitExample3 {\n", + " private int n;\n", + " public AtUnitExample3(int n) { this.n = n; }\n", + " public int getN() { return n; }\n", + " public String methodOne() {\n", + " return \"This is methodOne\";\n", + " }\n", + " public int methodTwo() {\n", + " System.out.println(\"This is methodTwo\");\n", + " return 2;\n", + " }\n", + " @TestObjectCreate\n", + " static AtUnitExample3 create() {\n", + " return new AtUnitExample3(47);\n", + " }\n", + " @Test\n", + " boolean initialization() { return n == 47; }\n", + " @Test\n", + " boolean methodOneTest() {\n", + " return methodOne().equals(\"This is methodOne\");\n", + " }\n", + " @Test\n", + " boolean m2() { return methodTwo() == 2; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AtUnitExample3\n", + ". initialization\n", + ". m2 This is methodTwo\n", + ". methodOneTest\n", + "OK (3 tests)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@TestObjectCreate** 修饰的方法必须声明为 **static** ,且必须返回一个你正在测试的类型对象,这一切都由 **@Unit** 负责确保成立。\n", + "\n", + "有的时候,你需要向单元测试中增加一些字段。这时候可以使用 **@TestProperty** 注解,由它注解的字段表示只在单元测试中使用(因此,在你将产品发布给客户之前,他们应该被删除)。在下面的例子中,一个 **String** 通过 `String.split()` 方法进行分割,从其中读取一个值,这个值将会被生成测试对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AtUnitExample4.java\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AtUnitExample4.class}\n", + "// {VisuallyInspectOutput}\n", + "package annotations;\n", + "import java.util.*;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AtUnitExample4 {\n", + " static String theory = \"All brontosauruses \" +\n", + " \"are thin at one end, much MUCH thicker in the \" +\n", + " \"middle, and then thin again at the far end.\";\n", + " private String word;\n", + " private Random rand = new Random(); // Time-based seed\n", + " public AtUnitExample4(String word) {\n", + " this.word = word;\n", + " }\n", + " public String getWord() { return word; }\n", + " public String scrambleWord() {\n", + " List chars = Arrays.asList(\n", + " ConvertTo.boxed(word.toCharArray()));\n", + " Collections.shuffle(chars, rand);\n", + " StringBuilder result = new StringBuilder();\n", + " for(char ch : chars)\n", + " result.append(ch);\n", + " return result.toString();\n", + " }\n", + " @TestProperty\n", + " static List input =\n", + " Arrays.asList(theory.split(\" \"));\n", + " @TestProperty\n", + " static Iterator words = input.iterator();\n", + " @TestObjectCreate\n", + " static AtUnitExample4 create() {\n", + " if(words.hasNext())\n", + " return new AtUnitExample4(words.next());\n", + " else\n", + " return null;\n", + " }\n", + " @Test\n", + " boolean words() {\n", + " System.out.println(\"'\" + getWord() + \"'\");\n", + " return getWord().equals(\"are\");\n", + " }\n", + " @Test\n", + " boolean scramble1() {\n", + "// Use specific seed to get verifiable results:\n", + " rand = new Random(47);\n", + " System.out.println(\"'\" + getWord() + \"'\");\n", + " String scrambled = scrambleWord();\n", + " System.out.println(scrambled);\n", + " return scrambled.equals(\"lAl\");\n", + " }\n", + " @Test\n", + " boolean scramble2() {\n", + " rand = new Random(74);\n", + " System.out.println(\"'\" + getWord() + \"'\");\n", + " String scrambled = scrambleWord();\n", + " System.out.println(scrambled);\n", + " return scrambled.equals(\"tsaeborornussu\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AtUnitExample4\n", + ". words 'All'\n", + "(failed)\n", + ". scramble1 'brontosauruses'\n", + "ntsaueorosurbs\n", + "(failed)\n", + ". scramble2 'are'\n", + "are\n", + "(failed)\n", + "(3 tests)\n", + ">>> 3 FAILURES <<<\n", + "annotations.AtUnitExample4: words\n", + "annotations.AtUnitExample4: scramble1\n", + "annotations.AtUnitExample4: scramble2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**@TestProperty** 也可以用来标记那些只在测试中使用的方法,但是它们本身不是测试方法。\n", + "\n", + "如果你的测试对象需要执行某些初始化工作,并且使用完成之后还需要执行清理工作,那么可以选择使用 **static** 的 **@TestObjectCleanup** 方法,当测试对象使用结束之后,该方法会为你执行清理工作。在下面的示例中,**@TestObjectCleanup** 为每一个测试对象都打开了一个文件,因此必须在丢弃测试的时候关闭该文件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/AtUnitExample5.java\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/AtUnitExample5.class}\n", + "package annotations;\n", + "import java.io.*;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class AtUnitExample5 {\n", + " private String text;\n", + " public AtUnitExample5(String text) {\n", + " this.text = text;\n", + " }\n", + " @Override\n", + " public String toString() { return text; }\n", + " @TestProperty\n", + " static PrintWriter output;\n", + " @TestProperty\n", + " static int counter;\n", + " @TestObjectCreate\n", + " static AtUnitExample5 create() {\n", + " String id = Integer.toString(counter++);\n", + " try {\n", + " output = new PrintWriter(\"Test\" + id + \".txt\");\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " return new AtUnitExample5(id);\n", + " }\n", + " @TestObjectCleanup\n", + " static void cleanup(AtUnitExample5 tobj) {\n", + " System.out.println(\"Running cleanup\");\n", + " output.close();\n", + " }\n", + " @Test\n", + " boolean test1() {\n", + " output.print(\"test1\");\n", + " return true;\n", + " }\n", + " @Test\n", + " boolean test2() {\n", + " output.print(\"test2\");\n", + " return true;\n", + " }\n", + " @Test\n", + " boolean test3() {\n", + " output.print(\"test3\");\n", + " return true;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.AtUnitExample5\n", + ". test1\n", + "Running cleanup\n", + ". test3\n", + "Running cleanup\n", + ". test2\n", + "Running cleanup\n", + "OK (3 tests)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在输出中我们可以看到,清理方法会在每个测试方法结束之后自动运行。\n", + "\n", + "### 在 @Unit 中使用泛型\n", + "\n", + "泛型为 **@Unit** 出了一个难题,因为我们不可能“通用测试”。我们必须针对某个特定类型的参数或者参数集才能进行测试。解决方法十分简单,让测试类继承自泛型类的一个特定版本即可:\n", + "\n", + "下面是一个 **stack** 的简单实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/StackL.java\n", + "// A stack built on a LinkedList\n", + "package annotations;\n", + "import java.util.*;\n", + "public class StackL {\n", + " private LinkedList list = new LinkedList<>();\n", + " public void push(T v) { list.addFirst(v); }\n", + " public T top() { return list.getFirst(); }\n", + " public T pop() { return list.removeFirst(); }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了测试 String 版本,我们直接让测试类继承一个 Stack\\ :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/StackLStringTst.java\n", + "// Applying @Unit to generics\n", + "// {java onjava.atunit.AtUnit\n", + "// build/classes/main/annotations/StackLStringTst.class}\n", + "package annotations;\n", + "import onjava.atunit.*;\n", + "import onjava.*;\n", + "public class\n", + "StackLStringTst extends StackL {\n", + " @Test\n", + " void tPush() {\n", + " push(\"one\");\n", + " assert top().equals(\"one\");\n", + " push(\"two\");\n", + " assert top().equals(\"two\");\n", + " }\n", + " @Test\n", + " void tPop() {\n", + " push(\"one\");\n", + " push(\"two\");\n", + " assert pop().equals(\"two\");\n", + " assert pop().equals(\"one\");\n", + " }\n", + " @Test\n", + " void tTop() {\n", + " push(\"A\");\n", + " push(\"B\");\n", + " assert top().equals(\"B\");\n", + " assert top().equals(\"B\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "annotations.StackLStringTst\n", + ". tTop\n", + ". tPush\n", + ". tPop\n", + "OK (3 tests)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这种方法存在的唯一缺点是,继承使我们失去了访问被测试的类中 **private** 方法的能力。这对你非常重要,那你要么把 private 方法变为 **protected**,要么添加一个非 **private** 的 **@TestProperty** 方法,由它来调用 **private** 方法(稍后我们会看到,**AtUnitRemover** 会删除产品中的 **@TestProperty** 方法)。\n", + "\n", + "**@Unit** 搜索那些包含合适注解的类文件,然后运行 **@Test** 方法。我的主要目标就是让 **@Unit** 测试系统尽可能的透明,使得人们使用它的时候只需要添加 **@Test** 注解,而不需要特殊的编码和知识(现在版本的 **JUnit** 符合这个实践)。不过,如果说编写测试不会遇到任何困难,也不太可能,因此 **@Unit** 会尽量让这些困难变的微不足道,希望通过这种方式,你们会更乐意编写测试。\n", + "\n", + "### 实现 @Unit\n", + "\n", + "首先我们需要定义所有的注解类型。这些都是简单的标签,并且没有任何字段。@Test 标签在本章开头已经定义过了,这里是其他所需要的注解:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/TestObjectCreate.java\n", + "// The @Unit @TestObjectCreate tag\n", + "package onjava.atunit;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.METHOD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface TestObjectCreate {}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/TestObjectCleanup.java\n", + "// The @Unit @TestObjectCleanup tag\n", + "package onjava.atunit;\n", + "import java.lang.annotation.*;\n", + "@Target(ElementType.METHOD)\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface TestObjectCleanup {}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/TestProperty.java\n", + "// The @Unit @TestProperty tag\n", + "package onjava.atunit;\n", + "import java.lang.annotation.*;\n", + "// Both fields and methods can be tagged as properties:\n", + "@Target({ElementType.FIELD, ElementType.METHOD})\n", + "@Retention(RetentionPolicy.RUNTIME)\n", + "public @interface TestProperty {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所有测试的保留属性都为 **RUNTIME**,这是因为 **@Unit** 必须在编译后的代码中发现这些注解。\n", + "\n", + "要实现系统并运行测试,我们还需要反射机制来提取注解。下面这个程序通过注解中的信息,决定如何构造测试对象,并在测试对象上运行测试。正是由于注解帮助,这个程序才会如此短小而直接:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/AtUnit.java\n", + "// An annotation-based unit-test framework\n", + "// {java onjava.atunit.AtUnit}\n", + "package onjava.atunit;\n", + "import java.lang.reflect.*;\n", + "import java.io.*;\n", + "import java.util.*;\n", + "import java.nio.file.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "public class AtUnit implements ProcessFiles.Strategy {\n", + " static Class testClass;\n", + " static List failedTests= new ArrayList<>();\n", + " static long testsRun = 0;\n", + " static long failures = 0;\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " ClassLoader.getSystemClassLoader()\n", + " .setDefaultAssertionStatus(true); // Enable assert\n", + " new ProcessFiles(new AtUnit(), \"class\").start(args);\n", + " if(failures == 0)\n", + " System.out.println(\"OK (\" + testsRun + \" tests)\");\n", + " else {\n", + " System.out.println(\"(\" + testsRun + \" tests)\");\n", + " System.out.println(\n", + " \"\\n>>> \" + failures + \" FAILURE\" +\n", + " (failures > 1 ? \"S\" : \"\") + \" <<<\");\n", + " for(String failed : failedTests)\n", + " System.out.println(\" \" + failed);\n", + " }\n", + " }\n", + " @Override\n", + " public void process(File cFile) {\n", + " try {\n", + " String cName = ClassNameFinder.thisClass(\n", + " Files.readAllBytes(cFile.toPath()));\n", + " if(!cName.startsWith(\"public:\"))\n", + " return;\n", + " cName = cName.split(\":\")[1];\n", + " if(!cName.contains(\".\"))\n", + " return; // Ignore unpackaged classes\n", + " testClass = Class.forName(cName);\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " TestMethods testMethods = new TestMethods();\n", + " Method creator = null;\n", + " Method cleanup = null;\n", + " for(Method m : testClass.getDeclaredMethods()) {\n", + " testMethods.addIfTestMethod(m);\n", + " if(creator == null)\n", + " creator = checkForCreatorMethod(m);\n", + " if(cleanup == null)\n", + " cleanup = checkForCleanupMethod(m);\n", + " }\n", + " if(testMethods.size() > 0) {\n", + " if(creator == null)\n", + " try {\n", + " if(!Modifier.isPublic(testClass\n", + " .getDeclaredConstructor()\n", + " .getModifiers())) {\n", + " System.out.println(\"Error: \" + testClass +\n", + " \" no-arg constructor must be public\");\n", + " System.exit(1);\n", + " }\n", + " } catch(NoSuchMethodException e) {\n", + "// Synthesized no-arg constructor; OK\n", + " }\n", + " System.out.println(testClass.getName());\n", + " }\n", + " for(Method m : testMethods) {\n", + " System.out.print(\" . \" + m.getName() + \" \");\n", + " try {\n", + " Object testObject = createTestObject(creator);\n", + " boolean success = false;\n", + " try {\n", + " if(m.getReturnType().equals(boolean.class))\n", + " success = (Boolean)m.invoke(testObject);\n", + " else {\n", + " m.invoke(testObject);\n", + " success = true; // If no assert fails\n", + " }\n", + " } catch(InvocationTargetException e) {\n", + "// Actual exception is inside e:\n", + " System.out.println(e.getCause());\n", + " }\n", + " System.out.println(success ? \"\" : \"(failed)\");\n", + " testsRun++;\n", + " if(!success) {\n", + " failures++;\n", + " failedTests.add(testClass.getName() +\n", + " \": \" + m.getName());\n", + " }\n", + " if(cleanup != null)\n", + " cleanup.invoke(testObject, testObject);\n", + " } catch(IllegalAccessException |\n", + " IllegalArgumentException |\n", + " InvocationTargetException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " }\n", + " public static\n", + " class TestMethods extends ArrayList {\n", + " void addIfTestMethod(Method m) {\n", + " if(m.getAnnotation(Test.class) == null)\n", + " return;\n", + " if(!(m.getReturnType().equals(boolean.class) ||\n", + " m.getReturnType().equals(void.class)))\n", + " throw new RuntimeException(\"@Test method\" +\n", + " \" must return boolean or void\");\n", + " m.setAccessible(true); // If it's private, etc.\n", + " add(m);\n", + " }\n", + " }\n", + " private static\n", + " Method checkForCreatorMethod(Method m) {\n", + " if(m.getAnnotation(TestObjectCreate.class) == null)\n", + " return null;\n", + " if(!m.getReturnType().equals(testClass))\n", + " throw new RuntimeException(\"@TestObjectCreate \" +\n", + " \"must return instance of Class to be tested\");\n", + " if((m.getModifiers() &\n", + " java.lang.reflect.Modifier.STATIC) < 1)\n", + " throw new RuntimeException(\"@TestObjectCreate \" +\n", + " \"must be static.\");\n", + " m.setAccessible(true);\n", + " return m;\n", + " }\n", + " private static\n", + " Method checkForCleanupMethod(Method m) {\n", + " if(m.getAnnotation(TestObjectCleanup.class) == null)\n", + " return null;\n", + " if(!m.getReturnType().equals(void.class))\n", + " throw new RuntimeException(\"@TestObjectCleanup \" +\n", + " \"must return void\");\n", + " if((m.getModifiers() &\n", + " java.lang.reflect.Modifier.STATIC) < 1)\n", + " throw new RuntimeException(\"@TestObjectCleanup \" +\n", + " \"must be static.\");\n", + " if(m.getParameterTypes().length == 0 ||\n", + " m.getParameterTypes()[0] != testClass)\n", + " throw new RuntimeException(\"@TestObjectCleanup \" +\n", + " \"must take an argument of the tested type.\");\n", + " m.setAccessible(true);\n", + " return m;\n", + " }\n", + " private static Object\n", + " createTestObject(Method creator) {\n", + " if(creator != null) {\n", + " try {\n", + " return creator.invoke(testClass);\n", + " } catch(IllegalAccessException |\n", + " IllegalArgumentException |\n", + " InvocationTargetException e) {\n", + " throw new RuntimeException(\"Couldn't run \" +\n", + " \"@TestObject (creator) method.\");\n", + " }\n", + " } else { // Use the no-arg constructor:\n", + " try {\n", + " return testClass.newInstance();\n", + " } catch(InstantiationException |\n", + " IllegalAccessException e) {\n", + " throw new RuntimeException(\n", + " \"Couldn't create a test object. \" +\n", + " \"Try using a @TestObject method.\");\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然它可能是“过早的重构”(因为它只在书中使用过一次),**AtUnit.java** 使用了 **ProcessFiles** 工具逐步判断命令行中的参数,决定它是一个目录还是文件,并采取相应的行为。这可以应用于不同的解决方法,是因为它包含了一个 可用于自定义的 **Strategy** 接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/ProcessFiles.java\n", + "package onjava;\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "public class ProcessFiles {\n", + " public interface Strategy {\n", + " void process(File file);\n", + " }\n", + " private Strategy strategy;\n", + " private String ext;\n", + " public ProcessFiles(Strategy strategy, String ext) {\n", + " this.strategy = strategy;\n", + " this.ext = ext;\n", + " }\n", + " public void start(String[] args) {\n", + " try {\n", + " if(args.length == 0)\n", + " processDirectoryTree(new File(\".\"));\n", + " else\n", + " for(String arg : args) {\n", + " File fileArg = new File(arg);\n", + " if(fileArg.isDirectory())\n", + " processDirectoryTree(fileArg);\n", + " else {\n", + "// Allow user to leave off extension:\n", + " if(!arg.endsWith(\".\" + ext))\n", + " arg += \".\" + ext;\n", + " strategy.process(\n", + " new File(arg).getCanonicalFile());\n", + " }\n", + " }\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " public void processDirectoryTree(File root) throws IOException {\n", + " PathMatcher matcher = FileSystems.getDefault()\n", + " .getPathMatcher(\"glob:**/*.{\" + ext + \"}\");\n", + " Files.walk(root.toPath())\n", + " .filter(matcher::matches)\n", + " .forEach(p -> strategy.process(p.toFile()));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**AtUnit** 类实现了 **ProcessFiles.Strategy**,其包含了一个 `process()` 方法。在这种方式下,**AtUnit** 实例可以作为参数传递给 **ProcessFiles** 构造器。第二个构造器的参数告诉 **ProcessFiles** 如寻找所有包含 “class” 拓展名的文件。\n", + "\n", + "如下是一个简单的使用示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// annotations/DemoProcessFiles.java\n", + "import onjava.ProcessFiles;\n", + "public class DemoProcessFiles {\n", + " public static void main(String[] args) {\n", + " new ProcessFiles(file -> System.out.println(file),\n", + " \"java\").start(args);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ".\\AtUnitExample1.java\n", + ".\\AtUnitExample2.java\n", + ".\\AtUnitExample3.java\n", + ".\\AtUnitExample4.java\n", + ".\\AtUnitExample5.java\n", + ".\\AUComposition.java\n", + ".\\AUExternalTest.java\n", + ".\\database\\Constraints.java\n", + ".\\database\\DBTable.java\n", + ".\\database\\Member.java\n", + ".\\database\\SQLInteger.java\n", + ".\\database\\SQLString.java\n", + ".\\database\\TableCreator.java\n", + ".\\database\\Uniqueness.java\n", + ".\\DemoProcessFiles.java\n", + ".\\HashSetTest.java\n", + ".\\ifx\\ExtractInterface.java\n", + ".\\ifx\\IfaceExtractorProcessor.java\n", + ".\\ifx\\Multiplier.java\n", + ".\\PasswordUtils.java\n", + ".\\simplest\\Simple.java\n", + ".\\simplest\\SimpleProcessor.java\n", + ".\\simplest\\SimpleTest.java\n", + ".\\SimulatingNull.java\n", + ".\\StackL.java\n", + ".\\StackLStringTst.java\n", + ".\\Testable.java\n", + ".\\UseCase.java\n", + ".\\UseCaseTracker.java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果没有命令行参数,这个程序会遍历当前的目录树。你还可以提供多个参数,这些参数可以是类文件(带或不带.class扩展名)或目录。\n", + "\n", + "回到我们对 **AtUnit.java** 的讨论,因为 **@Unit** 会自动找到可测试的类和方法,所以不需要“套件”机制。\n", + "\n", + "**AtUnit.java** 中存在的一个我们必须要解决的问题是,当它发现类文件时,类文件名中的限定类名(包括包)不明显。为了发现这个信息,必须解析类文件 - 这不是微不足道的,但也不是不可能的。 找到 .class 文件时,会打开它并读取其二进制数据并将其传递给 `ClassNameFinder.thisClass()`。 在这里,我们正在进入“字节码工程”领域,因为我们实际上正在分析类文件的内容:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/atunit/ClassNameFinder.java\n", + "// {java onjava.atunit.ClassNameFinder}\n", + "package onjava.atunit;\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import java.util.*;\n", + "import onjava.*;\n", + "public class ClassNameFinder {\n", + " public static String thisClass(byte[] classBytes) {\n", + " Map offsetTable = new HashMap<>();\n", + " Map classNameTable = new HashMap<>();\n", + " try {\n", + " DataInputStream data = new DataInputStream(\n", + " new ByteArrayInputStream(classBytes));\n", + " int magic = data.readInt(); // 0xcafebabe\n", + " int minorVersion = data.readShort();\n", + " int majorVersion = data.readShort();\n", + " int constantPoolCount = data.readShort();\n", + " int[] constantPool = new int[constantPoolCount];\n", + " for(int i = 1; i < constantPoolCount; i++) {\n", + " int tag = data.read();\n", + " // int tableSize;\n", + " switch(tag) {\n", + " case 1: // UTF\n", + " int length = data.readShort();\n", + " char[] bytes = new char[length];\n", + " for(int k = 0; k < bytes.length; k++)\n", + " bytes[k] = (char)data.read();\n", + " String className = new String(bytes);\n", + " classNameTable.put(i, className);\n", + " break;\n", + " case 5: // LONG\n", + " case 6: // DOUBLE\n", + " data.readLong(); // discard 8 bytes\n", + " i++; // Special skip necessary\n", + " break;\n", + " case 7: // CLASS\n", + " int offset = data.readShort();\n", + " offsetTable.put(i, offset);\n", + " break;\n", + " case 8: // STRING\n", + " data.readShort(); // discard 2 bytes\n", + " break;\n", + " case 3: // INTEGER\n", + " case 4: // FLOAT\n", + " case 9: // FIELD_REF\n", + " case 10: // METHOD_REF\n", + " case 11: // INTERFACE_METHOD_REF\n", + " case 12: // NAME_AND_TYPE\n", + " case 18: // Invoke Dynamic\n", + " data.readInt(); // discard 4 bytes\n", + " break;\n", + " case 15: // Method Handle\n", + " data.readByte();\n", + " data.readShort();\n", + " break;\n", + " case 16: // Method Type\n", + " data.readShort();\n", + " break;\n", + " default:\n", + " throw\n", + " new RuntimeException(\"Bad tag \" + tag);\n", + " }\n", + " }\n", + " short accessFlags = data.readShort();\n", + " String access = (accessFlags & 0x0001) == 0 ?\n", + " \"nonpublic:\" : \"public:\";\n", + " int thisClass = data.readShort();\n", + " int superClass = data.readShort();\n", + " return access + classNameTable.get(\n", + " offsetTable.get(thisClass)).replace('/', '.');\n", + " } catch(IOException | RuntimeException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " // Demonstration:\n", + " public static void main(String[] args) throws Exception {\n", + " PathMatcher matcher = FileSystems.getDefault()\n", + " .getPathMatcher(\"glob:**/*.class\");\n", + "// Walk the entire tree:\n", + " Files.walk(Paths.get(\".\"))\n", + " .filter(matcher::matches)\n", + " .map(p -> {\n", + " try {\n", + " return thisClass(Files.readAllBytes(p));\n", + " } catch(Exception e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " })\n", + " .filter(s -> s.startsWith(\"public:\"))\n", + "// .filter(s -> s.indexOf('$') >= 0)\n", + " .map(s -> s.split(\":\")[1])\n", + " .filter(s -> !s.startsWith(\"enums.\"))\n", + " .filter(s -> s.contains(\".\"))\n", + " .forEach(System.out::println);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "onjava.ArrayShow\n", + "onjava.atunit.AtUnit$TestMethods\n", + "onjava.atunit.AtUnit\n", + "onjava.atunit.ClassNameFinder\n", + "onjava.atunit.Test\n", + "onjava.atunit.TestObjectCleanup\n", + "onjava.atunit.TestObjectCreate\n", + "onjava.atunit.TestProperty\n", + "onjava.BasicSupplier\n", + "onjava.CollectionMethodDifferences\n", + "onjava.ConvertTo\n", + "onjava.Count$Boolean\n", + "onjava.Count$Byte\n", + "onjava.Count$Character\n", + "onjava.Count$Double\n", + "onjava.Count$Float\n", + "onjava.Count$Integer\n", + "onjava.Count$Long\n", + "onjava.Count$Pboolean\n", + "onjava.Count$Pbyte\n", + "onjava.Count$Pchar\n", + "onjava.Count$Pdouble\n", + "onjava.Count$Pfloat\n", + "onjava.Count$Pint\n", + "onjava.Count$Plong\n", + "onjava.Count$Pshort\n", + "onjava.Count$Short\n", + "onjava.Count\n", + "onjava.CountingIntegerList\n", + "onjava.CountMap\n", + "onjava.Countries\n", + "onjava.Enums\n", + "onjava.FillMap\n", + "onjava.HTMLColors\n", + "onjava.MouseClick\n", + "onjava.Nap\n", + "onjava.Null\n", + "onjava.Operations\n", + "onjava.OSExecute\n", + "onjava.OSExecuteException\n", + "onjava.Pair\n", + "onjava.ProcessFiles$Strategy\n", + "onjava.ProcessFiles\n", + "onjava.Rand$Boolean\n", + "onjava.Rand$Byte\n", + "onjava.Rand$Character\n", + "onjava.Rand$Double\n", + "onjava.Rand$Float\n", + "onjava.Rand$Integer\n", + "onjava.Rand$Long\n", + "onjava.Rand$Pboolean\n", + "onjava.Rand$Pbyte\n", + "onjava.Rand$Pchar\n", + "onjava.Rand$Pdouble\n", + "onjava.Rand$Pfloat\n", + "onjava.Rand$Pint\n", + "onjava.Rand$Plong\n", + "onjava.Rand$Pshort\n", + "onjava.Rand$Short\n", + "onjava.Rand$String\n", + "onjava.Rand\n", + "onjava.Range\n", + "onjava.Repeat\n", + "onjava.RmDir\n", + "onjava.Sets\n", + "onjava.Stack\n", + "onjava.Suppliers\n", + "onjava.TimedAbort\n", + "onjava.Timer\n", + "onjava.Tuple\n", + "onjava.Tuple2\n", + "onjava.Tuple3\n", + "onjava.Tuple4\n", + "onjava.Tuple5\n", + "onjava.TypeCounter" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然无法在这里介绍其中所有的细节,但是每个类文件都必须遵循一定的格式,而我已经尽力用有意义的字段来表示这些从 **ByteArrayInputStream** 中提取出来的数据片段。通过施加在输入流上的读操作,你能看出每个信息片的大小。例如每一个类的头 32 个 bit 总是一个 “神秘数字” **0xcafebabe**,而接下来的两个 **short** 值是版本信息。常量池包含了程序的常量,所以这是一个可变的值。接下来的 **short** 告诉我们这个常量池有多大,然后我们为其创建一个尺寸合适的数组。常量池中的每一个元素,其长度可能是固定式,也可能是可变的值,因此我们必须检查每一个常量的起始标记,然后才能知道该怎么做,这就是 switch 语句的工作。我们并不打算精确的分析类中所有的数据,仅仅是从文件的起始一步一步的走,直到取得我们所需的信息,因此你会发现,在这个过程中我们丢弃了大量的数据。关于类的信息都保存在 **classNameTable** 和 **offsetTable** 中。在读取常量池之后,就找到了 **this_class** 信息,这是 **offsetTable** 的一个坐标,通过它可以找到进入 **classNameTable** 的坐标,然后就可以得到我们所需的类的名字了。\n", + "\n", + "现在让我们回到 **AtUtil.java** 中,process() 方法中拥有了类的名字,然后检查它是否包含“.”,如果有就表示该类定义于一个包中。没有包的类会被忽略。如果一个类在包中,那么我们就可以使用标准的类加载器通过 `Class.forName()` 将其加载进来。现在我们可以对这个类进行 **@Unit** 注解的分析工作了。\n", + "\n", + "我们只需要关注三件事:首先是 **@Test** 方法,它们被保存在 **TestMehtods** 列表中,然后检查其是否具有 @TestObjectCreate 和 **@TestObjectCleanup****** 方法。从代码中可以看到,我们通过调用相应的方法来查询注解从而找到这些方法。\n", + "\n", + "每找到一个 @Test 方法,就打印出来当前类的名字,于是观察者立刻就可以知道发生了什么。接下来开始执行测试,也就是打印出方法名,然后调用 createTestObject() (如果存在一个加了 @TestObjectCreate 注解的方法),或者调用默认构造器。一旦创建出来测试对象,如果调用其上的测试方法。如果测试的返回值为 boolean,就捕获该结果。如果测试方法没有返回值,那么就没有异常发生,我们就假设测试成功,反之,如果当 assert 失败或者有任何异常抛出的时候,就说明测试失败,这时将异常信息打印出来以显示错误的原因。如果有失败的测试发生,那么还要统计失败的次数,并将失败所属的类和方法加入到 failedTests 中,以便最后报告给用户。\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "注解是 Java 引入的一项非常受欢迎的补充,它提供了一种结构化,并且具有类型检查能力的新途径,从而使得你能够为代码中加入元数据,而且不会导致代码杂乱并难以阅读。使用注解能够帮助我们避免编写累赘的部署描述性文件,以及其他的生成文件。而 Javadoc 中的 @deprecated 被 @Deprecated 注解所替代的事实也说明,与注释性文字相比,注解绝对更适用于描述类相关的信息。\n", + "\n", + "Java 提供了很少的内置注解。这意味着如果你在别处找不到可用的类库,那么就只能自己创建新的注解以及相应的处理器。通过将注解处理器链接到 javac,你可以一步完成编译新生成的文件,简化了构造过程。\n", + "\n", + "API 的提供方和框架将会将注解作为他们工具的一部分。通过 @Unit 系统,我们可以想象,注解会极大的改变我们的 Java 编程体验。\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + "[^3 ]: The Java designers coyly suggest that a mirror is where you find a reflection." + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/24-Concurrent-Programming.ipynb b/jupyter/24-Concurrent-Programming.ipynb new file mode 100644 index 00000000..f86e1f96 --- /dev/null +++ b/jupyter/24-Concurrent-Programming.ipynb @@ -0,0 +1,4536 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第二十四章 并发编程\n", + "\n", + ">爱丽丝:“但是我不想进入疯狂的人群中”\n", + ">\n", + ">猫咪:“oh,你无能为力,我们都疯了,我疯了,你也疯了”\n", + ">\n", + ">爱丽丝:“你怎么知道我疯了”。\n", + ">\n", + ">猫咪:“你一定疯了,否则你不会来到这里”——爱丽丝梦游仙境 第6章。\n", + "\n", + "到目前为止,我们一直在编程,就像文学中的意识流叙事设备一样:首先发生一件事,然后是下一件事。我们完全控制所有步骤及其发生的顺序。如果我们将值设置为5,那么稍后会回来并发现它是47,这将是非常令人惊讶的。\n", + "\n", + "我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能没有。很可能它会在某些条件下有效,而不是在其他条件下,你必须知道和了解这些情况以确定哪些有效。\n", + "\n", + "作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并移动它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。\n", + "\n", + "而不是单一的意识流叙事,我们在同时多条故事线进行的间谍小说里。一个间谍在一个特殊的岩石下李璐下微缩胶片,当第二个间谍来取回包裹时,它可能已经被第三个间谍带走了。但是这部特别的小说并没有把事情搞得一团糟;你可以轻松地走到尽头,永远不会弄明白什么。\n", + "\n", + "构建并发应用程序非常类似于游戏[Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。\n", + "\n", + "本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的Java 8工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够解决问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。\n", + "\n", + "对于更多凌乱,低级别的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读Brian Goetz等人的Java Concurrency in Practice。虽然在写作时,这本书已有十多年的历史,但它仍然包含你必须了解和理解的必需品。理想情况下,本章和附录是该书的精心准备。另一个有价值的资源是**Bill Venner**的Inside the Java Virtual Machine,它详细描述了JVM的最内部工作方式,包括线程。\n", + "\n", + "\n", + "\n", + "## 术语问题\n", + "\n", + "在编程文献中并发、并行、多任务、多处理、多线程、分布式系统(以及可能的其他)使用了许多相互冲突的方式,并且经常被混淆。Brian Goetz在2016年的演讲中指出了这一点[From Concurrent to Parallel](https://www.youtube.com/watch?v=NsDE7E8sIdQ),他提出了一个合理的解释:\n", + "\n", + "- 并发是关于正确有效地控制对共享资源的访问。\n", + "- 并行是使用额外的资源来更快地产生结果。\n", + "\n", + "这些都是很好的定义,但有几十年的混乱产生了反对解决问题的历史。一般来说,当人们使用“并发”这个词时,他们的意思是“一切变得混乱”,事实上,我可能会在很多地方自己陷入这种想法,大多数书籍,包括Brian Goetz的Java Concurrency in Practice,都在标题中使用这个词。\n", + "\n", + "并发通常意味着“不止一个任务正在执行中”,而并行性几乎总是意味着“不止一个任务同时执行。”你可以立即看到这些定义的区别:并行也有不止一个任务“正在进行”。区别在于细节,究竟是如何“执行”发生的。此外,重叠:为并行编写的程序有时可以在单个处理器上运行,而一些并发编程系统可以利用多个处理器。\n", + "\n", + "这是另一种方法,在减速[原文:slowdown]发生的地方写下定义:\n", + "\n", + "_并发_\n", + "\n", + "同时完成多个任务。在开始处理其他任务之前,当前任务不需要完成。并发解决了阻塞发生的问题。当任务无法进一步执行,直到外部环境发生变化时才会继续执行。最常见的例子是I/O,其中任务必须等待一些input(在这种情况下会被阻止)。这个问题产生在I/O密集型。\n", + "\n", + "_并行_\n", + "\n", + "同时在多个地方完成多个任务。这解决了所谓的计算密集型问题,如果将程序分成多个部分并在不同的处理器上编辑不同的部分,程序可以运行得更快。\n", + "\n", + "术语混淆的原因在上面的定义中显示:其中核心是“在同一时间完成多个任务。”并行性通过多个处理器增加分布。更重要的是,两者解决了不同类型的问题:解决I/O密集型问题,并行化可能对你没有任何好处,因为问题不是整体速度,而是阻塞。并且考虑到计算力限制问题并试图在单个处理器上使用并发来解决它可能会浪费时间。两种方法都试图在更短的时间内完成更多,但它们实现加速的方式是不同的,并且取决于问题所带来的约束。\n", + "\n", + "这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制**线程**来实现并发和并行。\n", + "\n", + "我们甚至可以尝试添加细致的粒度去定义(但是,这不是标准化的术语):\n", + "\n", + "- **纯并发**:任务仍然在单个CPU上运行。纯并发系统产生的结果比顺序系统更快,但如果有更多的处理器,则运行速度不会更快\n", + "- **并发-并行**:使用并发技术,结果程序利用更多处理器并更快地生成结果\n", + "- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams**就是一个很好的例子)。\n", + "- **纯并行**:除非有多个处理器,否则不会运行。\n", + "\n", + "在某些情况下,这可能是一个有用的分类法。\n", + "\n", + "对并发性的语言和库支持似乎[Leaky Abstraction](https://en.wikipedia.org/wiki/Leaky_abstraction)是完美候选者。抽象的目标是“抽象出”那些对于手头想法不重要的东西,从不必要的细节中汲取灵感。如果抽象是漏洞,那些碎片和细节会不断重新声明自己是重要的,无论你试图隐藏它们多少\n", + "\n", + "我开始怀疑是否真的有高度抽象。当编写这些类型的程序时,你永远不会被底层系统和工具屏蔽,甚至关于CPU缓存如何工作的细节。最后,如果你非常小心,你创作的东西在特定的情况下起作用,但它在其他情况下不起作用。有时,区别在于两台机器的配置方式,或者程序的估计负载。这不是Java特有的-它是并发和并行编程的本质。\n", + "\n", + "你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果它没有正确调整并且输入速率要么没有被正确估计或被限制(并且限制意味着,在不同情况下不同的东西具有不同的影响),该队列将填满并阻塞或溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式\n", + "\n", + "\n", + "### 并发的新定义\n", + "\n", + "几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它:\n", + ">**并发性是一系列性能技术,专注于减少等待**\n", + "\n", + "这实际上是一个相当多的声明,所以我将其分解:\n", + "\n", + "- 这是一个集合:有许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大\n", + "- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。\n", + "- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。\n", + "\n", + "值得强调的是,这个定义的有效性取决于等待这个词。如果没有什么可以等待,那就没有机会了。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。\n", + "\n", + "## 并发的超能力\n", + "\n", + "想象一下,你置身于一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并沿着走廊向下移动。走廊分开了。\n", + "\n", + "你自己完成这项任务需要一百个生命周期。\n", + "\n", + "现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。\n", + "\n", + "每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出50个自己来搜索这间房间。\n", + "\n", + "一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。\n", + "\n", + "我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是泄漏抽象的(leaky abstraction)。\n", + "\n", + "以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策\n", + "\n", + "让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,那么只是因为搜索者恰好是因为处理器闲置了被锁,等待一扇门被接听。相反,我们希望将处理器应用于搜索,在那里它可以做一些真正的工作,因此需要将处理器从一个任务切换到另一个任务的机制。\n", + "\n", + "许多型号能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。\n", + "\n", + "其中一个最大的影响取决于你是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。\n", + "\n", + "这可能会让你决定,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上值得让它运行得更慢以实现。\n", + "\n", + "在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本会降低一切,在这种情况下,如果你有多个进程,并发通常只会有意义。\n", + "\n", + "在接听电话的客户服务部门,你只有一定数量的人,但是你可以拨打很多电话。那些人(处理器)必须一次拨打一个电话,直到完成电话和额外的电话必须排队。\n", + "\n", + "在“鞋匠和精灵”的童话故事中,鞋匠做了很多工作,当他睡着时,一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时会产生限制 - 例如,如果鞋底需要制作鞋子,这会限制制鞋的速度并改变你设计解决方案的方式。\n", + "\n", + "因此,你尝试解决的问题驱动解决方案的设计。打破一个“独立运行”问题的高级[原文:lovely ]抽象,然后就是实际发生的现实。物理现实不断侵入和震撼,这种抽象。\n", + "\n", + "这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备收到蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先在框中获取蛋糕(通常使用锁定机制来解决问题,因此一个工作人员可以先抓住框并防止蛋糕砸)。\n", + "\n", + "当“同时”执行的任务相互干扰时,会出现问题。他可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,编写仅看起来可行的并发程序更为常见,但是在适当的条件下,将会失败。这些情况可能会发生,或者很少发生,你在测试期间从未看到它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。\n", + "这是推动并发的最强有力的论据之一:如果你忽略它,你可能会被咬。\n", + "\n", + "因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。\n", + "\n", + "\n", + "\n", + "## 并发为速度而生\n", + "\n", + "在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定它没有之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置交换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在孤立的地方。\n", + "\n", + "速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学习利用那些超级处理器,这是并发性给你的一个建议。\n", + "\n", + "使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。\n", + "\n", + "但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。\n", + "\n", + "可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。\n", + "\n", + "单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些长时间运行操作,从而最终忽略用户输入和无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中轮询它。这会产生笨拙的代码,无法保证程序员不会忘记执行检查。没有并发性,生成响应式用户界面的唯一方法是让所有任务定期检查用户输入。通过创建单独的执行线程来响应用户输入,该程序保证了一定程度的响应。\n", + "\n", + "实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和I/O等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。\n", + "\n", + "有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,以防止它们在并发频谱中的适用性(最终你习惯了标准的并发性克制,“这种方法适用于一些情况但不适用于其他情况”)\n", + "\n", + "一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。\n", + "\n", + "Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。\n", + "\n", + "并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;否则,你的代码部分将被迫明确标注通常由并发处理的操作。\n", + "\n", + "\n", + "## 四句格言\n", + "\n", + "在经历了多年的Java并发之后,我总结了以下四个格言:\n", + ">1.不要这样做\n", + ">\n", + ">2.没有什么是真的,一切可能都有问题\n", + ">\n", + ">3.它起作用,并不意味着它没有问题\n", + ">\n", + ">4.你仍然必须理解它\n", + "\n", + "这些特别是关于Java设计中的问题,尽管它也可以应用于其他一些语言。但是,确实存在旨在防止这些问题的语言。\n", + "\n", + "### 1.不要这样做\n", + "\n", + "(不要自己动手)\n", + "\n", + "避免纠缠于并发产生的深层问题的最简单方法就是不要这样做。虽然它是诱人的,并且似乎足够安全,可以尝试做简单的事情,但它存在无数、微妙的陷阱。如果你可以避免它,你的生活会更容易。\n", + "\n", + "证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 首先应用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。\n", + "\n", + "如果你被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。\n", + "\n", + "### 2.没有什么是真的,一切可能都有问题\n", + "\n", + "没有并发性的编程,你会发现你的世界有一定的顺序和一致性。通过简单地将变量赋值给某个值,很明显它应该始终正常工作。\n", + "\n", + "在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。我已经很熟悉的东西,认为它显然有效但实际上并没有。\n", + "\n", + "在非并发程序中你可以忽略的各种事情突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须了解对象构造的深度复杂性,以便你的构造对象不会意外地将数据暴露给其他线程进行更改。问题还有很多。\n", + "\n", + "虽然这些主题太复杂,无法为你提供本章的专业知识(再次参见Java Concurrency in Practice),但你必须了解它们。\n", + "\n", + "### 3.它起作用,并不意味着它没有问题\n", + "\n", + "我们很容易编写出一个看似完美实则有问题的并发程序,并且往往问题直在极端情况下才暴露出来 - 在程序部署后不可避免地会出现用户问题。\n", + "\n", + "- 你不能证明并发程序是正确的,你只能(有时)证明它是不正确的。\n", + "- 大多数情况下你甚至不能这样做:如果它有问题,你可能无法检测到它。\n", + "- 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。\n", + "- 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。\n", + "\n", + "在其他Java主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“你知道的越多,你认为你知道得越多。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。\n", + "\n", + "我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。\n", + "\n", + "在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。你可以在这个情况下做的最糟糕的事情是“自信”。\n", + "\n", + "### 4.你必须仍然理解\n", + "\n", + "在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续保留它。\n", + "\n", + "这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢?\n", + "\n", + "唉,你不能轻易逃脱:\n", + "\n", + "- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** clas那样简单的东西。\n", + "- 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思?\n", + "\n", + "人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。\n", + "\n", + "唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。\n", + "\n", + "Java是一种多线程语言,如果你了解它们是否存在并发问题。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。\n", + "\n", + "\n", + "## 残酷的真相\n", + "\n", + "当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出追去其他的的能力。火的使用促成了文明。\n", + "\n", + "我们现在通过计算机和网络技术创造了一个“外化大脑”,开始了第二次基本转变。虽然我们只是触及表面,但已经引发了其他转变,例如设计生物机制的能力,并且已经看到文化演变的显着加速(过去,人们不得不前往混合文化,但现在他们开始混合互联网)。这些转变的影响和好处已经超出了科幻作家预测它们的能力(他们在预测文化和个人变化,甚至技术转变的次要影响方面都特别困难)。\n", + "\n", + "有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件\n", + "\n", + "Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易就像语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气体相位期间,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。\n", + "\n", + "紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。\n", + "\n", + "[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。\n", + "\n", + "热情使原始Java设计师因为看起来有必要而投入功能。信心(以及原始语言的气味)让他们认为任何问题都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 - 这是非常有信心,相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但保留了对之前版本的支持。\n", + "\n", + "线程包含在Java 1.0中。当然,并发性是影响语言远角的基本语言设计决策,很难想象以后添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。\n", + "\n", + "C语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java的大规模扩展迅速暴露了基本问题。在Thread类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。\n", + "\n", + "不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在函数和方法中,所有不变和防止副作用的方法都会导致简化并发编程(这些是纯函数式编程语言的基础)的变化,但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么对这些选择有所了解,要么认为它们太不同了,并且会抛弃许多潜在的语言采用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。\n", + "\n", + "Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。似乎工作的程序充满了微妙的并发bug。\n", + "\n", + "为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。这艘船航行了;Java将不再是为并发而设计的语言,而只是一种允许它的语言。\n", + "\n", + "尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它还有多远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。\n", + "\n", + "这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然容易受到攻击,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。\n", + "\n", + "\n", + "## 本章其余部分\n", + "\n", + "这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得你的生活比旧的替代品更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。\n", + "\n", + "- Parallel Streams(并行流)\n", + "到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在你对该语法(作为一个粉丝,我希望)感到满意,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式\n", + "\n", + "添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。\n", + "\n", + "- 创建和运行任务\n", + "任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executorto运行。\n", + "\n", + "有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。\n", + "\n", + "- 终止长时间运行的任务\n", + "任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。\n", + "- Completable Futures\n", + "当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,最终你的衣服很干净,你可以拿起它。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。\n", + "\n", + "Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,等待任务没有完成。对于一系列操作,Futures并没有真正帮助那么多。\n", + "\n", + "Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,清理菜肴,储存菜肴”等一系列链式操作。\n", + "\n", + "- 死锁\n", + "某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,等待另一个被阻止的任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入僵局。\n", + "\n", + "如果在运行程序时没有立即出现死锁,则会出现最大的问题。你的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如你的开发机器,但是当你将其部署到不同的硬件时会开始死锁。\n", + "\n", + "死锁通常源于细微的编程错误;一系列无辜的决定,最终意外地创建了一个依赖循环。本节包含一个经典示例,演示了死锁的特性。\n", + "\n", + "我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是完成配置。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来加速计划。\n", + "\n", + "- 努力,复杂,成本\n", + "\n", + "## 并行流\n", + "\n", + "Java 8流的一个显着优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。这产生了相当神奇的结果,即能够简单用parallel()然后流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事\n", + "\n", + "例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/ParallelPrime.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import static java.util.stream.LongStream.*;\n", + "import java.io.*;\n", + "import java.nio.file.*;\n", + "import onjava.Timer;\n", + "\n", + "public class ParallelPrime {\n", + " static final int COUNT = 100_000;\n", + " public static boolean isPrime(long n){\n", + " return rangeClosed(2, (long)Math.sqrt(n)).noneMatch(i -> n % i == 0);\n", + " }\n", + " public static void main(String[] args)\n", + " throws IOException {\n", + " Timer timer = new Timer();\n", + " List primes =\n", + " iterate(2, i -> i + 1)\n", + " .parallel() // [1]\n", + " .filter(ParallelPrime::isPrime)\n", + " .limit(COUNT)\n", + " .mapToObj(Long::toString)\n", + " .collect(Collectors.toList());\n", + " System.out.println(timer.duration());\n", + " Files.write(Paths.get(\"primes.txt\"), primes, StandardOpenOption.CREATE);\n", + " }\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " Output:\n", + " 1224" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且消除了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。\n", + "\n", + "当我注释掉[1] parallel()行时,我的结果大约是parallel()的三倍。\n", + "\n", + "并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。\n", + "\n", + "- parallel()不是灵丹妙药\n", + "\n", + "作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:求和数字的增量序列。事实证明这是一个令人惊讶的数量,并且我将冒险将它们进行比较 - 试图小心,但承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“升温”),但我认为它仍然提供了一些有用的指示。\n", + "\n", + "我将从一个计时方法rigorously 开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。\n", + "\n", + "请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。\n", + "\n", + "所有关于时间和内存的数字和讨论都是指“我的机器”。你的经历可能会有所不同。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Summing.java\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "import onjava.Timer;\n", + "public class Summing {\n", + " static void timeTest(String id, long checkValue, LongSupplier operation){\n", + " System.out.print(id + \": \");\n", + " Timer timer = newTimer();\n", + " long result = operation.getAsLong();\n", + " if(result == checkValue)\n", + " System.out.println(timer.duration() + \"ms\");\n", + " else\n", + " System.out.format(\"result: %d%ncheckValue: %d%n\", result, checkValue);\n", + " }\n", + " public static final int SZ = 100_000_000;// This even works://\n", + " public static final int SZ = 1_000_000_000;\n", + " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2; // Gauss's formula\n", + " public static void main(String[] args){\n", + " System.out.println(CHECK);\n", + " timeTest(\"Sum Stream\", CHECK, () ->\n", + " LongStream.rangeClosed(0, SZ).sum());\n", + " timeTest(\"Sum Stream Parallel\", CHECK, () ->\n", + " LongStream.rangeClosed(0, SZ).parallel().sum());\n", + " timeTest(\"Sum Iterated\", CHECK, () ->\n", + " LongStream.iterate(0, i -> i + 1)\n", + " .limit(SZ+1).sum());\n", + " // Slower & runs out of memory above 1_000_000:\n", + " // timeTest(\"Sum Iterated Parallel\", CHECK, () ->\n", + " // LongStream.iterate(0, i -> i + 1)\n", + " // .parallel()\n", + " // .limit(SZ+1).sum());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "5000000050000000\n", + "Sum Stream: 167ms\n", + "Sum Stream Parallel: 46ms\n", + "Sum Iterated: 284ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**CHECK**值是使用Carl Friedrich Gauss在1700年代后期仍在小学时创建的公式计算出来的.\n", + "\n", + " **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于十亿分之一的SZ在没有溢出的情况下处理(我使用较小的数字,因此程序运行时间不长)。使用 **parallel()** 的基本范围操跟快。\n", + "\n", + "如果使用**iterate()**来生成序列,则减速是戏剧性的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,那么结果通常比非并行版本花费的时间更长,但是当**SZ**超过一百万时,它也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察:\n", + "\n", + "- 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。\n", + "- 阵列分割成本低廉,均匀且具有完美的分裂知识。\n", + "- 链接列表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余列表”,这相对无用。\n", + "- 无状态生成器的行为类似于数组;使用上述范围是无可争议的。\n", + "- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。\n", + "\n", + "现在让我们尝试通过在数组中填充值来填充数组来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。\n", + "\n", + "首先我们将尝试一个充满原始**long**的数组:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Summing2.java\n", + "// {ExcludeFromTravisCI}import java.util.*;\n", + "public class Summing2 {\n", + " static long basicSum(long[] ia) {\n", + " long sum = 0;\n", + " int size = ia.length;\n", + " for(int i = 0; i < size; i++)\n", + " sum += ia[i];return sum;\n", + " }\n", + " // Approximate largest value of SZ before\n", + " // running out of memory on mymachine:\n", + " public static final int SZ = 20_000_000;\n", + " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2;\n", + " public static void main(String[] args) {\n", + " System.out.println(CHECK);\n", + " long[] la = newlong[SZ+1];\n", + " Arrays.parallelSetAll(la, i -> i);\n", + " Summing.timeTest(\"Array Stream Sum\", CHECK, () ->\n", + " Arrays.stream(la).sum());\n", + " Summing.timeTest(\"Parallel\", CHECK, () ->\n", + " Arrays.stream(la).parallel().sum());\n", + " Summing.timeTest(\"Basic Sum\", CHECK, () ->\n", + " basicSum(la));// Destructive summation:\n", + " Summing.timeTest(\"parallelPrefix\", CHECK, () -> {\n", + " Arrays.parallelPrefix(la, Long::sum);\n", + " return la[la.length - 1];\n", + " });\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "200000010000000\n", + "Array Stream\n", + "Sum: 104ms\n", + "Parallel: 81ms\n", + "Basic Sum: 106ms\n", + "parallelPrefix: 265ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。”\n", + "\n", + "最后,考虑使用盒装**Long**的效果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Summing3.java\n", + "// {ExcludeFromTravisCI}\n", + "import java.util.*;\n", + "public class Summing3 {\n", + " static long basicSum(Long[] ia) {\n", + " long sum = 0;\n", + " int size = ia.length;\n", + " for(int i = 0; i < size; i++)\n", + " sum += ia[i];\n", + " return sum;\n", + " }\n", + " // Approximate largest value of SZ before\n", + " // running out of memory on my machine:\n", + " public static final int SZ = 10_000_000;\n", + " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2;\n", + " public static void main(String[] args) {\n", + " System.out.println(CHECK);\n", + " Long[] aL = newLong[SZ+1];\n", + " Arrays.parallelSetAll(aL, i -> (long)i);\n", + " Summing.timeTest(\"Long Array Stream Reduce\", CHECK, () ->\n", + " Arrays.stream(aL).reduce(0L, Long::sum));\n", + " Summing.timeTest(\"Long Basic Sum\", CHECK, () ->\n", + " basicSum(aL));\n", + " // Destructive summation:\n", + " Summing.timeTest(\"Long parallelPrefix\",CHECK, ()-> {\n", + " Arrays.parallelPrefix(aL, Long::sum);\n", + " return aL[aL.length - 1];\n", + " });\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "50000005000000\n", + "Long Array\n", + "Stream Reduce: 1038ms\n", + "Long Basic\n", + "Sum: 21ms\n", + "Long parallelPrefix: 3616ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了**basicSum()**,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。\n", + "\n", + "我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Summing4.java\n", + "// {ExcludeFromTravisCI}\n", + "import java.util.*;\n", + "public class Summing4 {\n", + " public static void main(String[] args) {\n", + " System.out.println(Summing3.CHECK);\n", + " Long[] aL = newLong[Summing3.SZ+1];\n", + " Arrays.parallelSetAll(aL, i -> (long)i);\n", + " Summing.timeTest(\"Long Parallel\",\n", + " Summing3.CHECK, () ->\n", + " Arrays.stream(aL)\n", + " .parallel()\n", + " .reduce(0L,Long::sum));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "50000005000000\n", + "Long Parallel: 1014ms" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它比非parallel()版本略快,但并不显着。\n", + "\n", + "这种时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。\n", + "\n", + "使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是超出缓存。\n", + "\n", + "这些示例使用不同的SZ值来显示内存限制。\n", + "\n", + "为了进行时间比较,以下是SZ设置为最小值1000万的结果:\n", + "\n", + "**Sum Stream: 69msSum\n", + "Stream Parallel: 18msSum\n", + "Iterated: 277ms\n", + "Array Stream Sum: 57ms\n", + "Parallel: 14ms\n", + "Basic Sum: 16ms\n", + "parallelPrefix: 28ms\n", + "Long Array Stream Reduce: 1046ms\n", + "Long Basic Sum: 21ms\n", + "Long parallelPrefix: 3287ms\n", + "Long Parallel: 1008ms**\n", + "\n", + "虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!”我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。\n", + "\n", + "- parallel()/limit()交点\n", + "\n", + "使用parallel()时会有更复杂的问题。从其他语言中吸取的流是围绕无限流模型设计的。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。\n", + "\n", + "Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在Collection和Map中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将Collection转换为存在这些操作的Stream:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CollectionIntoStream.java\n", + "import onjava.*;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class CollectionIntoStream {\n", + " public static void main(String[] args) {\n", + " List strings = Stream.generate(new Rand.String(5))\n", + " .limit(10)\n", + " .collect(Collectors.toList());\n", + " strings.forEach(System.out::println);\n", + " // Convert to a Stream for many more options:\n", + " String result = strings.stream()\n", + " .map(String::toUpperCase)\n", + " .map(s -> s.substring(2))\n", + " .reduce(\":\", (s1, s2) -> s1 + s2);\n", + " System.out.println(result);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pccux\n", + "szgvg\n", + "meinn\n", + "eeloz\n", + "tdvew\n", + "cippc\n", + "ygpoa\n", + "lkljl\n", + "bynxt\n", + ":PENCUXGVGINNLOZVEWPPCPOALJLNXT" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作.**ConcurrentHashMap**对**forEachand**和**reduce**操作有特别广泛的支持。\n", + "\n", + "在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意外。这是一个有趣的难题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/ParallelStreamPuzzle.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "public class ParallelStreamPuzzle {\n", + " static class IntGenerator\n", + " implements Supplier {\n", + " private int current = 0;\n", + " public Integer get() {\n", + " return current++;\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " List x = Stream.generate(newIntGenerator())\n", + " .limit(10)\n", + " .parallel() // [1]\n", + " .collect(Collectors.toList());\n", + " System.out.println(x);\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果[1]注释运行它,它会产生预期的:\n", + "**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]**\n", + "每次。但是包含了parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如:\n", + "**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]**\n", + "这样一个简单的程序怎么会这么破碎呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”“那意味着什么?一堆线程都在拉动一个生成器,在某种程度上选择一组有限的结果?代码使它看起来很简单,但它转向是一个特别凌乱的问题。\n", + "\n", + "为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用**ConcurrentLinkedDeque**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/ParallelStreamPuzzle2.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.concurrent.atomic.*;\n", + "import java.nio.file.*;\n", + "public class ParallelStreamPuzzle2 {\n", + " public static final Deque trace =\n", + " new ConcurrentLinkedDeque<>();\n", + " static class\n", + " IntGenerator implements Supplier {\n", + " private AtomicInteger current =\n", + " new AtomicInteger();\n", + " public Integerget() {\n", + " trace.add(current.get() + \": \" +Thread.currentThread().getName());\n", + " return current.getAndIncrement();\n", + " }\n", + " }\n", + " public static void main(String[] args) throws Exception {\n", + " List x = Stream.generate(newIntGenerator())\n", + " .limit(10)\n", + " .parallel()\n", + " .collect(Collectors.toList());\n", + " System.out.println(x);\n", + " Files.write(Paths.get(\"PSP2.txt\"), trace);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。\n", + "\n", + "在查看 **PSP2.txt**.**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。\n", + "\n", + "**0: main\n", + "1: ForkJoinPool.commonPool-worker-1\n", + "2: ForkJoinPool.commonPool-worker-2\n", + "3: ForkJoinPool.commonPool-worker-2\n", + "4: ForkJoinPool.commonPool-worker-1\n", + "5: ForkJoinPool.commonPool-worker-1\n", + "6: ForkJoinPool.commonPool-worker-1\n", + "7: ForkJoinPool.commonPool-worker-1\n", + "8: ForkJoinPool.commonPool-worker-4\n", + "9: ForkJoinPool.commonPool-worker-4\n", + "10: ForkJoinPool.commonPool-worker-4\n", + "11: main\n", + "12: main\n", + "13: main\n", + "14: main\n", + "15: main...10\n", + "17: ForkJoinPool.commonPool-worker-110\n", + "18: ForkJoinPool.commonPool-worker-610\n", + "19: ForkJoinPool.commonPool-worker-610\n", + "20: ForkJoinPool.commonPool-worker-110\n", + "21: ForkJoinPool.commonPool-worker-110\n", + "22: ForkJoinPool.commonPool-worker-110\n", + "23: ForkJoinPool.commonPool-worker-1**\n", + "\n", + "这个块大小似乎是内部实现的一部分(尝试使用**limit()**的不同参数来查看不同的块大小)。将**parallel()**与**limit()**结合使用可以预取一串值,作为流输出。\n", + "\n", + "试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用get()。添加limit(),你说“只需要这些。”基本上,当你将parallel()与limit()结合使用时,你要求随机输出 - 这可能对你正在解决的问题很好。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。\n", + "\n", + "什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/ParallelStreamPuzzle3.java\n", + "// {VisuallyInspectOutput}\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "public class ParallelStreamPuzzle3 {\n", + " public static void main(String[] args) {\n", + " List x = IntStream.range(0, 30)\n", + " .peek(e -> System.out.println(e + \": \" +Thread.currentThread()\n", + " .getName()))\n", + " .limit(10)\n", + " .parallel()\n", + " .boxed()\n", + " .collect(Collectors.toList());\n", + " System.out.println(x);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "8: main\n", + "6: ForkJoinPool.commonPool-worker-5\n", + "3: ForkJoinPool.commonPool-worker-7\n", + "5: ForkJoinPool.commonPool-worker-5\n", + "1: ForkJoinPool.commonPool-worker-3\n", + "2: ForkJoinPool.commonPool-worker-6\n", + "4: ForkJoinPool.commonPool-worker-1\n", + "0: ForkJoinPool.commonPool-worker-4\n", + "7: ForkJoinPool.commonPool-worker-1\n", + "9: ForkJoinPool.commonPool-worker-2\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。\n", + "\n", + "你还可以看到boxed()的添加,它接受int流并将其转换为Integer流。\n", + "\n", + "现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。\n", + "\n", + "它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。当一个简单的数字序列并行生成时,有点难以想象。如果你使用昂贵的产品,它可能有意义 - 但这都是猜测。唯一知道的是通过测试。记住这句格言:“首先制作它,然后快速制作 - 但只有你必须这样做。”**parallel()**和**limit()**仅供专家使用(并且要清楚,我不认为自己是这里的专家)。\n", + "\n", + "- 并行流只看起来很容易\n", + "\n", + "实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,只需将**parallel()**打到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现你的目标。\n", + "\n", + "## 创建和运行任务\n", + "\n", + "如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。\n", + "\n", + "Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。\n", + "\n", + "- Tasks and Executors\n", + "\n", + "在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。\n", + "\n", + "创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。\n", + "\n", + "首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/NapTask.java\n", + "import onjava.Nap;\n", + "public class NapTask implements Runnable {\n", + " finalint id;\n", + " public NapTask(int id) {\n", + " this.id = id;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " new Nap(0.1);// Seconds\n", + " System.out.println(this + \" \"+\n", + " Thread.currentThread().getName());\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return\"NapTask[\" + id + \"]\";\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这只是一个**Runnable**:一个包含**run()**方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Nap.java\n", + "package onjava;\n", + "import java.util.concurrent.*;\n", + "public class Nap {\n", + " public Nap(double t) { // Seconds\n", + " try {\n", + " TimeUnit.MILLISECONDS.sleep((int)(1000 * t));\n", + " } catch(InterruptedException e){\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " public Nap(double t, String msg) {\n", + " this(t);\n", + " System.out.println(msg);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了消除异常处理的视觉噪声,这被定义为实用程序。第二个构造函数在超时时显示一条消息\n", + "\n", + "对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。\n", + "\n", + "你可以看到**sleep()**抛出一个已检查的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。\n", + "\n", + "要执行任务,我们将从最简单的方法--SingleThreadExecutor开始:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "//concurrent/SingleThreadExecutor.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "public class SingleThreadExecutor {\n", + " public static void main(String[] args) {\n", + " ExecutorService exec =\n", + " Executors.newSingleThreadExecutor();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(NapTask::new)\n", + " .forEach(exec::execute);\n", + " System.out.println(\"All tasks submitted\");\n", + " exec.shutdown();\n", + " while(!exec.isTerminated()) {\n", + " System.out.println(\n", + " Thread.currentThread().getName()+\n", + " \" awaiting termination\");\n", + " new Nap(0.1);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "All tasks submitted\n", + "main awaiting termination\n", + "main awaiting termination\n", + "NapTask[0] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[1] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[2] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[3] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[4] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[5] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[6] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[7] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[8] pool-1-thread-1\n", + "main awaiting termination\n", + "NapTask[9] pool-1-thread-1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4]\n", + "\n", + "我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。\n", + "\n", + "请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。\n", + "\n", + "如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,虽然不需要(!exec.isTerminated())。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/SingleThreadExecutor2.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class SingleThreadExecutor2 {\n", + " public static void main(String[] args)throws InterruptedException {\n", + " ExecutorService exec\n", + " =Executors.newSingleThreadExecutor();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(NapTask::new)\n", + " .forEach(exec::execute);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NapTask[0] pool-1-thread-1\n", + "NapTask[1] pool-1-thread-1\n", + "NapTask[2] pool-1-thread-1\n", + "NapTask[3] pool-1-thread-1\n", + "NapTask[4] pool-1-thread-1\n", + "NapTask[5] pool-1-thread-1\n", + "NapTask[6] pool-1-thread-1\n", + "NapTask[7] pool-1-thread-1\n", + "NapTask[8] pool-1-thread-1\n", + "NapTask[9] pool-1-thread-1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/MoreTasksAfterShutdown.java\n", + "import java.util.concurrent.*;\n", + "public class MoreTasksAfterShutdown {\n", + " public static void main(String[] args) {\n", + " ExecutorService exec\n", + " =Executors.newSingleThreadExecutor();\n", + " exec.execute(newNapTask(1));\n", + " exec.shutdown();\n", + " try {\n", + " exec.execute(newNapTask(99));\n", + " } catch(RejectedExecutionException e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。\n", + "\n", + "- 使用更多线程\n", + "\n", + "使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,你将看到更多选项。例如CachedThreadPool:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CachedThreadPool.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class CachedThreadPool {\n", + " public static void main(String[] args) {\n", + " ExecutorService exec\n", + " =Executors.newCachedThreadPool();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(NapTask::new)\n", + " .forEach(exec::execute);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "NapTask[7] pool-1-thread-8\n", + "NapTask[4] pool-1-thread-5\n", + "NapTask[1] pool-1-thread-2\n", + "NapTask[3] pool-1-thread-4\n", + "NapTask[0] pool-1-thread-1\n", + "NapTask[8] pool-1-thread-9\n", + "NapTask[2] pool-1-thread-3\n", + "NapTask[9] pool-1-thread-10\n", + "NapTask[6] pool-1-thread-7\n", + "NapTask[5] pool-1-thread-6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当你运行这个程序时,你会发现它完成得更快。这是有道理的,而不是使用相同的线程来顺序运行每个任务,每个任务都有自己的线程,所以它们都并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。\n", + "\n", + "要理解这个问题,我们需要一个更复杂的任务:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/InterferingTask.java\n", + "public class InterferingTask implements Runnable {\n", + " final int id;\n", + " private static Integer val = 0;\n", + " public InterferingTask(int id) {\n", + " this.id = id;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " for(int i = 0; i < 100; i++)\n", + " val++;\n", + " System.out.println(id + \" \"+\n", + " Thread.currentThread().getName() + \" \" + val);\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个任务增加val一百次。这似乎很简单。让我们用CachedThreadPool尝试一下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CachedThreadPool2.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class CachedThreadPool2 {\n", + " public static void main(String[] args) {\n", + " ExecutorService exec\n", + " =Executors.newCachedThreadPool();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(InterferingTask::new)\n", + " .forEach(exec::execute);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0 pool-1-thread-1 200\n", + "1 pool-1-thread-2 200\n", + "4 pool-1-thread-5 300\n", + "5 pool-1-thread-6 400\n", + "8 pool-1-thread-9 500\n", + "9 pool-1-thread-10 600\n", + "2 pool-1-thread-3 700\n", + "7 pool-1-thread-8 800\n", + "3 pool-1-thread-4 900\n", + "6 pool-1-thread-7 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们说这样的类不是线程安全的。让我们看看SingleThreadExecutor会发生什么:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/SingleThreadExecutor3.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class SingleThreadExecutor3 {\n", + " public static void main(String[] args)throws InterruptedException {\n", + " ExecutorService exec\n", + " =Executors.newSingleThreadExecutor();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(InterferingTask::new)\n", + " .forEach(exec::execute);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "0 pool-1-thread-1 100\n", + "1 pool-1-thread-1 200\n", + "2 pool-1-thread-1 300\n", + "3 pool-1-thread-1 400\n", + "4 pool-1-thread-1 500\n", + "5 pool-1-thread-1 600\n", + "6 pool-1-thread-1 700\n", + "7 pool-1-thread-1 800\n", + "8 pool-1-thread-1 900\n", + "9 pool-1-thread-1 1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。线程限制限制了加速,但可以节省很多困难的调试和重写。\n", + "\n", + "- 产生结果\n", + "\n", + "因为**InterferingTask**是一个**Runnable**,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了**CachedThreadPool2.java**。**InterferingTask**中的**val**被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。\n", + "\n", + "避免竞争条件的最好方法是避免可变的共享状态。我们可以称之为自私的孩子原则:什么都不分享。\n", + "\n", + "使用**InterferingTask**,最好删除副作用并返回任务结果。为此,我们创建**Callable**而不是**Runnable**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CountingTask.java\n", + "import java.util.concurrent.*;\n", + "public class CountingTask implements Callable {\n", + " final int id;\n", + " public CountingTask(int id) { this.id = id; }\n", + " @Override\n", + " public Integer call() {\n", + " Integer val = 0;\n", + " for(int i = 0; i < 100; i++)\n", + " val++;\n", + " System.out.println(id + \" \" +\n", + " Thread.currentThread().getName() + \" \" + val);\n", + " return val;\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态\n", + "\n", + "**ExecutorService**允许你使用**invokeAll()**启动集合中的每个Callable:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CachedThreadPool3.java\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class CachedThreadPool3 {\n", + " public static Integer extractResult(Future f) {\n", + " try {\n", + " return f.get();\n", + " } catch(Exception e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " public static void main(String[] args)throws InterruptedException {\n", + " ExecutorService exec =\n", + " Executors.newCachedThreadPool();\n", + " List tasks =\n", + " IntStream.range(0, 10)\n", + " .mapToObj(CountingTask::new)\n", + " .collect(Collectors.toList());\n", + " List> futures =\n", + " exec.invokeAll(tasks);\n", + " Integer sum = futures.stream()\n", + " .map(CachedThreadPool3::extractResult)\n", + " .reduce(0, Integer::sum);\n", + " System.out.println(\"sum = \" + sum);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1 pool-1-thread-2 100\n", + "0 pool-1-thread-1 100\n", + "4 pool-1-thread-5 100\n", + "5 pool-1-thread-6 100\n", + "8 pool-1-thread-9 100\n", + "9 pool-1-thread-10 100\n", + "2 pool-1-thread-3 100\n", + "3 pool-1-thread-4 100\n", + "6 pool-1-thread-7 100\n", + "7 pool-1-thread-8 100\n", + "sum = 1000\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Futures.java\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class Futures {\n", + " public static void main(String[] args)throws InterruptedException, ExecutionException {\n", + " ExecutorService exec\n", + " =Executors.newSingleThreadExecutor();\n", + " Future f =\n", + " exec.submit(newCountingTask(99));\n", + " System.out.println(f.get()); // [1]\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "99 pool-1-thread-1 100\n", + "100" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- [1] 当你的任务尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。\n", + "\n", + "但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。\n", + "\n", + "还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。\n", + "\n", + "因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,你仍会在遗留库中遇到Futures\n", + "\n", + "我们可以使用并行Stream以更简单,更优雅的方式解决这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CountingStream.java\n", + "// {VisuallyInspectOutput}\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class CountingStream {\n", + " public static void main(String[] args) {\n", + " System.out.println(\n", + " IntStream.range(0, 10)\n", + " .parallel()\n", + " .mapToObj(CountingTask::new)\n", + " .map(ct -> ct.call())\n", + " .reduce(0, Integer::sum));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1 ForkJoinPool.commonPool-worker-3 100\n", + "8 ForkJoinPool.commonPool-worker-2 100\n", + "0 ForkJoinPool.commonPool-worker-6 100\n", + "2 ForkJoinPool.commonPool-worker-1 100\n", + "4 ForkJoinPool.commonPool-worker-5 100\n", + "9 ForkJoinPool.commonPool-worker-7 100\n", + "6 main 100\n", + "7 ForkJoinPool.commonPool-worker-4 100\n", + "5 ForkJoinPool.commonPool-worker-2 100\n", + "3 ForkJoinPool.commonPool-worker-3 100\n", + "1000" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这不仅更容易理解,我们需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。\n", + "\n", + "- Lambda和方法引用作为任务\n", + "\n", + "在 `java8` , 你不需要受限于在 `Runnables ` 和 `Callables` 时,使用`lambdas` 和方法引用, 同样也可以通过匹配签名来引用(即,它支持结构一致性)。 所以我们可以将 `notRunnables` 或 `Callables` 的参数传递给`ExecutorService` :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/LambdasAndMethodReferences.java\n", + "import java.util.concurrent.*;\n", + "class NotRunnable {\n", + " public void go() {\n", + " System.out.println(\"NotRunnable\");\n", + " }\n", + "}\n", + "class NotCallable {\n", + " public Integer get() {\n", + " System.out.println(\"NotCallable\");\n", + " return 1;\n", + " }\n", + "}\n", + "public class LambdasAndMethodReferences {\n", + " public static void main(String[] args)throws InterruptedException {\n", + " ExecutorService exec =\n", + " Executors.newCachedThreadPool();\n", + " exec.submit(() -> System.out.println(\"Lambda1\"));\n", + " exec.submit(new NotRunnable()::go);\n", + " exec.submit(() -> {\n", + " System.out.println(\"Lambda2\");\n", + " return 1;\n", + " });\n", + " exec.submit(new NotCallable()::get);\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Lambda1\n", + "NotCallable\n", + "NotRunnable\n", + "Lambda2\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,你可以在后两次调用的情况下提取结果。\n", + "\n", + "\n", + "## 终止耗时任务\n", + "\n", + "并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当你关闭程序时。\n", + "\n", + "最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。\n", + "\n", + "InterruptedException,因为设计的向后兼容性残留。\n", + "\n", + "任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的shutdown进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。\n", + "\n", + "以这种方式终止任务听起来很简单:设置任务可以看到的**boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。\n", + "\n", + "在研究Java文献时,你会发现很多解决这个问题的方法,经常使用**volatile**关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发](./Appendix-Low-Level-Concurrency.md)中有所涉及。\n", + "\n", + "Java 5引入了**Atomic**类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加**AtomicBoolean**标志,告诉任务清理自己并退出。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/QuittableTask.java\n", + "import java.util.concurrent.atomic.AtomicBoolean;import onjava.Nap;\n", + "public class QuittableTask implements Runnable {\n", + " final int id;\n", + " public QuittableTask(int id) {\n", + " this.id = id;\n", + " }\n", + " private AtomicBoolean running =\n", + " new AtomicBoolean(true);\n", + " public void quit() {\n", + " running.set(false);\n", + " }\n", + " @Override\n", + " public void run() {\n", + " while(running.get()) // [1]\n", + " new Nap(0.1);\n", + " System.out.print(id + \" \"); // [2]\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。\n", + "\n", + "- [1]:只要运行标志为true,此任务的run()方法将继续。\n", + "- [2]: 显示仅在任务退出时发生。\n", + "\n", + "需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。\n", + "\n", + "作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/QuittingTasks.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "public class QuittingTasks {\n", + " public static final int COUNT = 150;\n", + " public static void main(String[] args) {\n", + " ExecutorService es =\n", + " Executors.newCachedThreadPool();\n", + " List tasks =\n", + " IntStream.range(1, COUNT)\n", + " .mapToObj(QuittableTask::new)\n", + " .peek(qt -> es.execute(qt))\n", + " .collect(Collectors.toList());\n", + " new Nap(1);\n", + " tasks.forEach(QuittableTask::quit); es.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "24 27 31 8 11 7 19 12 16 4 23 3 28 32 15 20 63 60 68 6764 39 47 52 51 55 40 43 48 59 44 56 36 35 71 72 83 10396 92 88 99 100 87 91 79 75 84 76 115 108 112 104 107111 95 80 147 120 127 119 123 144 143 116 132 124 128\n", + "136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。\n", + "\n", + "## CompletableFuture类\n", + "\n", + "作为介绍,这里是使用CompletableFutures在QuittingTasks.java中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/QuittingCompletable.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "public class QuittingCompletable {\n", + " public static void main(String[] args) {\n", + " List tasks =\n", + " IntStream.range(1, QuittingTasks.COUNT)\n", + " .mapToObj(QuittableTask::new)\n", + " .collect(Collectors.toList());\n", + " List> cfutures =\n", + " tasks.stream()\n", + " .map(CompletableFuture::runAsync)\n", + " .collect(Collectors.toList());\n", + " new Nap(1);\n", + " tasks.forEach(QuittableTask::quit);\n", + " cfutures.forEach(CompletableFuture::join);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "任务是一个 `List`,就像在 `QuittingTasks.java` 中一样,但是在这个例子中,没有 `peek()` 将每个 `QuittableTask` 提交给 `ExecutorService`。相反,在创建 `cfutures` 期间,每个任务都交给 `CompletableFuture::runAsync`。这执行 `VerifyTask.run()` 并返回 `CompletableFuture` 。因为 `run()` 不返回任何内容,所以在这种情况下我只使用 `CompletableFuture` 调用 `join()` 来等待它完成。\n", + "\n", + "在本例中需要注意的重要一点是,运行任务不需要使用 `ExecutorService`。而是直接交给 `CompletableFuture` 管理 (不过你可以向它提供自己定义的 `ExectorService`)。您也不需要调用 `shutdown()`;事实上,除非你像我在这里所做的那样显式地调用 `join()`,否则程序将尽快退出,而不必等待任务完成。\n", + "\n", + "这个例子只是一个起点。你很快就会看到 `ComplempleFuture` 能够做得更多。\n", + "\n", + "### 基本用法\n", + "\n", + "这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Machina.java\n", + "import onjava.Nap;\n", + "public class Machina {\n", + " public enum State {\n", + " START, ONE, TWO, THREE, END;\n", + " State step() {\n", + " if(equals(END))\n", + " return END;\n", + " return values()[ordinal() + 1];\n", + " }\n", + " }\n", + " private State state = State.START;\n", + " private final int id;\n", + " public Machina(int id) {\n", + " this.id = id;\n", + " }\n", + " public static Machina work(Machina m) {\n", + " if(!m.state.equals(State.END)){\n", + " new Nap(0.1);\n", + " m.state = m.state.step();\n", + " }\n", + " System.out.println(m);\n", + " return m;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return\"Machina\" + id + \": \" + (state.equals(State.END)? \"complete\" : state);\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。\n", + "\n", + "**CompletableFuture**可以被用来做的一件事是, 使用**completedFuture()**将它感兴趣的对象进行包装。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletedMachina.java\n", + "import java.util.concurrent.*;\n", + "public class CompletedMachina {\n", + " public static void main(String[] args) {\n", + " CompletableFuture cf =\n", + " CompletableFuture.completedFuture(\n", + " new Machina(0));\n", + " try {\n", + " Machina m = cf.get(); // Doesn't block\n", + " } catch(InterruptedException |\n", + " ExecutionException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。\n", + "\n", + "通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。\n", + "\n", + "当我们将**handle()**包装在**CompletableFuture**中时,发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,使得事情变得更加有趣:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletableApply.java\n", + "import java.util.concurrent.*;\n", + "public class CompletableApply {\n", + " public static void main(String[] args) {\n", + " CompletableFuture cf =\n", + " CompletableFuture.completedFuture(\n", + " new Machina(0));\n", + " CompletableFuture cf2 =\n", + " cf.thenApply(Machina::work);\n", + " CompletableFuture cf3 =\n", + " cf2.thenApply(Machina::work);\n", + " CompletableFuture cf4 =\n", + " cf3.thenApply(Machina::work);\n", + " CompletableFuture cf5 =\n", + " cf4.thenApply(Machina::work);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**输出结果**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Machina0: ONE\n", + "Machina0: TWO\n", + "Machina0: THREE\n", + "Machina0: complete" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是(类似于流中的 `map()` )函数也可以返回不同的类型,这将体现在返回类型上。\n", + "\n", + "你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。\n", + "\n", + "我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletableApplyChained.javaimport java.util.concurrent.*;\n", + "import onjava.Timer;\n", + "public class CompletableApplyChained {\n", + " public static void main(String[] args) {\n", + " Timer timer = new Timer();\n", + " CompletableFuture cf =\n", + " CompletableFuture.completedFuture(\n", + " new Machina(0))\n", + " .thenApply(Machina::work)\n", + " .thenApply(Machina::work)\n", + " .thenApply(Machina::work)\n", + " .thenApply(Machina::work);\n", + " System.out.println(timer.duration());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Machina0: ONE\n", + "Machina0: TWO\n", + "Machina0: THREE\n", + "Machina0: complete\n", + "514" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里我们还添加了一个 `Timer`,它的功能在每一步都显性地增加 100ms 等待时间之外,还将 `CompletableFuture` 内部 `thenApply` 带来的额外开销给体现出来了。 \n", + "**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。\n", + "\n", + "回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletableApplyAsync.java\n", + "import java.util.concurrent.*;\n", + "import onjava.*;\n", + "public class CompletableApplyAsync {\n", + " public static void main(String[] args) {\n", + " Timer timer = new Timer();\n", + " CompletableFuture cf =\n", + " CompletableFuture.completedFuture(\n", + " new Machina(0))\n", + " .thenApplyAsync(Machina::work)\n", + " .thenApplyAsync(Machina::work)\n", + " .thenApplyAsync(Machina::work)\n", + " .thenApplyAsync(Machina::work);\n", + " System.out.println(timer.duration());\n", + " System.out.println(cf.join());\n", + " System.out.println(timer.duration());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "116\n", + "Machina0: ONE\n", + "Machina0: TWO\n", + "Machina0:THREE\n", + "Machina0: complete\n", + "Machina0: complete\n", + "552" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "同步调用(我们通常使用的那种)意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。\n", + "\n", + "事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到cf操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。\n", + "\n", + "这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。\n", + "\n", + "幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道系统的一些具体的变化,否则你更想使用异步调用来实现程序。\n", + "\n", + "- 其他操作\n", + "\n", + "当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务(在本书中,我们忽略了 `Executor` 选项)。\n", + "\n", + "下面的示例展示了所有\"基本\"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常(我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "package onjava;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class CompletableUtilities {\n", + " // Get and show value stored in a CF:\n", + " public static void showr(CompletableFuture c) {\n", + " try {\n", + " System.out.println(c.get());\n", + " } catch(InterruptedException\n", + " | ExecutionException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " // For CF operations that have no value:\n", + " public static void voidr(CompletableFuture c) {\n", + " try {\n", + " c.get(); // Returns void\n", + " } catch(InterruptedException\n", + " | ExecutionException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`showr()` 在 `CompletableFuture` 上调用 `get()`,并显示结果,`try/catch` 两个可能会出现的异常。\n", + "\n", + "`voidr()` 是 `CompletableFuture` 的 `showr()` 版本,也就是说,`CompletableFutures` 只为任务完成或失败时显示信息。\n", + "\n", + "为简单起见,下面的 `CompletableFutures` 只包装整数。`cfi()` 是一个便利的方法,它把一个整数包装在一个完整的 `CompletableFuture` :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletableOperations.java\n", + "import java.util.concurrent.*;\n", + "import static onjava.CompletableUtilities.*;\n", + "\n", + "public class CompletableOperations {\n", + " static CompletableFuture cfi(int i) {\n", + " return\n", + " CompletableFuture.completedFuture(\n", + " Integer.valueOf(i));\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " showr(cfi(1)); // Basic test\n", + " voidr(cfi(2).runAsync(() ->\n", + " System.out.println(\"runAsync\")));\n", + " voidr(cfi(3).thenRunAsync(() ->\n", + " System.out.println(\"thenRunAsync\")));\n", + " voidr(CompletableFuture.runAsync(() ->\n", + " System.out.println(\"runAsync is static\")));\n", + " showr(CompletableFuture.supplyAsync(() -> 99));\n", + " voidr(cfi(4).thenAcceptAsync(i ->\n", + " System.out.println(\"thenAcceptAsync: \" + i)));\n", + " showr(cfi(5).thenApplyAsync(i -> i + 42));\n", + " showr(cfi(6).thenComposeAsync(i -> cfi(i + 99)));\n", + " CompletableFuture c = cfi(7);\n", + " c.obtrudeValue(111);\n", + " showr(c);\n", + " showr(cfi(8).toCompletableFuture());\n", + " c = new CompletableFuture<>();\n", + " c.complete(9);\n", + " showr(c);\n", + " c = new CompletableFuture<>();\n", + " c.cancel(true);\n", + " System.out.println(\"cancelled: \" +\n", + " c.isCancelled());\n", + " System.out.println(\"completed exceptionally: \" +\n", + " c.isCompletedExceptionally());\n", + " System.out.println(\"done: \" + c.isDone());\n", + " System.out.println(c);\n", + " c = new CompletableFuture<>();\n", + " System.out.println(c.getNow(777));\n", + " c = new CompletableFuture<>();\n", + " c.thenApplyAsync(i -> i + 42)\n", + " .thenApplyAsync(i -> i * 12);\n", + " System.out.println(\"dependents: \" +\n", + " c.getNumberOfDependents());\n", + " c.thenApplyAsync(i -> i / 2);\n", + " System.out.println(\"dependents: \" +\n", + " c.getNumberOfDependents());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**输出结果** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "1\n", + "runAsync\n", + "thenRunAsync\n", + "runAsync is static\n", + "99\n", + "thenAcceptAsync: 4\n", + "47\n", + "105\n", + "111\n", + "8\n", + "9\n", + "cancelled: true\n", + "completed exceptionally: true\n", + "done: true\n", + "java.util.concurrent.CompletableFuture@6d311334[Complet ed exceptionally]\n", + "777\n", + "dependents: 1\n", + "dependents: 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `main()` 包含一系列可由其 `int` 值引用的测试。\n", + " - `cfi(1)` 演示了 `showr()` 正常工作。\n", + " - `cfi(2)` 是调用 `runAsync()` 的示例。由于 `Runnable` 不产生返回值,因此使用了返回 `CompletableFuture ` 的`voidr()` 方法。\n", + " - 注意使用 `cfi(3)`,`thenRunAsync()` 效果似乎与 上例 `cfi(2)` 使用的 `runAsync()`相同,差异在后续的测试中体现:\n", + " - `runAsync()` 是一个 `static` 方法,所以你通常不会像`cfi(2)`一样调用它。相反你可以在 `QuittingCompletable.java` 中使用它。\n", + " - 后续测试中表明 `supplyAsync()` 也是静态方法,区别在于它需要一个 `Supplier` 而不是`Runnable`, 并产生一个`CompletableFuture` 而不是 `CompletableFuture`。\n", + " - `then` 系列方法将对现有的 `CompletableFuture` 进一步操作。\n", + " - 与 `thenRunAsync()` 不同,`cfi(4)`,`cfi(5)` 和`cfi(6)` \"then\" 方法的参数是未包装的 `Integer`。\n", + " - 通过使用 `voidr()`方法可以看到: \n", + " - `AcceptAsync()`接收了一个 `Consumer`,因此不会产生结果。\n", + " - `thenApplyAsync()` 接收一个`Function`, 并生成一个结果(该结果的类型可以不同于其输入类型)。\n", + " - `thenComposeAsync()` 与 `thenApplyAsync()`非常相似,唯一区别在于其 `Function` 必须产生已经包装在`CompletableFuture`中的结果。\n", + " - `cfi(7)` 示例演示了 `obtrudeValue()`,它强制将值作为结果。\n", + " - `cfi(8)` 使用 `toCompletableFuture()` 从 `CompletionStage` 生成一个`CompletableFuture`。\n", + " - `c.complete(9)` 显示了如何通过给它一个结果来完成一个`task`(`future`)(与 `obtrudeValue()` 相对,后者可能会迫使其结果替换该结果)。\n", + " - 如果你调用 `CompletableFuture`中的 `cancel()`方法,如果已经完成此任务,则正常结束。 如果尚未完成,则使用 `CancellationException` 完成此 `CompletableFuture`。\n", + " - 如果任务(`future`)完成,则**getNow()**方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。\n", + " - 最后,我们看一下依赖(`dependents`)的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。\n", + " - 这表明你可以使用一个`CompletionStage`,当它完成时,可以根据其结果派生多个新任务。\n", + "\n", + "\n", + "\n", + "### 结合 CompletableFuture\n", + "\n", + "第二种类型的 `CompletableFuture` 方法采用两种 `CompletableFuture` 并以各异方式将它们组合在一起。就像两个人在比赛一样, 一个`CompletableFuture`通常比另一个更早地到达终点。这些方法允许你以不同的方式处理结果。\n", + "为了测试这一点,我们将创建一个任务,它有一个我们可以控制的定义了完成任务所需要的时间量的参数。 \n", + "CompletableFuture 先完成:\u0016" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Workable.java\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "public class Workable {\n", + " String id;\n", + " final double duration;\n", + "\n", + " public Workable(String id, double duration) {\n", + " this.id = id;\n", + " this.duration = duration;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"Workable[\" + id + \"]\";\n", + " }\n", + "\n", + " public static Workable work(Workable tt) {\n", + " new Nap(tt.duration); // Seconds\n", + " tt.id = tt.id + \"W\";\n", + " System.out.println(tt);\n", + " return tt;\n", + " }\n", + "\n", + " public static CompletableFuture make(String id, double duration) {\n", + " return CompletableFuture\n", + " .completedFuture(\n", + " new Workable(id, duration)\n", + " )\n", + " .thenApplyAsync(Workable::work);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母W附加到id上,表示工作已经完成。\n", + "\n", + "现在我们可以创建多个竞争的 `CompletableFuture`,并使用 `CompletableFuture` 库中的各种方法来进行操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/DualCompletableOperations.java\n", + "import java.util.concurrent.*;\n", + "import static onjava.CompletableUtilities.*;\n", + "\n", + "public class DualCompletableOperations {\n", + " static CompletableFuture cfA, cfB;\n", + "\n", + " static void init() {\n", + " cfA = Workable.make(\"A\", 0.15);\n", + " cfB = Workable.make(\"B\", 0.10); // Always wins\n", + " }\n", + "\n", + " static void join() {\n", + " cfA.join();\n", + " cfB.join();\n", + " System.out.println(\"*****************\");\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " init();\n", + " voidr(cfA.runAfterEitherAsync(cfB, () ->\n", + " System.out.println(\"runAfterEither\")));\n", + " join();\n", + "\n", + " init();\n", + " voidr(cfA.runAfterBothAsync(cfB, () ->\n", + " System.out.println(\"runAfterBoth\")));\n", + " join();\n", + "\n", + " init();\n", + " showr(cfA.applyToEitherAsync(cfB, w -> {\n", + " System.out.println(\"applyToEither: \" + w);\n", + " return w;\n", + " }));\n", + " join();\n", + "\n", + " init();\n", + " voidr(cfA.acceptEitherAsync(cfB, w -> {\n", + " System.out.println(\"acceptEither: \" + w);\n", + " }));\n", + " join();\n", + "\n", + " init();\n", + " voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> {\n", + " System.out.println(\"thenAcceptBoth: \"\n", + " + w1 + \", \" + w2);\n", + " }));\n", + " join();\n", + "\n", + " init();\n", + " showr(cfA.thenCombineAsync(cfB, (w1, w2) -> {\n", + " System.out.println(\"thenCombine: \"\n", + " + w1 + \", \" + w2);\n", + " return w1;\n", + " }));\n", + " join();\n", + "\n", + " init();\n", + " CompletableFuture\n", + " cfC = Workable.make(\"C\", 0.08),\n", + " cfD = Workable.make(\"D\", 0.09);\n", + " CompletableFuture.anyOf(cfA, cfB, cfC, cfD)\n", + " .thenRunAsync(() ->\n", + " System.out.println(\"anyOf\"));\n", + " join();\n", + "\n", + " init();\n", + " cfC = Workable.make(\"C\", 0.08);\n", + " cfD = Workable.make(\"D\", 0.09);\n", + " CompletableFuture.allOf(cfA, cfB, cfC, cfD)\n", + " .thenRunAsync(() ->\n", + " System.out.println(\"allOf\"));\n", + " join();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**输出结果**:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Workable[BW]\n", + "runAfterEither\n", + "Workable[AW]\n", + "*****************\n", + "Workable[BW]\n", + "Workable[AW]\n", + "runAfterBoth\n", + "*****************\n", + "Workable[BW]\n", + "applyToEither: Workable[BW]\n", + "Workable[BW]\n", + "Workable[AW]\n", + "*****************\n", + "Workable[BW]\n", + "acceptEither: Workable[BW]\n", + "Workable[AW]\n", + "*****************\n", + "Workable[BW]\n", + "Workable[AW]\n", + "thenAcceptBoth: Workable[AW], Workable[BW]\n", + "****************\n", + " Workable[BW]\n", + " Workable[AW]\n", + " thenCombine: Workable[AW], Workable[BW]\n", + " Workable[AW]\n", + " *****************\n", + " Workable[CW]\n", + " anyOf\n", + " Workable[DW]\n", + " Workable[BW]\n", + " Workable[AW]\n", + " *****************\n", + " Workable[CW]\n", + " Workable[DW]\n", + " Workable[BW]\n", + " Workable[AW]\n", + " *****************\n", + " allOf" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- 为了方便访问, 将 `cfA` 和 `cfB` 定义为 `static`的。 \n", + " - `init()`方法用于 `A`, `B` 初始化这两个变量,因为 `B` 总是给出比`A`较短的延迟,所以总是 `win` 的一方。\n", + " - `join()` 是在两个方法上调用 `join()` 并显示边框的另一个便利方法。\n", + "- 所有这些 “`dual`” 方法都以一个 `CompletableFuture` 作为调用该方法的对象,第二个 `CompletableFuture` 作为第一个参数,然后是要执行的操作。\n", + "- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载)的 `CompletableFuture`。\n", + "- 方法的名称不言自明,你可以通过查看输出来验证这一点。一个特别有趣的方法是 `combineAsync()`,它等待两个 `CompletableFuture` 完成,然后将它们都交给一个 `BiFunction`,这个 `BiFunction` 可以将结果加入到最终的 `CompletableFuture` 的有效负载中。\n", + "\n", + "\n", + "### 模拟\n", + "\n", + "作为使用 `CompletableFuture` 将一系列操作组合的示例,让我们模拟一下制作蛋糕的过程。在第一阶段,我们准备并将原料混合成面糊:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Batter.java\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "public class Batter {\n", + " static class Eggs {\n", + " }\n", + "\n", + " static class Milk {\n", + " }\n", + "\n", + " static class Sugar {\n", + " }\n", + "\n", + " static class Flour {\n", + " }\n", + "\n", + " static T prepare(T ingredient) {\n", + " new Nap(0.1);\n", + " return ingredient;\n", + " }\n", + "\n", + " static CompletableFuture prep(T ingredient) {\n", + " return CompletableFuture\n", + " .completedFuture(ingredient)\n", + " .thenApplyAsync(Batter::prepare);\n", + " }\n", + "\n", + " public static CompletableFuture mix() {\n", + " CompletableFuture eggs = prep(new Eggs());\n", + " CompletableFuture milk = prep(new Milk());\n", + " CompletableFuture sugar = prep(new Sugar());\n", + " CompletableFuture flour = prep(new Flour());\n", + " CompletableFuture\n", + " .allOf(eggs, milk, sugar, flour)\n", + " .join();\n", + " new Nap(0.1); // Mixing time\n", + " return CompletableFuture.completedFuture(new Batter());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每种原料都需要一些时间来准备。`allOf()` 等待所有的配料都准备好,然后使用更多些的时间将其混合成面糊。接下来,我们把单批面糊放入四个平底锅中烘烤。产品作为 `CompletableFutures` 流返回:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Baked.java\n", + "\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "import onjava.Nap;\n", + "\n", + "public class Baked {\n", + " static class Pan {\n", + " }\n", + "\n", + " static Pan pan(Batter b) {\n", + " new Nap(0.1);\n", + " return new Pan();\n", + " }\n", + "\n", + " static Baked heat(Pan p) {\n", + " new Nap(0.1);\n", + " return new Baked();\n", + " }\n", + "\n", + " static CompletableFuture bake(CompletableFuture cfb) {\n", + " return cfb\n", + " .thenApplyAsync(Baked::pan)\n", + " .thenApplyAsync(Baked::heat);\n", + " }\n", + "\n", + " public static Stream> batch() {\n", + " CompletableFuture batter = Batter.mix();\n", + " return Stream.of(\n", + " bake(batter),\n", + " bake(batter),\n", + " bake(batter),\n", + " bake(batter)\n", + " );\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最后,我们制作了一批糖,并用它对蛋糕进行糖化:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/FrostedCake.java\n", + "\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "import onjava.Nap;\n", + "\n", + "final class Frosting {\n", + " private Frosting() {\n", + " }\n", + "\n", + " static CompletableFuture make() {\n", + " new Nap(0.1);\n", + " return CompletableFuture\n", + " .completedFuture(new Frosting());\n", + " }\n", + "}\n", + "\n", + "public class FrostedCake {\n", + " public FrostedCake(Baked baked, Frosting frosting) {\n", + " new Nap(0.1);\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"FrostedCake\";\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " Baked.batch().forEach(\n", + " baked -> baked\n", + " .thenCombineAsync(Frosting.make(),\n", + " (cake, frosting) ->\n", + " new FrostedCake(cake, frosting))\n", + " .thenAcceptAsync(System.out::println)\n", + " .join());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦你习惯了这种背后的想法, `CompletableFuture` 它们相对易于使用。\n", + "\n", + "### 异常\n", + "\n", + "与 `CompletableFuture` 在处理链中包装对象的方式相同,它也会缓冲异常。这些在处理时调用者是无感的,但仅当你尝试提取结果时才会被告知。为了说明它们是如何工作的,我们首先创建一个类,它在特定的条件下抛出一个异常:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Breakable.java\n", + "import java.util.concurrent.*;\n", + "public class Breakable {\n", + " String id;\n", + " private int failcount;\n", + "\n", + " public Breakable(String id, int failcount) {\n", + " this.id = id;\n", + " this.failcount = failcount;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"Breakable_\" + id + \" [\" + failcount + \"]\";\n", + " }\n", + "\n", + " public static Breakable work(Breakable b) {\n", + " if (--b.failcount == 0) {\n", + " System.out.println(\n", + " \"Throwing Exception for \" + b.id + \"\"\n", + " );\n", + " throw new RuntimeException(\n", + " \"Breakable_\" + b.id + \" failed\"\n", + " );\n", + " }\n", + " System.out.println(b);\n", + " return b;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当`failcount` > 0,且每次将对象传递给 `work()` 方法时, `failcount - 1` 。当`failcount - 1 = 0` 时,`work()` 将抛出一个异常。如果传给 `work()` 的 `failcount = 0` ,`work()` 永远不会抛出异常。\n", + "\n", + "注意,异常信息此示例中被抛出( `RuntimeException` )\n", + "\n", + "在下面示例 `test()` 方法中,`work()` 多次应用于 `Breakable`,因此如果 `failcount` 在范围内,就会抛出异常。然而,在测试`A`到`E`中,你可以从输出中看到抛出了异常,但它们从未出现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletableExceptions.java\n", + "import java.util.concurrent.*;\n", + "public class CompletableExceptions {\n", + " static CompletableFuture test(String id, int failcount) {\n", + " return CompletableFuture.completedFuture(\n", + " new Breakable(id, failcount))\n", + " .thenApply(Breakable::work)\n", + " .thenApply(Breakable::work)\n", + " .thenApply(Breakable::work)\n", + " .thenApply(Breakable::work);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " // Exceptions don't appear ...\n", + " test(\"A\", 1);\n", + " test(\"B\", 2);\n", + " test(\"C\", 3);\n", + " test(\"D\", 4);\n", + " test(\"E\", 5);\n", + " // ... until you try to fetch the value:\n", + " try {\n", + " test(\"F\", 2).get(); // or join()\n", + " } catch (Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " // Test for exceptions:\n", + " System.out.println(\n", + " test(\"G\", 2).isCompletedExceptionally()\n", + " );\n", + " // Counts as \"done\":\n", + " System.out.println(test(\"H\", 2).isDone());\n", + " // Force an exception:\n", + " CompletableFuture cfi =\n", + " new CompletableFuture<>();\n", + " System.out.println(\"done? \" + cfi.isDone());\n", + " cfi.completeExceptionally(\n", + " new RuntimeException(\"forced\"));\n", + " try {\n", + " cfi.get();\n", + " } catch (Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Throwing Exception for A\n", + "Breakable_B [1]\n", + "Throwing Exception for B\n", + "Breakable_C [2]\n", + "Breakable_C [1]\n", + "Throwing Exception for C\n", + "Breakable_D [3]\n", + "Breakable_D [2]\n", + "Breakable_D [1]\n", + "Throwing Exception for D\n", + "Breakable_E [4]\n", + "Breakable_E [3]\n", + "Breakable_E [2]\n", + "Breakable_E [1]\n", + "Breakable_F [1]\n", + "Throwing Exception for F\n", + "java.lang.RuntimeException: Breakable_F failed\n", + "Breakable_G [1]\n", + "Throwing Exception for G\n", + "true\n", + "Breakable_H [1]\n", + "Throwing Exception for H\n", + "true\n", + "done? false\n", + "java.lang.RuntimeException: forced" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试F中调用 `get()` 时,我们才会看到抛出的异常。\n", + "测试 `G` 表明,你可以首先检查在处理期间是否抛出异常,而不抛出该异常。然而,test `H` 告诉我们,不管异常是否成功,它仍然被视为已“完成”。\n", + "代码的最后一部分展示了如何将异常插入到 `CompletableFuture` 中,而不管是否存在任何失败。\n", + "在连接或获取结果时,我们使用 `CompletableFuture` 提供的更复杂的机制来自动响应异常,而不是使用粗糙的 `try-catch`。\n", + "你可以使用与我们看到的所有 `CompletableFuture` 相同的表单来完成此操作:在链中插入一个 `CompletableFuture` 调用。有三个选项 `exceptionally()`,`handle()`, `whenComplete()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CatchCompletableExceptions.java\n", + "import java.util.concurrent.*;\n", + "public class CatchCompletableExceptions {\n", + " static void handleException(int failcount) {\n", + " // Call the Function only if there's an\n", + " // exception, must produce same type as came in:\n", + " CompletableExceptions\n", + " .test(\"exceptionally\", failcount)\n", + " .exceptionally((ex) -> { // Function\n", + " if (ex == null)\n", + " System.out.println(\"I don't get it yet\");\n", + " return new Breakable(ex.getMessage(), 0);\n", + " })\n", + " .thenAccept(str ->\n", + " System.out.println(\"result: \" + str));\n", + "\n", + " // Create a new result (recover):\n", + " CompletableExceptions\n", + " .test(\"handle\", failcount)\n", + " .handle((result, fail) -> { // BiFunction\n", + " if (fail != null)\n", + " return \"Failure recovery object\";\n", + " else\n", + " return result + \" is good\";\n", + " })\n", + " .thenAccept(str ->\n", + " System.out.println(\"result: \" + str));\n", + "\n", + " // Do something but pass the same result through:\n", + " CompletableExceptions\n", + " .test(\"whenComplete\", failcount)\n", + " .whenComplete((result, fail) -> { // BiConsumer\n", + " if (fail != null)\n", + " System.out.println(\"It failed\");\n", + " else\n", + " System.out.println(result + \" OK\");\n", + " })\n", + " .thenAccept(r ->\n", + " System.out.println(\"result: \" + r));\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.println(\"**** Failure Mode ****\");\n", + " handleException(2);\n", + " System.out.println(\"**** Success Mode ****\");\n", + " handleException(0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "**** Failure Mode ****\n", + "Breakable_exceptionally [1]\n", + "Throwing Exception for exceptionally\n", + "result: Breakable_java.lang.RuntimeException:\n", + "Breakable_exceptionally failed [0]\n", + "Breakable_handle [1]\n", + "Throwing Exception for handle\n", + "result: Failure recovery object\n", + "Breakable_whenComplete [1]\n", + "Throwing Exception for whenComplete\n", + "It failed\n", + "**** Success Mode ****\n", + "Breakable_exceptionally [-1]\n", + "Breakable_exceptionally [-2]\n", + "Breakable_exceptionally [-3]\n", + "Breakable_exceptionally [-4]\n", + "result: Breakable_exceptionally [-4]\n", + "Breakable_handle [-1]\n", + "Breakable_handle [-2]\n", + "Breakable_handle [-3]\n", + "Breakable_handle [-4]\n", + "result: Breakable_handle [-4] is good\n", + "Breakable_whenComplete [-1]\n", + "Breakable_whenComplete [-2]\n", + "Breakable_whenComplete [-3]\n", + "Breakable_whenComplete [-4]\n", + "Breakable_whenComplete [-4] OK\n", + "result: Breakable_whenComplete [-4]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `exceptionally()` 参数仅在出现异常时才运行。`exceptionally()` 局限性在于,该函数只能返回输入类型相同的值。\n", + "\n", + "- `exceptionally()` 通过将一个好的对象插入到流中来恢复到一个可行的状态。\n", + "\n", + "- `handle()` 一致被调用来查看是否发生异常(必须检查fail是否为true)。\n", + "\n", + " - 但是 `handle()` 可以生成任何新类型,所以它允许执行处理,而不是像使用 `exceptionally()`那样简单地恢复。\n", + "\n", + " - `whenComplete()` 类似于handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。\n", + "\n", + "\n", + "### 流异常(Stream Exception)\n", + "\n", + "通过修改**CompletableExceptions.java**,看看 **CompletableFuture**异常与流异常有何不同:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/StreamExceptions.java\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "public class StreamExceptions {\n", + " \n", + " static Stream test(String id, int failcount) {\n", + " return Stream.of(new Breakable(id, failcount))\n", + " .map(Breakable::work)\n", + " .map(Breakable::work)\n", + " .map(Breakable::work)\n", + " .map(Breakable::work);\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " // No operations are even applied ...\n", + " test(\"A\", 1);\n", + " test(\"B\", 2);\n", + " Stream c = test(\"C\", 3);\n", + " test(\"D\", 4);\n", + " test(\"E\", 5);\n", + " // ... until there's a terminal operation:\n", + " System.out.println(\"Entering try\");\n", + " try {\n", + " c.forEach(System.out::println); // [1]\n", + " } catch (Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Entering try\n", + "Breakable_C [2]\n", + "Breakable_C [1]\n", + "Throwing Exception for C\n", + "Breakable_C failed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用 `CompletableFuture`,我们可以看到测试A到E的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client \n", + "\n", + "`CompletableFuture` 执行工作并捕获任何异常供以后检索。比较这两者并不容易,因为 `Stream` 在没有终端操作的情况下根本不做任何事情——但是流绝对不会存储它的异常。\n", + "\n", + "### 检查性异常\n", + "\n", + "`CompletableFuture` 和 `parallel Stream` 都不支持包含检查性异常的操作。相反,你必须在调用操作时处理检查到的异常,这会产生不太优雅的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/ThrowsChecked.java\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class ThrowsChecked {\n", + " class Checked extends Exception {}\n", + "\n", + " static ThrowsChecked nochecked(ThrowsChecked tc) {\n", + " return tc;\n", + " }\n", + "\n", + " static ThrowsChecked withchecked(ThrowsChecked tc) throws Checked {\n", + " return tc;\n", + " }\n", + "\n", + " static void testStream() {\n", + " Stream.of(new ThrowsChecked())\n", + " .map(ThrowsChecked::nochecked)\n", + " // .map(ThrowsChecked::withchecked); // [1]\n", + " .map(\n", + " tc -> {\n", + " try {\n", + " return withchecked(tc);\n", + " } catch (Checked e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " });\n", + " }\n", + "\n", + " static void testCompletableFuture() {\n", + " CompletableFuture\n", + " .completedFuture(new ThrowsChecked())\n", + " .thenApply(ThrowsChecked::nochecked)\n", + " // .thenApply(ThrowsChecked::withchecked); // [2]\n", + " .thenApply(\n", + " tc -> {\n", + " try {\n", + " return withchecked(tc);\n", + " } catch (Checked e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " });\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出lambda表达式(或者编写一个不会抛出异常的包装器方法)。\n", + "\n", + "## 死锁\n", + "\n", + "由于任务可以被阻塞,因此一个任务有可能卡在等待另一个任务上,而后者又在等待别的任务,这样一直下去,知道这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间相互等待的连续循环, 没有哪个线程能继续, 这称之为死锁[^6]\n", + "如果你运行一个程序,而它马上就死锁了, 你可以立即跟踪下去。真正的问题在于,程序看起来工作良好, 但是具有潜在的死锁危险。这时, 死锁可能发生,而事先却没有任何征兆, 所以 `bug` 会潜伏在你的程序例,直到客户发现它出乎意料的发生(以一种几乎肯定是很难重现的方式发生)。因此在编写并发程序的时候,进行仔细的程序设计以防止死锁是关键部分。\n", + "埃德斯·迪克斯特拉(`Essger Dijkstra`)发明的“哲学家进餐\"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数目)。这些哲学家将花部分时间思考,花部分时间就餐。他们在思考的时候并不需要任何共享资源;但是他们使用的餐具数量有限。在最初的问题描述中,餐具是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子, 显然,每个哲学家都需要两根筷子才能吃饭。\n", + "引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更一般地讲,筷子的数量与哲学家相同)。他们围在桌子周围,每人之间放一根筷子。 当一个哲学家要就餐时,该哲学家必须同时持有左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到可得到必须的筷子。\n", + "\n", + "**StickHolder** 类通过将单根筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/StickHolder.java\n", + "import java.util.concurrent.*;\n", + "public class StickHolder {\n", + " private static class Chopstick {\n", + " }\n", + "\n", + " private Chopstick stick = new Chopstick();\n", + " private BlockingQueue holder =\n", + " new ArrayBlockingQueue<>(1);\n", + "\n", + " public StickHolder() {\n", + " putDown();\n", + " }\n", + "\n", + " public void pickUp() {\n", + " try {\n", + " holder.take(); // Blocks if unavailable\n", + " } catch (InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " public void putDown() {\n", + " try {\n", + " holder.put(stick);\n", + " } catch (InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为简单起见,`Chopstick`(`static`) 实际上不是由 `StickHolder` 生产的,而是在其类中保持私有的。\n", + "\n", + "如果您调用了`pickUp()`,而 `stick` 不可用,那么`pickUp()`将阻塞该 `stick`,直到另一个哲学家调用`putDown()` 将 `stick` 返回。 \n", + "\n", + "注意,该类中的所有线程安全都是通过 `BlockingQueue` 实现的。\n", + "\n", + "每个哲学家都是一项任务,他们试图把筷子分别 `pickUp()` 在左手和右手上,这样筷子才能吃东西,然后通过 `putDown()` 放下 `stick`。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Philosopher.java\n", + "public class Philosopher implements Runnable {\n", + " private final int seat;\n", + " private final StickHolder left, right;\n", + "\n", + " public Philosopher(int seat, StickHolder left, StickHolder right) {\n", + " this.seat = seat;\n", + " this.left = left;\n", + " this.right = right;\n", + " }\n", + "\n", + " @Override\n", + " public String toString() {\n", + " return \"P\" + seat;\n", + " }\n", + "\n", + " @Override\n", + " public void run() {\n", + " while (true) {\n", + " // System.out.println(\"Thinking\"); // [1]\n", + " right.pickUp();\n", + " left.pickUp();\n", + " System.out.println(this + \" eating\");\n", + " right.putDown();\n", + " left.putDown();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。\n", + "\n", + "结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为这种语法更简洁:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/DiningPhilosophers.java\n", + "// Hidden deadlock\n", + "// {ExcludeFromGradle} Gradle has trouble\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "public class DiningPhilosophers {\n", + " private StickHolder[] sticks;\n", + " private Philosopher[] philosophers;\n", + "\n", + " public DiningPhilosophers(int n) {\n", + " sticks = new StickHolder[n];\n", + " Arrays.setAll(sticks, i -> new StickHolder());\n", + " philosophers = new Philosopher[n];\n", + " Arrays.setAll(philosophers, i ->\n", + " new Philosopher(i,\n", + " sticks[i], sticks[(i + 1) % n])); // [1]\n", + " // Fix by reversing stick order for this one:\n", + " // philosophers[1] = // [2]\n", + " // new Philosopher(0, sticks[0], sticks[1]);\n", + " Arrays.stream(philosophers)\n", + " .forEach(CompletableFuture::runAsync); // [3]\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " // Returns right away:\n", + " new DiningPhilosophers(5); // [4]\n", + " // Keeps main() from exiting:\n", + " new Nap(3, \"Shutdown\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- 当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心不会产生死锁,但两核以上却很容易产生死锁。\n", + "- 此行为使该示例更好地说明了死锁,因为你可能正在具有2核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在2核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。\n", + "- 在 `DiningPhilosophers` 的构造方法中,每个哲学家都获得一个左右筷子的引用。除最后一个哲学家外,都是通过把哲学家放在下一双空闲筷子之间来初始化: \n", + " - 最后一位哲学家得到了第0根筷子作为他的右筷子,所以圆桌就完成。\n", + " - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。\n", + "- 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。\n", + " - 为了让每个哲学家在[3]上运行,调用 `runAsync()`,这意味着DiningPhilosophers的构造函数立即返回到[4]。\n", + " - 如果没有任何东西阻止 `main()` 完成,程序就会退出,不会做太多事情。\n", + " - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出(假设/可能是)死锁程序。\n", + " - 在给定的配置中,哲学家几乎不花时间思考。因此,他们在吃东西的时候都争着用筷子,而且往往很快就会陷入僵局。你可以改变这个:\n", + "\n", + "1. 通过增加[4]的值来添加更多哲学家。\n", + "\n", + "2. 在Philosopher.java中取消注释行[1]。\n", + "\n", + "任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个示例相当有趣,因为它演示了看起来可以正确运行,但实际上会可能发生死锁的程序。\n", + "\n", + "要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:\n", + "\n", + "1) 互斥条件。任务使用的资源中至少有一个不能共享的。 这里,一根筷子一次就只能被一个哲学家使用。\n", + "2) 至少有一个任务它必须持有一个资源且正在等待获取一个被当前别的任务持有的资源。也就是说,要发生死锁,哲学家必须拿着一根筷子并且等待另一根。\n", + "3) 资源不能被任务抢占, 任务必须把资源释放当作普通事件。哲学家很有礼貌,他们不会从其它哲学家那里抢筷子。\n", + "4) 必须有循环等待, 这时,一个任务等待其它任务所持有的资源, 后者又在等待另一个任务所持有的资源, 这样一直下去,知道有一个任务在等待第一个任务所持有的资源, 使得大家都被锁住。 在 `DiningPhilosophers.java` 中, 因为每个哲学家都试图先得到右边的 筷子, 然后得到左边的 筷子, 所以发生了循环等待。\n", + "\n", + "因为必须满足所有条件才能导致死锁,所以要阻止死锁的话,只需要破坏其中一个即可。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。\n", + "\n", + "在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。\n", + "这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。\n", + "没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。\n", + "\n", + "\n", + "\n", + "## 构造方法非线程安全\n", + "\n", + "当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*”\n", + "\n", + "不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。\n", + "\n", + "设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "//concurrent/HasID.java\n", + "public interface HasID {\n", + " int getID();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后 **StaticIDField** 类显式地实现该接口。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/StaticIDField.java\n", + "public class StaticIDField implements HasID {\n", + " private static int counter = 0;\n", + " private int id = counter++;\n", + " public int getID() { return id; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如你所想,该类是个简单无害的类,它甚至都没一个显式的构造器来引发问题。当我们运行多个用于创建此类对象的线程时,究竟会发生什么?为了搞清楚这点,我们做了以下测试。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/IDChecker.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import com.google.common.collect.Sets;\n", + "public class IDChecker {\n", + " public static final int SIZE = 100_000;\n", + "\n", + " static class MakeObjects implements\n", + " Supplier> {\n", + " private Supplier gen;\n", + "\n", + " MakeObjects(Supplier gen) {\n", + " this.gen = gen;\n", + " }\n", + "\n", + " @Override public List get() {\n", + " return Stream.generate(gen)\n", + " .limit(SIZE)\n", + " .map(HasID::getID)\n", + " .collect(Collectors.toList());\n", + " }\n", + " }\n", + "\n", + " public static void test(Supplier gen) {\n", + " CompletableFuture>\n", + " groupA = CompletableFuture.supplyAsync(new\n", + " MakeObjects(gen)),\n", + " groupB = CompletableFuture.supplyAsync(new\n", + " MakeObjects(gen));\n", + "\n", + " groupA.thenAcceptBoth(groupB, (a, b) -> {\n", + " System.out.println(\n", + " Sets.intersection(\n", + " Sets.newHashSet(a),\n", + " Sets.newHashSet(b)).size());\n", + " }).join();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**MakeObjects** 类是一个生产者类,包含一个能够产生 List\\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。\n", + "\n", + "使用 Guava 库中的 **Sets.`intersection()` 方法,计算出这两个返回的 List\\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。\n", + "\n", + "现在我们可以测试上面的 **StaticIDField** 类了。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/TestStaticIDField.java\n", + "public class TestStaticIDField {\n", + "\n", + " public static void main(String[] args) {\n", + " IDChecker.test(StaticIDField::new);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " 13287" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/GuardedIDField.java\n", + "import java.util.concurrent.atomic.*;\n", + "public class GuardedIDField implements HasID { \n", + " private static AtomicInteger counter = new\n", + " AtomicInteger();\n", + "\n", + " private int id = counter.getAndIncrement();\n", + "\n", + " public int getID() { return id; }\n", + "\n", + " public static void main(String[] args) { IDChecker.test(GuardedIDField::new);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "构造器有一种更微妙的状态共享方式:通过构造器参数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/SharedConstructorArgument.java\n", + "import java.util.concurrent.atomic.*;\n", + "interface SharedArg{\n", + " int get();\n", + "}\n", + "\n", + "class Unsafe implements SharedArg{\n", + " private int i = 0;\n", + "\n", + " public int get(){\n", + " return i++;\n", + " }\n", + "}\n", + "\n", + "class Safe implements SharedArg{\n", + " private static AtomicInteger counter = new AtomicInteger();\n", + "\n", + " public int get(){\n", + " return counter.getAndIncrement();\n", + " }\n", + "}\n", + "\n", + "class SharedUser implements HasID{\n", + " private final int id;\n", + "\n", + " SharedUser(SharedArg sa){\n", + " id = sa.get();\n", + " }\n", + "\n", + " @Override\n", + " public int getID(){\n", + " return id;\n", + " }\n", + "}\n", + "\n", + "public class SharedConstructorArgument{\n", + " public static void main(String[] args){\n", + " Unsafe unsafe = new Unsafe();\n", + " IDChecker.test(() -> new SharedUser(unsafe));\n", + "\n", + " Safe safe = new Safe();\n", + " IDChecker.test(() -> new SharedUser(safe));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " 24838\n", + " 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。\n", + "\n", + "同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/SynchronizedConstructor.java\n", + "\n", + "import java.util.concurrent.atomic.*;\n", + "\n", + "class SyncConstructor implements HasID{\n", + " private final int id;\n", + " private static Object constructorLock =\n", + " new Object();\n", + "\n", + " SyncConstructor(SharedArg sa){\n", + " synchronized (constructorLock){\n", + " id = sa.get();\n", + " }\n", + " }\n", + "\n", + " @Override\n", + " public int getID(){\n", + " return id;\n", + " }\n", + "}\n", + "\n", + "public class SynchronizedConstructor{\n", + " public static void main(String[] args){\n", + " Unsafe unsafe = new Unsafe();\n", + " IDChecker.test(() -> new SyncConstructor(unsafe));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Unsafe**类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/SynchronizedFactory.java\n", + "import java.util.concurrent.atomic.*;\n", + "\n", + "final class SyncFactory implements HasID{\n", + " private final int id;\n", + "\n", + " private SyncFactory(SharedArg sa){\n", + " id = sa.get();\n", + " }\n", + "\n", + " @Override\n", + " public int getID(){\n", + " return id;\n", + " }\n", + "\n", + " public static synchronized SyncFactory factory(SharedArg sa){\n", + " return new SyncFactory(sa);\n", + " }\n", + "}\n", + "\n", + "public class SynchronizedFactory{\n", + " public static void main(String[] args){\n", + " Unsafe unsafe = new Unsafe();\n", + " IDChecker.test(() -> SyncFactory.factory(unsafe));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " 0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。\n", + "\n", + "这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。\n", + "\n", + "## 复杂性和代价\n", + "\n", + "假设你正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/Pizza.java import java.util.function.*;\n", + "\n", + "import onjava.Nap;\n", + "public class Pizza{\n", + " public enum Step{\n", + " DOUGH(4), ROLLED(1), SAUCED(1), CHEESED(2),\n", + " TOPPED(5), BAKED(2), SLICED(1), BOXED(0);\n", + " int effort;// Needed to get to the next step \n", + "\n", + " Step(int effort){\n", + " this.effort = effort;\n", + " }\n", + "\n", + " Step forward(){\n", + " if (equals(BOXED)) return BOXED;\n", + " new Nap(effort * 0.1);\n", + " return values()[ordinal() + 1];\n", + " }\n", + " }\n", + "\n", + " private Step step = Step.DOUGH;\n", + " private final int id;\n", + "\n", + " public Pizza(int id){\n", + " this.id = id;\n", + " }\n", + "\n", + " public Pizza next(){\n", + " step = step.forward();\n", + " System.out.println(\"Pizza \" + id + \": \" + step);\n", + " return this;\n", + " }\n", + "\n", + " public Pizza next(Step previousStep){\n", + " if (!step.equals(previousStep))\n", + " throw new IllegalStateException(\"Expected \" +\n", + " previousStep + \" but found \" + step);\n", + " return next();\n", + " }\n", + "\n", + " public Pizza roll(){\n", + " return next(Step.DOUGH);\n", + " }\n", + "\n", + " public Pizza sauce(){\n", + " return next(Step.ROLLED);\n", + " }\n", + "\n", + " public Pizza cheese(){\n", + " return next(Step.SAUCED);\n", + " }\n", + "\n", + " public Pizza toppings(){\n", + " return next(Step.CHEESED);\n", + " }\n", + "\n", + " public Pizza bake(){\n", + " return next(Step.TOPPED);\n", + " }\n", + "\n", + " public Pizza slice(){\n", + " return next(Step.BAKED);\n", + " }\n", + "\n", + " public Pizza box(){\n", + " return next(Step.SLICED);\n", + " }\n", + "\n", + " public boolean complete(){\n", + " return step.equals(Step.BOXED);\n", + " }\n", + "\n", + " @Override\n", + " public String toString(){\n", + " return \"Pizza\" + id + \": \" + (step.equals(Step.BOXED) ? \"complete\" : step);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这只算得上是一个平凡的状态机,就像**Machina**类一样。 \n", + "\n", + "制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/OnePizza.java \n", + "\n", + "import onjava.Timer;\n", + "\n", + "public class OnePizza{\n", + " public static void main(String[] args){\n", + " Pizza za = new Pizza(0);\n", + " System.out.println(Timer.duration(() -> {\n", + " while (!za.complete()) za.next();\n", + " }));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Pizza 0: ROLLED \n", + "Pizza 0: SAUCED \n", + "Pizza 0: CHEESED \n", + "Pizza 0: TOPPED \n", + "Pizza 0: BAKED \n", + "Pizza 0: SLICED \n", + "Pizza 0: BOXED \n", + "\t1622 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "时间以毫秒为单位,加总所有步骤的工作量,会得出与我们的期望值相符的数字。 如果你以这种方式制作了五个披萨,那么你会认为它花费的时间是原来的五倍。 但是,如果这还不够快怎么办? 我们可以从尝试并行流方法开始:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/PizzaStreams.java\n", + "// import java.util.*; import java.util.stream.*;\n", + "\n", + "import onjava.Timer;\n", + "\n", + "public class PizzaStreams{\n", + " static final int QUANTITY = 5;\n", + "\n", + " public static void main(String[] args){\n", + " Timer timer = new Timer();\n", + " IntStream.range(0, QUANTITY)\n", + " .mapToObj(Pizza::new)\n", + " .parallel()//[1]\n", + " \t.forEach(za -> { while(!za.complete()) za.next(); }); \t\t\tSystem.out.println(timer.duration());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Pizza 2: ROLLED\n", + "Pizza 0: ROLLED\n", + "Pizza 1: ROLLED\n", + "Pizza 4: ROLLED\n", + "Pizza 3:ROLLED\n", + "Pizza 2:SAUCED\n", + "Pizza 1:SAUCED\n", + "Pizza 0:SAUCED\n", + "Pizza 4:SAUCED\n", + "Pizza 3:SAUCED\n", + "Pizza 2:CHEESED\n", + "Pizza 1:CHEESED\n", + "Pizza 0:CHEESED\n", + "Pizza 4:CHEESED\n", + "Pizza 3:CHEESED\n", + "Pizza 2:TOPPED\n", + "Pizza 1:TOPPED\n", + "Pizza 0:TOPPED\n", + "Pizza 4:TOPPED\n", + "Pizza 3:TOPPED\n", + "Pizza 2:BAKED\n", + "Pizza 1:BAKED\n", + "Pizza 0:BAKED\n", + "Pizza 4:BAKED\n", + "Pizza 3:BAKED\n", + "Pizza 2:SLICED\n", + "Pizza 1:SLICED\n", + "Pizza 0:SLICED\n", + "Pizza 4:SLICED\n", + "Pizza 3:SLICED\n", + "Pizza 2:BOXED\n", + "Pizza 1:BOXED\n", + "Pizza 0:BOXED\n", + "Pizza 4:BOXED\n", + "Pizza 3:BOXED\n", + "1739" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将**QUANTITY**更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。\n", + "\n", + "**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/PizzaParallelSteps.java \n", + "\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import onjava.Timer;\n", + "\n", + "public class PizzaParallelSteps{\n", + " static final int QUANTITY = 5;\n", + "\n", + " public static void main(String[] args){\n", + " Timer timer = new Timer();\n", + " IntStream.range(0, QUANTITY)\n", + " .mapToObj(Pizza::new)\n", + " .parallel()\n", + " .map(Pizza::roll)\n", + " .map(Pizza::sauce)\n", + " .map(Pizza::cheese)\n", + " .map(Pizza::toppings)\n", + " .map(Pizza::bake)\n", + " .map(Pizza::slice)\n", + " .map(Pizza::box)\n", + " .forEach(za -> System.out.println(za));\n", + " System.out.println(timer.duration());\n", + " }\n", + "} " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Pizza 2: ROLLED \n", + "Pizza 0: ROLLED \n", + "Pizza 1: ROLLED \n", + "Pizza 4: ROLLED \n", + "Pizza 3: ROLLED \n", + "Pizza 1: SAUCED \n", + "Pizza 0: SAUCED \n", + "Pizza 2: SAUCED \n", + "Pizza 3: SAUCED \n", + "Pizza 4: SAUCED \n", + "Pizza 1: CHEESED \n", + "Pizza 0: CHEESED \n", + "Pizza 2: CHEESED \n", + "Pizza 3: CHEESED \n", + "Pizza 4: CHEESED \n", + "Pizza 0: TOPPED \n", + "Pizza 2: TOPPED\n", + "Pizza 1: TOPPED \n", + "Pizza 3: TOPPED \n", + "Pizza 4: TOPPED \n", + "Pizza 1: BAKED \n", + "Pizza 2: BAKED \n", + "Pizza 0: BAKED \n", + "Pizza 4: BAKED \n", + "Pizza 3: BAKED \n", + "Pizza 0: SLICED \n", + "Pizza 2: SLICED \n", + "Pizza 1: SLICED \n", + "Pizza 3: SLICED \n", + "Pizza 4: SLICED \n", + "Pizza 1: BOXED \n", + "Pizza1: complete \n", + "Pizza 2: BOXED \n", + "Pizza 0: BOXED \n", + "Pizza2: complete \n", + "Pizza0: complete \n", + "Pizza 3: BOXED\n", + "Pizza 4: BOXED \n", + "Pizza4: complete \n", + "Pizza3: complete \n", + "1738 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的 `PizzaParallelSteps.java` 里面展示的一样。\n", + "\n", + "我们可以使用 **CompletableFutures** 重写这个例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// concurrent/CompletablePizza.java \n", + "\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.stream.*;\n", + "import onjava.Timer;\n", + "\n", + "public class CompletablePizza{\n", + " static final int QUANTITY = 5;\n", + "\n", + " public static CompletableFuture makeCF(Pizza za){\n", + " return CompletableFuture\n", + " .completedFuture(za)\n", + " .thenApplyAsync(Pizza::roll)\n", + " .thenApplyAsync(Pizza::sauce)\n", + " .thenApplyAsync(Pizza::cheese)\n", + " .thenApplyAsync(Pizza::toppings)\n", + " .thenApplyAsync(Pizza::bake)\n", + " .thenApplyAsync(Pizza::slice)\n", + " .thenApplyAsync(Pizza::box);\n", + " }\n", + "\n", + " public static void show(CompletableFuture cf){\n", + " try{\n", + " System.out.println(cf.get());\n", + " } catch (Exception e){\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args){\n", + " Timer timer = new Timer();\n", + " List> pizzas =\n", + " IntStream.range(0, QUANTITY)\n", + " .mapToObj(Pizza::new)\n", + " .map(CompletablePizza::makeCF)\n", + " .collect(Collectors.toList());\n", + " System.out.println(timer.duration());\n", + " pizzas.forEach(CompletablePizza::show);\n", + " System.out.println(timer.duration());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "169 \n", + "Pizza 0: ROLLED \n", + "Pizza 1: ROLLED \n", + "Pizza 2: ROLLED \n", + "Pizza 4: ROLLED \n", + "Pizza 3: ROLLED \n", + "Pizza 1: SAUCED \n", + "Pizza 0: SAUCED \n", + "Pizza 2: SAUCED \n", + "Pizza 4: SAUCED\n", + "Pizza 3: SAUCED \n", + "Pizza 0: CHEESED \n", + "Pizza 4: CHEESED \n", + "Pizza 1: CHEESED \n", + "Pizza 2: CHEESED \n", + "Pizza 3: CHEESED \n", + "Pizza 0: TOPPED \n", + "Pizza 4: TOPPED \n", + "Pizza 1: TOPPED \n", + "Pizza 2: TOPPED \n", + "Pizza 3: TOPPED \n", + "Pizza 0: BAKED \n", + "Pizza 4: BAKED \n", + "Pizza 1: BAKED \n", + "Pizza 3: BAKED \n", + "Pizza 2: BAKED \n", + "Pizza 0: SLICED \n", + "Pizza 4: SLICED \n", + "Pizza 1: SLICED \n", + "Pizza 3: SLICED\n", + "Pizza 2: SLICED \n", + "Pizza 4: BOXED \n", + "Pizza 0: BOXED \n", + "Pizza0: complete \n", + "Pizza 1: BOXED \n", + "Pizza1: complete \n", + "Pizza 3: BOXED \n", + "Pizza 2: BOXED \n", + "Pizza2: complete \n", + "Pizza3: complete \n", + "Pizza4: complete \n", + "1797 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究**Spliterator**的文档)。\n", + "\n", + "而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。\n", + "\n", + "对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。\n", + "\n", + "由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。\n", + "\n", + "使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。\n", + "\n", + "## 本章小结\n", + "\n", + "需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“你的程序运行速度还不够快”。\n", + "\n", + "如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以你应该仔细考虑是否值得为此付出努力,并考虑你能否以其他方式提升速度。\n", + "\n", + "例如,迁移到更快的硬件(这可能比消耗程序员的时间要便宜得多)或者将程序分解成多个部分,然后在不同的机器上运行这些部分。\n", + "\n", + "奥卡姆剃刀是一个经常被误解的原则。 我看过至少一部电影,他们将其定义为”最简单的解决方案是正确的解决方案“,就好像这是某种毋庸置疑的法律。实际上,这是一个准则:面对多种方法时,请先尝试需要最少假设的方法。 在编程世界中,这已演变为“尝试可能可行的最简单的方法”。当你了解了特定工具的知识时——就像你现在了解了有关并发性的知识一样,你可能会很想使用它,或者提前规定你的解决方案必须能够“速度飞快”,从而来证明从一开始就进行并发设计是合理的。但是,我们的奥卡姆剃刀编程版本表示你应该首先尝试最简单的方法(这种方法开发起来也更便宜),然后看看它是否足够好。\n", + "\n", + "由于我出身于底层学术背景(物理学和计算机工程),所以我很容易想到所有小轮子转动的成本。我确定使用最简单的方法不够快的场景出现的次数已经数不过来了,但是尝试后却发现它实际上绰绰有余。\n", + "\n", + "### 缺点\n", + "\n", + "并发编程的主要缺点是:\n", + "\n", + "1. 在线程等待共享资源时会降低速度。 \n", + "\n", + "2. 线程管理产生额外CPU开销。\n", + "\n", + "3. 糟糕的设计决策带来无法弥补的复杂性。\n", + "\n", + "4. 诸如饥饿,竞速,死锁和活锁(多线程各自处理单个任务而整体却无法完成)之类的问题。\n", + "\n", + "5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果你在后者上开发程序,则在分发程序时可能会感到非常惊讶。\n", + "\n", + "另外,并发的应用是一门艺术。 Java旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。\n", + "\n", + "### 共享内存陷阱\n", + "\n", + "并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且你必须确保多个任务不会同时读取和更改该资源。\n", + "\n", + "我花了多年的时间研究并发并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10]\n", + "\n", + "我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。\n", + "\n", + "再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给你一种一切都棒极了的印象。当涉及到共享内存并发编程时,你永远不应该对自己的编程能力变得过于自信。\n", + "\n", + "### This Albatross is Big\n", + "\n", + "如果你对Java并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。\n", + "\n", + "事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。\n", + "\n", + "在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。\n", + "\n", + "### 其他类库\n", + "\n", + "本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是你还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。\n", + "\n", + "### 考虑为并发设计的语言\n", + "\n", + "通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在Java的世界范围内)。\n", + "\n", + "如果你的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与你的主要Java代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11]\n", + "\n", + "你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许你在Scala中完成代码)。\n", + "\n", + "心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。\n", + "\n", + "无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在你最不期望出现的时候咬你。\n", + "\n", + "### 拓展阅读\n", + "\n", + "《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。\n", + "\n", + "[^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。\n", + "[^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论\n", + "[^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。\n", + "[^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为\n", + "[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。\n", + "[^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。\n", + "[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的Java版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩\n", + "[^8]: 库就在那里用于调用,而语言本身就被设计用于此目的,但实际上它很少发生,以至于可以说”没有“。↩\n", + "[^9]: 举例来说,如果没有Flyweight设计模式,在工程中创建数百万个对象用于有限元分析可能在Java中不可行。↩\n", + "[^10]: 在科学中,虽然从来没有一种理论被证实过,但是一种理论必须是可证伪的才有意义。而对于并发性,我们大部分时间甚至都无法得到这种可证伪性。↩\n", + "[^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/25-Patterns.ipynb b/jupyter/25-Patterns.ipynb new file mode 100644 index 00000000..f6bad1f5 --- /dev/null +++ b/jupyter/25-Patterns.ipynb @@ -0,0 +1,1674 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 第二十五章 设计模式\n", + "\n", + "\n", + "\n", + "## 概念\n", + "最初,你可以将模式视为解决特定类问题的一种特别巧妙且有深刻见解的方法。这就像前辈已经从所有角度去解决问题,并提出了最通用,最灵活的解决方案。问题可能是你之前看到并解决过的问题,但你的解决方案可能没有你在模式中体现的那种完整性。\n", + "\n", + "虽然它们被称为“设计模式”,但它们实际上并不与设计领域相关联。模式似乎与传统的分析、设计和实现的思维方式不同。相反,模式在程序中体现了一个完整的思想,因此它有时会出现在分析阶段或高级设计阶段。因为模式在代码中有一个直接的实现,所以你可能不会期望模式在低级设计或实现之前出现(而且通常在到达这些阶段之前,你不会意识到需要一个特定的模式)。\n", + "\n", + "模式的基本概念也可以看作是程序设计的基本概念:添加抽象层。当你抽象一些东西的时候,就像在剥离特定的细节,而这背后最重要的动机之一是:\n", + "> **将易变的事物与不变的事物分开**\n", + "\n", + "另一种方法是,一旦你发现程序的某些部分可能因某种原因而发生变化,你要保持这些变化不会引起整个代码中其他变化。 如果代码更容易理解,那么维护起来会更容易。\n", + "\n", + "通常,开发一个优雅且易维护设计中最困难的部分是发现我称之为变化的载体(也就是最易改变的地方)。这意味着找到系统中最重要的变化,换而言之,找到变化会导致最严重后果的地方。一旦发现变化载体,就可以围绕构建设计的焦点。\n", + "\n", + "因此,设计模式的目标是隔离代码中的更改。 如果以这种方式去看,你已经在本书中看到了设计模式。 例如,继承可以被认为是一种设计模式(虽然是由编译器实现的)。它允许你表达所有具有相同接口的对象(即保持相同的行为)中的行为差异(这就是变化的部分)。组合也可以被视为一种模式,因为它允许你动态或静态地更改实现类的对象,从而改变类的工作方式。\n", + "\n", + "你还看到了设计模式中出现的另一种模式:迭代器(Java 1.0和1.1随意地将其称为枚举; Java 2 集合才使用Iterator)。当你逐个选择元素时并逐步处理,这会隐藏集合的特定实现。迭代器允许你编写通用代码,该代码对序列中的所有元素执行操作,而不考虑序列的构建方式。因此,你的通用代码可以与任何可以生成迭代器的集合一起使用。\n", + "\n", + "即使模式是非常有用的,但有些人断言:\n", + "> **设计模式代表语言的失败。**\n", + "\n", + "这是一个非常重要的见解,因为一个模式在 C++ 有意义,可能在JAVA或者其他语言中就没有意义。出于这个原因,所以一个模式可能出现在设计模式书上,不意味着应用于你的编程语言是有用的。\n", + "\n", + "我认为“语言失败”这个观点是有道理的,但是我也认为这个观点过于简单化。如果你试图解决一个特定的问题,而你使用的语言没有直接提供支持你使用的技巧,你可以说这个是语言的失败。但是,你使用特定的技巧的频率的是多少呢?也许平衡是对的:当你使用特定的技巧的时候,你必须付出更多的努力,但是你又没有足够的理由去使得语言支持这个技术。另一方面,没有语言的支持,使用这种技术常常会很混乱,但是在语言支持下,你可能会改变编程方式(例如,Java 8流实现此目的)。\n", + "\n", + "### 单例模式\n", + "也许单例模式是最简单的设计模式,它是一种提供一个且只有一个对象实例的方法。这在java库中使用,但是这有个更直接的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/SingletonPattern.java\n", + "interface Resource {\n", + " int getValue();\n", + " void setValue(int x);\n", + "}\n", + "\n", + "/*\n", + "* 由于这不是从Cloneable基类继承而且没有添加可克隆性,\n", + "* 因此将其设置为final可防止通过继承添加可克隆性。\n", + "* 这也实现了线程安全的延迟初始化:\n", + "*/\n", + "final class Singleton {\n", + " private static final class ResourceImpl implements Resource {\n", + " private int i;\n", + " private ResourceImpl(int i) {\n", + " this.i = i;\n", + " }\n", + " public synchronized int getValue() {\n", + " return i;\n", + " }\n", + " public synchronized void setValue(int x) {\n", + " i = x;\n", + " }\n", + " }\n", + "\n", + " private static class ResourceHolder {\n", + " private static Resource resource = new ResourceImpl(47);\n", + " }\n", + " public static Resource getResource() {\n", + " return ResourceHolder.resource;\n", + " }\n", + "}\n", + "\n", + "public class SingletonPattern {\n", + " public static void main(String[] args) {\n", + " Resource r = Singleton.getResource();\n", + " System.out.println(r.getValue());\n", + " Resource s2 = Singleton.getResource();\n", + " s2.setValue(9);\n", + " System.out.println(r.getValue());\n", + " try { \n", + " // 不能这么做,会发生:compile-time error(编译时错误). \n", + " // Singleton s3 = (Singleton)s2.clone(); \n", + " } catch(Exception e) { \n", + " throw new RuntimeException(e); \n", + " } \n", + " }\n", + "} /* Output: 47 9 */" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "创建单例的关键是防止客户端程序员直接创建对象。 在这里,这是通过在Singleton类中将Resource的实现作为私有类来实现的。\n", + "\n", + "此时,你将决定如何创建对象。在这里,它是按需创建的,在第一次访问的时候创建。 该对象是私有的,只能通过public getResource()方法访问。\n", + "\n", + "\n", + "懒惰地创建对象的原因是它嵌套的私有类resourceHolder在首次引用之前不会加载(在getResource()中)。当Resource对象加载的时候,静态初始化块将被调用。由于JVM的工作方式,这种静态初始化是线程安全的。为保证线程安全,Resource中的getter和setter是同步的。\n", + "\n", + "### 模式分类\n", + "\n", + "“设计模式”一书讨论了23种不同的模式,分为以下三种类别(所有这些模式都围绕着可能变化的特定方面)。\n", + "\n", + "1. **创建型**:如何创建对象。 这通常涉及隔离对象创建的细节,这样你的代码就不依赖于具体的对象的类型,因此在添加新类型的对象时不会更改。单例模式(Singleton)被归类为创作模式,本章稍后你将看到Factory Method的示例。\n", + "\n", + "2. **构造型**:设计对象以满足特定的项目约束。它们处理对象与其他对象连接的方式,以确保系统中的更改不需要更改这些连接。\n", + "\n", + "3. **行为型**:处理程序中特定类型的操作的对象。这些封装要执行的过程,例如解释语言、实现请求、遍历序列(如在迭代器中)或实现算法。本章包含观察者和访问者模式的例子。\n", + "\n", + "《设计模式》一书中每个设计模式都有单独的一个章节,每个章节都有一个或者多个例子,通常使用C++,但有时也使用SmallTalk。 本章不重复设计模式中显示的所有模式,因为该书独立存在,应单独研究。 相反,你会看到一些示例,可以为你提供关于模式的理解以及它们如此重要的原因。\n", + "\n", + "\n", + "## 构建应用程序框架\n", + "\n", + "应用程序框架允许您从一个类或一组类开始,创建一个新的应用程序,重用现有类中的大部分代码,并根据需要覆盖一个或多个方法来定制应用程序。\n", + "\n", + "**模板方法模式**\n", + "\n", + "应用程序框架中的一个基本概念是模板方法模式,它通常隐藏在底层,通过调用基类中的各种方法来驱动应用程序(为了创建应用程序,您已经覆盖了其中的一些方法)。\n", + "\n", + "模板方法模式的一个重要特性是它是在基类中定义的,并且不能更改。它有时是一个 **private** 方法,但实际上总是 **final**。它调用其他基类方法(您覆盖的那些)来完成它的工作,但是它通常只作为初始化过程的一部分被调用(因此框架使用者不一定能够直接调用它)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/TemplateMethod.java\n", + "// Simple demonstration of Template Method\n", + "\n", + "abstract class ApplicationFramework {\n", + " ApplicationFramework() {\n", + " templateMethod();\n", + " }\n", + "\n", + " abstract void customize1();\n", + "\n", + " abstract void customize2(); // \"private\" means automatically \"final\": private void templateMethod() { IntStream.range(0, 5).forEach( n -> { customize1(); customize2(); }); }}// Create a new \"application\": class MyApp extends ApplicationFramework { @Override void customize1() { System.out.print(\"Hello \"); }@Override\n", + "\n", + " void customize2() {\n", + " System.out.println(\"World!\");\n", + " }\n", + "}\n", + "\n", + "public class TemplateMethod {\n", + " public static void main(String[] args) {\n", + " new MyApp();\n", + " }\n", + "}\n", + "/*\n", + "Output:\n", + "Hello World!\n", + "Hello World!\n", + "Hello World!\n", + "Hello World!\n", + "Hello World!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "基类构造函数负责执行必要的初始化,然后启动运行应用程序的“engine”(模板方法模式)(在GUI应用程序中,这个“engine”是主事件循环)。框架使用者只提供\n", + "**customize1()** 和 **customize2()** 的定义,然后“应用程序”已经就绪运行。\n", + "\n", + "![](images/designproxy.png)\n", + "\n", + "\n", + "## 面向实现\n", + "\n", + "代理模式和桥接模式都提供了在代码中使用的代理类;完成工作的真正类隐藏在这个代理类的后面。当您在代理中调用一个方法时,它只是反过来调用实现类中的方法。这两种模式非常相似,所以代理模式只是桥接模式的一种特殊情况。人们倾向于将两者合并,称为代理模式,但是术语“代理”有一个长期的和专门的含义,这可能解释了这两种模式不同的原因。基本思想很简单:从基类派生代理,同时派生一个或多个提供实现的类:创建代理对象时,给它一个可以调用实际工作类的方法的实现。\n", + "\n", + "\n", + "在结构上,代理模式和桥接模式的区别很简单:代理模式只有一个实现,而桥接模式有多个实现。在设计模式中被认为是不同的:代理模式用于控制对其实现的访问,而桥接模式允许您动态更改实现。但是,如果您扩展了“控制对实现的访问”的概念,那么这两者就可以完美地结合在一起\n", + "\n", + "**代理模式**\n", + "\n", + "如果我们按照上面的关系图实现,它看起来是这样的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/ProxyDemo.java\n", + "// Simple demonstration of the Proxy pattern\n", + "interface ProxyBase {\n", + " void f();\n", + "\n", + " void g();\n", + "\n", + " void h();\n", + "}\n", + "\n", + "class Proxy implements ProxyBase {\n", + " private ProxyBase implementation;\n", + "\n", + " Proxy() {\n", + " implementation = new Implementation();\n", + " }\n", + " // Pass method calls to the implementation:\n", + " @Override\n", + " public void f() { implementation.f(); }\n", + " @Override\n", + " public void g() { implementation.g(); }\n", + " @Override\n", + " public void h() { implementation.h(); }\n", + "}\n", + "\n", + "class Implementation implements ProxyBase {\n", + " public void f() {\n", + " System.out.println(\"Implementation.f()\");\n", + " }\n", + "\n", + " public void g() {\n", + " System.out.println(\"Implementation.g()\");\n", + " }\n", + "\n", + " public void h() {\n", + " System.out.println(\"Implementation.h()\");\n", + " }\n", + "}\n", + "\n", + "public class ProxyDemo {\n", + " public static void main(String[] args) {\n", + " Proxy p = new Proxy();\n", + " p.f();\n", + " p.g();\n", + " p.h();\n", + " }\n", + "}\n", + "/*\n", + "Output:\n", + "Implementation.f()\n", + "Implementation.g()\n", + "Implementation.h()\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "具体实现不需要与代理对象具有相同的接口;只要代理对象以某种方式“代表具体实现的方法调用,那么基本思想就算实现了。然而,拥有一个公共接口是很方便的,因此具体实现必须实现代理对象调用的所有方法。\n", + "\n", + "**状态模式**\n", + "\n", + "状态模式向代理对象添加了更多的实现,以及在代理对象的生命周期内从一个实现切换到另一种实现的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/StateDemo.java // Simple demonstration of the State pattern\n", + "interface StateBase {\n", + " void f();\n", + "\n", + " void g();\n", + "\n", + " void h();\n", + "\n", + " void changeImp(StateBase newImp);\n", + "}\n", + "\n", + "class State implements StateBase {\n", + " private StateBase implementation;\n", + "\n", + " State(StateBase imp) {\n", + " implementation = imp;\n", + " }\n", + "\n", + " @Override\n", + " public void changeImp(StateBase newImp) {\n", + " implementation = newImp;\n", + " }// Pass method calls to the implementation: @Override public void f() { implementation.f(); } @Override public void g() { implementation.g(); } @Override\n", + "\n", + " public void h() {\n", + " implementation.h();\n", + " }\n", + "}\n", + "\n", + "class Implementation1 implements StateBase {\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"Implementation1.f()\");\n", + " }\n", + "\n", + " @Override\n", + " public void g() {\n", + " System.out.println(\"Implementation1.g()\");\n", + " }\n", + "\n", + " @Override\n", + " public void h() {\n", + " System.out.println(\"Implementation1.h()\");\n", + " }\n", + "\n", + " @Override\n", + " public void changeImp(StateBase newImp) {\n", + " }\n", + "}\n", + "\n", + "class Implementation2 implements StateBase {\n", + " @Override\n", + " public void f() {\n", + " System.out.println(\"Implementation2.f()\");\n", + " }\n", + "\n", + " @Override\n", + " public void g() {\n", + " System.out.println(\"Implementation2.g()\");\n", + " }\n", + "\n", + " @Override\n", + " public void h() {\n", + " System.out.println(\"Implementation2.h()\");\n", + " }\n", + "\n", + " @Override\n", + " public void changeImp(StateBase newImp) {\n", + " }\n", + "}\n", + "\n", + "public class StateDemo {\n", + " static void test(StateBase b) {\n", + " b.f();\n", + " b.g();\n", + " b.h();\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " StateBase b = new State(new Implementation1());\n", + " test(b);\n", + " b.changeImp(new Implementation2());\n", + " test(b);\n", + " }\n", + "}\n", + "/* Output:\n", + "Implementation1.f()\n", + "Implementation1.g()\n", + "Implementation1.h()\n", + "Implementation2.f()\n", + "Implementation2.g()\n", + "Implementation2.h()\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在main()中,首先使用第一个实现,然后改变成第二个实现。代理模式和状态模式的区别在于它们解决的问题。设计模式中描述的代理模式的常见用途如下:\n", + "\n", + "1. 远程代理。它在不同的地址空间中代理对象。远程方法调用(RMI)编译器rmic会自动为您创建一个远程代理。\n", + "\n", + "2. 虚拟代理。这提供了“懒加载”来根据需要创建“昂贵”的对象。\n", + "\n", + "3. 保护代理。当您希望对代理对象有权限访问控制时使用。\n", + "\n", + "4. 智能引用。要在被代理的对象被访问时添加其他操作。例如,跟踪特定对象的引用数量,来实现写时复制用法,和防止对象别名。一个更简单的例子是跟踪特定方法的调用数量。您可以将Java引用视为一种保护代理,因为它控制在堆上实例对象的访问(例如,确保不使用空引用)。\n", + "\n", + "在设计模式中,代理模式和桥接模式并不是相互关联的,因为它们被赋予(我认为是任意的)不同的结构。桥接模式,特别是使用一个单独的实现,但这似乎对我来说是不必要的,除非你确定该实现是你无法控制的(当然有可能,但是如果您编写所有代码,那么没有理由不从单基类的优雅中受益)。此外,只要代理对象控制对其“前置”对象的访问,代模式理就不需要为其实现使用相同的基类。不管具体情况如何,在代理模式和桥接模式中,代理对象都将方法调用传递给具体实现对象。\n", + "\n", + "**状态机**\n", + "\n", + "桥接模式允许程序员更改实现,状态机利用一个结构来自动地将实现更改到下一个。当前实现表示系统所处的状态,系统在不同状态下的行为不同(因为它使用桥接模式)。基本上,这是一个利用对象的“状态机”。将系统从一种状态移动到另一种状态的代码通常是模板方法模式,如下例所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/state/StateMachineDemo.java\n", + "// The StateMachine pattern and Template method\n", + "// {java patterns.state.StateMachineDemo}\n", + "package patterns.state;\n", + "\n", + "import onjava.Nap;\n", + "\n", + "interface State {\n", + " void run();\n", + "}\n", + "\n", + "abstract class StateMachine {\n", + " protected State currentState;\n", + "\n", + " Nap(0.5);\n", + "System.out.println(\"Washing\"); new\n", + "\n", + " protected abstract boolean changeState();\n", + "\n", + " // Template method:\n", + " protected final void runAll() {\n", + " while (changeState()) // Customizable\n", + " currentState.run();\n", + " }\n", + "}\n", + "\n", + "// A different subclass for each state:\n", + "class Wash implements State {\n", + " @Override\n", + " public void run() {\n", + " }\n", + "}\n", + "\n", + "class Spin implements State {\n", + " @Override\n", + " public void run() {\n", + " System.out.println(\"Spinning\");\n", + " new Nap(0.5);\n", + " }\n", + "}\n", + "\n", + "class Rinse implements State {\n", + " @Override\n", + " public void run() {\n", + " System.out.println(\"Rinsing\");\n", + " new Nap(0.5);\n", + " }\n", + "}\n", + "\n", + "class Washer extends StateMachine {\n", + " private int i = 0;\n", + "\n", + " // The state table:\n", + " private State[] states = {new Wash(), new Spin(), new Rinse(), new Spin(),};\n", + "\n", + " Washer() {\n", + " runAll();\n", + " }\n", + "\n", + " @Override\n", + " public boolean changeState() {\n", + " if (i < states.length) {\n", + " // Change the state by setting the\n", + " // surrogate reference to a new object:\n", + " currentState = states[i++];\n", + " return true;\n", + " } else return false;\n", + " }\n", + "}\n", + "\n", + "public class StateMachineDemo {\n", + " public static void main(String[] args) {\n", + " new Washer();\n", + " }\n", + "}\n", + "/*\n", + "Output:\n", + "Washing\n", + "Spinning\n", + "Rinsing\n", + "Spinning\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这里,控制状态的类(本例中是状态机)负责决定下一个状态。然而,状态对象本身也可以决定下一步移动到什么状态,通常基于系统的某种输入。这是更灵活的解决方案。\n", + "\n", + "\n", + "\n", + "## 工厂模式\n", + "\n", + "当你发现必须将新类型添加到系统中时,合理的第一步是使用多态性为这些新类型创建一个通用接口。这会将你系统中的其余代码与要添加的特定类型的信息分开,使得可以在不改变现有代码的情况下添加新类型……或者看起来如此。起初,在这种设计中,似乎你必须更改代码的唯一地方就是你继承新类型的地方,但这并不是完全正确的。 你仍然必须创建新类型的对象,并且在创建时必须指定要使用的确切构造器。因此,如果创建对象的代码分布在整个应用程序中,那么在添加新类型时,你将遇到相同的问题——你仍然必须追查你代码中新类型碍事的所有地方。恰好是类型的创建碍事,而不是类型的使用(通过多态处理),但是效果是一样的:添加新类型可能会引起问题。\n", + "\n", + "解决方案是强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。\n", + "\n", + "由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。\n", + "\n", + "举例来说,让我们重新看一下**Shape**系统。 首先,我们需要一个用于所有示例的基本框架。 如果无法创建**Shape**对象,则需要抛出一个合适的异常:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/BadShapeCreation.java package patterns.shapes;\n", + "public class BadShapeCreation extends RuntimeException {\n", + " public BadShapeCreation(String msg) {\n", + " super(msg);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "接下来,是一个**Shape**基类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/Shape.java\n", + "package patterns.shapes;\n", + "public class Shape {\n", + "\tprivate static int counter = 0;\n", + " private int id = counter++;\n", + " @Override\n", + " public String toString(){\n", + " return getClass().getSimpleName() + \"[\" + id + \"]\";\n", + " }\n", + " public void draw() {\n", + " System.out.println(this + \" draw\");\n", + " }\n", + " public void erase() {\n", + " System.out.println(this + \" erase\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "该类自动为每一个**Shape**对象创建一个唯一的`id`。\n", + "\n", + "`toString()`使用运行期信息来发现特定的**Shape**子类的名字。\n", + "\n", + "现在我们能很快创建一些**Shape**子类了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/Circle.java\n", + "package patterns.shapes;\n", + "public class Circle extends Shape {}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/Square.java\n", + "package patterns.shapes;\n", + "public class Square extends Shape {}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/Triangle.java\n", + "package patterns.shapes;\n", + "public class Triangle extends Shape {} " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/FactoryMethod.java\n", + "package patterns.shapes;\n", + "public interface FactoryMethod {\n", + " Shape create(String type);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`create()`接收一个参数,这个参数使其决定要创建哪一种**Shape**对象,这里是`String`,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/shapes/FactoryTest.java\n", + "package patterns.shapes;\n", + "import java.util.stream.*;\n", + "public class FactoryTest {\n", + " public static void test(FactoryMethod factory) {\n", + " Stream.of(\"Circle\", \"Square\", \"Triangle\",\n", + " \"Square\", \"Circle\", \"Circle\", \"Triangle\")\n", + " .map(factory::create)\n", + " .peek(Shape::draw)\n", + " .peek(Shape::erase)\n", + " .count(); // Terminal operation\n", + " }\n", + "} " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在主函数`main()`里,要记住除非你在最后使用了一个终结操作,否则**Stream**不会做任何事情。在这里,`count()`的值被丢弃了。\n", + "\n", + "创建工厂的一种方法是显式创建每种类型:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/ShapeFactory1.java\n", + "// A simple static factory method\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import patterns.shapes.*;\n", + "public class ShapeFactory1 implements FactoryMethod {\n", + " public Shape create(String type) {\n", + " switch(type) {\n", + " case \"Circle\": return new Circle();\n", + " case \"Square\": return new Square();\n", + " case \"Triangle\": return new Triangle();\n", + " default: throw new BadShapeCreation(type);\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " FactoryTest.test(new ShapeFactory1());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Circle[0] draw\n", + "Circle[0] erase\n", + "Square[1] draw\n", + "Square[1] erase\n", + "Triangle[2] draw\n", + "Triangle[2] erase\n", + "Square[3] draw\n", + "Square[3] erase\n", + "Circle[4] draw\n", + "Circle[4] erase\n", + "Circle[5] draw\n", + "Circle[5] erase\n", + "Triangle[6] draw\n", + "Triangle[6] erase " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`create()`现在是添加新类型的Shape时系统中唯一需要更改的其他代码。\n", + "\n", + "### 动态工厂\n", + "\n", + "前面例子中的**静态**`create()`方法强制所有创建操作都集中在一个位置,因此这是添加新类型的**Shape**时唯一必须更改代码的地方。这当然是一个合理的解决方案,因为它把创建对象的过程限制在一个框内。但是,如果你在添加新类时无需修改任何内容,那就太好了。 以下版本使用反射在首次需要时将**Shape**的构造器动态加载到工厂列表中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/ShapeFactory2.java\n", + "import java.util.*;\n", + "import java.lang.reflect.*;\n", + "import java.util.stream.*;\n", + "import patterns.shapes.*;\n", + "public class ShapeFactory2 implements FactoryMethod {\n", + " Map factories = new HashMap<>();\n", + " static Constructor load(String id) {\n", + " System.out.println(\"loading \" + id);\n", + " try {\n", + " return Class.forName(\"patterns.shapes.\" + id)\n", + " .getConstructor();\n", + " } catch(ClassNotFoundException |\n", + " NoSuchMethodException e) {\n", + " throw new BadShapeCreation(id);\n", + " }\n", + " }\n", + " public Shape create(String id) {\n", + " try {\n", + " return (Shape)factories\n", + " .computeIfAbsent(id, ShapeFactory2::load)\n", + " .newInstance();\n", + " } catch(InstantiationException |\n", + " IllegalAccessException |\n", + " InvocationTargetException e) {\n", + " throw new BadShapeCreation(id);\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " FactoryTest.test(new ShapeFactory2());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "loading Circle\n", + "Circle[0] draw\n", + "Circle[0] erase\n", + "loading Square\n", + "Square[1] draw\n", + "Square[1] erase\n", + "loading Triangle\n", + "Triangle[2] draw\n", + "Triangle[2] erase\n", + "Square[3] draw\n", + "Square[3] erase\n", + "Circle[4] draw\n", + "Circle[4] erase\n", + "Circle[5] draw\n", + "Circle[5] erase\n", + "Triangle[6] draw\n", + "Triangle[6] erase" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "和之前一样,`create()`方法基于你传递给它的**String**参数生成新的**Shape**s,但是在这里,它是通过在**HashMap**中查找作为键的**String**来实现的。 返回的值是一个构造器,该构造器用于通过调用`newInstance()`创建新的**Shape**对象。\n", + "\n", + "然而,当你开始运行程序时,工厂的`map`为空。`create()`使用`map`的`computeIfAbsent()`方法来查找构造器(如果该构造器已存在于`map`中)。如果不存在则使用`load()`计算出该构造器,并将其插入到`map`中。 从输出中可以看到,每种特定类型的**Shape**都是在第一次请求时才加载的,然后只需要从`map`中检索它。\n", + "\n", + "### 多态工厂\n", + "\n", + "《设计模式》这本书强调指出,采用“工厂方法”模式的原因是可以从基本工厂中继承出不同类型的工厂。 再次修改示例,使工厂方法位于单独的类中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/ShapeFactory3.java\n", + "// Polymorphic factory methods\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import patterns.shapes.*;\n", + "interface PolymorphicFactory {\n", + " Shape create();\n", + "}\n", + "class RandomShapes implements Supplier {\n", + " private final PolymorphicFactory[] factories;\n", + " private Random rand = new Random(42);\n", + " RandomShapes(PolymorphicFactory... factories){\n", + " this.factories = factories;\n", + " }\n", + " public Shape get() {\n", + " return factories[ rand.nextInt(factories.length)].create();\n", + " }\n", + "}\n", + "public class ShapeFactory3 {\n", + " public static void main(String[] args) {\n", + " RandomShapes rs = new RandomShapes(\n", + " Circle::new,\n", + " Square::new,\n", + " Triangle::new);\n", + " Stream.generate(rs)\n", + " .limit(6)\n", + " .peek(Shape::draw)\n", + " .peek(Shape::erase)\n", + " .count();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Triangle[0] draw\n", + "Triangle[0] erase\n", + "Circle[1] draw\n", + "Circle[1] erase\n", + "Circle[2] draw\n", + "Circle[2] erase\n", + "Triangle[3] draw\n", + "Triangle[3] erase\n", + "Circle[4] draw\n", + "Circle[4] erase\n", + "Square[5] draw\n", + "Square[5] erase " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**RandomShapes**实现了**Supplier \\**,因此可用于通过`Stream.generate()`创建**Stream**。 它的构造器采用**PolymorphicFactory**对象的可变参数列表。 变量参数列表以数组形式出现,因此列表是以数组形式在内部存储的。`get()`方法随机获取此数组中一个对象的索引,并在结果上调用`create()`以产生新的**Shape**对象。 添加新类型的**Shape**时,**RandomShapes**构造器是唯一需要更改的地方。 请注意,此构造器需要**Supplier \\**。 我们传递给其**Shape**构造器的方法引用,该引用可满足**Supplier \\**约定,因为Java 8支持结构一致性。\n", + "\n", + "鉴于**ShapeFactory2.java**可能会抛出异常,使用此方法则没有任何异常——它在编译时完全确定。\n", + "\n", + "### 抽象工厂\n", + "\n", + "抽象工厂模式看起来像我们之前所见的工厂对象,但拥有不是一个工厂方法而是几个工厂方法, 每个工厂方法都会创建不同种类的对象。 这个想法是在创建工厂对象时,你决定如何使用该工厂创建的所有对象。 《设计模式》中提供的示例实现了跨各种图形用户界面(GUI)的可移植性:你创建一个适合你正在使用的GUI的工厂对象,然后从中请求菜单,按钮,滑块等等,它将自动为GUI创建适合该项目版本的组件。 因此,你可以将从一个GUI更改为另一个所产生的影响隔离限制在一处。 作为另一个示例,假设你正在创建一个通用游戏环境来支持不同类型的游戏。 使用抽象工厂看起来就像下文那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/abstractfactory/GameEnvironment.java\n", + "// An example of the Abstract Factory pattern\n", + "// {java patterns.abstractfactory.GameEnvironment}\n", + "package patterns.abstractfactory;\n", + "import java.util.function.*;\n", + "interface Obstacle {\n", + " void action();\n", + "}\n", + "\n", + "interface Player {\n", + " void interactWith(Obstacle o);\n", + "}\n", + "\n", + "class Kitty implements Player {\n", + " @Override\n", + " public void interactWith(Obstacle ob) {\n", + " System.out.print(\"Kitty has encountered a \");\n", + " ob.action();\n", + " }\n", + "}\n", + "\n", + "class KungFuGuy implements Player {\n", + " @Override\n", + " public void interactWith(Obstacle ob) {\n", + " System.out.print(\"KungFuGuy now battles a \");\n", + " ob.action();\n", + " }\n", + "}\n", + "\n", + "class Puzzle implements Obstacle {\n", + " @Override\n", + " public void action() {\n", + " System.out.println(\"Puzzle\");\n", + " }\n", + "}\n", + "\n", + "class NastyWeapon implements Obstacle {\n", + " @Override\n", + " public void action() {\n", + " System.out.println(\"NastyWeapon\");\n", + " }\n", + "}\n", + "\n", + "// The Abstract Factory:\n", + "class GameElementFactory {\n", + " Supplier player;\n", + " Supplier obstacle;\n", + "}\n", + "\n", + "// Concrete factories:\n", + "class KittiesAndPuzzles extends GameElementFactory {\n", + " KittiesAndPuzzles() {\n", + " player = Kitty::new;\n", + " obstacle = Puzzle::new;\n", + " }\n", + "}\n", + "\n", + "class KillAndDismember extends GameElementFactory {\n", + " KillAndDismember() {\n", + " player = KungFuGuy::new;\n", + " obstacle = NastyWeapon::new;\n", + " }\n", + "}\n", + "\n", + "public class GameEnvironment {\n", + " private Player p;\n", + " private Obstacle ob;\n", + "\n", + " public GameEnvironment(GameElementFactory factory) {\n", + " p = factory.player.get();\n", + " ob = factory.obstacle.get();\n", + " }\n", + " public void play() {\n", + " p.interactWith(ob);\n", + " }\n", + " public static void main(String[] args) {\n", + " GameElementFactory kp = new KittiesAndPuzzles(), kd = new KillAndDismember();\n", + " GameEnvironment g1 = new GameEnvironment(kp), g2 = new GameEnvironment(kd);\n", + " g1.play();\n", + " g2.play();\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "Kitty has encountered a Puzzle\n", + "KungFuGuy now battles a NastyWeapon" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这种环境中,**Player**对象与**Obstacle**对象进行交互,但是根据你所玩游戏的类型,存在不同类型的玩家和障碍物。 你可以通过选择特定的**GameElementFactory**来确定游戏的类型,然后**GameEnvironment**控制游戏的设置和玩法。 在此示例中,设置和玩法非常简单,但是这些活动(初始条件和状态变化)可以决定游戏的大部分结果。 这里,**GameEnvironment**不是为继承而设计的,尽管这样做很有意义。 它还包含“双重调度”和“工厂方法”的示例,稍后将对这两个示例进行说明。\n", + "\n", + "\n", + "\n", + "## 函数对象\n", + "\n", + "一个 *函数对象* 封装了一个函数。其特点就是将被调用函数的选择与那个函数被调用的位置进行解耦。\n", + "\n", + "*《设计模式》* 中也提到了这个术语,但是没有使用。然而,*函数对象* 的话题却在那本书的很多模式中被反复论及。\n", + "\n", + "### 命令模式\n", + "\n", + "从最直观的角度来看,*命令模式* 就是一个函数对象:一个作为对象的函数。我们可以将 *函数对象* 作为参数传递给其他方法或者对象,来执行特定的操作。\n", + "\n", + "在Java 8之前,想要产生单个函数的效果,我们必须明确将方法包含在对象中,而这需要太多的仪式了。而利用Java 8的lambda特性, *命令模式* 的实现将是微不足道的。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/CommandPattern.java\n", + "import java.util.*;\n", + "\n", + "public class CommandPattern {\n", + " public static void main(String[] args) {\n", + " List macro = Arrays.asList(\n", + " () -> System.out.print(\"Hello \"),\n", + " () -> System.out.print(\"World! \"),\n", + " () -> System.out.print(\"I'm the command pattern!\")\n", + " );\n", + " macro.forEach(Runnable::run);\n", + " }\n", + "}\n", + "/* Output:\n", + "Hello World! I'm the command pattern!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*命令模式* 的主要特点是允许向一个方法或者对象传递一个想要的动作。在上面的例子中,这个对象就是 **macro** ,而 *命令模式* 提供了将一系列需要一起执行的动作集进行排队的方法。在这里,*命令模式* 允许我们动态的创建新的行为,通常情况下我们需要编写新的代码才能完成这个功能,而在上面的例子中,我们可以通过解释运行一个脚本来完成这个功能(如果需要实现的东西很复杂请参考解释器模式)。\n", + "\n", + "*《设计模式》* 认为“命令模式是回调的面向对象的替代品”。尽管如此,我认为\"back\"(回来)这个词是callback(回调)这一概念的基本要素。也就是说,我认为回调(callback)实际上是返回到回调的创建者所在的位置。另一方面,对于 *命令* 对象,通常只需创建它并将其交给某种方法或对象,而不是自始至终以其他方式联系命令对象。不管怎样,这就是我对它的看法。在本章的后面内容中,我将会把一组设计模式放在“回调”的标题下面。\n", + "\n", + "### 策略模式\n", + "\n", + "*策略模式* 看起来像是从同一个基类继承而来的一系列 *命令* 类。但是仔细查看 *命令模式*,你就会发现它也具有同样的结构:一系列分层次的 *函数对象*。不同之处在于,这些函数对象的用法和策略模式不同。就像前面的 `io/DirList.java` 那个例子,使用 *命令* 是为了解决特定问题 -- 从一个列表中选择文件。“不变的部分”是被调用的那个方法,而变化的部分被分离出来放到 *函数对象* 中。我认为 *命令模式* 在编码阶段提供了灵活性,而 *策略模式* 的灵活性在运行时才会体现出来。尽管如此,这种区别却是非常模糊的。\n", + "\n", + "另外,*策略模式* 还可以添加一个“上下文(context)”,这个上下文(context)可以是一个代理类(surrogate class),用来控制对某个特定 *策略* 对象的选择和使用。就像 *桥接模式* 一样!下面我们来一探究竟:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/strategy/StrategyPattern.java\n", + "// {java patterns.strategy.StrategyPattern}\n", + "package patterns.strategy;\n", + "import java.util.function.*;\n", + "import java.util.*;\n", + "\n", + "// The common strategy base type:\n", + "class FindMinima {\n", + " Function, List> algorithm;\n", + "}\n", + "\n", + "// The various strategies:\n", + "class LeastSquares extends FindMinima {\n", + " LeastSquares() {\n", + " // Line is a sequence of points (Dummy data):\n", + " algorithm = (line) -> Arrays.asList(1.1, 2.2);\n", + " }\n", + "}\n", + "\n", + "class Perturbation extends FindMinima {\n", + " Perturbation() {\n", + " algorithm = (line) -> Arrays.asList(3.3, 4.4);\n", + " }\n", + "}\n", + "\n", + "class Bisection extends FindMinima {\n", + " Bisection() {\n", + " algorithm = (line) -> Arrays.asList(5.5, 6.6);\n", + " }\n", + "}\n", + "\n", + "// The \"Context\" controls the strategy:\n", + "class MinimaSolver {\n", + " private FindMinima strategy;\n", + " MinimaSolver(FindMinima strat) {\n", + " strategy = strat;\n", + " }\n", + " List minima(List line) {\n", + " return strategy.algorithm.apply(line);\n", + " }\n", + " void changeAlgorithm(FindMinima newAlgorithm) {\n", + " strategy = newAlgorithm;\n", + " }\n", + "}\n", + "\n", + "public class StrategyPattern {\n", + " public static void main(String[] args) {\n", + " MinimaSolver solver = \n", + " new MinimaSolver(new LeastSquares());\n", + " List line = Arrays.asList(\n", + " 1.0, 2.0, 1.0, 2.0, -1.0,\n", + " 3.0, 4.0, 5.0, 4.0 );\n", + " System.out.println(solver.minima(line)); \n", + " solver.changeAlgorithm(new Bisection()); \n", + " System.out.println(solver.minima(line));\n", + " }\n", + "}\n", + "/* Output:\n", + "[1.1, 2.2]\n", + "[5.5, 6.6]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`MinimaSolver` 中的 `changeAlgorithm()` 方法将一个不同的策略插入到了 `私有` 域 `strategy` 中,这使得在调用 `minima()` 方法时,可以使用新的策略。\n", + "\n", + "我们可以通过将上下文注入到 `FindMinima` 中来简化我们的解决方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/strategy/StrategyPattern2.java // {java patterns.strategy.StrategyPattern2}\n", + "package patterns.strategy;\n", + "import java.util.function.*;\n", + "import java.util.*;\n", + "\n", + "// \"Context\" is now incorporated:\n", + "class FindMinima2 {\n", + " Function, List> algorithm;\n", + " FindMinima2() { leastSquares(); } // default\n", + " // The various strategies:\n", + " void leastSquares() {\n", + " algorithm = (line) -> Arrays.asList(1.1, 2.2);\n", + " }\n", + " void perturbation() {\n", + " algorithm = (line) -> Arrays.asList(3.3, 4.4);\n", + " }\n", + " void bisection() {\n", + " algorithm = (line) -> Arrays.asList(5.5, 6.6);\n", + " }\n", + " List minima(List line) {\n", + " return algorithm.apply(line);\n", + " }\n", + "}\n", + "\n", + "public class StrategyPattern2 {\n", + " public static void main(String[] args) {\n", + " FindMinima2 solver = new FindMinima2();\n", + " List line = Arrays.asList(\n", + " 1.0, 2.0, 1.0, 2.0, -1.0,\n", + " 3.0, 4.0, 5.0, 4.0 );\n", + " System.out.println(solver.minima(line));\n", + " solver.bisection();\n", + " System.out.println(solver.minima(line));\n", + " }\n", + "}\n", + "/* Output:\n", + "[1.1, 2.2]\n", + "[5.5, 6.6]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`FindMinima2` 封装了不同的算法,也包含了“上下文”(Context),所以它便可以在一个单独的类中控制算法的选择了。\n", + "\n", + "### 责任链模式\n", + "\n", + "*责任链模式* 也许可以被看作一个使用了 *策略* 对象的“递归的动态一般化”。此时我们进行一次调用,在一个链序列中的每个策略都试图满足这个调用。这个过程直到有一个策略成功满足该调用或者到达链序列的末尾才结束。在递归方法中,一个方法将反复调用它自身直至达到某个终止条件;使用责任链,一个方法会调用相同的基类方法(拥有不同的实现),这个基类方法将会调用基类方法的其他实现,如此反复直至达到某个终止条件。\n", + "\n", + "除了调用某个方法来满足某个请求以外,链中的多个方法都有机会满足这个请求,因此它有点专家系统的意味。由于责任链实际上就是一个链表,它能够动态创建,因此它可以看作是一个更一般的动态构建的 `switch` 语句。\n", + "\n", + "在上面的 `StrategyPattern.java` 例子中,我们可能想自动发现一个解决方法。而 *责任链* 就可以达到这个目的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/chain/ChainOfResponsibility.java\n", + "// Using the Functional interface\n", + "// {java patterns.chain.ChainOfResponsibility}\n", + "package patterns.chain;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "\n", + "class Result {\n", + " boolean success;\n", + " List line;\n", + " Result(List data) {\n", + " success = true;\n", + " line = data;\n", + " }\n", + " Result() {\n", + " success = false;\n", + " line = Collections.emptyList();\n", + " }\n", + "}\n", + "\n", + "class Fail extends Result {}\n", + "\n", + "interface Algorithm {\n", + " Result algorithm(List line);\n", + "}\n", + "\n", + "class FindMinima {\n", + " public static Result leastSquares(List line) {\n", + " System.out.println(\"LeastSquares.algorithm\");\n", + " boolean weSucceed = false;\n", + " if(weSucceed) // Actual test/calculation here\n", + " return new Result(Arrays.asList(1.1, 2.2));\n", + " else // Try the next one in the chain:\n", + " return new Fail();\n", + " }\n", + " public static Result perturbation(List line) {\n", + " System.out.println(\"Perturbation.algorithm\");\n", + " boolean weSucceed = false;\n", + " if(weSucceed) // Actual test/calculation here\n", + " return new Result(Arrays.asList(3.3, 4.4));\n", + " else\n", + " return new Fail();\n", + " }\n", + " public static Result bisection(List line) {\n", + " System.out.println(\"Bisection.algorithm\");\n", + " boolean weSucceed = true;\n", + " if(weSucceed) // Actual test/calculation here\n", + " return new Result(Arrays.asList(5.5, 6.6));\n", + " else\n", + " return new Fail();\n", + " }\n", + " static List, Result>>\n", + " algorithms = Arrays.asList(\n", + " FindMinima::leastSquares,\n", + " FindMinima::perturbation,\n", + " FindMinima::bisection\n", + " );\n", + " public static Result minima(List line) {\n", + " for(Function, Result> alg :\n", + " algorithms) {\n", + " Result result = alg.apply(line);\n", + " if(result.success)\n", + " return result;\n", + " }\n", + " return new Fail();\n", + " }\n", + "}\n", + "\n", + "public class ChainOfResponsibility {\n", + " public static void main(String[] args) {\n", + " FindMinima solver = new FindMinima();\n", + " List line = Arrays.asList(\n", + " 1.0, 2.0, 1.0, 2.0, -1.0,\n", + " 3.0, 4.0, 5.0, 4.0);\n", + " Result result = solver.minima(line);\n", + " if(result.success)\n", + " System.out.println(result.line);\n", + " else\n", + " System.out.println(\"No algorithm found\");\n", + " }\n", + "}\n", + "/* Output:\n", + "LeastSquares.algorithm\n", + "Perturbation.algorithm\n", + "Bisection.algorithm\n", + "[5.5, 6.6]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们从定义一个 `Result` 类开始,这个类包含一个 `success` 标志,因此接收者就可以知道算法是否成功执行,而 `line` 变量保存了真实的数据。当算法执行失败时, `Fail` 类可以作为返回值。要注意的是,当算法执行失败时,返回一个 `Result` 对象要比抛出一个异常更加合适,因为我们有时可能并不打算解决这个问题,而是希望程序继续执行下去。\n", + "\n", + "每一个 `Algorithm` 接口的实现,都实现了不同的 `algorithm()` 方法。在 `FindMinama` 中,将会创建一个算法的列表(这就是所谓的“链”),而 `minima()` 方法只是遍历这个列表,然后找到能够成功执行的算法而已。\n", + "\n", + "\n", + "## 改变接口\n", + "\n", + "有时候我们需要解决的问题很简单,仅仅是“我没有需要的接口”而已。有两种设计模式用来解决这个问题:*适配器模式* 接受一种类型并且提供一个对其他类型的接口。*外观模式* 为一组类创建了一个接口,这样做只是为了提供一种更方便的方法来处理库或资源。\n", + "\n", + "### 适配器模式(Adapter)\n", + "\n", + "当我们手头有某个类,而我们需要的却是另外一个类,我们就可以通过 *适配器模式* 来解决问题。唯一需要做的就是产生出我们需要的那个类,有许多种方法可以完成这种适配。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/adapt/Adapter.java\n", + "// Variations on the Adapter pattern\n", + "// {java patterns.adapt.Adapter}\n", + "package patterns.adapt;\n", + "\n", + "class WhatIHave {\n", + " public void g() {}\n", + " public void h() {}\n", + "}\n", + "\n", + "interface WhatIWant {\n", + " void f();\n", + "}\n", + "\n", + "class ProxyAdapter implements WhatIWant {\n", + " WhatIHave whatIHave;\n", + " ProxyAdapter(WhatIHave wih) {\n", + " whatIHave = wih;\n", + " }\n", + " @Override\n", + " public void f() {\n", + " // Implement behavior using\n", + " // methods in WhatIHave:\n", + " whatIHave.g();\n", + " whatIHave.h();\n", + " }\n", + "}\n", + "\n", + "class WhatIUse {\n", + " public void op(WhatIWant wiw) {\n", + " wiw.f();\n", + " }\n", + "}\n", + "\n", + "// Approach 2: build adapter use into op():\n", + "class WhatIUse2 extends WhatIUse {\n", + " public void op(WhatIHave wih) {\n", + " new ProxyAdapter(wih).f();\n", + " }\n", + "}\n", + "\n", + "// Approach 3: build adapter into WhatIHave:\n", + "class WhatIHave2 extends WhatIHave implements WhatIWant {\n", + " @Override\n", + " public void f() {\n", + " g();\n", + " h();\n", + " }\n", + "}\n", + "\n", + "// Approach 4: use an inner class:\n", + "class WhatIHave3 extends WhatIHave {\n", + " private class InnerAdapter implements WhatIWant {\n", + " @Override\n", + " public void f() {\n", + " g();\n", + " h();\n", + " }\n", + " }\n", + " public WhatIWant whatIWant() {\n", + " return new InnerAdapter();\n", + " }\n", + "}\n", + "\n", + "public class Adapter {\n", + " public static void main(String[] args) {\n", + " WhatIUse whatIUse = new WhatIUse();\n", + " WhatIHave whatIHave = new WhatIHave();\n", + " WhatIWant adapt= new ProxyAdapter(whatIHave);\n", + " whatIUse.op(adapt);\n", + " // Approach 2:\n", + " WhatIUse2 whatIUse2 = new WhatIUse2();\n", + " whatIUse2.op(whatIHave);\n", + " // Approach 3:\n", + " WhatIHave2 whatIHave2 = new WhatIHave2();\n", + " whatIUse.op(whatIHave2);\n", + " // Approach 4:\n", + " WhatIHave3 whatIHave3 = new WhatIHave3();\n", + " whatIUse.op(whatIHave3.whatIWant());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我想冒昧的借用一下术语“proxy”(代理),因为在 *《设计模式》* 里,他们坚持认为一个代理(proxy)必须拥有和它所代理的对象一模一样的接口。但是,如果把这两个词一起使用,叫做“代理适配器(proxy adapter)”,似乎更合理一些。\n", + "\n", + "### 外观模式(Façade)\n", + "\n", + "当我想方设法试图将需求初步(first-cut)转化成对象的时候,通常我使用的原则是:\n", + "\n", + ">“把所有丑陋的东西都隐藏到对象里去”。\n", + "\n", + "基本上说,*外观模式* 干的就是这个事情。如果我们有一堆让人头晕的类以及交互(Interactions),而它们又不是客户端程序员必须了解的,那我们就可以为客户端程序员创建一个接口只提供那些必要的功能。\n", + "\n", + "外观模式经常被实现为一个符合单例模式(Singleton)的抽象工厂(abstract factory)。当然,你可以通过创建包含 **静态** 工厂方法(static factory methods)的类来达到上述效果。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// patterns/Facade.java\n", + "\n", + "class A { A(int x) {} }\n", + "\n", + "class B { B(long x) {} }\n", + "\n", + "class C { C(double x) {} }\n", + "\n", + "// Other classes that aren't exposed by the\n", + "// facade go here ...\n", + "public class Facade {\n", + " static A makeA(int x) { return new A(x); }\n", + " static B makeB(long x) { return new B(x); }\n", + " static C makeC(double x) { return new C(x); }\n", + " public static void main(String[] args) {\n", + " // The client programmer gets the objects\n", + " // by calling the static methods:\n", + " A a = Facade.makeA(1);\n", + " B b = Facade.makeB(1);\n", + " C c = Facade.makeC(1.0);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "《设计模式》给出的例子并不是真正的 *外观模式* ,而仅仅是一个类使用了其他的类而已。\n", + "\n", + "#### 包(Package)作为外观模式的变体\n", + "\n", + "我感觉,*外观模式* 更倾向于“过程式的(procedural)”,也就是非面向对象的(non-object-oriented):我们是通过调用某些函数才得到对象。它和抽象工厂(Abstract factory)到底有多大差别呢?*外观模式* 关键的一点是隐藏某个库的一部分类(以及它们的交互),使它们对于客户端程序员不可见,这样那些类的接口就更加简练和易于理解了。\n", + "\n", + "其实,这也正是 Java 的 packaging(包)的功能所完成的事情:在库以外,我们只能创建和使用被声明为公共(public)的那些类;所有非公共(non-public)的类只能被同一 package 的类使用。看起来,*外观模式* 似乎是 Java 内嵌的一个功能。\n", + "\n", + "公平起见,*《设计模式》* 主要是写给 C++ 读者的。尽管 C++ 有命名空间(namespaces)机制来防止全局变量和类名称之间的冲突,但它并没有提供类隐藏的机制,而在 Java 里我们可以通过声明 non-public 类来实现这一点。我认为,大多数情况下 Java 的 package 功能就足以解决针对 *外观模式* 的问题了。\n", + "\n", + "\n", + "## 解释器:运行时的弹性\n", + "\n", + "如果程序的用户需要更好的运行时弹性,例如创建脚本来增加需要的系统功能,你就能使用解释器设计模式。这个模式下,你可以创建一个语言解释器并将它嵌入你的程序内。\n", + "\n", + "在开发程序的过程中,设计自己的语言并为它构建一个解释器是一件让人分心且耗时的事。最好的解决方案就是复用代码:使用一个已经构建好并被调试过的解释器。Python 语言可以免费地嵌入营利性的应用中而不需要任何的协议许可、授权费或者是任何的声明。此外,有一个完全使用 Java 字节码实现的 Python 版本(叫做 Jython), 能够轻易地合并到 Java 程序中。Python 是一门非常易学习的脚本语言,代码的读写很有逻辑性。它支持函数与对象,有大量的可用库,并且可运行在所有的平台上。你可以在 [www.Python.org](https://www.python.org/) 上下载 Python 并了解更多信息。\n", + "\n", + "\n", + "## 回调\n", + "\n", + "\n", + "\n", + "## 多次调度\n", + "\n", + "\n", + "\n", + "## 模式重构\n", + "\n", + "\n", + "\n", + "## 抽象用法\n", + "\n", + "\n", + "\n", + "## 多次派遣\n", + "\n", + "\n", + "\n", + "## 访问者模式\n", + "\n", + "\n", + "\n", + "## RTTI的优劣\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Becoming-a-Programmer.ipynb b/jupyter/Appendix-Becoming-a-Programmer.ipynb new file mode 100644 index 00000000..25b35c24 --- /dev/null +++ b/jupyter/Appendix-Becoming-a-Programmer.ipynb @@ -0,0 +1,132 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:成为一名程序员\n", + "\n", + ">我分别于2003,2006,2007和2009年撰写的博客文章混搭\n", + "\n", + "\n", + "## 如何开始\n", + "\n", + "这是一条相当漫长和曲折的道路。我在高一学代数时(1971年),有个非常古怪的老师有一台计算机,还弄到了一台配有一个300波特的音频电话耦合器的ASR-33电传打字机,我学会了如何执行命令并得到响应,以及一个可以在高中区使用的HP-1000计算机上的帐户。我们能够创建和运行BASIC程序并将它们保存在打孔磁带上。我对此非常着迷,所以尽可能地把它带回家后在晚上写程序。我写了一个赛马模拟游戏--HOSRAC.BAS,用星号来代表马的移动,由于是在纸上打印输出,所以需要一点想象力。\n", + "\n", + "我的朋友丹尼尔(就是设计我的书封面的人)有一个兄弟,他有段时间通过向酒吧和餐馆提供弹球机来赚钱。他有一台投币式街机(老虎机),最早的《乓》游戏之一,我对此全然不知,到现在我还忍受不了这东西(现在我几乎不玩电脑游戏,这样看来我可能是个没有幽默的人,但似乎编程比玩电脑游戏更有趣、更具挑战性。)\n", + "\n", + "后来我在高中参与了摄影和新闻工作,在大学的第一年就主修新闻学。我觉得自己已经从学校学到了足够多的东西,又转修了物理学。后来我在加州大学欧文分校完成了物理学位,如果我当时选择了一个特定的工程领域,修了足够的工程课就能拿到双专业,但我试图走得更远一些,所以最后我获得的本科学位是 \"应用物理\"。作为一名本科生,我多多少少学习了一些可以自娱自乐,但又没有任何深度的计算机编程课程。我个人认为在这些课程细细熏陶下,帮我打下了一定的基础,但事实我理解的这些东西没有任何深度。我不知道计算机、编译器或解释器有什么区别(只是对编译器和解释器一点点的理解)。对我来说计算机是绝对可靠的,而且我从来没有想过在程序语言和操作系统中会有出现错误的可能。\n", + "\n", + "后来我去了在加州州立理工大学攻读研究生,主要有三点原因\n", + "\n", + "1. 我真的非常喜欢物理学这个领域\n", + "\n", + "2. 他们接受了我,甚至给了我一份教学工作和奖学金 \n", + "\n", + "3. 出乎意料的是他们给我的工作时间不止一个夏天\n", + "\n", + "而我完全没做好上班的准备。\n", + "\n", + "作为一名物理专业的学生,我学习的是太阳能发电系统,当时太阳能发电系统很大 (如果你的房子上装了太阳能或生意上是关于太阳能系统,加州就会给予税收抵免,因此也兴起很多生意),加州理工大学也承诺会在工程系开设相应的课程。然而因为学校没有提供必要的课程,要想获得在太阳能工程的学位得花好几年时间。所以我学习了研究生其他的工程课,包括介绍机械,太阳能,电气和电子工程。我上的课是非电气工程专业的电气工程导论。最常见的研究生工程课程是计算机工程专业,所以最后我拿了那个学位。我还上了艺术课,几门舞蹈课,还有一些计算机科学课程 (Pascal和数据结构),在计算机工程中,我终于弄清楚了处理器的工作流程,从那以后我一直带着一个处理器在身上。这些就是我学的计算机基础知识。\n", + "\n", + "刚开始工作的时候,凭借着一堆硬件和相对简单低水平的编程,做了一名计算机工程师。因为C语言似乎是理想的嵌入式系统语言,于是我开始自学,并慢慢开始了解更多关于编程语言的东西。我们在这家公司从源代码构建编译器,这让我大开眼界。 (想象一下一个编译器只是另一个软件的一部分!)\n", + "\n", + "当我去华盛顿大学海洋学院为Tom Keffer后来创建了“疯狗浪”)工作时,我们决定使用C++。我只有一本Stroustrup写的非初学者书可以参考,最终不得不通过检查C++预处理器生成的中间C代码来了解语言的功能。这个过程非常痛苦,但学习的效果很好。从那以后我就用相同的方式学习,因为它让我学习了如何剖析一种语言,并看到它本质的能力,与此同时开始有了批判性思维。 \n", + "\n", + "我并没有理解清楚所有的概念。只是在之后的日子里不断反复,我所知道的一切需要时间才能消化吸收。如果我现在能很容易地理解一个新概念,那只是因为它是我已经知道的积累概念的一个变种。在加州理工大学招收非计算机本科学历的计算机科学研究生项目中,学生们曾经说他们花了一年的时间才弄清楚他们对计算机的困惑(他们正在沉浸程序之中)。当人们学习计算机时,他们往往会对自己抱有不切实际的期望,通常是他们听说学计算机编程的好处,就希望在几周内找到一份高薪的工作。但是,最好的学习过程是先对计算机感兴趣,随着时间的推移,学习的越来越多,自然的就开始自学。\n", + "\n", + "这些就是我主要做的事,尽管我通过学计算机工程有还算扎实的基础,但我没上过编程课,而是通过自学。在此期间我也在不断地学习新事物,在这个行业里,不断学习是非常重要的一部分。\n", + "\n", + "\n", + "## 码农生涯\n", + "\n", + "我会定期收到有关职业建议的请求,所以我尝试在这里回答一下这个问题。\n", + "\n", + "人们提出的问题通常是错误的问题:“我应该学习 C++ 还是 Java ?”在本文中,我将尝试阐述我对选择计算机职业所涉及的真正问题的看法。\n", + "\n", + "请注意,我在这里并不是和那些已经知道自己使命的人聊(译者注:指计划成为程序员或者已经从业的程序员,暗指这里是讲给外行的小白的)。因为无论别人怎么说,你都要去做,因为它已经渗入你的血液,并且你将无法摆脱它。你已经知道答案了:你当然会学到 C++ ,Java ,shell 脚本,Python 和许多其他语言和技术。即使你只有14岁,你也已经知道其中几种语言。\n", + "\n", + "问我这个问题的人可能来自另一职业。也许他们来自 Web 开发等领域,他们已经发现 HTML 只是一种类似编程,他们想尝试构建更实质的内容。但是,我特别希望,如果你提出这个问题,你就已经意识到,要在计算机领域取得成功,你必须教自己如何学习,并且永不停止学习。\n", + "\n", + "随着我做的越来越多,在我看来,软件越发比其他任何东西都更像写作。而且我们还没有弄清怎样成为一个好的作家,我们只知道何时我们喜欢别人写的东西。这不是像一些工程那样,我们要做的只是将某些东西放到一端,然后转动曲柄。诱人的是将软件视为确定性的,这就是我们想要的,这就是我们不断推出工具来帮助我们实现所需行为的原因。但是我的经验不断表明事实是相反的:它更多地是关于人而不是过程,并且它在确定性机器上运行的事实变得越来越没有影响力(指运行环境受机器影响,与机器相关这个事实),就像海森堡原理(不确定性原理:不可能同时知道一个粒子的位置和它的速度)不会在人类规模上影响事物一样。\n", + "\n", + "在我青年时期,父亲是建造民居的,我偶尔会为他工作,大部分时间都从事艰苦的工作,有时还得悬挂石膏板。他和他的木匠会告诉我说,他们是为了我才把这些工作交给了我 —— 为了不让我从事这项工作。这确实是有效的。\n", + "\n", + "因此,我也可以用比喻说,建造软件就像盖房子一样。我们并不是指每个在房屋上工作的人都一样。有混凝土泥瓦匠,屋顶工,水管工,电工,石膏板工人,抹灰工,瓷砖铺砌工,普通劳工,粗木匠,精整木匠,当然还有总承包商。这些中的每一个都需要一套不同的技能,这需要花费不同的时间和精力 房屋建造也受制于繁荣和萧条的周期,例如编程。为了快速起步,你可能需要当普通劳工或石膏板工人工作,在那里你可以在没有太多学习曲线的情况下开始获得报酬。只要需求旺盛,你就可以稳定工作,而且如果没有足够的人来工作,你的薪水甚至可能会上涨。但是一旦经济低迷,木匠甚至总承包商就可以自己将石膏板挂起来。\n", + "\n", + "当 Internet 刚兴起时,你所要做的就是花一些时间学习 HTML ,就可以找到一份工作并赚到很多钱。但是,当情况恶化时,你很快就会发现需要的技能层次结构很深,HTML 程序员(例如劳工和石膏板工)排在第一位,而高技能的码农和木匠则被保留。\n", + "\n", + "我想在这里说的是:除非你准备致力于终身学习,否则请不要从事这项业务。有时,编程似乎是一份报酬丰厚,值得信赖的工作,但确保这一点的唯一方法是,始终使自己变得更有价值。\n", + "\n", + "当然,也可以找到例外。总会有一些人只学习一种语言,并且足够精通,甚至足够聪明,那么可以在不用多学很多其他知识的情况下继续工作。但是他们靠运气生存,最终很脆弱。为了减少自身的脆弱性,必须通过阅读,参加用户组,会议和研讨会来不断提高自己的能力。你在该领域的走得越深,你的价值就越大,这意味着你的工作前景更稳定,并且可以获得更高的薪水。\n", + "\n", + "另一种方法是从总体上看待该领域,并找到一个你能成为专家的点。例如,我的兄弟对软件感兴趣,并且涉足软件,但是他的业务是安装计算机,维修计算机和升级计算机。他一直都很细致,因此,当他安装或修理计算机时,你会知道计算机状态良好。不仅是软件,而且一直到电缆,电缆都整齐地捆扎在一起,并且不成束。他的工作多到做不完,而且他从不关心网络泡沫破灭。毋庸置疑,他是不可能失业的。\n", + "\n", + "我在大学待了很长时间,并以各种方式设法度过了难关。我甚至开始在加州大学洛杉矶分校攻读博士学位。这里的课程很短,我欣慰地说是因为我不再爱上大学了,而我在大学待了这么长时间的原因是因为我非常喜欢。但是我喜欢的通常是跑偏的东西。例如艺术,舞蹈课程,在大学报社工作,以及我参加的少数计算机编程课程(由于我是物理本科生和计算机工程专业的研究生,所以也算跑偏)。尽管我在学业上还算是出色的(具有讽刺意味的是,当时许多不接受我作为学生的大学现在都在课程中使用我的书),但我确实很享受大学生的生活,并且完成了博士学位。我可能会走上简单的道路,最终成为一名教授。\n", + "\n", + "但是事实证明,我从大学获得的最大价值一部分来自那些跑偏的课程,这些课程使我的思维超出了“我们已经知道的东西”。我认为在计算机领域尤其如此,因为你总是通过编程来实现其他目标,而你对该目标越了解,你的表现就会越好(我学习了一些欧洲研究生课程,这些课程要求结合其他一些专业研究计算,通过解决这个领域相关的问题,你就会形成一种新的理论体系并可以将它用在别处)。\n", + "\n", + "我还认为,不仅编程,多了解一些其它的知识,还可以大大提高你的解决问题的能力(就像了解一种以上的编程语言可以极大地提高你的编程能力一样)。在很多情况下,我遇到过仅接受过计算机科学训练的人,他们的思维似乎比其他背景(例如数学或物理学)的人更受限制,但其实这些人(数学或物理学领域的人)才更需要严格的思维。\n", + "\n", + "在我组织的一次会议上,主题之一是为理想的求职者提供一系列功能:\n", + "\n", + "- 将学习作为一种生活方式。例如,学习一种以上的语言;没有什么比学习另一种语言更能吸引你的眼球。\n", + "- 知道在哪里以及如何获得新知识。\n", + "- 研究现有技术。\n", + "- 我们是工具使用者,即要善于利用工具。\n", + "- 学习做最简单的事情。\n", + "- 了解业务(阅读杂志。从 *fast company*(国外一家商业杂志)开始,该公司的文章非常简短有趣。然后你就会知道是否要阅读其他的)\n", + "- 应对错误负责。 “我用着没事”是不可接受的策略。查找自己的错误。\n", + "- 成为领导者:那些沟通和鼓舞别人的人。\n", + "- 你在为谁服务?\n", + "- 没有正确的答案……但总是更好的方法。展示和讨论你的代码,不要有情感上的依恋。你不是你的代码。\n", + "- 这是通往完美的渐进旅程。\n", + "\n", + "承担一切可能的风险,最好的风险是那些可怕的风险,但是在尝试时你会比想象中的更加活跃。最好不要刻意去预测某个特定的结果,因为如果你过于重视某个结果,就会经常错过真正的可能性。应该“让我们做一点实验,看看会把我们带到哪里”。这些实验是我最好的冒险。\n", + "\n", + "有些人对这个答案感到失望,然后回答“是的,这都是非常有趣和有用的。但是实际上,我应该学习什么? C++ 还是 Java ?”,以防这些问题,我将在这里重复一遍:我知道似乎所有的 1 和 0 都应该使一切具有确定性因此此类问题应该有一个简单的答案,但事实并非如此。这与做出选择并完成选择无关,这是有关持续学习和有时需要大胆的选择。相信我,这样你的生活会更加令人兴奋。\n", + "\n", + "### 延伸阅读\n", + "* [Teach Yourself Programming In Ten Years](http://norvig.com/21-days.html), by Peter Norvig.\n", + "* [How To Be A Programmer](http://samizdat.mines.edu/howto/HowToBeAProgrammer.html), by Robert Read.\n", + "* A [speech by Steve Jobs](http://news.stanford.edu/news/2005/june15/jobs-061505.html) to inspire a group of graduating college students.\n", + "* Kathy Sierra: [Does College Matter](https://headrush.typepad.com/creating_passionate_users/2005/07/does_college_ma.html)?\n", + "* Paul Graham [on College](http://www.paulgraham.com/college.html).\n", + "* Joel Spolsky: [Advice for Computer Science College Students](https://www.joelonsoftware.com/2005/01/02/advice-for-computer-science-college-students/).\n", + "* James Shore: [Five Design Skills Every Programmer Should Have](https://www.jamesshore.com/Blog/Five-Design-Skills.html).\n", + "* Steve Yegge: [The Truth About Interviewing](http://steve-yegge.blogspot.com/2006/03/truth-about-interviewing.html).\n", + "\n", + "\n", + "## 百分之五的神话\n", + "\n", + "\n", + "\n", + "## 重在动手\n", + "\n", + "\n", + "\n", + "## 像打字般编程\n", + "\n", + "\n", + "\n", + "## 做你喜欢的事\n", + "\n", + "*“1960年,一位研究人员对1500名商学院学生进行了访谈,并将他们分为两类:那些为了钱财来这里上学的人,1245人,以及那些打算利用学位做他们非常关心的事情的人,255人。二十年后,研究人员再次访谈了这些毕业生,发现其中有101位百万富翁,除了其中一位,所有百万富翁都来自追求他们喜欢做的事的那255人!”*\n", + "\n", + "“现在你可能觉得你对巴洛克时期的冰岛诗歌,或者蝴蝶收集,或者高尔夫,抑或是对社会正义的热情,会因为要养家糊口而让你和你喜欢做的事分道扬镳,并非一定要如此。弗拉基米尔·纳博科夫(Vladimir Nabokov)是本世纪最伟大的小说家之一,他对蝴蝶收藏的热情远远超过写作。事实上,他的第一个大学教学工作是关于鳞翅类昆虫。在过去40年里,对40万美国群众的研究表明,即使是部分的、零散的追求培养你的激情,也可以帮助你充分利用你目前的能力,激励你培养新的能力。”--摘自《The Other 90%》 Robert K.Cooper\n", + "\n", + "当然你可以看Po Bronson写的《 What Should I Do With My Life?》这本书,对这些想法进行更多的探索。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb b/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb new file mode 100644 index 00000000..54cefe16 --- /dev/null +++ b/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb @@ -0,0 +1,42 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:静态语言类型检查\n", + "> 这是一本我多年来撰写的经过编辑过的论文集,论文集试图将静态检查语言和动态语言之间的争论放到一个正确的角度。还有一个前言部分,描述了我最近对这个话题的思考和见解。\n", + "\n", + "\n", + "## 前言\n", + "\n", + "\n", + "\n", + "## 静态类型检查和测试\n", + "\n", + "\n", + "\n", + "## 如何提升打字\n", + "\n", + "\n", + "\n", + "## 生产力的成本\n", + "\n", + "\n", + "\n", + "## 静态和动态\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Collection-Topics.ipynb b/jupyter/Appendix-Collection-Topics.ipynb new file mode 100644 index 00000000..3c17a322 --- /dev/null +++ b/jupyter/Appendix-Collection-Topics.ipynb @@ -0,0 +1,3861 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:集合主题\n", + "\n", + "> 本附录是一些比[第十二章 集合]()中介绍的更高级的内容。\n", + "\n", + "\n", + "## 示例数据\n", + "\n", + "这里创建一些样本数据用于集合示例。 以下数据将颜色名称与HTML颜色的RGB值相关联。请注意,每个键和值都是唯一的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/HTMLColors.java\n", + "// Sample data for collection examples\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class HTMLColors {\n", + " public static final Object[][] ARRAY = {\n", + " { 0xF0F8FF, \"AliceBlue\" },\n", + " { 0xFAEBD7, \"AntiqueWhite\" },\n", + " { 0x7FFFD4, \"Aquamarine\" },\n", + " { 0xF0FFFF, \"Azure\" },\n", + " { 0xF5F5DC, \"Beige\" },\n", + " { 0xFFE4C4, \"Bisque\" },\n", + " { 0x000000, \"Black\" },\n", + " { 0xFFEBCD, \"BlanchedAlmond\" },\n", + " { 0x0000FF, \"Blue\" },\n", + " { 0x8A2BE2, \"BlueViolet\" },\n", + " { 0xA52A2A, \"Brown\" },\n", + " { 0xDEB887, \"BurlyWood\" },\n", + " { 0x5F9EA0, \"CadetBlue\" },\n", + " { 0x7FFF00, \"Chartreuse\" },\n", + " { 0xD2691E, \"Chocolate\" },\n", + " { 0xFF7F50, \"Coral\" },\n", + " { 0x6495ED, \"CornflowerBlue\" },\n", + " { 0xFFF8DC, \"Cornsilk\" },\n", + " { 0xDC143C, \"Crimson\" },\n", + " { 0x00FFFF, \"Cyan\" },\n", + " { 0x00008B, \"DarkBlue\" },\n", + " { 0x008B8B, \"DarkCyan\" },\n", + " { 0xB8860B, \"DarkGoldenRod\" },\n", + " { 0xA9A9A9, \"DarkGray\" },\n", + " { 0x006400, \"DarkGreen\" },\n", + " { 0xBDB76B, \"DarkKhaki\" },\n", + " { 0x8B008B, \"DarkMagenta\" },\n", + " { 0x556B2F, \"DarkOliveGreen\" },\n", + " { 0xFF8C00, \"DarkOrange\" },\n", + " { 0x9932CC, \"DarkOrchid\" },\n", + " { 0x8B0000, \"DarkRed\" },\n", + " { 0xE9967A, \"DarkSalmon\" },\n", + " { 0x8FBC8F, \"DarkSeaGreen\" },\n", + " { 0x483D8B, \"DarkSlateBlue\" },\n", + " { 0x2F4F4F, \"DarkSlateGray\" },\n", + " { 0x00CED1, \"DarkTurquoise\" },\n", + " { 0x9400D3, \"DarkViolet\" },\n", + " { 0xFF1493, \"DeepPink\" },\n", + " { 0x00BFFF, \"DeepSkyBlue\" },\n", + " { 0x696969, \"DimGray\" },\n", + " { 0x1E90FF, \"DodgerBlue\" },\n", + " { 0xB22222, \"FireBrick\" },\n", + " { 0xFFFAF0, \"FloralWhite\" },\n", + " { 0x228B22, \"ForestGreen\" },\n", + " { 0xDCDCDC, \"Gainsboro\" },\n", + " { 0xF8F8FF, \"GhostWhite\" },\n", + " { 0xFFD700, \"Gold\" },\n", + " { 0xDAA520, \"GoldenRod\" },\n", + " { 0x808080, \"Gray\" },\n", + " { 0x008000, \"Green\" },\n", + " { 0xADFF2F, \"GreenYellow\" },\n", + " { 0xF0FFF0, \"HoneyDew\" },\n", + " { 0xFF69B4, \"HotPink\" },\n", + " { 0xCD5C5C, \"IndianRed\" },\n", + " { 0x4B0082, \"Indigo\" },\n", + " { 0xFFFFF0, \"Ivory\" },\n", + " { 0xF0E68C, \"Khaki\" },\n", + " { 0xE6E6FA, \"Lavender\" },\n", + " { 0xFFF0F5, \"LavenderBlush\" },\n", + " { 0x7CFC00, \"LawnGreen\" },\n", + " { 0xFFFACD, \"LemonChiffon\" },\n", + " { 0xADD8E6, \"LightBlue\" },\n", + " { 0xF08080, \"LightCoral\" },\n", + " { 0xE0FFFF, \"LightCyan\" },\n", + " { 0xFAFAD2, \"LightGoldenRodYellow\" },\n", + " { 0xD3D3D3, \"LightGray\" },\n", + " { 0x90EE90, \"LightGreen\" },\n", + " { 0xFFB6C1, \"LightPink\" },\n", + " { 0xFFA07A, \"LightSalmon\" },\n", + " { 0x20B2AA, \"LightSeaGreen\" },\n", + " { 0x87CEFA, \"LightSkyBlue\" },\n", + " { 0x778899, \"LightSlateGray\" },\n", + " { 0xB0C4DE, \"LightSteelBlue\" },\n", + " { 0xFFFFE0, \"LightYellow\" },\n", + " { 0x00FF00, \"Lime\" },\n", + " { 0x32CD32, \"LimeGreen\" },\n", + " { 0xFAF0E6, \"Linen\" },\n", + " { 0xFF00FF, \"Magenta\" },\n", + " { 0x800000, \"Maroon\" },\n", + " { 0x66CDAA, \"MediumAquaMarine\" },\n", + " { 0x0000CD, \"MediumBlue\" },\n", + " { 0xBA55D3, \"MediumOrchid\" },\n", + " { 0x9370DB, \"MediumPurple\" },\n", + " { 0x3CB371, \"MediumSeaGreen\" },\n", + " { 0x7B68EE, \"MediumSlateBlue\" },\n", + " { 0x00FA9A, \"MediumSpringGreen\" },\n", + " { 0x48D1CC, \"MediumTurquoise\" },\n", + " { 0xC71585, \"MediumVioletRed\" },\n", + " { 0x191970, \"MidnightBlue\" },\n", + " { 0xF5FFFA, \"MintCream\" },\n", + " { 0xFFE4E1, \"MistyRose\" },\n", + " { 0xFFE4B5, \"Moccasin\" },\n", + " { 0xFFDEAD, \"NavajoWhite\" },\n", + " { 0x000080, \"Navy\" },\n", + " { 0xFDF5E6, \"OldLace\" },\n", + " { 0x808000, \"Olive\" },\n", + " { 0x6B8E23, \"OliveDrab\" },\n", + " { 0xFFA500, \"Orange\" },\n", + " { 0xFF4500, \"OrangeRed\" },\n", + " { 0xDA70D6, \"Orchid\" },\n", + " { 0xEEE8AA, \"PaleGoldenRod\" },\n", + " { 0x98FB98, \"PaleGreen\" },\n", + " { 0xAFEEEE, \"PaleTurquoise\" },\n", + " { 0xDB7093, \"PaleVioletRed\" },\n", + " { 0xFFEFD5, \"PapayaWhip\" },\n", + " { 0xFFDAB9, \"PeachPuff\" },\n", + " { 0xCD853F, \"Peru\" },\n", + " { 0xFFC0CB, \"Pink\" },\n", + " { 0xDDA0DD, \"Plum\" },\n", + " { 0xB0E0E6, \"PowderBlue\" },\n", + " { 0x800080, \"Purple\" },\n", + " { 0xFF0000, \"Red\" },\n", + " { 0xBC8F8F, \"RosyBrown\" },\n", + " { 0x4169E1, \"RoyalBlue\" },\n", + " { 0x8B4513, \"SaddleBrown\" },\n", + " { 0xFA8072, \"Salmon\" },\n", + " { 0xF4A460, \"SandyBrown\" },\n", + " { 0x2E8B57, \"SeaGreen\" },\n", + " { 0xFFF5EE, \"SeaShell\" },\n", + " { 0xA0522D, \"Sienna\" },\n", + " { 0xC0C0C0, \"Silver\" },\n", + " { 0x87CEEB, \"SkyBlue\" },\n", + " { 0x6A5ACD, \"SlateBlue\" },\n", + " { 0x708090, \"SlateGray\" },\n", + " { 0xFFFAFA, \"Snow\" },\n", + " { 0x00FF7F, \"SpringGreen\" },\n", + " { 0x4682B4, \"SteelBlue\" },\n", + " { 0xD2B48C, \"Tan\" },\n", + " { 0x008080, \"Teal\" },\n", + " { 0xD8BFD8, \"Thistle\" },\n", + " { 0xFF6347, \"Tomato\" },\n", + " { 0x40E0D0, \"Turquoise\" },\n", + " { 0xEE82EE, \"Violet\" },\n", + " { 0xF5DEB3, \"Wheat\" },\n", + " { 0xFFFFFF, \"White\" },\n", + " { 0xF5F5F5, \"WhiteSmoke\" },\n", + " { 0xFFFF00, \"Yellow\" },\n", + " { 0x9ACD32, \"YellowGreen\" },\n", + " };\n", + " public static final Map MAP =\n", + " Arrays.stream(ARRAY)\n", + " .collect(Collectors.toMap(\n", + " element -> (Integer)element[0],\n", + " element -> (String)element[1],\n", + " (v1, v2) -> { // Merge function\n", + " throw new IllegalStateException();\n", + " },\n", + " LinkedHashMap::new\n", + " ));\n", + " // Inversion only works if values are unique:\n", + " public static Map\n", + " invert(Map map) {\n", + " return map.entrySet().stream()\n", + " .collect(Collectors.toMap(\n", + " Map.Entry::getValue,\n", + " Map.Entry::getKey,\n", + " (v1, v2) -> {\n", + " throw new IllegalStateException();\n", + " },\n", + " LinkedHashMap::new\n", + " ));\n", + " }\n", + " public static final Map\n", + " INVMAP = invert(MAP);\n", + " // Look up RGB value given a name:\n", + " public static Integer rgb(String colorName) {\n", + " return INVMAP.get(colorName);\n", + " }\n", + " public static final List LIST =\n", + " Arrays.stream(ARRAY)\n", + " .map(item -> (String)item[1])\n", + " .collect(Collectors.toList());\n", + " public static final List RGBLIST =\n", + " Arrays.stream(ARRAY)\n", + " .map(item -> (Integer)item[0])\n", + " .collect(Collectors.toList());\n", + " public static\n", + " void show(Map.Entry e) {\n", + " System.out.format(\n", + " \"0x%06X: %s%n\", e.getKey(), e.getValue());\n", + " }\n", + " public static void\n", + " show(Map m, int count) {\n", + " m.entrySet().stream()\n", + " .limit(count)\n", + " .forEach(e -> show(e));\n", + " }\n", + " public static void show(Map m) {\n", + " show(m, m.size());\n", + " }\n", + " public static\n", + " void show(Collection lst, int count) {\n", + " lst.stream()\n", + " .limit(count)\n", + " .forEach(System.out::println);\n", + " }\n", + " public static void show(Collection lst) {\n", + " show(lst, lst.size());\n", + " }\n", + " public static\n", + " void showrgb(Collection lst, int count) {\n", + " lst.stream()\n", + " .limit(count)\n", + " .forEach(n -> System.out.format(\"0x%06X%n\", n));\n", + " }\n", + " public static void showrgb(Collection lst) {\n", + " showrgb(lst, lst.size());\n", + " }\n", + " public static\n", + " void showInv(Map m, int count) {\n", + " m.entrySet().stream()\n", + " .limit(count)\n", + " .forEach(e ->\n", + " System.out.format(\n", + " \"%-20s 0x%06X%n\", e.getKey(), e.getValue()));\n", + " }\n", + " public static void showInv(Map m) {\n", + " showInv(m, m.size());\n", + " }\n", + " public static void border() {\n", + " System.out.println(\n", + " \"******************************\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**MAP** 是使用Streams([第十四章 流式编程]())创建的。 二维数组 **ARRAY** 作为流传输到 **Map** 中,但请注意我们不仅仅是使用简单版本的 `Collectors.toMap()` 。 那个版本生成一个 **HashMap** ,它使用散列函数来控制对键的排序。 为了保留原来的顺序,我们必须将键值对直接放入 **TreeMap** 中,这意味着我们需要使用更复杂的 `Collectors.toMap()` 版本。这需要两个函数从每个流元素中提取键和值,就像简单版本的`Collectors.toMap()` 一样。 然后它需要一个*合并函数*(merge function),它解决了与同一个键相关的两个值之间的冲突。这里的数据已经预先审查过,因此绝不会发生这种情况,如果有的话,这里会抛出异常。最后,传递生成所需类型的空map的函数,然后用流来填充它。\n", + "\n", + "`rgb()` 方法是一个便捷函数(convenience function),它接受颜色名称 **String** 参数并生成其数字RGB值。为此,我们需要一个反转版本的 **COLORS** ,它接受一个 **String**键并查找RGB的 **Integer** 值。 这是通过 `invert()` 方法实现的,如果任何 **COLORS** 值不唯一,则抛出异常。\n", + "\n", + "我们还创建包含所有名称的 **LIST** ,以及包含十六进制表示法的RGB值的 **RGBLIST** 。\n", + "\n", + "第一个 `show()` 方法接受一个 **Map.Entry** 并显示以十六进制表示的键,以便轻松地对原始 **ARRAY** 进行双重检查。 名称以 **show** 开头的每个方法都会重载两个版本,其中一个版本采用 **count** 参数来指示要显示的元素数量,第二个版本显示序列中的所有元素。\n", + "\n", + "这里是一个基本的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/HTMLColorTest.java\n", + "import static onjava.HTMLColors.*;\n", + "\n", + "public class HTMLColorTest {\n", + " static final int DISPLAY_SIZE = 20;\n", + " public static void main(String[] args) {\n", + " show(MAP, DISPLAY_SIZE);\n", + " border();\n", + " showInv(INVMAP, DISPLAY_SIZE);\n", + " border();\n", + " show(LIST, DISPLAY_SIZE);\n", + " border();\n", + " showrgb(RGBLIST, DISPLAY_SIZE);\n", + " }\n", + "}\n", + "/* Output:\n", + "0xF0F8FF: AliceBlue\n", + "0xFAEBD7: AntiqueWhite\n", + "0x7FFFD4: Aquamarine\n", + "0xF0FFFF: Azure\n", + "0xF5F5DC: Beige\n", + "0xFFE4C4: Bisque\n", + "0x000000: Black\n", + "0xFFEBCD: BlanchedAlmond\n", + "0x0000FF: Blue\n", + "0x8A2BE2: BlueViolet\n", + "0xA52A2A: Brown\n", + "0xDEB887: BurlyWood\n", + "0x5F9EA0: CadetBlue\n", + "0x7FFF00: Chartreuse\n", + "0xD2691E: Chocolate\n", + "0xFF7F50: Coral\n", + "0x6495ED: CornflowerBlue\n", + "0xFFF8DC: Cornsilk\n", + "0xDC143C: Crimson\n", + "0x00FFFF: Cyan\n", + "******************************\n", + "AliceBlue 0xF0F8FF\n", + "AntiqueWhite 0xFAEBD7\n", + "Aquamarine 0x7FFFD4\n", + "Azure 0xF0FFFF\n", + "Beige 0xF5F5DC\n", + "Bisque 0xFFE4C4\n", + "Black 0x000000\n", + "BlanchedAlmond 0xFFEBCD\n", + "Blue 0x0000FF\n", + "BlueViolet 0x8A2BE2\n", + "Brown 0xA52A2A\n", + "BurlyWood 0xDEB887\n", + "CadetBlue 0x5F9EA0\n", + "Chartreuse 0x7FFF00\n", + "Chocolate 0xD2691E\n", + "Coral 0xFF7F50\n", + "CornflowerBlue 0x6495ED\n", + "Cornsilk 0xFFF8DC\n", + "Crimson 0xDC143C\n", + "Cyan 0x00FFFF\n", + "******************************\n", + "AliceBlue\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "Beige\n", + "Bisque\n", + "Black\n", + "BlanchedAlmond\n", + "Blue\n", + "BlueViolet\n", + "Brown\n", + "BurlyWood\n", + "CadetBlue\n", + "Chartreuse\n", + "Chocolate\n", + "Coral\n", + "CornflowerBlue\n", + "Cornsilk\n", + "Crimson\n", + "Cyan\n", + "******************************\n", + "0xF0F8FF\n", + "0xFAEBD7\n", + "0x7FFFD4\n", + "0xF0FFFF\n", + "0xF5F5DC\n", + "0xFFE4C4\n", + "0x000000\n", + "0xFFEBCD\n", + "0x0000FF\n", + "0x8A2BE2\n", + "0xA52A2A\n", + "0xDEB887\n", + "0x5F9EA0\n", + "0x7FFF00\n", + "0xD2691E\n", + "0xFF7F50\n", + "0x6495ED\n", + "0xFFF8DC\n", + "0xDC143C\n", + "0x00FFFF\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,使用 **LinkedHashMap** 确实能够保留 **HTMLColors.ARRAY** 的顺序。\n", + "\n", + "\n", + "## List行为\n", + "\n", + "**Lists** 是存储和检索对象(次于数组)的最基本方法。基本列表操作包括:\n", + "\n", + "- `add()` 用于插入元素\n", + "- `get()` 用于随机访问元素\n", + "- `iterator()` 获取序列上的一个 **Iterator**\n", + "- `stream()` 生成元素的一个 **Stream**\n", + "\n", + "列表构造方法始终保留元素的添加顺序。\n", + "\n", + "以下示例中的方法各自涵盖了一组不同的行为:每个 **List** 可以执行的操作( `basicTest()` ),使用 **Iterator** ( `iterMotion()` )遍历序列,使用 **Iterator** ( `iterManipulation()` )更改内容,查看 **List** 操作( `testVisual()` )的效果,以及仅可用于 **LinkedLists** 的操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/ListOps.java\n", + "// Things you can do with Lists\n", + "import java.util.*;\n", + "import onjava.HTMLColors;\n", + "\n", + "public class ListOps {\n", + " // Create a short list for testing:\n", + " static final List LIST =\n", + " HTMLColors.LIST.subList(0, 10);\n", + " private static boolean b;\n", + " private static String s;\n", + " private static int i;\n", + " private static Iterator it;\n", + " private static ListIterator lit;\n", + " public static void basicTest(List a) {\n", + " a.add(1, \"x\"); // Add at location 1\n", + " a.add(\"x\"); // Add at end\n", + " // Add a collection:\n", + " a.addAll(LIST);\n", + " // Add a collection starting at location 3:\n", + " a.addAll(3, LIST);\n", + " b = a.contains(\"1\"); // Is it in there?\n", + " // Is the entire collection in there?\n", + " b = a.containsAll(LIST);\n", + " // Lists allow random access, which is cheap\n", + " // for ArrayList, expensive for LinkedList:\n", + " s = a.get(1); // Get (typed) object at location 1\n", + " i = a.indexOf(\"1\"); // Tell index of object\n", + " b = a.isEmpty(); // Any elements inside?\n", + " it = a.iterator(); // Ordinary Iterator\n", + " lit = a.listIterator(); // ListIterator\n", + " lit = a.listIterator(3); // Start at location 3\n", + " i = a.lastIndexOf(\"1\"); // Last match\n", + " a.remove(1); // Remove location 1\n", + " a.remove(\"3\"); // Remove this object\n", + " a.set(1, \"y\"); // Set location 1 to \"y\"\n", + " // Keep everything that's in the argument\n", + " // (the intersection of the two sets):\n", + " a.retainAll(LIST);\n", + " // Remove everything that's in the argument:\n", + " a.removeAll(LIST);\n", + " i = a.size(); // How big is it?\n", + " a.clear(); // Remove all elements\n", + " }\n", + " public static void iterMotion(List a) {\n", + " ListIterator it = a.listIterator();\n", + " b = it.hasNext();\n", + " b = it.hasPrevious();\n", + " s = it.next();\n", + " i = it.nextIndex();\n", + " s = it.previous();\n", + " i = it.previousIndex();\n", + " }\n", + " public static void iterManipulation(List a) {\n", + " ListIterator it = a.listIterator();\n", + " it.add(\"47\");\n", + " // Must move to an element after add():\n", + " it.next();\n", + " // Remove the element after the new one:\n", + " it.remove();\n", + " // Must move to an element after remove():\n", + " it.next();\n", + " // Change the element after the deleted one:\n", + " it.set(\"47\");\n", + " }\n", + " public static void testVisual(List a) {\n", + " System.out.println(a);\n", + " List b = LIST;\n", + " System.out.println(\"b = \" + b);\n", + " a.addAll(b);\n", + " a.addAll(b);\n", + " System.out.println(a);\n", + " // Insert, remove, and replace elements\n", + " // using a ListIterator:\n", + " ListIterator x =\n", + " a.listIterator(a.size()/2);\n", + " x.add(\"one\");\n", + " System.out.println(a);\n", + " System.out.println(x.next());\n", + " x.remove();\n", + " System.out.println(x.next());\n", + " x.set(\"47\");\n", + " System.out.println(a);\n", + " // Traverse the list backwards:\n", + " x = a.listIterator(a.size());\n", + " while(x.hasPrevious())\n", + " System.out.print(x.previous() + \" \");\n", + " System.out.println();\n", + " System.out.println(\"testVisual finished\");\n", + " }\n", + " // There are some things that only LinkedLists can do:\n", + " public static void testLinkedList() {\n", + " LinkedList ll = new LinkedList<>();\n", + " ll.addAll(LIST);\n", + " System.out.println(ll);\n", + " // Treat it like a stack, pushing:\n", + " ll.addFirst(\"one\");\n", + " ll.addFirst(\"two\");\n", + " System.out.println(ll);\n", + " // Like \"peeking\" at the top of a stack:\n", + " System.out.println(ll.getFirst());\n", + " // Like popping a stack:\n", + " System.out.println(ll.removeFirst());\n", + " System.out.println(ll.removeFirst());\n", + " // Treat it like a queue, pulling elements\n", + " // off the tail end:\n", + " System.out.println(ll.removeLast());\n", + " System.out.println(ll);\n", + " }\n", + " public static void main(String[] args) {\n", + " // Make and fill a new list each time:\n", + " basicTest(new LinkedList<>(LIST));\n", + " basicTest(new ArrayList<>(LIST));\n", + " iterMotion(new LinkedList<>(LIST));\n", + " iterMotion(new ArrayList<>(LIST));\n", + " iterManipulation(new LinkedList<>(LIST));\n", + " iterManipulation(new ArrayList<>(LIST));\n", + " testVisual(new LinkedList<>(LIST));\n", + " testLinkedList();\n", + " }\n", + "}\n", + "/* Output:\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "b = [AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", + "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", + "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", + "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", + "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "Bisque\n", + "Black\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", + "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one,\n", + "47, BlanchedAlmond, Blue, BlueViolet, AliceBlue,\n", + "AntiqueWhite, Aquamarine, Azure, Beige, Bisque, Black,\n", + "BlanchedAlmond, Blue, BlueViolet]\n", + "BlueViolet Blue BlanchedAlmond Black Bisque Beige Azure\n", + "Aquamarine AntiqueWhite AliceBlue BlueViolet Blue\n", + "BlanchedAlmond 47 one Beige Azure Aquamarine\n", + "AntiqueWhite AliceBlue BlueViolet Blue BlanchedAlmond\n", + "Black Bisque Beige Azure Aquamarine AntiqueWhite\n", + "AliceBlue\n", + "testVisual finished\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "[two, one, AliceBlue, AntiqueWhite, Aquamarine, Azure,\n", + "Beige, Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", + "two\n", + "two\n", + "one\n", + "BlueViolet\n", + "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", + "Bisque, Black, BlanchedAlmond, Blue]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `basicTest()` 和 `iterMotion()` 中,方法调用是为了展示正确的语法,尽管获取了返回值,但不会使用它。在某些情况下,根本不会去获取返回值。在使用这些方法之前,请查看JDK文档中这些方法的完整用法。\n", + "\n", + "\n", + "## Set行为\n", + "\n", + "**Set** 的主要用处是测试成员身份,不过也可以将其用作删除重复元素的工具。如果不关心元素顺序或并发性, **HashSet** 总是最好的选择,因为它是专门为了快速查找而设计的(这里使用了在[附录:理解equals和hashCode方法]()章节中探讨的散列函数)。\n", + "\n", + "其它的 **Set** 实现产生不同的排序行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/SetOrder.java\n", + "import java.util.*;\n", + "import onjava.HTMLColors;\n", + "\n", + "public class SetOrder {\n", + " static String[] sets = {\n", + " \"java.util.HashSet\",\n", + " \"java.util.TreeSet\",\n", + " \"java.util.concurrent.ConcurrentSkipListSet\",\n", + " \"java.util.LinkedHashSet\",\n", + " \"java.util.concurrent.CopyOnWriteArraySet\",\n", + " };\n", + " static final List RLIST =\n", + " new ArrayList<>(HTMLColors.LIST);\n", + " static {\n", + " Collections.reverse(RLIST);\n", + " }\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " for(String type: sets) {\n", + " System.out.format(\"[-> %s <-]%n\",\n", + " type.substring(type.lastIndexOf('.') + 1));\n", + " @SuppressWarnings(\"unchecked\")\n", + " Set set = (Set)\n", + " Class.forName(type).newInstance();\n", + " set.addAll(RLIST);\n", + " set.stream()\n", + " .limit(10)\n", + " .forEach(System.out::println);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "[-> HashSet <-]\n", + "MediumOrchid\n", + "PaleGoldenRod\n", + "Sienna\n", + "LightSlateGray\n", + "DarkSeaGreen\n", + "Black\n", + "Gainsboro\n", + "Orange\n", + "LightCoral\n", + "DodgerBlue\n", + "[-> TreeSet <-]\n", + "AliceBlue\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "Beige\n", + "Bisque\n", + "Black\n", + "BlanchedAlmond\n", + "Blue\n", + "BlueViolet\n", + "[-> ConcurrentSkipListSet <-]\n", + "AliceBlue\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "Beige\n", + "Bisque\n", + "Black\n", + "BlanchedAlmond\n", + "Blue\n", + "BlueViolet\n", + "[-> LinkedHashSet <-]\n", + "YellowGreen\n", + "Yellow\n", + "WhiteSmoke\n", + "White\n", + "Wheat\n", + "Violet\n", + "Turquoise\n", + "Tomato\n", + "Thistle\n", + "Teal\n", + "[-> CopyOnWriteArraySet <-]\n", + "YellowGreen\n", + "Yellow\n", + "WhiteSmoke\n", + "White\n", + "Wheat\n", + "Violet\n", + "Turquoise\n", + "Tomato\n", + "Thistle\n", + "Teal\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里需要使用 **@SuppressWarnings(“unchecked”)** ,因为这里将一个 **String** (可能是任何东西)传递给了 `Class.forName(type).newInstance()` 。编译器并不能保证这是一次成功的操作。\n", + "\n", + "**RLIST** 是 **HTMLColors.LIST** 的反转版本。因为 `Collections.reverse()` 是通过修改参数来执行反向操作,而不是返回包含反向元素的新 **List** ,所以该调用在 **static** 块内执行。 **RLIST** 可以防止我们意外地认为 **Set** 对其结果进行了排序。\n", + "\n", + "**HashSet** 的输出结果似乎没有可辨别的顺序,因为它是基于散列函数的。 **TreeSet** 和 **ConcurrentSkipListSet** 都对它们的元素进行了排序,它们都实现了 **SortedSet** 接口来标识这个特点。因为实现该接口的 **Set** 按顺序排列,所以该接口还有一些其他的可用操作。 **LinkedHashSet** 和 **CopyOnWriteArraySet** 尽管没有用于标识的接口,但它们还是保留了元素的插入顺序。\n", + "\n", + "**ConcurrentSkipListSet** 和 **CopyOnWriteArraySet** 是线程安全的。\n", + "\n", + "在附录的最后,我们将了解在非 **HashSet** 实现的 **Set** 上添加额外排序的性能成本,以及不同实现中的任何其他功能的成本。\n", + "\n", + "\n", + "## 在Map中使用函数式操作\n", + "\n", + "与 **Collection** 接口一样,`forEach()` 也内置在 **Map** 接口中。但是如果想要执行任何其他的基本功能操作,比如 `map()` ,`flatMap()` ,`reduce()` 或 `filter()` 时,该怎么办? 查看 **Map** 接口发现并没有这些。\n", + "\n", + "可以通过 `entrySet()` 连接到这些方法,该方法会生成一个由 **Map.Entry** 对象组成的 **Set** 。这个 **Set** 包含 `stream()` 和 `parallelStream()` 方法。只需要记住一件事,这里正在使用的是 **Map.Entry** 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/FunctionalMap.java\n", + "// Functional operations on a Map\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import static onjava.HTMLColors.*;\n", + "\n", + "public class FunctionalMap {\n", + " public static void main(String[] args) {\n", + " MAP.entrySet().stream()\n", + " .map(Map.Entry::getValue)\n", + " .filter(v -> v.startsWith(\"Dark\"))\n", + " .map(v -> v.replaceFirst(\"Dark\", \"Hot\"))\n", + " .forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "HotBlue\n", + "HotCyan\n", + "HotGoldenRod\n", + "HotGray\n", + "HotGreen\n", + "HotKhaki\n", + "HotMagenta\n", + "HotOliveGreen\n", + "HotOrange\n", + "HotOrchid\n", + "HotRed\n", + "HotSalmon\n", + "HotSeaGreen\n", + "HotSlateBlue\n", + "HotSlateGray\n", + "HotTurquoise\n", + "HotViolet\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "生成 **Stream** 后,所有的基本功能方法,甚至更多就都可以使用了。\n", + "\n", + "\n", + "## 选择Map片段\n", + "\n", + "由 **TreeMap** 和 **ConcurrentSkipListMap** 实现的 **NavigableMap** 接口解决了需要选择Map片段的问题。下面是一个示例,使用了 **HTMLColors** :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/NavMap.java\n", + "// NavigableMap produces pieces of a Map\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import static onjava.HTMLColors.*;\n", + "\n", + "public class NavMap {\n", + " public static final\n", + " NavigableMap COLORS =\n", + " new ConcurrentSkipListMap<>(MAP);\n", + " public static void main(String[] args) {\n", + " show(COLORS.firstEntry());\n", + " border();\n", + " show(COLORS.lastEntry());\n", + " border();\n", + " NavigableMap toLime =\n", + " COLORS.headMap(rgb(\"Lime\"), true);\n", + " show(toLime);\n", + " border();\n", + " show(COLORS.ceilingEntry(rgb(\"DeepSkyBlue\") - 1));\n", + " border();\n", + " show(COLORS.floorEntry(rgb(\"DeepSkyBlue\") - 1));\n", + " border();\n", + " show(toLime.descendingMap());\n", + " border();\n", + " show(COLORS.tailMap(rgb(\"MistyRose\"), true));\n", + " border();\n", + " show(COLORS.subMap(\n", + " rgb(\"Orchid\"), true,\n", + " rgb(\"DarkSalmon\"), false));\n", + " }\n", + "}\n", + "/* Output:\n", + "0x000000: Black\n", + "******************************\n", + "0xFFFFFF: White\n", + "******************************\n", + "0x000000: Black\n", + "0x000080: Navy\n", + "0x00008B: DarkBlue\n", + "0x0000CD: MediumBlue\n", + "0x0000FF: Blue\n", + "0x006400: DarkGreen\n", + "0x008000: Green\n", + "0x008080: Teal\n", + "0x008B8B: DarkCyan\n", + "0x00BFFF: DeepSkyBlue\n", + "0x00CED1: DarkTurquoise\n", + "0x00FA9A: MediumSpringGreen\n", + "0x00FF00: Lime\n", + "******************************\n", + "0x00BFFF: DeepSkyBlue\n", + "******************************\n", + "0x008B8B: DarkCyan\n", + "******************************\n", + "0x00FF00: Lime\n", + "0x00FA9A: MediumSpringGreen\n", + "0x00CED1: DarkTurquoise\n", + "0x00BFFF: DeepSkyBlue\n", + "0x008B8B: DarkCyan\n", + "0x008080: Teal\n", + "0x008000: Green\n", + "0x006400: DarkGreen\n", + "0x0000FF: Blue\n", + "0x0000CD: MediumBlue\n", + "0x00008B: DarkBlue\n", + "0x000080: Navy\n", + "0x000000: Black\n", + "******************************\n", + "0xFFE4E1: MistyRose\n", + "0xFFEBCD: BlanchedAlmond\n", + "0xFFEFD5: PapayaWhip\n", + "0xFFF0F5: LavenderBlush\n", + "0xFFF5EE: SeaShell\n", + "0xFFF8DC: Cornsilk\n", + "0xFFFACD: LemonChiffon\n", + "0xFFFAF0: FloralWhite\n", + "0xFFFAFA: Snow\n", + "0xFFFF00: Yellow\n", + "0xFFFFE0: LightYellow\n", + "0xFFFFF0: Ivory\n", + "0xFFFFFF: White\n", + "******************************\n", + "0xDA70D6: Orchid\n", + "0xDAA520: GoldenRod\n", + "0xDB7093: PaleVioletRed\n", + "0xDC143C: Crimson\n", + "0xDCDCDC: Gainsboro\n", + "0xDDA0DD: Plum\n", + "0xDEB887: BurlyWood\n", + "0xE0FFFF: LightCyan\n", + "0xE6E6FA: Lavender\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在主方法中可以看到 **NavigableMap** 的各种功能。 因为 **NavigableMap** 具有键顺序,所以它使用了 `firstEntry()` 和 `lastEntry()` 的概念。调用 `headMap()` 会生成一个 **NavigableMap** ,其中包含了从 **Map** 的开头到 `headMap()` 参数中所指向的一组元素,其中 **boolean** 值指示结果中是否包含该参数。调用 `tailMap()` 执行了类似的操作,只不过是从参数开始到 **Map** 的末尾。 `subMap()` 则允许生成 **Map** 中间的一部分。\n", + "\n", + "`ceilingEntry()` 从当前键值对向上搜索下一个键值对,`floorEntry()` 则是向下搜索。 `descendingMap()` 反转了 **NavigableMap** 的顺序。\n", + "\n", + "如果需要通过分割 **Map** 来简化所正在解决的问题,则 **NavigableMap** 可以做到。具有类似的功能的其它集合实现也可以用来帮助解决问题。\n", + "\n", + "\n", + "## 填充集合\n", + "\n", + "与 **Arrays** 一样,这里有一个名为 **Collections** 的伴随类(companion class),包含了一些 **static** 的实用方法,其中包括一个名为 `fill()` 的方法。 `fill()` 只复制整个集合中的单个对象引用。此外,它仅适用于 **List** 对象,但结果列表可以传递给构造方法或 `addAll()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/FillingLists.java\n", + "// Collections.fill() & Collections.nCopies()\n", + "import java.util.*;\n", + "\n", + "class StringAddress {\n", + " private String s;\n", + " StringAddress(String s) { this.s = s; }\n", + " @Override\n", + " public String toString() {\n", + " return super.toString() + \" \" + s;\n", + " }\n", + "}\n", + "\n", + "public class FillingLists {\n", + " public static void main(String[] args) {\n", + " List list = new ArrayList<>(\n", + " Collections.nCopies(4,\n", + " new StringAddress(\"Hello\")));\n", + " System.out.println(list);\n", + " Collections.fill(list,\n", + " new StringAddress(\"World!\"));\n", + " System.out.println(list);\n", + " }\n", + "}\n", + "/* Output:\n", + "[StringAddress@15db9742 Hello, StringAddress@15db9742\n", + "Hello, StringAddress@15db9742 Hello,\n", + "StringAddress@15db9742 Hello]\n", + "[StringAddress@6d06d69c World!, StringAddress@6d06d69c\n", + "World!, StringAddress@6d06d69c World!,\n", + "StringAddress@6d06d69c World!]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个示例展示了两种使用对单个对象的引用来填充 **Collection** 的方法。 第一个: `Collections.nCopies()` ,创建一个 **List**,并传递给 **ArrayList** 的构造方法,进而填充了 **ArrayList** 。\n", + "\n", + "**StringAddress** 中的 `toString()` 方法调用了 `Object.toString()` ,它先生成类名,后跟着对象的哈希码的无符号十六进制表示(哈希吗由 `hashCode()` 方法生成)。 输出显示所有的引用都指向同一个对象。调用第二个方法 `Collections.fill()` 后也是如此。 `fill()` 方法的用处非常有限,它只能替换 **List** 中已有的元素,而且不会添加新元素,\n", + "\n", + "### 使用 Suppliers 填充集合\n", + "\n", + "[第二十章 泛型]()章节中介绍的 **onjava.Suppliers** 类为填充集合提供了通用解决方案。 这是一个使用 **Suppliers** 初始化几种不同类型的 **Collection** 的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/SuppliersCollectionTest.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "\n", + "class Government implements Supplier {\n", + " static String[] foundation = (\n", + " \"strange women lying in ponds \" +\n", + " \"distributing swords is no basis \" +\n", + " \"for a system of government\").split(\" \");\n", + " private int index;\n", + " @Override\n", + " public String get() {\n", + " return foundation[index++];\n", + " }\n", + "}\n", + "\n", + "public class SuppliersCollectionTest {\n", + " public static void main(String[] args) {\n", + " // Suppliers class from the Generics chapter:\n", + " Set set = Suppliers.create(\n", + " LinkedHashSet::new, new Government(), 15);\n", + " System.out.println(set);\n", + " List list = Suppliers.create(\n", + " LinkedList::new, new Government(), 15);\n", + " System.out.println(list);\n", + " list = new ArrayList<>();\n", + " Suppliers.fill(list, new Government(), 15);\n", + " System.out.println(list);\n", + "\n", + " // Or we can use Streams:\n", + " set = Arrays.stream(Government.foundation)\n", + " .collect(Collectors.toSet());\n", + " System.out.println(set);\n", + " list = Arrays.stream(Government.foundation)\n", + " .collect(Collectors.toList());\n", + " System.out.println(list);\n", + " list = Arrays.stream(Government.foundation)\n", + " .collect(Collectors\n", + " .toCollection(LinkedList::new));\n", + " System.out.println(list);\n", + " set = Arrays.stream(Government.foundation)\n", + " .collect(Collectors\n", + " .toCollection(LinkedHashSet::new));\n", + " System.out.println(set);\n", + " }\n", + "}\n", + "/* Output:\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "[ponds, no, a, in, swords, for, is, basis, strange,\n", + "system, government, distributing, of, women, lying]\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "[strange, women, lying, in, ponds, distributing,\n", + "swords, is, no, basis, for, a, system, of, government]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**LinkedHashSet** 中的的元素按插入顺序排列,因为它维护一个链表来保存该顺序。\n", + "\n", + "但是请注意示例的第二部分:大多数情况下都可以使用 **Stream** 来创建和填充 **Collection** 。在本例中的 **Stream** 版本不需要声明 **Supplier** 所想要创建的元素数量;,它直接吸收了 **Stream** 中的所有元素。\n", + "\n", + "尽可能优先选择 **Stream** 来解决问题。\n", + "\n", + "### Map Suppliers\n", + "\n", + "使用 **Supplier** 来填充 **Map** 时需要一个 **Pair** 类,因为每次调用一个 **Supplier** 的 `get()` 方法时,都必须生成一对对象(一个键和一个值):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Pair.java\n", + "package onjava;\n", + "\n", + "public class Pair {\n", + " public final K key;\n", + " public final V value;\n", + " public Pair(K k, V v) {\n", + " key = k;\n", + " value = v;\n", + " }\n", + " public K key() { return key; }\n", + " public V value() { return value; }\n", + " public static Pair make(K k, V v) {\n", + " return new Pair(k, v);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pair** 是一个只读的 *数据传输对象* (Data Transfer Object)或 *信使* (Messenger)。 这与[第二十章 泛型]()章节中的 **Tuple2** 基本相同,但名字更适合 **Map** 初始化。我还添加了静态的 `make()` 方法,以便为创建 **Pair** 对象提供一个更简洁的名字。\n", + "\n", + "Java 8 的 **Stream** 提供了填充 **Map** 的便捷方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/StreamFillMaps.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "\n", + "class Letters\n", + "implements Supplier> {\n", + " private int number = 1;\n", + " private char letter = 'A';\n", + " @Override\n", + " public Pair get() {\n", + " return new Pair<>(number++, \"\" + letter++);\n", + " }\n", + "}\n", + "\n", + "public class StreamFillMaps {\n", + " public static void main(String[] args) {\n", + " Map m =\n", + " Stream.generate(new Letters())\n", + " .limit(11)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value));\n", + " System.out.println(m);\n", + "\n", + " // Two separate Suppliers:\n", + " Rand.String rs = new Rand.String(3);\n", + " Count.Character cc = new Count.Character();\n", + " Map mcs = Stream.generate(\n", + " () -> Pair.make(cc.get(), rs.get()))\n", + " .limit(8)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value));\n", + " System.out.println(mcs);\n", + "\n", + " // A key Supplier and a single value:\n", + " Map mcs2 = Stream.generate(\n", + " () -> Pair.make(cc.get(), \"Val\"))\n", + " .limit(8)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value));\n", + " System.out.println(mcs2);\n", + " }\n", + "}\n", + "/* Output:\n", + "{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J,\n", + "11=K}\n", + "{b=btp, c=enp, d=ccu, e=xsz, f=gvg, g=mei, h=nne,\n", + "i=elo}\n", + "{p=Val, q=Val, j=Val, k=Val, l=Val, m=Val, n=Val,\n", + "o=Val}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "上面的示例中出现了一个模式,可以使用它来创建一个自动创建和填充 **Map** 的工具:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/FillMap.java\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class FillMap {\n", + " public static Map\n", + " basic(Supplier> pairGen, int size) {\n", + " return Stream.generate(pairGen)\n", + " .limit(size)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value));\n", + " }\n", + " public static Map\n", + " basic(Supplier keyGen,\n", + " Supplier valueGen, int size) {\n", + " return Stream.generate(\n", + " () -> Pair.make(keyGen.get(), valueGen.get()))\n", + " .limit(size)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value));\n", + " }\n", + " public static >\n", + " M create(Supplier keyGen,\n", + " Supplier valueGen,\n", + " Supplier mapSupplier, int size) {\n", + " return Stream.generate( () ->\n", + " Pair.make(keyGen.get(), valueGen.get()))\n", + " .limit(size)\n", + " .collect(Collectors\n", + " .toMap(Pair::key, Pair::value,\n", + " (k, v) -> k, mapSupplier));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "basic() 方法生成一个默认的 **Map** ,而 `create()` 方法允许指定一个确切的 **Map** 类型,并返回那个确切的类型。\n", + "\n", + "下面是一个测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/FillMapTest.java\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.stream.*;\n", + "import onjava.*;\n", + "\n", + "public class FillMapTest {\n", + " public static void main(String[] args) {\n", + " Map mcs = FillMap.basic(\n", + " new Rand.String(4), new Count.Integer(), 7);\n", + " System.out.println(mcs);\n", + " HashMap hashm =\n", + " FillMap.create(new Rand.String(4),\n", + " new Count.Integer(), HashMap::new, 7);\n", + " System.out.println(hashm);\n", + " LinkedHashMap linkm =\n", + " FillMap.create(new Rand.String(4),\n", + " new Count.Integer(), LinkedHashMap::new, 7);\n", + " System.out.println(linkm);\n", + " }\n", + "}\n", + "/* Output:\n", + "{npcc=1, ztdv=6, gvgm=3, btpe=0, einn=4, eelo=5,\n", + "uxsz=2}\n", + "{npcc=1, ztdv=6, gvgm=3, btpe=0, einn=4, eelo=5,\n", + "uxsz=2}\n", + "{btpe=0, npcc=1, uxsz=2, gvgm=3, einn=4, eelo=5,\n", + "ztdv=6}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## 使用享元(Flyweight)自定义Collection和Map\n", + "\n", + "本节介绍如何创建自定义 **Collection** 和 **Map** 实现。每个 **java.util** 中的集合都有自己的 **Abstract** 类,它提供了该集合的部分实现,因此只需要实现必要的方法来生成所需的集合。你将看到通过继承 **java.util.Abstract** 类来创建自定义 **Map** 和 **Collection** 是多么简单。例如,要创建一个只读的 **Set** ,则可以从 **AbstractSet** 继承并实现 `iterator()` 和 `size()` 。最后一个示例是生成测试数据的另一种方法。生成的集合通常是只读的,并且所提供的方法最少。\n", + "\n", + "该解决方案还演示了 *享元* (Flyweight)设计模式。当普通解决方案需要太多对象时,或者当生成普通对象占用太多空间时,可以使用享元。享元设计模式将对象的一部分外部化(externalizes)。相比于把对象的所有内容都包含在对象中,这样做使得对象的部分或者全部可以在更有效的外部表中查找,或通过一些节省空间的其他计算生成。\n", + "\n", + "下面是一个可以是任何大小的 **List** ,并且(有效地)使用 **Integer** 数据进行预初始化。要从 **AbstractList** 创建只读 **List** ,必须实现 `get()` 和 `size()`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/CountingIntegerList.java\n", + "// List of any length, containing sample data\n", + "// {java onjava.CountingIntegerList}\n", + "package onjava;\n", + "import java.util.*;\n", + "\n", + "public class CountingIntegerList\n", + "extends AbstractList {\n", + " private int size;\n", + " public CountingIntegerList() { size = 0; }\n", + " public CountingIntegerList(int size) {\n", + " this.size = size < 0 ? 0 : size;\n", + " }\n", + " @Override\n", + " public Integer get(int index) {\n", + " return index;\n", + " }\n", + " @Override\n", + " public int size() { return size; }\n", + " public static void main(String[] args) {\n", + " List cil =\n", + " new CountingIntegerList(30);\n", + " System.out.println(cil);\n", + " System.out.println(cil.get(500));\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", + "16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]\n", + "500\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只有当想要限制 **List** 的长度时, **size** 值才是重要的,就像在主方法中那样。即使在这种情况下, `get()` 也会产生任何值。\n", + "\n", + "这个类是享元模式的一个简洁的例子。当需要的时候, `get()` “计算”所需的值,因此没必要存储和初始化实际的底层 **List** 结构。\n", + "\n", + "在大多数程序中,这里所保存的存储结构永远都不会改变。但是,它允许用非常大的 **index** 来调用 `List.get()` ,而 **List** 并不需要填充到这么大。此外,还可以在程序中大量使用 **CountingIntegerLists** 而无需担心存储问题。实际上,享元的一个好处是它允许使用更好的抽象而不用担心资源。\n", + "\n", + "可以使用享元设计模式来实现具有任何大小数据集的其他“初始化”自定义集合。下面是一个 **Map** ,它为每一个 **Integer** 键产生唯一的值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/CountMap.java\n", + "// Unlimited-length Map containing sample data\n", + "// {java onjava.CountMap}\n", + "package onjava;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class CountMap\n", + "extends AbstractMap {\n", + " private int size;\n", + " private static char[] chars =\n", + " \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", + " private static String value(int key) {\n", + " return\n", + " chars[key % chars.length] +\n", + " Integer.toString(key / chars.length);\n", + " }\n", + " public CountMap(int size) {\n", + " this.size = size < 0 ? 0 : size;\n", + " }\n", + " @Override\n", + " public String get(Object key) {\n", + " return value((Integer)key);\n", + " }\n", + " private static class Entry\n", + " implements Map.Entry {\n", + " int index;\n", + " Entry(int index) { this.index = index; }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof Entry &&\n", + " Objects.equals(index, ((Entry)o).index);\n", + " }\n", + " @Override\n", + " public Integer getKey() { return index; }\n", + " @Override\n", + " public String getValue() {\n", + " return value(index);\n", + " }\n", + " @Override\n", + " public String setValue(String value) {\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hashCode(index);\n", + " }\n", + " }\n", + " @Override\n", + " public Set> entrySet() {\n", + " // LinkedHashSet retains initialization order:\n", + " return IntStream.range(0, size)\n", + " .mapToObj(Entry::new)\n", + " .collect(Collectors\n", + " .toCollection(LinkedHashSet::new));\n", + " }\n", + " public static void main(String[] args) {\n", + " final int size = 6;\n", + " CountMap cm = new CountMap(60);\n", + " System.out.println(cm);\n", + " System.out.println(cm.get(500));\n", + " cm.values().stream()\n", + " .limit(size)\n", + " .forEach(System.out::println);\n", + " System.out.println();\n", + " new Random(47).ints(size, 0, 1000)\n", + " .mapToObj(cm::get)\n", + " .forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", + "9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0,\n", + "17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0,\n", + "25=Z0, 26=A1, 27=B1, 28=C1, 29=D1, 30=E1, 31=F1, 32=G1,\n", + "33=H1, 34=I1, 35=J1, 36=K1, 37=L1, 38=M1, 39=N1, 40=O1,\n", + "41=P1, 42=Q1, 43=R1, 44=S1, 45=T1, 46=U1, 47=V1, 48=W1,\n", + "49=X1, 50=Y1, 51=Z1, 52=A2, 53=B2, 54=C2, 55=D2, 56=E2,\n", + "57=F2, 58=G2, 59=H2}\n", + "G19\n", + "A0\n", + "B0\n", + "C0\n", + "D0\n", + "E0\n", + "F0\n", + "\n", + "Y9\n", + "J21\n", + "R26\n", + "D33\n", + "Z36\n", + "N16\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要创建一个只读的 **Map** ,则从 **AbstractMap** 继承并实现 `entrySet()` 。私有的 `value()` 方法计算任何键的值,并在 `get()` 和 `Entry.getValue()` 中使用。可以忽略 **CountMap** 的大小。\n", + "\n", + "这里是使用了 **LinkedHashSet** 而不是创建自定义 **Set** 类,因此并未完全实现享元。只有在调用 `entrySet()` 时才会生成此对象。\n", + "\n", + "现在创建一个更复杂的享元。这个示例中的数据集是世界各国及其首都的 **Map** 。 `capitals()` 方法生成一个国家和首都的 **Map** 。 `names()` 方法生成一个由国家名字组成的 **List** 。 当给定了表示所需大小的 **int** 参数时,两种方法都生成对应大小的列表片段:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/Countries.java\n", + "// \"Flyweight\" Maps and Lists of sample data\n", + "// {java onjava.Countries}\n", + "package onjava;\n", + "import java.util.*;\n", + "\n", + "public class Countries {\n", + " public static final String[][] DATA = {\n", + " // Africa\n", + " {\"ALGERIA\",\"Algiers\"},\n", + " {\"ANGOLA\",\"Luanda\"},\n", + " {\"BENIN\",\"Porto-Novo\"},\n", + " {\"BOTSWANA\",\"Gaberone\"},\n", + " {\"BURKINA FASO\",\"Ouagadougou\"},\n", + " {\"BURUNDI\",\"Bujumbura\"},\n", + " {\"CAMEROON\",\"Yaounde\"},\n", + " {\"CAPE VERDE\",\"Praia\"},\n", + " {\"CENTRAL AFRICAN REPUBLIC\",\"Bangui\"},\n", + " {\"CHAD\",\"N'djamena\"},\n", + " {\"COMOROS\",\"Moroni\"},\n", + " {\"CONGO\",\"Brazzaville\"},\n", + " {\"DJIBOUTI\",\"Dijibouti\"},\n", + " {\"EGYPT\",\"Cairo\"},\n", + " {\"EQUATORIAL GUINEA\",\"Malabo\"},\n", + " {\"ERITREA\",\"Asmara\"},\n", + " {\"ETHIOPIA\",\"Addis Ababa\"},\n", + " {\"GABON\",\"Libreville\"},\n", + " {\"THE GAMBIA\",\"Banjul\"},\n", + " {\"GHANA\",\"Accra\"},\n", + " {\"GUINEA\",\"Conakry\"},\n", + " {\"BISSAU\",\"Bissau\"},\n", + " {\"COTE D'IVOIR (IVORY COAST)\",\"Yamoussoukro\"},\n", + " {\"KENYA\",\"Nairobi\"},\n", + " {\"LESOTHO\",\"Maseru\"},\n", + " {\"LIBERIA\",\"Monrovia\"},\n", + " {\"LIBYA\",\"Tripoli\"},\n", + " {\"MADAGASCAR\",\"Antananarivo\"},\n", + " {\"MALAWI\",\"Lilongwe\"},\n", + " {\"MALI\",\"Bamako\"},\n", + " {\"MAURITANIA\",\"Nouakchott\"},\n", + " {\"MAURITIUS\",\"Port Louis\"},\n", + " {\"MOROCCO\",\"Rabat\"},\n", + " {\"MOZAMBIQUE\",\"Maputo\"},\n", + " {\"NAMIBIA\",\"Windhoek\"},\n", + " {\"NIGER\",\"Niamey\"},\n", + " {\"NIGERIA\",\"Abuja\"},\n", + " {\"RWANDA\",\"Kigali\"},\n", + " {\"SAO TOME E PRINCIPE\",\"Sao Tome\"},\n", + " {\"SENEGAL\",\"Dakar\"},\n", + " {\"SEYCHELLES\",\"Victoria\"},\n", + " {\"SIERRA LEONE\",\"Freetown\"},\n", + " {\"SOMALIA\",\"Mogadishu\"},\n", + " {\"SOUTH AFRICA\",\"Pretoria/Cape Town\"},\n", + " {\"SUDAN\",\"Khartoum\"},\n", + " {\"SWAZILAND\",\"Mbabane\"},\n", + " {\"TANZANIA\",\"Dodoma\"},\n", + " {\"TOGO\",\"Lome\"},\n", + " {\"TUNISIA\",\"Tunis\"},\n", + " {\"UGANDA\",\"Kampala\"},\n", + " {\"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)\",\n", + " \"Kinshasa\"},\n", + " {\"ZAMBIA\",\"Lusaka\"},\n", + " {\"ZIMBABWE\",\"Harare\"},\n", + " // Asia\n", + " {\"AFGHANISTAN\",\"Kabul\"},\n", + " {\"BAHRAIN\",\"Manama\"},\n", + " {\"BANGLADESH\",\"Dhaka\"},\n", + " {\"BHUTAN\",\"Thimphu\"},\n", + " {\"BRUNEI\",\"Bandar Seri Begawan\"},\n", + " {\"CAMBODIA\",\"Phnom Penh\"},\n", + " {\"CHINA\",\"Beijing\"},\n", + " {\"CYPRUS\",\"Nicosia\"},\n", + " {\"INDIA\",\"New Delhi\"},\n", + " {\"INDONESIA\",\"Jakarta\"},\n", + " {\"IRAN\",\"Tehran\"},\n", + " {\"IRAQ\",\"Baghdad\"},\n", + " {\"ISRAEL\",\"Jerusalem\"},\n", + " {\"JAPAN\",\"Tokyo\"},\n", + " {\"JORDAN\",\"Amman\"},\n", + " {\"KUWAIT\",\"Kuwait City\"},\n", + " {\"LAOS\",\"Vientiane\"},\n", + " {\"LEBANON\",\"Beirut\"},\n", + " {\"MALAYSIA\",\"Kuala Lumpur\"},\n", + " {\"THE MALDIVES\",\"Male\"},\n", + " {\"MONGOLIA\",\"Ulan Bator\"},\n", + " {\"MYANMAR (BURMA)\",\"Rangoon\"},\n", + " {\"NEPAL\",\"Katmandu\"},\n", + " {\"NORTH KOREA\",\"P'yongyang\"},\n", + " {\"OMAN\",\"Muscat\"},\n", + " {\"PAKISTAN\",\"Islamabad\"},\n", + " {\"PHILIPPINES\",\"Manila\"},\n", + " {\"QATAR\",\"Doha\"},\n", + " {\"SAUDI ARABIA\",\"Riyadh\"},\n", + " {\"SINGAPORE\",\"Singapore\"},\n", + " {\"SOUTH KOREA\",\"Seoul\"},\n", + " {\"SRI LANKA\",\"Colombo\"},\n", + " {\"SYRIA\",\"Damascus\"},\n", + " {\"TAIWAN (REPUBLIC OF CHINA)\",\"Taipei\"},\n", + " {\"THAILAND\",\"Bangkok\"},\n", + " {\"TURKEY\",\"Ankara\"},\n", + " {\"UNITED ARAB EMIRATES\",\"Abu Dhabi\"},\n", + " {\"VIETNAM\",\"Hanoi\"},\n", + " {\"YEMEN\",\"Sana'a\"},\n", + " // Australia and Oceania\n", + " {\"AUSTRALIA\",\"Canberra\"},\n", + " {\"FIJI\",\"Suva\"},\n", + " {\"KIRIBATI\",\"Bairiki\"},\n", + " {\"MARSHALL ISLANDS\",\"Dalap-Uliga-Darrit\"},\n", + " {\"MICRONESIA\",\"Palikir\"},\n", + " {\"NAURU\",\"Yaren\"},\n", + " {\"NEW ZEALAND\",\"Wellington\"},\n", + " {\"PALAU\",\"Koror\"},\n", + " {\"PAPUA NEW GUINEA\",\"Port Moresby\"},\n", + " {\"SOLOMON ISLANDS\",\"Honaira\"},\n", + " {\"TONGA\",\"Nuku'alofa\"},\n", + " {\"TUVALU\",\"Fongafale\"},\n", + " {\"VANUATU\",\"Port Vila\"},\n", + " {\"WESTERN SAMOA\",\"Apia\"},\n", + " // Eastern Europe and former USSR\n", + " {\"ARMENIA\",\"Yerevan\"},\n", + " {\"AZERBAIJAN\",\"Baku\"},\n", + " {\"BELARUS (BYELORUSSIA)\",\"Minsk\"},\n", + " {\"BULGARIA\",\"Sofia\"},\n", + " {\"GEORGIA\",\"Tbilisi\"},\n", + " {\"KAZAKSTAN\",\"Almaty\"},\n", + " {\"KYRGYZSTAN\",\"Alma-Ata\"},\n", + " {\"MOLDOVA\",\"Chisinau\"},\n", + " {\"RUSSIA\",\"Moscow\"},\n", + " {\"TAJIKISTAN\",\"Dushanbe\"},\n", + " {\"TURKMENISTAN\",\"Ashkabad\"},\n", + " {\"UKRAINE\",\"Kyiv\"},\n", + " {\"UZBEKISTAN\",\"Tashkent\"},\n", + " // Europe\n", + " {\"ALBANIA\",\"Tirana\"},\n", + " {\"ANDORRA\",\"Andorra la Vella\"},\n", + " {\"AUSTRIA\",\"Vienna\"},\n", + " {\"BELGIUM\",\"Brussels\"},\n", + " {\"BOSNIA-HERZEGOVINA\",\"Sarajevo\"},\n", + " {\"CROATIA\",\"Zagreb\"},\n", + " {\"CZECH REPUBLIC\",\"Prague\"},\n", + " {\"DENMARK\",\"Copenhagen\"},\n", + " {\"ESTONIA\",\"Tallinn\"},\n", + " {\"FINLAND\",\"Helsinki\"},\n", + " {\"FRANCE\",\"Paris\"},\n", + " {\"GERMANY\",\"Berlin\"},\n", + " {\"GREECE\",\"Athens\"},\n", + " {\"HUNGARY\",\"Budapest\"},\n", + " {\"ICELAND\",\"Reykjavik\"},\n", + " {\"IRELAND\",\"Dublin\"},\n", + " {\"ITALY\",\"Rome\"},\n", + " {\"LATVIA\",\"Riga\"},\n", + " {\"LIECHTENSTEIN\",\"Vaduz\"},\n", + " {\"LITHUANIA\",\"Vilnius\"},\n", + " {\"LUXEMBOURG\",\"Luxembourg\"},\n", + " {\"MACEDONIA\",\"Skopje\"},\n", + " {\"MALTA\",\"Valletta\"},\n", + " {\"MONACO\",\"Monaco\"},\n", + " {\"MONTENEGRO\",\"Podgorica\"},\n", + " {\"THE NETHERLANDS\",\"Amsterdam\"},\n", + " {\"NORWAY\",\"Oslo\"},\n", + " {\"POLAND\",\"Warsaw\"},\n", + " {\"PORTUGAL\",\"Lisbon\"},\n", + " {\"ROMANIA\",\"Bucharest\"},\n", + " {\"SAN MARINO\",\"San Marino\"},\n", + " {\"SERBIA\",\"Belgrade\"},\n", + " {\"SLOVAKIA\",\"Bratislava\"},\n", + " {\"SLOVENIA\",\"Ljuijana\"},\n", + " {\"SPAIN\",\"Madrid\"},\n", + " {\"SWEDEN\",\"Stockholm\"},\n", + " {\"SWITZERLAND\",\"Berne\"},\n", + " {\"UNITED KINGDOM\",\"London\"},\n", + " {\"VATICAN CITY\",\"Vatican City\"},\n", + " // North and Central America\n", + " {\"ANTIGUA AND BARBUDA\",\"Saint John's\"},\n", + " {\"BAHAMAS\",\"Nassau\"},\n", + " {\"BARBADOS\",\"Bridgetown\"},\n", + " {\"BELIZE\",\"Belmopan\"},\n", + " {\"CANADA\",\"Ottawa\"},\n", + " {\"COSTA RICA\",\"San Jose\"},\n", + " {\"CUBA\",\"Havana\"},\n", + " {\"DOMINICA\",\"Roseau\"},\n", + " {\"DOMINICAN REPUBLIC\",\"Santo Domingo\"},\n", + " {\"EL SALVADOR\",\"San Salvador\"},\n", + " {\"GRENADA\",\"Saint George's\"},\n", + " {\"GUATEMALA\",\"Guatemala City\"},\n", + " {\"HAITI\",\"Port-au-Prince\"},\n", + " {\"HONDURAS\",\"Tegucigalpa\"},\n", + " {\"JAMAICA\",\"Kingston\"},\n", + " {\"MEXICO\",\"Mexico City\"},\n", + " {\"NICARAGUA\",\"Managua\"},\n", + " {\"PANAMA\",\"Panama City\"},\n", + " {\"ST. KITTS AND NEVIS\",\"Basseterre\"},\n", + " {\"ST. LUCIA\",\"Castries\"},\n", + " {\"ST. VINCENT AND THE GRENADINES\",\"Kingstown\"},\n", + " {\"UNITED STATES OF AMERICA\",\"Washington, D.C.\"},\n", + " // South America\n", + " {\"ARGENTINA\",\"Buenos Aires\"},\n", + " {\"BOLIVIA\",\"Sucre (legal)/La Paz(administrative)\"},\n", + " {\"BRAZIL\",\"Brasilia\"},\n", + " {\"CHILE\",\"Santiago\"},\n", + " {\"COLOMBIA\",\"Bogota\"},\n", + " {\"ECUADOR\",\"Quito\"},\n", + " {\"GUYANA\",\"Georgetown\"},\n", + " {\"PARAGUAY\",\"Asuncion\"},\n", + " {\"PERU\",\"Lima\"},\n", + " {\"SURINAME\",\"Paramaribo\"},\n", + " {\"TRINIDAD AND TOBAGO\",\"Port of Spain\"},\n", + " {\"URUGUAY\",\"Montevideo\"},\n", + " {\"VENEZUELA\",\"Caracas\"},\n", + " };\n", + " // Use AbstractMap by implementing entrySet()\n", + " private static class FlyweightMap\n", + " extends AbstractMap {\n", + " private static class Entry\n", + " implements Map.Entry {\n", + " int index;\n", + " Entry(int index) { this.index = index; }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof FlyweightMap &&\n", + " Objects.equals(DATA[index][0], o);\n", + " }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hashCode(DATA[index][0]);\n", + " }\n", + " @Override\n", + " public String getKey() { return DATA[index][0]; }\n", + " @Override\n", + " public String getValue() {\n", + " return DATA[index][1];\n", + " }\n", + " @Override\n", + " public String setValue(String value) {\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " }\n", + " // Implement size() & iterator() for AbstractSet:\n", + " static class EntrySet\n", + " extends AbstractSet> {\n", + " private int size;\n", + " EntrySet(int size) {\n", + " if(size < 0)\n", + " this.size = 0;\n", + " // Can't be any bigger than the array:\n", + " else if(size > DATA.length)\n", + " this.size = DATA.length;\n", + " else\n", + " this.size = size;\n", + " }\n", + " @Override\n", + " public int size() { return size; }\n", + " private class Iter\n", + " implements Iterator> {\n", + " // Only one Entry object per Iterator:\n", + " private Entry entry = new Entry(-1);\n", + " @Override\n", + " public boolean hasNext() {\n", + " return entry.index < size - 1;\n", + " }\n", + " @Override\n", + " public Map.Entry next() {\n", + " entry.index++;\n", + " return entry;\n", + " }\n", + " @Override\n", + " public void remove() {\n", + " throw new UnsupportedOperationException();\n", + " }\n", + " }\n", + " @Override\n", + " public\n", + " Iterator> iterator() {\n", + " return new Iter();\n", + " }\n", + " }\n", + " private static\n", + " Set> entries =\n", + " new EntrySet(DATA.length);\n", + " @Override\n", + " public Set> entrySet() {\n", + " return entries;\n", + " }\n", + " }\n", + " // Create a partial map of 'size' countries:\n", + " static Map select(final int size) {\n", + " return new FlyweightMap() {\n", + " @Override\n", + " public Set> entrySet() {\n", + " return new EntrySet(size);\n", + " }\n", + " };\n", + " }\n", + " static Map map = new FlyweightMap();\n", + " public static Map capitals() {\n", + " return map; // The entire map\n", + " }\n", + " public static Map capitals(int size) {\n", + " return select(size); // A partial map\n", + " }\n", + " static List names =\n", + " new ArrayList<>(map.keySet());\n", + " // All the names:\n", + " public static List names() { return names; }\n", + " // A partial list:\n", + " public static List names(int size) {\n", + " return new ArrayList<>(select(size).keySet());\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(capitals(10));\n", + " System.out.println(names(10));\n", + " System.out.println(new HashMap<>(capitals(3)));\n", + " System.out.println(\n", + " new LinkedHashMap<>(capitals(3)));\n", + " System.out.println(new TreeMap<>(capitals(3)));\n", + " System.out.println(new Hashtable<>(capitals(3)));\n", + " System.out.println(new HashSet<>(names(6)));\n", + " System.out.println(new LinkedHashSet<>(names(6)));\n", + " System.out.println(new TreeSet<>(names(6)));\n", + " System.out.println(new ArrayList<>(names(6)));\n", + " System.out.println(new LinkedList<>(names(6)));\n", + " System.out.println(capitals().get(\"BRAZIL\"));\n", + " }\n", + "}\n", + "/* Output:\n", + "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo,\n", + "BOTSWANA=Gaberone, BURKINA FASO=Ouagadougou,\n", + "BURUNDI=Bujumbura, CAMEROON=Yaounde, CAPE VERDE=Praia,\n", + "CENTRAL AFRICAN REPUBLIC=Bangui, CHAD=N'djamena}\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN\n", + "REPUBLIC, CHAD]\n", + "{BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}\n", + "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", + "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", + "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", + "[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,\n", + "BURUNDI]\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI]\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI]\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI]\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI]\n", + "Brasilia\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "二维数组 **String DATA** 是 **public** 的,因此可以在别处使用。 **FlyweightMap** 必须实现 `entrySet()` 方法,该方法需要一个自定义 **Set** 实现和一个自定义 **Map.Entry** 类。这是实现享元的另一种方法:每个 **Map.Entry** 对象存储它自身的索引,而不是实际的键和值。当调用 `getKey()` 或 `getValue()` 时,它使用索引返回相应的 **DATA** 元素。 **EntrySet** 确保它的 **size** 不大于 **DATA** 。\n", + "\n", + "享元的另一部分在 **EntrySet.Iterator** 中实现。相比于为 **DATA** 中的每个数据对创建一个 **Map.Entry** 对象,这里每个迭代器只有一个 **Map.Entry** 对象。 **Entry** 对象作为数据的窗口,它只包含 **String** 静态数组的索引。每次为迭代器调用 `next()` 时,**Entry** 中的索引都会递增,因此它会指向下一个数据对,然后从 `next()` 返回 **Iterators** 的单个 **Entry** 对象。[^1]\n", + "\n", + "`select()` 方法生成一个包含所需大小的 **EntrySet** 的 **FlyweightMap** ,这用于在主方法中演示的重载的 `capitals()` 和 `names()` 方法。\n", + "\n", + "\n", + "## 集合功能\n", + "\n", + "下面这个表格展示了可以对 **Collection** 执行的所有操作(不包括自动继承自 **Object** 的方法),因此,可以用 **List** , **Set** , **Queue** 或 **Deque** 执行这里的所有操作(这些接口可能也提供了一些其他的功能)。**Map** 不是从 **Collection** 继承的,所以要单独处理它。\n", + "\n", + "| 方法名 | 描述 |\n", + "| :---: | :--- |\n", + "| **boolean add(T)** | 确保集合包含该泛型类型 **T** 的参数。如果不添加参数,则返回 **false** 。 (这是一种“可选”方法,将在下一节中介绍。) |\n", + "| **boolean addAll(Collection\\)** | 添加参数集合中的所有元素。只要有元素被成功添加则返回 **true**。(“可选的”) |\n", + "| **void clear()** | 删除集合中的所有元素。(“可选的”) |\n", + "| **boolean contains(T)** | 如果目标集合包含该泛型类型 **T** 的参数,则返回 **true** 。 |\n", + "| **boolean containsAll(Collection\\)** | 如果目标集合包含参数集合中的所有元素,则返回 **true** |\n", + "| **boolean isEmpty()** | 如果集合为空,则返回 **true** |\n", + "| **Iterator\\ iterator() Spliterator\\ spliterator()** | 返回一个迭代器来遍历集合中的元素。 **Spliterators** 更复杂一些,它用在并发场景 |\n", + "| **boolean remove(Object)** | 如果目标集合包含该参数,则在集合中删除该参数,如果成功删除则返回 **true** 。(“可选的”) |\n", + "| **boolean removeAll(Collection\\)** | 删除目标集合中,参数集合所包含的全部元素。如果有元素被成功删除则返回 **true** 。 (“可选的”) |\n", + "| **boolean removeIf(Predicate\\)** | 删除此集合中,满足给定断言(predicate)的所有元素 |\n", + "| **Stream\\ stream() Stream\\ parallelStream()** | 返回由该 **Collection** 中元素所组成的一个 **Stream** |\n", + "| **int size()** | 返回集合中所包含元素的个数 |\n", + "| **Object[] toArrat()** | 返回包含该集合所有元素的一个数组 |\n", + "| **\\ T[] toArray(T[] a)** | 返回包含该集合所有元素的一个数组。结果的运行时类型是参数数组而不是普通的 **Object** 数组。 |\n", + "\n", + "这里没有提供用于随机访问元素的 **get()** 方法,因为 **Collection** 还包含 **Set** ,它维护自己的内部排序,所以随机访问查找就没有意义了。因此,要查找 **Collection** 中的元素必须使用迭代器。\n", + "\n", + "下面这个示例演示了 **Collection** 的所有方法。这里以 **ArrayList** 为例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/CollectionMethods.java\n", + "// Things you can do with all Collections\n", + "import java.util.*;\n", + "import static onjava.HTMLColors.*;\n", + "\n", + "public class CollectionMethods {\n", + " public static void main(String[] args) {\n", + " Collection c =\n", + " new ArrayList<>(LIST.subList(0, 4));\n", + " c.add(\"ten\");\n", + " c.add(\"eleven\");\n", + " show(c);\n", + " border();\n", + " // Make an array from the List:\n", + " Object[] array = c.toArray();\n", + " // Make a String array from the List:\n", + " String[] str = c.toArray(new String[0]);\n", + " // Find max and min elements; this means\n", + " // different things depending on the way\n", + " // the Comparable interface is implemented:\n", + " System.out.println(\n", + " \"Collections.max(c) = \" + Collections.max(c));\n", + " System.out.println(\n", + " \"Collections.min(c) = \" + Collections.min(c));\n", + " border();\n", + " // Add a Collection to another Collection\n", + " Collection c2 =\n", + " new ArrayList<>(LIST.subList(10, 14));\n", + " c.addAll(c2);\n", + " show(c);\n", + " border();\n", + " c.remove(LIST.get(0));\n", + " show(c);\n", + " border();\n", + " // Remove all components that are\n", + " // in the argument collection:\n", + " c.removeAll(c2);\n", + " show(c);\n", + " border();\n", + " c.addAll(c2);\n", + " show(c);\n", + " border();\n", + " // Is an element in this Collection?\n", + " String val = LIST.get(3);\n", + " System.out.println(\n", + " \"c.contains(\" + val + \") = \" + c.contains(val));\n", + " // Is a Collection in this Collection?\n", + " System.out.println(\n", + " \"c.containsAll(c2) = \" + c.containsAll(c2));\n", + " Collection c3 =\n", + " ((List)c).subList(3, 5);\n", + " // Keep all the elements that are in both\n", + " // c2 and c3 (an intersection of sets):\n", + " c2.retainAll(c3);\n", + " show(c2);\n", + " // Throw away all the elements\n", + " // in c2 that also appear in c3:\n", + " c2.removeAll(c3);\n", + " System.out.println(\n", + " \"c2.isEmpty() = \" + c2.isEmpty());\n", + " border();\n", + " // Functional operation:\n", + " c = new ArrayList<>(LIST);\n", + " c.removeIf(s -> !s.startsWith(\"P\"));\n", + " c.removeIf(s -> s.startsWith(\"Pale\"));\n", + " // Stream operation:\n", + " c.stream().forEach(System.out::println);\n", + " c.clear(); // Remove all elements\n", + " System.out.println(\"after c.clear():\" + c);\n", + " }\n", + "}\n", + "/* Output:\n", + "AliceBlue\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "ten\n", + "eleven\n", + "******************************\n", + "Collections.max(c) = ten\n", + "Collections.min(c) = AliceBlue\n", + "******************************\n", + "AliceBlue\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "ten\n", + "eleven\n", + "Brown\n", + "BurlyWood\n", + "CadetBlue\n", + "Chartreuse\n", + "******************************\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "ten\n", + "eleven\n", + "Brown\n", + "BurlyWood\n", + "CadetBlue\n", + "Chartreuse\n", + "******************************\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "ten\n", + "eleven\n", + "******************************\n", + "AntiqueWhite\n", + "Aquamarine\n", + "Azure\n", + "ten\n", + "eleven\n", + "Brown\n", + "BurlyWood\n", + "CadetBlue\n", + "Chartreuse\n", + "******************************\n", + "c.contains(Azure) = true\n", + "c.containsAll(c2) = true\n", + "c2.isEmpty() = true\n", + "******************************\n", + "PapayaWhip\n", + "PeachPuff\n", + "Peru\n", + "Pink\n", + "Plum\n", + "PowderBlue\n", + "Purple\n", + "after c.clear():[]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了只演示 **Collection** 接口的方法,而没有其它额外的内容,所以这里创建包含不同数据集的 **ArrayList** ,并向上转型为 **Collection** 对象。\n", + "\n", + "\n", + "## 可选操作\n", + "\n", + "在 **Collection** 接口中执行各种添加和删除操作的方法是 *可选操作* (optional operations)。这意味着实现类不需要为这些方法提供功能定义。\n", + "\n", + "这是一种非常不寻常的定义接口的方式。正如我们所知,接口是一种合约(contract)。它表达的意思是,“无论你如何选择实现这个接口,我保证你可以将这些消息发送到这个对象”(我在这里使用术语“接口”来描述正式的 **interface** 关键字和“任何类或子类都支持的方法”的更一般含义)。但“可选”操作违反了这一基本原则,它表示调用某些方法不会执行有意义的行为。相反,它们会抛出异常!这看起来似乎丢失了编译时的类型安全性。\n", + "\n", + "其实没那么糟糕。如果操作是可选的,编译器仍然能够限制你仅调用该接口中的方法。它不像动态语言那样,可以为任何对象调用任何方法,并在运行时查找特定的调用是否可行。[^2]此外,大多数将 **Collection** 作为参数的方法仅从该 **Collection** 中读取,并且 **Collection** 的所有“读取”方法都不是可选的。\n", + "\n", + "为什么要将方法定义为“可选”的?因为这样做可以防止设计中的接口爆炸。集合库的其他设计往往会产生令人困惑的过多接口来描述主题的每个变体。这甚至使得不可能捕获到接口中的所有特殊情况,因为总有人能发明一个新的接口。“不支持的操作(unsupported operation)”这种方式实现了Java集合库的一个重要目标:集合要易于学习和使用。不支持的操作是一种特殊情况,可以推迟到必要的时候。但是,要使用此方法:\n", + "\n", + "1. **UnsupportedOperationException** 必须是一个罕见的事件。也就是说,对于大多数类,所有操作都应该起作用,并且只有在特殊情况下才应该不支持某项操作。这在Java集合库中是正确的,因为99%的时间使用到的类 —— **ArrayList** , **LinkedList** , **HashSet** 和 **HashMap** ,以及其他具体实现,都支持所有操作。该设计确实为创建一个新的 **Collection** 提供了一个“后门”,可以不为 **Collection** 接口中的所有方法都提供有意义的定义,这些定义仍然适合现有的类库。\n", + "\n", + "2. 当不支持某个操作时, **UnsupportedOperationException** 应该出现在实现阶段,而不是在将产品发送给客户之后。毕竟,这个异常表示编程错误:错误地使用了一个具体实现。\n", + "\n", + "值得注意的是,不支持的操作只能在运行时检测到,因此这代表动态类型检查。如果你来自像 C++ 这样的静态类型语言,Java 可能看起来只是另一种静态类型语言。当然, Java 肯定有静态类型检查,但它也有大量的动态类型,因此很难说它只是静态语言或动态语言。一旦你开始注意到这一点,你就会开始看到 Java 中动态类型检查的其他示例。\n", + "\n", + "\n", + "### 不支持的操作\n", + "\n", + "不支持的操作的常见来源是由固定大小的数据结构所支持的集合。使用 `Arrays.asList()` 方法将数组转换为 **List** 时,就会得到这样的集合。此外,还可以选择使用 **Collections** 类中的“不可修改(unmodifiable)”方法使任何集合(包括 **Map** )抛出 **UnsupportedOperationException** 异常。此示例展示了这两种情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Unsupported.java\n", + "// Unsupported operations in Java collections\n", + "import java.util.*;\n", + "\n", + "public class Unsupported {\n", + " static void\n", + " check(String description, Runnable tst) {\n", + " try {\n", + " tst.run();\n", + " } catch(Exception e) {\n", + " System.out.println(description + \"(): \" + e);\n", + " }\n", + " }\n", + " static void test(String msg, List list) {\n", + " System.out.println(\"--- \" + msg + \" ---\");\n", + " Collection c = list;\n", + " Collection subList = list.subList(1,8);\n", + " // Copy of the sublist:\n", + " Collection c2 = new ArrayList<>(subList);\n", + " check(\"retainAll\", () -> c.retainAll(c2));\n", + " check(\"removeAll\", () -> c.removeAll(c2));\n", + " check(\"clear\", () -> c.clear());\n", + " check(\"add\", () -> c.add(\"X\"));\n", + " check(\"addAll\", () -> c.addAll(c2));\n", + " check(\"remove\", () -> c.remove(\"C\"));\n", + " // The List.set() method modifies the value but\n", + " // doesn't change the size of the data structure:\n", + " check(\"List.set\", () -> list.set(0, \"X\"));\n", + " }\n", + " public static void main(String[] args) {\n", + " List list = Arrays.asList(\n", + " \"A B C D E F G H I J K L\".split(\" \"));\n", + " test(\"Modifiable Copy\", new ArrayList<>(list));\n", + " test(\"Arrays.asList()\", list);\n", + " test(\"unmodifiableList()\",\n", + " Collections.unmodifiableList(\n", + " new ArrayList<>(list)));\n", + " }\n", + "}\n", + "/* Output:\n", + "--- Modifiable Copy ---\n", + "--- Arrays.asList() ---\n", + "retainAll(): java.lang.UnsupportedOperationException\n", + "removeAll(): java.lang.UnsupportedOperationException\n", + "clear(): java.lang.UnsupportedOperationException\n", + "add(): java.lang.UnsupportedOperationException\n", + "addAll(): java.lang.UnsupportedOperationException\n", + "remove(): java.lang.UnsupportedOperationException\n", + "--- unmodifiableList() ---\n", + "retainAll(): java.lang.UnsupportedOperationException\n", + "removeAll(): java.lang.UnsupportedOperationException\n", + "clear(): java.lang.UnsupportedOperationException\n", + "add(): java.lang.UnsupportedOperationException\n", + "addAll(): java.lang.UnsupportedOperationException\n", + "remove(): java.lang.UnsupportedOperationException\n", + "List.set(): java.lang.UnsupportedOperationException\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "因为 `Arrays.asList()` 生成的 **List** 由一个固定大小的数组所支持,所以唯一支持的操作是那些不改变数组大小的操作。任何会导致更改基础数据结构大小的方法都会产生 **UnsupportedOperationException** 异常,来说明这是对不支持的方法的调用(编程错误)。\n", + "\n", + "请注意,始终可以将 `Arrays.asList()` 的结果作为一个参数传递给任何 **Collection** 的构造方法(或使用 `addAll()` 方法或静态的 `Collections.addAll()` 方法)来创建一个允许使用所有方法的常规集合,在主方法中第一次调用 `test()` 时显示了这种情况。这种调用产生了一个新的可调整大小的底层数据结构。\n", + "\n", + "**Collections** 类中的“unmodifiable”方法会将集合包装一个代理中,如果执行任何想要修改集合的操作,则该代理会生成 **UnsupportedOperationException** 异常。使用这些方法的目的是生成一个“常量”集合对象。稍后将描述“unmodifiable“集合方法的完整列表。\n", + "\n", + "`test()` 中的最后一个 `check()` 用于测试**List** 的 `set()` 方法。这里,“不支持的操作”技术的粒度(granularity)就派上用场了,得到的“接口”可以通过一种方法在 `Arrays.asList()` 返回的对象和 `Collections.unmodifiableList()` 返回的对象之间变换。 `Arrays.asList()` 返回固定大小的 **List** ,而 `Collections.unmodifiableList()` 生成无法更改的 **List** 。如输出中所示, `Arrays.asList()` 返回的 **List** 中的元素是可以修改的,因为这不会违反该 **List** 的“固定大小”特性。但很明显, `unmodifiableList()` 的结果不应该以任何方式修改。如果使用接口来描述,则需要两个额外的接口,一个具有可用的 `set()` 方法,而另一个没有。 **Collection** 的各种不可修改的子类型都将需要额外的接口。\n", + "\n", + "如果一个方法将一个集合作为它的参数,那么它的文档应该说明必须实现哪些可选方法。\n", + "\n", + "\n", + "## Set和存储顺序\n", + "\n", + "[第十二章 集合]()章节中的 **Set** 有关示例对 **Set** 的基本操作做了很好的介绍。 但是,这些示例可以方便地使用预定义的 Java 类型,例如 **Integer** 和 **String** ,它们可以在集合中使用。在创建自己的类型时请注意, **Set** (以及稍后会看到的 **Map** )需要一种维护存储顺序的方法,该顺序因 **Set** 的不同实现而异。因此,不同的 **Set** 实现不仅具有不同的行为,而且它们对可以放入特定 **Set** 中的对象类型也有不同的要求:\n", + "\n", + "| **Set** 类型 | 约束 |\n", + "| :---: | :--- |\n", + "| **Set(interface)** | 添加到 **Set** 中的每个元素必须是唯一的,否则,**Set** 不会添加重复元素。添加到 **Set** 的元素必须至少定义 `equals()` 方法以建立对象唯一性。 **Set** 与 **Collection** 具有完全相同的接口。 **Set** 接口不保证它将以任何特定顺序维护其元素。 |\n", + "| **HashSet\\*** | 注重快速查找元素的集合,其中元素必须定义 `hashCode()` 和 `equals()` 方法。 |\n", + "| **TreeSet** | 由树支持的有序 **Set**。这样,就可以从 **Set** 中获取有序序列,其中元素必须实现 **Comparable** 接口。 |\n", + "| **LinkedHashSet** | 具有 **HashSet** 的查找速度,但在内部使用链表维护元素的插入顺序。因此,当在遍历 **Set** 时,结果将按元素的插入顺序显示。元素必须定义 `hashCode()` 和 `equals()` 方法。 |\n", + "\n", + "**HashSet** 上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。\n", + "\n", + "定义 `hashCode()` 方法在[附录:理解equals和hashCode方法]()中进行了描述。必须为散列和树存储结构创建 `equals()` 方法,但只有当把类放在 **HashSet** 中时才需要 `hashCode()` (当然这很有可能,因为 **HashSet** 通常应该是作为 **Set** 实现的首选)或 **LinkedHashSet** 。 但是,作为一种良好的编程风格,在覆盖 `equals()` 时应始终覆盖 `hashCode()` 。\n", + "\n", + "下面的示例演示了成功使用具有特定 **Set** 实现的类型所需的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/TypesForSets.java\n", + "// Methods necessary to put your own type in a Set\n", + "import java.util.*;\n", + "import java.util.function.*;\n", + "import java.util.Objects;\n", + "\n", + "class SetType {\n", + " protected int i;\n", + " SetType(int n) { i = n; }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof SetType &&\n", + " Objects.equals(i, ((SetType)o).i);\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return Integer.toString(i);\n", + " }\n", + "}\n", + "\n", + "class HashType extends SetType {\n", + " HashType(int n) { super(n); }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hashCode(i);\n", + " }\n", + "}\n", + "\n", + "class TreeType extends SetType\n", + "implements Comparable {\n", + " TreeType(int n) { super(n); }\n", + " @Override\n", + " public int compareTo(TreeType arg) {\n", + " return Integer.compare(arg.i, i);\n", + " // Equivalent to:\n", + " // return arg.i < i ? -1 : (arg.i == i ? 0 : 1);\n", + " }\n", + "}\n", + "\n", + "public class TypesForSets {\n", + " static void\n", + " fill(Set set, Function type) {\n", + " for(int i = 10; i >= 5; i--) // Descending\n", + " set.add(type.apply(i));\n", + " for(int i = 0; i < 5; i++) // Ascending\n", + " set.add(type.apply(i));\n", + " }\n", + " static void\n", + " test(Set set, Function type) {\n", + " fill(set, type);\n", + " fill(set, type); // Try to add duplicates\n", + " fill(set, type);\n", + " System.out.println(set);\n", + " }\n", + " public static void main(String[] args) {\n", + " test(new HashSet<>(), HashType::new);\n", + " test(new LinkedHashSet<>(), HashType::new);\n", + " test(new TreeSet<>(), TreeType::new);\n", + " // Things that don't work:\n", + " test(new HashSet<>(), SetType::new);\n", + " test(new HashSet<>(), TreeType::new);\n", + " test(new LinkedHashSet<>(), SetType::new);\n", + " test(new LinkedHashSet<>(), TreeType::new);\n", + " try {\n", + " test(new TreeSet<>(), SetType::new);\n", + " } catch(Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " try {\n", + " test(new TreeSet<>(), HashType::new);\n", + " } catch(Exception e) {\n", + " System.out.println(e.getMessage());\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", + "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", + "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]\n", + "[1, 6, 8, 6, 2, 7, 8, 9, 4, 10, 7, 5, 1, 3, 4, 9, 9,\n", + "10, 5, 3, 2, 0, 4, 1, 2, 0, 8, 3, 0, 10, 6, 5, 7]\n", + "[3, 1, 4, 8, 7, 6, 9, 5, 3, 0, 10, 5, 5, 10, 7, 8, 8,\n", + "9, 1, 4, 10, 2, 6, 9, 1, 6, 0, 3, 2, 0, 7, 2, 4]\n", + "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5,\n", + "0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", + "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5,\n", + "0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", + "SetType cannot be cast to java.lang.Comparable\n", + "HashType cannot be cast to java.lang.Comparable\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了证明特定 **Set** 需要哪些方法,同时避免代码重复,这里创建了三个类。基类 **SetType** 存储一个 **int** 值,并通过 `toString()` 方法打印它。由于存储在 **Set** 中的所有类都必须具有 `equals()` ,因此该方法也放在基类中。基于 `int i` 来判断元素是否相等。\n", + "\n", + "**HashType** 继承自 **SetType** ,并添加了 `hashCode()` 方法,该方法对于 **Set** 的散列实现是必需的。\n", + "\n", + "要在任何类型的有序集合中使用对象,由 **TreeType** 实现的 **Comparable** 接口都是必需的,例如 **SortedSet** ( **TreeSet** 是其唯一实现)。在 `compareTo()` 中,请注意我没有使用“简单明了”的形式: `return i-i2` 。虽然这是一个常见的编程错误,但只有当 **i** 和 **i2** 是“无符号(unsigned)”整型时才能正常工作(如果 Java 有一个“unsigned”关键字的话,不过它没有)。它破坏了 Java 的有符号 **int** ,它不足以代表两个有符号整数的差异。如果 **i** 是一个大的正整数而 **j** 是一个大的负整数, `i-j` 将溢出并返回一个负值,这不是我们所需要的。\n", + "\n", + "通常希望 `compareTo()` 方法生成与 `equals()` 方法一致的自然顺序。如果 `equals()` 对于特定比较产生 **true**,则 `compareTo()` 应该为该比较返回结果 零,并且如果 `equals()` 为比较产生 **false** ,则 `compareTo()` 应该为该比较产生非零结果。\n", + "\n", + "在 **TypesForSets** 中, `fill()` 和 `test()` 都是使用泛型定义的,以防止代码重复。为了验证 **Set** 的行为, `test()` 在测试集上调用 `fill()` 三次,尝试引入重复的对象。 `fill()` 方法的参数可以接收任意一个 **Set** 类型,以及生成该类型的 **Function** 对象。因为此示例中使用的所有对象都有一个带有单个 **int** 参数的构造方法,所以可以将构造方法作为此 **Function** 传递,它将提供用于填充 **Set** 的对象。\n", + "\n", + "请注意, `fill()` 方法按降序添加前五个元素,按升序添加后五个元素,以此来指出生成的存储顺序。输出显示 **HashSet** 按升序保留元素,但是,在[附录:理解equals和hashCode方法]()中,你会发现这只是偶然的,因为散列会创建自己的存储顺序。这里只是因为元素是一个简单的 **int** ,在这种情况下它是升序的。 **LinkedHashSet** 按照插入顺序保存元素,**TreeSet** 按排序顺序维护元素(在此示例中因为 `compareTo()` 的实现方式,所以元素按降序排列。)\n", + "\n", + "特定的 **Set** 类型一般都有所必需的操作,如果尝试使用没能正确支持这些操作的类型,那么事情就会出错。将没有重新定义 `hashCode()` 方法的 **SetType** 或 **TreeType** 对象放入任何散列实现会导致重复值,因此违反了 **Set** 的主要契约。 这是相当令人不安的,因为这甚至不产生运行时错误。但是,默认的 `hashCode()` 是合法的,所以即使它是不正确的,这也是合法的行为。确保此类程序正确性的唯一可靠方法是将单元测试合并到构建系统中。\n", + "\n", + "如果尝试在 **TreeSet** 中使用没有实现 **Comparable** 接口的类型,则会得到更明确的结果:当 **TreeSet** 尝试将对象用作一个 **Comparable** 时,将会抛出异常。\n", + "\n", + "\n", + "### SortedSet\n", + "\n", + "**SortedSet** 中的元素保证按排序规则顺序, **SortedSet** 接口中的以下方法可以产生其他功能:\n", + "\n", + "- `Comparator comparator()` :生成用于此 **Set** 的**Comparator** 或 **null** 来用于自然排序。\n", + "- `Object first()` :返回第一个元素。\n", + "- `Object last()` :返回最后一个元素。\n", + "- `SortedSet subSet(fromElement,toElement)` :使用 **fromElement** (包含)和 **toElement** (不包括)中的元素生成此 **Set** 的一个视图。\n", + "- `SortedSet headSet(toElement)` :使用顺序在 **toElement** 之前的元素生成此 **Set** 的一个视图。\n", + "- `SortedSet tailSet(fromElement)` :使用顺序在 **fromElement** 之后(包含 **fromElement** )的元素生成此 **Set** 的一个视图。\n", + "\n", + "下面是一个简单的演示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/SortedSetDemo.java\n", + "import java.util.*;\n", + "import static java.util.stream.Collectors.*;\n", + "\n", + "public class SortedSetDemo {\n", + " public static void main(String[] args) {\n", + " SortedSet sortedSet =\n", + " Arrays.stream(\n", + " \"one two three four five six seven eight\"\n", + " .split(\" \"))\n", + " .collect(toCollection(TreeSet::new));\n", + " System.out.println(sortedSet);\n", + " String low = sortedSet.first();\n", + " String high = sortedSet.last();\n", + " System.out.println(low);\n", + " System.out.println(high);\n", + " Iterator it = sortedSet.iterator();\n", + " for(int i = 0; i <= 6; i++) {\n", + " if(i == 3) low = it.next();\n", + " if(i == 6) high = it.next();\n", + " else it.next();\n", + " }\n", + " System.out.println(low);\n", + " System.out.println(high);\n", + " System.out.println(sortedSet.subSet(low, high));\n", + " System.out.println(sortedSet.headSet(high));\n", + " System.out.println(sortedSet.tailSet(low));\n", + " }\n", + "}\n", + "/* Output:\n", + "[eight, five, four, one, seven, six, three, two]\n", + "eight\n", + "two\n", + "one\n", + "two\n", + "[one, seven, six, three]\n", + "[eight, five, four, one, seven, six, three]\n", + "[one, seven, six, three, two]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意, **SortedSet** 表示“根据对象的比较函数进行排序”,而不是“根据插入顺序”。可以使用 **LinkedHashSet** 保留元素的插入顺序。\n", + "\n", + "\n", + "## 队列\n", + "\n", + "有许多 **Queue** 实现,其中大多数是为并发应用程序设计的。许多实现都是通过排序行为而不是性能来区分的。这是一个涉及大多数 **Queue** 实现的基本示例,包括基于并发的队列。队列将元素从一端放入并从另一端取出:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/QueueBehavior.java\n", + "// Compares basic behavior\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class QueueBehavior {\n", + " static Stream strings() {\n", + " return Arrays.stream(\n", + " (\"one two three four five six seven \" +\n", + " \"eight nine ten\").split(\" \"));\n", + " }\n", + " static void test(int id, Queue queue) {\n", + " System.out.print(id + \": \");\n", + " strings().map(queue::offer).count();\n", + " while(queue.peek() != null)\n", + " System.out.print(queue.remove() + \" \");\n", + " System.out.println();\n", + " }\n", + " public static void main(String[] args) {\n", + " int count = 10;\n", + " test(1, new LinkedList<>());\n", + " test(2, new PriorityQueue<>());\n", + " test(3, new ArrayBlockingQueue<>(count));\n", + " test(4, new ConcurrentLinkedQueue<>());\n", + " test(5, new LinkedBlockingQueue<>());\n", + " test(6, new PriorityBlockingQueue<>());\n", + " test(7, new ArrayDeque<>());\n", + " test(8, new ConcurrentLinkedDeque<>());\n", + " test(9, new LinkedBlockingDeque<>());\n", + " test(10, new LinkedTransferQueue<>());\n", + " test(11, new SynchronousQueue<>());\n", + " }\n", + "}\n", + "/* Output:\n", + "1: one two three four five six seven eight nine ten\n", + "2: eight five four nine one seven six ten three two\n", + "3: one two three four five six seven eight nine ten\n", + "4: one two three four five six seven eight nine ten\n", + "5: one two three four five six seven eight nine ten\n", + "6: eight five four nine one seven six ten three two\n", + "7: one two three four five six seven eight nine ten\n", + "8: one two three four five six seven eight nine ten\n", + "9: one two three four five six seven eight nine ten\n", + "10: one two three four five six seven eight nine ten\n", + "11:\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Deque** 接口也继承自 **Queue** 。 除优先级队列外,**Queue** 按照元素的插入顺序生成元素。 在此示例中,**SynchronousQueue** 不会产生任何结果,因为它是一个阻塞队列,其中每个插入操作必须等待另一个线程执行相应的删除操作,反之亦然。\n", + "\n", + "\n", + "### 优先级队列\n", + "\n", + "考虑一个待办事项列表,其中每个对象包含一个 **String** 以及主要和次要优先级值。通过实现 **Comparable** 接口来控制此待办事项列表的顺序:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/ToDoList.java\n", + "// A more complex use of PriorityQueue\n", + "import java.util.*;\n", + "\n", + "class ToDoItem implements Comparable {\n", + " private char primary;\n", + " private int secondary;\n", + " private String item;\n", + " ToDoItem(String td, char pri, int sec) {\n", + " primary = pri;\n", + " secondary = sec;\n", + " item = td;\n", + " }\n", + " @Override\n", + " public int compareTo(ToDoItem arg) {\n", + " if(primary > arg.primary)\n", + " return +1;\n", + " if(primary == arg.primary)\n", + " if(secondary > arg.secondary)\n", + " return +1;\n", + " else if(secondary == arg.secondary)\n", + " return 0;\n", + " return -1;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return Character.toString(primary) +\n", + " secondary + \": \" + item;\n", + " }\n", + "}\n", + "\n", + "class ToDoList {\n", + " public static void main(String[] args) {\n", + " PriorityQueue toDo =\n", + " new PriorityQueue<>();\n", + " toDo.add(new ToDoItem(\"Empty trash\", 'C', 4));\n", + " toDo.add(new ToDoItem(\"Feed dog\", 'A', 2));\n", + " toDo.add(new ToDoItem(\"Feed bird\", 'B', 7));\n", + " toDo.add(new ToDoItem(\"Mow lawn\", 'C', 3));\n", + " toDo.add(new ToDoItem(\"Water lawn\", 'A', 1));\n", + " toDo.add(new ToDoItem(\"Feed cat\", 'B', 1));\n", + " while(!toDo.isEmpty())\n", + " System.out.println(toDo.remove());\n", + " }\n", + "}\n", + "/* Output:\n", + "A1: Water lawn\n", + "A2: Feed dog\n", + "B1: Feed cat\n", + "B7: Feed bird\n", + "C3: Mow lawn\n", + "C4: Empty trash\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这展示了通过优先级队列自动排序待办事项。\n", + "\n", + "\n", + "### 双端队列\n", + "\n", + "**Deque** (双端队列)就像一个队列,但是可以从任一端添加和删除元素。 Java 6为 **Deque** 添加了一个显式接口。以下是对实现了 **Deque** 的类的最基本的 **Deque** 方法的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/SimpleDeques.java\n", + "// Very basic test of Deques\n", + "import java.util.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.function.*;\n", + "\n", + "class CountString implements Supplier {\n", + " private int n = 0;\n", + " CountString() {}\n", + " CountString(int start) { n = start; }\n", + " @Override\n", + " public String get() {\n", + " return Integer.toString(n++);\n", + " }\n", + "}\n", + "\n", + "public class SimpleDeques {\n", + " static void test(Deque deque) {\n", + " CountString s1 = new CountString(),\n", + " s2 = new CountString(20);\n", + " for(int n = 0; n < 8; n++) {\n", + " deque.offerFirst(s1.get());\n", + " deque.offerLast(s2.get()); // Same as offer()\n", + " }\n", + " System.out.println(deque);\n", + " String result = \"\";\n", + " while(deque.size() > 0) {\n", + " System.out.print(deque.peekFirst() + \" \");\n", + " result += deque.pollFirst() + \" \";\n", + " System.out.print(deque.peekLast() + \" \");\n", + " result += deque.pollLast() + \" \";\n", + " }\n", + " System.out.println(\"\\n\" + result);\n", + " }\n", + " public static void main(String[] args) {\n", + " int count = 10;\n", + " System.out.println(\"LinkedList\");\n", + " test(new LinkedList<>());\n", + " System.out.println(\"ArrayDeque\");\n", + " test(new ArrayDeque<>());\n", + " System.out.println(\"LinkedBlockingDeque\");\n", + " test(new LinkedBlockingDeque<>(count));\n", + " System.out.println(\"ConcurrentLinkedDeque\");\n", + " test(new ConcurrentLinkedDeque<>());\n", + " }\n", + "}\n", + "/* Output:\n", + "LinkedList\n", + "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", + "27]\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "ArrayDeque\n", + "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", + "27]\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "LinkedBlockingDeque\n", + "[4, 3, 2, 1, 0, 20, 21, 22, 23, 24]\n", + "4 24 3 23 2 22 1 21 0 20\n", + "4 24 3 23 2 22 1 21 0 20\n", + "ConcurrentLinkedDeque\n", + "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", + "27]\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我只使用了 **Deque** 方法的“offer”和“poll”版本,因为当 **LinkedBlockingDeque** 的大小有限时,这些方法不会抛出异常。请注意, **LinkedBlockingDeque** 仅填充到它的限制大小为止,然后忽略额外的添加。\n", + "\n", + "\n", + "## 理解Map\n", + "\n", + "正如在[第十二章 集合]()章节中所了解到的,**Map**(也称为 *关联数组* )维护键值关联(对),因此可以使用键来查找值。标准 Java 库包含不同的 **Map** 基本实现,例如 **HashMap** , **TreeMap** , **LinkedHashMap** , **WeakHashMap** , **ConcurrentHashMap** 和 **IdentityHashMap** 。 它们都具有相同的基本 **Map** 接口,但它们的行为不同,包括效率,键值对的保存顺序和呈现顺序,保存对象的时间,如何在多线程程序中工作,以及如何确定键的相等性。 **Map** 接口的实现数量应该告诉你一些关于此工具重要性的信息。\n", + "\n", + "为了更深入地了解 **Map** ,学习如何构造关联数组会很有帮助。下面是一个非常简单的实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/AssociativeArray.java\n", + "// Associates keys with values\n", + "\n", + "public class AssociativeArray {\n", + " private Object[][] pairs;\n", + " private int index;\n", + " public AssociativeArray(int length) {\n", + " pairs = new Object[length][2];\n", + " }\n", + " public void put(K key, V value) {\n", + " if(index >= pairs.length)\n", + " throw new ArrayIndexOutOfBoundsException();\n", + " pairs[index++] = new Object[]{ key, value };\n", + " }\n", + " @SuppressWarnings(\"unchecked\")\n", + " public V get(K key) {\n", + " for(int i = 0; i < index; i++)\n", + " if(key.equals(pairs[i][0]))\n", + " return (V)pairs[i][1];\n", + " return null; // Did not find key\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " StringBuilder result = new StringBuilder();\n", + " for(int i = 0; i < index; i++) {\n", + " result.append(pairs[i][0].toString());\n", + " result.append(\" : \");\n", + " result.append(pairs[i][1].toString());\n", + " if(i < index - 1)\n", + " result.append(\"\\n\");\n", + " }\n", + " return result.toString();\n", + " }\n", + " public static void main(String[] args) {\n", + " AssociativeArray map =\n", + " new AssociativeArray<>(6);\n", + " map.put(\"sky\", \"blue\");\n", + " map.put(\"grass\", \"green\");\n", + " map.put(\"ocean\", \"dancing\");\n", + " map.put(\"tree\", \"tall\");\n", + " map.put(\"earth\", \"brown\");\n", + " map.put(\"sun\", \"warm\");\n", + " try {\n", + " map.put(\"extra\", \"object\"); // Past the end\n", + " } catch(ArrayIndexOutOfBoundsException e) {\n", + " System.out.println(\"Too many objects!\");\n", + " }\n", + " System.out.println(map);\n", + " System.out.println(map.get(\"ocean\"));\n", + " }\n", + "}\n", + "/* Output:\n", + "Too many objects!\n", + "sky : blue\n", + "grass : green\n", + "ocean : dancing\n", + "tree : tall\n", + "earth : brown\n", + "sun : warm\n", + "dancing\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "关联数组中的基本方法是 `put()` 和 `get()` ,但为了便于显示,重写了 `toString()` 方法以打印键值对。为了显示它的工作原理,主方法加载一个带有字符串对的 **AssociativeArray** 并打印生成的映射,然后调用其中一个值的 `get()` 方法。\n", + "\n", + "要使用 `get()` 方法,可以传入要查找的 **key** ,它将生成相关联的值作为结果,如果找不到则返回 **null** 。 `get()` 方法使用可能是效率最低的方法来定位值:从数组的头部开始并使用 `equals()` 来比较键。但这里是侧重于简单,而不是效率。\n", + "\n", + "这个版本很有启发性,但它不是很有效,而且它只有一个固定的大小,这是不灵活的。幸运的是, **java.util** 中的那些 **Map** 没有这些问题。\n", + "\n", + "\n", + "### 性能\n", + "\n", + "性能是 **Map** 的基本问题,在 `get()` 中使用线性方法搜索一个键时会非常慢。这就是 **HashMap** 要加速的地方。它使用一个称为 *哈希码* 的特殊值来替代慢速搜索一个键。哈希码是一种从相关对象中获取一些信息并将其转换为该对象的“相对唯一” **int** 的方法。 `hashCode()` 是根类 **Object** 中的一个方法,因此所有 Java 对象都可以生成哈希码。 **HashMap** 获取对象的 `hashCode()` 并使用它来快速搜索键。这就使得性能有了显著的提升。[^3]\n", + "\n", + "以下是基本的 **Map** 实现。 **HashMap**上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。其他实现强调其他特性,因此不如 **HashMap** 快。\n", + "\n", + "| **Map** 实现 | 描述 |\n", + "| :---: | :--- |\n", + "| **HashMap\\*** | 基于哈希表的实现。(使用此类来代替 **Hashtable** 。)为插入和定位键值对提供了常数时间性能。可以通过构造方法调整性能,这些构造方法允许你设置哈希表的容量和装填因子。 |\n", + "| **LinkedHashMap** | 与 **HashMap** 类似,但是当遍历时,可以按插入顺序或最近最少使用(LRU)顺序获取键值对。只比 **HashMap** 略慢,一个例外是在迭代时,由于其使用链表维护内部顺序,所以会更快些。 |\n", + "| **TreeMap** | 基于红黑树的实现。当查看键或键值对时,它们按排序顺序(由 **Comparable** 或 **Comparator** 确定)。 **TreeMap** 的侧重点是按排序顺序获得结果。 **TreeMap** 是唯一使用 `subMap()` 方法的 **Map** ,它返回红黑树的一部分。 |\n", + "| **WeakHashMap** | 一种具有 *弱键*(weak keys) 的 **Map** ,为了解决某些类型的问题,它允许释放 **Map** 所引用的对象。如果在 **Map** 外没有对特定键的引用,则可以对该键进行垃圾回收。 |\n", + "| **ConcurrentHashMap** | 不使用同步锁定的线程安全 **Mao** 。这在[第二十四章 并发编程]() 一章中讨论。 |\n", + "| **IdentityHashMap** | 使用 `==` 而不是 `equals()` 来比较键。仅用于解决特殊问题,不适用于一般用途。 |\n", + "\n", + "散列是在 **Map** 中存储元素的最常用方法。\n", + "\n", + "**Map** 中使用的键的要求与 **Set** 中的元素的要求相同。可以在 **TypesForSets.java** 中看到这些。任何键必须具有 `equals()` 方法。如果键用于散列映射,则它还必须具有正确的 `hashCode()` 方法。如果键在 **TreeMap** 中使用,则必须实现 **Comparable** 接口。\n", + "\n", + "以下示例使用先前定义的 **CountMap** 测试数据集显示通过 **Map** 接口可用的操作:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/MapOps.java\n", + "// Things you can do with Maps\n", + "import java.util.concurrent.*;\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class MapOps {\n", + " public static\n", + " void printKeys(Map map) {\n", + " System.out.print(\"Size = \" + map.size() + \", \");\n", + " System.out.print(\"Keys: \");\n", + " // Produce a Set of the keys:\n", + " System.out.println(map.keySet());\n", + " }\n", + " public static\n", + " void test(Map map) {\n", + " System.out.println(\n", + " map.getClass().getSimpleName());\n", + " map.putAll(new CountMap(25));\n", + " // Map has 'Set' behavior for keys:\n", + " map.putAll(new CountMap(25));\n", + " printKeys(map);\n", + " // Producing a Collection of the values:\n", + " System.out.print(\"Values: \");\n", + " System.out.println(map.values());\n", + " System.out.println(map);\n", + " System.out.println(\"map.containsKey(11): \" +\n", + " map.containsKey(11));\n", + " System.out.println(\n", + " \"map.get(11): \" + map.get(11));\n", + " System.out.println(\"map.containsValue(\\\"F0\\\"): \"\n", + " + map.containsValue(\"F0\"));\n", + " Integer key = map.keySet().iterator().next();\n", + " System.out.println(\"First key in map: \" + key);\n", + " map.remove(key);\n", + " printKeys(map);\n", + " map.clear();\n", + " System.out.println(\n", + " \"map.isEmpty(): \" + map.isEmpty());\n", + " map.putAll(new CountMap(25));\n", + " // Operations on the Set change the Map:\n", + " map.keySet().removeAll(map.keySet());\n", + " System.out.println(\n", + " \"map.isEmpty(): \" + map.isEmpty());\n", + " }\n", + " public static void main(String[] args) {\n", + " test(new HashMap<>());\n", + " test(new TreeMap<>());\n", + " test(new LinkedHashMap<>());\n", + " test(new IdentityHashMap<>());\n", + " test(new ConcurrentHashMap<>());\n", + " test(new WeakHashMap<>());\n", + " }\n", + "}\n", + "/* Output: (First 11 Lines)\n", + "HashMap\n", + "Size = 25, Keys: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n", + "12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]\n", + "Values: [A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0,\n", + "L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0]\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", + "9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0,\n", + "17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0}\n", + "map.containsKey(11): true\n", + "map.get(11): L0\n", + "map.containsValue(\"F0\"): true\n", + "First key in map: 0\n", + "Size = 24, Keys: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n", + "12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]\n", + "map.isEmpty(): true\n", + "map.isEmpty(): true\n", + " ...\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`printKeys()` 方法演示了如何生成 **Map** 的 **Collection** 视图。 `keySet()` 方法生成一个由 **Map** 中的键组成的 **Set** 。 打印 `values()` 方法的结果会生成一个包含 **Map** 中所有值的 **Collection** 。(请注意,键必须是唯一的,但值可以包含重复项。)由于这些 **Collection** 由 **Map** 支持,因此 **Collection** 中的任何更改都会反映在所关联的 **Map** 中。\n", + "\n", + "程序的其余部分提供了每个 **Map** 操作的简单示例,并测试了每种基本类型的 **Map** 。\n", + "\n", + "\n", + "### SortedMap\n", + "\n", + "使用 **SortedMap** (由 **TreeMap** 或 **ConcurrentSkipListMap** 实现),键保证按排序顺序,这允许在 **SortedMap** 接口中使用这些方法来提供其他功能:\n", + "\n", + "- `Comparator comparator()` :生成用于此 **Map** 的比较器, **null** 表示自然排序。\n", + "- `T firstKey()` :返回第一个键。\n", + "- `T lastKey()` :返回最后一个键。\n", + "- `SortedMap subMap(fromKey,toKey)` :生成此 **Map** 的视图,其中键从 **fromKey**(包括),到 **toKey** (不包括)。\n", + "- `SortedMap headMap(toKey)` :使用小于 **toKey** 的键生成此 **Map** 的视图。\n", + "- `SortedMap tailMap(fromKey)` :使用大于或等于 **fromKey** 的键生成此 **Map** 的视图。\n", + "\n", + "这是一个类似于 **SortedSetDemo.java** 的示例,显示了 **TreeMap** 的这种额外行为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/SortedMapDemo.java\n", + "// What you can do with a TreeMap\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class SortedMapDemo {\n", + " public static void main(String[] args) {\n", + " TreeMap sortedMap =\n", + " new TreeMap<>(new CountMap(10));\n", + " System.out.println(sortedMap);\n", + " Integer low = sortedMap.firstKey();\n", + " Integer high = sortedMap.lastKey();\n", + " System.out.println(low);\n", + " System.out.println(high);\n", + " Iterator it =\n", + " sortedMap.keySet().iterator();\n", + " for(int i = 0; i <= 6; i++) {\n", + " if(i == 3) low = it.next();\n", + " if(i == 6) high = it.next();\n", + " else it.next();\n", + " }\n", + " System.out.println(low);\n", + " System.out.println(high);\n", + " System.out.println(sortedMap.subMap(low, high));\n", + " System.out.println(sortedMap.headMap(high));\n", + " System.out.println(sortedMap.tailMap(low));\n", + " }\n", + "}\n", + "/* Output:\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", + "9=J0}\n", + "0\n", + "9\n", + "3\n", + "7\n", + "{3=D0, 4=E0, 5=F0, 6=G0}\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0}\n", + "{3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里,键值对按照键的排序顺序进行排序。因为 **TreeMap** 中存在顺序感,所以“位置”的概念很有意义,因此可以拥有第一个、最后一个元素或子图。\n", + "\n", + "\n", + "### LinkedHashMap\n", + "\n", + "**LinkedHashMap** 针对速度进行哈希处理,但在遍历期间也会按插入顺序生成键值对( `System.out.println()` 可以遍历它,因此可以看到遍历的结果)。 此外,可以在构造方法中配置 **LinkedHashMap** 以使用基于访问的 *最近最少使用*(LRU) 算法,因此未访问的元素(因此是删除的候选者)会出现在列表的前面。 这样可以轻松创建一个能够定期清理以节省空间的程序。下面是一个显示这两个功能的简单示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/LinkedHashMapDemo.java\n", + "// What you can do with a LinkedHashMap\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class LinkedHashMapDemo {\n", + " public static void main(String[] args) {\n", + " LinkedHashMap linkedMap =\n", + " new LinkedHashMap<>(new CountMap(9));\n", + " System.out.println(linkedMap);\n", + " // Least-recently-used order:\n", + " linkedMap =\n", + " new LinkedHashMap<>(16, 0.75f, true);\n", + " linkedMap.putAll(new CountMap(9));\n", + " System.out.println(linkedMap);\n", + " for(int i = 0; i < 6; i++)\n", + " linkedMap.get(i);\n", + " System.out.println(linkedMap);\n", + " linkedMap.get(0);\n", + " System.out.println(linkedMap);\n", + " }\n", + "}\n", + "/* Output:\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}\n", + "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}\n", + "{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}\n", + "{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些键值对确实是按照插入顺序进行遍历,即使对于LRU版本也是如此。 但是,在LRU版本中访问前六项(仅限)后,最后三项将移至列表的前面。然后,当再次访问“ **0** ”后,它移动到了列表的后面。\n", + "\n", + "\n", + "## 集合工具类\n", + "\n", + "集合有许多独立的实用工具程序,在 **java.util.Collections** 中表示为静态方法。之前已经见过其中一些,例如 `addAll()` , `reverseOrder()` 和 `binarySearch()` 。以下是其他内容(同步和不可修改的实用工具程序将在后面的章节中介绍)。在此表中,在需要的时候使用了泛型:\n", + "\n", + "| 方法 | 描述 |\n", + "| :--- | :--- |\n", + "| **checkedCollection(Collection\\ c, Class\\ type)**

**checkedList(List\\ list, Class\\ type)**

**checkedMap(Map\\ m, Class\\ keyType, Class\\ valueType)**

**checkedSet(Set\\ s, Class\\ type)**

**checkedSortedMap(SortedMap\\ m, Class\\ keyType, Class\\ valueType)**

**checkedSortedSet(SortedSet\\ s, Class\\ type)** | 生成 **Collection** 的动态类型安全视图或 **Collection** 的特定子类型。 当无法使用静态检查版本时使用这个版本。

这些方法的使用在[第九章 多态]()章节的“动态类型安全”标题下进行了展示。 |\n", + "| **max(Collection)**

**min(Collection)** | 使用 **Collection** 中对象的自然比较方法生成参数集合中的最大或最小元素。 |\n", + "| **max(Collection, Comparator)**

**min(Collection, Comparator)** | 使用 **Comparator** 指定的比较方法生成参数集合中的最大或最小元素。 |\n", + "| **indexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内第一次出现的起始索引,如果不存在则返回 -1。 |\n", + "| **lastIndexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内最后一次出现的起始索引,如果不存在则返回 -1。 |\n", + "| **replaceAll(List\\ list, T oldVal, T newVal)** | 用 **newVal** 替换列表中所有的 **oldVal** 。 |\n", + "| **reverse(List)**| 反转列表 |\n", + "| **reverseOrder()**

**reverseOrder(Comparator\\)** | 返回一个 **Comparator** ,它与集合中实现了 **comparable\\** 接口的对象的自然顺序相反。第二个版本颠倒了所提供的 **Comparator** 的顺序。 |\n", + "| **rotate(List, int distance)** | 将所有元素向前移动 **distance** ,将尾部的元素移到开头。(译者注:即循环移动) |\n", + "| **shuffle(List)**

**shuffle(List, Random)** | 随机置换指定列表(即打乱顺序)。第一个版本使用了默认的随机化源,或者也可以使用第二个版本,提供自己的随机化源。 |\n", + "| **sort(List\\)**

**sort(List\\, Comparator\\ c)** | 第一个版本使用元素的自然顺序排序该 **List\\** 。第二个版本根据提供的 **Comparator** 排序。 |\n", + "| **copy(List\\ dest, List\\ src)** | 将 **src** 中的元素复制到 **dest** 。 |\n", + "| **swap(List, int i, int j)** | 交换 **List** 中位置 **i** 和 位置 **j** 的元素。可能比你手工编写的速度快。 |\n", + "| **fill(List\\, T x)** | 用 **x** 替换 **List** 中的所有元素。|\n", + "| **nCopies(int n, T x)** | 返回大小为 **n** 的不可变 **List\\** ,其引用都指向 **x** 。 |\n", + "| **disjoint(Collection, Collection)** | 如果两个集合没有共同元素,则返回 **true** 。 |\n", + "| **frequency(Collection, Object x)** | 返回 **Collection** 中,等于 **x** 的元素个数。 |\n", + "| **emptyList()**

**emptyMap()**

**emptySet()** | 返回不可变的空 **List** , **Map** 或 **Set** 。这些是泛型的,因此生成的 **Collection** 可以被参数化为所需的类型。 |\n", + "| **singleton(T x)**

**singletonList(T x)**

**singletonMap(K key, V value)** | 生成一个不可变的 **List** , **Set** 或 **Map** ,其中只包含基于给定参数的单个元素。 |\n", + "| **list(Enumeration\\ e)** | 生成一个 **ArrayList\\** ,其中元素为(旧式) **Enumeration** ( **Iterator** 的前身)中的元素。用于从遗留代码向新式转换。 |\n", + "| **enumeration(Collection\\)** | 为参数集合生成一个旧式的 **Enumeration\\** 。 |\n", + "\n", + "请注意, `min()` 和 `max()` 使用 **Collection** 对象,而不使用 **List** ,因此不必担心是否应对 **Collection** 进行排序。(如前所述,在执行 `binarySearch()` 之前,将会对 **List** 或数组进行`sort()` 排序。)\n", + "\n", + "下面是一个示例,展示了上表中大多数实用工具程序的基本用法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Utilities.java\n", + "// Simple demonstrations of the Collections utilities\n", + "import java.util.*;\n", + "\n", + "public class Utilities {\n", + " static List list = Arrays.asList(\n", + " \"one Two three Four five six one\".split(\" \"));\n", + " public static void main(String[] args) {\n", + " System.out.println(list);\n", + " System.out.println(\"'list' disjoint (Four)?: \" +\n", + " Collections.disjoint(list,\n", + " Collections.singletonList(\"Four\")));\n", + " System.out.println(\n", + " \"max: \" + Collections.max(list));\n", + " System.out.println(\n", + " \"min: \" + Collections.min(list));\n", + " System.out.println(\n", + " \"max w/ comparator: \" + Collections.max(list,\n", + " String.CASE_INSENSITIVE_ORDER));\n", + " System.out.println(\n", + " \"min w/ comparator: \" + Collections.min(list,\n", + " String.CASE_INSENSITIVE_ORDER));\n", + " List sublist =\n", + " Arrays.asList(\"Four five six\".split(\" \"));\n", + " System.out.println(\"indexOfSubList: \" +\n", + " Collections.indexOfSubList(list, sublist));\n", + " System.out.println(\"lastIndexOfSubList: \" +\n", + " Collections.lastIndexOfSubList(list, sublist));\n", + " Collections.replaceAll(list, \"one\", \"Yo\");\n", + " System.out.println(\"replaceAll: \" + list);\n", + " Collections.reverse(list);\n", + " System.out.println(\"reverse: \" + list);\n", + " Collections.rotate(list, 3);\n", + " System.out.println(\"rotate: \" + list);\n", + " List source =\n", + " Arrays.asList(\"in the matrix\".split(\" \"));\n", + " Collections.copy(list, source);\n", + " System.out.println(\"copy: \" + list);\n", + " Collections.swap(list, 0, list.size() - 1);\n", + " System.out.println(\"swap: \" + list);\n", + " Collections.shuffle(list, new Random(47));\n", + " System.out.println(\"shuffled: \" + list);\n", + " Collections.fill(list, \"pop\");\n", + " System.out.println(\"fill: \" + list);\n", + " System.out.println(\"frequency of 'pop': \" +\n", + " Collections.frequency(list, \"pop\"));\n", + " List dups =\n", + " Collections.nCopies(3, \"snap\");\n", + " System.out.println(\"dups: \" + dups);\n", + " System.out.println(\"'list' disjoint 'dups'?: \" +\n", + " Collections.disjoint(list, dups));\n", + " // Getting an old-style Enumeration:\n", + " Enumeration e =\n", + " Collections.enumeration(dups);\n", + " Vector v = new Vector<>();\n", + " while(e.hasMoreElements())\n", + " v.addElement(e.nextElement());\n", + " // Converting an old-style Vector\n", + " // to a List via an Enumeration:\n", + " ArrayList arrayList =\n", + " Collections.list(v.elements());\n", + " System.out.println(\"arrayList: \" + arrayList);\n", + " }\n", + "}\n", + "/* Output:\n", + "[one, Two, three, Four, five, six, one]\n", + "'list' disjoint (Four)?: false\n", + "max: three\n", + "min: Four\n", + "max w/ comparator: Two\n", + "min w/ comparator: five\n", + "indexOfSubList: 3\n", + "lastIndexOfSubList: 3\n", + "replaceAll: [Yo, Two, three, Four, five, six, Yo]\n", + "reverse: [Yo, six, five, Four, three, Two, Yo]\n", + "rotate: [three, Two, Yo, Yo, six, five, Four]\n", + "copy: [in, the, matrix, Yo, six, five, Four]\n", + "swap: [Four, the, matrix, Yo, six, five, in]\n", + "shuffled: [six, matrix, the, Four, Yo, five, in]\n", + "fill: [pop, pop, pop, pop, pop, pop, pop]\n", + "frequency of 'pop': 7\n", + "dups: [snap, snap, snap]\n", + "'list' disjoint 'dups'?: true\n", + "arrayList: [snap, snap, snap]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出解释了每种实用方法的行为。请注意由于大小写的缘故,普通版本的 `min()` 和 `max()` 与带有 **String.CASE_INSENSITIVE_ORDER** 比较器参数的版本的区别。\n", + "\n", + "\n", + "### 排序和搜索列表\n", + "\n", + "用于执行排序和搜索 **List** 的实用工具程序与用于排序对象数组的程序具有相同的名字和方法签名,只不过是 **Collections** 的静态方法而不是 **Arrays** 。 这是一个使用 **Utilities.java** 中的 **list** 数据的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/ListSortSearch.java\n", + "// Sorting/searching Lists with Collections utilities\n", + "import java.util.*;\n", + "\n", + "public class ListSortSearch {\n", + " public static void main(String[] args) {\n", + " List list =\n", + " new ArrayList<>(Utilities.list);\n", + " list.addAll(Utilities.list);\n", + " System.out.println(list);\n", + " Collections.shuffle(list, new Random(47));\n", + " System.out.println(\"Shuffled: \" + list);\n", + " // Use ListIterator to trim off last elements:\n", + " ListIterator it = list.listIterator(10);\n", + " while(it.hasNext()) {\n", + " it.next();\n", + " it.remove();\n", + " }\n", + " System.out.println(\"Trimmed: \" + list);\n", + " Collections.sort(list);\n", + " System.out.println(\"Sorted: \" + list);\n", + " String key = list.get(7);\n", + " int index = Collections.binarySearch(list, key);\n", + " System.out.println(\n", + " \"Location of \" + key + \" is \" + index +\n", + " \", list.get(\" + index + \") = \" +\n", + " list.get(index));\n", + " Collections.sort(list,\n", + " String.CASE_INSENSITIVE_ORDER);\n", + " System.out.println(\n", + " \"Case-insensitive sorted: \" + list);\n", + " key = list.get(7);\n", + " index = Collections.binarySearch(list, key,\n", + " String.CASE_INSENSITIVE_ORDER);\n", + " System.out.println(\n", + " \"Location of \" + key + \" is \" + index +\n", + " \", list.get(\" + index + \") = \" +\n", + " list.get(index));\n", + " }\n", + "}\n", + "/* Output:\n", + "[one, Two, three, Four, five, six, one, one, Two,\n", + "three, Four, five, six, one]\n", + "Shuffled: [Four, five, one, one, Two, six, six, three,\n", + "three, five, Four, Two, one, one]\n", + "Trimmed: [Four, five, one, one, Two, six, six, three,\n", + "three, five]\n", + "Sorted: [Four, Two, five, five, one, one, six, six,\n", + "three, three]\n", + "Location of six is 7, list.get(7) = six\n", + "Case-insensitive sorted: [five, five, Four, one, one,\n", + "six, six, three, three, Two]\n", + "Location of three is 7, list.get(7) = three\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "就像使用数组进行搜索和排序一样,如果使用 **Comparator** 进行排序,则必须使用相同的 **Comparator** 执行 `binarySearch()` 。\n", + "\n", + "该程序还演示了 **Collections** 中的 `shuffle()` 方法,该方法随机打乱了 **List** 的顺序。 **ListIterator** 是在打乱后的列表中的特定位置创建的,用于从该位置删除元素,直到列表末尾。\n", + "\n", + "\n", + "### 创建不可修改的 Collection 或 Map\n", + "\n", + "通常,创建 **Collection** 或 **Map** 的只读版本会很方便。 **Collections** 类通过将原始集合传递给一个方法然后返回一个只读版本的集合。 对于 **Collection** (如果不能将 **Collection** 视为更具体的类型), **List** , **Set** 和 **Map** ,这类方法有许多变体。这个示例展示了针对每种类型,正确构建只读版本集合的方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/ReadOnly.java\n", + "// Using the Collections.unmodifiable methods\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class ReadOnly {\n", + " static Collection data =\n", + " new ArrayList<>(Countries.names(6));\n", + " public static void main(String[] args) {\n", + " Collection c =\n", + " Collections.unmodifiableCollection(\n", + " new ArrayList<>(data));\n", + " System.out.println(c); // Reading is OK\n", + " //- c.add(\"one\"); // Can't change it\n", + "\n", + " List a = Collections.unmodifiableList(\n", + " new ArrayList<>(data));\n", + " ListIterator lit = a.listIterator();\n", + " System.out.println(lit.next()); // Reading is OK\n", + " //- lit.add(\"one\"); // Can't change it\n", + "\n", + " Set s = Collections.unmodifiableSet(\n", + " new HashSet<>(data));\n", + " System.out.println(s); // Reading is OK\n", + " //- s.add(\"one\"); // Can't change it\n", + "\n", + " // For a SortedSet:\n", + " Set ss =\n", + " Collections.unmodifiableSortedSet(\n", + " new TreeSet<>(data));\n", + "\n", + " Map m =\n", + " Collections.unmodifiableMap(\n", + " new HashMap<>(Countries.capitals(6)));\n", + " System.out.println(m); // Reading is OK\n", + " //- m.put(\"Ralph\", \"Howdy!\");\n", + "\n", + " // For a SortedMap:\n", + " Map sm =\n", + " Collections.unmodifiableSortedMap(\n", + " new TreeMap<>(Countries.capitals(6)));\n", + " }\n", + "}\n", + "/* Output:\n", + "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI]\n", + "ALGERIA\n", + "[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,\n", + "BURUNDI]\n", + "{BENIN=Porto-Novo, BOTSWANA=Gaberone, ANGOLA=Luanda,\n", + "BURKINA FASO=Ouagadougou, ALGERIA=Algiers,\n", + "BURUNDI=Bujumbura}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为特定类型调用 “unmodifiable” 方法不会导致编译时检查,但是一旦发生转换,对修改特定集合内容的任何方法调用都将产生 **UnsupportedOperationException** 异常。\n", + "\n", + "在每种情况下,在将集合设置为只读之前,必须使用有意义的数据填充集合。填充完成后,最好的方法是用 “unmodifiable” 方法调用生成的引用替换现有引用。这样,一旦使得内容无法修改,那么就不会冒有意外更改内容的风险。另一方面,此工具还允许将可修改的集合保留为类中的**私有**集合,并从方法调用处返回对该集合的只读引用。所以,你可以在类内修改它,但其他人只能读它。\n", + "\n", + "\n", + "### 同步 Collection 或 Map\n", + "\n", + "**synchronized** 关键字是多线程主题的重要组成部分,更复杂的内容在[第二十四章 并发编程]()中介绍。在这里,只需要注意到 **Collections** 类包含一种自动同步整个集合的方法。 语法类似于 “unmodifiable” 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Synchronization.java\n", + "// Using the Collections.synchronized methods\n", + "import java.util.*;\n", + "\n", + "public class Synchronization {\n", + " public static void main(String[] args) {\n", + " Collection c =\n", + " Collections.synchronizedCollection(\n", + " new ArrayList<>());\n", + " List list = Collections\n", + " .synchronizedList(new ArrayList<>());\n", + " Set s = Collections\n", + " .synchronizedSet(new HashSet<>());\n", + " Set ss = Collections\n", + " .synchronizedSortedSet(new TreeSet<>());\n", + " Map m = Collections\n", + " .synchronizedMap(new HashMap<>());\n", + " Map sm = Collections\n", + " .synchronizedSortedMap(new TreeMap<>());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最好立即通过适当的 “synchronized” 方法传递新集合,如上所示。这样,就不会意外地暴露出非同步版本。\n", + "\n", + "\n", + "#### Fail Fast\n", + "\n", + "Java 集合还具有防止多个进程修改集合内容的机制。如果当前正在迭代集合,然后有其他一些进程介入并插入,删除或更改该集合中的对象,则会出现此问题。也许在集合中已经遍历过了那个元素,也许还没有遍历到,也许在调用 `size()` 之后集合的大小会缩小...有许多灾难情景。 Java 集合库使用一种 *fail-fast* 的机制,该机制可以检测到除了当前进程引起的更改之外,其它任何对集合的更改操作。如果它检测到其他人正在修改集合,则会立即生成 **ConcurrentModificationException** 异常。这就是“fail-fast”的含义——它不会在以后使用更复杂的算法尝试检测问题(快速失败)。\n", + "\n", + "通过创建迭代器并向迭代器指向的集合中添加元素,可以很容易地看到操作中的 fail-fast 机制,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/FailFast.java\n", + "// Demonstrates the \"fail-fast\" behavior\n", + "import java.util.*;\n", + "\n", + "public class FailFast {\n", + " public static void main(String[] args) {\n", + " Collection c = new ArrayList<>();\n", + " Iterator it = c.iterator();\n", + " c.add(\"An object\");\n", + " try {\n", + " String s = it.next();\n", + " } catch(ConcurrentModificationException e) {\n", + " System.out.println(e);\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "java.util.ConcurrentModificationException\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "异常来自于在从集合中获得迭代器之后,又尝试在集合中添加元素。程序的两个部分可能会修改同一个集合,这种可能性的存在会产生不确定状态,因此异常会通知你更改代码。在这种情况下,应先将所有元素添加到集合,然后再获取迭代器。\n", + "\n", + "**ConcurrentHashMap** , **CopyOnWriteArrayList** 和 **CopyOnWriteArraySet** 使用了特定的技术来避免产生 **ConcurrentModificationException** 异常。\n", + "\n", + "\n", + "## 持有引用\n", + "\n", + "**java.lang.ref** 中库包含一组类,这些类允许垃圾收集具有更大的灵活性。特别是当拥有可能导致内存耗尽的大对象时,这些类特别有用。这里有三个从抽象类 **Reference** 继承来的类: **SoftReference** (软引用), **WeakReference** (弱引用)和 **PhantomReference** (虚引用)继承了三个类。如果一个对象只能通过这其中的一个 **Reference** 对象访问,那么这三种类型每个都为垃圾收集器提供不同级别的间接引用(indirection)。\n", + "\n", + "如果一个对象是 *可达的*(reachable),那么意味着在程序中的某个位置可以找到该对象。这可能意味着在栈上有一个直接引用该对象的普通引用,但也有可能是引用了一个对该对象有引用的对象,这可以有很多中间环节。如果某个对象是可达的,则垃圾收集器无法释放它,因为它仍然被程序所使用。如果某个对象是不可达的,则程序无法使用它,那么垃圾收集器回收该对象就是安全的。\n", + "\n", + "使用 **Reference** 对象继续保持对该对象的引用,以到达该对象,但也允许垃圾收集器释放该对象。因此,程序可以使用该对象,但如果内存即将耗尽,则允许释放该对象。\n", + "\n", + "可以通过使用 **Reference** 对象作为你和普通引用之间的中介(代理)来实现此目的。此外,必须没有对象的普通引用(未包含在 **Reference** 对象中的对象)。如果垃圾收集器发现对象可通过普通引用访问,则它不会释放该对象。\n", + "\n", + "按照 **SoftReference** , **WeakReference** 和 **PhantomReference** 的顺序,每个都比前一个更“弱”,并且对应于不同的可达性级别。软引用用于实现对内存敏感的缓存。弱引用用于实现“规范化映射”( canonicalized mappings)——对象的实例可以在程序的多个位置同时使用,以节省存储,但不会阻止其键(或值)被回收。虚引用用于调度 pre-mortem 清理操作,这是一种比 Java 终结机制(Java finalization mechanism)更灵活的方式。\n", + "\n", + "使用 **SoftReference** 和 **WeakReference** ,可以选择是否将它们放在 **ReferenceQueue** (用于 pre-mortem 清理操作的设备)中,但 **PhantomReference** 只能在 **ReferenceQueue** 上构建。下面是一个简单的演示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/References.java\n", + "// Demonstrates Reference objects\n", + "import java.lang.ref.*;\n", + "import java.util.*;\n", + "\n", + "class VeryBig {\n", + " private static final int SIZE = 10000;\n", + " private long[] la = new long[SIZE];\n", + " private String ident;\n", + " VeryBig(String id) { ident = id; }\n", + " @Override\n", + " public String toString() { return ident; }\n", + " @Override\n", + " protected void finalize() {\n", + " System.out.println(\"Finalizing \" + ident);\n", + " }\n", + "}\n", + "\n", + "public class References {\n", + " private static ReferenceQueue rq =\n", + " new ReferenceQueue<>();\n", + " public static void checkQueue() {\n", + " Reference inq = rq.poll();\n", + " if(inq != null)\n", + " System.out.println(\"In queue: \" + inq.get());\n", + " }\n", + " public static void main(String[] args) {\n", + " int size = 10;\n", + " // Or, choose size via the command line:\n", + " if(args.length > 0)\n", + " size = Integer.valueOf(args[0]);\n", + " LinkedList> sa =\n", + " new LinkedList<>();\n", + " for(int i = 0; i < size; i++) {\n", + " sa.add(new SoftReference<>(\n", + " new VeryBig(\"Soft \" + i), rq));\n", + " System.out.println(\n", + " \"Just created: \" + sa.getLast());\n", + " checkQueue();\n", + " }\n", + " LinkedList> wa =\n", + " new LinkedList<>();\n", + " for(int i = 0; i < size; i++) {\n", + " wa.add(new WeakReference<>(\n", + " new VeryBig(\"Weak \" + i), rq));\n", + " System.out.println(\n", + " \"Just created: \" + wa.getLast());\n", + " checkQueue();\n", + " }\n", + " SoftReference s =\n", + " new SoftReference<>(new VeryBig(\"Soft\"));\n", + " WeakReference w =\n", + " new WeakReference<>(new VeryBig(\"Weak\"));\n", + " System.gc();\n", + " LinkedList> pa =\n", + " new LinkedList<>();\n", + " for(int i = 0; i < size; i++) {\n", + " pa.add(new PhantomReference<>(\n", + " new VeryBig(\"Phantom \" + i), rq));\n", + " System.out.println(\n", + " \"Just created: \" + pa.getLast());\n", + " checkQueue();\n", + " }\n", + " }\n", + "}\n", + "/* Output: (First and Last 10 Lines)\n", + "Just created: java.lang.ref.SoftReference@15db9742\n", + "Just created: java.lang.ref.SoftReference@6d06d69c\n", + "Just created: java.lang.ref.SoftReference@7852e922\n", + "Just created: java.lang.ref.SoftReference@4e25154f\n", + "Just created: java.lang.ref.SoftReference@70dea4e\n", + "Just created: java.lang.ref.SoftReference@5c647e05\n", + "Just created: java.lang.ref.SoftReference@33909752\n", + "Just created: java.lang.ref.SoftReference@55f96302\n", + "Just created: java.lang.ref.SoftReference@3d4eac69\n", + "Just created: java.lang.ref.SoftReference@42a57993\n", + "...________...________...________...________...\n", + "Just created: java.lang.ref.PhantomReference@45ee12a7\n", + "In queue: null\n", + "Just created: java.lang.ref.PhantomReference@330bedb4\n", + "In queue: null\n", + "Just created: java.lang.ref.PhantomReference@2503dbd3\n", + "In queue: null\n", + "Just created: java.lang.ref.PhantomReference@4b67cf4d\n", + "In queue: null\n", + "Just created: java.lang.ref.PhantomReference@7ea987ac\n", + "In queue: null\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当运行此程序(将输出重定向到文本文件以查看页面中的输出)时,将会看到对象是被垃圾收集了的,虽然仍然可以通过 **Reference** 对象访问它们(使用 `get()` 来获取实际的对象引用)。 还可以看到 **ReferenceQueue** 始终生成包含 **null** 对象的 **Reference** 。 要使用它,请从特定的 **Reference** 类继承,并为新类添加更多有用的方法。\n", + "\n", + "\n", + "\n", + "### WeakHashMap\n", + "\n", + "集合类库中有一个特殊的 **Map** 来保存弱引用: **WeakHashMap** 。 此类可以更轻松地创建规范化映射。在这种映射中,可以通过仅仅创建一个特定值的实例来节省存储空间。当程序需要该值时,它会查找映射中的现有对象并使用它(而不是从头开始创建一个)。 该映射可以将值作为其初始化的一部分,但更有可能的是在需要时创建该值。\n", + "\n", + "由于这是一种节省存储空间的技术,因此 **WeakHashMap** 允许垃圾收集器自动清理键和值,这是非常方便的。不能对放在 **WeakHashMap** 中的键和值做任何特殊操作,它们由 map 自动包装在 **WeakReference** 中。当键不再被使用的时候才允许清理,如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/CanonicalMapping.java\n", + "// Demonstrates WeakHashMap\n", + "import java.util.*;\n", + "\n", + "class Element {\n", + " private String ident;\n", + " Element(String id) { ident = id; }\n", + " @Override\n", + " public String toString() { return ident; }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hashCode(ident);\n", + " }\n", + " @Override\n", + " public boolean equals(Object r) {\n", + " return r instanceof Element &&\n", + " Objects.equals(ident, ((Element)r).ident);\n", + " }\n", + " @Override\n", + " protected void finalize() {\n", + " System.out.println(\"Finalizing \" +\n", + " getClass().getSimpleName() + \" \" + ident);\n", + " }\n", + "}\n", + "\n", + "class Key extends Element {\n", + " Key(String id) { super(id); }\n", + "}\n", + "\n", + "class Value extends Element {\n", + " Value(String id) { super(id); }\n", + "}\n", + "\n", + "public class CanonicalMapping {\n", + " public static void main(String[] args) {\n", + " int size = 1000;\n", + " // Or, choose size via the command line:\n", + " if(args.length > 0)\n", + " size = Integer.valueOf(args[0]);\n", + " Key[] keys = new Key[size];\n", + " WeakHashMap map =\n", + " new WeakHashMap<>();\n", + " for(int i = 0; i < size; i++) {\n", + " Key k = new Key(Integer.toString(i));\n", + " Value v = new Value(Integer.toString(i));\n", + " if(i % 3 == 0)\n", + " keys[i] = k; // Save as \"real\" references\n", + " map.put(k, v);\n", + " }\n", + " System.gc();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Key** 类必须具有 `hashCode()` 和 `equals()` ,因为它将被用作散列数据结构中的键。 `hashCode()` 的内容在[附录:理解hashCode和equals方法]()中进行了描述。\n", + "\n", + "运行程序,你会看到垃圾收集器每三个键跳过一次。对该键的普通引用也被放置在 **keys** 数组中,因此这些对象不能被垃圾收集。\n", + "\n", + "\n", + "## Java 1.0 / 1.1 的集合类\n", + "\n", + "不幸的是,许多代码是使用 Java 1.0 / 1.1 中的集合编写的,甚至新代码有时也是使用这些类编写的。编写新代码时切勿使用旧集合。旧的集合类有限,所以关于它们的讨论不多。由于它们是不合时宜的,所以我会尽量避免过分强调一些可怕的设计决定。\n", + "\n", + "\n", + "### Vector 和 Enumeration\n", + "\n", + "Java 1.0 / 1.1 中唯一的自扩展序列是 **Vector** ,因此它被用于很多地方。它的缺陷太多了,无法在这里描述(参见《Java编程思想》第1版,可从[www.OnJava8.com](www.OnJava8.com)免费下载)。基本上,你可以将它看作是具有冗长且笨拙的方法名称的 **ArrayList** 。在修订后的 Java 集合库中,**Vector** 已经被调整适配过,因此可以作为 **Collection** 和 **List** 来使用。事实证明这有点不正常,集合类库仍然包含它只是为了支持旧的 Java 代码,但这会让一些人误以为 **Vector** 已经变得更好了。\n", + "\n", + "迭代器的 Java 1.0 / 1.1 版本选择创建一个新名称“enumeration”,而不是使用每个人都熟悉的术语(“iterator”)。 **Enumeration** 接口小于 **Iterator** ,只包含两个方法,并且它使用更长的方法名称:如果还有更多元素,则 `boolean hasMoreElements()` 返回 `true` , `Object nextElement()` 返回此enumeration的下一个元素 (否则会抛出异常)。\n", + "\n", + "**Enumeration** 只是一个接口,而不是一个实现,甚至新的类库有时仍然使用旧的 **Enumeration** ,这是不幸的,但通常是无害的。应该总是在自己的代码中使用 **Iterator** ,但要做好准备应对那些提供 **Enumeration** 的类库。\n", + "\n", + "此外,可以使用 `Collections.enumeration()` 方法为任何 **Collection** 生成 **Enumeration** ,如下例所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Enumerations.java\n", + "// Java 1.0/1.1 Vector and Enumeration\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class Enumerations {\n", + " public static void main(String[] args) {\n", + " Vector v =\n", + " new Vector<>(Countries.names(10));\n", + " Enumeration e = v.elements();\n", + " while(e.hasMoreElements())\n", + " System.out.print(e.nextElement() + \", \");\n", + " // Produce an Enumeration from a Collection:\n", + " e = Collections.enumeration(new ArrayList<>());\n", + " }\n", + "}\n", + "/* Output:\n", + "ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", + "BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN\n", + "REPUBLIC, CHAD,\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要生成 **Enumeration** ,可以调用 `elements()` ,然后可以使用它来执行向前迭代。\n", + "\n", + "最后一行创建一个 **ArrayList** ,并使用 `enumeration() ` 来将 **ArrayList** 适配为一个 **Enumeration** 。 因此,如果有旧代码需要使用 **Enumeration** ,你仍然可以使用新集合。\n", + "\n", + "\n", + "### Hashtable\n", + "\n", + "正如你在本附录中的性能比较中所看到的,基本的 **Hashtable** 与 **HashMap** 非常相似,甚至方法名称都相似。在新代码中没有理由使用 **Hashtable** 而不是 **HashMap** 。\n", + "\n", + "\n", + "### Stack\n", + "\n", + "之前使用 **LinkedList** 引入了栈的概念。 Java 1.0 / 1.1 **Stack** 的奇怪之处在于,不是以组合方式使用 **Vector** ,而是继承自 **Vector** 。 因此它具有 **Vector** 的所有特征和行为以及一些额外的 **Stack** 行为。很难去知道设计师是否有意识地认为这样做是有用的,或者它是否只是太天真了,无论如何,它在进入发行版之前显然没有经过审查,所以这个糟糕的设计仍然存在(但不要使用它)。\n", + "\n", + "这是 **Stack** 的简单演示,向栈中放入枚举中每一个类型的 **String** 形式。它还展示了如何轻松地将 **LinkedList** 用作栈,或者使用在[第十二章:集合]()章节中创建的 **Stack** 类:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Stacks.java\n", + "// Demonstration of Stack Class\n", + "import java.util.*;\n", + "\n", + "enum Month { JANUARY, FEBRUARY, MARCH, APRIL,\n", + " MAY, JUNE, JULY, AUGUST, SEPTEMBER,\n", + " OCTOBER, NOVEMBER }\n", + "\n", + "public class Stacks {\n", + " public static void main(String[] args) {\n", + " Stack stack = new Stack<>();\n", + " for(Month m : Month.values())\n", + " stack.push(m.toString());\n", + " System.out.println(\"stack = \" + stack);\n", + " // Treating a stack as a Vector:\n", + " stack.addElement(\"The last line\");\n", + " System.out.println(\n", + " \"element 5 = \" + stack.elementAt(5));\n", + " System.out.println(\"popping elements:\");\n", + " while(!stack.empty())\n", + " System.out.print(stack.pop() + \" \");\n", + "\n", + " // Using a LinkedList as a Stack:\n", + " LinkedList lstack = new LinkedList<>();\n", + " for(Month m : Month.values())\n", + " lstack.addFirst(m.toString());\n", + " System.out.println(\"lstack = \" + lstack);\n", + " while(!lstack.isEmpty())\n", + " System.out.print(lstack.removeFirst() + \" \");\n", + "\n", + " // Using the Stack class from\n", + " // the Collections Chapter:\n", + " onjava.Stack stack2 =\n", + " new onjava.Stack<>();\n", + " for(Month m : Month.values())\n", + " stack2.push(m.toString());\n", + " System.out.println(\"stack2 = \" + stack2);\n", + " while(!stack2.isEmpty())\n", + " System.out.print(stack2.pop() + \" \");\n", + "\n", + " }\n", + "}\n", + "/* Output:\n", + "stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,\n", + "JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER]\n", + "element 5 = JUNE\n", + "popping elements:\n", + "The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY\n", + "JUNE MAY APRIL MARCH FEBRUARY JANUARY lstack =\n", + "[NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY,\n", + "APRIL, MARCH, FEBRUARY, JANUARY]\n", + "NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL\n", + "MARCH FEBRUARY JANUARY stack2 = [NOVEMBER, OCTOBER,\n", + "SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH,\n", + "FEBRUARY, JANUARY]\n", + "NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL\n", + "MARCH FEBRUARY JANUARY\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**String** 形式是由 **Month** 中的枚举常量生成的,使用 `push()` 压入到栈中,然后使用 `pop()` 从栈顶部取出。为了说明一点,将 **Vector** 的操作也在 **Stack** 对象上执行, 这是可能的,因为凭借继承, **Stack** 是 **Vector** 。 因此,可以在 **Vector** 上执行的所有操作也可以在 **Stack** 上执行,例如 `elementAt()` 。\n", + "\n", + "如前所述,在需要栈行为时使用 **LinkedList** ,或者从 **LinkedList** 类创建的 **onjava.Stack** 类。\n", + "\n", + "\n", + "### BitSet\n", + "\n", + "**BitSet** 用于有效地存储大量的开关信息。仅从尺寸大小的角度来看它是有效的,如果你正在寻找有效的访问,它比使用本机数组(native array)稍慢。\n", + "\n", + "此外, **BitSet** 的最小大小是 **long** :64位。这意味着如果你要存储更小的东西,比如8位, **BitSet** 就是浪费,如果尺寸有问题,你最好创建自己的类,或者只是用一个数组来保存你的标志。(只有在你创建许多包含开关信息列表的对象时才会出现这种情况,并且只应根据分析和其他指标来决定。如果你做出此决定只是因为您认为 **BitSet** 太大,那么最终会产生不必要的复杂性并且浪费大量时间。)\n", + "\n", + "当添加更多元素时,普通集合会扩展, **BitSet**也会这样做。以下示例显示了 **BitSet** 的工作原理:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// collectiontopics/Bits.java\n", + "// Demonstration of BitSet\n", + "import java.util.*;\n", + "\n", + "public class Bits {\n", + " public static void printBitSet(BitSet b) {\n", + " System.out.println(\"bits: \" + b);\n", + " StringBuilder bbits = new StringBuilder();\n", + " for(int j = 0; j < b.size() ; j++)\n", + " bbits.append(b.get(j) ? \"1\" : \"0\");\n", + " System.out.println(\"bit pattern: \" + bbits);\n", + " }\n", + " public static void main(String[] args) {\n", + " Random rand = new Random(47);\n", + " // Take the LSB of nextInt():\n", + " byte bt = (byte)rand.nextInt();\n", + " BitSet bb = new BitSet();\n", + " for(int i = 7; i >= 0; i--)\n", + " if(((1 << i) & bt) != 0)\n", + " bb.set(i);\n", + " else\n", + " bb.clear(i);\n", + " System.out.println(\"byte value: \" + bt);\n", + " printBitSet(bb);\n", + "\n", + " short st = (short)rand.nextInt();\n", + " BitSet bs = new BitSet();\n", + " for(int i = 15; i >= 0; i--)\n", + " if(((1 << i) & st) != 0)\n", + " bs.set(i);\n", + " else\n", + " bs.clear(i);\n", + " System.out.println(\"short value: \" + st);\n", + " printBitSet(bs);\n", + "\n", + " int it = rand.nextInt();\n", + " BitSet bi = new BitSet();\n", + " for(int i = 31; i >= 0; i--)\n", + " if(((1 << i) & it) != 0)\n", + " bi.set(i);\n", + " else\n", + " bi.clear(i);\n", + " System.out.println(\"int value: \" + it);\n", + " printBitSet(bi);\n", + "\n", + " // Test bitsets >= 64 bits:\n", + " BitSet b127 = new BitSet();\n", + " b127.set(127);\n", + " System.out.println(\"set bit 127: \" + b127);\n", + " BitSet b255 = new BitSet(65);\n", + " b255.set(255);\n", + " System.out.println(\"set bit 255: \" + b255);\n", + " BitSet b1023 = new BitSet(512);\n", + " b1023.set(1023);\n", + " b1023.set(1024);\n", + " System.out.println(\"set bit 1023: \" + b1023);\n", + " }\n", + "}\n", + "/* Output:\n", + "byte value: -107\n", + "bits: {0, 2, 4, 7}\n", + "bit pattern: 101010010000000000000000000000000000000000\n", + "0000000000000000000000\n", + "short value: 1302\n", + "bits: {1, 2, 4, 8, 10}\n", + "bit pattern: 011010001010000000000000000000000000000000\n", + "0000000000000000000000\n", + "int value: -2014573909\n", + "bits: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24,\n", + "25, 26, 31}\n", + "bit pattern: 110101010101000000110111111000010000000000\n", + "0000000000000000000000\n", + "set bit 127: {127}\n", + "set bit 255: {255}\n", + "set bit 1023: {1023, 1024}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "随机数生成器用于创建随机 **byte** , **short** 和 **int** ,并且每个都在 **BitSet** 中转换为相应的位模式。这样可以正常工作,因为 **BitSet** 是64位,所以这些都不会导致它的大小增加,然后创建更大的 **BitSet** 。 请注意, **BitSet** 会根据需要进行扩展。\n", + "\n", + "对于可以命名的固定标志集, **EnumSet** (参见[第二十二章:枚举]()章节)通常比 **BitSet** 更好,因为 **EnumSet** 允许操作名称而不是数字位位置,从而可以减少错误。 **EnumSet** 还可以防止意外地添加新的标记位置,这可能会导致一些严重的,难以发现的错误。使用 **BitSet** 而不是 **EnumSet** 的唯一原因是,不知道在运行时需要多少标志,或者为标志分配名称是不合理的,或者需要 **BitSet** 中的一个特殊操作(请参阅 **BitSet** 和 **EnumSet** 的 JDK 文档)。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "集合可以说是编程语言中最常用的工具。有些语言(例如Python)甚至将基本集合组件(列表,映射和集合)作为内置函数包含在其中。\n", + "\n", + "正如在[第十二章:集合]()章节中看到的那样,可以使用集合执行许多非常有用的操作,而不需要太多努力。但是,在某些时候,为了正确地使用它们而不得不更多地了解集合,特别是,必须充分了解散列操作以编写自己的 `hashCode()` 方法(并且必须知道何时需要),并且你必须充分了解各种集合实现,以根据你的需求选择合适的集合。本附录涵盖了这些概念,并讨论了有关集合库的其他有用详细信息。你现在应该已经准备好在日常编程任务中使用 Java 集合了。\n", + "\n", + "集合库的设计很困难(大多数库设计问题都是如此)。在 C++ 中,集合类涵盖了许多不同类的基础。这比之前可用的 C++ 集合类更好,但它没有很好地转换为 Java 。在另一个极端,我看到了一个由单个类“collection”组成的集合库,它同时充当线性序列和关联数组。 Java 集合库试图在功能和复杂性之间取得平衡。结果在某些地方看起来有点奇怪。与早期 Java 库中的一些决策不同,这些奇怪的不是事故,而是在基于复杂性的权衡下而仔细考虑的决策。\n", + "\n", + "\n", + "[^1]: **java.util** 中的 **Map** 使用 **Map** 的 `getKey()` 和 `getValue()` 执行批量复制,因此这是有效的。如果自定义 **Map** 只是复制整个 **Map.Entry** ,那么这种方法就会出现问题。\n", + "\n", + "[^2]: 虽然当我用这种方式描述它的时候听起来很奇怪而且好像没什么用处,但在[第十九章 类型信息]()章节中已经看到过,这种动态行为也可以非常强大有用。\n", + "\n", + "[^3]: 如果这些加速仍然无法满足性能需求,则可以通过编写自己的 **Map** 并将其自定义为特定类型来进一步加速表查找,以避免因向 **对象** 转换而导致的延迟。为了达到更高的性能水平,速度爱好者可以使用 Donald Knuth 的《计算机程序设计艺术(第3卷):排序与查找》(第二版),将溢出桶列表(overflow bucket lists)替换为具有两个额外优势的阵列:它们可以针对磁盘存储进行优化,并且它们可以节省大部分创建和回收个别记录(individual records)的时间。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Data-Compression.ipynb b/jupyter/Appendix-Data-Compression.ipynb new file mode 100644 index 00000000..f6d0a51f --- /dev/null +++ b/jupyter/Appendix-Data-Compression.ipynb @@ -0,0 +1,417 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:数据压缩\n", + "\n", + "Java I/O 类库提供了可以读写压缩格式流的类。你可以将其他 I/O 类包装起来用于提供压缩功能。\n", + "\n", + "这些类不是从 **Reader** 和 **Writer** 类派生的,而是 **InputStream** 和 **OutputStream** 层级结构的一部分。这是由于压缩库处理的是字节,而不是字符。但是,你可能会被迫混合使用两种类型的流(请记住,你可以使用 **InputStreamReader** 和 **OutputStreamWriter**,这两个类可以在字节类型和字符类型之间轻松转换)。\n", + "\n", + "| 压缩类 | 功能 |\n", + "| ------------------------ | ------------------------------------------------------------ |\n", + "| **CheckedInputStream** | `getCheckSum()` 可以对任意 **InputStream** 计算校验和(而不只是解压) |\n", + "| **CheckedOutputStream** | `getCheckSum()` 可以对任意 **OutputStream** 计算校验和(而不只是压缩) |\n", + "| **DeflaterOutputStream** | 压缩类的基类 |\n", + "| **ZipOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 Zip 文件结构 |\n", + "| **GZIPOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 GZIP 文件结构 |\n", + "| **InflaterInputStream** | 解压类的基类 |\n", + "| **ZipInputStream** | **InflaterInputStream** 类的一种,用于解压 Zip 文件结构的数据 |\n", + "| **GZIPInputStream** | **InflaterInputStream** 类的一种,用于解压 GZIP 文件结构的数据 |\n", + "\n", + "尽管存在很多压缩算法,但是 Zip 和 GZIP 可能是最常见的。你可以使用许多用于读取和写入这些格式的工具,来轻松操作压缩数据。\n", + "\n", + "\n", + "\n", + "## 使用 Gzip 简单压缩\n", + "\n", + "\n", + "\n", + "GZIP 接口十分简单,因此当你有一个需要压缩的数据流(而不是一个包含不同数据分片的容器)时,使用 GZIP 更为合适。如下是一个压缩单个文件的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// compression/GZIPcompress.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// {java GZIPcompress GZIPcompress.java}\n", + "// {VisuallyInspectOutput}\n", + "\n", + "public class GZIPcompress {\n", + " public static void main(String[] args) {\n", + " if (args.length == 0) {\n", + " System.out.println(\n", + " \"Usage: \\nGZIPcompress file\\n\" +\n", + " \"\\tUses GZIP compression to compress \" +\n", + " \"the file to test.gz\");\n", + " System.exit(1);\n", + " }\n", + " try (\n", + " InputStream in = new BufferedInputStream(\n", + " new FileInputStream(args[0]));\n", + " BufferedOutputStream out =\n", + " new BufferedOutputStream(\n", + " new GZIPOutputStream(\n", + " new FileOutputStream(\"test.gz\")))\n", + " ) {\n", + " System.out.println(\"Writing file\");\n", + " int c;\n", + " while ((c = in.read()) != -1)\n", + " out.write(c);\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.println(\"Reading file\");\n", + " try (\n", + " BufferedReader in2 = new BufferedReader(\n", + " new InputStreamReader(new GZIPInputStream(\n", + " new FileInputStream(\"test.gz\"))))\n", + " ) {\n", + " in2.lines().forEach(System.out::println);\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用压缩类非常简单,你只需要把你的输出流包装在 **GZIPOutputStream** 或 **ZipOutputStream** 中,将输入流包装在 **GZIPInputStream** 或 **ZipInputStream**。其他的一切就只是普通的 I/O 读写。这是面向字符流和面向字节流的混合示例;in 使用 Reader 类,而 **GZIPOutputStreams** 构造函数只能接受 **OutputStream** 对象,而不能接受 **Writer** 对象。当打开文件的时候,**GZIPInputStream** 会转换成为 **Reader**。\n", + "\n", + "## 使用 zip 多文件存储\n", + "\n", + "支持 Zip 格式的库比 GZIP 库更广泛。有了它,你可以轻松存储多个文件,甚至还有一个单独的类可以轻松地读取 Zip 文件。该库使用标准 Zip 格式,因此它可以与当前可在 Internet 上下载的所有 Zip 工具无缝协作。以下示例与前一个示例具有相同的形式,但它可以根据需要处理任意数量的命令行参数。此外,它还显示了 **Checksum** 类计算和验证文件的校验和。有两种校验和类型:Adler32(更快)和 CRC32(更慢但更准确)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// compression/ZipCompress.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// We make no guarantees that this code is fit for any purpose.\n", + "// Visit http://OnJava8.com for more book information.\n", + "// Uses Zip compression to compress any\n", + "// number of files given on the command line\n", + "// {java ZipCompress ZipCompress.java}\n", + "// {VisuallyInspectOutput}\n", + "public class ZipCompress {\n", + " public static void main(String[] args) {\n", + " try (\n", + " FileOutputStream f =\n", + " new FileOutputStream(\"test.zip\");\n", + " CheckedOutputStream csum =\n", + " new CheckedOutputStream(f, new Adler32());\n", + " ZipOutputStream zos = new ZipOutputStream(csum);\n", + " BufferedOutputStream out =\n", + " new BufferedOutputStream(zos)\n", + " ) {\n", + " zos.setComment(\"A test of Java Zipping\");\n", + " // No corresponding getComment(), though.\n", + " for (String arg : args) {\n", + " System.out.println(\"Writing file \" + arg);\n", + " try (\n", + " InputStream in = new BufferedInputStream(\n", + " new FileInputStream(arg))\n", + " ) {\n", + " zos.putNextEntry(new ZipEntry(arg));\n", + " int c;\n", + " while ((c = in.read()) != -1)\n", + " out.write(c);\n", + " }\n", + " out.flush();\n", + " }\n", + " // Checksum valid only after the file is closed!\n", + " System.out.println(\n", + " \"Checksum: \" + csum.getChecksum().getValue());\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // Now extract the files:\n", + " System.out.println(\"Reading file\");\n", + " try (\n", + " FileInputStream fi =\n", + " new FileInputStream(\"test.zip\");\n", + " CheckedInputStream csumi =\n", + " new CheckedInputStream(fi, new Adler32());\n", + " ZipInputStream in2 = new ZipInputStream(csumi);\n", + " BufferedInputStream bis =\n", + " new BufferedInputStream(in2)\n", + " ) {\n", + " ZipEntry ze;\n", + " while ((ze = in2.getNextEntry()) != null) {\n", + " System.out.println(\"Reading file \" + ze);\n", + " int x;\n", + " while ((x = bis.read()) != -1)\n", + " System.out.write(x);\n", + " }\n", + " if (args.length == 1)\n", + " System.out.println(\n", + " \"Checksum: \" + csumi.getChecksum().getValue());\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // Alternative way to open and read Zip files:\n", + " try (\n", + " ZipFile zf = new ZipFile(\"test.zip\")\n", + " ) {\n", + " Enumeration e = zf.entries();\n", + " while (e.hasMoreElements()) {\n", + " ZipEntry ze2 = (ZipEntry) e.nextElement();\n", + " System.out.println(\"File: \" + ze2);\n", + " // ... and extract the data as before\n", + " }\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于要添加到存档的每个文件,必须调用 `putNextEntry()` 并传递 **ZipEntry** 对象。 **ZipEntry** 对象包含一个扩展接口,用于获取和设置 Zip 文件中该特定条目的所有可用数据:名称,压缩和未压缩大小,日期,CRC 校验和,额外字段数据,注释,压缩方法以及它是否是目录条目。但是,即使 Zip 格式有设置密码的方法,Java 的 Zip 库也不支持。虽然 **CheckedInputStream** 和 **CheckedOutputStream** 都支持 Adler32 和 CRC32 校验和,但 **ZipEntry** 类仅支持 CRC 接口。这是对基础 Zip 格式的限制,但它可能会限制你使用更快的 Adler32。\n", + "\n", + "要提取文件,**ZipInputStream** 有一个 `getNextEntry()` 方法,这个方法在有文件存在的情况下调用,会返回下一个 **ZipEntry**。作为一个更简洁的替代方法,你可以使用 **ZipFile** 对象读取该文件,该对象具有方法 entries() 返回一个包裹 **ZipEntries** 的 **Enumeration**。\n", + "\n", + "要读取校验和,你必须以某种方式访问关联的 **Checksum** 对象。这里保留了对 **CheckedOutputStream** 和 **CheckedInputStream** 对象的引用,但你也可以保持对 **Checksum** 对象的引用。 Zip 流中的一个令人困惑的方法是 `setComment()`。如 **ZipCompress** 所示。在 Java 中,你可以在编写文件时设置注释,但是没有办法恢复 **ZipInputStream** 中的注释。注释似乎仅通过 **ZipEntry** 在逐个条目的基础上完全支持。\n", + "\n", + "使用 GZIP 或 Zip 库时,你不仅被限制于文件——你可以压缩任何内容,包括通过网络连接发送的数据。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## Java 的 jar\n", + "\n", + "Zip 格式也用于 JAR(Java ARchive)文件格式,这是一种将一组文件收集到单个压缩文件中的方法,就像 Zip 一样。但是,与 Java 中的其他所有内容一样,JAR 文件是跨平台的,因此你不必担心平台问题。你还可以将音频和图像文件像类文件一样包含在其中。\n", + "\n", + "JAR 文件由一个包含压缩文件集合的文件和一个描述它们的“清单(manifest)”组成。(你可以创建自己的清单文件;否则,jar 程序将为你执行此操作。)你可以在 JDK 文档中,找到更多关于 JAR 清单的信息。\n", + "\n", + "JDK 附带的 jar 工具会自动压缩你选择的文件。你可以在命令行上调用它:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar [options] destination [manifest] inputfile(s)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "选项是一组字母(不需要连字符或任何其他指示符)。 Unix / Linux 用户会注意到这些选项与 tar 命令选项的相似性。这些是:\n", + "\n", + "| 选项 | 功能 |\n", + "| ---------- | ------------------------------------------------------------ |\n", + "| **c** | 创建一个新的或者空的归档文件 |\n", + "| **t** | 列出内容目录 |\n", + "| **x** | 提取所有文件 |\n", + "| **x** file | 提取指定的文件 |\n", + "| **f** | 这代表着,“传递文件的名称。”如果你不使用它,jar 假定它的输入将来自标准输入,或者,如果它正在创建一个文件,它的输出将转到标准输出。 |\n", + "| **m** | 代表第一个参数是用户创建的清单文件的名称。 |\n", + "| **v** | 生成详细的输出用于表述 jar 所作的事情 |\n", + "| **0** | 仅存储文件;不压缩文件(用于创建放在类路径中的 JAR 文件)。 |\n", + "| **M** | 不要自动创建清单文件 |\n", + "\n", + "如果放入 JAR 文件的文件中包含子目录,则会自动添加该子目录,包括其所有子目录等。还会保留路径信息。\n", + "\n", + "以下是一些调用 jar 的典型方法。以下命令创建名为 myJarFile 的 JAR 文件。 jar 包含当前目录中的所有类文件,以及自动生成的清单文件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar cf myJarFile.jar *.class" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下一个命令与前面的示例类似,但它添加了一个名为 myManifestFile.mf 的用户创建的清单文件。 :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar cmf myJarFile.jar myManifestFile.mf *.class" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个命令输出了 myJarFile.jar 中的文件目录:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar tf myJarFile.jar" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如下添加了一个“verbose”的标志,用于生成更多关于 myJarFile.jar 中文件的详细信息:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar tvf myJarFile.jar" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "假设 audio,classes 和 image 都是子目录,它将所有子目录组合到文件 myApp.jar 中。还包括“verbose”标志,以便在 jar 程序工作时提供额外的反馈:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "jar cvf myApp.jar audio classes image" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你在创建 JAR 文件时使用了 0(零) 选项,该文件将会被替换在你的类路径(CLASSPATH)中:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "CLASSPATH=\"lib1.jar;lib2.jar;\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "然后 Java 可以搜索到 lib1.jar 和 lib2.jar 的类文件。\n", + "\n", + "jar 工具不像 Zip 实用程序那样通用。例如,你无法将文件添加或更新到现有 JAR 文件;只能从头开始创建 JAR 文件。\n", + "\n", + "此外,你无法将文件移动到 JAR 文件中,在移动文件时将其删除。\n", + "\n", + "但是,在一个平台上创建的 JAR 文件可以通过任何其他平台上的 jar 工具透明地读取(这个问题有时会困扰 Zip 实用程序)。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-IO-Streams.ipynb b/jupyter/Appendix-IO-Streams.ipynb new file mode 100644 index 00000000..de3842cf --- /dev/null +++ b/jupyter/Appendix-IO-Streams.ipynb @@ -0,0 +1,716 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:流式IO\n", + "\n", + "> Java 7 引入了一种简单明了的方式来读写文件和操作目录。大多情况下,[文件](./17-Files.md)这一章所介绍的那些库和技术就足够你用了。但是,如果你必须面对一些特殊的需求和比较底层的操作,或者处理一些老版本的代码,那么你就必须了解本附录中的内容。\n", + "\n", + "对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、缓冲、二进制、字符、按行和按字等)。\n", + "\n", + "Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。\n", + "\n", + "因此,要想充分理解 Java I/O 系统以便正确运用它,我们需要学习一定数量的类。另外,理解 I/O 类库的演化过程也很有必要,因为如果缺乏历史的眼光,很快我们就会对什么时候该使用哪些类,以及什么时候不该使用它们而感到困惑。\n", + "\n", + "编程语言的 I/O 类库经常使用**流**这个抽象概念,它将所有数据源或者数据接收器表示为能够产生或者接收数据片的对象。\n", + "\n", + "> **注意**:Java 8 函数式编程中的 `Stream` 类和这里的 I/O stream 没有任何关系。这又是另一个例子,如果再给设计者一次重来的机会,他们将使用不同的术语。\n", + "\n", + "I/O 流屏蔽了实际的 I/O 设备中处理数据的细节:\n", + "\n", + "1. 字节流对应原生的二进制数据;\n", + "2. 字符流对应字符数据,它会自动处理与本地字符集之间的转换;\n", + "3. 缓冲流可以提高性能,通过减少底层 API 的调用次数来优化 I/O。\n", + "\n", + "从 JDK 文档的类层次结构中可以看到,Java 类库中的 I/O 类分成了输入和输出两部分。在设计 Java 1.0 时,类库的设计者们就决定让所有与输入有关系的类都继承自 `InputStream`,所有与输出有关系的类都继承自 `OutputStream`。所有从 `InputStream` 或 `Reader` 派生而来的类都含有名为 `read()` 的基本方法,用于读取单个字节或者字节数组。同样,所有从 `OutputStream` 或 `Writer` 派生而来的类都含有名为 `write()` 的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。\n", + "\n", + "我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是**装饰器设计模式**)。为了创建一个流,你却要创建多个对象,这也是 Java I/O 类库让人困惑的主要原因。\n", + "\n", + "这里我只会提供这些类的概述,并假定你会使用 JDK 文档来获取它们的详细信息(比如某个类的所以方法的详细列表)。\n", + "\n", + "\n", + "## 输入流类型\n", + "\n", + "`InputStream` 表示那些从不同数据源产生输入的类,如[表 I/O-1](#table-io-1) 所示,这些数据源包括:\n", + "\n", + "1. 字节数组;\n", + "2. `String` 对象;\n", + "3. 文件;\n", + "4. “管道”,工作方式与实际生活中的管道类似:从一端输入,从另一端输出;\n", + "5. 一个由其它种类的流组成的序列,然后我们可以把它们汇聚成一个流;\n", + "6. 其它数据源,如 Internet 连接。\n", + "\n", + "每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后再讨论。\n", + "\n", + "**表 I/O-1 `InputStream` 类型**\n", + "\n", + "| 类 | 功能 | 构造器参数 | 如何使用 |\n", + "| :--: | :-- | :-------- | :----- |\n", + "| `ByteArrayInputStream` | 允许将内存的缓冲区当做 `InputStream` 使用 | 缓冲区,字节将从中取出 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", + "| `StringBufferInputStream` | 将 `String` 转换成 `InputStream` | 字符串。底层实现实际使用 `StringBuffer` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", + "| `FileInputStream` | 用于从文件中读取信息 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", + "| `PipedInputStream` | 产生用于写入相关 `PipedOutputStream` 的数据。实现“管道化”概念 | `PipedOutputSteam` | 作为多线程中的数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", + "| `SequenceInputStream` | 将两个或多个 `InputStream` 对象转换成一个 `InputStream` | 两个 `InputStream` 对象或一个容纳 `InputStream` 对象的容器 `Enumeration` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", + "| `FilterInputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它的 `InputStream` 类提供有用的功能。见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) |\n", + "\n", + "\n", + "## 输出流类型\n", + "\n", + "如[表 I/O-2](#table-io-2) 所示,该类别的类决定了输出所要去往的目标:字节数组(但不是 `String`,当然,你也可以用字节数组自己创建)、文件或管道。\n", + "\n", + "另外,`FilterOutputStream` 为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口与输出流连接了起来,这些稍后会讨论。\n", + "\n", + "**表 I/O-2:`OutputStream` 类型**\n", + "\n", + "| 类 | 功能 | 构造器参数 | 如何使用 |\n", + "| :--: | :-- | :-------- | :----- |\n", + "| `ByteArrayOutputStream` | 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区 | 缓冲区初始大小(可选) | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", + "| `FileOutputStream` | 用于将信息写入文件 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", + "| `PipedOutputStream` | 任何写入其中的信息都会自动作为相关 `PipedInputStream` 的输出。实现“管道化”概念 | `PipedInputStream` | 指定用于多线程的数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", + "| `FilterOutputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它 `OutputStream` 提供有用功能。见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) |\n", + "\n", + "\n", + "\n", + "## 添加属性和有用的接口\n", + "\n", + "装饰器在[泛型](./20-Generics.md)这一章引入。Java I/O 类库需要多种不同功能的组合,这正是使用装饰器模式的原因所在[^1]。而之所以存在 **filter**(过滤器)类,是因为让抽象类 **filter** 作为所有装饰器类的基类。装饰器必须具有和它所装饰对象相同的接口,但它也可以扩展接口,不过这种情况只发生在个别 **filter** 类中。\n", + "\n", + "但是,装饰器模式也有一个缺点:在编写程序的时候,它给我们带来了相当多的灵活性(因为我们可以很容易地对属性进行混搭),但它同时也增加了代码的复杂性。Java I/O 类库操作不便的原因在于:我们必须创建许多类(“核心” I/O 类型加上所有的装饰器)才能得到我们所希望的单个 I/O 对象。\n", + "\n", + "`FilterInputStream` 和 `FilterOutputStream` 是用来提供装饰器类接口以控制特定输入流 `InputStream` 和 输出流 `OutputStream` 的两个类,但它们的名字并不是很直观。`FilterInputStream` 和 `FilterOutputStream` 分别从 I/O 类库中的基类 `InputStream` 和 `OutputStream` 派生而来,这两个类是创建装饰器的必要条件(这样它们才能为所有被装饰的对象提供统一接口)。\n", + "\n", + "### 通过 `FilterInputStream` 从 `InputStream` 读取\n", + "\n", + "`FilterInputStream` 类能够完成两件截然不同的事情。其中,`DataInputStream` 允许我们读取不同的基本数据类型和 `String` 类型的对象(所有方法都以 “read” 开头,例如 `readByte()`、`readFloat()`等等)。搭配其对应的 `DataOutputStream`,我们就可以通过数据“流”将基本数据类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由[表 I/O-1](#table-io-1) 中的那些类决定的。\n", + "\n", + "其它 `FilterInputStream` 类则在内部修改 `InputStream` 的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否允许把单个字符推回输入流等等。最后两个类看起来就像是为了创建编译器提供的(它们被添加进来可能是为了对“用 Java 构建编译器”实现提供支持),因此我们在一般编程中不会用到它们。\n", + "\n", + "在实际应用中,不管连接的是什么 I/O 设备,我们基本上都会对输入进行缓冲。所以当初 I/O 类库如果能默认都让输入进行缓冲,同时将无缓冲输入作为一种特殊情况(或者只是简单地提供一个方法调用),这样会更加合理,而不是像现在这样迫使我们基本上每次都得手动添加缓冲。\n", + "\n", + "\n", + "**表 I/O-3:`FilterInputStream` 类型**\n", + "\n", + "| 类 | 功能 | 构造器参数 | 如何使用 |\n", + "| :--: | :-- | :-------- | :----- |\n", + "| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int`、`char`、`long` 等) | `InputStream` | 包含用于读取基本数据类型的全部接口 |\n", + "| `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |\n", + "| `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()` 和 `setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 |\n", + "| `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 |\n", + "\n", + "### 通过 `FilterOutputStream` 向 `OutputStream` 写入\n", + "\n", + "与 `DataInputStream` 对应的是 `DataOutputStream`,它可以将各种基本数据类型和 `String` 类型的对象格式化输出到“流”中,。这样一来,任何机器上的任何 `DataInputStream` 都可以读出它们。所有方法都以 “write” 开头,例如 `writeByte()`、`writeFloat()` 等等。\n", + "\n", + "`PrintStream` 最初的目的就是为了以可视化格式打印所有基本数据类型和 `String` 类型的对象。这和 `DataOutputStream` 不同,后者的目的是将数据元素置入“流”中,使 `DataInputStream` 能够可移植地重构它们。\n", + "\n", + "`PrintStream` 内有两个重要方法:`print()` 和 `println()`。它们都被重载了,可以打印各种各种数据类型。`print()` 和 `println()` 之间的差异是,后者在操作完毕后会添加一个换行符。\n", + "\n", + "`PrintStream` 可能会造成一些问题,因为它捕获了所有 `IOException`(因此,我们必须使用 `checkError()` 自行测试错误状态,如果出现错误它会返回 `true`)。另外,`PrintStream` 没有处理好国际化问题。这些问题都在 `PrintWriter` 中得到了解决,这在后面会讲到。\n", + "\n", + "`BufferedOutputStream` 是一个修饰符,表明这个“流”使用了缓冲技术,因此每次向流写入的时候,不是每次都会执行物理写操作。我们在进行输出操作的时候可能会经常用到它。\n", + "\n", + "**表 I/O-4:`FilterOutputStream` 类型**\n", + "\n", + "| 类 | 功能 | 构造器参数 | 如何使用 |\n", + "| :--: | :-- | :-------- | :----- |\n", + "| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本数据类型(`int`、`char`、`long` 等) | `OutputStream` | 包含用于写入基本数据类型的全部接口 |\n", + "| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 |\n", + "| `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |\n", + "\n", + "\n", + "\n", + "## Reader和Writer\n", + "\n", + "Java 1.1 对基本的 I/O 流类库做了重大的修改。你初次遇到 `Reader` 和 `Writer` 时,可能会以为这两个类是用来替代 `InputStream` 和 `OutputStream` 的,但实际上并不是这样。尽管一些原始的“流”类库已经过时了(如果使用它们,编译器会发出警告),但是 `InputStream` 和 `OutputStream` 在面向字节 I/O 这方面仍然发挥着极其重要的作用,而 `Reader` 和 `Writer` 则提供兼容 Unicode 和面向字符 I/O 的功能。另外:\n", + "\n", + "1. Java 1.1 往 `InputStream` 和 `OutputStream` 的继承体系中又添加了一些新类,所以这两个类显然是不会被取代的;\n", + "\n", + "2. 有时我们必须把来自“字节”层级结构中的类和来自“字符”层次结构中的类结合起来使用。为了达到这个目的,需要用到“适配器(adapter)类”:`InputStreamReader` 可以把 `InputStream` 转换为 `Reader`,而 `OutputStreamWriter` 可以把 `OutputStream` 转换为 `Writer`。\n", + "\n", + "设计 `Reader` 和 `Writer` 继承体系主要是为了国际化。老的 I/O 流继承体系仅支持 8 比特的字节流,并且不能很好地处理 16 比特的 Unicode 字符。由于 Unicode 用于字符国际化(Java 本身的 `char` 也是 16 比特的 Unicode),所以添加 `Reader` 和 `Writer` 继承体系就是为了让所有的 I/O 操作都支持 Unicode。另外,新类库的设计使得它的操作比旧类库要快。\n", + "\n", + "### 数据的来源和去处\n", + "\n", + "几乎所有原始的 Java I/O 流类都有相应的 `Reader` 和 `Writer` 类来提供原生的 Unicode 操作。但是在某些场合,面向字节的 `InputStream` 和 `OutputStream` 才是正确的解决方案。特别是 `java.util.zip` 类库就是面向字节而不是面向字符的。因此,最明智的做法是尽量**尝试**使用 `Reader` 和 `Writer`,一旦代码没法成功编译,你就会发现此时应该使用面向字节的类库了。\n", + "\n", + "下表展示了在两个继承体系中,信息的来源和去处(即数据物理上来自哪里又去向哪里)之间的对应关系:\n", + "\n", + "| 来源与去处:Java 1.0 类 | 相应的 Java 1.1 类 |\n", + "| :-------------------: | :--------------: |\n", + "| `InputStream` | `Reader`
适配器:`InputStreamReader` |\n", + "| `OutputStream` | `Writer`
适配器:`OutputStreamWriter` |\n", + "| `FileInputStream` | `FileReader` |\n", + "| `FileOutputStream` | `FileWriter` |\n", + "| `StringBufferInputStream`(已弃用) | `StringReader` |\n", + "| (无相应的类) | `StringWriter` |\n", + "| `ByteArrayInputStream` | `CharArrayReader` |\n", + "| `ByteArrayOutputStream` | `CharArrayWriter` |\n", + "| `PipedInputStream` | `PipedReader` |\n", + "| `PipedOutputStream` | `PipedWriter` |\n", + "\n", + "总的来说,这两个不同的继承体系中的接口即便不能说完全相同,但也是非常相似的。\n", + "\n", + "### 更改流的行为\n", + "\n", + "对于 `InputStream` 和 `OutputStream` 来说,我们会使用 `FilterInputStream` 和 `FilterOutputStream` 的装饰器子类来修改“流”以满足特殊需要。`Reader` 和 `Writer` 的类继承体系沿用了相同的思想——但是并不完全相同。\n", + "\n", + "在下表中,左右之间对应关系的近似程度现比上一个表格更加粗略一些。造成这种差别的原因是类的组织形式不同,`BufferedOutputStream` 是 `FilterOutputStream` 的子类,但 `BufferedWriter` 却不是 `FilterWriter` 的子类(尽管 `FilterWriter` 是抽象类,但却没有任何子类,把它放在表格里只是占个位置,不然你可能奇怪 `FilterWriter` 上哪去了)。然而,这些类的接口却又十分相似。\n", + "\n", + "| 过滤器:Java 1.0 类 | 相应 Java 1.1 类 |\n", + "| :--------------- | :-------------- |\n", + "| `FilterInputStream` | `FilterReader` |\n", + "| `FilterOutputStream` | `FilterWriter` (抽象类,没有子类) |\n", + "| `BufferedInputStream` | `BufferedReader`(也有 `readLine()`) |\n", + "| `BufferedOutputStream` | `BufferedWriter` |\n", + "| `DataInputStream` | 使用 `DataInputStream`( 如果必须用到 `readLine()`,那你就得使用 `BufferedReader`。否则,一般情况下就用 `DataInputStream` |\n", + "| `PrintStream` | `PrintWriter` |\n", + "| `LineNumberInputStream` | `LineNumberReader` |\n", + "| `StreamTokenizer` | `StreamTokenizer`(使用具有 `Reader` 参数的构造器) |\n", + "| `PushbackInputStream` | `PushbackReader` |\n", + "\n", + "有一条限制需要明确:一旦要使用 `readLine()`,我们就不应该用 `DataInputStream`(否则,编译时会得到使用了过时方法的警告),而应该使用 `BufferedReader`。除了这种情况之外的情形中,`DataInputStream` 仍是 I/O 类库的首选成员。\n", + "\n", + "为了使用时更容易过渡到 `PrintWriter`,它提供了一个既能接受 `Writer` 对象又能接受任何 `OutputStream` 对象的构造器。`PrintWriter` 的格式化接口实际上与 `PrintStream` 相同。\n", + "\n", + "Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简化文件的创建过程,你马上就会见到它们。\n", + "\n", + "其中一种 `PrintWriter` 构造器还有一个执行**自动 flush**[^2] 的选项。如果构造器设置了该选项,就会在每个 `println()` 调用之后,自动执行 flush。\n", + "\n", + "### 未发生改变的类\n", + "\n", + "有一些类在 Java 1.0 和 Java 1.1 之间未做改变。\n", + "\n", + "| 以下这些 Java 1.0 类在 Java 1.1 中没有相应类 |\n", + "| --- |\n", + "| `DataOutputStream` |\n", + "| `File` |\n", + "| `RandomAccessFile` |\n", + "| `SequenceInputStream` |\n", + "\n", + "特别是 `DataOutputStream`,在使用时没有任何变化;因此如果想以可传输的格式存储和检索数据,请用 `InputStream` 和 `OutputStream` 继承体系。\n", + "\n", + "\n", + "## RandomAccessFile类\n", + "\n", + "`RandomAccessFile` 适用于由大小已知的记录组成的文件,所以我们可以使用 `seek()` 将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改。文件中记录的大小不一定都相同,只要我们能确定那些记录有多大以及它们在文件中的位置即可。\n", + "\n", + "最初,我们可能难以相信 `RandomAccessFile` 不是 `InputStream` 或者 `OutputStream` 继承体系中的一部分。除了实现了 `DataInput` 和 `DataOutput` 接口(`DataInputStream` 和 `DataOutputStream` 也实现了这两个接口)之外,它和这两个继承体系没有任何关系。它甚至都不使用 `InputStream` 和 `OutputStream` 类中已有的任何功能。它是一个完全独立的类,其所有的方法(大多数都是 `native` 方法)都是从头开始编写的。这么做是因为 `RandomAccessFile` 拥有和别的 I/O 类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接继承自 `Object`。\n", + "\n", + "从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream` 和 `DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度。另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。\n", + "\n", + "在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件**(mmap)取代,详见[附录:新 I/O](./Appendix-New-IO.md)。\n", + "\n", + "\n", + "\n", + "## IO流典型用途\n", + "\n", + "尽管我们可以用不同的方式来组合 I/O 流类,但常用的也就其中几种。你可以下面的例子可以作为 I/O 典型用法的基本参照(在你确定无法使用[文件](./17-Files.md)这一章所述的库之后)。\n", + "\n", + "在这些示例中,异常处理都被简化为将异常传递给控制台,但是这样做只适用于小型的示例和工具。在你自己的代码中,你需要考虑更加复杂的错误处理方式。\n", + "\n", + "### 缓冲输入文件\n", + "\n", + "如果想要打开一个文件进行字符输入,我们可以使用一个 `FileInputReader` 对象,然后传入一个 `String` 或者 `File` 对象作为文件名。为了提高速度,我们希望对那个文件进行缓冲,那么我们可以将所产生的引用传递给一个 `BufferedReader` 构造器。`BufferedReader` 提供了 `line()` 方法,它会产生一个 `Stream` 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/BufferedInputFile.java\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "import java.util.stream.*;\n", + "\n", + "public class BufferedInputFile {\n", + " public static String read(String filename) {\n", + " try (BufferedReader in = new BufferedReader(\n", + " new FileReader(filename))) {\n", + " return in.lines()\n", + " .collect(Collectors.joining(\"\\n\"));\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " System.out.print(\n", + " read(\"BufferedInputFile.java\"));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`Collectors.joining()` 在其内部使用了一个 `StringBuilder` 来累加其运行结果。该文件会通过 `try-with-resources` 子句自动关闭。\n", + "\n", + "### 从内存输入\n", + "\n", + "下面示例中,从 `BufferedInputFile.read()` 读入的 `String` 被用来创建一个 `StringReader` 对象。然后调用其 `read()` 方法,每次读取一个字符,并把它显示在控制台上:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/MemoryInput.java\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "\n", + "public class MemoryInput {\n", + " public static void\n", + " main(String[] args) throws IOException {\n", + " StringReader in = new StringReader(\n", + " BufferedInputFile.read(\"MemoryInput.java\"));\n", + " int c;\n", + " while ((c = in.read()) != -1)\n", + " System.out.print((char) c);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意 `read()` 是以 `int` 形式返回下一个字节,所以必须类型转换为 `char` 才能正确打印。\n", + "\n", + "### 格式化内存输入\n", + "\n", + "要读取格式化数据,我们可以使用 `DataInputStream`,它是一个面向字节的 I/O 类(不是面向字符的)。这样我们就必须使用 `InputStream` 类而不是 `Reader` 类。我们可以使用 `InputStream` 以字节形式读取任何数据(比如一个文件),但这里使用的是字符串。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/FormattedMemoryInput.java\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "\n", + "public class FormattedMemoryInput {\n", + " public static void main(String[] args) {\n", + " try (\n", + " DataInputStream in = new DataInputStream(\n", + " new ByteArrayInputStream(\n", + " BufferedInputFile.read(\n", + " \"FormattedMemoryInput.java\")\n", + " .getBytes()))\n", + " ) {\n", + " while (true)\n", + " System.out.write((char) in.readByte());\n", + " } catch (EOFException e) {\n", + " System.out.println(\"\\nEnd of stream\");\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`ByteArrayInputStream` 必须接收一个字节数组,所以这里我们调用了 `String.getBytes()` 方法。所产生的的 `ByteArrayInputStream` 是一个适合传递给 `DataInputStream` 的 `InputStream`。\n", + "\n", + "如果我们用 `readByte()` 从 `DataInputStream` 一次一个字节地读取字符,那么任何字节的值都是合法结果,因此返回值不能用来检测输入是否结束。取而代之的是,我们可以使用 `available()` 方法得到剩余可用字符的数量。下面例子演示了怎么一次一个字节地读取文件:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/TestEOF.java\n", + "// Testing for end of file\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "\n", + "public class TestEOF {\n", + " public static void main(String[] args) {\n", + " try (\n", + " DataInputStream in = new DataInputStream(\n", + " new BufferedInputStream(\n", + " new FileInputStream(\"TestEOF.java\")))\n", + " ) {\n", + " while (in.available() != 0)\n", + " System.out.write(in.readByte());\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意,`available()` 的工作方式会随着所读取媒介类型的不同而有所差异,它的字面意思就是“在没有阻塞的情况下所能读取的字节数”。对于文件,能够读取的是整个文件;但是对于其它类型的“流”,可能就不是这样,所以要谨慎使用。\n", + "\n", + "我们也可以通过捕获异常来检测输入的末尾。但是,用异常作为控制流是对异常的一种错误使用方式。\n", + "\n", + "### 基本文件的输出\n", + "\n", + "`FileWriter` 对象用于向文件写入数据。实际使用时,我们通常会用 `BufferedWriter` 将其包装起来以增加缓冲的功能(可以试试移除此包装来感受一下它对性能的影响——缓冲往往能显著地增加 I/O 操作的性能)。在本例中,为了提供格式化功能,它又被装饰成了 `PrintWriter`。按照这种方式创建的数据文件可作为普通文本文件来读取。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/BasicFileOutput.java\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "\n", + "public class BasicFileOutput {\n", + " static String file = \"BasicFileOutput.dat\";\n", + "\n", + " public static void main(String[] args) {\n", + " try (\n", + " BufferedReader in = new BufferedReader(\n", + " new StringReader(\n", + " BufferedInputFile.read(\n", + " \"BasicFileOutput.java\")));\n", + " PrintWriter out = new PrintWriter(\n", + " new BufferedWriter(new FileWriter(file)))\n", + " ) {\n", + " in.lines().forEach(out::println);\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // Show the stored file:\n", + " System.out.println(BufferedInputFile.read(file));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`try-with-resources` 语句会自动 flush 并关闭文件。\n", + "\n", + "### 文本文件输出快捷方式\n", + "\n", + "Java 5 在 `PrintWriter` 中添加了一个辅助构造器,有了它,你在创建并写入文件时,就不必每次都手动执行一些装饰的工作。下面的代码使用这种快捷方式重写了 `BasicFileOutput.java`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/FileOutputShortcut.java\n", + "// {VisuallyInspectOutput}\n", + "import java.io.*;\n", + "\n", + "public class FileOutputShortcut {\n", + " static String file = \"FileOutputShortcut.dat\";\n", + "\n", + " public static void main(String[] args) {\n", + " try (\n", + " BufferedReader in = new BufferedReader(\n", + " new StringReader(BufferedInputFile.read(\n", + " \"FileOutputShortcut.java\")));\n", + " // Here's the shortcut:\n", + " PrintWriter out = new PrintWriter(file)\n", + " ) {\n", + " in.lines().forEach(out::println);\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.println(BufferedInputFile.read(file));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "使用这种方式仍具备了缓冲的功能,只是现在不必自己手动添加缓冲了。但遗憾的是,其它常见的写入任务都没有快捷方式,因此典型的 I/O 流依旧涉及大量冗余的代码。本书[文件](./17-Files.md)一章中介绍的另一种方式,对此类任务进行了极大的简化。\n", + "\n", + "### 存储和恢复数据\n", + "\n", + "`PrintWriter` 是用来对可读的数据进行格式化。但如果要输出可供另一个“流”恢复的数据,我们可以用 `DataOutputStream` 写入数据,然后用 `DataInputStream` 恢复数据。当然,这些流可能是任何形式,在下面的示例中使用的是一个文件,并且对读写都进行了缓冲。注意 `DataOutputStream` 和 `DataInputStream` 是面向字节的,因此要使用 `InputStream` 和 `OutputStream` 体系的类。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/StoringAndRecoveringData.java\n", + "import java.io.*;\n", + "\n", + "public class StoringAndRecoveringData {\n", + " public static void main(String[] args) {\n", + " try (\n", + " DataOutputStream out = new DataOutputStream(\n", + " new BufferedOutputStream(\n", + " new FileOutputStream(\"Data.txt\")))\n", + " ) {\n", + " out.writeDouble(3.14159);\n", + " out.writeUTF(\"That was pi\");\n", + " out.writeDouble(1.41413);\n", + " out.writeUTF(\"Square root of 2\");\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " try (\n", + " DataInputStream in = new DataInputStream(\n", + " new BufferedInputStream(\n", + " new FileInputStream(\"Data.txt\")))\n", + " ) {\n", + " System.out.println(in.readDouble());\n", + " // Only readUTF() will recover the\n", + " // Java-UTF String properly:\n", + " System.out.println(in.readUTF());\n", + " System.out.println(in.readDouble());\n", + " System.out.println(in.readUTF());\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "3.14159\n", + "That was pi\n", + "1.41413\n", + "Square root of 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们使用 `DataOutputStream` 进行数据写入,那么 Java 就保证了即便读和写数据的平台多么不同,我们仍可以使用 `DataInputStream` 准确地读取数据。这一点很有价值,众所周知,人们曾把大量精力耗费在数据的平台相关性问题上。但现在,只要两个平台上都有 Java,就不会存在这样的问题[^3]。\n", + "\n", + "当我们使用 `DastaOutputStream` 时,写字符串并且让 `DataInputStream` 能够恢复它的唯一可靠方式就是使用 UTF-8 编码,在这个示例中是用 `writeUTF()` 和 `readUTF()` 来实现的。UTF-8 是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。如果我们使用的只是 ASCII 或者几乎都是 ASCII 字符(只占 7 比特),那么就显得及其浪费空间和带宽,所以 UTF-8 将 ASCII 字符编码成一个字节的形式,而非 ASCII 字符则编码成两到三个字节的形式。另外,字符串的长度保存在 UTF-8 字符串的前两个字节中。但是,`writeUTF()` 和 `readUTF()` 使用的是一种适用于 Java 的 UTF-8 变体(JDK 文档中有这些方法的详尽描述),因此如果我们用一个非 Java 程序读取用 `writeUTF()` 所写的字符串时,必须编写一些特殊的代码才能正确读取。\n", + "\n", + "有了 `writeUTF()` 和 `readUTF()`,我们就可以在 `DataOutputStream` 中把字符串和其它数据类型混合使用。因为字符串完全可以作为 Unicode 格式存储,并且可以很容易地使用 `DataInputStream` 来恢复它。\n", + "\n", + "`writeDouble()` 将 `double` 类型的数字存储在流中,并用相应的 `readDouble()` 恢复它(对于其它的书类型,也有类似的方法用于读写)。但是为了保证所有的读方法都能够正常工作,我们必须知道流中数据项所在的确切位置,因为极有可能将保存的 `double` 数据作为一个简单的字节序列、`char` 或其它类型读入。因此,我们必须:要么为文件中的数据采用固定的格式;要么将额外的信息保存到文件中,通过解析额外信息来确定数据的存放位置。注意,对象序列化和 XML (二者都在[附录:对象序列化](Appendix-Object-Serialization.md)中介绍)是存储和读取复杂数据结构的更简单的方式。\n", + "\n", + "### 读写随机访问文件\n", + "\n", + "使用 `RandomAccessFile` 就像是使用了一个 `DataInputStream` 和 `DataOutputStream` 的结合体(因为它实现了相同的接口:`DataInput` 和 `DataOutput`)。另外,我们还可以使用 `seek()` 方法移动文件指针并修改对应位置的值。\n", + "\n", + "在使用 `RandomAccessFile` 时,你必须清楚文件的结构,否则没法正确使用它。`RandomAccessFile` 有一套专门的方法来读写基本数据类型的数据和 UTF-8 编码的字符串:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// iostreams/UsingRandomAccessFile.java\n", + "import java.io.*;\n", + "\n", + "public class UsingRandomAccessFile {\n", + " static String file = \"rtest.dat\";\n", + "\n", + " public static void display() {\n", + " try (\n", + " RandomAccessFile rf =\n", + " new RandomAccessFile(file, \"r\")\n", + " ) {\n", + " for (int i = 0; i < 7; i++)\n", + " System.out.println(\n", + " \"Value \" + i + \": \" + rf.readDouble());\n", + " System.out.println(rf.readUTF());\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " try (\n", + " RandomAccessFile rf =\n", + " new RandomAccessFile(file, \"rw\")\n", + " ) {\n", + " for (int i = 0; i < 7; i++)\n", + " rf.writeDouble(i * 1.414);\n", + " rf.writeUTF(\"The end of the file\");\n", + " rf.close();\n", + " display();\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " try (\n", + " RandomAccessFile rf =\n", + " new RandomAccessFile(file, \"rw\")\n", + " ) {\n", + " rf.seek(5 * 8);\n", + " rf.writeDouble(47.0001);\n", + " rf.close();\n", + " display();\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Value 0: 0.0\n", + "Value 1: 1.414\n", + "Value 2: 2.828\n", + "Value 3: 4.242\n", + "Value 4: 5.656\n", + "Value 5: 7.069999999999999\n", + "Value 6: 8.484\n", + "The end of the file\n", + "Value 0: 0.0\n", + "Value 1: 1.414\n", + "Value 2: 2.828\n", + "Value 3: 4.242\n", + "Value 4: 5.656\n", + "Value 5: 47.0001\n", + "Value 6: 8.484\n", + "The end of the file" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`display()` 方法打开了一个文件,并以 `double` 值的形式显示了其中的七个元素。在 `main()` 中,首先创建了文件,然后打开并修改了它。因为 `double` 总是 8 字节长,所以如果要用 `seek()` 定位到第 5 个(从 0 开始计数) `double` 值,则要传入的地址值应该为 `5*8`。\n", + "\n", + "正如前面所诉,虽然 `RandomAccess` 实现了 `DataInput` 和 `DataOutput` 接口,但实际上它和 I/O 继承体系中的其它部分是分离的。它不支持装饰,故而不能将其与 `InputStream` 及 `OutputStream` 子类中的任何一个组合起来,所以我们也没法给它添加缓冲的功能。\n", + "\n", + "该类的构造器还有第二个必选参数:我们可以指定让 `RandomAccessFile` 以“只读”(r)方式或“读写”\n", + "(rw)方式打开文件。\n", + "\n", + "除此之外,还可以使用 `nio` 中的“内存映射文件”代替 `RandomAccessFile`,这在[附录:新 I/O](Appendix-New-IO.md)中有介绍。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "Java 的 I/O 流类库的确能够满足我们的基本需求:我们可以通过控制台、文件、内存块,甚至因特网进行读写。通过继承,我们可以创建新类型的输入和输出对象。并且我们甚至可以通过重新定义“流”所接受对象类型的 `toString()` 方法,进行简单的扩展。当我们向一个期望收到字符串的方法传送一个非字符串对象时,会自动调用对象的 `toString()` 方法(这是 Java 中有限的“自动类型转换”功能之一)。\n", + "\n", + "在 I/O 流类库的文档和设计中,仍留有一些没有解决的问题。例如,我们打开一个文件用于输出,如果在我们试图覆盖这个文件时能抛出一个异常,这样会比较好(有的编程系统只有当该文件不存在时,才允许你将其作为输出文件打开)。在 Java 中,我们应该使用一个 `File` 对象来判断文件是否存在,因为如果我们用 `FileOutputStream` 或者 `FileWriter` 打开,那么这个文件肯定会被覆盖。\n", + "\n", + "I/O 流类库让我们喜忧参半。它确实挺有用的,而且还具有可移植性。但是如果我们没有理解“装饰器”模式,那么这种设计就会显得不是很直观。所以,它的学习成本相对较高。而且它并不完善,比如说在过去,我不得不编写相当数量的代码去实现一个读取文本文件的工具——所幸的是,Java 7 中的 nio 消除了此类需求。\n", + "\n", + "一旦你理解了装饰器模式,并且开始在某些需要这种灵活性的场景中使用该类库,那么你就开始能从这种设计中受益了。到那时候,为此额外多写几行代码的开销应该不至于让人觉得太麻烦。但还是请务必检查一下,确保使用[文件](./17-Files.md)一章中的库和技术没法解决问题后,再考虑使用本章的 I/O 流库。\n", + "\n", + "[^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。\n", + "\n", + "[^2]: 译者注:“flush” 直译是“清空”,意思是把缓冲中的数据清空,输送到对应的目的地(如文件和屏幕)。\n", + "\n", + "[^3]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Javadoc.ipynb b/jupyter/Appendix-Javadoc.ipynb new file mode 100644 index 00000000..c54561ce --- /dev/null +++ b/jupyter/Appendix-Javadoc.ipynb @@ -0,0 +1,400 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:文档注释\n", + "\n", + "编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,那么每次更改代码时更改文档都会变得很繁琐。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这完整的画面,您需要一个特殊的注释语法来标记文档,以及一个工具来将这些注释提取为有用的表单中。这就是Java所做的。\n", + "\n", + "提取注释的工具称为Javadoc,它是 JDK 安装的一部分。它使用Java编译器中的一些技术来寻找特殊的注释标记。它不仅提取由这些标记所标记的信息,还提取与注释相邻的类名或方法名。通过这种方式,您就可以用最少的工作量来生成合适的程序文档。\n", + "\n", + "Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于Javadoc,您有一个简单的标准来创建文档,因此您可以期望所有Java libraries都有文档。\n", + "\n", + "此外,您可以编写自己的Javadoc处理程序doclet,对于 Javadoc(例如,以不同的格式生成输出)。\n", + "\n", + "以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。\n", + "\n", + "## 句法规则\n", + "\n", + "所有Javadoc指令都发生在以 **/**** 开头(但仍然以 ***/** 结尾)的注释中。\n", + "\n", + "使用Javadoc有两种主要方法:\n", + "\n", + "嵌入HTML或使用“doc标签”。独立的doc标签是指令它以 **@** 开头,放在注释行的开头。(然而,前面的 ***** 将被忽略。)可能会出现内联doc标签\n", + "\n", + "Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被花括号包围。\n", + "\n", + "有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// javadoc/Documentation1.java \n", + "/** 一个类注释 */\n", + "public class Documentation1 {\n", + " /** 一个属性注释 */\n", + " public int i;\n", + " /** 一个方法注释 */ \n", + " public void f() {}\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 \n", + "\n", + "默认情况下,将忽略对 **私有成员** 和包访问成员的注释(请参阅[\"隐藏实现\"](/docs/book/07-Implementation-Hiding.md)一章),并且您将看不到任何输出。 \n", + "\n", + "这是有道理的,因为仅客户端程序员的观点是,在文件外部可以使用 **公共成员** 和 **受保护成员** 。 您可以使用 **-private** 标志和包含 **私人** 成员。\n", + "\n", + "要通过Javadoc处理前面的代码,命令是:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "cmd" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "javadoc Documentation1.java" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览你的类。\n", + "\n", + "## 内嵌 HTML\n", + "\n", + "Javadoc传递未修改的HTML代码,用以生成的HTML文档。这使你可以充分利用HTML。但是,这样做的主要目的是让你格式化代码,例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// javadoc/Documentation2.java\n", + "/**
\n",
+    "* System.out.println(new Date());\n",
+    "* 
\n", + "*/\n", + "public class Documentation2 {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "您你也可以像在其他任何Web文档中一样使用HTML来格式化说明中的文字:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// javadoc/Documentation3.java\n", + "/** You can even insert a list:\n", + "*
    \n", + "*
  1. Item one\n", + "*
  2. Item two\n", + "*
  3. Item three\n", + "*
\n", + "*/\n", + "public class Documentation3 {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意,在文档注释中,Javadoc删除了行首的星号以及前导空格。 Javadoc重新格式化了所有内容,使其符合标准文档的外观。不要将诸如 \\或 \\之类的标题用作嵌入式HTML,因为Javadoc会插入自己的标题,后插入的标题将对其生成的文档产生干扰。\n", + "\n", + "所有类型的注释文档(类,字段和方法)都可以支持嵌入式HTML。\n", + "\n", + "## 示例标签\n", + "\n", + "以下是一些可用于代码文档的Javadoc标记。在尝试使用Javadoc进行任何认真的操作之前,请查阅JDK文档中的Javadoc参考,以了解使用Javadoc的所有不同方法。\n", + "\n", + "### @see\n", + "\n", + "这个标签可以将其他的类连接到文档中,Javadoc 将使用 @see 标记超链接到其他文档中,形式为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@see classname\n", + "@see fully-qualified-classname\n", + "@see fully-qualified-classname#method-name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个都向生成的文档中添加超链接的“另请参阅”条目。 Javadoc 不会检查超链接的有效性。\n", + "\n", + "### {@link package.class#member label}\n", + "\n", + "和 @see 非常相似,不同之处在于它可以内联使用,并使用标签作为超链接文本,而不是“另请参阅”。\n", + "\n", + "### {@docRoot}\n", + "\n", + "生成文档根目录的相对路径。对于显式超链接到文档树中的页面很有用。\n", + "\n", + "### {@inheritDoc}\n", + "\n", + "将文档从此类的最近基类继承到当前文档注释中。\n", + "\n", + "### @version\n", + "\n", + "其形式为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@version version-information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中 version-information 是你认为适合包含的任何重要信息。当在Javadoc命令行上放置 -version 标志时,特别在生成的HTML文档中用于生成version信息。\n", + "\n", + "### @author\n", + "\n", + "其形式为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@author author-information" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "author-information 大概率是你的名字,但是一样可以包含你的 email 地址或者其他合适的信息。当在 Javadoc 命令行上放置 -author 标志的时候,在生成的HTML文档中特别注明了作者信息。\n", + "\n", + "你可以对作者列表使用多个作者标签,但是必须连续放置它们。所有作者信息都集中在生成的HTML中的单个段落中。\n", + "\n", + "### @since\n", + "\n", + "此标记指示此代码的版本开始使用特定功能。例如,它出现在HTML Java文档中,以指示功能首次出现的JDK版本。\n", + "\n", + "### @param\n", + "\n", + "这将生成有关方法参数的文档:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@param parameter-name description" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中parameter-name是方法参数列表中的标识符,description 是可以在后续行中继续的文本。当遇到新的文档标签时,说明被视为完成。@param 标签的可以任意使用,大概每个参数一个。\n", + "\n", + "### @return\n", + "\n", + "这记录了返回值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@return description" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中description给出了返回值的含义。它可延续到后面的行内。\n", + "\n", + "### @throws\n", + "\n", + "一个方法可以产生许多不同类型的异常,所有这些异常都需要描述。异常标记的形式为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "@throws fully-qualified-class-name description" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "fully-qualified-class-name 给出明确的异常分类名称,并且 description (可延续到后面的行内)告诉你为什么这特定类型的异常会在方法调用后出现。\n", + "\n", + "### @deprecated\n", + "\n", + "这表示已被改进的功能取代的功能。deprecated 标记表明你不再使用此特定功能,因为将来有可能将其删除。标记为@不赞成使用的方法会导致编译器在使用时发出警告。在Java 5中,@deprecated Javadoc 标记已被 @Deprecated 注解取代(在[注解]()一章中进行了描述)。 \n", + "\n", + "## 文档示例\n", + "\n", + "**objects/HelloDate.java** 是带有文档注释的例子。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// javadoc/HelloDateDoc.java\n", + "import java.util.*;\n", + "/** The first On Java 8 example program.\n", + " * Displays a String and today's date.\n", + " * @author Bruce Eckel\n", + " * @author www.MindviewInc.com\n", + " * @version 5.0\n", + " */\n", + "public class HelloDateDoc {\n", + " /** Entry point to class & application.\n", + " * @param args array of String arguments\n", + " * @throws exceptions No exceptions thrown\n", + " */\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Hello, it's: \");\n", + " System.out.println(new Date());\n", + " }\n", + "}\n", + "/* Output:\n", + "Hello, it's:\n", + "Tue May 09 06:07:27 MDT 2017\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你可以在Java标准库的源代码中找到许多Javadoc注释文档的示例。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Low-Level-Concurrency.ipynb b/jupyter/Appendix-Low-Level-Concurrency.ipynb new file mode 100644 index 00000000..ec54e455 --- /dev/null +++ b/jupyter/Appendix-Low-Level-Concurrency.ipynb @@ -0,0 +1,2446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:并发底层原理\n", + "\n", + "> 尽管不建议你自己编写底层 Java 并发代码,但是这样通常有助于了解它是如何工作的。\n", + "\n", + "[并发编程](./24-Concurrent-Programming.md) 章节中介绍了一些用于高级并发的概念,包括为 Java 并发编程而最新提出的,更安全的概念( parallel Streams 和 CompletableFutures )。本附录则介绍在 Java 中底层并发概念,因此在阅读本篇时,你能有所了解掌握这些代码。你还会将进一步了解并发的普遍问题。\n", + "\n", + "在 Java 的早期版本中, 底层并发概念是并发编程的重要组成部分。我们会着眼于围绕这些技巧的复杂性以及为何你应该避免它们而谈。 “并发编程” 章节展示最新的 Java 版本(尤其是 Java 8)所提供的改进技巧,这些技巧使得并发的使用,如果本来不容易使用,也会变得更容易些。\n", + "\n", + "\n", + "## 什么是线程?\n", + "\n", + "并发将程序划分成独立分离运行的任务。每个任务都由一个 *执行线程* 来驱动,我们通常将其简称为 *线程* 。而一个 *线程* 就是操作系统进程中单一顺序的控制流。因此,单个进程可以有多个并发执行的任务,但是你的程序使得每个任务都好像有自己的处理器一样。此线程模型为编程带来了便利,它简化了在单一程序中处理变戏法般的多任务过程。操作系统则从处理器上分配时间片到你程序的所有线程中。\n", + "\n", + "Java 并发的核心机制是 **Thread** 类,在该语言最初版本中, **Thread (线程)** 是由程序员直接创建和管理的。随着语言的发展以及人们发现了更好的一些方法,中间层机制 - 特别是 **Executor** 框架 - 被添加进来,以消除自己管理线程时候的心理负担(及错误)。 最终,甚至发展出比 **Executor** 更好的机制,如 [并发编程](./24-Concurrent-Programming.md) 一章所示。\n", + "\n", + "**Thread(线程)** 是将任务关联到处理器的软件概念。虽然创建和使用 **Thread** 类看起来与任何其他类都很相似,但实际上它们是非常不同的。当你创建一个 **Thread** 时,JVM 将分配一大块内存到专为线程保留的特殊区域上,用于提供运行任务时所需的一切,包括:\n", + "\n", + "* 程序计数器,指明要执行的下一个 JVM 字节码指令。\n", + "* 用于支持 Java 代码执行的栈,包含有关此线程已到达当时执行位置所调用方法的信息。它也包含每个正在执行的方法的所有局部变量(包括原语和堆对象的引用)。每个线程的栈通常在 64K 到 1M 之间 [^1] 。\n", + "* 第二个则用于 native code(本机方法代码)执行的栈\n", + "* *thread-local variables* (线程本地变量)的存储区域\n", + "* 用于控制线程的状态管理变量\n", + "\n", + "包括 `main()` 在内的所有代码都会在某个线程内运行。 每当调用一个方法时,当前程序计数器被推到该线程的栈上,然后栈指针向下移动以足够来创建一个栈帧,其栈帧里存储该方法的所有局部变量,参数和返回值。所有基本类型变量都直接在栈上,虽然方法中创建(或方法中使用)对象的任何引用都位于栈帧中,但对象本身存于堆中。这仅且只有一个堆,被程序中所有线程所共享。\n", + "\n", + "除此以外,线程必须绑定到操作系统,这样它就可以在某个时候连接到处理器。这是作为线程构建过程的一部分为你管理的。Java 使用底层操作系统中的机制来管理线程的执行。\n", + "\n", + "### 最佳线程数\n", + "\n", + "如果你查看第 24 章 [并发编程](./24-Concurrent-Programming.md) 中使用 *CachedThreadPool* 的用例,你会发现 **ExecutorService** 为每个我们提交的任务分配一个线程。然而,并行流(**parallel Stream**)在 [**CountingStream.java** ](https://github.com/BruceEckel/OnJava8-Examples/blob/master/concurrent/CountingStream.java\n", + ") 中只分配了 8 个线程(id 中 1-7 为工作线程,8 为 `main()` 方法的主线程,它巧妙地将其用作额外的并行流)。如果你尝试提高 `range()` 方法中的上限值,你会看到没有创建额外的线程。这是为什么?\n", + "\n", + "我们可以查出当前机器上处理器的数量:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "Java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/NumberOfProcessors.java\n", + "\n", + "public class NumberOfProcessors {\n", + " public static void main(String[] args) {\n", + " System.out.println(\n", + " Runtime.getRuntime().availableProcessors());\n", + " }\n", + "}\n", + "/* Output:\n", + "8\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在我的机器上(使用英特尔酷睿i7),我有四个内核,每个内核呈现两个*超线程*(指一种硬件技巧,能在单个处理器上产生非常快速的上下文切换,在某些情况下可以使内核看起来像运行两个硬件线程)。虽然这是 “最近” 计算机上的常见配置(在撰写本文时),但你可能会看到不同的结果,包括 **CountingStream.java ** 中同等数量的默认线程。\n", + "\n", + "你的操作系统可能有办法来查出关于处理器的更多信息,例如,在Windows 10上,按下 “开始” 键,输入 “任务管理器” 和 Enter 键。点击 “详细信息” 。选择 “性能” 标签,你将会看到各种各样的关于你的硬件信息,包括“内核” 和 “逻辑处理器” 。\n", + "\n", + "事实证明,“通用”线程的最佳数量就算是可用处理器的数量(对于特定的问题可能不是这样)。这原因来自在Java线程之间切换上下文的代价:存储被挂起线程的当前状态,并检索另一个线程的当前状态,以便从它进入挂起的位置继续执行。对于 8 个处理器和 8 个(计算密集型)Java线程,JVM 在运行这8个任务时从不需要切换上下文。对于比处理器数量少的任务,分配更多线程没有帮助。\n", + "\n", + "定义了 “逻辑处理器” 数量的 Intel 超线程,但并没有增加计算能力 - 该特性在硬件级别维护额外的线程上下文,从而加快了上下文切换,这有助于提高用户界面的响应能力。对于计算密集型任务,请考虑将线程数量与物理内核(而不是超线程)的数量匹配。尽管Java认为每个超线程都是一个处理器,但这似乎是由于 Intel 对超线程的过度营销造成的错误。尽管如此,为了简化编程,我只允许 JVM 决定默认的线程数。 你将需要试验你的产品应用。 这并不意味着将线程数与处理器数相匹配就适用于所有问题; 相反,它主要用于计算密集型解决方案。\n", + "\n", + "### 我可以创建多少个线程?\n", + "\n", + "Thread(线程)对象的最大部分是用于执行方法的 Java 堆栈。查看 Thread (线程)对象的大小因操作系统而异。该程序通过创建 Thread 对象来测试它,直到 JVM 内存不足为止:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/ThreadSize.java\n", + "// {ExcludeFromGradle} Takes a long time or hangs\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "public class ThreadSize {\n", + " static class Dummy extends Thread {\n", + " @Override\n", + " public void run() { new Nap(1); }\n", + " }\n", + " public static void main(String[] args) {\n", + " ExecutorService exec =\n", + " Executors.newCachedThreadPool();\n", + " int count = 0;\n", + " try {\n", + " while(true) {\n", + " exec.execute(new Dummy());\n", + " count++;\n", + " }\n", + " } catch(Error e) {\n", + " System.out.println(\n", + " e.getClass().getSimpleName() + \": \" + count);\n", + " System.exit(0);\n", + " } finally {\n", + " exec.shutdown();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只要你不断递交任务,**CachedThreadPool** 就会继续创建线程。将 **Dummy** 对象递交到 `execute()` 方法以开始任务,如果线程池无可用线程,则分配一个新线程。执行的暂停方法 `pause()` 运行时间必须足够长,使任务不会开始即完成(从而为新任务释放现有线程)。只要任务不断进入而没有完成,**CachedThreadPool** 最终就会耗尽内存。\n", + "\n", + "我并不总是能够在我尝试的每台机器上造成内存不足的错误。在一台机器上,我看到这样的结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "> java ThreadSize\n", + "OutOfMemoryError: 2816" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以使用 **-Xss** 标记减少每个线程栈分配的内存大小。允许的最小线程栈大小是 64k:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ">java -Xss64K ThreadSize\n", + "OutOfMemoryError: 4952" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们将线程栈大小增加到 2M ,我们就可以分配更少的线程。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ">java -Xss2M ThreadSize\n", + "OutOfMemoryError: 722" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Windows 操作系统默认栈大小是 320K,我们可以通过验证它给出的数字与我们完全不设置栈大小时的数字是大致相同:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ">java -Xss320K ThreadSize\n", + "OutOfMemoryError: 2816" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "你还可以使用 **-Xmx** 标志增加 JVM 的最大内存分配:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "shell" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + ">java -Xss64K -Xmx5M ThreadSize\n", + "OutOfMemoryError: 5703" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "请注意的是操作系统还可能对允许的线程数施加限制。\n", + "\n", + "因此,“我可以拥有多少线程”这一问题的答案是“几千个”。但是,如果你发现自己分配了数千个线程,那么你可能需要重新考虑你的做法; 恰当的问题是“我需要多少线程?”\n", + "\n", + "### The WorkStealingPool (工作窃取线程池)\n", + "\n", + "这是一个 **ExecutorService** ,它使用所有可用的(由JVM报告) 处理器自动创建线程池。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/WorkStealingPool.java\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "class ShowThread implements Runnable {\n", + " @Override\n", + " public void run() {\n", + " System.out.println(\n", + " Thread.currentThread().getName());\n", + " }\n", + "}\n", + "\n", + "public class WorkStealingPool {\n", + " public static void main(String[] args)\n", + " throws InterruptedException {\n", + " System.out.println(\n", + " Runtime.getRuntime().availableProcessors());\n", + " ExecutorService exec =\n", + " Executors.newWorkStealingPool();\n", + " IntStream.range(0, 10)\n", + " .mapToObj(n -> new ShowThread())\n", + " .forEach(exec::execute);\n", + " exec.awaitTermination(1, TimeUnit.SECONDS);\n", + " }\n", + "}\n", + "/* Output:\n", + "8\n", + "ForkJoinPool-1-worker-2\n", + "ForkJoinPool-1-worker-1\n", + "ForkJoinPool-1-worker-2\n", + "ForkJoinPool-1-worker-3\n", + "ForkJoinPool-1-worker-2\n", + "ForkJoinPool-1-worker-1\n", + "ForkJoinPool-1-worker-3\n", + "ForkJoinPool-1-worker-1\n", + "ForkJoinPool-1-worker-4\n", + "ForkJoinPool-1-worker-2\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "工作窃取算法允许已经耗尽输入队列中的工作项的线程从其他队列“窃取”工作项。目标是在处理器之间分配工作项,从而最大限度地利用所有可用的处理器来完成计算密集型任务。这项算法也用于 Java 的fork/join 框架。\n", + "\n", + "\n", + "## 异常捕获\n", + "\n", + "这可能会让你感到惊讶:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SwallowedException.java\n", + "import java.util.concurrent.*;\n", + "\n", + "public class SwallowedException {\n", + " public static void main(String[] args)\n", + " throws InterruptedException {\n", + " ExecutorService exec =\n", + " Executors.newSingleThreadExecutor();\n", + " exec.submit(() -> {\n", + " throw new RuntimeException();\n", + " });\n", + " exec.shutdown();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个程序什么也不输出(然而,如果你用 **execute** 方法替换 `submit()` 方法,你就将会看到异常抛出。这说明在线程中抛出异常是很棘手的,需要特别注意的事情。\n", + "\n", + "你无法捕获到从线程逃逸的异常。一旦异常越过了任务的 `run()` 方法,它就会传递至控制台,除非你采取特殊步骤来捕获此类错误异常。\n", + "\n", + "下面是一个抛出异常的代码,该异常会传递到它的 `run()` 方法之外,而 `main()` 方法会显示运行它时会发生什么:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/ExceptionThread.java\n", + "// {ThrowsException}\n", + "import java.util.concurrent.*;\n", + "\n", + "public class ExceptionThread implements Runnable {\n", + " @Override\n", + " public void run() {\n", + " throw new RuntimeException();\n", + " }\n", + " public static void main(String[] args) {\n", + " ExecutorService es =\n", + " Executors.newCachedThreadPool();\n", + " es.execute(new ExceptionThread());\n", + " es.shutdown();\n", + " }\n", + "}\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"pool-1-thread-1\"\n", + "java.lang.RuntimeException\n", + " at ExceptionThread.run(ExceptionThread.java:8)\n", + " at java.util.concurrent.ThreadPoolExecutor.runW\n", + "orker(ThreadPoolExecutor.java:1142)\n", + " at java.util.concurrent.ThreadPoolExecutor$Work\n", + "er.run(ThreadPoolExecutor.java:617)\n", + " at java.lang.Thread.run(Thread.java:745)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出是(经过调整一些限定符以适应阅读):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Exception in thread \"pool-1-thread-1\" RuntimeException\n", + " at ExceptionThread.run(ExceptionThread.java:9)\n", + " at ThreadPoolExecutor.runWorker(...)\n", + " at ThreadPoolExecutor$Worker.run(...)\n", + " at java.lang.Thread.run(Thread.java:745)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "即使在 `main()` 方法体内包裹 **try-catch** 代码块来捕获异常也不成功:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/NaiveExceptionHandling.java\n", + "// {ThrowsException}\n", + "import java.util.concurrent.*;\n", + "\n", + "public class NaiveExceptionHandling {\n", + " public static void main(String[] args) {\n", + " ExecutorService es =\n", + " Executors.newCachedThreadPool();\n", + " try {\n", + " es.execute(new ExceptionThread());\n", + " } catch(RuntimeException ue) {\n", + " // This statement will NOT execute!\n", + " System.out.println(\"Exception was handled!\");\n", + " } finally {\n", + " es.shutdown();\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "___[ Error Output ]___\n", + "Exception in thread \"pool-1-thread-1\"\n", + "java.lang.RuntimeException\n", + " at ExceptionThread.run(ExceptionThread.java:8)\n", + " at java.util.concurrent.ThreadPoolExecutor.runW\n", + "orker(ThreadPoolExecutor.java:1142)\n", + " at java.util.concurrent.ThreadPoolExecutor$Work\n", + "er.run(ThreadPoolExecutor.java:617)\n", + " at java.lang.Thread.run(Thread.java:745)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这会产生与前一个示例相同的结果:未捕获异常。\n", + "\n", + "为解决这个问题,需要改变 **Executor** (执行器)生成线程的方式。 **Thread.UncaughtExceptionHandler** 是一个添加给每个 **Thread** 对象,用于进行异常处理的接口。\n", + "\n", + "当该线程即将死于未捕获的异常时,将自动调用 `Thread.UncaughtExceptionHandler.uncaughtException()`\n", + " 方法。为了调用该方法,我们创建一个新的 **ThreadFactory** 类型来让 **Thread.UncaughtExceptionHandler** 对象附加到每个它所新创建的 **Thread**(线程)对象上。我们赋值该工厂对象给 **Executors** 对象的 方法,让它的方法来生成新的 **ExecutorService** 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/CaptureUncaughtException.java\n", + "import java.util.concurrent.*;\n", + "\n", + "class ExceptionThread2 implements Runnable {\n", + " @Override\n", + " public void run() {\n", + " Thread t = Thread.currentThread();\n", + " System.out.println(\"run() by \" + t.getName());\n", + " System.out.println(\n", + " \"eh = \" + t.getUncaughtExceptionHandler());\n", + " throw new RuntimeException();\n", + " }\n", + "}\n", + "\n", + "class MyUncaughtExceptionHandler implements\n", + "Thread.UncaughtExceptionHandler {\n", + " @Override\n", + " public void uncaughtException(Thread t, Throwable e) {\n", + " System.out.println(\"caught \" + e);\n", + " }\n", + "}\n", + "\n", + "class HandlerThreadFactory implements ThreadFactory {\n", + " @Override\n", + " public Thread newThread(Runnable r) {\n", + " System.out.println(this + \" creating new Thread\");\n", + " Thread t = new Thread(r);\n", + " System.out.println(\"created \" + t);\n", + " t.setUncaughtExceptionHandler(\n", + " new MyUncaughtExceptionHandler());\n", + " System.out.println(\n", + " \"eh = \" + t.getUncaughtExceptionHandler());\n", + " return t;\n", + " }\n", + "}\n", + "\n", + "public class CaptureUncaughtException {\n", + " public static void main(String[] args) {\n", + " ExecutorService exec =\n", + " Executors.newCachedThreadPool(\n", + " new HandlerThreadFactory());\n", + " exec.execute(new ExceptionThread2());\n", + " exec.shutdown();\n", + " }\n", + "}\n", + "/* Output:\n", + "HandlerThreadFactory@4e25154f creating new Thread\n", + "created Thread[Thread-0,5,main]\n", + "eh = MyUncaughtExceptionHandler@70dea4e\n", + "run() by Thread-0\n", + "eh = MyUncaughtExceptionHandler@70dea4e\n", + "caught java.lang.RuntimeException\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "额外会在代码中添加跟踪机制,用来验证工厂对象创建的线程是否获得新 **UncaughtExceptionHandler** 。现在未捕获的异常由 **uncaughtException** 方法捕获。\n", + "\n", + "上面的示例根据具体情况来设置处理器。如果你知道你将要在代码中处处使用相同的异常处理器,那么更简单的方式是在 **Thread** 类中设置一个 **static**(静态) 字段,并将这个处理器设置为默认的未捕获异常处理器:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SettingDefaultHandler.java\n", + "import java.util.concurrent.*;\n", + "\n", + "public class SettingDefaultHandler {\n", + " public static void main(String[] args) {\n", + " Thread.setDefaultUncaughtExceptionHandler(\n", + " new MyUncaughtExceptionHandler());\n", + " ExecutorService es =\n", + " Executors.newCachedThreadPool();\n", + " es.execute(new ExceptionThread());\n", + " es.shutdown();\n", + " }\n", + "}\n", + "/* Output:\n", + "caught java.lang.RuntimeException\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只有在每个线程没有设置异常处理器时候,默认处理器才会被调用。系统会检查线程专有的版本,如果没有,则检查是否线程组中有专有的 `uncaughtException()` 方法;如果都没有,就会调用 **defaultUncaughtExceptionHandler** 方法。\n", + "\n", + "可以将此方法与 **CompletableFuture** 的改进方法进行比较。\n", + "\n", + "\n", + "## 资源共享\n", + "\n", + "你可以将单线程程序看作一个孤独的实体,在你的问题空间中移动并同一时间只做一件事。因为只有一个实体,你永远不会想到两个实体试图同时使用相同资源的问题:问题犹如两个人试图同时停放在同一个空间,同时走过一扇门,甚至同时说话。\n", + "\n", + "通过并发,事情不再孤单,但现在两个或更多任务可能会相互干扰。如果你不阻止这种冲突,你将有两个任务同时尝试访问同一个银行帐户,打印到同一个打印机,调整同一个阀门,等等。\n", + "\n", + "### 资源竞争\n", + "\n", + "当你启动一个任务来执行某些工作时,可以通过两种不同的方式捕获该工作的结果:通过副作用或通过返回值。\n", + "\n", + "从编程方式上看,副作用似乎更容易:你只需使用结果来操作环境中的某些东西。例如,你的任务可能会执行一些计算,然后直接将其结果写入集合。\n", + "\n", + "伴随这种方式的问题是集合通常是共享资源。当运行多个任务时,任何任务都可能同时读写 *共享资源* 。这揭示了 *资源竞争* 问题,这是处理任务时的主要陷阱之一。\n", + "\n", + "在单线程系统中,你不需要考虑资源竞争,因为你永远不可能同时做多件事。当你有多个任务时,你就必须始终防止资源竞争。\n", + "\n", + "解决此问题的的一种方法是使用能够应对资源竞争的集合,如果多个任务同时尝试对此类集合进行写入,那么此类集合可以应付该问题。在 Java 并发库中,你将发现许多尝试解决资源竞争问题的类;在本附录中,你将看到其中的一些,但覆盖范围并不全面。\n", + "\n", + "请思考以下的示例,其中一个任务负责生成偶数,其他任务则负责消费这些数字。在这里,消费者任务的唯一工作就是检查偶数的有效性。\n", + "\n", + "我们将定义消费者任务 **EvenChecker** 类,以便在后续示例中可复用。为了将 **EvenChecker** 与我们的各种实验生成器类解耦,我们首先创建名为 **IntGenerator** 的抽象类,它包含 **EvenChecker** 必须知道的最低必要方法:它包含 `next()` 方法,以及可以取消它执行生成的方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/IntGenerator.java\n", + "import java.util.concurrent.atomic.AtomicBoolean;\n", + "\n", + "public abstract class IntGenerator {\n", + " private AtomicBoolean canceled =\n", + " new AtomicBoolean();\n", + " public abstract int next();\n", + " public void cancel() { canceled.set(true); }\n", + " public boolean isCanceled() {\n", + " return canceled.get();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`cancel()` 方法改变 **AtomicBoolean** 类型的 **canceled** 标志位的状态, 而 `isCanceled()` 方法则告诉标志位是否设置。因为 **canceled** 标志位是 **AtomicBoolean** 类型,由于它是原子性的,这意味着分配和值返回等简单操作发生时没有中断的可能性,因此你无法在这些简单操作中看到该字段处于中间状态。你将在本附录的后面部分了解有关原子性和 **Atomic** 类的更多信息\n", + "\n", + "任何 **IntGenerator** 都可以使用下面的 **EvenChecker** 类进行测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/EvenChecker.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import onjava.TimedAbort;\n", + "\n", + "public class EvenChecker implements Runnable {\n", + " private IntGenerator generator;\n", + " private final int id;\n", + " public EvenChecker(IntGenerator generator, int id) {\n", + " this.generator = generator;\n", + " this.id = id;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " while(!generator.isCanceled()) {\n", + " int val = generator.next();\n", + " if(val % 2 != 0) {\n", + " System.out.println(val + \" not even!\");\n", + " generator.cancel(); // Cancels all EvenCheckers\n", + " }\n", + " }\n", + " }\n", + " // Test any IntGenerator:\n", + " public static void test(IntGenerator gp, int count) {\n", + " List> checkers =\n", + " IntStream.range(0, count)\n", + " .mapToObj(i -> new EvenChecker(gp, i))\n", + " .map(CompletableFuture::runAsync)\n", + " .collect(Collectors.toList());\n", + " checkers.forEach(CompletableFuture::join);\n", + " }\n", + " // Default value for count:\n", + " public static void test(IntGenerator gp) {\n", + " new TimedAbort(4, \"No odd numbers discovered\");\n", + " test(gp, 10);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`test()` 方法开启了许多访问同一个 **IntGenerator** 的 **EvenChecker**。**EvenChecker** 任务们会不断读取和测试与其关联的 **IntGenerator** 对象中的生成值。如果 **IntGenerator** 导致失败,`test()` 方法会报告并返回。\n", + "\n", + "依赖于 **IntGenerator** 对象的所有 **EvenChecker** 任务都会检查它是否已被取消。如果 `generator.isCanceled()` 返回值为 true ,则 `run()` 方法返回。 任何 **EvenChecker** 任务都可以在 **IntGenerator** 上调用 `cancel()` ,这会导致使用该 **IntGenerator** 的其他所有 **EvenChecker** 正常关闭。\n", + "\n", + "在本设计中,共享公共资源( **IntGenerator** )的任务会监视该资源的终止信号。这消除所谓的竞争条件,其中两个或更多的任务竞争响应某个条件并因此冲突或不一致结果的情况。\n", + "\n", + "你必须仔细考虑并防止并发系统失败的所有可能途径。例如,一个任务不能依赖于另一个任务,因为任务关闭的顺序无法得到保证。这里,通过使任务依赖于非任务对象,我们可以消除潜在的竞争条件。\n", + "\n", + "一般来说,我们假设 `test()` 方法最终失败,因为各个 **EvenChecker** 的任务在 **IntGenerator** 处于 “不恰当的” 状态时,仍能够访问其中的信息。但是,直到 **IntGenerator** 完成许多循环之前,它可能无法检测到问题,具体取决于操作系统的详细信息和其他实现细节。为确保本书的自动构建不会卡住,我们使用 **TimedAbort** 类,在此处定义:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/TimedAbort.java\n", + "// Terminate a program after t seconds\n", + "package onjava;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class TimedAbort {\n", + " private volatile boolean restart = true;\n", + " public TimedAbort(double t, String msg) {\n", + " CompletableFuture.runAsync(() -> {\n", + " try {\n", + " while(restart) {\n", + " restart = false;\n", + " TimeUnit.MILLISECONDS\n", + " .sleep((int)(1000 * t));\n", + " }\n", + " } catch(InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.println(msg);\n", + " System.exit(0);\n", + " });\n", + " }\n", + " public TimedAbort(double t) {\n", + " this(t, \"TimedAbort \" + t);\n", + " }\n", + " public void restart() { restart = true; }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们使用 lambda 表达式创建一个 **Runnable** ,该表达式使用 **CompletableFuture** 的 `runAsync()` 静态方法执行。 `runAsync()` 方法的值会立即返回。 因此,**TimedAbort** 不会保持任何打开的任务,否则已完成任务,但如果它需要太长时间,它仍将终止该任务( **TimedAbort** 有时被称为守护进程)。\n", + "\n", + "**TimedAbort** 还允许你 `restart()` 方法重启任务,在有某些有用的活动进行时保持程序打开。\n", + "\n", + "我们可以看到正在运行的 **TimedAbort** 示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/TestAbort.java\n", + "import onjava.*;\n", + "\n", + "public class TestAbort {\n", + " public static void main(String[] args) {\n", + " new TimedAbort(1);\n", + " System.out.println(\"Napping for 4\");\n", + " new Nap(4);\n", + " }\n", + "}\n", + "/* Output:\n", + "Napping for 4\n", + "TimedAbort 1.0\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你注释掉 **Nap** 创建实列那行,程序执行会立即退出,表明 **TimedAbort** 没有维持程序打开。\n", + "\n", + "我们将看到第一个 **IntGenerator** 示例有一个生成一系列偶数值的 `next()` 方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/EvenProducer.java\n", + "// When threads collide\n", + "// {VisuallyInspectOutput}\n", + "\n", + "public class EvenProducer extends IntGenerator {\n", + " private int currentEvenValue = 0;\n", + " @Override\n", + " public int next() {\n", + " ++currentEvenValue; // [1]\n", + " ++currentEvenValue;\n", + " return currentEvenValue;\n", + " }\n", + " public static void main(String[] args) {\n", + " EvenChecker.test(new EvenProducer());\n", + " }\n", + "}\n", + "/* Output:\n", + "419 not even!\n", + "425 not even!\n", + "423 not even!\n", + "421 not even!\n", + "417 not even!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* [1] 一个任务有可能在另外一个任务执行第一个对 **currentEvenValue** 的自增操作之后,但是没有执行第二个操作之前,调用 `next()` 方法。这将使这个值处于 “不恰当” 的状态。\n", + "\n", + "为了证明这是可能发生的, `EvenChecker.test()` 创建了一组 **EventChecker** 对象,以连续读取 **EvenProducer** 的输出并测试检查每个数值是否都是偶数。如果不是,就会报告错误,而程序也将关闭。\n", + "\n", + "多线程程序的部分问题是,即使存在 bug ,如果失败的可能性很低,程序仍然可以正确显示。\n", + "\n", + "重要的是要注意到自增操作自身需要多个步骤,并且在自增过程中任务可能会被线程机制挂起 - 也就是说,在 Java 中,自增不是原子性的操作。因此,如果不保护任务,即使单纯的自增也不是线程安全的。\n", + "\n", + "该示例程序并不总是在第一次非偶数产生时终止。所有任务都不会立即关闭,这是并发程序的典型特征。\n", + "\n", + "### 解决资源竞争\n", + "\n", + "前面的示例揭示了当你使用线程时的基本问题:你永远不知道线程哪个时刻运行。想象一下坐在一张桌子上,用叉子,将最后一块食物放在盘子上,当叉子到达时,食物突然消失...仅因为你的线程被挂起而另一个用餐者进来吃了食物了。这就是在编写并发程序时要处理的问题。为了使并发工作有效,你需要某种方式来阻止两个任务访问同一个资源,至少在关键时期是这样。\n", + "\n", + "防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它,而在其被解锁时候,另一个任务就可以锁定并使用它,以此类推。如果汽车前排座位是受限资源,那么大喊着 “冲呀” 的孩子就会(在这次旅途过程中)获得该资源的锁。\n", + "\n", + "为了解决线程冲突的问题,基本的并发方案将序列化访问共享资源。这意味着一次只允许一个任务访问共享资源。这通常是通过在访问资源的代码片段周围加上一个子句来实现的,该子句一次只允许一个任务访问这段代码。因为这个子句产生 *互斥* 效果,所以这种机制的通常称为是 *mutex* (互斥量)。\n", + "\n", + "考虑一下屋子里的浴室:多个人(即多个由线程驱动的任务)都希望能独立使用浴室(即共享资源)。为了使用浴室,一个人先敲门来看看是否可用。如果没人的话,他就能进入浴室并锁上门。任何其他想使用浴室的任务就会被 “阻挡”,因此这些任务就在门口等待,直到浴室是可用的。\n", + "\n", + "当浴室使用完毕,就是时候给其他任务进入,这时比喻就有点不准确了。事实上没有人排队,我们也不知道下一个使用浴室是谁,因为线程调度机制并不是确定性的。相反,就好像在浴室前面有一组被阻止的任务一样,当锁定浴室的任务解锁并出现时,线程调度机制将会决定下一个要进入的任务。\n", + "\n", + "Java 以提供关键字 **synchronized** 的形式,为防止资源冲突提供了内置支持。当任务希望执行被 **synchronized** 关键字保护的代码片段的时候,Java 编译器会生成代码以查看锁是否可用。如果可用,该任务获取锁,执行代码,然后释放锁。\n", + "\n", + "共享资源一般是以对象形式存在的内存片段,但也可以是文件、I/O 端口,或者类似打印机的东西。要控制对共享资源的访问,得先把它包装进一个对象。然后把任何访问该资源的方法标记为 **synchronized** 。 如果一个任务在调用其中一个 **synchronized** 方法之内,那么在这个任务从该方法返回之前,其他所有要调用该对象的 **synchronized** 方法的任务都会被阻塞。\n", + "\n", + "通常你会将字段设为 **private**,并仅通过方法访问这些字段。你可用通过使用 **synchronized** 关键字声明方法来防止资源冲突。如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "synchronized void f() { /* ... */ }\n", + "synchronized void g() { /* ... */ }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所有对象都自动包含独立的锁(也称为 *monitor*,即监视器)。当你调用对象上任何 **synchronized** 方法,此对象将被加锁,并且该对象上的的其他 **synchronized** 方法调用只有等到前一个方法执行完成并释放了锁之后才能被调用。如果一个任务对对象调用了 `f()` ,对于同一个对象而言,就只能等到 `f()` 调用结束并释放了锁之后,其他任务才能调用 `f()` 和 `g()`。所以,某个特定对象的所有 **synchronized** 方法共享同一个锁,这个锁可以防止多个任务同时写入对象内存。\n", + "\n", + "在使用并发时,将字段设为 **private** 特别重要;否则,**synchronized** 关键字不能阻止其他任务直接访问字段,从而产生资源冲突。\n", + "\n", + "一个线程可以获取对象的锁多次。如果一个方法调用在同一个对象上的第二个方法,而后者又在同一个对象上调用另一个方法,就会发生这种情况。 JVM 会跟踪对象被锁定的次数。如果对象已解锁,则其计数为 0 。当一个线程首次获得锁时,计数变为 1 。每次同一线程在同一对象上获取另一个锁时,计数就会自增。显然,只有首先获得锁的线程才允许多次获取多个锁。每当线程离开 **synchronized** 方法时,计数递减,直到计数变为 0 ,完全释放锁以给其他线程使用。每个类也有一个锁(作为该类的 **Class** 对象的一部分),因此 **synchronized** 静态方法可以在类范围的基础上彼此锁定,不让同时访问静态数据。\n", + "\n", + "你应该什么时候使用同步呢?可以永远 *Brian* 的同步法则[^2]。\n", + "\n", + "> 如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。\n", + "\n", + "如果在你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关方法。如果只同步其中一个方法,那么其他方法可以忽略对象锁,并且可以不受惩罚地调用。这是很重要的一点:每个访问临界共享资源的方法都必须被同步,否则将不会正确地工作。\n", + "\n", + "### 同步控制 EventProducer\n", + "\n", + "通过在 **EvenProducer.java** 文件中添加 **synchronized** 关键字,可以防止不希望的线程访问:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SynchronizedEvenProducer.java\n", + "// Simplifying mutexes with the synchronized keyword\n", + "import onjava.Nap;\n", + "\n", + "public class\n", + "SynchronizedEvenProducer extends IntGenerator {\n", + " private int currentEvenValue = 0;\n", + " @Override\n", + " public synchronized int next() {\n", + " ++currentEvenValue;\n", + " new Nap(0.01); // Cause failure faster\n", + " ++currentEvenValue;\n", + " return currentEvenValue;\n", + " }\n", + " public static void main(String[] args) {\n", + " EvenChecker.test(new SynchronizedEvenProducer());\n", + " }\n", + "}\n", + "/* Output:\n", + "No odd numbers discovered\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在两个自增操作之间插入 `Nap()` 构造器方法,以提高在 **currentEvenValue** 是奇数的状态时上下文切换的可能性。因为互斥锁可以阻止多个任务同时进入临界区,所有这不会产生失败。第一个进入 `next()` 方法的任务将获得锁,任何试图获取锁的后续任务都将被阻塞,直到第一个任务释放锁。此时,调度机制选择另一个等待锁的任务。通过这种方式,任何时刻只能有一个任务通过互斥锁保护的代码。\n", + "\n", + "\n", + "## volatile 关键字\n", + "\n", + "**volatile** 可能是 Java 中最微妙和最难用的关键字。幸运的是,在现代 Java 中,你几乎总能避免使用它,如果你确实看到它在代码中使用,你应该保持怀疑态度和怀疑 - 这很有可能代码是过时的,或者编写代码的人不清楚使用它在大体上(或两者都有)易变性(**volatile**) 或并发性的后果。\n", + "\n", + "使用 **volatile** 有三个理由。\n", + "\n", + "### 字分裂\n", + "\n", + "当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行[^3],这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。\n", + "\n", + "在缺乏任何其他保护的情况下,用 **volatile** 修饰符定义一个 **long** 或 **double** 变量,可阻止字分裂情况。然而,如果使用 **synchronized** 或 **java.util.concurrent.atomic** 类之一保护这些变量,则 **volatile** 将被取代。此外,**volatile** 不会影响到增量操作并不是原子操作的事实。\n", + "\n", + "### 可见性\n", + "\n", + "第二个问题属于 [Java 并发的四句格言](./24-Concurrent-Programming.md#四句格言)里第二句格言 “一切都重要” 的部分。你必须假设每个任务拥有自己的处理器,并且每个处理器都有自己的本地内存缓存。该缓存准许处理器允许的更快,因为处理器并不总是需要从比起使用缓存显著花费更多时间的主内存中获取数据。\n", + "\n", + "出现这个问题是因为 Java 尝试尽可能地提高执行效率。缓存的主要目的是避免从主内存中读取数据。当并发时,有时不清楚 Java 什么时候应该将值从主内存刷新到本地缓存 — 而这个问题称为 *缓存一致性* ( *cache coherence* )。\n", + "\n", + "每个线程都可以在处理器缓存中存储变量的本地副本。将字段定义为 **volatile** 可以防止这些编译器优化,这样读写就可以直接进入内存,而不会被缓存。一旦该字段发生写操作,所有任务的读操作都将看到更改。如果一个 **volatile** 字段刚好存储在本地缓存,则会立即将其写入主内存,并且该字段的任何读取都始终发生在主内存中。\n", + "\n", + "**volatile** 应该在何时适用于变量:\n", + "\n", + "1. 该变量同时被多个任务访问。\n", + "2. 这些访问中至少有一个是写操作。\n", + "3. 你尝试避免同步 (在现代 Java 中,你可以使用高级工具来避免进行同步)。\n", + "\n", + "举个例字,如果你使用变量作为停止任务的标志值。那么该变量至少必须声明为 **volatile** (尽管这并不一定能保证这种标志的线程安全)。否则,当一个任务更改标志值时,这些更改可以存储在本地处理器缓存中,而不会刷新到主内存。当另一个任务查看标记值时,它不会看到更改。我更喜欢在 [并发编程](./24-Concurrent-Programming.md) 中 [终止耗时任务](./24-Concurrent-Programming.md#终止耗时任务) 章节中使用 **AtomicBoolean** 类型作为标志值的办法\n", + "\n", + "任务对其自身变量所做的任何写操作都始终对该任务可见,因此,如果只在任务中使用变量,你不需要使其变量声明为 **volatile** 。\n", + "\n", + "如果单个线程对变量写入而其他线程只读取它,你可以放弃该变量声明为 **volatile**。通常,如果你有多个线程对变量写入,**volatile** 无法解决你的问题,并且你必须使用 **synchronized** 来防止竞争条件。 这有一个特殊的例外:可以让多个线程对该变量写入,*只要它们不需要先读取它并使用该值创建新值来写入变量* 。如果这些多个线程在结果中使用旧值,则会出现竞争条件,因为其余一个线程之一可能会在你的线程进行计算时修改该变量。即使你开始做对了,想象一下在代码修改或维护过程中忘记和引入一个重大变化是多么容易,或者对于不理解问题的不同程序员来说是多么容易(这在 Java 中尤其成问题因为程序员倾向于严重依赖编译时检查来告诉他们,他们的代码是否正确)。\n", + "\n", + "重要的是要理解原子性和可见性是两个不同的概念。在非 **volatile** 变量上的原子操作是不能保证是否将其刷新到主内存。\n", + "\n", + "同步也会让主内存刷新,所以如果一个变量完全由 **synchronized** 的方法或代码段(或者 **java.util.concurrent.atomic** 库里类型之一)所保护,则不需要让变量用 **volatile**。\n", + "\n", + "### 重排与 *Happen-Before* 原则\n", + "\n", + "只要结果不会改变程序表现,Java 可以通过重排指令来优化性能。然而,重排可能会影响本地处理器缓存与主内存交互的方式,从而产生细微的程序 bug 。直到 Java 5 才理解并解决了这个无法阻止重排的问题。现在,**volatile** 关键字可以阻止重排 **volatile** 变量周围的读写指令。这种重排规则称为 *happens before* 担保原则 。\n", + "\n", + "这项原则保证在 **volatile** 变量读写之前发生的指令先于它们的读写之前发生。同样,任何跟随 **volatile** 变量之后读写的操作都保证发生在它们的读写之后。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/ReOrdering.java\n", + "\n", + "public class ReOrdering implements Runnable {\n", + " int one, two, three, four, five, six;\n", + " volatile int volaTile;\n", + " @Override\n", + " public void run() {\n", + " one = 1;\n", + " two = 2;\n", + " three = 3;\n", + " volaTile = 92;\n", + " int x = four;\n", + " int y = five;\n", + " int z = six;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "例子中 **one**,**two**,**three** 变量赋值操作就可以被重排,只要它们都发生在 **volatile** 变量写操作之前。同样,只要 **volatile** 变量写操作发生在所有语句之前, **x**,**y**,**z** 语句可以被重排。这种 **volatile** (易变性)操作通常称为 *memory barrier* (内存屏障)。 *happens before* 担保原则确保 **volatile** 变量的读写指令不能跨过内存屏障进行重排。\n", + "\n", + "*happens before* 担保原则还有另一个作用:当线程向一个 **volatile** 变量写入时,在线程写入之前的其他所有变量(包括非 **volatile** 变量)也会刷新到主内存。当线程读取一个 **volatile** 变量时,它也会读取其他所有变量(包括非 **volatile** 变量)与 **volatile** 变量一起刷新到主内存。尽管这是一个重要的特性,它解决了 Java 5 版本之前出现的一些非常狡猾的 bug ,但是你不应该依赖这项特性来“自动”使周围的变量变得易变性 ( **volatile** )的 。如果你希望变量是易变性 ( **volatile** )的,那么维护代码的任何人都应该清楚这一点。\n", + "\n", + "### 什么时候使用 volatile\n", + "\n", + "对于 Java 早期版本,编写一个证明需要 **volatile** 的示例并不难。如果你进行搜索,你可以找到这样的例子,但是如果你在 Java 8 中尝试这些例子,它们就不起作用了(我没有找到任何一个)。我努力写这样一个例子,但没什么用。这可能原因是 JVM 或者硬件,或两者都得到了改进。这种效果对现有的应该 **volatile** (易变性) 但不 **volatile** 的存储的程序是有益的;对于此类程序,失误发生的频率要低得多,而且问题更难追踪。\n", + "\n", + "如果你尝试使用 **volatile** ,你可能更应该尝试让一个变量线程安全而不是引起同步的成本。因为 **volatile** 使用起来非常微妙和棘手,所以我建议根本不要使用它;相反,请使用本附录后面介绍的 **java.util.concurrent.atomic** 里面类之一。它们以比同步低得多的成本提供了完全的线程安全性。\n", + "\n", + "如果你正在尝试调试其他人的并发代码,请首先查找使用 **volatile** 的代码并将其替换为**Atomic** 变量。除非你确定程序员对并发性有很高的理解,否则它们很可能会误用 **volatile** 。\n", + "\n", + "\n", + "## 原子性\n", + "\n", + "在 Java 线程的讨论中,经常反复提交但不正确的知识是:“原子操作不需要同步”。 一个 *原子操作* 是不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕。依赖于原子性是很棘手且很危险的,如果你是一个并发编程专家,或者你得到了来自这样的专家的帮助,你才应该使用原子性来代替同步,如果你认为自己足够聪明可以应付这种玩火似的情况,那么请接受下面的测试:\n", + "\n", + "> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步[^4] 。\n", + "\n", + "了解原子性是很有用的,并且知道它与其他高级技术一起用于实现一些更加巧妙的 **java.util.concurrent** 库组件。 但是要坚决抵制自己依赖它的冲动。\n", + "\n", + "原子性可以应用于除 **long** 和 **double** 之外的所有基本类型之上的 “简单操作”。对于读写和写入除 **long** 和 **double** 之外的基本类型变量这样的操作,可以保证它们作为不可分 (原子) 的操作执行。\n", + "\n", + "\n", + "因为原子操作不能被线程机制中断。专家程序员可以利用这个来编写无锁代码(*lock-free code*),这些代码不需要被同步。但即使这样也过于简单化了。有时候,甚至看起来应该是安全的原子操作,实际上也可能不安全。本书的读者通常不会通过前面提到的 Goetz 测试,因此也就不具备用原子操作来替换同步的能力。尝试着移除同步通常是一种表示不成熟优化的信号,并且会给你带来大量的麻烦,可能不会获得太多或任何的好处。\n", + "\n", + "在多核处理器系统,相对于单核处理器而言,可见性问题远比原子性问题多得多。一个任务所做的修改,即使它们是原子性的,也可能对其他任务不可见(例如,修改只是暂时性存储在本地处理器缓存中),因此不同的任务对应用的状态有不同的视图。另一方面,同步机制强制多核处理器系统上的一个任务做出的修改必须在应用程序中是可见的。如果没有同步机制,那么修改时可见性将无法确认。\n", + "\n", + "什么才属于原子操作时?对于属性中的值做赋值和返回操作通常都是原子性的,但是在 C++ 中,甚至下面的操作都可能是原子性的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "c++" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "i++; // Might be atomic in C++\n", + "i += 2; // Might be atomic in C++" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是在 C++ 中,这取决于编译器和处理器。你无法编写出依赖于原子性的 C++ 跨平台代码,因为 C++ [^5]没有像 Java 那样的一致 *内存模型* (memory model)。\n", + "\n", + "在 Java 中,上面的操作肯定不是原子性的,正如下面的方法产生的 JVM 指令中可以看到的那样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/NotAtomic.java\n", + "// {javap -c NotAtomic}\n", + "// {VisuallyInspectOutput}\n", + "\n", + "public class NotAtomic {\n", + " int i;\n", + " void f1() { i++; }\n", + " void f2() { i += 3; }\n", + "}\n", + "/* Output:\n", + "Compiled from \"NotAtomic.java\"\n", + "public class NotAtomic {\n", + " int i;\n", + "\n", + " public NotAtomic();\n", + " Code:\n", + " 0: aload_0\n", + " 1: invokespecial #1 // Method\n", + "java/lang/Object.\"\":()V\n", + " 4: return\n", + "\n", + " void f1();\n", + " Code:\n", + " 0: aload_0\n", + " 1: dup\n", + " 2: getfield #2 // Field\n", + "i:I\n", + " 5: iconst_1\n", + " 6: iadd\n", + " 7: putfield #2 // Field\n", + "i:I\n", + " 10: return\n", + "\n", + " void f2();\n", + " Code:\n", + " 0: aload_0\n", + " 1: dup\n", + " 2: getfield #2 // Field\n", + "i:I\n", + " 5: iconst_3\n", + " 6: iadd\n", + " 7: putfield #2 // Field\n", + "i:I\n", + " 10: return\n", + "}\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每条指令都会产生一个 “get” 和 “put”,它们之间还有一些其他指令。因此在获取指令和放置指令之间,另有一个任务可能会修改这个属性,所有,这些操作不是原子性的。\n", + "\n", + "让我们通过定义一个抽象类来测试原子性的概念,这个抽象类的方法是将一个整数类型进行偶数自增,并且 `run()` 不断地调用这个方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/IntTestable.java\n", + "import java.util.function.*;\n", + "\n", + "public abstract class\n", + "IntTestable implements Runnable, IntSupplier {\n", + " abstract void evenIncrement();\n", + " @Override\n", + " public void run() {\n", + " while(true)\n", + " evenIncrement();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**IntSupplier** 是一个带 `getAsInt()` 方法的函数式接口。\n", + "\n", + "现在我们可以创建一个测试,它作为一个独立的任务启动 `run()` 方法 ,然后获取值来检查它们是否为偶数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/Atomicity.java\n", + "import java.util.concurrent.*;\n", + "import onjava.TimedAbort;\n", + "\n", + "public class Atomicity {\n", + " public static void test(IntTestable it) {\n", + " new TimedAbort(4, \"No failures found\");\n", + " CompletableFuture.runAsync(it);\n", + " while(true) {\n", + " int val = it.getAsInt();\n", + " if(val % 2 != 0) {\n", + " System.out.println(\"failed with: \" + val);\n", + " System.exit(0);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "很容易盲目地应用原子性的概念。在这里,`getAsInt()` 似乎是安全的原子性方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/UnsafeReturn.java\n", + "import java.util.function.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class UnsafeReturn extends IntTestable {\n", + " private int i = 0;\n", + " public int getAsInt() { return i; }\n", + " public synchronized void evenIncrement() {\n", + " i++; i++;\n", + " }\n", + " public static void main(String[] args) {\n", + " Atomicity.test(new UnsafeReturn());\n", + " }\n", + "}\n", + "/* Output:\n", + "failed with: 79\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但是, `Atomicity.test()` 方法还是出现有非偶数的失败。尽管,返回 **i** 变量确实是原子操作,但是同步缺失允许了在对象处于不稳定的中间状态时读取值。最重要的是,由于 **i** 也不是 **volatile** 变量,所以存在可见性问题。包括 `getValue()` 和 `evenIncrement()` 都必须同步(这也顾及到没有使用 **volatile** 修饰的 **i** 变量):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SafeReturn.java\n", + "import java.util.function.*;\n", + "import java.util.concurrent.*;\n", + "\n", + "public class SafeReturn extends IntTestable {\n", + " private int i = 0;\n", + " public synchronized int getAsInt() { return i; }\n", + " public synchronized void evenIncrement() {\n", + " i++; i++;\n", + " }\n", + " public static void main(String[] args) {\n", + " Atomicity.test(new SafeReturn());\n", + " }\n", + "}\n", + "/* Output:\n", + "No failures found\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "只有并发编程专家有能力去尝试做像前面例子情况的优化;再次强调,请遵循 Brain 的同步法则。\n", + "\n", + "### Josh 的序列号\n", + "\n", + "作为第二个示例,考虑某些更简单的东西:创建一个产生序列号的类,灵感启发于 Joshua Bloch 的 *Effective Java Programming Language Guide* (Addison-Wesley 出版社, 2001) 第 190 页。每次调用 `nextSerialNumber()` 都必须返回唯一值。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SerialNumbers.java\n", + "\n", + "public class SerialNumbers {\n", + " private volatile int serialNumber = 0;\n", + " public int nextSerialNumber() {\n", + " return serialNumber++; // Not thread-safe\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SerialNumbers** 是你可以想象到最简单的类,如果你具备 C++ 或者其他底层的知识背景,你可能会认为自增是一个原子操作,因为 C++ 的自增操作通常被单个微处理器指令所实现(尽管不是以任何一致,可靠,跨平台的方式)。但是,正如前面所提到的,Java 自增操作不是原子性的,并且操作同时涉及读取和写入,因此即使在这样一个简单的操作中,也存在有线程问题的空间。\n", + "\n", + "我们在这里加入 volatile ,看看它是否有帮助。然而,真正的问题是 `nextSerialNumber()` 方法在不进行线程同步的情况下访问共享的可变变量值。\n", + "\n", + "为了测试 **SerialNumbers**,我们将创建一个不会耗尽内存的集合,假如需要很长时间来检测问题。这里展示的 **CircularSet** 重用了存储 **int** 变量的内存,最终新值会覆盖旧值(复制的速度通常发生足够快,你也可以使用 **java.util.Set** 来代替):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/CircularSet.java\n", + "// Reuses storage so we don't run out of memory\n", + "import java.util.*;\n", + "\n", + "public class CircularSet {\n", + " private int[] array;\n", + " private int size;\n", + " private int index = 0;\n", + " public CircularSet(int size) {\n", + " this.size = size;\n", + " array = new int[size];\n", + " // Initialize to a value not produced\n", + " // by SerialNumbers:\n", + " Arrays.fill(array, -1);\n", + " }\n", + " public synchronized void add(int i) {\n", + " array[index] = i;\n", + " // Wrap index and write over old elements:\n", + " index = ++index % size;\n", + " }\n", + " public synchronized boolean contains(int val) {\n", + " for(int i = 0; i < size; i++)\n", + " if(array[i] == val) return true;\n", + " return false;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`add()` 和 `contains()` 方法是线程同步的,以防止线程冲突。\n", + "The add() and contains() methods are synchronized to prevent thread collisions.\n", + "\n", + "**SerialNumberChecker** 类包含一个存储最近序列号的 **CircularSet** 变量,以及一个填充数值给 **CircularSet** 和确保它里面的序列号是唯一的 `run()` 方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SerialNumberChecker.java\n", + "// Test SerialNumbers implementations for thread-safety\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "public class SerialNumberChecker implements Runnable {\n", + " private CircularSet serials = new CircularSet(1000);\n", + " private SerialNumbers producer;\n", + " public SerialNumberChecker(SerialNumbers producer) {\n", + " this.producer = producer;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " while(true) {\n", + " int serial = producer.nextSerialNumber();\n", + " if(serials.contains(serial)) {\n", + " System.out.println(\"Duplicate: \" + serial);\n", + " System.exit(0);\n", + " }\n", + " serials.add(serial);\n", + " }\n", + " }\n", + " static void test(SerialNumbers producer) {\n", + " for(int i = 0; i < 10; i++)\n", + " CompletableFuture.runAsync(\n", + " new SerialNumberChecker(producer));\n", + " new Nap(4, \"No duplicates detected\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`test()` 方法创建多个任务来竞争单独的 **SerialNumbers** 对象。这时参于竞争的的 SerialNumberChecker 任务们就会试图生成重复的序列号(这情况在具有更多内核处理器的机器上发生得更快)。\n", + "\n", + "当我们测试基本的 **SerialNumbers** 类,它会失败(产生重复序列号):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SerialNumberTest.java\n", + "\n", + "public class SerialNumberTest {\n", + " public static void main(String[] args) {\n", + " SerialNumberChecker.test(new SerialNumbers());\n", + " }\n", + "}\n", + "/* Output:\n", + "Duplicate: 148044\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**volatile** 在这里没有帮助。要解决这个问题,将 **synchronized** 关键字添加到 `nextSerialNumber()` 方法 :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SynchronizedSerialNumbers.java\n", + "\n", + "public class\n", + "SynchronizedSerialNumbers extends SerialNumbers {\n", + " private int serialNumber = 0;\n", + " public synchronized int nextSerialNumber() {\n", + " return serialNumber++;\n", + " }\n", + " public static void main(String[] args) {\n", + " SerialNumberChecker.test(\n", + " new SynchronizedSerialNumbers());\n", + " }\n", + "}\n", + "/* Output:\n", + "No duplicates detected\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**volatile** 不再是必需的,因为 **synchronized** 关键字保证了 volatile (易变性) 的特性。\n", + "\n", + "读取和赋值原语应该是安全的原子操作。然后,正如在 **UnsafeReturn.java** 中所看到,使用原子操作访问处于不稳定中间状态的对象仍然很容易。对这个问题做出假设既棘手又危险。最明智的做法就是遵循 Brian 的同步规则(如果可以,首先不要共享变量)。\n", + "\n", + "### 原子类\n", + "\n", + "Java 5 引入了专用的原子变量类,例如 **AtomicInteger**、**AtomicLong**、**AtomicReference** 等。这些提供了原子性升级。这些快速、无锁的操作,它们是利用了现代处理器上可用的机器级原子性。\n", + "\n", + "下面,我们可以使用 **atomicinteger** 重写 **unsafereturn.java** 示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/AtomicIntegerTest.java\n", + "import java.util.concurrent.*;\n", + "import java.util.concurrent.atomic.*;\n", + "import java.util.*;\n", + "import onjava.*;\n", + "\n", + "public class AtomicIntegerTest extends IntTestable {\n", + " private AtomicInteger i = new AtomicInteger(0);\n", + " public int getAsInt() { return i.get(); }\n", + " public void evenIncrement() { i.addAndGet(2); }\n", + " public static void main(String[] args) {\n", + " Atomicity.test(new AtomicIntegerTest());\n", + " }\n", + "}\n", + "/* Output:\n", + "No failures found\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在,我们通过使用 **AtomicInteger** 来消除了 **synchronized** 关键字。\n", + "\n", + "下面使用 **AtomicInteger** 来重写 **SynchronizedEvenProducer.java** 示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/AtomicEvenProducer.java\n", + "// Atomic classes: occasionally useful in regular code\n", + "import java.util.concurrent.atomic.*;\n", + "\n", + "public class AtomicEvenProducer extends IntGenerator {\n", + " private AtomicInteger currentEvenValue =\n", + " new AtomicInteger(0);\n", + " @Override\n", + " public int next() {\n", + " return currentEvenValue.addAndGet(2);\n", + " }\n", + " public static void main(String[] args) {\n", + " EvenChecker.test(new AtomicEvenProducer());\n", + " }\n", + "}\n", + "/* Output:\n", + "No odd numbers discovered\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "再次,使用 **AtomicInteger** 消除了对所有其他同步方式的需要。\n", + "\n", + "下面是一个使用 **AtomicInteger** 实现 **SerialNumbers** 的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/AtomicSerialNumbers.java\n", + "import java.util.concurrent.atomic.*;\n", + "\n", + "public class\n", + "AtomicSerialNumbers extends SerialNumbers {\n", + " private AtomicInteger serialNumber =\n", + " new AtomicInteger();\n", + " public synchronized int nextSerialNumber() {\n", + " return serialNumber.getAndIncrement();\n", + " }\n", + " public static void main(String[] args) {\n", + " SerialNumberChecker.test(\n", + " new AtomicSerialNumbers());\n", + " }\n", + "}\n", + "/* Output:\n", + "No duplicates detected\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这些都是对单一字段的简单示例; 当你创建更复杂的类时,你必须确定哪些字段需要保护,在某些情况下,你可能仍然最后在方法上使用 **synchronized** 关键字。\n", + "\n", + "\n", + "## 临界区\n", + "\n", + "有时,你只是想防止多线程访问方法中的部分代码,而不是整个方法。要隔离的代码部分称为临界区,它使用我们用于保护整个方法相同的 **synchronized** 关键字创建,但使用不同的语法。语法如下, **synchronized** 指定某个对象作为锁用于同步控制花括号内的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "synchronized(syncObject) {\n", + " // This code can be accessed\n", + " // by only one task at a time\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这也被称为 *同步控制块* (synchronized block);在进入此段代码前,必须得到 **syncObject** 对象的锁。如果一些其他任务已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。当发生这种情况时,尝试获取该锁的任务就会挂起。线程调度会定期回来并检查锁是否已经释放;如果释放了锁则唤醒任务。\n", + "\n", + "使用同步控制块而不是同步控制整个方法的主要动机是性能(有时,算法确实聪明,但还是要特别警惕来自并发性问题上的聪明)。下面的示例演示了同步控制代码块而不是整个方法可以使方法更容易被其他任务访问。该示例会统计成功访问 `method()` 的计数并且发起一些任务来尝试竞争调用 `method()` 方法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SynchronizedComparison.java\n", + "// speeds up access.\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.concurrent.atomic.*;\n", + "import onjava.Nap;\n", + "\n", + "abstract class Guarded {\n", + " AtomicLong callCount = new AtomicLong();\n", + " public abstract void method();\n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName() +\n", + " \": \" + callCount.get();\n", + " }\n", + "}\n", + "\n", + "class SynchronizedMethod extends Guarded {\n", + " public synchronized void method() {\n", + " new Nap(0.01);\n", + " callCount.incrementAndGet();\n", + " }\n", + "}\n", + "\n", + "class CriticalSection extends Guarded {\n", + " public void method() {\n", + " new Nap(0.01);\n", + " synchronized(this) {\n", + " callCount.incrementAndGet();\n", + " }\n", + " }\n", + "}\n", + "\n", + "class Caller implements Runnable {\n", + " private Guarded g;\n", + " Caller(Guarded g) { this.g = g; }\n", + " private AtomicLong successfulCalls =\n", + " new AtomicLong();\n", + " private AtomicBoolean stop =\n", + " new AtomicBoolean(false);\n", + " @Override\n", + " public void run() {\n", + " new Timer().schedule(new TimerTask() {\n", + " public void run() { stop.set(true); }\n", + " }, 2500);\n", + " while(!stop.get()) {\n", + " g.method();\n", + " successfulCalls.getAndIncrement();\n", + " }\n", + " System.out.println(\n", + " \"-> \" + successfulCalls.get());\n", + " }\n", + "}\n", + "\n", + "public class SynchronizedComparison {\n", + " static void test(Guarded g) {\n", + " List> callers =\n", + " Stream.of(\n", + " new Caller(g),\n", + " new Caller(g),\n", + " new Caller(g),\n", + " new Caller(g))\n", + " .map(CompletableFuture::runAsync)\n", + " .collect(Collectors.toList());\n", + " callers.forEach(CompletableFuture::join);\n", + " System.out.println(g);\n", + " }\n", + " public static void main(String[] args) {\n", + " test(new CriticalSection());\n", + " test(new SynchronizedMethod());\n", + " }\n", + "}\n", + "/* Output:\n", + "-> 243\n", + "-> 243\n", + "-> 243\n", + "-> 243\n", + "CriticalSection: 972\n", + "-> 69\n", + "-> 61\n", + "-> 83\n", + "-> 36\n", + "SynchronizedMethod: 249\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Guarded** 类负责跟踪 **callCount** 中成功调用 `method()` 的次数。**SynchronizedMethod** 的方式是同步控制整个 `method` 方法,而 **CriticalSection** 的方式是使用同步控制块来仅同步 `method` 方法的一部分代码。这样,耗时的 **Nap** 对象可以被排除到同步控制块外。输出会显示 **CriticalSection** 中可用的 `method()` 有多少。\n", + "\n", + "请记住,使用同步控制块是有风险;它要求你确切知道同步控制块外的非同步代码是实际上要线程安全的。\n", + "\n", + "**Caller** 是尝试在给定的时间周期内尽可能多地调用 `method()` 方法(并报告调用次数)的任务。为了构建这个时间周期,我们会使用虽然有点过时但仍然可以很好地工作的 **java.util.Timer** 类。此类接收一个 **TimerTask** 参数, 但该参数并不是函数式接口,所以我们不能使用 **lambda** 表达式,必须显式创建该类对象(在这种情况下,使用匿名内部类)。当超时的时候,定时对象将设置 **AtomicBoolean** 类型的 **stop** 字段为 true ,这样循环就会退出。\n", + "\n", + "`test()` 方法接收一个 **Guarded** 类对象并创建四个 **Caller** 任务。所有这些任务都添加到同一个 **Guarded** 对象上,因此它们竞争来获取使用 `method()` 方法的锁。\n", + "\n", + "你通常会看到从一次运行到下一次运行的输出变化。结果表明, **CriticalSection** 方式比起 **SynchronizedMethod** 方式允许更多地访问 `method()` 方法。这通常是使用 **synchronized** 块取代同步控制整个方法的原因:允许其他任务更多访问(只要这样做是线程安全的)。\n", + "\n", + "### 在其他对象上同步\n", + "\n", + "**synchronized** 块必须给定一个在其上进行同步的对象。并且最合理的方式是,使用其方法正在被调用的当前对象: **synchronized(this)**,这正是前面示例中 **CriticalSection** 采取的方式。在这种方式中,当 **synchronized** 块获得锁的时候,那么该对象其他的 **synchronized** 方法和临界区就不能被调用了。因此,在进行同步时,临界区的作用是减小同步的范围。\n", + "\n", + "有时必须在另一个对象上同步,但是如果你要这样做,就必须确保所有相关的任务都是在同一个任务上同步的。下面的示例演示了当对象中的方法在不同的锁上同步时,两个任务可以同时进入同一对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/SyncOnObject.java\n", + "// Synchronizing on another object\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import onjava.Nap;\n", + "\n", + "class DualSynch {\n", + " ConcurrentLinkedQueue trace =\n", + " new ConcurrentLinkedQueue<>();\n", + " public synchronized void f(boolean nap) {\n", + " for(int i = 0; i < 5; i++) {\n", + " trace.add(String.format(\"f() \" + i));\n", + " if(nap) new Nap(0.01);\n", + " }\n", + " }\n", + " private Object syncObject = new Object();\n", + " public void g(boolean nap) {\n", + " synchronized(syncObject) {\n", + " for(int i = 0; i < 5; i++) {\n", + " trace.add(String.format(\"g() \" + i));\n", + " if(nap) new Nap(0.01);\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "public class SyncOnObject {\n", + " static void test(boolean fNap, boolean gNap) {\n", + " DualSynch ds = new DualSynch();\n", + " List> cfs =\n", + " Arrays.stream(new Runnable[] {\n", + " () -> ds.f(fNap), () -> ds.g(gNap) })\n", + " .map(CompletableFuture::runAsync)\n", + " .collect(Collectors.toList());\n", + " cfs.forEach(CompletableFuture::join);\n", + " ds.trace.forEach(System.out::println);\n", + " }\n", + " public static void main(String[] args) {\n", + " test(true, false);\n", + " System.out.println(\"****\");\n", + " test(false, true);\n", + " }\n", + "}\n", + "/* Output:\n", + "f() 0\n", + "g() 0\n", + "g() 1\n", + "g() 2\n", + "g() 3\n", + "g() 4\n", + "f() 1\n", + "f() 2\n", + "f() 3\n", + "f() 4\n", + "****\n", + "f() 0\n", + "g() 0\n", + "f() 1\n", + "f() 2\n", + "f() 3\n", + "f() 4\n", + "g() 1\n", + "g() 2\n", + "g() 3\n", + "g() 4\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`DualSync.f()` 方法(通过同步整个方法)在 **this** 上同步,而 `g()` 方法有一个在 **syncObject** 上同步的 **synchronized** 块。因此,这两个同步是互相独立的。在 `test()` 方法中运行的两个调用 `f()` 和 `g()` 方法的独立任务演示了这一点。**fNap** 和 **gNap** 标志变量分别指示 `f()` 和 `g()` 是否应该在其 **for** 循环中调用 `Nap()` 方法。例如,当 f() 线程休眠时 ,该线程继续持有它的锁,但是你可以看到这并不阻止调用 `g()` ,反之亦然。\n", + "\n", + "### 使用显式锁对象\n", + "\n", + "**java.util.concurrent** 库包含在 **java.util.concurrent.locks** 中定义的显示互斥锁机制。 必须显式地创建,锁定和解锁 **Lock** 对象,因此它产出的代码没有内置 **synchronized** 关键字那么优雅。然而,它在解决某些类型的问题时更加灵活。下面是使用显式 **Lock** 对象重写 **SynchronizedEvenProducer.java** 代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/MutexEvenProducer.java\n", + "// Preventing thread collisions with mutexes\n", + "import java.util.concurrent.locks.*;\n", + "import onjava.Nap;\n", + "\n", + "public class MutexEvenProducer extends IntGenerator {\n", + " private int currentEvenValue = 0;\n", + " private Lock lock = new ReentrantLock();\n", + " @Override\n", + " public int next() {\n", + " lock.lock();\n", + " try {\n", + " ++currentEvenValue;\n", + " new Nap(0.01); // Cause failure faster\n", + " ++currentEvenValue;\n", + " return currentEvenValue;\n", + " } finally {\n", + " lock.unlock();\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " EvenChecker.test(new MutexEvenProducer());\n", + " }\n", + "}\n", + "/*\n", + "No odd numbers discovered\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**MutexEvenProducer** 添加一个名为 **lock** 的互斥锁并在 `next()` 中使用 `lock()` 和 `unlock()` 方法创建一个临界区。当你使用 **Lock** 对象时,使用下面显示的习惯用法很重要:在调用 `Lock()` 之后,你必须放置 **try-finally** 语句,该语句在 **finally** 子句中带有 `unlock()` 方法 - 这是确保锁总是被释放的惟一方法。注意,**return** 语句必须出现在 **try** 子句中,以确保 **unlock()** 不会过早发生并将数据暴露给第二个任务。\n", + "\n", + "尽管 **try-finally** 比起使用 **synchronized** 关键字需要用得更多代码,但它也代表了显式锁对象的优势之一。如果使用 **synchronized** 关键字失败,就会抛出异常,但是你没有机会进行任何清理以保持系统处于良好状态。而使用显式锁对象,可以使用 **finally** 子句在系统中维护适当的状态。\n", + "\n", + "一般来说,当你使用 **synchronized** 的时候,需要编写的代码更少,并且用户出错的机会也大大减少,因此通常只在解决特殊问题时使用显式锁对象。例如,使用 **synchronized** 关键字,你不能尝试获得锁并让其失败,或者你在一段时间内尝试获得锁,然后放弃 - 为此,你必须使用这个并发库。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/AttemptLocking.java\n", + "// Locks in the concurrent library allow you\n", + "// to give up on trying to acquire a lock\n", + "import java.util.concurrent.*;\n", + "import java.util.concurrent.locks.*;\n", + "import onjava.Nap;\n", + "\n", + "public class AttemptLocking {\n", + " private ReentrantLock lock = new ReentrantLock();\n", + " public void untimed() {\n", + " boolean captured = lock.tryLock();\n", + " try {\n", + " System.out.println(\"tryLock(): \" + captured);\n", + " } finally {\n", + " if(captured)\n", + " lock.unlock();\n", + " }\n", + " }\n", + " public void timed() {\n", + " boolean captured = false;\n", + " try {\n", + " captured = lock.tryLock(2, TimeUnit.SECONDS);\n", + " } catch(InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " try {\n", + " System.out.println(\n", + " \"tryLock(2, TimeUnit.SECONDS): \" + captured);\n", + " } finally {\n", + " if(captured)\n", + " lock.unlock();\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " final AttemptLocking al = new AttemptLocking();\n", + " al.untimed(); // True -- lock is available\n", + " al.timed(); // True -- lock is available\n", + " // Now create a second task to grab the lock:\n", + " CompletableFuture.runAsync( () -> {\n", + " al.lock.lock();\n", + " System.out.println(\"acquired\");\n", + " });\n", + " new Nap(0.1); // Give the second task a chance\n", + " al.untimed(); // False -- lock grabbed by task\n", + " al.timed(); // False -- lock grabbed by task\n", + " }\n", + "}\n", + "/* Output:\n", + "tryLock(): true\n", + "tryLock(2, TimeUnit.SECONDS): true\n", + "acquired\n", + "tryLock(): false\n", + "tryLock(2, TimeUnit.SECONDS): false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**ReentrantLock** 可以尝试或者放弃获取锁,因此如果某些任务已经拥有锁,你可以决定放弃并执行其他操作,而不是一直等到锁释放,就像 `untimed()` 方法那样。而在 `timed()` 方法中,则尝试获取可能在 2 秒后没成功而放弃的锁。在 `main()` 方法中,一个单独的线程被匿名类所创建,并且它会获得锁,因此让 `untimed()` 和 `timed() ` 方法有东西可以去竞争。\n", + "\n", + "显式锁比起内置同步锁提供更细粒度的加锁和解锁控制。这对于实现专门的同步并发结构,比如用于遍历链表节点的 *交替锁* ( *hand-over-hand locking* ) ,也称为 *锁耦合* ( *lock coupling* )- 该遍历代码要求必须在当前节点的解锁之前捕获下一个节点的锁。\n", + "\n", + "\n", + "## 库组件\n", + "\n", + "**java.util.concurrent** 库提供大量旨在解决并发问题的类,可以帮助你生成更简单,更鲁棒的并发程序。但请注意,这些工具是比起并行流和 **CompletableFuture** 更底层的机制。\n", + "\n", + "在本节中,我们将看一些使用不同组件的示例,然后讨论一下 *lock-free*(无锁) 库组件是如何工作的。\n", + "\n", + "### DelayQueue\n", + "\n", + "这是一个无界阻塞队列 ( **BlockingQueue** ),用于放置实现了 **Delayed** 接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,因此队首对象的延迟到期的时间最长。如果没有任何延迟到期,那么就不会有队首元素,并且 `poll()` 将返回 **null**(正因为这样,你不能将 **null** 放置到这种队列中)。\n", + "\n", + "下面是一个示例,其中的 **Delayed** 对象自身就是任务,而 **DelayedTaskConsumer** 将最“紧急”的任务(到期时间最长的任务)从队列中取出,然后运行它。注意的是这样 **DelayQueue** 就成为了优先级队列的一种变体。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/DelayQueueDemo.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import static java.util.concurrent.TimeUnit.*;\n", + "\n", + "class DelayedTask implements Runnable, Delayed {\n", + " private static int counter = 0;\n", + " private final int id = counter++;\n", + " private final int delta;\n", + " private final long trigger;\n", + " protected static List sequence =\n", + " new ArrayList<>();\n", + " DelayedTask(int delayInMilliseconds) {\n", + " delta = delayInMilliseconds;\n", + " trigger = System.nanoTime() +\n", + " NANOSECONDS.convert(delta, MILLISECONDS);\n", + " sequence.add(this);\n", + " }\n", + " @Override\n", + " public long getDelay(TimeUnit unit) {\n", + " return unit.convert(\n", + " trigger - System.nanoTime(), NANOSECONDS);\n", + " }\n", + " @Override\n", + " public int compareTo(Delayed arg) {\n", + " DelayedTask that = (DelayedTask)arg;\n", + " if(trigger < that.trigger) return -1;\n", + " if(trigger > that.trigger) return 1;\n", + " return 0;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " System.out.print(this + \" \");\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return\n", + " String.format(\"[%d] Task %d\", delta, id);\n", + " }\n", + " public String summary() {\n", + " return String.format(\"(%d:%d)\", id, delta);\n", + " }\n", + " public static class EndTask extends DelayedTask {\n", + " EndTask(int delay) { super(delay); }\n", + " @Override\n", + " public void run() {\n", + " sequence.forEach(dt ->\n", + " System.out.println(dt.summary()));\n", + " }\n", + " }\n", + "}\n", + "\n", + "public class DelayQueueDemo {\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " DelayQueue tasks =\n", + " Stream.concat( // Random delays:\n", + " new Random(47).ints(20, 0, 4000)\n", + " .mapToObj(DelayedTask::new),\n", + " // Add the summarizing task:\n", + " Stream.of(new DelayedTask.EndTask(4000)))\n", + " .collect(Collectors\n", + " .toCollection(DelayQueue::new));\n", + " while(tasks.size() > 0)\n", + " tasks.take().run();\n", + " }\n", + "}\n", + "/* Output:\n", + "[128] Task 12 [429] Task 6 [551] Task 13 [555] Task 2\n", + "[693] Task 3 [809] Task 15 [961] Task 5 [1258] Task 1\n", + "[1258] Task 20 [1520] Task 19 [1861] Task 4 [1998] Task\n", + "17 [2200] Task 8 [2207] Task 10 [2288] Task 11 [2522]\n", + "Task 9 [2589] Task 14 [2861] Task 18 [2868] Task 7\n", + "[3278] Task 16 (0:4000)\n", + "(1:1258)\n", + "(2:555)\n", + "(3:693)\n", + "(4:1861)\n", + "(5:961)\n", + "(6:429)\n", + "(7:2868)\n", + "(8:2200)\n", + "(9:2522)\n", + "(10:2207)\n", + "(11:2288)\n", + "(12:128)\n", + "(13:551)\n", + "(14:2589)\n", + "(15:809)\n", + "(16:3278)\n", + "(17:1998)\n", + "(18:2861)\n", + "(19:1520)\n", + "(20:1258)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**DelayedTask** 包含一个称为 **sequence** 的 **List<DelayedTask>** ,它保存了任务被创建的顺序,因此我们可以看到排序是按照实际发生的顺序执行的。\n", + "\n", + "**Delay** 接口有一个方法, `getDelay()` , 该方法用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期了。这个方法强制我们去使用 **TimeUnit** 类,因为这就是参数类型。这会产生一个非常方便的类,因为你可以很容易地转换单位而无需作任何声明。例如,**delta** 的值是以毫秒为单位存储的,但是 `System.nanoTime()` 产生的时间则是以纳秒为单位的。你可以转换 **delta** 的值,方法是声明它的单位以及你希望以什么单位来表示,就像下面这样:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "NANOSECONDS.convert(delta, MILLISECONDS);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `getDelay()` 中, 所希望的单位是作为 **unit** 参数传递进来的,你使用它将当前时间与触发时间之间的差转换为调用者要求的单位,而无需知道这些单位是什么(这是*策略*设计模式的一个简单示例,在这种模式中,算法的一部分是作为参数传递进来的)。\n", + "\n", + "为了排序, **Delayed** 接口还继承了 **Comparable** 接口,因此必须实现 `compareTo()` , 使其可以产生合理的比较。\n", + "\n", + "从输出中可以看到,任务创建的顺序对执行顺序没有任何影响 - 相反,任务是按照所期望的延迟顺序所执行的。\n", + "\n", + "### PriorityBlockingQueue\n", + "\n", + "这是一个很基础的优先级队列,它具有可阻塞的读取操作。在下面的示例中, **Prioritized** 对象会被赋予优先级编号。几个 **Producer** 任务的实例会插入 **Prioritized** 对象到 **PriorityBlockingQueue** 中,但插入之间会有随机延时。然后,单个 **Consumer** 任务在执行 `take()` 时会显示多个选项,**PriorityBlockingQueue** 会将当前具有最高优先级的 **Prioritized** 对象提供给它。\n", + "\n", + "在 **Prioritized** 中的静态变量 **counter** 是 **AtomicInteger** 类型。这是必要的,因为有多个 **Producer** 并行运行;如果不是 **AtomicInteger** 类型,你将会看到重复的 **id** 号。 这个问题在 [并发编程](./24-Concurrent-Programming.md) 的 [构造函数非线程安全](./24-Concurrent-Programming.md) 一节中讨论过。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// lowlevel/PriorityBlockingQueueDemo.java\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.concurrent.*;\n", + "import java.util.concurrent.atomic.*;\n", + "import onjava.Nap;\n", + "\n", + "class Prioritized implements Comparable {\n", + " private static AtomicInteger counter =\n", + " new AtomicInteger();\n", + " private final int id = counter.getAndIncrement();\n", + " private final int priority;\n", + " private static List sequence =\n", + " new CopyOnWriteArrayList<>();\n", + " Prioritized(int priority) {\n", + " this.priority = priority;\n", + " sequence.add(this);\n", + " }\n", + " @Override\n", + " public int compareTo(Prioritized arg) {\n", + " return priority < arg.priority ? 1 :\n", + " (priority > arg.priority ? -1 : 0);\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return String.format(\n", + " \"[%d] Prioritized %d\", priority, id);\n", + " }\n", + " public void displaySequence() {\n", + " int count = 0;\n", + " for(Prioritized pt : sequence) {\n", + " System.out.printf(\"(%d:%d)\", pt.id, pt.priority);\n", + " if(++count % 5 == 0)\n", + " System.out.println();\n", + " }\n", + " }\n", + " public static class EndSentinel extends Prioritized {\n", + " EndSentinel() { super(-1); }\n", + " }\n", + "}\n", + "\n", + "class Producer implements Runnable {\n", + " private static AtomicInteger seed =\n", + " new AtomicInteger(47);\n", + " private SplittableRandom rand =\n", + " new SplittableRandom(seed.getAndAdd(10));\n", + " private Queue queue;\n", + " Producer(Queue q) {\n", + " queue = q;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " rand.ints(10, 0, 20)\n", + " .mapToObj(Prioritized::new)\n", + " .peek(p -> new Nap(rand.nextDouble() / 10))\n", + " .forEach(p -> queue.add(p));\n", + " queue.add(new Prioritized.EndSentinel());\n", + " }\n", + "}\n", + "\n", + "class Consumer implements Runnable {\n", + " private PriorityBlockingQueue q;\n", + " private SplittableRandom rand =\n", + " new SplittableRandom(47);\n", + " Consumer(PriorityBlockingQueue q) {\n", + " this.q = q;\n", + " }\n", + " @Override\n", + " public void run() {\n", + " while(true) {\n", + " try {\n", + " Prioritized pt = q.take();\n", + " System.out.println(pt);\n", + " if(pt instanceof Prioritized.EndSentinel) {\n", + " pt.displaySequence();\n", + " break;\n", + " }\n", + " new Nap(rand.nextDouble() / 10);\n", + " } catch(InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "public class PriorityBlockingQueueDemo {\n", + " public static void main(String[] args) {\n", + " PriorityBlockingQueue queue =\n", + " new PriorityBlockingQueue<>();\n", + " CompletableFuture.runAsync(new Producer(queue));\n", + " CompletableFuture.runAsync(new Producer(queue));\n", + " CompletableFuture.runAsync(new Producer(queue));\n", + " CompletableFuture.runAsync(new Consumer(queue))\n", + " .join();\n", + " }\n", + "}\n", + "/* Output:\n", + "[15] Prioritized 2\n", + "[17] Prioritized 1\n", + "[17] Prioritized 5\n", + "[16] Prioritized 6\n", + "[14] Prioritized 9\n", + "[12] Prioritized 0\n", + "[11] Prioritized 4\n", + "[11] Prioritized 12\n", + "[13] Prioritized 13\n", + "[12] Prioritized 16\n", + "[14] Prioritized 18\n", + "[15] Prioritized 23\n", + "[18] Prioritized 26\n", + "[16] Prioritized 29\n", + "[12] Prioritized 17\n", + "[11] Prioritized 30\n", + "[11] Prioritized 24\n", + "[10] Prioritized 15\n", + "[10] Prioritized 22\n", + "[8] Prioritized 25\n", + "[8] Prioritized 11\n", + "[8] Prioritized 10\n", + "[6] Prioritized 31\n", + "[3] Prioritized 7\n", + "[2] Prioritized 20\n", + "[1] Prioritized 3\n", + "[0] Prioritized 19\n", + "[0] Prioritized 8\n", + "[0] Prioritized 14\n", + "[0] Prioritized 21\n", + "[-1] Prioritized 28\n", + "(0:12)(2:15)(1:17)(3:1)(4:11)\n", + "(5:17)(6:16)(7:3)(8:0)(9:14)\n", + "(10:8)(11:8)(12:11)(13:13)(14:0)\n", + "(15:10)(16:12)(17:12)(18:14)(19:0)\n", + "(20:2)(21:0)(22:10)(23:15)(24:11)\n", + "(25:8)(26:18)(27:-1)(28:-1)(29:16)\n", + "(30:11)(31:6)(32:-1)\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "与前面的示例一样,**Prioritized** 对象的创建顺序在 **sequence** 的 **list** 对象上所记入,以便与实际执行顺序进行比较。 **EndSentinel** 是用于告知 **Consumer** 对象关闭的特殊类型。\n", + "\n", + "**Producer** 使用 **AtomicInteger** 变量为 **SplittableRandom** 设置随机生成种子,以便不同的 **Producer** 生成不同的队列。 这是必需的,因为多个生产者并行创建,如果不是这样,创建过程并不会是线程安全的。\n", + "\n", + "**Producer** 和 **Consumer** 通过 **PriorityBlockingQueue** 相互连接。因为阻塞队列的性质提供了所有必要的同步,因为阻塞队列的性质提供了所有必要的同步,请注意,显式同步是并不需要的 — 从队列中读取数据时,你不用考虑队列中是否有任何元素,因为队列在没有元素时将阻塞读取。\n", + "\n", + "### 无锁集合\n", + "\n", + "[集合](./12-Collections.md) 章节强调集合是基本的编程工具,这也要求包含并发性。因此,早期的集合比如 **Vector** 和 **Hashtable** 有许多使用 **synchronized** 机制的方法。当这些集合不是在多线程应用中使用时,这就导致了不可接收的开销。在 Java 1.2 版本中,新的集合库是非同步的,而给 **Collection** 类赋予了各种 **static** **synchronized** 修饰的方法来同步不同的集合类型。虽然这是一个改进,因为它让你可以选择是否对集合使用同步,但是开销仍然基于同步锁定。 Java 5 版本添加新的集合类型,专门用于增加线程安全性能,使用巧妙的技术来消除锁定。\n", + "\n", + "无锁集合有一个有趣的特性:只要读取者仅能看到已完成修改的结果,对集合的修改就可以同时发生在读取发生时。这是通过一些策略实现的。为了让你了解它们是如何工作的,我们来看看其中的一些。\n", + "\n", + "#### 复制策略\n", + "\n", + "使用“复制”策略,修改是在数据结构一部分的单独副本(或有时是整个数据的副本)上进行的,并且在整个修改过程期间这个副本是不可见的。仅当修改完成时,修改后的结构才与“主”数据结构安全地交换,然后读取者才会看到修改。\n", + "\n", + "在 **CopyOnWriteArrayList** ,写入操作会复制整个底层数组。保留原来的数组,以便在修改复制的数组时可以线程安全地进行读取。当修改完成后,原子操作会将其交换到新数组中,以便新的读取操作能够看到新数组内容。 **CopyOnWriteArrayList** 的其中一个好处是,当多个迭代器遍历和修改列表时,它不会抛出 **ConcurrentModificationException** 异常,因此你不用就像过去必须做的那样,编写特殊的代码来防止此类异常。\n", + "\n", + "**CopyOnWriteArraySet** 使用 **CopyOnWriteArrayList** 来实现其无锁行为。\n", + "\n", + "**ConcurrentHashMap** 和 **ConcurrentLinkedQueue** 使用类似的技术来允许并发读写,但是只复制和修改集合的一部分,而不是整个集合。然而,读取者仍然不会看到任何不完整的修改。**ConcurrentHashMap** **不会抛出concurrentmodificationexception** 异常。\n", + "\n", + "#### 比较并交换 (CAS)\n", + "\n", + "在 比较并交换 (CAS) 中,你从内存中获取一个值,并在计算新值时保留原始值。然后使用 CAS 指令,它将原始值与当前内存中的值进行比较,如果这两个值是相等的,则将内存中的旧值替换为计算新值的结果,所有操作都在一个原子操作中完成。如果原始值比较失败,则不会进行交换,因为这意味着另一个线程同时修改了内存。在这种情况下,你的代码必须再次尝试,获取一个新的原始值并重复该操作。\n", + "\n", + "如果内存仅轻量竞争,CAS操作几乎总是在没有重复尝试的情况下完成,因此它非常快。相反,**synchronized** 操作需要考虑每次获取和释放锁的成本,这要昂贵得多,而且没有额外的好处。随着内存竞争的增加,使用 CAS 的操作会变慢,因为它必须更频繁地重复自己的操作,但这是对更多资源竞争的动态响应。这确实是一种优雅的方法。\n", + "\n", + "最重要的是,许多现代处理器的汇编语言中都有一条 CAS 指令,并且也被 JVM 中的 CAS 操作(例如 **Atomic** 类中的操作)所使用。CAS 指令在硬件层面中是原子性的,并且与你所期望的操作一样快。\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "本附录主要是为了让你在遇到底层并发代码时能对此有一定的了解,尽管本文还远没对这个主题进行全面的讨论。为此,你需要先从阅读由 Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea (Addison-Wesley 出版社, 2006)所著作的 *Java Concurrency in Practice* (国内译名:Java并发编程实战)开始了解。理想情况下,这本书会完全吓跑你在 Java 中尝试去编写底层并发代码。如果没有,那么你几乎肯定患上了达克效应(DunningKruger Effect),这是一种认知偏差,“你知道的越少,对自己的能力就越有信心”。请记住,当前的语言设计人员仍然在清理早期语言设计人员过于自信造成的混乱(例如,查看 Thread 类中有多少方法被弃用,而 volatile 直到 Java 5 才正确工作)。\n", + "\n", + "以下是并发编程的步骤:\n", + "\n", + "1. 不要使用它。想一些其他方法来使你写的程序变的更快。\n", + "2. 如果你必须使用它,请使用在 [并发编程](./24-Concurrent-Programming.md) - parallel Streams and CompletableFutures 中展示的现代高级工具。\n", + "3. 不要在任务间共享变量,在任务之间必须传递的任何信息都应该使用 Java.util.concurrent 库中的并发数据结构。\n", + "4. 如果必须在任务之间共享变量,请使用 java.util.concurrent.atomic 里面其中一种类型,或在任何直接或间接访问这些变量的方法上应用 synchronized。 当你不这样做时,很容易被愚弄,以为你已经把所有东西都包括在内。 说真的,尝试使用步骤 3。\n", + "5. 如果步骤 4 产生的结果太慢,你可以尝试使用volatile 或其他技术来调整代码,但是如果你正在阅读本书并认为你已经准备好尝试这些方法,那么你就超出了你的深度。 返回步骤#1。\n", + "\n", + "通常可以只使用 java.util.concurrent 库组件来编写并发程序,完全避免来自应用 volatile 和 synchronized 的挑战。注意,我可以通过 [并发编程](./24-Concurrent-Programming.md) 中的示例来做到这一点。\n", + "\n", + "[^1]: 在某些平台上,特别是 Windows ,默认值可能非常难以查明。你可以使用 -Xss 标志调整堆栈大小。\n", + "\n", + "[^2]: 引自 Brian Goetz, Java Concurrency in Practice 一书的作者 , 该书由 Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea 联合著作 (Addison-Wesley 出版社, 2006)。↩\n", + "\n", + "[^3]: 请注意,在64位处理器上可能不会发生这种情况,从而消除了这个问题。\n", + "\n", + "[^4]: 这个测试的推论是,“如果某人表示线程是容易并且简单的,请确保这个人没有对你的项目做出重要的决策。如果那个人已经做出,那么你就已经陷入麻烦之中了。”\n", + "\n", + "[^5]: 这在即将产生的 C++ 的标准中得到了补救。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-New-IO.ipynb b/jupyter/Appendix-New-IO.ipynb new file mode 100644 index 00000000..e5cd36b7 --- /dev/null +++ b/jupyter/Appendix-New-IO.ipynb @@ -0,0 +1,1446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:新IO\n", + "\n", + "\n", + "> Java 新I/O 库是在 1.4 版本引入到 `Java .nio.* package` 中的,旨在更快速。\n", + "\n", + "实际上,新 I/O 使用 **NIO**(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 **NIO** 的种种优点。即使我们不显式地使用 **NIO** 方式来编写代码,也能带来性能和速度的提高。这种提升不仅仅体现在文件读写(File I/O),同时也体现在网络读写(Network I/O)中。例如,网络编程。\n", + "\n", + "速度的提升来自于使用了更接近操作系统 I/O 执行方式的结构:**Channel**(通道) 和 **Buffer**(缓冲区)。我们可以想象一个煤矿:通道就是连接矿层(数据)的矿井,缓冲区是运送煤矿的小车。通过小车装煤,再从车里取矿。换句话说,我们不能直接和 **Channel** 交互; 我们需要与 **Buffer** 交互并将 **Buffer** 中的数据发送到 **Channel** 中;**Channel** 需要从 **Buffer** 中提取或放入数据。\n", + "\n", + "本篇我们将深入探讨 `nio` 包。虽然 像 I/O 流这样的高级库使用了 **NIO**,但多数时候,我们考虑这个层次的问题。使用Java 7 和 8 版本,理想情况下我们甚至不必费心去处理 I/O 流。当然,一些特殊情况除外。在[文件](./17-Files.md)(**File**)一章中基本涵盖了我们日常使用的相关内容。只有在遇到性能瓶颈(例如内存映射文件)或创建自己的 I/O 库时,我们才需要去理解 **NIO**。\n", + "\n", + "\n", + "\n", + "## ByteBuffer\n", + "\n", + "\n", + "有且仅有 **ByteBuffer**(字节缓冲区,保存原始字节的缓冲区)这一类型可直接与通道交互。查看 `java.nio.`**ByteBuffer** 的 JDK 文档,你会发现它是相当基础的:通过初始化某个大小的存储空间,再使用一些方法以原始字节形式或原始数据类型来放置和获取数据。但是我们无法直接存放对象,即使是最基本的 **String** 类型数据。这是一个相当底层的操作,也正因如此,使得它与大多数操作系统的映射更加高效。\n", + "\n", + "\n", + "旧式 I/O 中的三个类分别被更新成 **FileChannel**(文件通道),分别是:**FileInputStream**、**FileOutputStream**,以及用于读写的 **RandomAccessFile** 类。\n", + "\n", + "注意,这些都是符合底层 **NIO** 特性的字节操作流。 另外,还有 **Reader** 和 **Writer** 字符模式的类是不产生通道的。但 `java.nio.channels.`**Channels** 类具有从通道中生成 **Reader** 和 **Writer** 的实用方法。\n", + "\n", + "下面来练习上述三种类型的流生成可读、可写、可读/写的通道:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们不保证这段代码用于其他用途时是否有效\n", + "// 访问 http://OnJava8.com 了解更多信息\n", + "// 从流中获取通道\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class GetChannel {\n", + " private static String name = \"data.txt\";\n", + " private static final int BSIZE = 1024;\n", + " public static void main(String[] args) {\n", + " // 写入一个文件:\n", + " try(\n", + " FileChannel fc = new FileOutputStream(name)\n", + " .getChannel()\n", + " ) {\n", + " fc.write(ByteBuffer\n", + " .wrap(\"Some text \".getBytes()));\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // 在文件尾添加:\n", + " try(\n", + " FileChannel fc = new RandomAccessFile(\n", + " name, \"rw\").getChannel()\n", + " ) {\n", + " fc.position(fc.size()); // 移动到结尾\n", + " fc.write(ByteBuffer\n", + " .wrap(\"Some more\".getBytes()));\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // 读取文件e:\n", + " try(\n", + " FileChannel fc = new FileInputStream(name)\n", + " .getChannel()\n", + " ) {\n", + " ByteBuffer buff = ByteBuffer.allocate(BSIZE);\n", + " fc.read(buff);\n", + " buff.flip();\n", + " while(buff.hasRemaining())\n", + " System.out.write(buff.get());\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.flush();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Some text Some more" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们这里所讲的任何流类,都可以通过调用 `getChannel( )` 方法生成一个 **FileChannel**(文件通道)。**FileChannel** 的操作相当基础:操作 **ByteBuffer** 来用于读写,并独占式访问和锁定文件区域(稍后将对此进行描述)。\n", + "\n", + "将字节放入 **ByteBuffer** 的一种方法是直接调用 `put()` 方法将一个或多个字节放入 **ByteBuffer**;当然也可以是其它基本类型的数据。此外,参考上例,我们还可以调用 `wrap()` 方法包装现有字节数组到 **ByteBuffer**。执行此操作时,不会复制底层数组,而是将其用作生成的 **ByteBuffer** 存储。这样产生的 **ByteBuffer** 是数组“支持”的。\n", + "\n", + "data.txt 文件被 **RandomAccessFile** 重新打开。**注意**,你可以在文件中移动 **FileChanne**。 在这里,它被移动到末尾,以便添加额外的写操作。\n", + "\n", + "对于只读访问,必须使用静态 `allocate()` 方法显式地分配 **ByteBuffer**。**NIO** 的目标是快速移动大量数据,因此 **ByteBuffer** 的大小应该很重要 —— 实际上,这里设置的 1K 都可能偏小了(我们在工作中应该反复测试以找到最佳大小)。\n", + "\n", + "通过使用 `allocateDirect()` 而不是 `allocate()` 来生成与操作系统具备更高耦合度的“直接”缓冲区,也有可能获得更高的速度。然而,这种分配的开销更大,而且实际效果因操作系统的不同而有所不同,因此,在工作中你必须再次测试程序,以检验直接缓冲区是否能为你带来速度上的优势。\n", + "\n", + "一旦调用 **FileChannel** 类的 `read()` 方法将字节数据存储到 **ByteBuffer** 中,你还必须调用缓冲区上的 `flip()` 方法来准备好提取字节(这听起来有点粗糙,实际上这已是非常低层的操作,且为了达到最高速度)。如果要进一步调用 `read()` 来使用 **ByteBuffer** ,还需要每次 `clear()` 来准备缓冲区。下面是个简单的代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/ChannelCopy.java\n", + "\n", + "// 使用 channels and buffers 移动文件\n", + "// {java ChannelCopy ChannelCopy.java test.txt}\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class ChannelCopy {\n", + " private static final int BSIZE = 1024;\n", + " public static void main(String[] args) {\n", + " if(args.length != 2) {\n", + " System.out.println(\n", + " \"arguments: sourcefile destfile\");\n", + " System.exit(1);\n", + " }\n", + " try(\n", + " FileChannel in = new FileInputStream(\n", + " args[0]).getChannel();\n", + " FileChannel out = new FileOutputStream(\n", + " args[1]).getChannel()\n", + " ) {\n", + " ByteBuffer buffer = ByteBuffer.allocate(BSIZE);\n", + " while(in.read(buffer) != -1) {\n", + " buffer.flip(); // 准备写入\n", + " out.write(buffer);\n", + " buffer.clear(); // 准备读取\n", + " }\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**第一个FileChannel** 用于读取;**第二个FileChannel** 用于写入。当 **ByteBuffer** 分配好存储,调用 **FileChannel** 的 `read()` 方法返回 **-1**(毫无疑问,这是来源于 Unix 和 C 语言)时,说明输入流读取完了。在每次 `read()` 将数据放入缓冲区之后,`flip()` 都会准备好缓冲区,以便 `write()` 提取它的信息。在 `write()` 之后,数据仍然在缓冲区中,我们需要 `clear()` 来重置所有内部指针,以便在下一次 `read()` 中接受数据。 \n", + "\n", + "但是,上例并不是处理这种操作的理想方法。方法 `transferTo()` 和 `transferFrom()` 允许你直接连接此通道到彼通道:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/TransferTo.java\n", + "\n", + "// 使用 transferTo() 在通道间传输\n", + "// {java TransferTo TransferTo.java TransferTo.txt}\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class TransferTo {\n", + " public static void main(String[] args) {\n", + " if(args.length != 2) {\n", + " System.out.println(\n", + " \"arguments: sourcefile destfile\");\n", + " System.exit(1);\n", + " }\n", + " try(\n", + " FileChannel in = new FileInputStream(\n", + " args[0]).getChannel();\n", + " FileChannel out = new FileOutputStream(\n", + " args[1]).getChannel()\n", + " ) {\n", + " in.transferTo(0, in.size(), out);\n", + " // Or:\n", + " // out.transferFrom(in, 0, in.size());\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可能不会经常用到,但知道这一点很好。\n", + "\n", + "\n", + "\n", + "## 数据转换\n", + "\n", + "\n", + "为了将 **GetChannel.java** 文件中的信息打印出来。在 Java 中,我们每次提取一个字节的数据并将其转换为字符。看起来很简单 —— 如果你有看过 `ava.nio.`**CharBuffer** 类,你会发现一个 `toString()` 方法。该方法的作用是“返回一个包含此缓冲区字符的字符串”。\n", + "\n", + "既然 **ByteBuffer** 可以通过 **CharBuffer** 类的 `asCharBuffer()` 方法查看,那我们就来尝试一样。从下面输出语句的第一行可以看出,这并不正确:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/BufferToText.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// text 和 ByteBuffers 互转\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.nio.charset.*;\n", + "import java.io.*;\n", + "\n", + "public class BufferToText {\n", + " private static final int BSIZE = 1024;\n", + " public static void main(String[] args) {\n", + "\n", + " try(\n", + " FileChannel fc = new FileOutputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.write(ByteBuffer.wrap(\"Some text\".getBytes()));\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "\n", + " ByteBuffer buff = ByteBuffer.allocate(BSIZE);\n", + "\n", + " try(\n", + " FileChannel fc = new FileInputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.read(buff);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "\n", + " buff.flip();\n", + " // 无法运行\n", + " System.out.println(buff.asCharBuffer());\n", + " // 使用默认系统默认编码解码\n", + " buff.rewind();\n", + " String encoding =\n", + " System.getProperty(\"file.encoding\");\n", + " System.out.println(\"Decoded using \" +\n", + " encoding + \": \"\n", + " + Charset.forName(encoding).decode(buff));\n", + "\n", + " // 编码和打印\n", + " try(\n", + " FileChannel fc = new FileOutputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.write(ByteBuffer.wrap(\n", + " \"Some text\".getBytes(\"UTF-16BE\")));\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "\n", + " // 尝试再次读取:\n", + " buff.clear();\n", + " try(\n", + " FileChannel fc = new FileInputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.read(buff);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "\n", + " buff.flip();\n", + " System.out.println(buff.asCharBuffer());\n", + " // 通过 CharBuffer 写入:\n", + " buff = ByteBuffer.allocate(24);\n", + " buff.asCharBuffer().put(\"Some text\");\n", + "\n", + " try(\n", + " FileChannel fc = new FileOutputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.write(buff);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // 读取和显示:\n", + " buff.clear();\n", + "\n", + " try(\n", + " FileChannel fc = new FileInputStream(\n", + " \"data2.txt\").getChannel()\n", + " ) {\n", + " fc.read(buff);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "\n", + " buff.flip();\n", + " System.out.println(buff.asCharBuffer());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "????\n", + "Decoded using windows-1252: Some text\n", + "Some text\n", + "Some textNULNULNUL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "缓冲区包含普通字节,为了将这些字节转换为字符,我们必须在输入时对它们进行编码(这样它们输出时就有意义了),或者在输出时对它们进行解码。我们可以使用 `java.nio.charset.`**Charset** 字符集工具类来完成。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/AvailableCharSets.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// 展示 Charsets 和 aliases\n", + "import java.nio.charset.*;\n", + "import java.util.*;\n", + "\n", + "public class AvailableCharSets {\n", + "\n", + " public static void main(String[] args) {\n", + " SortedMap charSets =\n", + " Charset.availableCharsets();\n", + "\n", + " for(String csName : charSets.keySet()) {\n", + " System.out.print(csName);\n", + " Iterator aliases = charSets.get(csName)\n", + " .aliases().iterator();\n", + " if(aliases.hasNext())\n", + " System.out.print(\": \");\n", + " \n", + " while(aliases.hasNext()) {\n", + " System.out.print(aliases.next());\n", + " if(aliases.hasNext())\n", + " System.out.print(\", \");\n", + " }\n", + " System.out.println();\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Big5: csBig5\n", + "Big5-HKSCS: big5-hkscs, big5hk, Big5_HKSCS, big5hkscs\n", + "CESU-8: CESU8, csCESU-8\n", + "EUC-JP: csEUCPkdFmtjapanese, x-euc-jp, eucjis,\n", + "Extended_UNIX_Code_Packed_Format_for_Japanese, euc_jp,\n", + "eucjp, x-eucjp\n", + "EUC-KR: ksc5601-1987, csEUCKR, ksc5601_1987, ksc5601,\n", + "5601,\n", + "euc_kr, ksc_5601, ks_c_5601-1987, euckr\n", + "GB18030: gb18030-2000\n", + "GB2312: gb2312, euc-cn, x-EUC-CN, euccn, EUC_CN,\n", + "gb2312-80,\n", + "gb2312-1980\n", + " ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "回到 **BufferToText.java** 中,如果你 `rewind()` 缓冲区(回到数据的开头),使用该平台的默认字符集 `decode()` 数据,那么生成的 **CharBuffer** 数据将在控制台上正常显示。可以通过 `System.getProperty(“file.encoding”)` 方法来查看平台默认字符集名称。传递该名称参数到 `Charset.forName()` 方法可以生成对应的 `Charset` 对象用于解码字符串。\n", + "\n", + "另一种方法是使用字符集 `encode()` 方法,该字符集在读取文件时生成可打印的内容,如你在 **BufferToText.java** 的第三部分中所看到的。上例中,**UTF-16BE** 被用于将文本写入文件,当文本被读取时,你所要做的就是将其转换为 **CharBuffer**,并生成预期的文本。\n", + "\n", + "最后,如果将 **CharBuffer** 写入 **ByteBuffer**,你会看到发生了什么(更多详情,稍后了解)。**注意**,为 **ByteBuffer** 分配了24个字节,按照每个字符占用 2 个自字节, 12 个字符的空间已经足够了。由于“some text”只有 9 个字符,受其 `toString()` 方法影响,剩下的 0 字节部分也出现在了 **CharBuffer** 的展示中,如输出所示。\n", + "\n", + "\n", + "\n", + "## 基本类型获取\n", + "\n", + "\n", + "虽然 **ByteBuffer** 只包含字节,但它包含了一些方法,用于从其所包含的字节中生成各种不同的基本类型数据。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/GetData.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// 从 ByteBuffer 中获取不同的数据展示\n", + "import java.nio.*;\n", + "\n", + "public class GetData {\n", + " private static final int BSIZE = 1024;\n", + " public static void main(String[] args) {\n", + " ByteBuffer bb = ByteBuffer.allocate(BSIZE);\n", + " // 自动分配 0 到 ByteBuffer:\n", + " int i = 0;\n", + " while(i++ < bb.limit())\n", + " if(bb.get() != 0)\n", + " System.out.println(\"nonzero\");\n", + " System.out.println(\"i = \" + i);\n", + " bb.rewind();\n", + " // 保存和读取 char 数组:\n", + " bb.asCharBuffer().put(\"Howdy!\");\n", + " char c;\n", + " while((c = bb.getChar()) != 0)\n", + " System.out.print(c + \" \");\n", + " System.out.println();\n", + " bb.rewind();\n", + " // 保存和读取 short:\n", + " bb.asShortBuffer().put((short)471142);\n", + " System.out.println(bb.getShort());\n", + " bb.rewind();\n", + " // 保存和读取 int:\n", + " bb.asIntBuffer().put(99471142);\n", + " System.out.println(bb.getInt());\n", + " bb.rewind();\n", + " // 保存和读取 long:\n", + " bb.asLongBuffer().put(99471142);\n", + " System.out.println(bb.getLong());\n", + " bb.rewind();\n", + " // 保存和读取 float:\n", + " bb.asFloatBuffer().put(99471142);\n", + " System.out.println(bb.getFloat());\n", + " bb.rewind();\n", + " // 保存和读取 double:\n", + " bb.asDoubleBuffer().put(99471142);\n", + " System.out.println(bb.getDouble());\n", + " bb.rewind();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "i = 1025\n", + "H o w d y !\n", + "12390\n", + "99471142\n", + "99471142\n", + "9.9471144E7\n", + "9.9471142E7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在分配 **ByteBuffer** 之后,我们检查并确认它的 1,024 元素被初始化为 0。(截至到达 `limit()` 结果的位置)。\n", + "\n", + "将基本类型数据插入 **ByteBuffer** 的最简单方法就是使用 `asCharBuffer()`、`asShortBuffer()` 等方法获取该缓冲区适当的“视图”(View),然后调用该“视图”的 `put()` 方法。\n", + "\n", + "这是针对每种基本数据类型执行的。其中唯一有点奇怪的是 **ShortBuffer** 的 `put()`,它需要类型强制转换。其他视图缓冲区不需要在其 `put()` 方法中进行转换。\n", + "\n", + "\n", + "\n", + "## 视图缓冲区\n", + "\n", + "\n", + "“视图缓冲区”(view buffer)是通过特定的基本类型的窗口来查看底层 **ByteBuffer**。**ByteBuffer** 仍然是“支持”视图的实际存储,因此对视图所做的任何更改都反映在对 **ByteBuffer** 中的数据的修改中。\n", + "\n", + "如前面的示例所示,这方便地将基本类型插入 **ByteBuffer**。视图缓冲区还可以从 **ByteBuffer** 读取基本类型数据,每次单个(**ByteBuffer** 规定),或者批量读取到数组。下面是一个通过 **IntBuffer** 在 **ByteBuffer** 中操作 **int** 的例子:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/IntBufferDemo.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// 利用 IntBuffer 保存 int 数据到 ByteBuffer\n", + "import java.nio.*;\n", + "\n", + "public class IntBufferDemo {\n", + " private static final int BSIZE = 1024;\n", + " public static void main(String[] args) {\n", + " ByteBuffer bb = ByteBuffer.allocate(BSIZE);\n", + " IntBuffer ib = bb.asIntBuffer();\n", + " // 保存 int 数组:\n", + " ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016 });\n", + " //绝对位置读写:\n", + " System.out.println(ib.get(3));\n", + " ib.put(3, 1811);\n", + " // 在重置缓冲区前设置新的限制\n", + " \n", + " ib.flip();\n", + " while(ib.hasRemaining()) {\n", + " int i = ib.get();\n", + " System.out.println(i);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "99\n", + "11\n", + "42\n", + "47\n", + "1811\n", + "143\n", + "811\n", + "1016" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`put()` 方法重载,首先用于存储 **int** 数组。下面的 `get()` 和 `put()` 方法调用直接访问底层 **ByteBuffer** 中的 **int** 位置。**注意**,通过直接操作 **ByteBuffer** ,这些绝对位置访问也可以用于基本类型。\n", + "\n", + "一旦底层 **ByteBuffer** 通过视图缓冲区填充了 **int** 或其他基本类型,那么就可以直接将该 **ByteBuffer** 写入通道。你可以轻松地从通道读取数据,并使用视图缓冲区将所有内容转换为特定的基本类型。下面是一个例子,通过在同一个 **ByteBuffer** 上生成不同的视图缓冲区,将相同的字节序列解释为 **short**、**int**、**float**、**long** 和 **double**。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/ViewBuffers.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "import java.nio.*;\n", + "\n", + "public class ViewBuffers {\n", + " public static void main(String[] args) {\n", + " ByteBuffer bb = ByteBuffer.wrap(\n", + " new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });\n", + " bb.rewind();\n", + " System.out.print(\"Byte Buffer \");\n", + " while(bb.hasRemaining())\n", + " System.out.print(\n", + " bb.position()+ \" -> \" + bb.get() + \", \");\n", + " System.out.println();\n", + " CharBuffer cb =\n", + " ((ByteBuffer)bb.rewind()).asCharBuffer();\n", + " System.out.print(\"Char Buffer \");\n", + " while(cb.hasRemaining())\n", + " System.out.print(\n", + " cb.position() + \" -> \" + cb.get() + \", \");\n", + " System.out.println();\n", + " FloatBuffer fb =\n", + " ((ByteBuffer)bb.rewind()).asFloatBuffer();\n", + " System.out.print(\"Float Buffer \");\n", + " while(fb.hasRemaining())\n", + " System.out.print(\n", + " fb.position()+ \" -> \" + fb.get() + \", \");\n", + " System.out.println();\n", + " IntBuffer ib =\n", + " ((ByteBuffer)bb.rewind()).asIntBuffer();\n", + " System.out.print(\"Int Buffer \");\n", + " while(ib.hasRemaining())\n", + " System.out.print(\n", + " ib.position()+ \" -> \" + ib.get() + \", \");\n", + " System.out.println();\n", + " LongBuffer lb =\n", + " ((ByteBuffer)bb.rewind()).asLongBuffer();\n", + " System.out.print(\"Long Buffer \");\n", + " while(lb.hasRemaining())\n", + " System.out.print(\n", + " lb.position()+ \" -> \" + lb.get() + \", \");\n", + " System.out.println();\n", + " ShortBuffer sb =\n", + " ((ByteBuffer)bb.rewind()).asShortBuffer();\n", + " System.out.print(\"Short Buffer \");\n", + " while(sb.hasRemaining())\n", + " System.out.print(\n", + " sb.position()+ \" -> \" + sb.get() + \", \");\n", + " System.out.println();\n", + " DoubleBuffer db =\n", + " ((ByteBuffer)bb.rewind()).asDoubleBuffer();\n", + " System.out.print(\"Double Buffer \");\n", + " while(db.hasRemaining())\n", + " System.out.print(\n", + " db.position()+ \" -> \" + db.get() + \", \");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5\n", + "-> 0, 6 -> 0, 7 -> 97,\n", + "Char Buffer 0 -> NUL, 1 -> NUL, 2 -> NUL, 3 -> a,\n", + "Float Buffer 0 -> 0.0, 1 -> 1.36E-43,\n", + "Int Buffer 0 -> 0, 1 -> 97,\n", + "Long Buffer 0 -> 97,\n", + "Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97,\n", + "Double Buffer 0 -> 4.8E-322," + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**ByteBuffer** 通过“包装”一个 8 字节数组生成,然后通过所有不同基本类型的视图缓冲区显示该数组。下图显示了从不同类型的缓冲区读取数据时,数据显示的差异:\n", + "\n", + "![1554546258113](../images/1554546258113.png)\n", + "\n", + "\n", + "### 字节存储次序\n", + "\n", + "不同的机器可以使用不同的字节存储顺序(Endians)来存储数据。“高位优先”(Big Endian):将最重要的字节放在最低内存地址中,而“低位优先”(Little Endian):将最重要的字节放在最高内存地址中。\n", + "\n", + "当存储大于单字节的数据时,如 **int**、**float** 等,我们可能需要考虑字节排序问题。**ByteBuffer** 以“高位优先”形式存储数据;通过网络发送的数据总是使用“高位优先”形式。我们可以 使用 **ByteOrder** 的 `order()` 方法和参数 **ByteOrder.BIG_ENDIAN** 或 **ByteOrder.LITTLE_ENDIAN** 来改变它的字节存储次序。\n", + "\n", + "下例是一个包含两个字节的 **ByteBuffer** :\n", + "\n", + "![1554546378822](../images/1554546378822.png)\n", + "\n", + "\n", + "将数据作为 **short** 型来读取(`ByteBuffer.asshortbuffer()`)),生成数字 97 (00000000 01100001)。更改为“低位优先”后 将生成数字 24832 (01100001 00000000)。\n", + "\n", + "这显示了字节顺序的变化取决于字节存储次序设置:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/Endians.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// 不同字节存储次序的存储\n", + "import java.nio.*;\n", + "import java.util.*;\n", + "\n", + "public class Endians {\n", + " public static void main(String[] args) {\n", + " ByteBuffer bb = ByteBuffer.wrap(new byte[12]);\n", + " bb.asCharBuffer().put(\"abcdef\");\n", + " System.out.println(Arrays.toString(bb.array()));\n", + " bb.rewind();\n", + " bb.order(ByteOrder.BIG_ENDIAN);\n", + " bb.asCharBuffer().put(\"abcdef\");\n", + " System.out.println(Arrays.toString(bb.array()));\n", + " bb.rewind();\n", + " bb.order(ByteOrder.LITTLE_ENDIAN);\n", + " bb.asCharBuffer().put(\"abcdef\");\n", + " System.out.println(Arrays.toString(bb.array()));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]\n", + "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]\n", + "[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**ByteBuffer** 分配空间将 **charArray** 中的所有字节作为外部缓冲区保存,因此可以调用 `array()` 方法来显示底层字节。`array()` 方法是“可选的”,你只能在数组支持的缓冲区上调用它,否则将抛出 **UnsupportedOperationException** 异常。\n", + "\n", + "**charArray** 通过 **CharBuffer** 视图插入到 **ByteBuffer** 中。当显示底层字节时,默认排序与后续“高位”相同,而“地位”交换字节\n", + "\n", + "\n", + "## 缓冲区数据操作\n", + "\n", + "\n", + "下图说明了 **nio** 类之间的关系,展示了如何移动和转换数据。例如,要将字节数组写入文件,使用 **ByteBuffer.**`wrap()` 方法包装字节数组,使用 `getChannel()` 在 **FileOutputStream** 上打开通道,然后从 **ByteBuffer** 将数据写入 **FileChannel**。\n", + "\n", + "![1554546452861](../images/1554546452861.png)\n", + "\n", + "**ByteBuffer** 是将数据移入和移出通道的唯一方法,我们只能创建一个独立的基本类型缓冲区,或者使用 `as` 方法从 **ByteBuffer** 获得一个新缓冲区。也就是说,不能将基本类型缓冲区转换为 **ByteBuffer**。但我们能够通过视图缓冲区将基本类型数据移动到 **ByteBuffer** 中或移出 **ByteBuffer**。\n", + "\n", + " \n", + "\n", + "### 缓冲区细节\n", + "\n", + "缓冲区由数据和四个索引组成,以有效地访问和操作该数据:mark、position、limit 和 capacity(标记、位置、限制和容量)。伴随着的还有一组方法可以设置和重置这些索引,并可查询它们的值。\n", + "\n", + "| | |\n", + "| :----- | :----- |\n", + "| **capacity()** | 返回缓冲区的 capacity |\n", + "|**clear()** |清除缓冲区,将 position 设置为零并 设 limit 为 capacity;可调用此方法来覆盖现有缓冲区|\n", + "|**flip()** | 将 limit 设置为 position,并将 position 设置为 0;此方法用于准备缓冲区,以便在数据写入缓冲区后进行读取|\n", + "|**limit()** |返回 limit 的值|\n", + "|**limit(int limit)**| 重设 limit|\n", + "|**mark()** |设置 mark 为当前的 position |\n", + "|**position()** |返回 position |\n", + "|**position(int pos)**| 设置 position|\n", + "|**remaining()** |返回 limit 到 position |\n", + "|**hasRemaining()**| 如果在 position 与 limit 中间有元素,返回 `true`|\n", + "\n", + "从缓冲区插入和提取数据的方法通过更新索引来反映所做的更改。下例使用一种非常简单的算法(交换相邻字符)来对 **CharBuffer** 中的字符进行加扰和解扰。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/UsingBuffers.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "import java.nio.*;\n", + "\n", + "public class UsingBuffers {\n", + " private static\n", + " void symmetricScramble(CharBuffer buffer) {\n", + " while(buffer.hasRemaining()) {\n", + " buffer.mark();\n", + " char c1 = buffer.get();\n", + " char c2 = buffer.get();\n", + " buffer.reset();\n", + " buffer.put(c2).put(c1);\n", + " }\n", + " }\n", + "\n", + " public static void main(String[] args) {\n", + " char[] data = \"UsingBuffers\".toCharArray();\n", + " ByteBuffer bb =\n", + " ByteBuffer.allocate(data.length * 2);\n", + " CharBuffer cb = bb.asCharBuffer();\n", + " cb.put(data);\n", + " System.out.println(cb.rewind());\n", + " symmetricScramble(cb);\n", + " System.out.println(cb.rewind());\n", + " symmetricScramble(cb);\n", + " System.out.println(cb.rewind());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "UsingBuffers\n", + "sUniBgfuefsr\n", + "UsingBuffers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然可以通过使用 **char** 数组调用 `wrap()` 直接生成 **CharBuffer**,但是底层的 **ByteBuffer** 将被分配,而 **CharBuffer** 将作为 **ByteBuffer** 上的视图生成。这强调了目标始终是操作 **ByteBuffer**,因为它与通道交互。\n", + "\n", + "下面是程序在 `symmetricgrab()` 方法入口时缓冲区的样子:\n", + "\n", + "![1554546627710](../images/1554546627710.png)\n", + "\n", + "position 指向缓冲区中的第一个元素,capacity 和 limit 紧接在最后一个元素之后。在`symmetricgrab()` 中,**while** 循环迭代到 position 等于 limit。当在缓冲区上调用相对位置的 `get()` 或 `put()` 函数时,缓冲区的位置会发生变化。你可以调用绝对位置的 `get()` 和 `put()` 方法,它们包含索引参数:`get()` 或 `put()` 发生的位置。这些方法不修改缓冲区 position 的值。\n", + "\n", + "当控件进入 **while** 循环时,使用 `mark()` 设置 mark 的值。缓冲区的状态为:\n", + "\n", + "![1554546666685](../images/1554546666685.png)\n", + "\n", + "两个相对 `get()` 调用将前两个字符的值保存在变量 `c1` 和 `c2` 中。在这两个调用之后,缓冲区看起来是这样的:\n", + "\n", + "![1554546693664](../images/1554546693664.png)\n", + "\n", + "为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 position 的值设置为 mark:\n", + "\n", + "![1554546847181](../images/1554546847181.png)\n", + "\n", + "两个 `put()` 方法分别编写 `c2` 和 `c1` :\n", + "\n", + "![1554546861836](../images/1554546861836.png)\n", + "\n", + "在下一次循环中,将 mark 设置为 position 的当前值:\n", + "\n", + "![1554546881189](../images/1554546881189.png)\n", + "\n", + " 该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾,position 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()` 将 position 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态(mark 的值变成未定义):\n", + "\n", + "![1554546890132](../images/1554546890132.png)\n", + "\n", + "再次调用 `symmetricgrab()` 方法时,**CharBuffer** 将经历相同的过程并恢复到原始状态。\n", + "\n", + "\n", + "\n", + "## 内存映射文件\n", + "\n", + "内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/LargeMappedFiles.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// 使用内存映射来创建一个大文件\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class LargeMappedFiles {\n", + " static int length = 0x8000000; // 128 MB\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " try(\n", + " RandomAccessFile tdat =\n", + " new RandomAccessFile(\"test.dat\", \"rw\")\n", + " ) {\n", + " MappedByteBuffer out = tdat.getChannel().map(\n", + " FileChannel.MapMode.READ_WRITE, 0, length);\n", + " for(int i = 0; i < length; i++)\n", + " out.put((byte)'x');\n", + " System.out.println(\"Finished writing\");\n", + " for(int i = length/2; i < length/2 + 6; i++)\n", + " System.out.print((char)out.get(i));\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Finished writing\n", + "xxxxxx" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了读写,我们从 **RandomAccessFile** 开始,获取该文件的通道,然后调用 `map()` 来生成 **MappedByteBuffer** ,这是一种特殊的直接缓冲区。你必须指定要在文件中映射的区域的起始点和长度—这意味着你可以选择映射大文件的较小区域。\n", + "\n", + "**MappedByteBuffer** 继承了 **ByteBuffer**,所以拥有**ByteBuffer** 全部的方法。这里只展示了 `put()` 和 `get()` 的最简单用法,但是你也可以使用 `asCharBuffer()` 等方法。\n", + "\n", + "使用前面的程序创建的文件长度为 128MB,可能比你的操作系统单次所允许的操作的内存要大。该文件似乎可以同时访问,因为它只有一部分被带进内存,而其他部分被交换出去。这样,一个非常大的文件(最多 2GB)可以很容易地修改。**注意**,操作系统底层的文件映射工具用于性能的最大化。\n", + "\n", + "\n", + "### 性能\n", + "\n", + "虽然旧的 I/O 流的性能通过使用 **NIO** 实现得到了改进,但是映射文件访问往往要快得多。下例带来一个简单的性能比较。代码示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/MappedIO.java\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "import java.util.*;\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class MappedIO {\n", + " private static int numOfInts = 4_000_000;\n", + " private static int numOfUbuffInts = 100_000;\n", + " private abstract static class Tester {\n", + " private String name;\n", + " Tester(String name) {\n", + " this.name = name;\n", + " }\n", + "\n", + " public void runTest() {\n", + " System.out.print(name + \": \");\n", + " long start = System.nanoTime();\n", + " test();\n", + " double duration = System.nanoTime() - start;\n", + " System.out.format(\"%.3f%n\", duration/1.0e9);\n", + " }\n", + "\n", + " public abstract void test();\n", + " }\n", + "\n", + " private static Tester[] tests = {\n", + " new Tester(\"Stream Write\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " DataOutputStream dos =\n", + " new DataOutputStream(\n", + " new BufferedOutputStream(\n", + " new FileOutputStream(\n", + " new File(\"temp.tmp\"))))\n", + " ) {\n", + " for(int i = 0; i < numOfInts; i++)\n", + " dos.writeInt(i);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " },\n", + " new Tester(\"Mapped Write\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " FileChannel fc =\n", + " new RandomAccessFile(\"temp.tmp\", \"rw\")\n", + " .getChannel()\n", + " ) {\n", + " IntBuffer ib =\n", + " fc.map(FileChannel.MapMode.READ_WRITE,\n", + " 0, fc.size()).asIntBuffer();\n", + " for(int i = 0; i < numOfInts; i++)\n", + " ib.put(i);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " },\n", + " new Tester(\"Stream Read\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " DataInputStream dis =\n", + " new DataInputStream(\n", + " new BufferedInputStream(\n", + " new FileInputStream(\"temp.tmp\")))\n", + " ) {\n", + " for(int i = 0; i < numOfInts; i++)\n", + " dis.readInt();\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " },\n", + " new Tester(\"Mapped Read\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " FileChannel fc = new FileInputStream(\n", + " new File(\"temp.tmp\")).getChannel()\n", + " ) {\n", + " IntBuffer ib =\n", + " fc.map(FileChannel.MapMode.READ_ONLY,\n", + " 0, fc.size()).asIntBuffer();\n", + " while(ib.hasRemaining())\n", + " ib.get();\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " },\n", + " new Tester(\"Stream Read/Write\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " RandomAccessFile raf =\n", + " new RandomAccessFile(\n", + " new File(\"temp.tmp\"), \"rw\")\n", + " ) {\n", + " raf.writeInt(1);\n", + " for(int i = 0; i < numOfUbuffInts; i++) {\n", + " raf.seek(raf.length() - 4);\n", + " raf.writeInt(raf.readInt());\n", + " }\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " },\n", + " new Tester(\"Mapped Read/Write\") {\n", + " @Override\n", + " public void test() {\n", + " try(\n", + " FileChannel fc = new RandomAccessFile(\n", + " new File(\"temp.tmp\"), \"rw\").getChannel()\n", + " ) {\n", + " IntBuffer ib =\n", + " fc.map(FileChannel.MapMode.READ_WRITE,\n", + " 0, fc.size()).asIntBuffer();\n", + " ib.put(0);\n", + " for(int i = 1; i < numOfUbuffInts; i++)\n", + " ib.put(ib.get(i - 1));\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " }\n", + " };\n", + " public static void main(String[] args) {\n", + " Arrays.stream(tests).forEach(Tester::runTest);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Stream Write: 0.615\n", + "Mapped Write: 0.050\n", + "Stream Read: 0.577\n", + "Mapped Read: 0.015\n", + "Stream Read/Write: 4.069\n", + "Mapped Read/Write: 0.013" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Tester** 使用了模板方法(Template Method)模式,它为匿名内部子类中定义的 `test()` 的各种实现创建一个测试框架。每个子类都执行一种测试,因此 `test()` 方法还提供了执行各种I/O 活动的原型。\n", + "\n", + "虽然映射的写似乎使用 **FileOutputStream**,但是文件映射中的所有输出必须使用 **RandomAccessFile**,就像前面代码中的读/写一样。\n", + "\n", + "请注意,`test()` 方法包括初始化各种 I/O 对象的时间,因此,尽管映射文件的设置可能很昂贵,但是与流 I/O 相比,总体收益非常可观。\n", + "\n", + "\n", + "## 文件锁定\n", + "\n", + "文件锁定可同步访问,因此文件可以共享资源。但是,争用同一文件的两个线程可能位于不同的 JVM 中,或者一个可能是 Java 线程,另一个可能是操作系统中的本机线程。文件锁对其他操作系统进程可见,因为 Java 文件锁定直接映射到本机操作系统锁定工具。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/FileLocking.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "import java.nio.channels.*;\n", + "import java.util.concurrent.*;\n", + "import java.io.*;\n", + "\n", + "public class FileLocking {\n", + " public static void main(String[] args) {\n", + " try(\n", + " FileOutputStream fos =\n", + " new FileOutputStream(\"file.txt\");\n", + " FileLock fl = fos.getChannel().tryLock()\n", + " ) {\n", + " if(fl != null) {\n", + " System.out.println(\"Locked File\");\n", + " TimeUnit.MILLISECONDS.sleep(100);\n", + " fl.release();\n", + " System.out.println(\"Released Lock\");\n", + " }\n", + " } catch(IOException | InterruptedException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Locked File\n", + "Released Lock" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通过调用 **FileChannel** 上的 `tryLock()` 或 `lock()`,可以获得整个文件的 **FileLock**。(**SocketChannel**、**DatagramChannel** 和 **ServerSocketChannel** 不需要锁定,因为它们本质上是单进程实体;通常不会在两个进程之间共享一个网络套接字)。\n", + "\n", + "`tryLock()` 是非阻塞的。它试图获取锁,若不能获取(当其他进程已经持有相同的锁,并且它不是共享的),它只是从方法调用返回。\n", + "\n", + "`lock()` 会阻塞,直到获得锁,或者调用 `lock()` 的线程中断,或者调用 `lock()` 方法的通道关闭。使用 **FileLock.**`release()` 释放锁。\n", + "\n", + "还可以使用\n", + "\n", + " > `tryLock(long position, long size, boolean shared)`\n", + "\n", + " 或 \n", + "\n", + " > `lock(long position, long size, boolean shared)` \n", + " \n", + " 锁定文件的一部分,锁住 **size-position** 区域。第三个参数指定是否共享此锁。\n", + "\n", + "虽然零参数锁定方法适应文件大小的变化,但是如果文件大小发生变化,具有固定大小的锁不会发生变化。如果从一个位置到另一个位置获得一个锁,并且文件的增长超过了 position + size ,那么超出 position + size 的部分没有被锁定。零参数锁定方法锁定整个文件,即使它在增长。\n", + "\n", + "底层操作系统必须提供对独占锁或共享锁的支持。如果操作系统不支持共享锁并且对一个操作系统发出请求,则使用独占锁。可以使用 **FileLock.**`isShared()` 查询锁的类型(共享或独占)。\n", + "\n", + "\n", + "### 映射文件的部分锁定\n", + "\n", + "文件映射通常用于非常大的文件。你可能需要锁定此类文件的某些部分,以便其他进程可以修改未锁定的部分。例如,数据库必须同时对许多用户可用。这里你可以看到两个线程,每个线程都锁定文件的不同部分:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// newio/LockingMappedFiles.java\n", + "// (c)2017 MindView LLC: see Copyright.txt\n", + "// 我们无法保证该代码是否适用于其他用途。\n", + "// 访问 http://OnJava8.com 了解更多本书信息。\n", + "// Locking portions of a mapped file\n", + "import java.nio.*;\n", + "import java.nio.channels.*;\n", + "import java.io.*;\n", + "\n", + "public class LockingMappedFiles {\n", + " static final int LENGTH = 0x8FFFFFF; // 128 MB\n", + " static FileChannel fc;\n", + " public static void\n", + " main(String[] args) throws Exception {\n", + " fc = new RandomAccessFile(\"test.dat\", \"rw\")\n", + " .getChannel();\n", + " MappedByteBuffer out = fc.map(\n", + " FileChannel.MapMode.READ_WRITE, 0, LENGTH);\n", + " for(int i = 0; i < LENGTH; i++)\n", + " out.put((byte)'x');\n", + " new LockAndModify(out, 0, 0 + LENGTH/3);\n", + " new LockAndModify(\n", + " out, LENGTH/2, LENGTH/2 + LENGTH/4);\n", + " }\n", + "\n", + " private static class LockAndModify extends Thread {\n", + " private ByteBuffer buff;\n", + " private int start, end;\n", + " LockAndModify(ByteBuffer mbb, int start, int end) {\n", + " this.start = start;\n", + " this.end = end;\n", + " mbb.limit(end);\n", + " mbb.position(start);\n", + " buff = mbb.slice();\n", + " start();\n", + " }\n", + "\n", + " @Override\n", + " public void run() {\n", + " try {\n", + " // Exclusive lock with no overlap:\n", + " FileLock fl = fc.lock(start, end, false);\n", + " System.out.println(\n", + " \"Locked: \"+ start +\" to \"+ end);\n", + " // Perform modification:\n", + " while(buff.position() < buff.limit() - 1)\n", + " buff.put((byte)(buff.get() + 1));\n", + " fl.release();\n", + " System.out.println(\n", + " \"Released: \" + start + \" to \" + end);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Locked: 75497471 to 113246206\n", + "Locked: 0 to 50331647\n", + "Released: 75497471 to 113246206\n", + "Released: 0 to 50331647" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**LockAndModify** 线程类设置缓冲区并创建要修改的 `slice()`,在 `run()` 中,锁在文件通道上获取(不能在缓冲区上获取锁—只能在通道上获取锁)。`lock()` 的调用非常类似于获取对象上的线程锁 —— 现在有了一个“临界区”,可以对文件的这部分进行独占访问。[^1]\n", + "\n", + "当 JVM 退出或关闭获取锁的通道时,锁会自动释放,但是你也可以显式地调用 **FileLock** 对象上的 `release()`,如上所示。\n", + "\n", + "\n", + "\n", + "[^1]:更多详情可参考[附录:并发底层原理](./book/Appendix-Low-Level-Concurrency.md)。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Object-Serialization.ipynb b/jupyter/Appendix-Object-Serialization.ipynb new file mode 100644 index 00000000..e17b0499 --- /dev/null +++ b/jupyter/Appendix-Object-Serialization.ipynb @@ -0,0 +1,1374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:对象序列化\n", + "\n", + "当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这么做肯定是有意义的,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用。这样,在下次运行程序时,该对象将被重建并且拥有的信息与在程序上次运行时它所拥有的信息相同。当然,你可以通过将信息写入文件或数据库来达到相同的效果,但是在使万物都成为对象的精神中,如果能够将一个对象声明为是“持久性”的,并为我们处理掉所有细节,那将会显得十分方便。\n", + "\n", + "Java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行 Unix 系统的计算机,然后在那里准确地重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心宇节的顺序或者其他任何细节。\n", + "\n", + "就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写人磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种\"persistent\"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。\n", + "\n", + "对象序列化的概念加入到语言中是为了支持两种主要特性。一是 Java 的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。\n", + "\n", + "再者,对 Java Beans 来说,对象的序列化也是必需的(在撰写本文时被视为失败的技术),使用一个 Bean 时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复,这种具体工作就是由对象序列化完成的。\n", + "\n", + "只要对象实现了 Serializable 接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准库类都发生了改变,以便具备序列化特性-其中包括所有基本数据类型的封装器、所有容器类以及许多其他的东西。甚至 Class 对象也可以被序列化。\n", + "\n", + "要序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装在一个 ObjectOutputStream 对象内。这时,只需调用 writeObject() 即可将对象序列化,并将其发送给 OutputStream(对象化序列是基于字节的,因要使用 InputStream 和 OutputStream 继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个 InputStream 封装在 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的 Object,所以必须向下转型才能直接设置它们。\n", + "\n", + "对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪,依此类推。这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组以及成员对象。如果必须保持一套自己的对象序列化机制,那么维护那些可追踪到所有链接的代码可能会显得非常麻烦。然而,由于 Java 的对象序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子通过对链接的对象生成一个 worm(蠕虫)对序列化机制进行了测试。每个对象都与 worm 中的下一段链接,同时又与属于不同类(Data)的对象引用数组链接:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/Worm.java\n", + "// Demonstrates object serialization\n", + "import java.io.*;\n", + "import java.util.*;\n", + "class Data implements Serializable {\n", + " private int n;\n", + " Data(int n) { this.n = n; }\n", + " @Override\n", + " public String toString() {\n", + " return Integer.toString(n);\n", + " }\n", + "}\n", + "public class Worm implements Serializable {\n", + " private static Random rand = new Random(47);\n", + " private Data[] d = {\n", + " new Data(rand.nextInt(10)),\n", + " new Data(rand.nextInt(10)),\n", + " new Data(rand.nextInt(10))\n", + " };\n", + " private Worm next;\n", + " private char c;\n", + " // Value of i == number of segments\n", + " public Worm(int i, char x) {\n", + " System.out.println(\"Worm constructor: \" + i);\n", + " c = x;\n", + " if(--i > 0)\n", + " next = new Worm(i, (char)(x + 1));\n", + " }\n", + " public Worm() {\n", + " System.out.println(\"No-arg constructor\");\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " StringBuilder result = new StringBuilder(\":\");\n", + " result.append(c);\n", + " result.append(\"(\");\n", + " for(Data dat : d)\n", + " result.append(dat);\n", + " result.append(\")\");\n", + " if(next != null)\n", + " result.append(next);\n", + " return result.toString();\n", + " }\n", + " public static void\n", + " main(String[] args) throws ClassNotFoundException,\n", + " IOException {\n", + " Worm w = new Worm(6, 'a');\n", + " System.out.println(\"w = \" + w);\n", + " try(\n", + " ObjectOutputStream out = new ObjectOutputStream(\n", + " new FileOutputStream(\"worm.dat\"))\n", + " ) {\n", + " out.writeObject(\"Worm storage\\n\");\n", + " out.writeObject(w);\n", + " }\n", + " try(\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(\"worm.dat\"))\n", + " ) {\n", + " String s = (String)in.readObject();\n", + " Worm w2 = (Worm)in.readObject();\n", + " System.out.println(s + \"w2 = \" + w2);\n", + " }\n", + " try(\n", + " ByteArrayOutputStream bout =\n", + " new ByteArrayOutputStream();\n", + " ObjectOutputStream out2 =\n", + " new ObjectOutputStream(bout)\n", + " ) {\n", + " out2.writeObject(\"Worm storage\\n\");\n", + " out2.writeObject(w);\n", + " out2.flush();\n", + " try(\n", + " ObjectInputStream in2 = new ObjectInputStream(\n", + " new ByteArrayInputStream(\n", + " bout.toByteArray()))\n", + " ) {\n", + " String s = (String)in2.readObject();\n", + " Worm w3 = (Worm)in2.readObject();\n", + " System.out.println(s + \"w3 = \" + w3);\n", + " }\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Worm constructor: 6\n", + "Worm constructor: 5\n", + "Worm constructor: 4\n", + "Worm constructor: 3\n", + "Worm constructor: 2\n", + "Worm constructor: 1\n", + "w = :a(853):b(119):c(802):d(788):e(199):f(881)\n", + "Worm storage\n", + "w2 = :a(853):b(119):c(802):d(788):e(199):f(881)\n", + "Worm storage\n", + "w3 = :a(853):b(119):c(802):d(788):e(199):f(881)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "更有趣的是,Worm 内的 Data 对象数组是用随机数初始化的(这样就不用怀疑编译器保留了某种原始信息),每个 Worm 段都用一个 char 加以标记。该 char 是在递归生成链接的 Worm 列表时自动产生的。要创建一个 Worm,必须告诉构造器你所希望的它的长度。在产生下一个引用时,要调用 Worm 构造器,并将长度减 1,以此类推。最后一个 next 引用则为 null(空),表示已到达 Worm 的尾部\n", + "\n", + "以上这些操作都使得事情变得更加复杂,从而加大了对象序列化的难度。然而,真正的序列化过程却是非常简单的。一旦从另外某个流创建了 ObjectOutputstream,writeObject() 就会将对象序列化。注意也可以为一个 String 调用 writeObject() 也可以用与 DataOutputStream 相同的方法写人所有基本数据类型(它们具有同样的接口)。\n", + "\n", + "有两段看起来相似的独立的代码。一个读写的是文件,而另一个读写的是字节数组(ByteArray),可利用序列化将对象读写到任何 DatalnputStream 或者 DataOutputStream。\n", + "\n", + "从输出中可以看出,被还原后的对象确实包含了原对象中的所有链接。\n", + "\n", + "注意在对一个 Serializable 对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。整个对象都是通过从 InputStream 中取得数据恢复而来的。\n", + "\n", + "\n", + "\n", + "## 查找类\n", + "\n", + "你或许会奇怪,将一个对象从它的序列化状态中恢复出来,有哪些工作是必须的呢?举个例子来说,假如我们将一个对象序列化,并通过网络将其作为文件传送给另一台计算机,那么,另一台计算机上的程序可以只利用该文件内容来还原这个对象吗?\n", + "\n", + "回答这个问题的最好方法就是做一个实验。下面这个文件位于本章的子目录下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/Alien.java\n", + "// A serializable class\n", + "import java.io.*;\n", + "public class Alien implements Serializable {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "而用于创建和序列化一个 Alien 对象的文件也位于相同的目录下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/FreezeAlien.java\n", + "// Create a serialized output file\n", + "import java.io.*;\n", + "public class FreezeAlien {\n", + " public static void main(String[] args) throws Exception {\n", + " try(\n", + " ObjectOutputStream out = new ObjectOutputStream(\n", + " new FileOutputStream(\"X.file\"));\n", + " ) {\n", + " Alien quellek = new Alien();\n", + " out.writeObject(quellek);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "一旦该程序被编译和运行,它就会在 c12 目录下产生一个名为 X.file 的文件。以下代码位于一个名为 xiles 的子目录下:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/xfiles/ThawAlien.java\n", + "// Recover a serialized file\n", + "// {java serialization.xfiles.ThawAlien}\n", + "// {RunFirst: FreezeAlien}\n", + "package serialization.xfiles;\n", + "import java.io.*;\n", + "public class ThawAlien {\n", + " public static void main(String[] args) throws Exception {\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(new File(\"X.file\")));\n", + " Object mystery = in.readObject();\n", + " System.out.println(mystery.getClass());\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "class Alien" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了正常运行,必须保证 Java 虚拟机能找到相关的.class 文件。\n", + "\n", + "\n", + "\n", + "## 控制序列化\n", + "\n", + "正如大家所看到的,默认的序列化机制并不难操纵。然而,如果有特殊的需要那又该怎么办呢?例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化。\n", + "\n", + "在这些特殊情况下,可通过实现 Externalizable 接口——代替实现 Serializable 接口-来对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal0 和 readExternal0。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。\n", + "\n", + "下面这个例子展示了 Externalizable 接口方法的简单实现。注意 Blip1 和 Blip2 除了细微的差别之外,几乎完全一致(研究一下代码,看看你能否发现):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/Blips.java\n", + "// Simple use of Externalizable & a pitfall\n", + "import java.io.*;\n", + "class Blip1 implements Externalizable {\n", + " public Blip1() {\n", + " System.out.println(\"Blip1 Constructor\");\n", + " }\n", + " @Override\n", + " public void writeExternal(ObjectOutput out)\n", + " throws IOException {\n", + " System.out.println(\"Blip1.writeExternal\");\n", + " }\n", + " @Override\n", + " public void readExternal(ObjectInput in)\n", + " throws IOException, ClassNotFoundException {\n", + " System.out.println(\"Blip1.readExternal\");\n", + " }\n", + "}\n", + "class Blip2 implements Externalizable {\n", + " Blip2() {\n", + " System.out.println(\"Blip2 Constructor\");\n", + " }\n", + " @Override\n", + " public void writeExternal(ObjectOutput out)\n", + " throws IOException {\n", + " System.out.println(\"Blip2.writeExternal\");\n", + " }\n", + " @Override\n", + " public void readExternal(ObjectInput in)\n", + " throws IOException, ClassNotFoundException {\n", + " System.out.println(\"Blip2.readExternal\");\n", + " }\n", + "}\n", + "public class Blips {\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Constructing objects:\");\n", + " Blip1 b1 = new Blip1();\n", + " Blip2 b2 = new Blip2();\n", + " try(\n", + " ObjectOutputStream o = new ObjectOutputStream(\n", + " new FileOutputStream(\"Blips.serialized\"))\n", + " ) {\n", + " System.out.println(\"Saving objects:\");\n", + " o.writeObject(b1);\n", + " o.writeObject(b2);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // Now get them back:\n", + " System.out.println(\"Recovering b1:\");\n", + " try(\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(\"Blips.serialized\"))\n", + " ) {\n", + " b1 = (Blip1)in.readObject();\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // OOPS! Throws an exception:\n", + " //- System.out.println(\"Recovering b2:\");\n", + " //- b2 = (Blip2)in.readObject();\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Constructing objects:\n", + "Blip1 Constructor\n", + "Blip2 Constructor\n", + "Saving objects:\n", + "Blip1.writeExternal\n", + "Blip2.writeExternal\n", + "Recovering b1:\n", + "Blip1 Constructor\n", + "Blip1.readExternal" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "没有恢复 Blip2 对象的原因是那样做会导致一个异常。你找出 Blip1 和 Blip2 之间的区别了吗?Blipl 的构造器是“公共的”(pablic),Blip2 的构造器却不是,这样就会在恢复时造成异常。试试将 Blip2 的构造器变成 public 的,然后删除//注释标记,看看是否能得到正确的结果。\n", + "\n", + "恢复 b1 后,会调用 Blip1 默认构造器。这与恢复一个 Serializable 对象不同。对于 Serializable 对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个 Externalizable 对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用 readExternal() 必须注意这一点--所有默认的构造器都会被调用,才能使 Externalizable 对象产生正确的行为。\n", + "\n", + "下面这个例子示范了如何完整保存和恢复一个 Externalizable 对象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/Blip3.java\n", + "// Reconstructing an externalizable object\n", + "import java.io.*;\n", + "public class Blip3 implements Externalizable {\n", + " private int i;\n", + " private String s; // No initialization\n", + " public Blip3() {\n", + " System.out.println(\"Blip3 Constructor\");\n", + "// s, i not initialized\n", + " }\n", + " public Blip3(String x, int a) {\n", + " System.out.println(\"Blip3(String x, int a)\");\n", + " s = x;\n", + " i = a;\n", + "// s & i initialized only in non-no-arg constructor.\n", + " }\n", + " @Override\n", + " public String toString() { return s + i; }\n", + " @Override\n", + " public void writeExternal(ObjectOutput out)\n", + " throws IOException {\n", + " System.out.println(\"Blip3.writeExternal\");\n", + "// You must do this:\n", + " out.writeObject(s);\n", + " out.writeInt(i);\n", + " }\n", + " @Override\n", + " public void readExternal(ObjectInput in)\n", + " throws IOException, ClassNotFoundException {\n", + " System.out.println(\"Blip3.readExternal\");\n", + "// You must do this:\n", + " s = (String)in.readObject();\n", + " i = in.readInt();\n", + " }\n", + " public static void main(String[] args) {\n", + " System.out.println(\"Constructing objects:\");\n", + " Blip3 b3 = new Blip3(\"A String \", 47);\n", + " System.out.println(b3);\n", + " try(\n", + " ObjectOutputStream o = new ObjectOutputStream(\n", + " new FileOutputStream(\"Blip3.serialized\"))\n", + " ) {\n", + " System.out.println(\"Saving object:\");\n", + " o.writeObject(b3);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + "// Now get it back:\n", + " System.out.println(\"Recovering b3:\");\n", + " try(\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(\"Blip3.serialized\"))\n", + " ) {\n", + " b3 = (Blip3)in.readObject();\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.println(b3);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Constructing objects:\n", + "Blip3(String x, int a)\n", + "A String 47\n", + "Saving object:\n", + "Blip3.writeExternal\n", + "Recovering b3:\n", + "Blip3 Constructor\n", + "Blip3.readExternal\n", + "A String 47" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "其中,字段 s 和只在第二个构造器中初始化,而不是在默认的构造器中初始化。这意味着假如不在 readExternal0 中初始化 s 和 i,s 就会为 null,而就会为零(因为在创建对象的第一步中将对象的存储空间清理为 0)。如果注释掉跟随于\"You must do this”后面的两行代码,然后运行程序,就会发现当对象被还原后,s 是 null,而 i 是零。\n", + "\n", + "我们如果从一个 Externalizable 对象继承,通常需要调用基类版本的 writeExternal() 和 readExternal() 来为基类组件提供恰当的存储和恢复功能。\n", + "\n", + "因此,为了正常运行,我们不仅需要在 writeExternal() 方法(没有任何默认行为来为 Externalizable 对象写入任何成员对象)中将来自对象的重要信息写入,还必须在 readExternal() 方法中恢复数据。起先,可能会有一点迷惑,因为 Externalizable 对象的默认构造行为使其看起来似乎像某种自动发生的存储与恢复操作。但实际上并非如此。\n", + "\n", + "### transient 关键字\n", + "\n", + "当我们对序列化进行控制时,可能某个特定子对象不想让 Java 的序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况。即使对象中的这些信息是 private(私有)属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问到它。\n", + "\n", + "有一种办法可防止对象的敏感部分被序列化,就是将类实现为 Externalizable,如前面所示。这样一来,没有任何东西可以自动序列化,并且可以在 writeExternal() 内部只对所需部分进行显式的序列化。\n", + "\n", + "然而,如果我们正在操作的是一个 Seralizable 对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用 transient(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据——我自己会处理的\"。\n", + "\n", + "例如,假设某个 Logon 对象保存某个特定的登录会话信息,登录的合法性通过校验之后,我们想把数据保存下来,但不包括密码。为做到这一点,最简单的办法是实现 Serializable,并将 password 字段标志为 transient,下面是具体的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/Logon.java\n", + "// Demonstrates the \"transient\" keyword\n", + "import java.util.concurrent.*;\n", + "import java.io.*;\n", + "import java.util.*;\n", + "import onjava.Nap;\n", + "public class Logon implements Serializable {\n", + " private Date date = new Date();\n", + " private String username;\n", + " private transient String password;\n", + " public Logon(String name, String pwd) {\n", + " username = name;\n", + " password = pwd;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"logon info: \\n username: \" +\n", + " username + \"\\n date: \" + date +\n", + " \"\\n password: \" + password;\n", + " }\n", + " public static void main(String[] args) {\n", + " Logon a = new Logon(\"Hulk\", \"myLittlePony\");\n", + " System.out.println(\"logon a = \" + a);\n", + " try(\n", + " ObjectOutputStream o =\n", + " new ObjectOutputStream(\n", + " new FileOutputStream(\"Logon.dat\"))\n", + " ) {\n", + " o.writeObject(a);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " new Nap(1);\n", + "// Now get them back:\n", + " try(\n", + " ObjectInputStream in = new ObjectInputStream(\n", + " new FileInputStream(\"Logon.dat\"))\n", + " ) {\n", + " System.out.println(\n", + " \"Recovering object at \" + new Date());\n", + " a = (Logon)in.readObject();\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " System.out.println(\"logon a = \" + a);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "logon a = logon info:\n", + "username: Hulk\n", + "date: Tue May 09 06:07:47 MDT 2017\n", + "password: myLittlePony\n", + "Recovering object at Tue May 09 06:07:49 MDT 2017\n", + "logon a = logon info:\n", + "username: Hulk\n", + "date: Tue May 09 06:07:47 MDT 2017\n", + "password: null" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,其中的 date 和 username 是一般的(不是 transient 的),所以它们会被自动序列化。而 password 是 transient 的,所以不会被自动保存到磁盘;另外,自动序列化机制也不会尝试去恢复它。当对象被恢复时,password 就会变成 null。注意,虽然 toString() 是用重载后的+运算符来连接 String 对象,但是 null 引用会被自动转换成字符串 null。\n", + "\n", + "我们还可以发现:date 字段被存储了到磁盘并从磁盘上被恢复了出来,而且没有再重新生成。由于 Externalizable 对象在默认情况下不保存它们的任何字段,所以 transient 关键字只能和 Serializable 对象一起使用。\n", + "\n", + "### Externalizable 的替代方法\n", + "\n", + "如果不是特别坚持实现 Externalizable 接口,那么还有另一种方法。我们可以实现 Serializable 接口,并添加(注意我说的是“添加”,而非“覆盖”或者“实现”)名为 writeObject() 和 readObject() 的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制。\n", + "\n", + "这些方法必须具有准确的方法特征签名:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "private void writeObject(ObjectOutputStream stream) throws IOException\n", + "\n", + "private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "从设计的观点来看,现在事情变得真是不可思议。首先,我们可能会认为由于这些方法不是基类或者 Serializable 接口的一部分,所以应该在它们自己的接口中进行定义。但是注意它们被定义成了 private,这意味着它们仅能被这个类的其他成员调用。然而,实际上我们并没有从这个类的其他方法中调用它们,而是 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject() 和 readobject() 方法调用你的对象的 writeObject() 和 readObject() 方法(注意关于这里用到的相同方法名,我尽量抑制住不去谩骂。一句话:混乱)。读者可能想知道 ObjectOutputStream 和 ObjectInputStream 对象是怎样访问你的类中的 private 方法的。我们只能假设这正是序列化神奇的一部分。\n", + "\n", + "在接口中定义的所有东西都自动是 public 的,因此如果 writeObject() 和 readObject() 必须是 private 的,那么它们不会是接口的一部分。因为我们必须要完全遵循其方法特征签名,所以其效果就和实现了接口一样。\n", + "\n", + "在调用 ObjectOutputStream.writeObject() 时,会检查所传递的 Serializable 对象,看看是否实现了它自己的 writeObject()。如果是这样,就跳过正常的序列化过程并调用它的 writeObiect()。readObject() 的情形与此相同。\n", + "\n", + "还有另外一个技巧。在你的 writeObject() 内部,可以调用 defaultWriteObject() 来选择执行默认的 writeObject()。类似地,在 readObject() 内部,我们可以调用 defaultReadObject(),下面这个简单的例子演示了如何对一个 Serializable 对象的存储与恢复进行控制:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/SerialCtl.java\n", + "// Controlling serialization by adding your own\n", + "// writeObject() and readObject() methods\n", + "import java.io.*;\n", + "public class SerialCtl implements Serializable {\n", + " private String a;\n", + " private transient String b;\n", + " public SerialCtl(String aa, String bb) {\n", + " a = \"Not Transient: \" + aa;\n", + " b = \"Transient: \" + bb;\n", + " }\n", + " @Override\n", + " public String toString() { return a + \"\\n\" + b; }\n", + " private void writeObject(ObjectOutputStream stream)\n", + " throws IOException {\n", + " stream.defaultWriteObject();\n", + " stream.writeObject(b);\n", + " }\n", + " private void readObject(ObjectInputStream stream)\n", + " throws IOException, ClassNotFoundException {\n", + " stream.defaultReadObject();\n", + " b = (String)stream.readObject();\n", + " }\n", + " public static void main(String[] args) {\n", + " SerialCtl sc = new SerialCtl(\"Test1\", \"Test2\");\n", + " System.out.println(\"Before:\\n\" + sc);\n", + " try (\n", + " ByteArrayOutputStream buf =\n", + " new ByteArrayOutputStream();\n", + " ObjectOutputStream o =\n", + " new ObjectOutputStream(buf);\n", + " ) {\n", + " o.writeObject(sc);\n", + "// Now get it back:\n", + " try (\n", + " ObjectInputStream in =\n", + " new ObjectInputStream(\n", + " new ByteArrayInputStream(\n", + " buf.toByteArray()));\n", + " ) {\n", + " SerialCtl sc2 = (SerialCtl)in.readObject();\n", + " System.out.println(\"After:\\n\" + sc2);\n", + " }\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Before:\n", + "Not Transient: Test1\n", + "Transient: Test2\n", + "After:\n", + "Not Transient: Test1\n", + "Transient: Test2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在这个例子中,有一个 String 字段是普通字段,而另一个是 transient 字段,用来证明非 transient 字段由 defaultWriteObject() 方法保存,而 transient 字段必须在程序中明确保存和恢复。字段是在构造器内部而不是在定义处进行初始化的,以此可以证实它们在反序列化还原期间没有被一些自动化机制初始化。\n", + "\n", + "如果我们打算使用默认机制写入对象的非 transient 部分,那么必须调用 defaultwriteObject() 作为 writeObject() 中的第一个操作,并让 defaultReadObject() 作为 readObject() 中的第一个操作。这些都是奇怪的方法调用。例如,如果我们正在为 ObjectOutputStream 调用 defaultWriteObject() 且没有传递任何参数,然而不知何故它却可以运行,并且知道对象的引用以及如何写入非 transient 部分。真是奇怪之极。\n", + "\n", + "对 transient 对象的存储和恢复使用了我们比较熟悉的代码。请再考虑一下在这里所发生的事情。在 main0)中,创建 SerialCtl 对象,然后将其序列化到 ObjectOutputStream(注意在这种情况下,使用的是缓冲区而不是文件-这对于 ObjectOutputStream 来说是完全一样的)。序列化发生在下面这行代码当中" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "o.writeObject(sc);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "writeObject() 方法必须检查 sc,判断它是否拥有自己的 writeObject() 方法(不是检查接口——这里根本就没有接口,也不是检查类的类型,而是利用反射来真正地搜索方法)。如果有,那么就会使用它。对 readObject() 也采用了类似的方法。或许这是解决这个问题的唯一切实可行的方法,但它确实有点古怪。\n", + "\n", + "### 版本控制\n", + "\n", + "有时可能想要改变可序列化类的版本(比如源类的对象可能保存在数据库中)。虽然 Java 支持这种做法,但是你可能只在特殊的情况下才这样做,此外,还需要对它有相当深程度的了解(在这里我们就不再试图达到这一点)。从 http://java.oracle.com 下的 JDK 文档中对这一主题进行了非常彻底的论述。\n", + "\n", + "\n", + "\n", + "## 使用持久化\n", + "\n", + "个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象-它们都具有指向第三个对象的引用-进行序列化,会发生什么情况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢?\n", + "\n", + "下面这个例子说明了上述问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/MyWorld.java\n", + "import java.io.*;\n", + "import java.util.*;\n", + "class House implements Serializable {}\n", + "class Animal implements Serializable {\n", + " private String name;\n", + " private House preferredHouse;\n", + " Animal(String nm, House h) {\n", + " name = nm;\n", + " preferredHouse = h;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return name + \"[\" + super.toString() +\n", + " \"], \" + preferredHouse + \"\\n\";\n", + " }\n", + "}\n", + "public class MyWorld {\n", + " public static void main(String[] args) {\n", + " House house = new House();\n", + " List animals = new ArrayList<>();\n", + " animals.add(\n", + " new Animal(\"Bosco the dog\", house));\n", + " animals.add(\n", + " new Animal(\"Ralph the hamster\", house));\n", + " animals.add(\n", + " new Animal(\"Molly the cat\", house));\n", + " System.out.println(\"animals: \" + animals);\n", + " try(\n", + " ByteArrayOutputStream buf1 =\n", + " new ByteArrayOutputStream();\n", + " ObjectOutputStream o1 =\n", + " new ObjectOutputStream(buf1)\n", + " ) {\n", + " o1.writeObject(animals);\n", + " o1.writeObject(animals); // Write a 2nd set\n", + "// Write to a different stream:\n", + " try(\n", + " ByteArrayOutputStream buf2 = new ByteArrayOutputStream();\n", + " ObjectOutputStream o2 = new ObjectOutputStream(buf2)\n", + " ) {\n", + " o2.writeObject(animals);\n", + "// Now get them back:\n", + " try(\n", + " ObjectInputStream in1 =\n", + " new ObjectInputStream(\n", + " new ByteArrayInputStream(\n", + " buf1.toByteArray()));\n", + " ObjectInputStream in2 =\n", + " new ObjectInputStream(\n", + " new ByteArrayInputStream(\n", + " buf2.toByteArray()))\n", + " ) {\n", + " List\n", + " animals1 = (List)in1.readObject(),\n", + " animals2 = (List)in1.readObject(),\n", + " animals3 = (List)in2.readObject();\n", + " System.out.println(\n", + " \"animals1: \" + animals1);\n", + " System.out.println(\n", + " \"animals2: \" + animals2);\n", + " System.out.println(\n", + " \"animals3: \" + animals3);\n", + " }\n", + " }\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "animals: [Bosco the dog[Animal@15db9742],\n", + "House@6d06d69c\n", + ", Ralph the hamster[Animal@7852e922], House@6d06d69c\n", + ", Molly the cat[Animal@4e25154f], House@6d06d69c\n", + "]\n", + "animals1: [Bosco the dog[Animal@7ba4f24f],\n", + "House@3b9a45b3\n", + ", Ralph the hamster[Animal@7699a589], House@3b9a45b3\n", + ", Molly the cat[Animal@58372a00], House@3b9a45b3\n", + "]\n", + "animals2: [Bosco the dog[Animal@7ba4f24f],\n", + "House@3b9a45b3\n", + ", Ralph the hamster[Animal@7699a589], House@3b9a45b3\n", + ", Molly the cat[Animal@58372a00], House@3b9a45b3\n", + "]\n", + "animals3: [Bosco the dog[Animal@4dd8dc3],\n", + "House@6d03e736\n", + ", Ralph the hamster[Animal@568db2f2], House@6d03e736\n", + ", Molly the cat[Animal@378bf509], House@6d03e736\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里有一件有趣的事:我们可以通过一个字节数组来使用对象序列化,从而实现对任何可 Serializable 对象的“深度复制\"(deep copy)—— 深度复制意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。复制对象将在本书的 [附录:传递和返回对象 ]() 一章中进行深入地探讨。\n", + "\n", + "在这个例子中,Animal 对象包含有 House 类型的字段。在 main() 方法中,创建了一个 Animal 列表并将其两次序列化,分别送至不同的流。当其被反序列化还原并被打印时,我们可以看到所示的执行某次运行后的结果(每次运行时,对象将会处在不同的内存地址)。\n", + "\n", + "当然,我们期望这些反序列化还原后的对象地址与原来的地址不同。但请注意,在 animals1 和 animals2 中却出现了相同的地址,包括二者共享的那个指向 House 对象的引用。另一方面,当恢复 animals3 时,系统无法知道另一个流内的对象是第一个流内的对象的别名,因此它会产生出完全不同的对象网。\n", + "\n", + "只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事,无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),它们都可以被写出。\n", + "\n", + "最安全的做法是将其作为“原子”操作进行序列化。如果我们序列化了某些东西,再去做其他一些工作,再来序列化更多的东西,如此等等,那么将无法安全地保存系统状态。取而代之的是,将构成系统状态的所有对象都置入单一容器内,并在一个操作中将该容器直接写出。然后同样只需一次方法调用,即可以将其恢复。\n", + "\n", + "下面这个例子是一个想象的计算机辅助设计(CAD)系统,该例演示了这一方法。此外,它还引入了 static 字段的问题:如果我们查看 JDK 文档,就会发现 Class 是 Serializable 的,因此只需直接对 Class 对象序列化,就可以很容易地保存 static 字段。在任何情况下,这都是一种明智的做法。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/AStoreCADState.java\n", + "// Saving the state of a fictitious CAD system\n", + "import java.io.*;\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "enum Color { RED, BLUE, GREEN }\n", + "abstract class Shape implements Serializable {\n", + " private int xPos, yPos, dimension;\n", + " private static Random rand = new Random(47);\n", + " private static int counter = 0;\n", + " public abstract void setColor(Color newColor);\n", + " public abstract Color getColor();\n", + " Shape(int xVal, int yVal, int dim) {\n", + " xPos = xVal;\n", + " yPos = yVal;\n", + " dimension = dim;\n", + " }\n", + " public String toString() {\n", + " return getClass() + \"color[\" + getColor() +\n", + " \"] xPos[\" + xPos + \"] yPos[\" + yPos +\n", + " \"] dim[\" + dimension + \"]\\n\";\n", + " }\n", + " public static Shape randomFactory() {\n", + " int xVal = rand.nextInt(100);\n", + " int yVal = rand.nextInt(100);\n", + " int dim = rand.nextInt(100);\n", + " switch(counter++ % 3) {\n", + " default:\n", + " case 0: return new Circle(xVal, yVal, dim);\n", + " case 1: return new Square(xVal, yVal, dim);\n", + " case 2: return new Line(xVal, yVal, dim);\n", + " }\n", + " }\n", + "}\n", + "class Circle extends Shape {\n", + " private static Color color = Color.RED;\n", + " Circle(int xVal, int yVal, int dim) {\n", + " super(xVal, yVal, dim);\n", + " }\n", + " public void setColor(Color newColor) {\n", + " color = newColor;\n", + " }\n", + " public Color getColor() { return color; }\n", + "}\n", + "class Square extends Shape {\n", + " private static Color color = Color.RED;\n", + " Square(int xVal, int yVal, int dim) {\n", + " super(xVal, yVal, dim);\n", + " }\n", + " public void setColor(Color newColor) {\n", + " color = newColor;\n", + " }\n", + " public Color getColor() { return color; }\n", + "}\n", + "class Line extends Shape {\n", + " private static Color color = Color.RED;\n", + " public static void\n", + " serializeStaticState(ObjectOutputStream os)\n", + " throws IOException { os.writeObject(color); }\n", + " public static void\n", + " deserializeStaticState(ObjectInputStream os)\n", + " throws IOException, ClassNotFoundException {\n", + " color = (Color)os.readObject();\n", + " }\n", + " Line(int xVal, int yVal, int dim) {\n", + " super(xVal, yVal, dim);\n", + " }\n", + " public void setColor(Color newColor) {\n", + " color = newColor;\n", + " }\n", + " public Color getColor() { return color; }\n", + "}\n", + "public class AStoreCADState {\n", + " public static void main(String[] args) {\n", + " List> shapeTypes =\n", + " Arrays.asList(\n", + " Circle.class, Square.class, Line.class);\n", + " List shapes = IntStream.range(0, 10)\n", + " .mapToObj(i -> Shape.randomFactory())\n", + " .collect(Collectors.toList());\n", + " // Set all the static colors to GREEN:\n", + " shapes.forEach(s -> s.setColor(Color.GREEN));\n", + " // Save the state vector:\n", + " try(\n", + " ObjectOutputStream out =\n", + " new ObjectOutputStream(\n", + " new FileOutputStream(\"CADState.dat\"))\n", + " ) {\n", + " out.writeObject(shapeTypes);\n", + " Line.serializeStaticState(out);\n", + " out.writeObject(shapes);\n", + " } catch(IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " // Display the shapes:\n", + " System.out.println(shapes);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[class Circlecolor[GREEN] xPos[58] yPos[55] dim[93]\n", + ", class Squarecolor[GREEN] xPos[61] yPos[61] dim[29]\n", + ", class Linecolor[GREEN] xPos[68] yPos[0] dim[22]\n", + ", class Circlecolor[GREEN] xPos[7] yPos[88] dim[28]\n", + ", class Squarecolor[GREEN] xPos[51] yPos[89] dim[9]\n", + ", class Linecolor[GREEN] xPos[78] yPos[98] dim[61]\n", + ", class Circlecolor[GREEN] xPos[20] yPos[58] dim[16]\n", + ", class Squarecolor[GREEN] xPos[40] yPos[11] dim[22]\n", + ", class Linecolor[GREEN] xPos[4] yPos[83] dim[6]\n", + ", class Circlecolor[GREEN] xPos[75] yPos[10] dim[42]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Shape 类实现了 Serializable,所以任何自 Shape 继承的类也都会自动是 Serializable 的。每个 Shape 都含有数据,而且每个派生自 Shape 的类都包含一个 static 字段,用来确定各种 Shape 类型的颜色(如果将 static 字段置入基类,只会产生一个 static 字段,因为 static 字段不能在派生类中复制)。可对基类中的方法进行重载,以便为不同的类型设置颜色(static 方法不会动态绑定,所以这些都是普通的方法)。每次调用 randomFactory() 方法时,它都会使用不同的随机数作为 Shape 的数据,从而创建不同的 Shape。\n", + "\n", + "在 main() 中,一个 ArrayList 用于保存 Class 对象,而另一个用于保存几何形状。\n", + "\n", + "恢复对象相当直观:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/RecoverCADState.java\n", + "// Restoring the state of the fictitious CAD system\n", + "// {RunFirst: AStoreCADState}\n", + "import java.io.*;\n", + "import java.util.*;\n", + "public class RecoverCADState {\n", + " @SuppressWarnings(\"unchecked\")\n", + " public static void main(String[] args) {\n", + " try(\n", + " ObjectInputStream in =\n", + " new ObjectInputStream(\n", + " new FileInputStream(\"CADState.dat\"))\n", + " ) {\n", + "// Read in the same order they were written:\n", + " List> shapeTypes =\n", + " (List>)in.readObject();\n", + " Line.deserializeStaticState(in);\n", + " List shapes =\n", + " (List)in.readObject();\n", + " System.out.println(shapes);\n", + " } catch(IOException | ClassNotFoundException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[class Circlecolor[RED] xPos[58] yPos[55] dim[93]\n", + ", class Squarecolor[RED] xPos[61] yPos[61] dim[29]\n", + ", class Linecolor[GREEN] xPos[68] yPos[0] dim[22]\n", + ", class Circlecolor[RED] xPos[7] yPos[88] dim[28]\n", + ", class Squarecolor[RED] xPos[51] yPos[89] dim[9]\n", + ", class Linecolor[GREEN] xPos[78] yPos[98] dim[61]\n", + ", class Circlecolor[RED] xPos[20] yPos[58] dim[16]\n", + ", class Squarecolor[RED] xPos[40] yPos[11] dim[22]\n", + ", class Linecolor[GREEN] xPos[4] yPos[83] dim[6]\n", + ", class Circlecolor[RED] xPos[75] yPos[10] dim[42]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以看到,xPos,yPos 以及 dim 的值都被成功地保存和恢复了,但是对 static 信息的读取却出现了问题。所有读回的颜色应该都是“3”,但是真实情况却并非如此。Circle 的值为 1(定义为 RED),而 Square 的值为 0(记住,它们是在构造器中被初始化的)。看上去似乎 static 数据根本没有被序列化!确实如此——尽管 Class 类是 Serializable 的,但它却不能按我们所期望的方式运行。所以假如想序列化 static 值,必须自己动手去实现。\n", + "\n", + "这正是 Line 中的 serializeStaticState() 和 deserializeStaticState() 两个 static 方法的用途。可以看到,它们是作为存储和读取过程的一部分被显式地调用的。(注意必须维护写入序列化文件和从该文件中读回的顺序。)因此,为了使 CADStatejava 正确运转起来,你必须:\n", + "\n", + "1. 为几何形状添加 serializeStaticState() 和 deserializeStaticState()\n", + "2. 移除 ArrayList shapeTypes 以及与之有关的所有代码。\n", + "3. 在几何形状内添加对新的序列化和反序列化还原静态方法的调用。\n", + "\n", + "另一个要注意的问题是安全,因为序列化也会将 private 数据保存下来。如果你关心安全问题,那么应将其标记成 transient,但是这之后,还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些 private 变量。\n", + "\n", + "## XML\n", + "\n", + "对象序列化的一个重要限制是它只是 Java 的解决方案:只有 Java 程序才能反序列化这种对象。一种更具互操作性的解决方案是将数据转换为 XML 格式,这可以使其被各种各样的平台和语言使用。\n", + "\n", + "因为 XML 十分流行,所以用它来编程时的各种选择不胜枚举,包括随 JDK 发布的 javax.xml.*类库。我选择使用 Elliotte Rusty Harold 的开源 XOM 类库(可从 www.xom.nu 下载并获得文档),因为它看起来最简单,同时也是最直观的用 Java 产生和修改 XML 的方式。另外,XOM 还强调了 XML 的正确性。\n", + "\n", + "作为一个示例,假设有一个 APerson 对象,它包含姓和名,你想将它们序列化到 XML 中。下面的 APerson 类有一个 getXML() 方法,它使用 XOM 来产生被转换为 XML 的 Element 对象的 APerson 数据;还有一个构造器,接受 Element 并从中抽取恰当的 APerson 数据(注意,XML 示例都在它们自己的子目录中):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/APerson.java\n", + "// Use the XOM library to write and read XML\n", + "// nu.xom.Node comes from http://www.xom.nu\n", + "import nu.xom.*;\n", + "import java.io.*;\n", + "import java.util.*;\n", + "public class APerson {\n", + " private String first, last;\n", + " public APerson(String first, String last) {\n", + " this.first = first;\n", + " this.last = last;\n", + " }\n", + " // Produce an XML Element from this APerson object:\n", + " public Element getXML() {\n", + " Element person = new Element(\"person\");\n", + " Element firstName = new Element(\"first\");\n", + " firstName.appendChild(first);\n", + " Element lastName = new Element(\"last\");\n", + " lastName.appendChild(last);\n", + " person.appendChild(firstName);\n", + " person.appendChild(lastName);\n", + " return person;\n", + " }\n", + " // Constructor restores a APerson from XML:\n", + " public APerson(Element person) {\n", + " first = person\n", + " .getFirstChildElement(\"first\").getValue();\n", + " last = person\n", + " .getFirstChildElement(\"last\").getValue();\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return first + \" \" + last;\n", + " }\n", + " // Make it human-readable:\n", + " public static void\n", + " format(OutputStream os, Document doc)\n", + " throws Exception {\n", + " Serializer serializer =\n", + " new Serializer(os,\"ISO-8859-1\");\n", + " serializer.setIndent(4);\n", + " serializer.setMaxLength(60);\n", + " serializer.write(doc);\n", + " serializer.flush();\n", + " }\n", + " public static void main(String[] args) throws Exception {\n", + " List people = Arrays.asList(\n", + " new APerson(\"Dr. Bunsen\", \"Honeydew\"),\n", + " new APerson(\"Gonzo\", \"The Great\"),\n", + " new APerson(\"Phillip J.\", \"Fry\"));\n", + " System.out.println(people);\n", + " Element root = new Element(\"people\");\n", + " for(APerson p : people)\n", + " root.appendChild(p.getXML());\n", + " Document doc = new Document(root);\n", + " format(System.out, doc);\n", + " format(new BufferedOutputStream(\n", + " new FileOutputStream(\"People.xml\")), doc);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出为:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "xml" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]\n", + "\n", + "\n", + " \n", + " Dr. Bunsen\n", + " Honeydew\n", + " \n", + " \n", + " Gonzo\n", + " The Great\n", + " \n", + " \n", + " Phillip J.\n", + " Fry\n", + " \n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "XOM 的方法都具有相当的自解释性,可以在 XOM 文档中找到它们。XOM 还包含一个 Serializer 类,你可以在 format() 方法中看到它被用来将 XML 转换为更具可读性的格式。如果只调用 toXML(),那么所有东西都会混在一起,因此 Serializer 是一种便利工具。\n", + "\n", + "从 XML 文件中反序列化 Person 对象也很简单:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// serialization/People.java\n", + "// nu.xom.Node comes from http://www.xom.nu\n", + "// {RunFirst: APerson}\n", + "import nu.xom.*;\n", + "import java.io.File;\n", + "import java.util.*;\n", + "public class People extends ArrayList {\n", + " public People(String fileName) throws Exception {\n", + " Document doc =\n", + " new Builder().build(new File(fileName));\n", + " Elements elements =\n", + " doc.getRootElement().getChildElements();\n", + " for(int i = 0; i < elements.size(); i++)\n", + " add(new APerson(elements.get(i)));\n", + " }\n", + " public static void main(String[] args) throws Exception {\n", + " People p = new People(\"People.xml\");\n", + " System.out.println(p);\n", + " }\n", + "}\n", + "/* Output:\n", + "[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "People 构造器使用 XOM 的 Builder.build() 方法打开并读取一个文件,而 getChildElements() 方法产生了一个 Elements 列表(不是标准的 Java List,只是一个拥有 size() 和 get() 方法的对象,因为 Harold 不想强制人们使用特定版本的 Java,但是仍旧希望使用类型安全的容器)。在这个列表中的每个 Element 都表示一个 Person 对象,因此它可以传递给第二个 Person 构造器。注意,这要求你提前知道 XML 文件的确切结构,但是这经常会有些问题。如果文件结构与你预期的结构不匹配,那么 XOM 将抛出异常。对你来说,如果你缺乏有关将来的 XML 结构的信息,那么就有可能会编写更复杂的代码去探测 XML 文档,而不是只对其做出假设。\n", + "\n", + "为了获取这些示例去编译它们,你必须将 XOM 发布包中的 JAR 文件放置到你的类路径中。\n", + "\n", + "这里只给出了用 Java 和 XOM 类库进行 XML 编程的简介,更详细的信息可以浏览 www.xom.nu 。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Passing-and-Returning-Objects.ipynb b/jupyter/Appendix-Passing-and-Returning-Objects.ipynb new file mode 100644 index 00000000..1ecfd6a2 --- /dev/null +++ b/jupyter/Appendix-Passing-and-Returning-Objects.ipynb @@ -0,0 +1,88 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:对象传递和返回\n", + "\n", + "> 到现在为止,你已经对“传递”对象实际上是传递引用这一想法想法感到满意。\n", + "\n", + "在许多编程语言中,你可以使用该语言的“常规”方式来传递对象,并且大多数情况下一切正常。 但是通常会出现这种情况,你必须做一些不平常的事情,突然事情变得更加复杂。 Java也不例外,当您传递对象并对其进行操作时,准确了解正在发生的事情很重要。 本附录提供了这种见解。\n", + "\n", + "提出本附录问题的另一种方法是,如果你之前使用类似C++的编程语言,则是“ Java是否有指针?” Java中的每个对象标识符(除原语外)都是这些指针之一,但它们的用法是不仅受编译器的约束,而且受运行时系统的约束。 换一种说法,Java有指针,但没有指针算法。 这些就是我一直所说的“引用”,您可以将它们视为“安全指针”,与小学的安全剪刀不同-它们不敏锐,因此您不费吹灰之力就无法伤害自己,但是它们有时可能很乏味。\n", + "\n", + "\n", + "\n", + "## 传递引用\n", + "\n", + "\n", + "\n", + "当你将引用传递给方法时,它仍指向同一对象。 一个简单的实验演示了这一点:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// references/PassReferences.java\n", + "public class PassReferences {\n", + "public static void f(PassReferences h) {\n", + " \tSystem.out.println(\"h inside f(): \" + h);\n", + " }\n", + " public static void main(String[] args) {\n", + " PassReferences p = new PassReferences();\n", + " System.out.println(\"p inside main(): \" + p);\n", + " f(p);\n", + " }\n", + "}\n", + "/* Output:\n", + "p inside main(): PassReferences@15db9742\n", + "h inside f(): PassReferences@15db9742\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "方法 `toString() ` 在打印语句中自动调用,并且 `PassReferences` 直接从 `Object` 继承而无需重新定义 `toString()` 。 因此,使用的是 `Object` 的 `toString()` 版本,它打印出对象的类,然后打印出该对象所在的地址(不是引用,而是实际的对象存储)。\n", + "\n", + "## 本地拷贝\n", + "\n", + "\n", + "\n", + "## 控制克隆\n", + "\n", + "\n", + "\n", + "## 不可变类\n", + "\n", + "\n", + "\n", + "## 本章小结\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Programming-Guidelines.ipynb b/jupyter/Appendix-Programming-Guidelines.ipynb new file mode 100644 index 00000000..c7a87fc8 --- /dev/null +++ b/jupyter/Appendix-Programming-Guidelines.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:编程指南\n", + "\n", + "> 本附录包含了有助于指导你进行低级程序设计和编写代码的建议。\n", + "\n", + "当然,这些只是指导方针,而不是规则。我们的想法是将它们用作灵感,并记住偶尔会违反这些指导方针的特殊情况。\n", + "\n", + "\n", + "## 设计\n", + "\n", + "1. **优雅总是会有回报**。从短期来看,似乎需要更长的时间才能找到一个真正优雅的问题解决方案,但是当该解决方案第一次应用并能轻松适应新情况,而不需要数小时,数天或数月的挣扎时,你会看到奖励(即使没有人可以测量它们)。它不仅为你提供了一个更容易构建和调试的程序,而且它也更容易理解和维护,这也正是经济价值所在。这一点可以通过一些经验来理解,因为当你想要使一段代码变得优雅时,你可能看起来效率不是很高。抵制急于求成的冲动,它只会减慢你的速度。\n", + "\n", + "2. **先让它工作,然后再让它变快**。即使你确定一段代码非常重要并且它是你系统中的主要瓶颈**,也是如此。不要这样做。使用尽可能简单的设计使系统首先运行。然后如果速度不够快,请对其进行分析。你几乎总会发现“你的”瓶颈不是问题。节省时间,才是真正重要的东西。\n", + "\n", + "3. **记住“分而治之”的原则**。如果所面临的问题太过混乱**,就去想象一下程序的基本操作,因为存在一个处理困难部分的神奇“片段”(piece)。该“片段”是一个对象,编写使用该对象的代码,然后查看该对象并将其困难部分封装到其他对象中,等等。\n", + "\n", + "4. **将类创建者与类用户(客户端程序员)分开**。类用户是“客户”,不需要也不想知道类幕后发生了什么。类创建者必须是设计类的专家,他们编写类,以便新手程序员都可以使用它,并仍然可以在应用程序中稳健地工作。将该类视为其他类的*服务提供者*(service provider)。只有对其它类透明,才能很容易地使用这个类。\n", + "\n", + "5. **创建类时,给类起个清晰的名字,就算不需要注释也能理解这个类**。你的目标应该是使客户端程序员的接口在概念上变得简单。为此,在适当时使用方法重载来创建直观,易用的接口。\n", + "\n", + "6. **你的分析和设计必须至少能够产生系统中的类、它们的公共接口以及它们与其他类的关系,尤其是基类**。 如果你的设计方法产生的不止于此,就该问问自己,该方法生成的所有部分是否在程序的生命周期内都具有价值。如果不是,那么维护它们会很耗费精力。对于那些不会影响他们生产力的东西,开发团队的成员往往不会去维护,这是许多设计方法都没有考虑的生活现实。\n", + "\n", + "7. **让一切自动化**。首先在编写类之前,编写测试代码,并将其与类保持一致。通过构建工具自动运行测试。你可能会使用事实上的标准Java构建工具Gradle。这样,通过运行测试代码可以自动验证任何更改,将能够立即发现错误。因为你知道自己拥有测试框架的安全网,所以当发现需要时,可以更大胆地进行彻底的更改。请记住,语言的巨大改进来自内置的测试,包括类型检查,异常处理等,但这些内置功能很有限,你必须完成剩下的工作,针对具体的类或程序,去完善这些测试内容,从而创建一个强大的系统。\n", + "\n", + "8. **在编写类之前,先编写测试代码,以验证类的设计是完善的**。如果不编写测试代码,那么就不知道类是什么样的。此外,通过编写测试代码,往往能够激发出类中所需的其他功能或约束。而这些功能或约束并不总是出现在分析和设计过程中。测试还会提供示例代码,显示了如何使用这个类。\n", + "\n", + "9. **所有的软件设计问题,都可以通过引入一个额外的间接概念层次(extra level of conceptual indirection)来解决**。这个软件工程的基本规则[^1]是抽象的基础,是面向对象编程的主要特征。在面向对象编程中,我们也可以这样说:“如果你的代码太复杂,就要生成更多的对象。”\n", + "\n", + "10. **间接(indirection)应具有意义(与准则9一致)**。这个含义可以简单到“将常用代码放在单个方法中。”如果添加没有意义的间接(抽象,封装等)级别,那么它就像没有足够的间接性那样糟糕。\n", + "\n", + "11. **使类尽可能原子化**。 为每个类提供一个明确的目的,它为其他类提供一致的服务。如果你的类或系统设计变得过于复杂,请将复杂类分解为更简单的类。最直观的指标是尺寸大小,如果一个类很大,那么它可能是做的事太多了,应该被拆分。建议重新设计类的线索是:\n", + " - 一个复杂的*switch*语句:考虑使用多态。\n", + " - 大量方法涵盖了很多不同类型的操作:考虑使用多个类。\n", + " - 大量成员变量涉及很多不同的特征:考虑使用多个类。\n", + " - 其他建议可以参见Martin Fowler的*Refactoring: Improving the Design of Existing Code*(重构:改善既有代码的设计)(Addison-Wesley 1999)。\n", + "\n", + "12. **注意长参数列表**。那样方法调用会变得难以编写,读取和维护。相反,尝试将方法移动到更合适的类,并且(或者)将对象作为参数传递。\n", + "\n", + "13. **不要重复自己**。如果一段代码出现在派生类的许多方法中,则将该代码放入基类中的单个方法中,并从派生类方法中调用它。这样不仅可以节省代码空间,而且可以轻松地传播更改。有时,发现这个通用代码会为接口添加有价值的功能。此指南的更简单版本也可以在没有继承的情况下发生:如果类具有重复代码的方法,则将该重复代码放入一个公共方,法并在其他方法中调用它。\n", + "\n", + "14. **注意*switch*语句或链式*if-else*子句**。一个*类型检查编码*(type-check coding)的指示器意味着需要根据某种类型信息选择要执行的代码(确切的类型最初可能不明显)。很多时候可以用继承和多态替换这种代码,多态方法调用将会执行类型检查,并提供了更可靠和更容易的可扩展性。 \n", + "\n", + "15. **从设计的角度,寻找和分离那些因不变的事物而改变的事物**。也就是说,在不强制重新设计的情况下搜索可能想要更改的系统中的元素,然后将这些元素封装在类中。\n", + "\n", + "16. **不要通过子类扩展基本功能**。如果一个接口元素对于类来说是必不可少的,则它应该在基类中,而不是在派生期间添加。如果要在继承期间添加方法,请考虑重新设计。\n", + "\n", + "17. **少即是多**。从一个类的最小接口开始,尽可能小而简单,以解决手头的问题,但不要试图预测类的所有使用方式。在使用该类时,就将会了解如何扩展接口。但是,一旦这个类已经在使用了,就无法在不破坏客户端代码的情况下缩小接口。如果必须添加更多方法,那很好,它不会破坏代码。但即使新方法取代旧方法的功能,也只能是保留现有接口(如果需要,可以结合底层实现中的功能)。如果必须通过添加更多参数来扩展现有方法的接口,请使用新参数创建重载方法,这样,就不会影响到对现有方法的任何调用。\n", + "\n", + "18. **大声读出你的类以确保它们合乎逻辑**。将基类和派生类之间的关系称为“is-a”,将成员对象称为“has-a”。\n", + "\n", + "19. **在需要在继承和组合之间作决定时,问一下自己是否必须向上转换为基类型**。如果不是,则使用组合(成员对象)更好。这可以消除对多种基类型的感知需求(perceived need)。如果使用继承,则用户会认为他们应该向上转型。\n", + "\n", + "20. **注意重载**。方法不应该基于参数的值而有条件地执行代码。在这里,应该创建两个或多个重载方法。\n", + "\n", + "21. **使用异常层次结构**,最好是从标准Ja​​va异常层次结构中的特定适当类派生。然后,捕获异常的人可以为特定类型的异常编写处理程序,然后为基类型编写处理程序。如果添加新的派生异常,现有客户端代码仍将通过基类型捕获异常。\n", + "\n", + "22. **有时简单的聚合可以完成工作**。航空公司的“乘客舒适系统”由独立的元素组成:座位,空调,影视等,但必须在飞机上创建许多这样的元素。你创建私有成员并建立一个全新的接口了吗?如果不是,在这种情况下,组件也应该是公共接口的一部分,因此应该创建公共成员对象。这些对象有自己的私有实现,这些实现仍然是安全的。请注意,简单聚合不是经常使用的解决方案,但确实会有时候会用到。\n", + "\n", + "23. **考虑客户程序员和维护代码的人的观点**。设计类以便尽可能直观地被使用。预测要进行的更改,并精心设计类,以便轻松地进行更改。\n", + "\n", + "24. **注意“巨型对象综合症”**(giant object syndrome)。这通常是程序员的痛苦,他们是面向对象编程的新手,总是编写面向过程程序并将其粘贴在一个或两个巨型对象中。除应用程序框架外,对象代表应用程序中的概念,而不是应用程序本身。\n", + "\n", + "25. **如果你必须做一些丑陋的事情,至少要把类内的丑陋本地化**。\n", + "\n", + "26. **如果必须做一些不可移植的事情,那就对这个事情做一个抽象,并在一个类中进行本地化**。这种额外的间接级别可防止在整个程序中扩散这种不可移植性。 (这个原则也体现在*桥接*模式中,等等)。\n", + "\n", + "27. **对象不应该仅仅只是持有一些数据**。它们也应该有明确的行为。有时候,“数据传输对象”(data transfer objects)是合适的,但只有在泛型集合不合适时,才被明确用于打包和传输一组元素。\n", + "\n", + "28. **在从现有类创建新类时首先选择组合**。仅在设计需要时才使用继承。如果在可以使用组合的地方使用继承,那么设计将会变得很复杂,这是没必要的。\n", + "\n", + "29. **使用继承和覆盖方法来表达行为的差异,而不是使用字段来表示状态的变化**。如果发现一个类使用了状态变量,并且有一些方法是基于这些变量切换行为的,那么请重新设计它,以表示子类和覆盖方法中的行为差异。一个极端的反例是继承不同的类来表示颜色,而不是使用“颜色”字段。\n", + "\n", + "30. **注意*协变*(variance)**。两个语义不同的对象可能具有相同的操作或职责。为了从继承中受益,会试图让其中一个成为另一个的子类,这是一种很自然的诱惑。这称为协变,但没有真正的理由去强制声明一个并不存在的父子类关系。更好的解决方案是创建一个通用基类,并为两者生成一个接口,使其成为这个通用基类的派生类。这仍然可以从继承中受益,并且这可能是关于设计的一个重要发现。\n", + "\n", + "31. **在继承期间注意*限定*(limitation)**。最明确的设计为继承的类增加了新的功能。含糊的设计在继承期间删除旧功能而不添加新功能。但是规则是用来打破的,如果是通过调用一个旧的类库来工作,那么将一个现有类限制在其子类型中,可能比重构层次结构更有效,因此新类适合在旧类的上层。\n", + "\n", + "32. **使用设计模式来消除“裸功能”(naked functionality)**。也就是说,如果类只需要创建一个对象,请不要推进应用程序并写下注释“只生成一个。”应该将其包装成一个单例(singleton)。如果主程序中有很多乱七八糟的代码去创建对象,那么找一个像工厂方法一样的创建模式,可以在其中封装创建过程。消除“裸功能”不仅会使代码更易于理解和维护,而且还会使其能够更加防范应对后面的善意维护者(well-intentioned maintainers)。\n", + "\n", + "33. **注意“分析瘫痪”(analysis paralysis)**。记住,不得不经常在不了解整个项目的情况下推进项目,并且通常了解那些未知因素的最好、最快的方式是进入下一步而不是尝试在脑海中弄清楚。在获得解决方案之前,往往无法知道解决方案。Java有内置的防火墙,让它们为你工作。你在一个类或一组类中的错误不会破坏整个系统的完整性。\n", + "\n", + "34. **如果认为自己有很好的分析,设计或实施,请做一个演练**。从团队外部带来一些人,不一定是顾问,但可以是公司内其他团体的人。用一双新眼睛评审你的工作,可以在一个更容易修复它们的阶段发现问题,而不仅仅是把大量时间和金钱全扔到演练过程中。\n", + "\n", + "\n", + "## 实现\n", + "\n", + "36. **遵循编码惯例**。有很多不同的约定,例如,[谷歌使用的约定](https://google.github.io/styleguide/javaguide.html)(本书中的代码尽可能地遵循这些约定)。如果坚持使用其他语言的编码风格,那么读者就会很难去阅读。无论决定采用何种编码约定,都要确保它们在整个项目中保持一致。集成开发环境通常包含内置的重新格式化(reformatter)和检查器(checker)。\n", + "\n", + "37. **无论使用何种编码风格,如果你的团队(甚至更好是公司)对其进行标准化,它就确实会产生重大影响**。这意味着,如果不符合这个标准,那么每个人都认为修复别人的编码风格是公平的游戏。标准化的价值在于解析代码可以花费较少的脑力,因此可以更专注于代码的含义。\n", + "\n", + "38. **遵循标准的大写规则**。类名的第一个字母大写。字段,方法和对象(引用)的第一个字母应为小写。所有标识符应该将各个单词组合在一起,并将所有中间单词的首字母大写。例如:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "- **ThisIsAClassName**\n", + "- **thisIsAMethodOrFieldName**\n", + "\n", + "将 **static final** 类型的标识符的所有字母全部大写,并用下划线分隔各个单词,这些标识符在其定义中具有常量初始值。这表明它们是编译时常量。\n", + "- **包是一个特例**,它们都是小写的字母,即使是中间词。域扩展(com,org,net,edu等)也应该是小写的。这是Java 1.1和Java 2之间的变化。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "39. **不要创建自己的“装饰”私有字段名称**。这通常以前置下划线和字符的形式出现。匈牙利命名法(译者注:一种命名规范,基本原则是:变量名=属性+类型+对象描述。Win32程序风格采用这种命名法,如`WORD wParam1;LONG lParam2;HANDLE hInstance`)是最糟糕的例子,你可以在其中附加额外字符用于指示数据类型,用途,位置等,就好像你正在编写汇编语言一样,编译器根本没有提供额外的帮助。这些符号令人困惑,难以阅读,并且难以执行和维护。让类和包来指定名称范围。如果认为必须装饰名称以防止混淆,那么代码就可能过于混乱,这应该被简化。\n", + "\n", + "40. 在创建一般用途的类时,**遵循“规范形式”**。包括**equals()**,**hashCode()**,**toString()**,**clone()**的定义(实现**Cloneable**,或选择其他一些对象复制方法,如序列化),并实现**Comparable**和**Serializable**。\n", + "\n", + "41. **对读取和更改私有字段的方法使用“get”,“set”和“is”命名约定**。这种做法不仅使类易于使用,而且也是命名这些方法的标准方法,因此读者更容易理解。\n", + "\n", + "42. **对于所创建的每个类,请包含该类的JUnit测试**(请参阅*junit.org*以及[第十六章:代码校验]()中的示例)。无需删除测试代码即可在项目中使用该类,如果进行更改,则可以轻松地重新运行测试。测试代码也能成为如何使用这个类的示例。\n", + "\n", + "43. **有时需要继承才能访问基类的protected成员**。这可能导致对多种基类型的感知需求(perceived need)。如果不需要向上转型,则可以首先派生一个新类来执行受保护的访问。然后把该新类作为使用它的任何类中的成员对象,以此来代替直接继承。\n", + "\n", + "44. **为了提高效率,避免使用*final*方法**。只有在分析后发现方法调用是瓶颈时,才将**final**用于此目的。\n", + "\n", + "45. **如果两个类以某种功能方式相互关联(例如集合和迭代器),则尝试使一个类成为另一个类的内部类**。这不仅强调了类之间的关联,而且通过将类嵌套在另一个类中,可以允许在单个包中重用类名。Java集合库通过在每个集合类中定义内部**Iterator**类来实现此目的,从而为集合提供通用接口。使用内部类的另一个原因是作为**私有**实现的一部分。这里,内部类将有利于实现隐藏,而不是上面提到的类关联和防止命名空间污染。\n", + "\n", + "46. **只要你注意到类似乎彼此之间具有高耦合,请考虑如果使用内部类可能获得的编码和维护改进**。内部类的使用不会解耦类,而是明确耦合关系,并且更方便。\n", + "\n", + "47. **不要成为过早优化的牺牲品**。过早优化是很疯狂的行为。特别是,不要担心编写(或避免)本机方法(native methods),将某些方法设置为**final**,或者在首次构建系统时调整代码以使其高效。你的主要目标应该是验证设计。即使设计需要一定的效率,也*先让它工作,然后再让它变快*。\n", + "\n", + "48. **保持作用域尽可能小,以便能见度和对象的寿命尽可能小**。这减少了在错误的上下文中使用对象并隐藏了难以发现的bug的机会。例如,假设有一个集合和一段迭代它的代码。如果复制该代码以用于一个新集合,那么可能会意外地将旧集合的大小用作新集合的迭代上限。但是,如果旧集合比较大,则会在编译时捕获错误。\n", + "\n", + "49. **使用标准Java库中的集合**。熟练使用它们,将会大大提高工作效率。首选**ArrayList**用于序列,**HashSet**用于集合,**HashMap**用于关联数组,**LinkedList**用于堆栈(而不是**Stack**,尽管也可以创建一个适配器来提供堆栈接口)和队列(也可以使用适配器,如本书所示)。当使用前三个时,将其分别向上转型为**List**,**Set**和**Map**,那么就可以根据需要轻松更改为其他实现。\n", + "\n", + "50. **为使整个程序健壮,每个组件必须健壮**。在所创建的每个类中,使用Java所提供的所有工具,如访问控制,异常,类型检查,同步等。这样,就可以在构建系统时安全地进入下一级抽象。\n", + "\n", + "51. **编译时错误优于运行时错误**。尝试尽可能在错误发生点处理错误。在最近的处理程序中尽其所能地捕获它能处理的所有异常。在当前层面处理所能处理的所有异常,如果解决不了,就重新抛出异常。 \n", + "\n", + "52. **注意长方法定义**。方法应该是简短的功能单元,用于描述和实现类接口的离散部分。维护一个冗长而复杂的方法是很困难的,而且代价很大,并且这个方法可能是试图做了太多事情。如果看到这样的方法,这表明,至少应该将它分解为多种方法。也可能建议去创建一个新类。小的方法也可以促进类重用。(有时方法必须很大,但它们应该只做一件事。)\n", + "\n", + "53. **保持“尽可能私有”**。一旦公开了你的类库中的一个方面(一个方法,一个类,一个字段),你就永远无法把它拿回来。如果这样做,就将破坏某些人的现有代码,迫使他们重写和重新设计。如果你只公开了必须公开的内容,就可以轻易地改变其他一切,而不会对其他人造成影响,而且由于设计趋于发展,这是一个重要的自由。通过这种方式,更改具体实现将对派生类造成的影响最小。在处理多线程时,私有尤其重要,只有**私有**字段可以防止不同步使用。具有包访问权限的类应该仍然具有**私有**字段,但通常有必要提供包访问权限的方法而不是将它们**公开**。\n", + "\n", + "54. **大量使用注释,并使用*Javadoc commentdocumentation*语法生成程序文档**。但是,注释应该为代码增加真正的意义,如果注释只是重申了代码已经清楚表达的内容,这是令人讨厌的。请注意,Java类和方法名称的典型详细信息减少了对某些注释的需求。\n", + "\n", + "55. **避免使用“魔法数字”**。这些是指硬编码到代码中的数字。如果后续必须要更改它们,那将是一场噩梦,因为你永远不知道“100”是指“数组大小”还是“完全不同的东西”。相反,创建一个带有描述性名称的常量并在整个程序中使用常量标识符。这使程序更易于理解,更易于维护。\n", + "\n", + "56. **在创建构造方法时,请考虑异常**。最好的情况是,构造方法不会做任何抛出异常的事情。次一级的最佳方案是,该类仅由健壮的类组成或继承自健壮的类,因此如果抛出异常则不需要处理。否则,必须清除**finally**子句中的组合类。如果构造方法必然失败,则适当的操作是抛出异常,因此调用者不会认为该对象是正确创建的而盲目地继续下去。\n", + "\n", + "57. **在构造方法内部,只需要将对象设置为正确的状态**。主动避免调用其他方法(**final**方法除外),因为这些方法可以被其他人覆盖,从而在构造期间产生意外结果。(有关详细信息,请参阅[第六章:初始化和清理]()章节。)较小,较简单的构造方法不太可能抛出异常或导致问题。\n", + "\n", + "58. **如果类在客户端程序员用完对象时需要进行任何清理,请将清理代码放在一个明确定义的方法中**,并使用像 **dispose()** 这样的名称来清楚地表明其目的。另外,在类中放置一个 **boolean** 标志来指示是否调用了 **dispose()** ,因此 **finalize()** 可以检查“终止条件”(参见[第六章:初始化和清理]()章节)。\n", + "\n", + "59. ***finalize()* 的职责只能是验证对象的“终止条件”以进行调试**。(参见[第六章:初始化和清理]()一章)在特殊情况下,可能需要释放垃圾收集器无法释放的内存。因为可能无法为对象调用垃圾收集器,所以无法使用 **finalize()** 执行必要的清理。为此,必须创建自己的 **dispose()** 方法。在类的 **finalize()** 方法中,检查以确保对象已被清理,如果没有被清理,则抛出一个派生自**RuntimeException**的异常,以指示编程错误。在依赖这样的计划之前,请确保 **finalize()** 适用于你的系统。(可能需要调用 **System.gc()** 来确保此行为。)\n", + "\n", + "60. **如果必须在特定范围内清理对象(除了通过垃圾收集器),请使用以下准则:** 初始化对象,如果成功,立即进入一个带有 **finally** 子句的 **try** 块,并在 **finally**中执行清理操作。\n", + "\n", + "61. **在继承期间覆盖 *finalize()* 时,记得调用 *super.finalize()***。(如果是直接继承自 **Object** 则不需要这样做。)调用 **super.finalize()** 作为重写的 **finalize()** 的最终行为而不是在第一行调用它,这样可以确保基类组件在需要时仍然有效。\n", + "\n", + "62. **创建固定大小的对象集合时,将它们转换为数组,** 尤其是在从方法中返回此集合时。这样就可以获得数组编译时类型检查的好处,并且数组的接收者可能不需要在数组中强制转换对象来使用它们。请注意,集合库的基类 **java.util.Collection** 有两个 **toArray()** 方法来完成此任务。\n", + "\n", + "63. **优先选择 *接口* 而不是 *抽象类***。如果知道某些东西应该是基类,那么第一选择应该是使其成为一个接口,并且只有在需要方法定义或成员变量时才将其更改为抽象类。一个接口关心客户端想要做什么,而一个类倾向于关注(或允许)实现细节。\n", + "\n", + "64. **为了避免非常令人沮丧的经历,请确保类路径中的每个名称只对应一个不在包中的类**。否则,编译器可以首先找到具有相同名称的其他类,并报告没有意义的错误消息。如果你怀疑自己有类路径问题,请尝试在类路径的每个起始点查找具有相同名称的 **.class** 文件。理想情况下,应该将所有类放在包中。\n", + "\n", + "65. **注意意外重载**。如果尝试覆盖基类方法但是拼写错误,则最终会添加新方法而不是覆盖现有方法。但是,这是完全合法的,因此在编译时或运行时不会获得任何错误消息,但代码将无法正常工作。始终使用 **@Override** 注释来防止这种情况。\n", + "\n", + "66. **注意过早优化**。先让它工作,然后再让它变快。除非发现代码的特定部分存在性能瓶颈。除非是使用分析器发现瓶颈,否则过早优化会浪费时间。性能调整所隐藏的额外成本是代码将变得难以理解和维护。\n", + "\n", + "67. **请注意,相比于编写代码,代码被阅读的机会更多**。清晰的设计可能产生易于理解的程序,但注释,详细解释,测试和示例是非常宝贵的,它们可以帮助你和你的所有后继者。如果不出意外,试图从JDK文档中找出有用信息的挫败感应该可以说服你。\n", + "\n", + "[^1]: Andrew Koenig向我解释了它。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Standard-IO.ipynb b/jupyter/Appendix-Standard-IO.ipynb new file mode 100644 index 00000000..493d055f --- /dev/null +++ b/jupyter/Appendix-Standard-IO.ipynb @@ -0,0 +1,335 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:标准IO\n", + "\n", + ">*标准 I/O*这个术语参考Unix中的概念,指程序所使用的单一信息流(这种思想在大多数操作系统中,也有相似形式的实现)。\n", + "\n", + "程序的所有输入都可以来自于*标准输入*,其所有输出都可以流向*标准输出*,并且其所有错误信息均可以发送到*标准错误*。*标准 I/O* 的意义在于程序之间可以很容易地连接起来,一个程序的标准输出可以作为另一个程序的标准输入。这是一个非常强大的工具。\n", + "\n", + "## 从标准输入中读取\n", + "\n", + "遵循标准 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` 之前必须先对其进行包装。\n", + "\n", + "我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader` 把 `System.in` 转换[^2]成 `Reader` 。下面这个例子将键入的每一行显示出来:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// standardio/Echo.java\n", + "// How to read from standard input\n", + "import java.io.*;\n", + "import onjava.TimedAbort;\n", + "\n", + "public class Echo {\n", + " public static void main(String[] args) {\n", + " TimedAbort abort = new TimedAbort(2);\n", + " new BufferedReader(\n", + " new InputStreamReader(System.in))\n", + " .lines()\n", + " .peek(ln -> abort.restart())\n", + " .forEach(System.out::println);\n", + " // Ctrl-Z or two seconds inactivity\n", + " // terminates the program\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。\n", + "\n", + "## 将`System.out` 转换成 `PrintWriter`\n", + "\n", + "`System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// standardio/ChangeSystemOut.java\n", + "// Turn System.out into a PrintWriter\n", + "\n", + "import java.io.*;\n", + "\n", + "public class ChangeSystemOut {\n", + " public static void main(String[] args) {\n", + " PrintWriter out =\n", + " new PrintWriter(System.out, true);\n", + " out.println(\"Hello, world\");\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "输出结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Hello, world" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "要使用 `PrintWriter` 带有两个参数的构造器,并设置第二个参数为 `true`,从而使能自动刷新到输出缓冲区的功能;否则,可能无法看到打印输出。\n", + "\n", + "## 重定向标准 I/O\n", + "\n", + "Java的 `System` 类提供了简单的 `static` 方法调用,从而能够重定向标准输入流、标准输出流和标准错误流:\n", + "- setIn(InputStream)\n", + "- setOut(PrintStream)\n", + "- setErr(PrintStream)\n", + "\n", + "如果我们突然需要在显示器上创建大量的输出,而这些输出滚动的速度太快以至于无法阅读时,重定向输出就显得格外有用,可把输出内容重定向到文件中供后续查看。对于我们想重复测试特定的用户输入序列的命令行程序来说,重定向输入就很有价值。下例简单演示了这些方法的使用:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// standardio/Redirecting.java\n", + "// Demonstrates standard I/O redirection\n", + "import java.io.*;\n", + "\n", + "public class Redirecting {\n", + " public static void main(String[] args) {\n", + " PrintStream console = System.out;\n", + " try (\n", + " BufferedInputStream in = new BufferedInputStream(\n", + " new FileInputStream(\"Redirecting.java\"));\n", + " PrintStream out = new PrintStream(\n", + " new BufferedOutputStream(\n", + " new FileOutputStream(\"Redirecting.txt\")))\n", + " ) {\n", + " System.setIn(in);\n", + " System.setOut(out);\n", + " System.setErr(out);\n", + " new BufferedReader(\n", + " new InputStreamReader(System.in))\n", + " .lines()\n", + " .forEach(System.out::println);\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " } finally {\n", + " System.setOut(console);\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "该程序将文件中内容载入到标准输入,并把标准输出和标准错误重定向到另一个文件。它在程序的开始保存了最初对 `System.out` 对象的引用,并且在程序结束时将系统输出恢复到了该对象上。\n", + "\n", + "I/O重定向操作的是字节流而不是字符流,因此使用 `InputStream` 和 `OutputStream`,而不是 `Reader` 和 `Writer`。\n", + "\n", + "\n", + "## 执行控制\n", + "\n", + "你经常需要在Java内部直接执行操作系统的程序,并控制这些程序的输入输出,Java类库提供了执行这些操作的类。\n", + "\n", + "一项常见的任务是运行程序并将输出结果发送到控制台。本节包含了一个可以简化此任务的实用工具。\n", + "\n", + "在使用这个工具时可能会产生两种类型的错误:导致异常的普通错误——对于这些错误我们只需要重新抛出一个 `RuntimeException` 即可,以及进程自身的执行过程中导致的错误——我们需要用单独的异常来报告这些错误:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/OSExecuteException.java\n", + "package onjava;\n", + "\n", + "public class OSExecuteException extends RuntimeException {\n", + " public OSExecuteException(String why) {\n", + " super(why);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了运行程序,我们需要传递给 `OSExecute.command()` 一个 `String command`,我们可以在控制台键入同样的指令运行程序。该命令传递给 `java.lang.ProcessBuilder` 的构造器(需要将其作为 `String` 对象的序列),然后启动生成的 `ProcessBuilder` 对象。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// onjava/OSExecute.java\n", + "// Run an operating system command\n", + "// and send the output to the console\n", + "package onjava;\n", + "import java.io.*;\n", + "\n", + "public class OSExecute {\n", + " public static void command(String command) {\n", + " boolean err = false;\n", + " try {\n", + " Process process = new ProcessBuilder(\n", + " command.split(\" \")).start();\n", + " try (\n", + " BufferedReader results = new BufferedReader(\n", + " new InputStreamReader(\n", + " process.getInputStream()));\n", + " BufferedReader errors = new BufferedReader(\n", + " new InputStreamReader(\n", + " process.getErrorStream()))\n", + " ) {\n", + " results.lines()\n", + " .forEach(System.out::println);\n", + " err = errors.lines()\n", + " .peek(System.err::println)\n", + " .count() > 0;\n", + " }\n", + " } catch (IOException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " if (err)\n", + " throw new OSExecuteException(\n", + " \"Errors executing \" + command);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为了捕获在程序执行时产生的标准输出流,我们可以调用 `getInputStream()`。这是因为 `InputStream` 是我们可以从中读取信息的流。\n", + "\n", + "这里这些行只是被打印了出来,但是你也可以从 `command()` 捕获和返回它们。\n", + "\n", + "该程序的错误被发送到了标准错误流,可以调用 `getErrorStream()` 捕获。如果存在任何错误,它们都会被打印并且抛出 `OSExcuteException` ,以便调用程序处理这个问题。\n", + "\n", + "下面是展示如何使用 `OSExecute` 的示例:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// standardio/OSExecuteDemo.java\n", + "// Demonstrates standard I/O redirection\n", + "// {javap -cp build/classes/main OSExecuteDemo}\n", + "import onjava.*;\n", + "\n", + "public class OSExecuteDemo {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里使用 `javap` 反编译器(随JDK发布)来反编译程序,编译结果:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Compiled from \"OSExecuteDemo.java\"\n", + "public class OSExecuteDemo {\n", + " public OSExecuteDemo();\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[^1]: 译者注:这里用到了**装饰器模式**。\n", + "\n", + "[^2]: 译者注:这里用到了**适配器模式**。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Supplements.ipynb b/jupyter/Appendix-Supplements.ipynb new file mode 100644 index 00000000..71c9d00d --- /dev/null +++ b/jupyter/Appendix-Supplements.ipynb @@ -0,0 +1,39 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:补充\n", + "\n", + "> 本书有许多补充内容,包括MindView网站提供的项目和服务。\n", + "\n", + "本附录介绍了这些补充内容,你可以自行决定它们是否对你有所帮助。\n", + "\n", + "\n", + "## 可下载的补充\n", + "\n", + "可以从 [https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 免费下载本书的代码。这里包括Gradle构建文件和其它一些必要的支持文件,以便成功构建和执行本书中所有的示例代码。\n", + "\n", + "\n", + "## 通过Thinking-in-C来巩固Java基础\n", + "\n", + "在 [www.OnJava8.com](www.OnJava8.com) 上,可以免费下载*Thinking in C*的演示文稿。 此演示文稿由Chuck Allison创建,由MindView有限责任公司开发。这是一个电子演示文稿,介绍了Java语法所基于的C语法,运算符和函数。\n", + "\n", + "\n", + "## Hand-On Java 电子演示文稿\n", + "\n", + "*Hand-On Java 电子演示文稿*(Hands-On Java eSeminar)是基于*Thinking in Java*第2版。对应于该书中的每一章,它附带有一个音频讲解和相应的幻灯片。我创建了这个电子演示文稿,并讲述了这些材料。这个资料是HTML5格式的,所以它应该可以在大多数现代浏览器上运行。该演示文稿将在[www.OnJava8.com](www.OnJava8.com)上发售,你可以在该网站上找到该产品的试用版演示。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb b/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb new file mode 100644 index 00000000..ac347181 --- /dev/null +++ b/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb @@ -0,0 +1,44 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "# 附录:C++和Java的优良传统\n", + "\n", + "> 在各种讨论声中,有一些人认为C++是一种设计糟糕的语言。 我认为理解C++和Java语言的选择有助于了解更大的视角。\n", + "\n", + "也就是说,我几乎不再使用C++了。当我使用它的时候,要么是用来检查遗留代码,要么是编写性能关键(performance-critical)部分,程序通常尽可能小,以便用其他语言编写的其他程序来调用。\n", + "\n", + "因为我在最初的8年里一直在C++标准委员会工作,所以我见证了那些被做出的决定。它们都经过了极其谨慎的考虑,远远超过了许多在Java中做出的决定。\n", + "\n", + "然而,正如人们正确地指出的那样,由此产生的语言使用起来既复杂又痛苦,而且只要我一段时间不使用它,我就会忘记那些古怪的规则。在我写书的时候,我是从第一原理(first principles)处了解这些规则的,而不是记住了它们。\n", + "\n", + "为了理解C++语言为何既令人不愉快且复杂,同时又是精心设计的,必须要牢记C++中所有内容的主要设计决策:与C. Bjarne Stroustrup(该语言的创造者,即“C++之父”)的兼容性决定。这样的设计似乎是为了可以让大量的C程序员透明地转移到对象(代指C++)上:允许他们在C++下编译他们的C代码。这是一个巨大的限制,一直是C++最大的优势......而且也是它的祸根。这就是使得C++成功的原因,也是使它复杂的原因。\n", + "\n", + "它也欺骗了那些不太了解C++的Java设计师。例如,他们认为运算符重载对于程序员来说很难正确使用。这在C++中基本上是正确的,因为C++既有栈分配又有堆分配,你必须重载运算符来处理所有情况而且不要造成内存泄漏。这确实很困难。然而,Java有单一的内存分配机制和一个垃圾收集器,这使得运算符重载变得微不足道,正如C#中那样(但在早于Java的Python中已经可以看到)。但多年来,来自Java团队的一贯态度是“运算符重载太复杂了”。这里还有许多决策,所做的事明显不应该是他们做的。正是由于这些原因,让我有了蔑视Gosling(即“Java之父”)和Java团队决策的名声。(Java 7和8由于某种原因包含了更好的决策。但是向后兼容性这个约束总是会阻碍真正的改进。语言永远不会是它本来的样子。)\n", + "\n", + "还有很多其他的例子。“为了提高效率,必须包含基本类型”;坚持“万物皆对象”是正确的;当对性能有要求的时候,提供一个陷阱门(trap door)来做低级别的活动(lower-level activities)(这里也可以使用hotspot技术透明地提高性能,正如他们最终做的那样);不能直接使用浮点处理器去计算超越函数,它用软件来完成。我已经尽可能多地提出了这样的问题,但我得到的却一直是类似“这是Java方式”这样的回复。\n", + "\n", + "当我提出关于泛型的设计有多糟糕的时候,我得到了相同的回复,以及“我们必须向后兼容那样以前用Java做出的决策”(即使它们是糟糕的决策)。最近越来越多的人已经获得了足够的泛型经验,可以发现泛型真的很难用。事实上,C++模板更强大、更一致(现在更容易使用,因为编译器的错误消息是可以容忍的)。人们一直在认真对待物化(reification),这可能是有用的东西,但是在那种被严格约束所削弱的设计中并没有多大影响。\n", + "\n", + "这样的例子还有很多很多。这是否意味着Java失败了?绝对不。Java将程序员的主流带入了垃圾收集、虚拟机和一致的错误处理模型的世界。由于它的所有缺陷,它将我们提升到了一个水平,现在我们已经准备好使用更高级别的语言了。\n", + "\n", + "有一点,C++是领先的语言,人们认为它总是如此。许多人对Java有同样的看法,但由于JVM,Java使得取代自己变得更加容易。现在有可能会有人创建一种新语言,并使其在短时间内像Java一样高效运行。以前,为新语言开发一个正确有效的编译器需要花费大部分开发时间。\n", + "\n", + "这种情况已经发生了,包括像Scala这样的高级静态语言,以及动态语言,新的且可移植的,如Groovy,Clojure,JRuby和Jython。这是未来,并且过渡很顺畅,因为可以很轻易地将这些新语言与现有Java代码结合使用,并且必要时可以重写那些在Java中的瓶颈。\n", + "\n", + "在撰写本文时,Java是世界上的头号编程语言。然而,Java最终将会减弱,就像C++一样,沦只在特殊情况下使用(或者只是用来支持传统的代码,因为它不能像C++那样和硬件连接)。但是无意中的好处,也是Java真正意外的光彩之处在于它为自己的替代品创造了一条非常畅通的道路,即使Java本身已经达到了无法再发展的程度。未来所有的语言都应该从中学习:要么创建一个可以重构的文化(像Python和Ruby做的那样),要么就让竞争者茁壮成长。\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb b/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb new file mode 100644 index 00000000..2f0ad1c4 --- /dev/null +++ b/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb @@ -0,0 +1,1340 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[TOC]\n", + "\n", + "\n", + "\n", + "\n", + "# 附录:理解equals和hashCode方法\n", + "假设有一个容器使用hash函数,当你创建一个放到这个容器时,你必须定义 **hashCode()** 函数和 **equals()** 函数。这两个函数一起被用于hash容器中的查询操作。\n", + "\n", + "\n", + "\n", + "## equals规范\n", + "当你创建一个类的时候,它自动继承自 **Objcet** 类。如果你不覆写 **equals()** ,你将会获得 **Objcet** 对象的 **equals()** 函数。默认情况下,这个函数会比较对象的地址。所以只有你在比较同一个对象的时候,你才会获得**true**。默认的情况是\"区分度最高的\"。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/DefaultComparison.java\n", + "class DefaultComparison {\n", + " private int i, j, k;\n", + " DefaultComparison(int i, int j, int k) {\n", + " this.i = i;\n", + " this.j = j;\n", + " this.k = k;\n", + " }\n", + " \n", + " public static void main(String[] args) {\n", + " DefaultComparison \n", + " a = new DefaultComparison(1, 2, 3),\n", + " b = new DefaultComparison(1, 2, 3);\n", + " System.out.println(a == a);\n", + " System.out.println(a == b);\n", + " } \n", + "} \n", + "/*\n", + "Output:\n", + "true\n", + "false\n", + "*/\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "通常你会希望放宽这个限制。一般来说如果两个对象有相同的类型和相同的字段,你会认为这两个对象相等,但也会有一些你不想加入 **equals()** 函数中来比较的字段。这是类型设计的一部分。\n", + "\n", + "一个合适的 **equals()**函数必须满足以下五点条件:\n", + "1. 反身性:对于任何 **x**, **x.equals(x)** 应该返回 **true**。\n", + "2. 对称性:对于任何 **x** 和 **y**, **x.equals(y)** 应该返回 **true**当且仅当 **y.equals(x)** 返回 **true** 。\n", + "3. 传递性:对于任何**x**,**y**,还有**z**,如果 **x.equals(y)** 返回 **true** 并且 **y.equals(z)** 返回 **true**,那么 **x.equals(z)** 应该返回 **true**。\n", + "4. 一致性:对于任何 **x**和**y**,在对象没有被改变的情况下,多次调用 **x.equals(y)** 应该总是返回 **true** 或者**false**。\n", + "5. 对于任何非**null**的**x**,**x.equals(null)**应该返回**false**。\n", + "\n", + "下面是满足这些条件的测试,并且判断对象是否和自己相等(我们这里称呼其为**右值**):\n", + "1. 如果**右值**是**null**,那么不相等。\n", + "2. 如果**右值**是**this**,那么两个对象相等。\n", + "3. 如果**右值**不是同一个类型或者子类,那么两个对象不相等。\n", + "4. 如果所有上面的检查通过了,那么你必须决定 **右值** 中的哪些字段是重要的,然后比较这些字段。\n", + "Java 7 引入了 **Objects** 类型来帮助这个流程,这样我们能够写出更好的 **equals()** 函数。\n", + "\n", + "下面的例子比较了不同类型的 **Equality**类。为了避免重复的代码,我们使用*工厂函数设计模*式来实现样例。 **EqualityFactory**接口提供**make()**函数来生成一个**Equaity**对象,这样不同的**EqualityFactory**能够生成**Equality**不同的子类。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/EqualityFactory.java\n", + "import java.util.*;\n", + "interface EqualityFactory {\n", + " Equality make(int i, String s, double d);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "现在我们来定义 **Equality**,它包含三个字段(所有的字段我们认为在比较中都很重要)和一个 **equals()** 函数用来满足上述的四种检查。构造函数展示了它的类名来保证我们在执行我们想要的测试:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/Equality.java\n", + "import java.util.*;\n", + "public class Equality {\n", + " protected int i;\n", + " protected String s;\n", + " protected double d;public Equality(int i, String s, double d) {\n", + " this.i = i;\n", + " this.s = s;\n", + " this.d = d;\n", + " System.out.println(\"made 'Equality'\");\n", + " } \n", + " \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " if(rval == null)\n", + " return false;\n", + " if(rval == this)\n", + " return true;\n", + " if(!(rval instanceof Equality))\n", + " return false;\n", + " Equality other = (Equality)rval;\n", + " if(!Objects.equals(i, other.i))\n", + " return false;\n", + " if(!Objects.equals(s, other.s))\n", + " return false;\n", + " if(!Objects.equals(d, other.d))return false;\n", + " return true;\n", + " } \n", + " \n", + " public void test(String descr, String expected, Object rval) {\n", + " System.out.format(\"-- Testing %s --%n\" + \"%s instanceof Equality: %s%n\" +\n", + " \"Expected %s, got %s%n\",\n", + " descr, descr, rval instanceof Equality,\n", + " expected, equals(rval));\n", + " } \n", + " \n", + " public static void testAll(EqualityFactory eqf) {\n", + " Equality\n", + " e = eqf.make(1, \"Monty\", 3.14),\n", + " eq = eqf.make(1, \"Monty\", 3.14),\n", + " neq = eqf.make(99, \"Bob\", 1.618);\n", + " e.test(\"null\", \"false\", null);\n", + " e.test(\"same object\", \"true\", e);\n", + " e.test(\"different type\",\n", + " \"false\", Integer.valueOf(99));e.test(\"same values\", \"true\", eq);\n", + " e.test(\"different values\", \"false\", neq);\n", + " } \n", + " \n", + " public static void main(String[] args) {\n", + " testAll( (i, s, d) -> new Equality(i, s, d));\n", + " } \n", + " \n", + "} \n", + "/*\n", + "Output:\n", + "made 'Equality'\n", + "made 'Equality'\n", + "made 'Equality'\n", + "-- Testing null --\n", + "null instanceof Equality: false\n", + "Expected false, got false\n", + "-- Testing same object --\n", + "same object instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different type --\n", + "different type instanceof Equality: false\n", + "Expected false, got false-- Testing same values --\n", + "same values instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different values --\n", + "different values instanceof Equality: true\n", + "Expected false, got false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**testAll()** 执行了我们期望的所有不同类型对象的比较。它使用工厂创建了**Equality**对象。\n", + "\n", + "在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示**make()**函数。\n", + "\n", + "上述的 **equals()** 函数非常繁琐,并且我们能够将其简化成规范的形式,请注意:\n", + "1. **instanceof**检查减少了**null**检查的需要。\n", + "2. 和**this**的比较是多余的。一个正确书写的 **equals()** 函数能正确地和自己比较。\n", + "\n", + "\n", + "因为 **&&** 是一个短路比较,它会在第一次遇到失败的时候退出并返回**false**。所以,通过使用 **&&** 将检查链接起来,我们可以写出更精简的 **equals()** 函数:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SuccinctEquality.java\n", + "import java.util.*;\n", + "public class SuccinctEquality extends Equality {\n", + " public SuccinctEquality(int i, String s, double d) {\n", + " super(i, s, d);\n", + " System.out.println(\"made 'SuccinctEquality'\");\n", + " } \n", + " \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof SuccinctEquality &&\n", + " Objects.equals(i, ((SuccinctEquality)rval).i) &&\n", + " Objects.equals(s, ((SuccinctEquality)rval).s) &&\n", + " Objects.equals(d, ((SuccinctEquality)rval).d);\n", + " } \n", + " public static void main(String[] args) {\n", + " Equality.testAll( (i, s, d) ->\n", + " new SuccinctEquality(i, s, d));\n", + " } \n", + " \n", + "}\n", + "/* Output:\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "-- Testing null --\n", + "null instanceof Equality: false\n", + "Expected false, got false\n", + "-- Testing same object --\n", + "same object instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different type --\n", + "different type instanceof Equality: false\n", + "Expected false, got false\n", + "-- Testing same values --\n", + "same values instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different values --different values instanceof Equality: true\n", + "Expected false, got false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于每个 **SuccinctEquality**,基类构造函数在派生类构造函数前被调用,输出显示我们依然获得了正确的结果,你可以发现短路返回已经发生了,不然的话,**null**测试和“不同类型”的测试会在 **equals()** 函数下面的比较中强制转化的时候抛出异常。\n", + " **Objects.equals()** 会在你组合其他类型的时候发挥很大的作用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/ComposedEquality.java\n", + "import java.util.*;\n", + "class Part {\n", + " String ss;\n", + " double dd;\n", + " \n", + " Part(String ss, double dd) {\n", + " this.ss = ss;\n", + " this.dd = dd;\n", + " }\n", + " \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof Part &&\n", + " Objects.equals(ss, ((Part)rval).ss) &&\n", + " Objects.equals(dd, ((Part)rval).dd);\n", + " } \n", + " \n", + "} \n", + " \n", + "public class ComposedEquality extends SuccinctEquality {\n", + " Part part;\n", + " public ComposedEquality(int i, String s, double d) {\n", + " super(i, s, d);\n", + " part = new Part(s, d);\n", + " System.out.println(\"made 'ComposedEquality'\");\n", + " }\n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof ComposedEquality &&\n", + " super.equals(rval) &&\n", + " Objects.equals(part,\n", + " ((ComposedEquality)rval).part);\n", + " \n", + " } \n", + " \n", + " public static void main(String[] args) {\n", + " Equality.testAll( (i, s, d) ->\n", + " new ComposedEquality(i, s, d));\n", + " }\n", + "}\n", + "/*\n", + "Output:\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "made 'ComposedEquality'\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "made 'ComposedEquality'\n", + "made 'Equality'\n", + "made 'SuccinctEquality'\n", + "made 'ComposedEquality'\n", + "-- Testing null --null instanceof Equality: false\n", + "Expected false, got false\n", + "-- Testing same object --\n", + "same object instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different type --\n", + "different type instanceof Equality: false\n", + "Expected false, got false\n", + "-- Testing same values --\n", + "same values instanceof Equality: true\n", + "Expected true, got true\n", + "-- Testing different values --\n", + "different values instanceof Equality: true\n", + "Expected false, got false\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意super.equals()这个调用,没有必要重新发明它(因为你不总是有权限访问基类所有的必要字段)\n", + "\n", + "\n", + "### 不同子类的相等性\n", + "继承意味着两个不同子类的对象当其向上转型的时候可以是相等的。假设你有一个Animal对象的集合。这个集合天然接受**Animal**的子类。在这个例子中是**Dog**和**Pig**。每个**Animal**有一个**name**和**size**,还有唯一的内部**id**数字。\n", + "\n", + "我们通过**Objects**类,以规范的形式定义 **equals()**函数和**hashCode()**。但是我们只能在基类**Animal**中定义他们。并且我们在这两个函数中没有包含**id**字段。从**equals()**函数的角度看待,这意味着我们只关心它是否是**Animal**,而不关心是否是**Animal**的某个子类。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SubtypeEquality.java\n", + "import java.util.*;\n", + "enum Size { SMALL, MEDIUM, LARGE }\n", + "class Animal {\n", + " private static int counter = 0;\n", + " private final int id = counter++;\n", + " private final String name;\n", + " private final Size size;\n", + " Animal(String name, Size size) {\n", + " this.name = name;\n", + " this.size = size;\n", + " } \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof Animal &&\n", + " // Objects.equals(id, ((Animal)rval).id) && // [1]\n", + " Objects.equals(name, ((Animal)rval).name) &&\n", + " Objects.equals(size, ((Animal)rval).size);\n", + " } \n", + " \n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hash(name, size);\n", + " // return Objects.hash(name, size, id); // [2]\n", + " } \n", + " \n", + " @Override\n", + " public String toString() {\n", + " return String.format(\"%s[%d]: %s %s %x\",\n", + " getClass().getSimpleName(), id,\n", + " name, size, hashCode());\n", + " } \n", + "} \n", + " \n", + "class Dog extends Animal {\n", + " Dog(String name, Size size) {\n", + " super(name, size);\n", + " } \n", + "} \n", + "\n", + "class Pig extends Animal {\n", + " Pig(String name, Size size) {\n", + " super(name, size);\n", + " } \n", + "} \n", + " \n", + "public class SubtypeEquality {\n", + " public static void main(String[] args) {\n", + " Set pets = new HashSet<>();\n", + " pets.add(new Dog(\"Ralph\", Size.MEDIUM));\n", + " pets.add(new Pig(\"Ralph\", Size.MEDIUM));\n", + " pets.forEach(System.out::println);\n", + " } \n", + "} \n", + "/*\n", + "Output:\n", + "Dog[0]: Ralph MEDIUM a752aeee\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果我们只考虑类型的话,某些情况下它的确说得通——只从基类的角度看待问题,这是李氏替换原则的基石。这个代码完美符合替换理论因为派生类没有添加任何额外不再基类中的额外函数。派生类只是在表现上不同,而不是在接口上。(当然这不是常态)\n", + "\n", + "但是当我们提供了两个有着相同数据的不同的对象类型,然后将他们放置在 **HashSet** 中。只有他们中的一个能存活。这强调了 **equals()** 不是完美的数学理论,而只是机械般的理论。\n", + " **hashCode()** 和 **equals()** 必须能够允许类型在hash数据结构中正常工作。例子中 **Dog** 和 **Pig** 会被映射到同 **HashSet** 的同一个桶中。这个时候,**HashSet** 回退到 **equals()** 来区分对象,但是 **equals()** 也认为两个对象是相同的。**HashSet**因为已经有一个相同的对象了,所以没有添加 **Pig**。\n", + "我们依然能够通过使得其他字段对象不同来让例子能够正常工作。在这里每个 **Animal** 已经有了一个独一无二的 **id** ,所以你能够取消 **equals()** 函数中的 **[1]** 行注释,或者取消 **hashCode()** 函数中的 **[2]** 行注释。按照规范,你应该同时完成这两个操作,如此能够将所有“不变的”字段包含在两个操作中(“不变”所以 **equals()** 和 **hashCode()** 在哈希数据结构中的排序和取值时,不会生成不同的值。我将“不变的”放在引号中因为你必须计算出是否已经发生变化)。\n", + "\n", + "> **旁注**: 在**hashCode()**中,如果你只能够使用一个字段,使用**Objcets.hashCode()**。如果你使用多个字段,那么使用 **Objects.hash()**。\n", + "\n", + "我们也可以通过标准方式,将 **equals()** 定义在子类中(不包含 **id** )解决这个问题:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SubtypeEquality2.java\n", + "import java.util.*;\n", + "class Dog2 extends Animal {\n", + " Dog2(String name, Size size) {\n", + " super(name, size);\n", + " } \n", + " \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof Dog2 &&super.equals(rval);\n", + " } \n", + "} \n", + "\n", + "class Pig2 extends Animal {\n", + " Pig2(String name, Size size) {\n", + " super(name, size);\n", + " } \n", + " \n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof Pig2 &&\n", + " super.equals(rval);\n", + " } \n", + "}\n", + "\n", + "public class SubtypeEquality2 {\n", + " public static void main(String[] args) {\n", + " Set pets = new HashSet<>();\n", + " pets.add(new Dog2(\"Ralph\", Size.MEDIUM));\n", + " pets.add(new Pig2(\"Ralph\", Size.MEDIUM));\n", + " pets.forEach(System.out::println);\n", + " }\n", + "} \n", + "/*\n", + "Output:\n", + "Dog2[0]: Ralph MEDIUM a752aeee\n", + "Pig2[1]: Ralph MEDIUM a752aeee\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "注意 **hashCode()** 是独一无二的,但是因为对象不再 **equals()** ,所以两个函数都出现在**HashSet**中。另外,**super.equals()** 意味着我们不需要访问基类的**private**字段。\n", + "\n", + "\n", + "一种说法是Java从**equals()** 和**hashCode()** 的定义中分离了可替代性。我们仍然能够将**Dog**和**Pig**放置在 **Set\\** 中,无论 **equals()** 和 **hashCode()** 是如何定义的,但是对象不会在哈希数据结构中正常工作,除非这些函数能够被合理定义。不幸的是,**equals()** 不总是和 **hashCode()** 一起使用,这在你尝试为了某个特殊类型避免定义它的时候会让问题复杂化。并且这也是为什么遵循规范是有价值的。然而这会变得更加复杂,因为你不总是需要定义其中一个函数。\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "## 哈希和哈希码\n", + "\n", + "在 [集合]() 章节中,我们使用预先定义的类作为 HashMap 的键。这些示例之所以有用,是因为预定义的类具有所有必需的连线,以使它们正确地充当键。\n", + "\n", + "当创建自己的类作为HashMap的键时,会发生一个常见的陷阱,从而忘记进行必要的接线。例如,考虑一个将Earthhog 对象与 Prediction 对象匹配的天气预报系统。这似乎很简单:使用Groundhog作为键,使用Prediction作为值:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/Groundhog.java\n", + "// Looks plausible, but doesn't work as a HashMap key\n", + "public class Groundhog {\n", + " protected int number;\n", + " public Groundhog(int n) { number = n; }\n", + " @Override\n", + " public String toString() {\n", + " return \"Groundhog #\" + number;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/Prediction.java\n", + "// Predicting the weather\n", + "import java.util.*;\n", + "public class Prediction {\n", + " private static Random rand = new Random(47);\n", + " @Override\n", + " public String toString() {\n", + " return rand.nextBoolean() ?\n", + " \"Six more weeks of Winter!\" : \"Early Spring!\";\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SpringDetector.java\n", + "// What will the weather be?\n", + "import java.util.*;\n", + "import java.util.stream.*;\n", + "import java.util.function.*;\n", + "import java.lang.reflect.*;\n", + "public class SpringDetector {\n", + " public static \n", + " void detectSpring(Class type) {\n", + " try {\n", + " Constructor ghog =\n", + " type.getConstructor(int.class);\n", + " Map map =\n", + " IntStream.range(0, 10)\n", + " .mapToObj(i -> {\n", + " try {\n", + " return ghog.newInstance(i);\n", + " } catch(Exception e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " })\n", + " .collect(Collectors.toMap(\n", + " Function.identity(),\n", + " gh -> new Prediction()));\n", + " map.forEach((k, v) ->\n", + " System.out.println(k + \": \" + v));\n", + " Groundhog gh = ghog.newInstance(3);\n", + " System.out.println(\n", + " \"Looking up prediction for \" + gh);\n", + " if(map.containsKey(gh))\n", + " System.out.println(map.get(gh));\n", + " else\n", + " System.out.println(\"Key not found: \" + gh);\n", + " } catch(NoSuchMethodException |\n", + " IllegalAccessException |\n", + " InvocationTargetException |\n", + " InstantiationException e) {\n", + " throw new RuntimeException(e);\n", + " }\n", + " }\n", + " public static void main(String[] args) {\n", + " detectSpring(Groundhog.class);\n", + " }\n", + "}\n", + "/* Output:\n", + "Groundhog #3: Six more weeks of Winter!\n", + "Groundhog #0: Early Spring!\n", + "Groundhog #8: Six more weeks of Winter!\n", + "Groundhog #6: Early Spring!\n", + "Groundhog #4: Early Spring!\n", + "Groundhog #2: Six more weeks of Winter!\n", + "Groundhog #1: Early Spring!\n", + "Groundhog #9: Early Spring!\n", + "Groundhog #5: Six more weeks of Winter!\n", + "Groundhog #7: Six more weeks of Winter!\n", + "Looking up prediction for Groundhog #3\n", + "Key not found: Groundhog #3\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "每个 Groundhog 都被赋予了一个常数,因此你可以通过如下的方式在 HashMap 中寻找对应的 Prediction。“给我一个和 Groundhog#3 相关联的 Prediction”。而 Prediction 通过一个随机生成的 boolean 来选择天气。`detectSpring()` 方法通过反射来实例化 Groundhog 类,或者它的子类。稍后,当我们继承一种新型的“Groundhog ”以解决此处演示的问题时,这将派上用场。\n", + "\n", + "这里的 HashMap 被 Groundhog 和其相关联的 Prediction 充满。并且上面展示了 HashMap 里面填充的内容。接下来我们使用填充了常数 3 的 Groundhog 作为 key 用于寻找对应的 Prediction 。(这个键值对肯定在 Map 中)。\n", + "\n", + "这看起来十分简单,但是这样做并没有奏效 —— 它无法找到数字3这个键。问题出在Groundhog自动地继承自基类Object,所以这里使用Object的hashCode0方法生成散列码,而它默认是使用对象的地址计算散列码。因此,由Groundhog(3)生成的第一个实例的散列码与由Groundhog(3)生成的第二个实例的散列码是不同的,而我们正是使用后者进行查找的。\n", + "\n", + "我们需要恰当的重写hashCode()方法。但是它仍然无法正常运行,除非你同时重写 equals()方法,它也是Object的一部分。HashMap使用equals()判断当前的键是否与表中存在的键相同。\n", + "\n", + "这是因为默认的Object.equals()只是比较对象的地址,所以一个Groundhog(3)并不等于另一个Groundhog(3),因此,如果要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals(),如下所示:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/Groundhog2.java\n", + "// A class that's used as a key in a HashMap\n", + "// must override hashCode() and equals()\n", + "import java.util.*;\n", + "public class Groundhog2 extends Groundhog {\n", + " public Groundhog2(int n) { super(n); }\n", + " @Override\n", + " public int hashCode() { return number; }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof Groundhog2 &&\n", + " Objects.equals(\n", + " number, ((Groundhog2)o).number);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SpringDetector2.java\n", + "// A working key\n", + "public class SpringDetector2 {\n", + " public static void main(String[] args) {\n", + " SpringDetector.detectSpring(Groundhog2.class);\n", + " }\n", + "}\n", + "/* Output:\n", + "Groundhog #0: Six more weeks of Winter!\n", + "Groundhog #1: Early Spring!\n", + "Groundhog #2: Six more weeks of Winter!\n", + "Groundhog #3: Early Spring!\n", + "Groundhog #4: Early Spring!\n", + "Groundhog #5: Six more weeks of Winter!\n", + "Groundhog #6: Early Spring!\n", + "Groundhog #7: Early Spring!\n", + "Groundhog #8: Six more weeks of Winter!\n", + "Groundhog #9: Six more weeks of Winter!\n", + "Looking up prediction for Groundhog #3\n", + "Early Spring!\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Groundhog2.hashCode0返回Groundhog的标识数字(编号)作为散列码。在此例中,程序员负责确保不同的Groundhog具有不同的编号。hashCode()并不需要总是能够返回唯一的标识码(稍后你会理解其原因),但是equals() 方法必须严格地判断两个对象是否相同。此处的equals()是判断Groundhog的号码,所以作为HashMap中的键,如果两个Groundhog2对象具有相同的Groundhog编号,程序就出错了。\n", + "\n", + "如何定义 equals() 方法在上一节 [equals 规范]()中提到了。输出表明我们现在的输出是正确的。\n", + "\n", + "### 理解 hashCode\n", + "\n", + "前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖hashCode() 和equals() ,那么使用散列的数据结构(HashSet,HashMap,LinkedHashst或LinkedHashMap)就无法正确处理你的键。然而,要很好地解决此问题,你必须了解这些数据结构的内部构造。\n", + "\n", + "首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者你自己实现的Map也可以达到此目的。与散列实现相反,下面的示例用一对ArrayLists实现了一个Map,与AssociativeArray.java不同,这其中包含了Map接口的完整实现,因此提供了entrySet()方法:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SlowMap.java\n", + "// A Map implemented with ArrayLists\n", + "import java.util.*;\n", + "import onjava.*;\n", + "public class SlowMap extends AbstractMap {\n", + " private List keys = new ArrayList<>();\n", + " private List values = new ArrayList<>();\n", + " @Override\n", + " public V put(K key, V value) {\n", + " V oldValue = get(key); // The old value or null\n", + " if(!keys.contains(key)) {\n", + " keys.add(key);\n", + " values.add(value);\n", + " } else\n", + " values.set(keys.indexOf(key), value);\n", + " return oldValue;\n", + " }\n", + " @Override\n", + " public V get(Object key) { // key: type Object, not K\n", + " if(!keys.contains(key))\n", + " return null;\n", + " return values.get(keys.indexOf(key));\n", + " }\n", + " @Override\n", + " public Set> entrySet() {\n", + " Set> set= new HashSet<>();\n", + " Iterator ki = keys.iterator();\n", + " Iterator vi = values.iterator();\n", + " while(ki.hasNext())\n", + " set.add(new MapEntry<>(ki.next(), vi.next()));\n", + " return set;\n", + " }\n", + " public static void main(String[] args) {\n", + " SlowMap m= new SlowMap<>();\n", + " m.putAll(Countries.capitals(8));\n", + " m.forEach((k, v) ->\n", + " System.out.println(k + \"=\" + v));\n", + " System.out.println(m.get(\"BENIN\"));\n", + " m.entrySet().forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "CAMEROON=Yaounde\n", + "ANGOLA=Luanda\n", + "BURKINA FASO=Ouagadougou\n", + "BURUNDI=Bujumbura\n", + "ALGERIA=Algiers\n", + "BENIN=Porto-Novo\n", + "CAPE VERDE=Praia\n", + "BOTSWANA=Gaberone\n", + "Porto-Novo\n", + "CAMEROON=Yaounde\n", + "ANGOLA=Luanda\n", + "BURKINA FASO=Ouagadougou\n", + "BURUNDI=Bujumbura\n", + "ALGERIA=Algiers\n", + "BENIN=Porto-Novo\n", + "CAPE VERDE=Praia\n", + "BOTSWANA=Gaberone\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "put()方法只是将键与值放入相应的ArrayList。为了与Map接口保持一致,它必须返回旧的键,或者在没有任何旧键的情况下返回null。\n", + "\n", + "同样遵循了Map规范,get()会在键不在SlowMap中的时候产生null。如果键存在,它将被用来查找表示它在keys列表中的位置的数值型索引,并且这个数字被用作索引来产生与values列表相关联的值。注意,在get()中key的类型是Object,而不是你所期望的参数化类型K(并且是在AssociativeArrayjava中真正使用的类型),这是将泛型注入到Java语言中的时刻如此之晚所导致的结果-如果泛型是Java语言最初就具备的属性,那么get()就可以执行其参数的类型。\n", + "\n", + "Map.entrySet() 方法必须产生一个Map.Entry对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型,就必须同时定义Map.Entry的实现:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/MapEntry.java\n", + "// A simple Map.Entry for sample Map implementations\n", + "import java.util.*;\n", + "public class MapEntry implements Map.Entry {\n", + " private K key;\n", + " private V value;\n", + " public MapEntry(K key, V value) {\n", + " this.key = key;\n", + " this.value = value;\n", + " }\n", + " @Override\n", + " public K getKey() { return key; }\n", + " @Override\n", + " public V getValue() { return value; }\n", + " @Override\n", + " public V setValue(V v) {\n", + " V result = value;\n", + " value = v;\n", + " return result;\n", + " }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hash(key, value);\n", + " }\n", + " @SuppressWarnings(\"unchecked\")\n", + " @Override\n", + " public boolean equals(Object rval) {\n", + " return rval instanceof MapEntry &&\n", + " Objects.equals(key,\n", + " ((MapEntry)rval).getKey()) &&\n", + " Objects.equals(value,\n", + " ((MapEntry)rval).getValue());\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return key + \"=\" + value;\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这里 equals 方法的实现遵循了[equals 规范]()。在 Objects 类中有一个非常熟悉的方法可以帮助创建 hashCode() 方法: Objects.hash()。当你定义含有超过一个属性的对象的 `hashCode()` 时,你可以使用这个方法。如果你的对象只有一个属性,可以直接使用 ` Objects.hashCode()`。\n", + "\n", + "尽管这个解决方案非常简单,并且看起来在SlowMap.main() 的琐碎测试中可以正常工作,但是这并不是一个恰当的实现,因为它创建了键和值的副本。entrySet() 的恰当实现应该在Map中提供视图,而不是副本,并且这个视图允许对原始映射表进行修改(副本就不行)。\n", + "\n", + "### 为了速度而散列\n", + "\n", + "SlowMap.java 说明了创建一种新的Map并不困难。但是正如它的名称SlowMap所示,它不会很快,所以如果有更好的选择,就应该放弃它。它的问题在于对键的查询,键没有按照任何特定顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。\n", + "\n", + "散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。\n", + "\n", + "散列则更进一步,它将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(请小心留意,我是说键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有一个问题:我们希望在Map中保存数量不确定的值,但是如果键的数量被数组的容量限制了,该怎么办呢?\n", + "\n", + "答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(在计算机科学的术语中称为散列函数)生成。\n", + "\n", + "于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。\n", + "\n", + "理解了散列的原理,我们就能够实现一个简单的散列Map了:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/SimpleHashMap.java\n", + "// A demonstration hashed Map\n", + "import java.util.*;\n", + "import onjava.*;\n", + "public\n", + "class SimpleHashMap extends AbstractMap {\n", + " // Choose a prime number for the hash table\n", + "// size, to achieve a uniform distribution:\n", + " static final int SIZE = 997;\n", + " // You can't have a physical array of generics,\n", + "// but you can upcast to one:\n", + " @SuppressWarnings(\"unchecked\")\n", + " LinkedList>[] buckets =\n", + " new LinkedList[SIZE];\n", + " @Override\n", + " public V put(K key, V value) {\n", + " V oldValue = null;\n", + " int index = Math.abs(key.hashCode()) % SIZE;\n", + " if(buckets[index] == null)\n", + " buckets[index] = new LinkedList<>();\n", + " LinkedList> bucket = buckets[index];\n", + " MapEntry pair = new MapEntry<>(key, value);\n", + " boolean found = false;\n", + " ListIterator> it =\n", + " bucket.listIterator();\n", + " while(it.hasNext()) {\n", + " MapEntry iPair = it.next();\n", + " if(iPair.getKey().equals(key)) {\n", + " oldValue = iPair.getValue();\n", + " it.set(pair); // Replace old with new\n", + " found = true;\n", + " break;\n", + " }\n", + " }\n", + " if(!found)\n", + " buckets[index].add(pair);\n", + " return oldValue;\n", + " }\n", + " @Override\n", + " public V get(Object key) {\n", + " int index = Math.abs(key.hashCode()) % SIZE;\n", + " if(buckets[index] == null) return null;\n", + " for(MapEntry iPair : buckets[index])\n", + " if(iPair.getKey().equals(key))\n", + " return iPair.getValue();\n", + " return null;\n", + " }\n", + " @Override\n", + " public Set> entrySet() {\n", + " Set> set= new HashSet<>();\n", + " for(LinkedList> bucket : buckets) {\n", + " if(bucket == null) continue;\n", + " for(MapEntry mpair : bucket)\n", + " set.add(mpair);\n", + " }\n", + " return set;\n", + " }\n", + " public static void main(String[] args) {\n", + " SimpleHashMap m =\n", + " new SimpleHashMap<>();\n", + " m.putAll(Countries.capitals(8));\n", + " m.forEach((k, v) ->\n", + " System.out.println(k + \"=\" + v));\n", + " System.out.println(m.get(\"BENIN\"));\n", + " m.entrySet().forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "CAMEROON=Yaounde\n", + "ANGOLA=Luanda\n", + "BURKINA FASO=Ouagadougou\n", + "BURUNDI=Bujumbura\n", + "ALGERIA=Algiers\n", + "BENIN=Porto-Novo\n", + "CAPE VERDE=Praia\n", + "BOTSWANA=Gaberone\n", + "Porto-Novo\n", + "CAMEROON=Yaounde\n", + "ANGOLA=Luanda\n", + "BURKINA FASO=Ouagadougou\n", + "BURUNDI=Bujumbura\n", + "ALGERIA=Algiers\n", + "BENIN=Porto-Novo\n", + "CAPE VERDE=Praia\n", + "BOTSWANA=Gaberone\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为bucket,为使散列分布均匀,桶的数量通常使用质数[^2]。注意,为了能够自动处理冲突,使用了一个LinkedList的数组;每一个新的元素只是直接添加到list尾的某个特定桶位中。即使Java不允许你创建泛型数组,那你也可以创建指向这种数组的引用。这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行额外的转型。\n", + "\n", + "对于put() 方法,hashCode() 将针对键而被调用,并且其结果被强制转换为正数。为了使产生的数字适合bucket数组的大小,取模操作符将按照该数组的尺寸取模。如果数组的某个位置是 null,这表示还没有元素被散列至此,所以,为了保存刚散列到该定位的对象,需要创建一个新的LinkedList。一般的过程是,查看当前位置的ist中是否有相同的元素,如果有,则将旧的值赋给oldValue,然后用新的值取代旧的值。标记found用来跟踪是否找到(相同的)旧的键值对,如果没有,则将新的对添加到list的末尾。\n", + "\n", + "get()方法按照与put()方法相同的方式计算在buckets数组中的索引(这很重要,因为这样可以保证两个方法可以计算出相同的位置)如果此位置有LinkedList存在,就对其进行查询。\n", + "\n", + "注意,这个实现并不意味着对性能进行了调优,它只是想要展示散列映射表执行的各种操作。如果你浏览一下java.util.HashMap的源代码,你就会看到一个调过优的实现。同样,为了简单,SimpleHashMap使用了与SlowMap相同的方式来实现entrySet(),这个方法有些过于简单,不能用于通用的Map。\n", + "\n", + "### 重写 hashCode()\n", + "\n", + "在明白了如何散列之后,编写自己的hashCode()就更有意义了。\n", + "\n", + "首先,你无法控制bucket数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子(本章稍后会介绍这个术语)有关。hashCode()生成的结果,经过处理后成为桶位的下标(在SimpleHashMap中,只是对其取模,模数为bucket数组的大小)。\n", + "\n", + "设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。\n", + "\n", + "此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this值,这只能产生很糟糕的hashCode(),因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。这正是SpringDetector.java的问题所在,因为它默认的hashCode0使用的是对象的地址。所以,应该使用对象内有意义的识别信息。\n", + "\n", + "下面以String类为例。String有个特点:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。所以new String(\"hello\")生成的两个实例,虽然是相互独立的,但是对它们使用hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/StringHashCode.java\n", + "public class StringHashCode {\n", + " public static void main(String[] args) {\n", + " String[] hellos = \"Hello Hello\".split(\" \");\n", + " System.out.println(hellos[0].hashCode());\n", + " System.out.println(hellos[1].hashCode());\n", + " }\n", + "}\n", + "/* Output:\n", + "69609650\n", + "69609650\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "对于String而言,hashCode() 明显是基于String的内容的。\n", + "\n", + "因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过 hashCode() 和 equals() ,必须能够完全确定对象的身份。\n", + "\n", + "因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int即可。\n", + "\n", + "还有另一个影响因素:好的hashCode() 应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。\n", + "\n", + "在Effective Java Programming Language Guide(Addison-Wesley 2001)这本书中,Joshua Bloch为怎样写出一份像样的hashCode()给出了基本的指导: \n", + "\n", + "1. 给int变量result赋予某个非零值常量,例如17。\n", + "2. 为对象内每个有意义的字段(即每个可以做equals)操作的字段计算出一个int散列码c:\n", + "\n", + "| 字段类型 | 计算公式 |\n", + "| ------------------------------------------------------ | ------------------------------------------------------------ |\n", + "| boolean | c = (f ? 0 : 1) |\n", + "| byte , char , short , or int | c = (int)f |\n", + "| long | c = (int)(f ^ (f>>>32)) |\n", + "| float | c = Float.floatToIntBits(f); |\n", + "| double | long l =Double.doubleToLongBits(f);
c = (int)(l ^ (l >>> 32)) |\n", + "| Object , where equals() calls equals() for this field | c = f.hashCode() |\n", + "| Array | 应用以上规则到每一个元素中 |\n", + "\n", + "3. 合并计算得到的散列码: **result = 37 * result + c;​**\n", + "4. 返回 result。\n", + "5. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。\n", + "\n", + "下面便是遵循这些指导的一个例子。提示,你没有必要书写像如下的代码 —— 相反,使用 `Objects.hash()` 去用于散列多字段的对象(如同在本例中的那样),然后使用 `Objects.hashCode()` 如散列单字段的对象。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/CountedString.java\n", + "// Creating a good hashCode()\n", + "import java.util.*;\n", + "public class CountedString {\n", + " private static List created =\n", + " new ArrayList<>();\n", + " private String s;\n", + " private int id = 0;\n", + " public CountedString(String str) {\n", + " s = str;\n", + " created.add(s);\n", + "// id is the total number of instances\n", + "// of this String used by CountedString:\n", + " for(String s2 : created)\n", + " if(s2.equals(s))\n", + " id++;\n", + " }\n", + " @Override\n", + " public String toString() {\n", + " return \"String: \" + s + \" id: \" + id +\n", + " \" hashCode(): \" + hashCode();\n", + " }\n", + " @Override\n", + " public int hashCode() {\n", + "// The very simple approach:\n", + "// return s.hashCode() * id;\n", + "// Using Joshua Bloch's recipe:\n", + " int result = 17;\n", + " result = 37 * result + s.hashCode();\n", + " result = 37 * result + id;\n", + " return result;\n", + " }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof CountedString &&\n", + " Objects.equals(s, ((CountedString)o).s) &&\n", + " Objects.equals(id, ((CountedString)o).id);\n", + " }\n", + " public static void main(String[] args) {\n", + " Map map = new HashMap<>();\n", + " CountedString[] cs = new CountedString[5];\n", + " for(int i = 0; i < cs.length; i++) {\n", + " cs[i] = new CountedString(\"hi\");\n", + " map.put(cs[i], i); // Autobox int to Integer\n", + " }\n", + " System.out.println(map);\n", + " for(CountedString cstring : cs) {\n", + " System.out.println(\"Looking up \" + cstring);\n", + " System.out.println(map.get(cstring));\n", + " }\n", + " }\n", + "}\n", + "/* Output:\n", + "{String: hi id: 4 hashCode(): 146450=3, String: hi id:\n", + "5 hashCode(): 146451=4, String: hi id: 2 hashCode():\n", + "146448=1, String: hi id: 3 hashCode(): 146449=2,\n", + "String: hi id: 1 hashCode(): 146447=0}\n", + "Looking up String: hi id: 1 hashCode(): 146447\n", + "0\n", + "Looking up String: hi id: 2 hashCode(): 146448\n", + "1\n", + "Looking up String: hi id: 3 hashCode(): 146449\n", + "2\n", + "Looking up String: hi id: 4 hashCode(): 146450\n", + "3\n", + "Looking up String: hi id: 5 hashCode(): 146451\n", + "4\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "CountedString由一个String和一个id组成,此id代表包含相同String的CountedString对象的编号。所有的String都被存储在static ArrayList中,在构造器中通过选代遍历此ArrayList完成对id的计算。\n", + "\n", + "hashCode()和equals() 都基于CountedString的这两个字段来生成结果;如果它们只基于String或者只基于id,不同的对象就可能产生相同的值。\n", + "\n", + "在main)中,使用相同的String创建了多个CountedString对象。这说明,虽然String相同,但是由于id不同,所以使得它们的散列码并不相同。在程序中,HashMap被打印了出来,因此可以看到它内部是如何存储元素的(以无法辨别的次序),然后单独查询每一个键,以此证明查询机制工作正常。\n", + "\n", + "作为第二个示例,请考虑Individual类,它被用作[类型信息]()中所定义的typeinfo.pet类库的基类。Individual类在那一章中就用到了,而它的定义则放到了本章,因此你可以正确地理解其实现。\n", + "\n", + "在这里替换了手工去计算 `hashCode()`,我们使用了更合适的方式 ` Objects.hash() `:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// typeinfo/pets/Individual.java\n", + "package typeinfo.pets;\n", + "import java.util.*;\n", + "public class\n", + "Individual implements Comparable {\n", + " private static long counter = 0;\n", + " private final long id = counter++;\n", + " private String name;\n", + " public Individual(String name) { this.name = name; }\n", + " // 'name' is optional:\n", + " public Individual() {}\n", + " @Override\n", + " public String toString() {\n", + " return getClass().getSimpleName() +\n", + " (name == null ? \"\" : \" \" + name);\n", + " }\n", + " public long id() { return id; }\n", + " @Override\n", + " public boolean equals(Object o) {\n", + " return o instanceof Individual &&\n", + " Objects.equals(id, ((Individual)o).id);\n", + " }\n", + " @Override\n", + " public int hashCode() {\n", + " return Objects.hash(name, id);\n", + " }\n", + " @Override\n", + " public int compareTo(Individual arg) {\n", + " // Compare by class name first:\n", + " String first = getClass().getSimpleName();\n", + " String argFirst = arg.getClass().getSimpleName();\n", + " int firstCompare = first.compareTo(argFirst);\n", + " if(firstCompare != 0)\n", + " return firstCompare;\n", + " if(name != null && arg.name != null) {\n", + " int secondCompare = name.compareTo(arg.name);\n", + " if(secondCompare != 0)\n", + " return secondCompare;\n", + " }\n", + " return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "compareTo() 方法有一个比较结构,因此它会产生一个排序序列,排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后按照创建的顺序排序。下面的示例说明了它是如何工作的:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "attributes": { + "classes": [ + "java" + ], + "id": "" + } + }, + "outputs": [], + "source": [ + "// equalshashcode/IndividualTest.java\n", + "import collections.MapOfList;\n", + "import typeinfo.pets.*;\n", + "import java.util.*;\n", + "public class IndividualTest {\n", + " public static void main(String[] args) {\n", + " Set pets = new TreeSet<>();\n", + " for(List lp :\n", + " MapOfList.petPeople.values())\n", + " for(Pet p : lp)\n", + " pets.add(p);\n", + " pets.forEach(System.out::println);\n", + " }\n", + "}\n", + "/* Output:\n", + "Cat Elsie May\n", + "Cat Pinkola\n", + "Cat Shackleton\n", + "Cat Stanford\n", + "Cymric Molly\n", + "Dog Margrett\n", + "Mutt Spot\n", + "Pug Louie aka Louis Snorkelstein Dupree\n", + "Rat Fizzy\n", + "Rat Freckly\n", + "Rat Fuzzy\n", + "*/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "由于所有的宠物都有名字,因此它们首先按照类型排序,然后在同类型中按照名字排序。\n", + "\n", + "\n", + "\n", + "## 调优 HashMap\n", + "\n", + "我们有可能手动调优HashMap以提高其在特定应用程序中的性能。为了理解调整HashMap时的性能问题,一些术语是必要的:\n", + "\n", + "- 容量(Capacity):表中存储的桶数量。\n", + "- 初始容量(Initial Capacity):当表被创建时,桶的初始个数。 HashMap 和 HashSet 有可以让你指定初始容量的构造器。\n", + "- 个数(Size):目前存储在表中的键值对的个数。\n", + "- 负载因子(Load factor):通常表现为 $\\frac{size}{capacity}$。当负载因子大小为 0 的时候表示为一个空表。当负载因子大小为 0.5 表示为一个半满表(half-full table),以此类推。轻负载的表几乎没有冲突,因此是插入和查找的最佳选择(但会减慢使用迭代器进行遍历的过程)。 HashMap 和 HashSet 有可以让你指定负载因子的构造器。当表内容量达到了负载因子,集合就会自动扩充为原始容量(桶的数量)的两倍,并且会将原始的对象存储在新的桶集合中(也被称为 rehashing)\n", + "\n", + "HashMap 中负载因子的大小为 0.75(当表内容量大小不足四分之三的时候,不会发生 rehashing 现象)。这看起来是一个非常好的同时考虑到时间和空间消耗的平衡策略。更高的负载因子会减少空间的消耗,但是会增加查询的耗时。重要的是,查询操作是你使用的最频繁的一个操作(包括 `get()` 和 `put()` 方法)。\n", + "\n", + "如果你知道存储在 HashMap 中确切的条目个数,直接创建一个足够容量大小的 HashMap,以避免自动发生的 rehashing 操作。\n", + "\n", + "[^1]: \n", + "[^2]: 事实证明,质数实际上并不是散列桶的理想容量。近来,(经过广泛的测试)Java的散列函数都使用2的整数次方。对现代的处理器来说,除法与求余数是最慢的操作。使用2的整数次方长度的散列表,可用掩码代替除法。\n", + "\n", + "\n", + "\n", + "
" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/jupyter/GLOSSARY.ipynb b/jupyter/GLOSSARY.ipynb new file mode 100644 index 00000000..e8f7bc3b --- /dev/null +++ b/jupyter/GLOSSARY.ipynb @@ -0,0 +1,23 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 词汇表\n", + "\n", + "| 词汇 | 解释 |\n", + "| ----------------| ----------|\n", + "| **OOP** (*Object-oriented programming*) | 面向对象编程,一种编程思维模式和编程架构|\n", + "| **UML** (*Unified Modeling Language*) | 统一建模语言,类图 |\n", + "| **Aggregation** | 聚合,关联关系的一种,是强的关联关系|\n", + "| **Composition** | 组合,关联关系的一种,是比聚合关系强的关系 |\n", + "| **STL**(*the Standard Template Library*)| C++ 标准模板库|\n", + "| **Fibonacci Sequence**| [斐波那契数列](https://zh.wikipedia.org/wiki/斐波那契数列),又称黄金分割数列 |" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 4 +} From b7b9308480ecefc8e37e806539b7a2f71df3da38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E9=80=B8=E6=89=AC?= <33390928+gdut-yy@users.noreply.github.com> Date: Sat, 2 May 2020 18:41:00 +0800 Subject: [PATCH 225/371] =?UTF-8?q?Fix=20README.md=20=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E3=80=82=E7=A9=BA=E6=A0=BC=E4=BB=A5=E5=8F=8A=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E9=94=81=E8=BF=9B=20(#447)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 87 +++++++++++++++++++++---------------------------------- 1 file changed, 33 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 750efa20..3524cc79 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ ## 书籍简介 -* 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 -* 本书是事实上的 《Java 编程思想》第五版。 -* 《Java 编程思想》第四版基于 JAVA **5** 版本;《On Java 8》 基于 JAVA **8** 版本。 - +- 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 +- 本书是事实上的 《Java 编程思想》第五版。 +- 《Java 编程思想》第四版基于 JAVA **5** 版本;《On Java 8》 基于 JAVA **8** 版本。 ## 传送门 @@ -15,30 +14,29 @@ - Gitee Pages 完整阅读:[进入](https://lingcoder.gitee.io/onjava8/) - ## 翻译进度 - [x] [前言](docs/book/00-Preface.md) - [x] [简介](docs/book/00-Introduction.md) - [x] [第一章 对象的概念](docs/book/01-What-is-an-Object.md) -- [x] [第二章 安装Java和本书用例](docs/book/02-Installing-Java-and-the-Book-Examples.md) +- [x] [第二章 安装 Java 和本书用例](docs/book/02-Installing-Java-and-the-Book-Examples.md) - [x] [第三章 万物皆对象](docs/book/03-Objects-Everywhere.md) - [x] [第四章 运算符](docs/book/04-Operators.md) - [x] [第五章 控制流](docs/book/05-Control-Flow.md) -- [x] [第六章 初始化和清理](docs/book/06-Housekeeping.md) -- [x] [第七章 封装](docs/book/07-Implementation-Hiding.md) +- [x] [第六章 初始化和清理](docs/book/06-Housekeeping.md) +- [x] [第七章 封装](docs/book/07-Implementation-Hiding.md) - [x] [第八章 复用](docs/book/08-Reuse.md) - [x] [第九章 多态](docs/book/09-Polymorphism.md) - [x] [第十章 接口](docs/book/10-Interfaces.md) - [x] [第十一章 内部类](docs/book/11-Inner-Classes.md) - [x] [第十二章 集合](docs/book/12-Collections.md) - [x] [第十三章 函数式编程](docs/book/13-Functional-Programming.md) -- [x] [第十四章 流式编程](docs/book/14-Streams.md) +- [x] [第十四章 流式编程](docs/book/14-Streams.md) - [x] [第十五章 异常](docs/book/15-Exceptions.md) - [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - [x] [第十七章 文件](docs/book/17-Files.md) - [x] [第十八章 字符串](docs/book/18-Strings.md) -- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) +- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) - [x] [第二十章 泛型](docs/book/20-Generics.md) - [x] [第二十一章 数组](docs/book/21-Arrays.md) - [x] [第二十二章 枚举](docs/book/22-Enumerations.md) @@ -49,42 +47,36 @@ - [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) - [x] [附录:文档注释](docs/book/Appendix-Javadoc.md) - [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) -- [x] [附录:流式IO](docs/book/Appendix-IO-Streams.md) -- [x] [附录:标准IO](docs/book/Appendix-Standard-IO.md) -- [x] [附录:新IO](docs/book/Appendix-New-IO.md) -- [x] [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) -- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md) +- [x] [附录:流式 IO](docs/book/Appendix-IO-Streams.md) +- [x] [附录:标准 IO](docs/book/Appendix-Standard-IO.md) +- [x] [附录:新 IO](docs/book/Appendix-New-IO.md) +- [x] [附录:理解 equals 和 hashCode 方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) +- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md) - [x] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md) - [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md) - [x] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md) - [ ] [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) -- [x] [附录:C++和Java的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) +- [x] [附录:C++ 和 Java 的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) - [ ] [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) ## INSTALL 1. 首先安装[Jupyter Lab](https://jupyter.org/) -2. 安装[Java Kernel](https://github.com/SpencerPark/IJava) - -注意: 打开文件后,在工具栏最右边选择`Java`。 Mac下按`CMD + Enter`可以运行Code。 - - Java SDK需要1.9及以上。可以用[sdkman](sdkman.io)安装. - +2. 安装[Java Kernel](https://github.com/SpencerPark/IJava) + 注意: 打开文件后,在工具栏最右边选择 `Java`。 Mac 下按 `CMD + Enter` 可以运行 Code。 + Java SDK 需要 1.9 及以上。可以用[sdkman](sdkman.io)安装. 3. 代码运行。 + ```java public class Hello { public static void main(String [] args){ - System.out.println("Hello, world!") + System.out.println("Hello, world!") } - } //调用静态方法main Hello.main(new String [0]); - - - ``` - + ``` ## 一起交流 @@ -94,11 +86,9 @@ QQGroupQRCode - ## 大事记 -- 2018-11-20 初始化项目 - +- 2018-11-20 初始化项目 ## 原书资料 @@ -106,34 +96,30 @@ cover_small -* 作者: Bruce Eckel -* ISBN: 9780981872520 -* 页数:2038 -* 发行:仅电子版 - +- 作者: Bruce Eckel +- ISBN: 9780981872520 +- 页数:2038 +- 发行:仅电子版 ## 示例代码 -* [gradle: OnJava8-Examples](https://github.com/BruceEckel/OnJava8-Examples) -* [maven: OnJava8-Examples-Maven](https://github.com/sjsdfg/OnJava8-Examples-Maven) - +- [gradle: OnJava8-Examples](https://github.com/BruceEckel/OnJava8-Examples) +- [maven: OnJava8-Examples-Maven](https://github.com/sjsdfg/OnJava8-Examples-Maven) ## 贡献者 -* 主译:[LingCoder](https://github.com/LingCoder),[sjsdfg](https://github.com/sjsdfg),[xiangflight](https://github.com/xiangflight) -* 参译:[Langdon-Chen](https://github.com/Langdon-Chen),[1326670425](https://github.com/1326670425),[LortSir](https://github.com/LortSir) -* 校对:[LingCoder](https://github.com/LingCoder),[jason31520](https://github.com/jason31520),[xiangflight](https://github.com/xiangflight),[nickChenyx](https://github.com/nickChenyx) - +- 主译:[LingCoder](https://github.com/LingCoder),[sjsdfg](https://github.com/sjsdfg),[xiangflight](https://github.com/xiangflight) +- 参译:[Langdon-Chen](https://github.com/Langdon-Chen),[1326670425](https://github.com/1326670425),[LortSir](https://github.com/LortSir) +- 校对:[LingCoder](https://github.com/LingCoder),[jason31520](https://github.com/jason31520),[xiangflight](https://github.com/xiangflight),[nickChenyx](https://github.com/nickChenyx) ## 翻译说明 1. 本书排版布局和翻译风格上参考**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) 2. 采用第一人称叙述。 3. 由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。所以本人在翻译过程中,去除了部分主题无关内容、重复描写。 -4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文以及《Java编程思想》第四版中文版的部分内容(对其翻译死板,生造名词,语言精炼度差问题进行规避和改正)。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 +4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文以及《Java 编程思想》第四版中文版的部分内容(对其翻译死板,生造名词,语言精炼度差问题进行规避和改正)。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 5. 由于译者个人能力、时间有限,如有翻译错误和笔误的地方,还请大家批评指正! - ## 如何参与 如果你想对本书做出一些贡献的话 @@ -145,23 +131,16 @@ 完成之后 PullRequest 如没问题的话,我会合并到主分支 如不熟悉 md 排版,可不必纠结,我会在合并 pr 时代为排版 -如还有其它问题,欢迎发送 issue,谢谢~ - +如还有其它问题,欢迎发送 issue,谢谢~ ## 友情链接 [Effective.Java.3rd.Edition 中文版](https://sjsdfg.github.io/effective-java-3rd-chinese/#/) - - ## 开源协议 本项目基于 MIT 协议开源。 ## 联系方式 -* E-mail : - - - - +- E-mail : From 79d4de9061b15e60dd3d4f10d1335adba811822a Mon Sep 17 00:00:00 2001 From: legendyql Date: Fri, 8 May 2020 11:43:02 +0800 Subject: [PATCH 226/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E4=BC=98=E5=8C=96=20?= =?UTF-8?q?(#451)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第337行,将原翻译“Lists承诺将元素保存在特定的序列中。”中的Lists改为List,因为在中文语法中,没有复数。 第613行,英文原文:“It can produce indices of the next and previous elements relative to where the iterator is pointing in the list,”原翻译太拗口,改为:“它可以生成迭代器在列表中指向位置的后一个和前一个元素的索引,” --- docs/book/12-Collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 8c9c88a5..fd0e3c25 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -334,7 +334,7 @@ public class PrintingCollections { ## 列表List -**List**s承诺将元素保存在特定的序列中。 **List** 接口在 **Collection** 的基础上添加了许多方法,允许在 **List** 的中间插入和删除元素。 +**List**承诺将元素保存在特定的序列中。 **List** 接口在 **Collection** 的基础上添加了许多方法,允许在 **List** 的中间插入和删除元素。 有两种类型的 **List** : @@ -610,7 +610,7 @@ public class CrossCollectionIteration2 { ### 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 From 0ee9cd0ae21176732bed6cd098eedd647a422a48 Mon Sep 17 00:00:00 2001 From: aibowen Date: Wed, 13 May 2020 10:28:53 +0800 Subject: [PATCH 227/371] Update Appendix-Object-Serialization.md (#455) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第三段最后一句“可以考虑像 Hibemate 之类的工具”中的hibemate应该为hibernate --- docs/book/Appendix-Object-Serialization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Object-Serialization.md b/docs/book/Appendix-Object-Serialization.md index 3640de30..a5a09d14 100644 --- a/docs/book/Appendix-Object-Serialization.md +++ b/docs/book/Appendix-Object-Serialization.md @@ -7,7 +7,7 @@ Java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行 Unix 系统的计算机,然后在那里准确地重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心宇节的顺序或者其他任何细节。 -就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写人磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种"persistent"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。 +就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种"persistent"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibernate 之类的工具。 对象序列化的概念加入到语言中是为了支持两种主要特性。一是 Java 的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。 @@ -978,4 +978,4 @@ People 构造器使用 XOM 的 Builder.build() 方法打开并读取一个文件 -
\ No newline at end of file +
From fbce1f0b0c13f56ee94ed34a975cf99e7f8e61b0 Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Wed, 13 May 2020 10:29:15 +0800 Subject: [PATCH 228/371] Update 04-Operators.md (#454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 大小写拼写错误 --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index d9f83652..e644f725 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -550,7 +550,7 @@ bll: 101111101011111010111110101111 Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,它可以初始化所有的整数类型。 -使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。 +使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`toBinaryString()` 时,类型将自动转换为 **int**。 ### 下划线 From 4fc96bad2cc12050590b06a9b32870c3a19e270c Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Sat, 16 May 2020 21:53:30 +0800 Subject: [PATCH 229/371] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E5=AD=97=20(#459)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 04-Operators.md 大小写拼写错误 * 删除多余字 --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index e644f725..60fc4fc3 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -932,7 +932,7 @@ public class Casting { 诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 **int** 型数据为 **long** 型。 -当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。 +当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。 除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。 From 21513a644f21e88b8a290260336411d320d0edb8 Mon Sep 17 00:00:00 2001 From: freestyle-coding <64198234+freestyle-coding@users.noreply.github.com> Date: Sat, 16 May 2020 22:01:56 +0800 Subject: [PATCH 230/371] Translation fix (#457) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修正原翻译中对value一词的错误翻译 * 修正原翻译中因果关系翻译错误 * 方法名拼写错误 --- docs/book/13-Functional-Programming.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 3ddd4493..84e6e12b 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -628,7 +628,7 @@ public class FunctionalAnnotation { } ``` -`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的值在 `NotFunctional` 的定义中可见:接口中如果有多个方法则会产生编译时错误消息。 +`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的价值从 `NotFunctional` 的定义中可以看出:接口中如果有多个方法则会产生编译时错误消息。 仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。 @@ -930,7 +930,7 @@ public interface IntToDoubleFunction { } ``` -之所以我们可以简单地编写 `Function ` 并返回合适的结果,很明显是为了性能。使用基本类型可以防止传递参数和返回结果过程中的自动装箱和自动拆箱。 +我们可以简单地编写 `Function ` 并达到合适的结果,所以,很明显,使用基本类型的函数式接口的唯一原因就是防止传递参数和返回结果过程中的自动装箱和自动拆箱 进而提升性能。 似乎是考虑到使用频率,某些函数类型并没有预定义。 @@ -1037,7 +1037,7 @@ O 在这里,`transform()` 生成一个与传入的函数具有相同签名的函数,但是你可以生成任何你想要的类型。 -这里使用到了 `Function` 接口中名为 `andThen()` 的默认方法,该方法专门用于操作函数。 顾名思义,在调用 `in` 函数之后调用 `toThen()`(还有个 `compose()` 方法,它在 `in` 函数之前应用新函数)。 要附加一个 `andThen()` 函数,我们只需将该函数作为参数传递。 `transform()` 产生的是一个新函数,它将 `in` 的动作与 `andThen()` 参数的动作结合起来。 +这里使用到了 `Function` 接口中名为 `andThen()` 的默认方法,该方法专门用于操作函数。 顾名思义,在调用 `in` 函数之后调用 `andThen()`(还有个 `compose()` 方法,它在 `in` 函数之前应用新函数)。 要附加一个 `andThen()` 函数,我们只需将该函数作为参数传递。 `transform()` 产生的是一个新函数,它将 `in` 的动作与 `andThen()` 参数的动作结合起来。 From 0a8907a9eb1f25c5fdbc80227fa1513153419e9e Mon Sep 17 00:00:00 2001 From: legendyql Date: Tue, 19 May 2020 12:52:07 +0800 Subject: [PATCH 231/371] =?UTF-8?q?=E7=AC=AC13=E7=AB=A0=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20(#458)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 第13章翻译修改 修改第13章翻译中拗口、错误的地方 * Update 13-Functional-Programming.md * Update 13-Functional-Programming.md --- docs/book/13-Functional-Programming.md | 42 ++++++++++++++------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 84e6e12b..ed33d550 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -7,9 +7,9 @@ > 函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。 -在计算机时代早期,内存是稀缺和昂贵的。几乎每个人都用汇编语言编程。人们对编译器有所了解,但仅仅想到编译生成的代码肯定会比手工编码多很多字节。 +在计算机时代早期,内存是稀缺和昂贵的。几乎每个人都用汇编语言编程。人们虽然知道编译器,但编译器生成的代码很低效,比手工编码的汇编程序多很多字节,仅仅想到这一点,人们还是选择汇编语言。 -通常,只是为了使程序适合有限的内存,程序员通过修改内存中的代码来节省代码空间,以便在程序执行时执行不同的操作。这种技术被称为**自修改代码** (self-modifying code)。只要程序足够小,少数人可以维护所有棘手和神秘的汇编代码,你就可以让它运行起来。 +通常,为了使程序能在有限的内存上运行,在程序运行时,程序员通过修改内存中的代码,使程序可以执行不同的操作,用这种方式来节省代码空间。这种技术被称为**自修改代码** (self-modifying code)。只要程序小到几个人就能够维护所有棘手和难懂的汇编代码,你就能让程序运行起来。 随着内存和处理器变得更便宜、更快。C 语言出现并被大多数汇编程序员认为更“高级”。人们发现使用 C 可以显著提高生产力。同时,使用 C 创建自修改代码仍然不难。 @@ -17,7 +17,7 @@ 然而,使用代码以某种方式操纵其他代码的想法也很有趣,只要能保证它更安全。从代码创建,维护和可靠性的角度来看,这个想法非常吸引人。我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。难道这不会让我们更有效率,同时创造更健壮的代码吗? -这就是**函数式编程**(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,一些非函数式语言已经习惯了使用函数式编程产生的优雅的语法。 +这就是**函数式编程**(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用。 你也可以这样想: @@ -471,9 +471,11 @@ X::f() 截止目前,我们已经知道了与接口方法同名的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果:即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。 -要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数的接口,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,这在 Java 中是允许的。这次我们需要调整下心里预期——使用未绑定的引用时,函数方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 理由是:你需要一个对象来调用方法。 +要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,在 Java 中是允许的。我们必须做第二个心理调整——使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。 -**[2]** 的结果有点像脑筋急转弯。 我接受未绑定的引用并对其调用 `transform()`,将其传递给 `X`,并以某种方式导致对 `x.f()` 的调用。 Java 知道它必须采用第一个参数,这实际上就是 `this`,并在其上调用方法。 +**[2]** 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的`transform()`方法,将一个X类的对象传递给它,然后就以某种方式导致了对 `x.f()` 的调用。Java知道它必须拿到第一个参数,该参数实际就是`this`,并在其上调用方法。 + +如果你的函数式接口中的方法有多个参数,就以第一个参数接受`this`的模式来处理。 ```java // functional/MultiUnbound.java @@ -512,7 +514,7 @@ public class MultiUnbound { } ``` -为了说明这一点,我将类命名为 **This** ,函数方法的第一个参数则是 **athis**,但是你应该选择其他名称以防止生产代码混淆。 +为了指明这一点,我将类命名为 **This**,将函数式方法的第一个参数命名为 **athis**,但你在生产代码中应该使用其他名字,以防止混淆。 ### 构造函数引用 @@ -558,7 +560,7 @@ public class CtorReference { **注意**我们如何对 **[1]**,**[2]** 和 **[3]** 中的每一个使用 `Dog :: new`。 这 3 个构造函数只有一个相同名称:`:: new`,但在每种情况下都赋值给不同的接口。编译器可以检测并知道从哪个构造函数引用。 -编译器能识别并调用你的构造函数( 在本例中为 `make()`)。 +编译器知道调用函数式方法(本例中为 `make()`)就相当于调用构造函数。 ## 函数式接口 @@ -628,11 +630,11 @@ public class FunctionalAnnotation { } ``` -`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的价值从 `NotFunctional` 的定义中可以看出:接口中如果有多个方法则会产生编译时错误消息。 +`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 在 `NotFunctional` 的定义中可看到`@FunctionalInterface` 的作用:接口中如果有多个方法则会产生编译期错误。 -仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。 +仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。 -尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java 不允许我们将 `FunctionalAnnotation` 像 `fac` 定义一样直接赋值给 `Functional`,因为它没有明确地实现 `Functional` 接口。 令人惊奇的是 ,Java 8 允许我们以简便的语法为接口赋值函数。 +尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java不允许我们像`fac`定义中的那样,将 `FunctionalAnnotation` 直接赋值给 `Functional`,因为 `FunctionalAnnotation` 没有明确说明实现 `Functional` 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。 `java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。 @@ -790,7 +792,7 @@ someOtherName() 因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 `accept()`),而不是你的方法名。 -现在我们来看看所有基于类的函数式,应用于方法引用(即那些不涉及基本类型的函数)。下例我们创建了一个最简单的函数式签名。代码示例: +现在我们来看看,将方法引用应用于基于类的函数式接口(即那些不包含基本类型的函数式接口)。下面的例子中,我创建了适合函数式方法签名的最简单的方法: ```java // functional/ClassFunctionals.java @@ -930,7 +932,7 @@ public interface IntToDoubleFunction { } ``` -我们可以简单地编写 `Function ` 并达到合适的结果,所以,很明显,使用基本类型的函数式接口的唯一原因就是防止传递参数和返回结果过程中的自动装箱和自动拆箱 进而提升性能。 +因为我们可以简单地写 `Function ` 并产生正常的结果,所以用基本类型的唯一原因是可以避免传递参数和返回结果过程中的自动装箱和自动拆箱,进而提升性能。 似乎是考虑到使用频率,某些函数类型并没有预定义。 @@ -1050,7 +1052,7 @@ O 考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 -首先,下例函数中,方法返回访问对象字段和方法参数。代码示例: +首先,下列方法返回一个函数,该函数访问对象字段和方法参数: ```java // functional/Closure1.java @@ -1065,7 +1067,7 @@ public class Closure1 { } ``` -但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,垃圾收集器几乎肯定会保留一个对象,并将现有的函数以这种方式绑定到该对象上[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间: +但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,被现存函数以这种方式绑定的对象,垃圾收集器肯定会保留[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间: ```java // functional/SharedStorage.java @@ -1109,7 +1111,7 @@ public class Closure2 { } ``` -由 `makeFun()` 返回的 `IntSupplier` “关闭” `i` 和 `x`,因此当你调用返回的函数时两者仍然有效。 但请**注意**,我没有像 `Closure1.java` 那样递增 `i`,因为会产生编译时错误。代码示例: +由 `makeFun()` 返回的 `IntSupplier` “关住了” `i` 和 `x`,因此即使`makeFun()`已执行完毕,当你调用返回的函数时`i` 和 `x`仍然有效,而不是像正常情况下那样在 `makeFun()` 执行后 `i` 和`x`就消失了。 但请注意,我没有像 `Closure1.java` 那样递增 `i`,因为会产生编译时错误。代码示例: ```java // functional/Closure3.java @@ -1126,7 +1128,7 @@ public class Closure3 { } ``` -`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。 +`x` 和 `i` 的操作都犯了同样的错误:被 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。 如果使用 `final` 修饰 `x`和 `i`,就不能再递增它们的值了。代码示例: @@ -1189,7 +1191,7 @@ public class Closure6 { 上例中 `iFinal` 和 `xFinal` 的值在赋值后并没有改变过,因此在这里使用 `final` 是多余的。 -如果这里是引用的话,需要把 **int** 型更改为 **Integer** 型。代码示例: +如果函数式方法中使用的外部局部变量是引用,而不是基本类型的话,会是什么情况呢?我们可以把`int`类型改为`Integer`类型研究一下: ```java // functional/Closure7.java @@ -1206,7 +1208,7 @@ public class Closure7 { } ``` -编译器非常智能,它能识别变量 `i` 的值被更改过了。 对于包装类型的处理可能比较特殊,因此我们尝试下 **List**: +编译器非常聪明地识别到变量 `i` 的值被更改过。 因为包装类型可能被特殊处理过了,所以我们尝试下 **List**: ```java // functional/Closure8.java @@ -1244,7 +1246,7 @@ public class Closure8 { [1, 96] ``` -可以看到,这次一切正常。我们改变了 **List** 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList`, 它们之间互不干扰。 +可以看到,这次一切正常。我们改变了 **List** 的内容却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新而非共享的 `ArrayList`。也就是说,每个闭包都有自己独立的 `ArrayList`,它们之间互不干扰。 请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。 @@ -1498,7 +1500,7 @@ public class CurriedIntAdd { Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。在下一章中,你会看到它们在流式编程中的应用。相信你会像我一样,喜欢上流式编程。 -这些特性满足大部分 Java 程序员的需求。他们开始羡慕嫉妒 Clojure、Scala 这类新语言的功能,并试图阻止 Java 程序员流失到其他阵营 (就算不能阻止,起码提供了更好的选择)。 +这些特性满足了很多羡慕Clojure、Scala 这类更函数化语言的程序员,并且阻止了Java程序员转向那些更函数化的语言(就算不能阻止,起码提供了更好的选择)。 但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它的使用还是会让人感觉沮丧和鸡肋。 From 371063442ead08da9268452174e6436ac4f021d5 Mon Sep 17 00:00:00 2001 From: Haseo Chen Date: Wed, 20 May 2020 12:11:39 +0800 Subject: [PATCH 232/371] Update 03-Objects-Everywhere.md (#373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 感觉这样更顺畅些 --- docs/book/03-Objects-Everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index e6ebe9d9..1c91e4b1 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -49,7 +49,7 @@ Java 语法允许我们使用带双引号的文本内容来初始化字符串。 1. **寄存器**(Registers)最快的存储区域,位于 CPU 内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。 -2. **栈内存**(Stack)存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。 +2. **栈内存**(Stack)存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存。这是一种仅次于寄存器的非常快速有效的分配存储方式。创建程序时,Java 系统必须知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据(如对象引用),但 Java 对象本身的数据却是保存在堆内存的。 3. **堆内存**(Heap)这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。 From 99efb34801bee85a296b6b6ce20ffd94ff4da31c Mon Sep 17 00:00:00 2001 From: golden retriever <1146726774@qq.com> Date: Fri, 22 May 2020 20:36:29 +0800 Subject: [PATCH 233/371] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E7=AB=A0=20=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=92=8C=E6=B8=85=E7=90=86=20--=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=8F=8F=E8=BF=B0=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 8d126fd2..a7aebb8d 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -362,7 +362,7 @@ double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double) ### 返回值的重载 -经常会有人困惑,"为什么只能通过类名和参数列表,不能通过方法的返回值区分方法呢?"。例如以下两个方法,它们有相同的命名和参数,但是很容易区分: +经常会有人困惑,"为什么只能通过方法名和参数列表,不能通过方法名和返回值区分方法呢?"。例如以下两个方法,它们有相同的命名和参数,但是很容易区分: ```java void f(){} From 17f3c2695e4ce545a43f9a6b5f49b245bd3d82bc Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 21:57:55 +0800 Subject: [PATCH 234/371] =?UTF-8?q?=E7=BB=88=E7=AB=AF=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=A0=87=E6=B3=A8=E8=8B=B1=E6=96=87=E5=8E=9F=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 3950ebf7..5977128d 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1136,7 +1136,7 @@ is it ## Optional类 -在我们学习终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)? +在我们学习终端操作(Terminal Operations)之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)? **Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括: @@ -1684,7 +1684,7 @@ Signal(dash) ## 终端操作 -以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中所做的最后一件事。 +以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作(Terminal Operations)总是我们在流管道中所做的最后一件事。 From 565a48c423bfda277bbc83bd935e8201ad29f762 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 22:32:32 +0800 Subject: [PATCH 235/371] fix #453 --- docs/book/24-Concurrent-Programming.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index f863dc8b..cbfad195 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -13,17 +13,18 @@ 到目前为止,我们一直在编程,就像文学中的意识流叙事设备一样:首先发生一件事,然后是下一件事。我们完全控制所有步骤及其发生的顺序。如果我们将值设置为5,那么稍后会回来并发现它是47,这将是非常令人惊讶的。 -我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能没有。很可能它会在某些条件下有效,而不是在其他条件下,你必须知道和了解这些情况以确定哪些有效。 +我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能无效。很可能它只会在某些条件下有效。你必须知道和了解这些情况以确定哪些有效。 -作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并移动它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。 +作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并转移它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。 -而不是单一的意识流叙事,我们在同时多条故事线进行的间谍小说里。一个间谍在一个特殊的岩石下李璐下微缩胶片,当第二个间谍来取回包裹时,它可能已经被第三个间谍带走了。但是这部特别的小说并没有把事情搞得一团糟;你可以轻松地走到尽头,永远不会弄明白什么。 +假设我们在同时多条故事线进行的间谍小说里,而非单一意识流地叙事。第一个间谍在特殊的岩石处留下了微缩胶片。当第二个间谍过来准备取回包裹时,胶片可能已被第三个间谍带走了。但是小说并没有交代此处的细节。直到故事结尾,我们都没搞清楚这里到底发生了什么。 -构建并发应用程序非常类似于游戏[Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。 +构建并发应用程序非常类似于游戏 [Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。 -本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的Java 8工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够解决问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。 +本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的 Java 8 工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。 + +对于更麻烦和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。尽管在撰写本文时,该书已有十多年的历史了,但它仍然包含我们必须了解和理解的要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也是很有价值的资源。它详细描述了 JVM 的内部工作方式,包括线程。 -对于更多凌乱,低级别的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读Brian Goetz等人的Java Concurrency in Practice。虽然在写作时,这本书已有十多年的历史,但它仍然包含你必须了解和理解的必需品。理想情况下,本章和附录是该书的精心准备。另一个有价值的资源是**Bill Venner**的Inside the Java Virtual Machine,它详细描述了JVM的最内部工作方式,包括线程。 From a6adc5436af84b5810a66774a94941decfc4da1e Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 23:27:34 +0800 Subject: [PATCH 236/371] fix #431 --- jupyter/Appendix-Object-Serialization.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter/Appendix-Object-Serialization.ipynb b/jupyter/Appendix-Object-Serialization.ipynb index e17b0499..a09d12ca 100644 --- a/jupyter/Appendix-Object-Serialization.ipynb +++ b/jupyter/Appendix-Object-Serialization.ipynb @@ -13,7 +13,7 @@ "\n", "Java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行 Unix 系统的计算机,然后在那里准确地重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心宇节的顺序或者其他任何细节。\n", "\n", - "就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写人磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种\"persistent\"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。\n", + "就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种\"persistent\"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。\n", "\n", "对象序列化的概念加入到语言中是为了支持两种主要特性。一是 Java 的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。\n", "\n", From fae6ef808201d310817b643d987d32477e7cd67c Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 23:36:26 +0800 Subject: [PATCH 237/371] =?UTF-8?q?=E4=BD=A0=E7=9F=A5=E9=81=93=E7=9A=84?= =?UTF-8?q?=E8=B6=8A=E5=A4=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 0e4eac6e..c865cea9 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -490,7 +490,7 @@ public class PrivateOverride { } } -public Derived extends PrivateOverride { +class Derived extends PrivateOverride { public void f() { System.out.println("public f()"); } From 34a061b6df01c5fba26b242b449415dd5351b66c Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 23:48:02 +0800 Subject: [PATCH 238/371] fix #449 --- docs/book/06-Housekeeping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index a7aebb8d..7874b2bd 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1269,7 +1269,7 @@ a1[4] = 6; ### 动态数组创建 -如果在编写程序时,不确定数组中需要多少个元素,那么该怎么办呢?你可以直接使用 **new** 在数组中创建元素。下面例子中,尽管创建的是基本类型数组,**new** 仍然可以工作(不能用 **new** 创建单个的基本类型数组): +如果在编写程序时,不确定数组中需要多少个元素,可以使用 **new** 在数组中创建元素。如下例所示,使用 **new** 创建基本类型数组。**new** 不能创建非数组以外的基本类型数据: ```java // housekeeping/ArrayNew.java @@ -1294,7 +1294,7 @@ length of a = 18 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ``` -数组的大小是通过 `Random.nextInt()` 随机确定的,这个方法会返回 0 到输入参数之间的一个值。 由于随机性,很明显数组的创建确实是在运行时进行的。此外,程序输出表明,数组元素中的基本数据类型值会自动初始化为空值(对于数字和字符是 0;对于布尔型是 **false**)。`Arrays.toString()` 是 **java.util** 标准类库中的方法,会产生一维数组的可打印版本。 +数组的大小是通过 `Random.nextInt()` 随机确定的,这个方法会返回 0 到输入参数之间的一个值。 由于随机性,很明显数组的创建确实是在运行时进行的。此外,程序输出表明,数组元素中的基本数据类型值会自动初始化为默认值(对于数字和字符是 0;对于布尔型是 **false**)。`Arrays.toString()` 是 **java.util** 标准类库中的方法,会产生一维数组的可打印版本。 本例中,数组也可以在定义的同时进行初始化: From e2542e77d399c83a0144272a4da834fc8f9497fb Mon Sep 17 00:00:00 2001 From: LingCoder Date: Sun, 24 May 2020 23:50:19 +0800 Subject: [PATCH 239/371] FIX #413 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index cbfad195..7418d54b 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -346,7 +346,7 @@ import onjava.Timer; public class Summing { static void timeTest(String id, long checkValue, LongSupplier operation){ System.out.print(id + ": "); - Timer timer = newTimer(); + Timer timer = new Timer(); long result = operation.getAsLong(); if(result == checkValue) System.out.println(timer.duration() + "ms"); From e65281627ee79c0820a240b53448ce9f8a538005 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Mon, 25 May 2020 00:29:41 +0800 Subject: [PATCH 240/371] fix #389 --- docs/book/09-Polymorphism.md | 2 ++ docs/images/1561774164644.png | Bin 4346 -> 0 bytes docs/images/1562204648023.png | Bin 17350 -> 56116 bytes docs/images/1562252767216.png | Bin 25939 -> 99346 bytes docs/images/1562406479787.png | Bin 23533 -> 49227 bytes docs/images/1562409366637.png | Bin 18426 -> 85411 bytes docs/images/1562409366638.png | Bin 0 -> 30168 bytes docs/images/1562409926765.png | Bin 8537 -> 21837 bytes 8 files changed, 2 insertions(+) delete mode 100644 docs/images/1561774164644.png create mode 100644 docs/images/1562409366638.png diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index c865cea9..95dabd2d 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -1168,6 +1168,8 @@ SadActor 纯粹的替代意味着派生类可以完美地替代基类,当使用它们时,完全不需要知道这些子类的信息。也就是说,基类可以接收任意发送给派生类的消息,因为它们具有完全相同的接口。只需将派生类向上转型,不要关注对象的具体类型。所有一切都可以通过多态处理。 +![](../images/1562409366638.png) + 按这种方式思考,似乎只有纯粹的“is - a”关系才是唯一明智的做法,其他任何设计只会导致混乱且注定失败。这其实也是个陷阱。一旦按这种方式开始思考,就会转而发现继承扩展接口(遗憾的是,extends 关键字似乎怂恿我们这么做)才是解决特定问题的完美方案。这可以称为“is - like - a” 关系,因为派生类就像是基类——它有着相同的基本接口,但还具有需要额外方法实现的其他特性: ![](../images/1562409366637.png) diff --git a/docs/images/1561774164644.png b/docs/images/1561774164644.png deleted file mode 100644 index 7444d9aa3a931e8265b38e026dd6358df9325cb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4346 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 D%nX3D diff --git a/docs/images/1562204648023.png b/docs/images/1562204648023.png index 6f5041c7911131683cb6edb64ece6780de1ea529..d11af6421db687fd388a6a516f55387736888d6a 100644 GIT binary patch literal 56116 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 literal 17350 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 Ba83XK diff --git a/docs/images/1562252767216.png b/docs/images/1562252767216.png index f9312610adf3fc34d08c929f79608042068c121a..e3b63baa9ca6705917e108c7e2e03a145ef63f59 100644 GIT binary patch literal 99346 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≤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≺4fY$^`N*`POH^- z>3Gbr#nnq(RgqsdEqhie(g72ZXRa^*kFEVtuIb-<>rVVuJ#!NBs$w)tPI3}~{{wjQ zPw)E|yXg9E)@;omjk~r%t0Guav;AE$ICiw63iWuv)GZj!W;^et9Zoc=tWW9p1a>a{ z?Nspp>t><(m}Vih&<{tGe|s{jRc7u^Sy51)7xHGGKIBFXGp1)Z4lAEh4=S-AtfafE zl4nahMOk+lSrt~4IOA;@-x95wbFOL{4PxLlL9Y+2JX>jh*FAetJ(T1tKhCRQ`GP>4 zXo$xqo2QVmS{K3G^&sb#Pab?6h6t7aVCTnfE*KQktNqvR2y_p}X^Kx|U>`v!5MLy! zrV@J8VT5Z4BCP#5E{YokOaY#LkRq0*7xu-0-xD*iIbQ)WxNL8g>;RLAecNImk=kJdj_l=XkW?tlX zg5yD_P3qC^I}21p3_hVD$OaJPJh_25X}hq=x7Pwz6a3Nh(3)Ul9$^1AXQ)vZ)8Zstkos2aTQ# zV_t-0RpON#hWy~oKlFsUi=wu}lit%5)0{Git_X@7;o2pq2#Ofmj4Nf2z(KtmR}Tp> z_r&lF!6`J3?Sg)vMd*rmtUg(=0{Y;n#G+@vdRP{(M>^33V+)zWIy)b4HaKd^wkIP6 z;&{jze{~LluCz`~axq+~nsZL+36O*52a8FVy`5c+3y;H06xqhRieXMTh+hs2%Z_pDD45=I-a4dab1wY@ zuX`Qv$uI!a3Ls=qjcc@xSTgO}0}6c(P%(Yp0Uajxs&x-3u}6QHj{BrAaXiY-dk_Cp z(E`!w1D)>&4;1v|9Js^Of{r6-T1-k zdp<980U7yqlN1kN7(K$&^^|gJ{5@c-xen;2zTMNte?X_S3Sm{hdPn(OC>B)IHh@*F z*syOi2ywI&FNT%rjulFI(Q*QVe>bcj5EI_2npdU#SVRGq1prHY0idB~IV}0n8eOB5 z8J`gpz^IXuJfYPDHeSlY43LTo07k7NppHG7RBPF`4Jsq!-qeEk{)rHFO0b*ix-Vkv z0)VAI7sWetxW0Ko-N^(L#0t#1nVo}QlOLtS)m{MVPPG9?a3}5~;EQBNmVr@%ld@^q zXa;s!zpv&?o5mP;H+Y*R=s`H%e z(ZpJ~___UC16hP3_{iB>nuzXZM)MvqZxpO-PcS88%3 z#9{`%K-YTbKej*;mG`Q%|C5)XmMaC(KPltbz|8Z74%j*^%JbboRHPm);||Ak{>tv< zGvfrFj2CB2`tu8Tng7UY?r#R-?1zBMX#kKRkE9uaeN0LJ>u(^ryL{q(-TUUh4bUN4 zolkZ)Da4sDOk~LY;i~8-#EYUo`a5&(yJa#o~o|#XFsuW)9DDC9oQy*p|}Dkth@^yK#L=eKSl=iFfJNB!&Omn)_O}8 z$7WlGE*bQ-f@~O|5D)fA7=!Cjo|1+VIBxsD0a%S4&Q14*?eFd>I<7006%*vG@$uKe z1uTfijh(;-cbGbSMHp&d0xu$B?GYA1u=aPSH8I0rok(&VaMmjQS#T3MAi4JjYH z=`WSMGZS*FaW+*Q&|v<4{tua>7E6CjQp|F6Za;Y+==wMsxexusf9(OF4G^=xn3$Dj z>|%38YOXg5UCtGFxT5A+xs zU;Hwj!RvOX(O}AD6Ka1L^hE%MZ3vp6HmAxMwaW2z-llowaFi;?RgvzIJ24>UH^AT+ z4@cz8`~Vc?;Bw$AAN@j{vCRe_3GitY=!i`$3QmF4b@vA|`IG)H1mkghxWMCgbCXD` zd%rp-Q*ta3=j+6N2WQG4^xuL_7VGHE_|3lWGpp9ER3 z^-r2qB9vsT`QN;QD;3 zmF{VC#N?#)_ARvS^4~tj0R)NFOs`n-LiVyT31XnRiF(o zujso;p8yk4Y#SKntS%U7(BecMg@XEL>&}I$Fb$FV$>yBGH2mLR*j~_&LHmz}Eu3Y* z+TG;`bRS^gn zOi_lWNxnOoYSt$RR0Bwqdes>KwZV7y0^CH99do>a#A~hgfrUq`i=Fq0;yy{A<5GDi z%d7}j`h>W*f^7wpC3o&1+PN1>`FsVIuN=7s!a!lSfARCv5HYL!7OP>$bfNI(lo>Dj zNjq~HoA;E02Qp(MhUs0`;|sjE^*vKunj%7k%zyNIYk}EeITQsBY=u}?5$a|d?S2F6 z#*pPyC*~Yr9Z6C;W!V9=r~~)clae|4Q2=Ob^SAUe8T~h;fl}_8e&9;|Otr>EOYPrR zR)F>BD!V2f*s8y99`RF}N(+FJWUAf=_=StZ@=nlxLw8y@xUf4n(o(A@))i@A4sdHaC)Uo1;;H#@k$^;)%6uN2Ku?OcwT-eJQvM-+IbAyvR?m5jbNL{INCUXNmm?VvOWH{s$ZwbuSj(T##*D8#65)Qj$&)5XS9Mm)JD{!E(2+<&CPh*FK;#6!>-T<74Jo?+Snb$?6DP{T0 zJSOI77b+xBrlJo3I_$+KoF}nR2U<&uTH&L+8bj=a{#*;-$|`tI2*OVLHHtL-Lc zu%p2R`-N*MR^!r1@~3EB4AU0w7EU1$$*cxcy;?f@9@(?FQOXCWg}g=x~|l7G0O z8o3VQqIrm-2FmbB>WC+sucp(fCf7a!gtBhQLDO&mu)WsemUI1`O`R};kt~MdtTb|l z-09afrkXbBHA*_Ab>zZJ`|({M@jFQ9htr|8@!&Qav*n84Dgd3|wo2_4euv8~n_}xG zV)9NlkMXvp47i}g0fC=0&sYTz1>Y>gv_ z_OD=>+MV`_dv3O-VMK-~-9j$qXeCS`8f-V({-YS?W+MIH>QU?#$4`LLwQm$Fz+-hN z1#xI!!M9yVF6D6|4z_~Bz zqp(Rj1@z6!-%^K zt~*!WO3c+2PL>;%KdOkq98s5>51MQ!DWgyjK2(Y!&9<bN>YE)T!@G$PgA=*cF+0DZuVHn{-j0*8 z?z~dOW}xJdCOrbt--8symJP+iTeI`!OzI1!(q3DqD4&mL@M%Ad36U5xVZ>HEWO1DC zKa!Er_p6gwys9Inu;53WvX!yQf^?rtC3~n|ANV^Y`!?XmpDByX_{NuLm3kTgI-5P- z&K?z4UI&i@op++U3(D*Y{_8rB(}`qEki-Wf#2uX`u2_(kf-M|f1!kbC93u9D+%TFa zx@BwCET^k0)x>7I|VgP5DBH0d1D3(MF)yDk|r5D=(4}pJn1bc3^Y2W zE#!Mv^0#w?4TJAU+;2FSlc$3?y44s~u$rQDWKw7q4zzCQv_xE-3fQ_1sXZuE6#D)p zv6%$zq|*?ampm($@Zx7+x|j%fod7KP7h%b-95LA>wyD~1v3hiyG+5oq@x~xygMwxmDAQi%I^ccdP2Q9o?r@lhFizu4CXTn#Yy0%?~VQ zN>(4Xe=c_f#P_IkHUu8=NA4+a!MTMICBlAa8MT7nDJc+k-Q8^N4#a+{_Y4l1@MWBB z>F{$k#BT5WWY!<-J2BVS-to5d^jcfnJe)mHJ;&KI6xJROhgONW#`W0vjCGV*!MG`} zvPq7xHU!(4H}bF~yeV;z1fI7)9=!%eseF4`M#zSKVxe07npBe`Y&b%%4trUmOTG?4 z6;4Wul$Se_^&9aQAy>n==A+dQAcbJ5V9q;3&`*^0!GM1hC~YrOpoMYt4dAE@r66s& z$ZEu%PTz0a+RdaDB}G#(Ku`e;8ilzt02K;;2ZTIwU5UvtDjbL9W=ASclq4Y9!luD* zZ0%u6>0OTVPBwFJ#t#pv2Q8MD(D4ht_76u}1|NCKn=mEu#|H|I^UOYD!vQ>molMP~4$Lk8>2 z1!NB&u&K zuyUL^*6k-k&_t~l99kVIhqixI)O5p*>}oC_6009NPL!O6;OFv2oC{C5Wu{;wiy%nx zP|YEn$vs-$Hm*nkoM}(jBbkO1gspE&2kOCQRQ@+kg+_DSEAV<XZl&GOZE3;O-nema)tNdt)Bs3C}oJQ0MtYB?t5=U4uh7?)`YZf&Of{6lZ&yR z`c6u67!n!Y2V*7e#I89yuAY~S{BOW}A~uX(wAj?W6mREv|4HQfIKQ-&b4#MfM&h&&^9&}zrFy3_sP6cP50qy=;fQy8~@{s zkFRZoO*5bU38)vpx%|v#)p_&sE-j#vV5;eQYytmfEtSAb4ForBzYB1C)x`;D=|#)$ zogsWi;EJic$=}z1fokCgT)s#!Hu=<^A}y))p~CvF?RV37&e`}%F>SJ*&XBFwy5pD< z0x=PFeFCIMdA>p&>kw}_1HV~58}vuhNI@&GnV7(8CLBZNyL zX(-BX;>V^e>2V7EeJklZHF&-IXU@tsqL}R} zQ19miyQ>|Cv6|k-MLPse+9eBvPW!ykmS$tR>+MP1j?*OfRBy2=AIa$nCcZ2j&Ip2& zC1d9`S&5AM!-zLx?err@j7@$?{{=jMU6I9_2=XE7L6b8!Jg=Z7#ormO5_m&!`+uFquB3;@JACK+u7bNeMr_&shL5Ne;Ey2~{tt*|y5tWE?FAn2=gzDm0a@N)0_~g@A zN{-j2M{D!hR&Yrv9@>J7qm3Jr})J++lHyLST@d;VEJM?z3#W%vQgIP)81=& z?bs~RlC>KXa@)59;73t?T#d+}Z_}-6eqOz89+gR(ZFg+^`wO;LQId6ul@*=tzc)L^ zpkzf*r&<5}a2TjYF5?KR_I(PVVu;d#MG4*&<((!eb>|NLZ9RFM%JRN#TGBD=Iyitv zw!#(Zc0Ns!1hH;YEZ(S!t{+u|@`9OASY!+UxD z%J&{dPKWje-cq&>ui|LMH<-aUNvVyS6JyeZC6;_FjN&T#bx*hzA~64&MAcoQ?a=6s ztq^m78WEAhARZ*J7`bx|QH+wCTZTPoQ{)Rtk*%r~8LQ>kD3!lpCxuyL9LNxZvqOA1 zLNrzZY!pUnA9bDk@|P4z=f$iM$?VC=Y(19+Ogr~}{LGPf4&EhA(NPiR^tW_^n|$ow zIj4+ zkYaf_={UMR*)>})PF`E(YF}Tb^@}nWPWT;6(rA1b8F!KRo(_sgQBj+CzVaQh@ly>! zYE?G8)-yW?#R)_*|KIzDJ-3jRiW}f! z6W&ne@atm)W5fo86-i#n14GZ{6Wj4!{_11Ii<)@|t1+aC*3*)yO;fy$hcFILh;@T1 zIf!eI>+Mox({nmlfkDN6GX|5foysxa-vw#i{`i9Co^bt~h0?^Njr{dp0^EwHH>#)D z;bayZS>RcJlLX}hKw6&kLV{;AM(^B0Um=sLFk8acgVY{k9OyG6PZ!{QBV7_!s{wWx z>`K)tYGqBeV=64wZ371hLbAU`%bvE@XTRDzs*r-TPS%`&XQ%Gn#8fCaP z3?PXi1wz#_R|R9Cz`h@x7UBz9UmGRQ8Z}{J!f+|`sTToePIq$g9traR1=_E0_+XMl z0Wx5TAQr6R))zx4w0%aPKO`Zb5~m};^x0O)q|jjo3bo_at-Q6bmk(f<2AJyNMrsS< zOZNOwN~*NE;${d)EErgn)>F4;u(nYjkLcu3Nz?ZRAZ#)*)g0lLAd;O*=Y+Mx_giHI z(Si{=*5yFNz^L{mhQJcJNJR?5u}X4v-*}HQiSN*@i8vnpA+?d17P380|1H0O_N6Ye zgOZDWafCZgw~pcOY2y*L7hiM2+y}OYM6?Z*W@eFDH^C01-1tOs66MTc+`A>Mpbo^u zlcF7NXtjU3LSn|kK!30qharUbcb4emkaCkk3dXrdTw!t+H%)+P_bX)%Z@ia|yvO$+e%P&!kWebEdr;J5C5A4-1e}UP(Ph{H3!f~A9D%Vf z1fHY~yc@Y;Qq=n*2YKe<`;RAnLK-{oqmq1Q@McW3X=PF zTy(;_XUW68HD3eQeC{~tq3#}L@_KLH3RA{Yno3;Ts5gPs$%gvuVzPEFo{vH0 zRv!fBhby_#KXd9O8oX2ld0`ak8&gH)NUJKxeK8Y5P#w?I{46Ycby|X&XU8`pl zY{o}-ZA?#~qRDncdgw$)2cu<%n zRWvYI%6rR#a7}l_a1R{e#%fh!(HbF4A?tt;bqKV8=o7hG5G5zLx8W;C+ubntATY}| zMq@t6{+}bQjMC^$5spb{orc0-*dSf25ap|lU#Munn!-fefeX<9*~xum016LnyUq%J zRS- zVWuY`TaF@T(~$%1-+m=|-+0h{G>W~}{OOH6%bIB;gHoq;k_THu*FC!@s0_LL;_QwW zHWDH&>xl$+313{$pIW^7S7*FSp_Msk?I3Yz2uu*vk6@4qppgb@)Ava_+QB4(%Lj@9 zOuD(Tpk&pO^fsVHq%Si$eV!UAjaPwb}Kd%1|Q07jw&REOtZNaBMcm(l7exE;Ga@ zo1*A8qdfCKTihkI<9^~elU|Up;;7gRVBu0wNd>SvjKEkZ)*;VV?@ziL2WTmTw{VP0 zxcbpNmAEuVHR3v@55Ta_Vr#4?@ijQAM1!Fp@nvyZ7#-Iv*m{;sQ~whix75_%-V>_B z6exnj_05R&kS%o7ejhZ6VL_WX5O9REJ=jXKKM^!YKawgin^%P=zc!6+?dTMDD}FGw zu<~p~prDIrb#M5qbgp)<5okKXkkEu@ZY0(@;bvx=QlM&V>OZSky`ZD%mV}S*XcRy9 zW?Wi3!)8m;K$~cW&ogL(e$?5B@nzjZZTx!Qw|sHMdtAWCQtxv4?;@CGIg*ZvH}hyp z%jW7u-wR9{W+!AKyS_Th(J${2N#Te?BuFSY5#02-20c61*m<#|my>HGmtMm?gSuvo z%3CWjVyLJm@p+v}fhs@D_M71uzKs8Q0W^OLIt;%((sS4ihbH?}g{)QhRiQb8h)aCL z&usYSjk+%Mi9T&>m0k0`GunR=Bl<-gBl+y%LHm#D zPdmgU+#n)@ZMt#YZU)y8t}Tzyhtygim;RmE?|a?4HLV|>>jd{wQB`#A-mPV zCF9{v*AQ#n)9D+etezNwIe)mrtP88;q~LkqVA*75*RQ%)fPE(q?nB3K z(}B;DI7d;ffRsduERgUjj@fR~9ZNUD`VLyS0vW?mSIh)X2zVXHpIdw{&PsAz9-jtm z4H2Tf5H$%AYU7;iLEDKUa9YL8w-@2 z7}3&}jG$VuVHwhxB{bK^b<~RdQ#G(()HTcWd3F1JM@TEto^}UeY}%dNs^TT9^Sf&N z*S3R0q<(^cQNH&V2c5iV|LV@&$TFR;qg6v5_C@~^t5eAE;I3%Jnruc_S2gZynnB|C zdU4-6du~_9fZk*0kuEu0s^K8beOjd$j8Mm8C-DYU&dhD}UL|~ymdA!>UqsZya ze_ARw<$W_eFpWuUJ;A|uAtN`Sd#)l1v3GUHMnO-O-oMlem!LFuF(k)WiTq3ME{YP_ zx}ly?(ef(?U2oQk0_7TE3ErcVYcPM{zm)er-_vDB?8VshFXe47RVs?IElJ`oJ2Tb`z8*=!V0eU@UukC42#J5d6F{`Yq$w; zYe2Y~Bg-QrPZYpr>F^^PS_N+&Bu{d{CJ8qpH7rf`~@UNK= zF=pDdb+4PAE8^`_JZF`k<^zE;gq#;~MZLPd< zm}JuLpo}A6!)KWW73xV%56(=4s>whgi1t+ww%x2l=qG;wNsNYnFRgo-ID=M7lze@Z zlOCt3Mw?_~c6!;^srpA8f<>@8MdR^8lTI$C^zM!26pd_&o&*Zi9Ycr1S;_H@eSu2s z5oInbxt7(fx5cgBA8q0}P;g%R)Xtc`c=RL~Du|6v*yiwtT;ds4zvw=z^K)lW%MTrF z*DM*&hFO8jZC3$Ty4dbK;Mg^}{zmK`U5BqNFH|HM?4VgH6Oq1;@3+1bY`7{`o8-9A z^*hs5!>KTM$Y+(y(UciV>I(un0GZo)i#<1=JV4zJ(o2tdNyH6xsYB^LP#4wRM!dKO)g(*^eR-WJ z*oVFn4~GitPDxrvqPE109aPuJ8O_Kr<7qo71_6Smx;5g+sJJ8ht&qbN-yU&a{>dnu zrdo2(3>5=~|I}x{6i)$}UtiRQIEUQ{yE`mUlG%gFaZsNpZ9}JCz|v@$0fBdc(Ic77R6 zDoU&><^+X)BWCJldnz%zlsiSC`Un{1=vJ5+nVMXRyxc+X$s++KOGMiIi1TP-YXIW| zMoCMnKCRJ8Alq_OeXL<|Mf=WtOp|f@x1tEzj`&XE|3lMR|26fuad?bw7%)b6hXY0< zJ-WLkl`ath0qGna(lL;dZV;tAq(MqLqy?1JXWt*5|A6s2=X1`v@Avz$iiKlnZqD z?Rq(xpOS=Ht*%2FWw}Up5lFClEAt`cxOmaCH{seTKQr*^xLbN7tQj>MKI$qO!O~3P ztieSY3|m5RZo*Wn(>`I&JY8@C&h~*sxY20<0YVsl(-tUN5~TDogHtxbprEROmrwAz zCQlfLIKA?((rN=?OJ~b<$pD{&^ZE2w1b2O=ozkhomf+DzYh2!#GF3RP-1pLCOasG^ zO?+`qaBu|tC49w-5?sh0G{C5&QmZ4vGwgJu7AVuR0*j(fEw~>6Jg(T`4bN(2j zOATq!pNHtLJ}-CY5v1H^f+&czHV~qZQpf*1ymn;5>#dk*d|YmomlCitX|1s{r)8uRJ$Unz2xB7moz_mu%1=Wa z_7sXASk zv)@G+-XJHR!)C)!oM(sPuMqYTGRNyFFLtilOT;`v_{XfONWKvyrH0 zRL6ZLzTjgc!f9QdYa5E05X<`;k!)mekygFFJ-pJL49Df%CC8?CBG0MHf$#Ie*r2i| zNL88x^u`1@ILm&+#G^XaK-CRe%IR@L>f=M$7};-?Wn{*KAyq_M!^ad}^*x>y!3~uZ zB!nlNTw!UxHJn@%udvh~giG~{F!mIJjpS3BkW%s`j$rJ1lRZc?<4Us+|er z1MD~2k~uz#T!FLWGRO=&;n6C;tEw4tb@u2IjR?E+oKm?5`;=8ip9R{Clu`sjLzk8nA~D7?tZEJ}9lu{Uyq!gX$Mh*j!VzaHDYYGI%J-n&uzFW0~s-Qg}V492>0KFAT9vb&eKe0 zDGJyFu=Majw+2N9Uey%hY}@o__U(P3-DTQy6DigWRYMwJ@buHN>F`1I4+KpwD^A+5 zbc5RvEkDsS(a=4wgf6RHVgMQVWCSFh>~LjE(1?QtzaFH>MDpsQH3KhvtBI@IV=~`% zXMWkXlkjs&#VHqWA87SvX9{PS9}=`NHf#y~WP;(Q;DTW_kS7X{HJSkvslO5Yf|p9X zNhnJ2v*oT`YU#a3b!%55f@3#1V&MHQXWuxRAHwjXQO1iCVb5Dot(m7%TgA*iDaBx_ zGJBS-{TNw$J2{&ns|zz}wG+k&NwfFMP>u#g1rS~qd?i4ZEL=lSeYMwnF2t|79aWgT z%NFelo~BxAU3DKfRkhB#{UC}N8^Swk(vy)ZNv7gQdiD7*EY|3wIHRXPD~9zXoY3s! z8*deL3ab%RqD2bS(8G#9k?Y20o*UJ-8P{f%4d%y{LoQXc?v^_J z*{AVX@jZ6qWD(m-XQE0@Az6|IYAjV~FZd{P&xO5mbsKIHTq;`T?pE3qu)F>_VyUxU z`#y*`TX0`w001FRE`y_o42m zBDCtTD3>HA5P~JBSvuHi>A5|yP4EG^58C)>=tWTmFvPUe(2bYA(V`wrPw&U^hM-qw zL-6e(=D0X&X81jAhBjP7at#pkKzz#HpXe@rQ#6K-&8x+sOKd{XK`?zmeuk#Tcv>{T zER>ZJJI?#8Qk+;4LhqH0MG~OWl9HVj-ka)$C-po-)_$Hvj$}j?fMay4NP`J0RRc0Y zGBnIEP)8sXZqyTE9nP&KDz9M)b8AS(tfvXX(8yn{*i+NxFm^cH8o;m;0PAEbCm~>V zWVN=@PWm%RKzp^A!JlqzYn^-hmOi1nc)=lgCV)GBw4bUe;a;gdx{s_HxLnqldu8Lw z@5LP8r&z=ZW(bCHZghwM$JmavjUC3gZ0}CC>pUJSE*38b-(^uudjutbc+toYUhSwm zz!u31E4h%3u{GIU5u~1rn@TMf{H5yaRH3Gk839aIXl9S1?HnjNPH`dpi8V@&Ar5Z7g4TQV(9is4Y=Zy~IewYGM zz*O-l5{%+lSt#&RYEs}X3u-~lHX0Npy3u=OVSuOp$*Q+lJ4M4K&w2xu6D{-_qQdyk zQ}TqO92WNCm}dSYFH2%qn+lU9uZ{N+vE`{$a7LXUvCT!QWynGXa^D{h1Qc>Eg` z#)C#GRZ3TxFK@Qs-~3B7Te-EW=mz^QlBK|uRNa>}-8NP>0V)DaOv(5u{lqel^ZMG8 zi8_4ms4$`4sf%{f0OHEX>(!+0YxVAHlsG`faXys=L1N#ih)&O{;xtdzNBZk}PW%0& zixn!&`cOoQ_FoU=&?zFWry}KyWL2G7&Ba(2PO_c(v4;DRQ^)?8uqbI^(#Lr-zAFEn zlKaLK)2EeCIlT9em%}m-|3%vl_qO90IN`VVA{89 zUhf}=-y`y2@EJ&^>YZ#1awOgEoZ2k-LEY-w7GKbA5;dv!Ldi{09N@nk*#=|Wgivmq z1*lni*_E&Pm%keuK8*=2ywI35JiwU{+}+s`vB!-`zJ+{#a%RY0L(WoljU` zFTRy03LRocnr1VaEVwvjdyaBPZofv^NoiF;F}!ys0VAPROgxbkKQ0sB>hycO`|cO` zXZ6S3@zOs-=49#Sg%YD+9zq0TY@Tnm%P5IK30f6Im|Wq9H$|g^DV-nTgntuE<`5nH z6b-xfq}Tt&AK!flf2L;`PFgD4u%A!^S3=5}^ju(v83~$XD6)&k*(k;~e55_B=5877 z$7kox`nSX}*-Ps+Kq^8AcF<)UV-fm&G$s4%WYAfnCYn_67PzwVJ+e+A{pcKlw`53u zk70P(iVXgR5|orMJXK6@3u~pns`g?lF-AnzWIAILGuY=UUBl6A%t&5c3irpXYlLZ| z@O}?trQ2(Ct+25oo0#N?$tlV6$713j$D`SgUZ14=zG^`);6M<4*>+XVCw4(^PysnV zFB9iIT9I!^<+j5I%isSARf9PwqHHsL-v4>~{%tQNrTvU{gGvFLnc669No1vO!3S;5 zCEisy^!vV4)UPJhAnD34Qr$ieZsUAr&!ql^328|_?~Vc6p;X53DZU-2vy;lCJ0Fti0=0WLh|EvwzF(+wrbgp`>ioUpl5e%5X-nB?%hWRl})e2^hpFjWRU)UEBsq0$0P zQHhuIkp>nO<;9LFNB+P%ipgrl%CwXJ__xJ>&*=E$>W4zl+1T@-e-Hc}*U`a(lMK82 z=I&ePnXB%G7#4mrfBv0MoZpeQ{FC@A#PDyI1NFlrt*K|pF2Uw*T+uT~k|z{d|5^7m z>edWyEx0QMVZ!(_1zG>|Iyag93~M9?`7__43J(3KA!1ie+uGP3OHaXNbNP#)Jwt}O z>1gvt#ga3O?{B)^3#0(AM}KsE-~Y>k#zqX#q8)3XB)Fi<)WrB8+Va?3P)_<#7!5Ck zC3n$N+JV1lF?@QMaPJA-Ju~^1<$2cl+Pbj-91+FaW=+5)N1eZc(&N(f4$%9?V#tkT z3~=KaDoL=P-y}n>h zhP;d~4?YTF@cS1nbDyIAth>6z^y^yb4eBVhyiF^|AEDavs9W>! zKjySN+Jk=K{rrDYngw#ff&T)r@Ar&@_qeqEajV~ga@H};o8C#$69WUN zcE^%KZubBtG_VK5@r4Au;x*bi2#;6;`ba8n%7JMee z(LvgXDoM$pqK%%VT)^(%Ep^3AV9C4GC<6O^=;Z~9$D~X9ci-dhJ}Ric`5#f=i_mrk zuE0Cg^8C-*6-xN|h%(o_7SQR52tN~oN=7~!0@?_jj^N4 zhx2ET5h&wHx|T9mh|)AQ7%g@1zJ&g!qVO)eko4lL>+vw@Z^bJ{#|M;5TaRqf%2t&` zEwS?N&$>VN6ce68NZ$4 ziCoUdq>2&N#662Q>6UG*r135w_0&$>+U567ltNjtdEaS5|MO7hl41)qfeuCLBr6gn z+%_jt=pjs|e4-Gdkq8IO&uADS@VJr zVSK@VjW!W!=@T2`cPP;_zuFr^PyruLPA=roHk-A9L z#4-Y4Eeu@g3qp-CN}Z0Dbp2Af#Bx4}heh6vO!V&azmv0_KjeQ{^*B(xTC=0S%0j87 z2QfWb$wX$&f7}~wyv56R$*7>E&mqkh#Fe>ZoXsQI6|v&?+emMAAb;5S_f&Yto1PfN zoBOjJNSL1xPPDr&H5F!tXy{g8>UtKqg%>XNPYmHH%4y7IgEU*^6D~B*M=`?y#(8fC8wRjALoX32dW4Ys34iJC@7~Lctrh6pG}(n6CZ25ZzCjG=)R5bR*4ZJP5@ev@7rB!x6_l-@2QF~ z$ZX+iR}kTeAFJrM6dSHNYw4L|nevj9@MRt+7QLtuuRFzJd0ASthY}xIV_yHKVyK?b zWAFaY1hg+QPDpAU`L_uBInERR=aX##CLv^6#Q@rlZQE`GO;hIZl$%yCj_SdVI3d-o zAh$SqqmR53s4?`lOGF_jc-`+>{F^^f=%4O?Pc1n9o1xh90UC)Untmodn${;Ag@KJW z<10S^11*jt!_3UMs8>>km?$DBpaph)LC@GZs=)9(O*tAYgv;*Rz}tjhIDcps!>}yJ zAcO&Z7SiW_7KdisIQ(21)tr0{rZh=di9@S?>H8z-&wnM#GF9G}bO(elrE62_OT7Hw zU(x)iw+BNVtKHnDFzDNUNn*m%UCh7Uh^%&@j){+0z~f80w`;oQyNXmNAjIGs>w_!u z3)Zmq=eL{T=csckJE;a4Bfk&wbV`2No?@4hOHTJrv1l>vgsbwt``<(Nyi(eV>0_jr z{cf}D7PdV5R*GgNHO_aExH7O6k7z9TfrE!H8f}RA@6BU=!aC~V_M#+V6SfDro;HsY zpKIJ6?Pqt5Gzp}pNJ6~Miv^|S^z;L7v7 z1ET)We>B;58JL{VpDxH!-9RWsbe#T30M+a7thz3E-9KM%IAY%-l1vvXb}NR5#%~Dv zx0S%u=20W3we4Vm*ID8uB^qm+@J+`Nc0{q|?^=H}w8cC#2$T`T$Z#2>e38biWMRKT z*@y0@vaRA}hj>6eGaLkd+EX}0(X3*oC$d=Q#fm2u<*uXOL0T}|cT?4;Pf*27Yx``U zULa3fI&6N>q5CHXcrpw(9u}+dx@sj*HoFSV`wJHl?~d*GRIc*%%o+>eb|$9ZYi+A} z8aXhAwGlPJ7>1C%mG{0ZL`Z^=<|17{JdbPZC{ZA8IHU^yw zlQBY)QDM;A0Xg0t#VB%hs*UC`(&!1nRJ@AzrG0#W4S z(~qa;SGDF*zi~^mi3T(JHQosyb4X+ryZut66NV8M74IwNu!!ahZZPZTRtOzrSCu4H zRaCW)q3LvCBI2sJWqV8PKOsy9#XSr%{62g%5J+GewbUs|O=#sHvN&bOYVi_(VI~|y z03Qe}L0?P5!C@Gs|$0ZdXQbcc9{-_gGM4lKZ-q?fMaG+iD`bm(Ci4~KJHqWWqDV|^CEXo;D)2^z6cB=xwNqQxH zl^yae=Y}vQZL=QR2(O zlvbg&m0g`iKywY$;D;K`$ZLwmP4X)Oc`{;!aFJ@9YRPV*vD0*6u>U=S7+FKOqlIA> zTrHacwpt9{1}B6Rk9s9xP|l50h(qvY(;0mk>Hj2ehI*vDdZD5PW8L~sXhrz^Y%kz+ z)D86Ntm@?6@Ur=1b}OgT#28q|S{D&TJ!|XhM}+4&cxkqTRJP8eVGNlU&QZZgG%Fh% zkdCAx$L}WP#INUm!4roKz!#!kg1BS!$UM!-_=A@$P3TkdvUA_8UG)!!d$(zE`XZT=KGJGFfvP@F#V1Bw*Ar71oiuKE- z#j3nH^yb2m4!loTk@mE{<9%io0N&%frLhfN@V_|hzhOS=+ByP6!?7m4Wn-rl!O`hF zXDC)hVs8ZA+Y&DZqJl^F?v_Vib5Tige_LtLMVv_zdkjyAF3~gaf&JOCKgKt){h{Im z3sgXDfN9HpdwTk=s)4i)XShHNyS;G@AC{5h-aB993wz7SEUnu|v&ZQ<3|Halh+Oh3 z`6PTm2oqA8PHNZ`4=>iFXf^enivx*DB1Hb+;M5An_<2r2?4RNVHr`Z9?zhr=F<=s6 zKceho5}|?p{t-Wb`(_S(9AySL2rF&kV`M;9A;XG|#OMhyTc@PQ>9Y-9_07eGTfe8M02QpT0 zZ<#`c=j5AyYGMr;EA0?TaQ(j2HLD-MWS3n;JYbf9$E3ndD4Oo?f9mLgu7+?7jMRlT z+;8)_*w`crsnwDaz*c6MOB^j8Pin~+rtLM%Jk1Fj7hv~Ae9@6_&nN7tr6w-{S}6D- zKL6o1KKyn(#P0cIcguV4Hdj;=lR_kO2|04OE6&xzE`*ps#Y-k%GAMYqX&mAKU{#xN z17^j<)Z@W$^Z9MOP0!he(9cVpPGQDsyEyXH+@0S}y?hD>8NfD~~kS z-4oYiw87+6w(`0=+Z_J}`nF?5Dfi5k==&>CENM6KRmD5y_t(m>-Kpf6G5+3@W6zKt zyds}aS``cWFLG4b-lFWMS5nRzI4Bhz)8t2%lyLfY?Z{SIk9b`IL5#!rMgUATk_zCA zo%N@ z|Mt_opkW}Sb*HdP*_Gz_5huI$| zj@?!{rMDuXib;LL7R%eL@rIZ`*I;Rk9B>wXARN3!?in_`KYH8$!f^nR|Ah=lBgvA5 znHk#0IZy*3b?lSf`ocw4EQ*@lE)meirYQx>lYg{rIRy0AJ1Mp>cqX6aM|k4}*i~{Z z=4g@u^7F&t=4ld6c--(oT1`g4*h?-WAz5l8l=XM;~;rL7$BxtS9q)cg4^6R`kCrFTKm z7&GSctuEVyb4#+}!vaz$^iq^z8jp&=Drb5)Ynzv5mW=T1AJ^=#4l?c`AJ-4Lo?;-M zX%AwRuuhR$Ki;y_UW9PYX{HQY&kc{k;p1n-Gd3MLncUfk%NhKW-S`M45C}AD<+~UCDDr1U< z>w>7d9(a`4nB>UrU%ixPj181TXDQn=>)bc#dpSSx)TI5;0gH|YdYhqnqIDino(M{= zSquamDrFc!&{#A(vF^!DtQK?Yj(Bmw1Mm`@+H~6)$`IK))F@IJ)!_MaTtHwpoYvdx zJIRli%p*Y;=f66&M~z^^4B?d5$KILf)98ccN78shi4XEs+;Z!KiE=Se_c*D2pVl(**g2fs7YZsS%(~) zBe4FJhK=+2wXlARIeg(+ytuOIki?F5CqwOZWNXB68Eus37|-k&TP3)$jm%Ly*(F5L)TK~9Whf=QEMO;qmZ{($Bp0vl7oys1piCLw%=)LNd;d= zJ_Gm{TUsq02WtT0uk#hQ_c0p45?raAPjrlu+oZc8FVVRKF{AMa_;=cux#@Dn&*g~~*g+A}m?rcddqsY=K9WK& zxO?6Avzx!D5b3rOzBrMH>NJVl%(QZbBkoCy%sZn4kb(F&`AESA! zv_XX0U549_Op2T~bGtZVHgNmvL9ae0{2Mm0RwARITEd|hIiL7JLo3T)_x;NyB}4Bp zepl15)jRq7h`9Cb8qpuN={v{u__%=3w@YUKM~UW-JEpkIIlpIHgrXo0Gem>ggmiyO)Fy~4aAKEh0QYl{jB@TjjLx%-a!5q6t-+WB>k=sb6w`KUk0)%$jauk?V38O zQQ}827i1}9q$C~YlMtBhxLd;It#o-T5ETSokbF7N7osuQ1o#?Gir#@X|~Ta>$q>QdnN*g@4}-ozX$ZN;UziXj6@x+Yrd?F_OE7*JZL$g#H(@51gufd;bR!_mA^5W>SF5CC zBi52lNyUandR#B9z`}?YLZFmEi_jw@_&KI*eHi*GNPExhC>a6BF12)H(#Zwua&L#B zPB!SsJT=0VZPLp9pKKYfeN3;OJ;*)v^@M}|q2HYRKA(qw{7Qw~sJX|H5}P3I2TxLZ zy5v}H%cM9d)vq5{8DC%LvW~;C62jEW>#buS^UHY-?hS9dx6>p#P?e2QM z_Ve0suU`Wzdx;347ySJ&7X2GE$zt#E@8Dv)qjJ5S_~fB5X;PxH*|kx*e>Q&y%$Iz+ zfhh8xeWp~Dys7NCF!(#jbrbqf50C*0S`08s0lA=pFgAl*fm%|b!_!fTjmggTJ$*w` zBslM5?i@~=?iwYl1>e7%XF^4WpKY{X&tP~^HW%=3_?nmFn7!rDCYwpVFe~FgkS3or zQ2WiXE~8a$>5}DF<-S$04M@LSHt`-n}p0A>*^xWB(JX zDq9j0nM$ssNU|qH7q(FLEZ1Wup~hY<*(W}AOHbHNzL4`Mlq!3Xt?CmsK%pDSY})d5{u4+TC4I79@#|84K>E1skAt; zK{$R&N9+$qMI^_-WmRl5_Ms6hi%7!{{a9a?QIJ3sA6<~+mA`R3J>}g|dX;$X-?Y7s zt5V#h#J2qzGI_z9GQWHn#%Ou6TeaCVFmRYoYFrZhCpI&J%4&)m7YTO*VSAB1C~W+E z8~(5t3`AEijkWTo)%TAgVl$a2I?t30g2{t`b+{cic=%SDI4@`Og8Tm?s3|4Os!g-+ z8gM!D&mzk?>Kow=qY9Zg-viS+2HB{ybgCA>4r{Aeis@y&j@LMAmW=LWlfj#q}v5`ZwyfG&yEz2++YDvTZnk!+EX9Dhr7oGa;JVua-z&J0y3y;-!v6ypqO9_)*8_??ef$Dwz_|ZjHDO z^T-I@hn6ydSA@Jk;ty;hUC0v!dLf}zcpaimj^J6GG(Tz zGN^_O-f*U5XT#*Q4WGZI{G`~c4&mZ8{)nTbq1q?jb}ui0WM&#jPm&%%q;g{S%5DOL z)Pxw9OMuXb#IVb;*<(R;)(Cnm-x zXcf=%XQ!G$2AfsTE;7x1k{FYTjp7m1oM`rH&arV5F|$Y`ki5@y`~@;}IMq6emHX!m zw56(!=xn(tH9xn62rCWOzj#=$M2E<)pTcBNdw{N*)RlIrMOF&|-=zrfOmqmzt%(#A3|Ul`^>&_viJep( z;`+KDg|d-BtOlT|`{ZcUK=gWbGbXf<+3|A65nND_rMN95dS^V7PvNB4|1BXrq{>IA zrd0f52sR4j6>p6*d=5M8a9d?bQtW&>dn)zo9o(<-CA8Zm5@*u0jQfuz#sRn4uJ*i) zwB1IFD!-*mUD{o%i2)!|CL$zc4IN21^$4YkBD$JS{(McJqpZXy z{hLO%028|uVo8-w_k8D*$gu^6368z8)vEwsV*-{}1FxNJ!mw3n!*%(xG;s55SL6vo z(7I6j<=-$*;dJ);h*S(%Dzd6NwPy-V$^~$^ax58!*)vCe^~Eme4}w5bD&Qt}#y(tcU?F~U7-&Ed8zc}AdY zwrRg&Z~{hUoXG%_P&Ylw$datcR0N;~5Ku#{uIXMgFaedM9u#MG7~0D}XB2(j2E;C%J)UHM{{ZA7_P&ZjE!``W|knS*~w)o2#CxxHYKAWT!Q z+H%<^5EF|5Wp6B);|)N>c<`<5w7eM5D$PiSnW+7Bg=$=cCIhjDoSBs;-Z~H%&bqN3%CN9N$+?1LA@|m4c z2P>p3X;3pWjO$yMw-Oe487#t~8)0S6tXc!!@#q5h)v!Q0D+ob#Rn5Re+1Z|Cy(lJR zMMOMFi?!dt<~9C?wIX}w&#?VXiW$KCws-5<0Sg9(FYkZEM0z?@eg-GGxR)aP7i$*d z0V$8|M`%e;h(o%y-7@6>M<}O{TB;`5LN^iQsb6O z9uJzH?CqxlOe3oZ-;SR6_WtkHR|^D$-5YQ?Gre}R2QCt=xNx5GkoNn9$#N$lFKC@y zPYlHe_c!7p@YSpia6mK~y(@Wr^U(iTjCV7e`v<0hPYYe!9UO^b{~oWl2<4d5ntC8< z+tNgXJVZ>m_Jp$_P%N{bY#z^052Qt%`hK38V2grm^kcEv&(RiUZ5>iftx_i<#0ET+ zPLPHGj|A?@DbN|C;fFg_AHsGSyPmTVZfr{&MMC%1dO!RbYiY4hO`v9&!C-@|qkJpl zr3o%Rv$4auxRGP$u*O=C=!OJpWwl-tAn%H(pFuG_H+GI+Ol+<4RJ+Oy9~Tf_`qLl8 zoj6MRp|nTlVvr^&_W0SiK>&JesLitbyWIio>`gX8|K2(8QB8xSA+M~^ZpCHE7-X_?G=_uBhctyz!1whm-|`zIFr3yx(W*9ZZBfw+827-qItSrMP_RKqX}4RGuEGdch|ztt%>6zA}6P+yVk zmi}ln0ct3idPB|ReH$5nr|V-{9U~hog90)SY=G)yC+jLPOR1Jbd8JNMFAwbsZ`<3V z+)zWj)|jA)l0PhQg7d$(^DmmvT6{&4J4l&1CWnz0lsPQi#Ob-?ghkFl>@#FO@6I~P zHM#JUxi~bnPwBGZfA%Ey90zdJe_0pB1_=g^3@^6ALPht{@dw;-3BLNmUspV{Y)4)R zqX+kHGpVYak-^vScIL4CZRR&$>ibRkA5gDF^=|&&DY!gh_x!ELz!MA zC|WF)FH*tqc1Mm-X=d(UgIVpGUAQvg#mbfwo=P4c=rfYV9cSbi9$&j+DF_C9^K@d{ z!scqZV5DBHV&=CG;f}I4^kyzA*Qz+Lw|INHuw=#Z=Pk-xp76KhFP+=FDT>YHrD*Io z%MSi10&}Min;~LQ8>HSKxRaA$@||^H4;g`*_x`>wO=P{sEAqc3@g~4)GehItqwh0S zc%-ZXgsEVgg4qcCOKK)Q9({UxpdxzkA%ht!zHOW$fjw^iVk#~h@-aq0%z5yZSz0mm z9V?E;1o&B>R!uT!4U7G9cyAw0H;)-QLXFDFe94OiXf*$kCjNW+?~kOh#T$o-H+u`l zvGkg`;iL9=ptTyb0msdm0fg#@7yg3|#umXxdmo-I1dYc&T4j5lkFjrJQ~tX^GFWV% zqo5{v-T2-A(BU1-o0=ZMpOmtFI^)lG&1{MWcLBBKyTdYfu@Ie&A;`N8&$$GG*PxM|(NYdsq&>TZdxo9QVlMgW`|I zs=1_a2KsKo2J#NVDCw;xVZJf^7}UtFBnoTouIw!fu5a&puRJ%A1 zFL=L&Nr3sR`)AIl!h0e9l0qdXR)^#WqgR#mwTEHPAjyEaJq#1ulT-$){qt>gehO=u z_TBAx{VPkm=}|bmgcL!+gCS*a4ZOGf z$ds%gn}K^KXP~Fi$uJOS~mrbdFgBr-nSd{xZ@H++EVPr0v&E z=R&mOQ)}!}%UrnA#}=%zsdj*8B!nAWZcaZWxwrexg*qEOWEgd06Y)9n|E7hDQPkI5 z+oyfZg2Sj8e3^&kH>gN4Pe6(>BhAU6jm{m$0bBlGZ=^mb`4aSTE#ho+7O>}l4VYN` zVVcC{M=d3@BD4f)f<)o*;NVfVlc>DWA>^>zLpcgu-72;?cak(DF%%FVga{?_1N>G{ z>JUPiu&$3y@-~wwV zYFriiJu`U_=t&hsQUMw>`T+#K8`4C5dvR93dFo^1pZo%*i}MN$u)qJ~lcqHza~(lq zD1zb#8qiwHi-S$&7l}ijQ@wa$LQphLFjDaQdtxIY&+i{+WDGO^5|d&{o{xzQA~^;u zuGS8umx!#gwhUQ0?E5BectL>7E$=JjzRGVv*d4o^15*BlRy3=ryUi;H=UHYW4Uj|V zvlI{8CjEhrar?fR*1VafP+aIA`DCYj7z|4dZx{WJDGCEe7m>=mPEd`)%VP(&;&?T8 ztU0f=g0dIG)7w0mM1k=wvPvRD_P$dfYww*{!t6J((Ki}Q1zK&nl<1D_CW-XfUJ8B8 z^aPA|%UQj0fy_-zJA#EW5u&+qX!zCm{Oy3*zZ0H{)jj&;yF(-bA^0EpW56Zbb_@?D z`gJyhL-!k2zz-C;N|SEB9#L}X&Z+>N3Eo4`*`Ktt1q1f3>g8j5;U88KQ!2`J-Bd3m zQkex+NvP>(bVAf%SfQ)q(cg;3&p~TiV&~aI?MX zmR|F6!{jd$aq9iKimwgvdH*7Swbt`th7Ne7czLCqVg%;N)vhKsZ%IQU)w338$p9X> z(W2hEvOT8bS%P#PIY3pR1FCwE?=FoZXwf={OsnyG`inQch>?8v1y2sQg*b>ikw8W> zPGua?5HInDJtIpf7cz-WrlsMKk!3TEZhk5iOe4`WV)5YQ7n&hxe<2xy){%6{m*-aC zIyE8jlIWFw6pf2UlsqHBEpu;CE*PeWRzFV48cl8;w-tBXGNLapzsyL+%ACMM8lAF> z2$+%*`YOgcf4vpW;68DG63dZ)_x^{u@8b9F+v*o8@kZW6Wt*Ybff`ZH9d$DT%1yt;EKE>n_yJ@4bL$r2u`sNUEQLO
Axuv)u9Yi9W6G{gt^fH>vFIuX3i{tQ!|vvdUQZOaqdk{jKU{2G^c^NxI54$f6^>LuiKlLNp-gz~IB-q)fYL`JaUaq>GD(OVExx%$TfmQpYum34j0yzCZ zlYs7uZSE<5-OdHX`JA@zD>1hcA7gVOHwSf*a|5@e}(p75RXvbm(Y9 z7JPF7c>pz}jyq?fYZiH%33_}HEFE~W)PxtY@mN9psn zQCBRizmD07YR)HgUv$GW{3B$r%7rx5BHqw-OCC&;zo#1cBFLKbwN<5wS`T?DH$iWa zGvPfE;7d)PhFlclAMdLkT)F`OE;IsPT(&6)@=;y%KAyl~D zxD6KZ1+re#&hamq>>S)0c491!%lcJ_8lsR0sYIWRQ~pH%70d0lbqd2wYnWV!sPp(I z%5>7oDn**Csu;9hh^h^FKe9^S$7+F7XoS?5x+V)Kirjv?&}aepaFKum$l~CPDG(8q)D_2}&z$cvnxw89Hof4@poB*(u`dOpJGyuz zEfrI$YP?#(8_$7L<(C-6DP!xl(N~M#aggD(l6OE{LCw#ETr1CGUk{Kfn^ZU@Qd7V; zug&6@`*G}q)EMwsiT13+3v|ZmFa~t>c${$9V<&(;8F;3=mDJ?%!3!bq3I917Yy9WC z(0ebbW62jN=Jx7qHuK*BnJcf+=tX5KJqpsBSQ|jP&4-%+8g|W!IIrvsm^$t_Hmqtn zz0LM3n|h|0!XIGXsD{FS^NDxe5sb1_-Kpp$DumC7#lW6_wZ6hhf)vAx(_mTcG6T>E zS)jMoZZ8;ehQfuEV8FyIp3wkmk~dTS@`b6No(_J|wqw;v}{r`bd7Rm*pM zT;@2H+>(C*@MqgFj$wI!8(;ZHr%=G-&26W$`PA1r!16|G?8H0}$}a9iD9_<%wv>+0 zOL!O(&_B*q`w9u72Ve|P+ujPKuv69&?F!rMzYhF4pN_$+die2n-7PRA+Hruw$>PK+ z=Aii3G6z3h=m!9&Fm|kEyjJu;Y)g?@Tci|OC`t!~;8JQJ(-P~&($WP9eaX_KiwMZ$ zwuPgP!5ys7z5V6s+3jV;5r9k_6^UyITx9Ei0MS4$zjrxP$J%lZ%C}eKB2e;lo8t$^ zNbcPt&e5NplNB(a97v?x}bYQm84 zwzs`a5O}pyWVqBlJ%xp9B|?Zo2Bx0T(7C8(IHY7vWFh8}88UnFZsmmx48zkD2*$K3 zXsBuowdS&ba>s@Hg%L_ETHWgcnRw-pcxmgQ8&BwkOz#LcB_lYbeCBSTYY4lwmXUvcf=#&!9Lq)!t&he4I!7hyY$vlZkE_xx%km|rpYz$<_ZE)Aw2j+ z3@|(Iz$mgmUu#R#no!h)I7BA8oKunJ!309YZQi}KP1LtNsD zd}=sXF#UKEDms%d5=4k5(luOQje51*b6ys3R#%=7c7ARxn039mQ`C7HQ0o?FqDWNh z8b-6JS5*u*=ph zw#g*w4BU|Al`WAb9&nM#&KQ$>=0NlsuX4Qz20J|QlO`aM4RR9n$infy_q}hn1kM`_ z(S*2_h*$&~-;O@|Xy1?2BK1UgR2AO$*>zVtT?)hNmnwtaevf&K)c|6twlRZvsJJR% z62Sn!Mv8O$?kTc5GmZRCI{KKe7`qSN^rt}jFM$#0w!Jx90PL}NUBno1Lf|w8hJ;|y zhl`UUzcX9^F%+grti-a&Skus8vCnO4NOFXG1|So5r*`+~au+>v63Dl>bF?LH48iHbQAZr9EyoCC z5-!(z+ASLvQ1}4#JLw(4@=jH}J=n!@5eL?sT3SgKp6k%>q}wNz^7h!bh{)-HR|!YOPLv*W@J!->HwrizzIhK?g4tQFGzZpHW3mI%G5?KlV?%C=(bI1B)(2?Fxo0kP$nEvY^hjKpG5Q9i)$B zk%!msx!~2bq{;wiQZ(Qq#$u>-Jaxutr<>`?M+r|8Rp~Dv@Bj$1)489*0n#S7ceYLH zK1fE$@)eGVgfLpvu5Z7S{@IhVO-VNyfwjRNIRPXfV;NDw>5xpFb+8CDN)!!us}h8! zI%FAGc$Nn-#(<$o6LP+Tv99X!{_VOeF1y_FOTeHf!P&JXzAx>~xo3VarJ-noF_9Jm z&7~n)1(U#l7p@dmBwX@f6r_MBN7uj-eXx6f&h-TkBNw+cffs&Wkg)oMCkK~n#BHNI zLa)WH(dKv3DS)d|ifC4fy?%U4)!=v1F%Bd|6bY`@*?%W}5&OK-HGt+@4OoHUx+`-2 zgg2v)+xWKUT;-BhRYF+Ym;&kIXD?RkI^P>kKy=3H4MZj@qrnIRQzlOfWZbBd6* zex%J$Lx=M?NAEr3vgPx7@lrv`CR~s*+4T#S%_bQp@8) zg#sN{KXxQUVH8~*f(I8EAg8t@wDgOd)B+#{wxY$jw<(B4Oun_0y{642fr`|aF$~Zb zWx)kh)dmPKW7}&s;d+Tfd4bszOJLS!d8-Y3bUwlbDRD7RS1E6U?dWCDgC zammegCV&J0jG=trhwppR=RTX5Joa7hyyyoPJ?SY=7N5xc*!tGnZUYF6x5M{iDVbW4 z6zN5J>5ZqT~?EVT0P}iA60uWh@*ab%C}9OtB3h!9(~CGtKvU3S)>&s{;@5x4Ma~u+33l}|R**47 zj2ok~6vw0q_JSmb(P8B1B>)$y!U&d}`S?X?uCgn4PsLu|O_#95orDN7l#pp6q7(^< zOAu%go&x045P%(wU5_v#ro249#13%6X8DE@3sDFnL6t;;qgO{CR(W7+ACCfC1fkaj z!XcxKRwNRnz$q3C&|+H*JQg`hwyt0%b%*=nL;x4)PHY(KF&axUIQHIz-I54^1tnAS zAiqFz6Ic!}hXEjq9024^H+S#(s>0~e5~G-dO2?#DJa)`(3FibJ4PxO<0>5hxhEEd! zcqx-c1wYBEXs#iW!Fdcl0SSXZ0${=Dl1WEsiya1#T%!p^lpa5Je(q?L*k#L>IZyK- zjHYQq8hVL($pHg6nB)u6v;-~V`U(&Ft0*O?xQPo`IHNp(5n^zb=PA+1O)wnI#oaX4 zCspxpYcvGJTAzs}RJdTOISuW<|Nbw0(TgnQZS_Q>h0dQHeYCL-4PpYRk#^-og;x{8 z5{4!)WNN*XOpvJ6qV7m&qNElngPkb)RE?Yrgu`uXekKa9po=#VdSY zn~-RtA;Q6JmlX$?)RNYc6+8)F1gijax~@*5Q~JOnv5}`dSedzFQ|3^m|BQtgQV}fI zE=r!0dQ)`P0gxjB(8z_6VM4B!SfWDXfZN*EeKIY2c)`9(Et*7wwg^d!+`w(}G^#mO z;KTuz(k%+aTOklVYE9%77TEa`uXf1<+X|S};j!Qa%jskFEruKmxaV}(A%`N+3pxFB zF~m?BZBv;PQ1})uFp%J>o-u|hg9I1^2H_ZkFak|TLPGNZXlix$`eqTk(Per z^!x-zK0TaX(CdPR(E$uVoPGhi6FGn=oI2r(S6uDf|9BE}R(M1ynkZ?TA@c#eX=MlE z#Xw?-!tN*zefN39(b`KBc%HJZQMcNhR|Y`Kj5gCc&&4+e9~y?>Mb=?2PC~5kx*BgR z4p3llsX5i%4mM3-L>%9M$t1*K!ljp98pkA_aADB}009>I1Vc`G(W)29xI~izqdQKb zISl#8(zTid#Cneo2wW!Sx*_c9!Z*?q;$gQ;Lqk1}vONzV2b|s!pu`2J3pMJ?2D5o? zQGo2iYm62LftmD>7y5wF3d3LVd)y>oP@)$V2qHvN%&SR&B}mvGu^J^$d1b)bC9#wm z8FtDfyrYRGEb!{Mm~e8mq9h;8-FQ)^QvgjON0Uor*ma6nfdP|1U^n*uu}j#~mtzLO z596hIGCTPXFTIaSawx~+2d^dtFy0E&<8p82H5tzlJa~>M z1Qf}HJoita)AX>?E59yrxOCee*4y~S!R`^Re_o(SDI~@g*om|6)%Bv0!)0%^c{(B! z0L%~WGJ9(tw1#%c3o!kyXZ|9uqtO~ol+?1pFbAc*13X zcm){@A1Dbj7;r>!XAw)9Xu}JL=Q1udFp0Ae!(b<0jULi-TEFlt8h&r@9gobY$A6&yI`z&yUfhi*t$f@y^`- z`CdfJ)h^?rJ#dg)AA96cTrQEN7j+~9Xbu(zig-xZrT|k3O_YqtrKmu!rU?Mv{9>3M z)u;_KLv}Z1u#w!6^>;)k>%hT$sR#9Rhb(q0) zEOy9@JCDh;-fr<>OU8Isy6(x*Q~*$t6nO_s2)D{KNn>$3BgWTgc-&=((Q}{af|ET(p{R@B)2fEiDs;u0xYBh3kMPckeMzuIF?|Wk;G+O#!WBRhJz*3# z1^E~ujUrQ-))Kv)=KTV$+%j&Br8|1mJKjiBcfwtj2t1rdYf3044(}p^ zFKR_6CMZcolS_s97&Knjijrv(0pR4A+`wdBe);9T9S)h3%q;EEXtek^5GbV-P90*G}pA|h43|(xo85juw`vF3lTx&IByhb+DDEwmiDpyxXhh{foGaX#?^#*08JQHo2jenpoWhD@+{ z(-dCp!unWr{9JcmYg{1V*f-1vxgE8`Z${9;fqzv6d@9X zr7uKvP?^u7;6kZrBE-i`J+8BMPuOVH5jg?_42WdJ;sLBXlM+0P8WNdL3i{BxAoGT&w_TFDoLWYQlm&GAB}J2RoLV>DNzH?Mkmu-uX%gGU^C z@^Ov&nfez^A*`!!Z*dfRPz9Td3KWEzA_Gsfo#q3Z5JoWzb#M*!GazTpMiCkgYuZH*b5EQBcKC-W6XLwm9cyw zo{t}d5=KEoGBQX4&6w#u5oufwwP7^*?vINvzBs9r&h*EtMyW6F%?rCXz3EMeJ;U?f z=7&H0;V3q4G%P_52%=bx9EWSnxa6&bDG>tTbk34>)U2n5WNI~-cOYFRJix5vX&<~+ zpR}{+;W+aSJQh~sar8b-?Dst)C+jm!E=4R#ECK+4m`w4BhD-!%Fu(gqua5(}1bYf7 zjM0Pji!7>w70PRZN$)VZWn5<35;i`7j0+j07|ZPg#wDB5$Ggz*DBt+THzFfM6ZQl! z%J8NRqdKJ^3jhx;WklHib{1fk7=mp9<^jPFhpHh35+Ia_`2w6;t58ftp#giN&GR`+ z7Bu{ocuoMwF>ia@+XN@i)}4|suF%7a3IMSHyd>+0SoAPH`X$1zyY4zHJ_Rozb(#`J zwI=KYilXQVCWI)I;0)&%e&H8u=Q*P2gTUJ^rY%p}z&au@ zy5T~D1#$Gmt2KR5FA?C|F62uD;Z3Iz0yZW2=x0~jC7JDj3PBmiI(W>-T3;z~fce7%4fN2@Tv%hZXe$VbMBVupNi z1r8$`_j#ldi=A6xNWiGbiv%W&@xuJ>@BXfrJjqY|;Kk*Va6m#uNJN@Cc#HrnpStLXqx+)7x`BnZNYXOYQa$D1=}kK~qGHWOT^Dx>gE4m#GV~05xSnt`b}Wbo_p@|oh&ZBfa?PWd-v|Oc7cD_yM7n!lWnZSUVgL*%~@xi z4&Q*~YZ_uAl%)hy*M~P93X$N)B^3It2N-PJojWJDQ|@ZBgRgRZ)hjRZ z8k^QodWrGki!b(R5c7!e<2!h7b$ib{!C*yo<&{@@*EiASuJ#K=+~-SnOaZ*_hF2rz zaj+)Bg@+|=_l-(BcDTKTjR*BYQIT&P7^sCDdSc@R(TGvNc$3YGyS{hL?i^E z$L~&z0OXsmM2pzaqr~aU9i3+qC=7HV0N_Qe?|^lfPoOCEc^j+zgtzI)O!bC0yg^6Q zGf5)Gh+SmjwOJIFbo1$#H~-X6%5#uPLhFhvuDIcb8|XkoNb-GAk=@=u`zQZYMvqoK zc{hgxO5h}@$|YbN6XXCtxU40t3|{s=j|SsP#3JrPe$r}IvcHk`;F*STElxd@wSV?4~&x zRMO%<9r?aQj=BE&>m6!IIoym3yGI~CEvF{UdYpp+Sz`0OCNMGzbEGFUy0cv)fqm`p zs02f-&7xiu^z?@`*%`j?yUP&NZV8O0He>_$=wg5wTp}2!38tSV@|CWrpmkNwz3 zKJpPU!-<+0kW4VZWaP_De*M>fodOb=fD~+WKww7mE$8og*SjRhfAeqtO&;80*UH_F zm9S8NlBN&z@QPyq@$6CpHV08#N7N8AqSw$wGBSPLa?356U=YWr1e)D^I0w=w;GO^b zB3WO&iA&dppk^*L;t~lHqh(;>LJX*z{U8Lf001BWNkl z`~bSF!y_OXLG! zOoo>77UCw=q)cl&r=w}IVk3|Q^PaEz#G;4|%O(N=GAjk-OL%&!QQA)MCV|4qtZVM_ zZP7&edY5EkO(YeX=r;S~91&pAJtoa0Rx}bdMvCN2>kf{R6PE;b0U_a=zzdysX2i-uE=!_>pwqe5lo<{&UVbhk+QEy{r)|z1{OZCHPO%g+|QXtE#7?`&qP788VT2Dtq4%OfFvaRR}Wc3nJf^&_$21&aqkqC0(o zF+w^>fT4^Pu_P+TpqJ+?%ta}ISOh#4J>sHdKT&kcZB6=xN0wGKN;?2D9UHh=bYXF3 zEqK}YzWO(R>%Y9?zx|GFUqYH3)12HUqTE4Wzx{Uei@P8i0>qmQgcsvo`#fMEeJNECec1K{hWE&(8 znb!PbC=XgC3r>fP@(^AayrM!(ie53`i&`WtA*6}p#6$c@nrM+Y(TFr#1>q!Xo_+S& z-XAfMn(G`p2}sXt1d=&QkO)6BkEkFd5@?>N0|*k7QNXn9+cHgPf*+SyB37&2f>q$j z-*(Dx{FmQ!NmZ=?yoy50xhKv3Tw?7Yhe;L4$oW_Q>R;I~F|jaWP#Ad2aG!1T zxu|sH8!-TcYhp4!Pi3n~6r)J?mM|(r=O+fF{VWWXNiXRyanuOWl#IUu(VYCpIRP zM8L#2m_)uEak;yUoR_(YNg38*k(H)Y2kCrl6P&_+~JNZ1sDR$a9xnEmWt@C z#H|1{CBTT%kn;arfc1~ zM48~E$vmZwy&9f}cU#f$Mecnw3~Mv8W9pZ^{N=t4fODC`ddq|(ftmMM(#*!u0F#m= zA-f1vu>pyf2ko~UO`B^b#&C zO{y?pAt7Kwkibrm=qTR;>JZCuC3*`%cM2}VN`&YIJDpuZ>i3?z?>2CFm+)Gd1xBXF z8=XrlnmR~kUxs%;Q?suXyo{U|ZK7GaALz%X!LWsuxMjGl57?PZ(vQ;~jPhTEa7otJ zPIB0bGq6LNT=ED6FM2dQM7YbM0||8*aY&W*=9_6$xa@Sa`!u zCIFdeORZ~20$rNy0M6OPgfB|RD6lN4Akk!u(H57>NYg4iif@v^3OxD22v(O$r$H45 zA?wMzDy<_5I;-+8LkSE>HRDTKQkRFm*M8;8 zSAX{EANkRL=rhWuFHW&o;kyKbr7#fy)WMX?(7O!AWq=+roGpV~eZ7pFH@h?H z8YC;TxWV&~6}Ll0hcX42+P{>bB=O`!+%V#KMC$MSf;37>C-P zuZS?u4(-q-Jcmwb#Dra%K%|M#;bz5@L4N|%HFlSrYGn=Ml9TdYpL0%q`Gr{L23A$S z{L8;g2chQaBi#~DFw{V%kfRqh$5UHhT9tH-Op{HgS}6n1WnE*bFK58>Q_c{w_)W8i zX}a;i*s+1VxF;F1AcJ#(CllPnn+YVV4bjC59B0TP5Dm$0Q3L>7Vu07UGCc|gfZ78* zPdGeH@ks`W(}6Y#@bP9M3M1iVHsoKti3)M3HT|YeM_^Ivs9*M_ylm}Bu%ie%lcNi* z>MpkWm+tgPZF8tO%tVPS8b&yby+tS!pA=9?PzsR1>sywp(wgg@yj4>z!0e_A%>sro zbNdTC*0Vjb{CdVSpXnVCju0Ml10R`faKVU4i~`Rk-h>cmc~2zLxDp^R;!`xZz~~)j zIQo_yy#rxG@03C-Vg&Rd*(DwHCEfz)3sOx2JtN$ues$rFAQlphPgOac@)XI@t20u= z@i+iYl-G-bQuqZnHr!LD<+6rr`t^WUoAaWpnh%(g zie+_RF)I|_^PcxuDO(hyab(O!9_BHVOR&QzEFSyC-z>$B=()lD4~fkA5&#*?GCc=` z*Q!Kpd0cFrVmHR+G9Q0X!0%Vdg+5on4T!C(WG}FOmH3HFVJ8B}1UM^m^a?gHdB)>) z80X!VPTqZTR1FYqGPJ;@n89X#p%t;zR_ri@APKsN9gM|J)D&jZ2{WTmQful0l*@vt z0q}Mxj4-iyImODx+aeZKmZ(g@DUN`+$pM2)(a2Ly8bnuo1j*@QgGU69a0=cNyUYT7 zr!V0_6dI>h9_V^c!2-xFExLG_a|M_eL_hrD52qKz9&%{nZtUVSH-c0|px8^K(aJdo z0_>jj{L_E_&-^BXgev)YbLxicua`vif&e>dYJC9$+iU6$jqVh1T8Yiq`RfHHL2d}R z(2%A8QtHd#gPBG%;@d`li&+L^X7hvxfSrX0gIi8{^}~|LL}$8-&Ch$`3w&3L022w^ zflCnbk@*2v?Z37pHoAt=0S4y-ok6@{1}x-t;J zn61P)z#*Y?&X9R#>qy6X+1pAM6avL_SH8?oEHW`(F8_(2_z5oYUbuz4gy;DmcCCya zfXfy8ahJAVGL7xO7w{Z6V}ullW;&Zoe)U$mVa{}sM(OgMTifL2m_x<$ZhGt2`@`j93lA$Hg1CC6ufbhXK9}Q z%R1ccV8`oi-s&b0qXiA03fg$q;g7^dl(-URc{$S}NvMNOMs|T7V|eAt8XSwr(My>u zbYzsVlkkEUz7V~baav^|XqiPCm2y+y@*bc$ zR5($USp=BA@|00ZMxfBaNYx06tpHdQK=6as;33EHwUF%tPqYIAvN`iA{1O1hB=f-! ze88ELS&nCgrFD#xaBF2-G)ryoWLlY7IdLC)1m*{|(fMYgBy>kx+XJ&4GQ24i z(5iMhh@65cKnTKEhzG3&zj|PKId6s@nRJ9gM#~V)>eZ&_Jny-G=lj3UchmWHwTB;l zxXLQVPv9Q6 z=Xk*De)u2!^#AdXe*HK8^Tn55;zz1za>mo1>PrW-{*T{#?~|T<+V}t6AK+s%n!HF9 zcAd(mPQ5Jcsg%Idoqp{+ywd5kO=KIpR(La7-pB$g1HT5Ddc3K|fNSEd3QrtnZ3hES zfQbS3KhK(6VsN*d#fo{sBHB>_PjQDZkP_Uk@|jj)Nt*SI$H1~3S4v1e=a4mcc7A9$ znoQwjfA40NZ_FCryCceynFRBi#hgW(HL{J`<(FUXZDa~ikLwYrO%rG+gV-iO{8$B2 zL#uH8m0$f;E2kHncb=CN^kPq`NB#cPn}70}&wt^c{NtbXC7(|?;Y4-5y6}YK_gwNl z7k~U?f9{JGy(yQkK>Wm?f7~LBR{GVukkcvs>)EG-ihc_`4VZ^!8t>M|vkb#BFWYi1 zX8{DE*m%+T=X<&=gy3TY*}}V;5{%iy(#oRQD=H))u%;0bJS8eDq_j#iR$!(S^PT_@ z-f>_2XvL3Gdb2Sw-3c~Z0*f&7?OA7?Wj6`dmyBzTZYGqs2odm94Pa#^Lag&lP+En( z_nxp5DA!70rdc|exc=l%-+%fU-6Uzs#qNQQB^^~2${Vb?*VZtHa4zAIIiknwU@SKd z3*-)+5le%n*_Ue`R}=E^ppOhdB3aU`|9am6jmrcCVn8U1>3O=erYU6Xu1U>yB!u4m z%;jKsHA>+X0{93gBmxs;F42$>JeCBF%XljoFyJ$vz4}!bU3A5jS93MC-W|M^EhW))_mX<{0;7&!$A z&Sj~)ua>TIWWg7fi2yPxMRUrF>CN&x>0Ux}>(XE8R!IHvj=BufZ~_PDUb^p#Wd~Dp z2b-(WxP;_K@dKk~5CGq)t%Bnnl<_7?MstZw6RGhyj0ho3j|yIeqZI%$Txw)6$kas1 z#JVsXEP#%>r;l)q^nv$X&)7BTOQ%}l!QKmiCr!!S5p7Z0bP{gsU>U9>t)^NFJF^(E zB%p~tx(1|xX{fkF(|yNuUxG>9A^KlJ_qQXRuOPPh*kZqv{^2XHkN_AUb~DBT@C!R@ z5?wqMTk#qUn7@P*JTG*P*x!X*VlSZ>0xn2FgAtsN10Y7JJ3`DVWCGA|bYVdddcxBk z`oMw;93_3u$~w&R9hm5*yC{Q!E}N9s9U4$4}trzDd>-anA(M&zi!}wt` zW=gO{JRRq0Wk+1>0zkly@v&MfjI@fK5O5rWl924B2xM`T3KpUeYjvrPSiF(xQb9u$ zGC+7GC#OR`sA*9KPo^5BIAQ6Oy8p2g=6^z&IC%SZ`2#?UKb-EN7 z{V!bOC6-ELQUI~O(jyoU0v$yTUIzxI8=vh%DN3C_U48Y{Hmo}B$JE^v=?Il)B&>P%ID|mP-M^p)Pq%El4tH#7AJ$3rFAr_z{NghZp)q5@O!uGI64nzY;m{ z=n*3W1JWbOE+MrNuDDe5CLz=S>n_n7*NeSyi4%D1Y@4}=O?SA+4~)e?kWoWH=C?{5 z!VwFP5>k&L24t9eN9hD!$ay$L8VrTs@s4+x_w)`%5G1YwfCmMo5@diLaR@n}U)#&M z66ri$GE*6bk+Ypk0MlRxGqjpZST<&L8H*!^=T4Mqg2C&OxkVT<1&x{<-qM5s3x5*_Fr3}WKm{BuP?$t77a7{dDiUEi# zk%L{d5Cz~Y7`!gS>k=FS013nbL|3FC5D98{g-_L*1VGkjWIB+yzVYr zow0YS^hyRv0heezIP|qLp5ej!Vf0Hcz0{)wGD(2Z1+qv;9rp2BYX|}aMpl4vmNmf~ zw*QzYF$0KN;6hV03(}?0#%eHe31XH~rwLxEB}7Pwmw5YMrSizFO95Ry;LVib0SqoHAnrjQC#ClY2}~=OYQ;~eQN}~Q z#~$C;oeRmYe#Pz>Kc2QTj|RIlw*JSoy33u%{JUR!s}JIM=v zKb>pC+x_L~?h7*fW?ql(D3*ZkPc<=xcWc+(xuvsR*6t2EI^!rMZk&pA&SlRoD)zHE z%O5<@tyO#K?s+W=NEiUe0phG)kPz&gD|k>!U*Hl6V8XlZMgaiG)S&K=BR#NT7X}>? z{jQykwXgcrC(kgMd3Zv^wxO8iEbbr7qHh z3yY5c2`YHXcmibGCuX_>%bnd_7Qhk?GSWH;ONDSU`X62#p`2%q+Pkht=oS23v!(M2 z`d(X>Za%3j5f`YLr~8?@k(B^RU~C#wt2rfs6c=~?}OtQY+VoQY(Rh zOBWqWe^8OydcnlPAiu;X6b*4{CwlNe zcjJu0eksUZ_ZTV_CA;BRf%VHWNB1t#_|6AkT&!QcBL;N%{vPS3GQ+QKC6^si)!lbp zOE>JUblC1v40lk^?_zz*k#pXP`!y6MI3oLirZd2lG7uQb0XxdT7>!K=GS@(a3sWY@$+c!0rWk-2Sw z+E%p@ilJ)cL^zb=a)Qx10Lo>N1`{DmosN7R%9_mj+Up6nmE0|ylrmo|@%Ge#YxcUe z2n2+t#FjvX^ieG}7B61mRxr|4Hgq-J#PRCJHe3``cat661`>|lUhxWU?{yGd(YZ6A@0}FectPcr;TP@%xuiknb>Ti z&DhFf3e98N&SaxS&&1&r7fdA6+P^AP2^mQ%j03UVwoIws9PH+`I-Zvzb&1c^CiW2I z6cj61%yjeLsKAWCLw^f0z(oB~o7P0Nnn1Ih6zf#85Qn#jLJv;q;BGn|#a^#NX@Wm$ zWesv3U8!D%T~voG@I)X4@ybMk0dhc@9;fx9HJ;`hb5P(+OvJ({cohIlqz!tUafxwa zYs6~OZ)kL?_nNrtbyPAaR|ra7fP$>qw=kT6Pa9xbm+PrWnpEARse^(FX_N~Vhb{=%gfk8Ok#z>H&c6)}g1M^b`O2M9WF!3-`( ztlRo8o5U=vJ<$+ar!`HauaSjuyo=IQ=*MYF6XMW^rr-f$LmWy=A|#m!T+ct_EVybU z$u4V5EkFHcA|=+#7J3<<;tETV(*dLP<)FvIXbR`L0n5&LIUI>T8UGSIu40nHyVj!xCcIU7)?6y9G_`Yf-gWWmo}1v&bk%yaH0&OX^CWO za5W4C^kU6ryGe%2T7E8r>FYz$R+Ei&zO4~UmYF^!#m)m8g^R%l_*@2GY+lF;rpSaT zt>G;+;w@wdc--b5Fvx8KmRyFbzy(;^(T>(blHN!FD*z4yXszX^X{q1C9ax&shoxXb zuTwy}18B|bGr}nJK@PZbS&-vEgm3wpiE2}Ti_z+_cVzYT7+egP7WB#(Rfm|?#YEyD z3rpQgLdZD}q`5%C6Kv&Ww!%07TuxK0XdD9BP9Z!I8^&IfrD-&hYx!E0g3o0+UxKe& z3t0nVnc6Q5HRE!I#bV)_H^eo5wim)%(+s55O@t>-1PQXlN@fO*|6SC#W|X9)p4FE!7?r(a_V}Vrp6HHvP2lM!2r5>YSaaqlt?Zx zz;F-0tUKT0aGX+XGHtYlY-tZPIgt&yS+y_@+eUsF;Cpv;xWZd7K@RA-SmAv!mU$<^ zR>;C$giOY%%Vp9YYl z2tPp%5cB5M%yl)3CYN1ZfLtD4FVWPT>kltG=VdUfS8SS`+6QRH$wDxW4<@bZxP+cu zu8EVlCu)~gcw~@*BS6>>8{MS)deD1)NsdBbga9n|%}w2l!hJ!UQuhT@cRIMNo3=At zTWpnfC>AC&?jR7X0AM&Va5}2#r(w)BVk}&0fhX$f^)I9n_VGGhbT8HPC6}dNKwuF? z2=(-($8xq=xI(G!dpk6`(qrWG_0nKVQv-?xSTBYum{kIiHVA<6UXax^104pJ<0_eL zq&;N9PlPB(WGO=Cp(If3g_54>ZV=?FLt!KnG6h#P$W? zyUomJu1!ulbJ?-?-(lDRi2DlC`w? zVLfI|N4S1V-ko2<22$omDmSVjWFHgrrjB}=085yWzdC#N$Hz70#Ni-7td!QlW$zLAeR*C{}i&)X{}^wEy!Th0dQ%`Wx*_6_8ZB9 z3}xwMen50_R^((XJUB4g{L&VOGNUjqzH{eN3}DR)4~8GuoH-$De8vJ3AjSb_HB<0u z?eJAR7vj=d1G8I0$uGH;5bG;ymtJaV(WKUD3DfIS`YH++KvQW=7}OYKnZuVl?d^b| z!}oCNXi~$qZu7&@r09cDSk9jMX63vHiq%AOw#>w30BN13bgjf-0Ml4%ve48*rg0UT zX#~^o>8`NXy&n-Kb6o5=rpO6(S65yiz};onKAOb~^L#+h+TDQF20*jzy?#r z5yFh-F@hNfvL3=T;}jVVq{X zm(Y~DumDnu9GBzlE5_teC1*uG;aec#84LR-mm5xekz1*@-Z+dhb_g)Ts(5G63|a@5 zwb)vR8b5$DgaNX?tmV{-)%Y3?Z^0BG_h~M>?|&FYZ_euByQX#6=b7%7)E>DYF0%7PhOhOx9xyc`Ih=W=2%g;964{x!b9fTC3`t>z704Qa7Znta!G zkrN@QgA9Ek07OoWnGJhijCVql>EehIrsh2SVK$BOx=o%9zPe1K1JU(JYAtKB(A1ck zGVtuyar!wihs5?XW8n&(VuuJy+f1OQpU7U28TB+_?3|{K%>U6WT!3ke1AJDav`R7% z2Y><1B45T1iQ4GN0(HDW);2Gf$ZC8IuV%cHmyl>eg{MQHP6$e5BCC<&53l)SQP|2bc*}Bs`c*tnfM*C|588eB?w_G!DVQh-vD` z3bal@)j@QKKJnSaJ_}hHS{dv_`a%W>-2Lg-=H(J&ha8>=5uDZgM8UI=790^xyIfDES*;EECo2Y*)=Mp&_CFiq6A0AP>7f{DNPWRdQA{4-55>mH6*; z8z~GwBi^)zx9%mFr5)bLB%Q#6OZ#20Qg};uki`Ok%tvW;AEPg=b$65DrEGWTT`;5T z^4{okV3WBv71mf7Bwp*j{e($9_3vlWj3efXsSBn?mZky}10XXW7p{T_)NZQr<8Z{L z$MM!_@j0$c&0DwD@@stP>sA<-0}Q07f}~-ZN=Jw5ycBzd=J3|SbOazBbrVJZz5l4o z9XE}0YR-!)(St-U6p)oSf+ot+TC}fu%SZ|^k^K;|e-q)*<&mJ$>40!*chy-?SWpr& z%~C~v5f~s`gHC5$cHfxX7c`NXI`c)~)g=7njJ}7iG8~fmYlcb-)sz@Zl4IK((;Ed? znW$=wFK__{EYtAG0Rv)})99zoDYb-nlLWxT+!hDbECmkNc<+&EItfAOJF;%6ur#>@ z=#c%J7fUQfbY`g0F0DgXIkKXf4pzK8WoX=FA&fz=1vBkFnl5y)TAX8<>te%<<-q>V z#0kY(*U(JEjJJledv$J7nMPmm#hV2)NzBrL_juE6F7!oyctbzVm3an@CM>A|CC zCan|eNG1)SBowZ>=9-Db3yAcS;#XdIrGaPcf-SR**o(HogN;s|%Nm|NDD)woC~cg# zbT??Kg$$Y|UdF%UO_?LcrOoblLKNi-97i1#ffMc1wRWEr9hFhMjjpHvt+HK+qQ5s; z?DLEFYpRRI8EjXM9t4qB4>UU zloPq?+*Z%e<&2^0V*c2hsSv*~*Zue3-z~qYPYTUonws;9-3cQ1-~QWw8=&B42-Ora zz_?M4AYt+{XnJqeEj+v5B7y=%t0O2=cq(88EFE{wNs1iHkkRs|;8qm}5jYS#eX(<> z)5UVb4B|;(z3~; zn_`yWvK0(%p;+GY?swbM*|r#(bl$8@6I z98*i1-MU0s&*Am_d>DSsLM8r6*CNnZiVbVc3p_RFg#!QOmtUSW&R`BIA{G{~Mw9%b z9xKq_78nw6^UXK^#&7(_GoJB`pZv+6L=a0Y>_Aqd7~j3{g)bzbghICetC6`%-bAE2 zD!uv5Z@%oZ%Vvk^Dtm0~Yi5W`+VM;yDpO=WBRq|40r9oMlAmms8xnl+#TS3#6Q5wLFMa7t z`9ZP;)PJp1N*r`Y-lHE1Un|KgmLsSHYxF4rwqj|GaYHOn?y2Izd1jmqQFXl2sH!#! z+1lhFAOYi)4YT+bDBd)~AJLCT@UgU!XfSNAZZ7`dYYn3?6F}x87J=`)^UlwG?sJgR zz+2w(7WN9h=G`)|p(PF^_@sQ}RW@pXF*Qx1Pj=oLrA+I0#TMX4_f!JO6v2-x7PTRa zY zFs`5d*`NL9H@}%Ro%bdldg!6RM_}; z&g?l&+~VNt+t1w?E4VqmX?EtBXL`>ks*3d={m~!wvex2gzP6gM1DCM?-zgCux2|nR zb4pA6#5*s_a zpY6w~@30h3f-+aCll(SzkaPANzV)pGyKJRP5ug}R z>J1;7C@O(u##|7sHryxS;xGwM94=QvqhACIh&?3> zXh7x@zzp4{=DeCmWY_P5)J=;F*LPqd`?r7lw{N@cHad%Sg97ZmMOa)-v@Q&UMuNKp z*AUzxIDz0!fX3b3T@nZy+}#Pm-7UDgySuyJ&38`j=%4?S`|N3V^{(nwt7N?b{hHEt zpAyO&Ilb2dN7)hk=*bQ>>t6?*hZm!wU@1evNh;P0r5Qim_y{+Y)mq7`%}E79ib_T=in>n7%dXfd7hVM0@9mg3D5_GLh|m8Ak9BiQ@cLg!Fc zw|=RGd3&cZh8nqqGzFCW-=pu>aO0LTuGX(cdED>>@GcxTe{srw7%Y1lD5Gd8+?^+p zia*8XX>o=oj5hDhr_e}ra&!_Z3 zX*g$4ug_QXYq%v}&jL1yZ%!1(1j-AG&!Il&jHre``T}rUE-QmQ=asFnP5*lmt^DXy zE)D76hrY_N#JqZ++N$?Iok#|8q=9NJM>gRl zWRJ%#!g)^aj!XnBMmanACe~YSS02k=uiL(249s7#iqzyA`~A<;UEQuS;?_&-;~8ch z<1#73Iqc1qaGWOQ-WrdXr!+i&6}mr?|E)^!zo#7$!E!q_L2pnGu$dG~Q(UM6T&(Ou z=XNFRbzUwK_@*b5m4@+5vNqB2^TC<_J-QwA<=qbHxz}%z)dm=zUP?;YtF4$zq|vF( z@=ru6$)G@)Z1sU|EtJasePszsg9B)?-@)e$)`z6UZ!n)80?`3Fei3sw$y|faOmUKz zc%PWlw37|te;&Hxw~oi`V(M@h$51D2&!@+7^clLDQeTqpP(ZhWuK+XXvM{bkI!8Ue z1x$;7BW(OO!uI_4fZW6}AgB~SdVx$$%T=M=DQ@E%3GyAs^r<`o$m+? zcas9olN6%da*EO~RV@zC4}d);nyM>jO+?DYzbLL~pIVhzJ%Wkum*v0Tdb3m%Y`;h% zFh-%S6Ixi=?#31`+4g6(jn?_KdNgDD!1=#JUEUr)>_+(ukAiElrqWERs5pKG{e8#c zG16H;Cgi`iQ)ZzwZV2Ot52N$l{)NtGaOQTi6N@Fr;=ggU#Wv-(S=XGvH2>c}M2#Au zI<-#rrbYx>a8FaC0-zg))hU&DXj8V+!fQ2PBZe$wd1>{mYH z)dv5YOKMzi4cju9y=VX5ZWPCW)JHU^J2@);HH=^aG>kn$DUi)&rTj{KE68y!Cjh$TrN zirUAk+iM5il78ei8iaHANK@SNz1(ezO>UHwSs6jKf3OAKPnl zxN$g-Bm=h4yU&9@_l&nk^Y6^?7u4jcSL$*>Z~o0~fP366uiRe@gX|F|jlYZ0t=2yl zFtO*Hxfx@SS0QBiuBU)x$nJ;WtyEt^(*UM>+mR#Chkjy|?)RLsE-D({Xv*J>uWont z3=AN@qyfc5P1g;^pVb=LGXBjJNv?8)jQ5ANPH7fBlbY09RM%bHZgyRmQqoMXR%Q4w zMpyn5yS$TJrkQeU;lLcpYEgt8p|~Atk!`o6UGZ1mzl>3(0VJp4la73!e^b5rEn{d) zu{Zw92E*1s;^An5O?mm3iZ0cGFS*fA@BK?598Ew{Sv(@L{J&{<$O8D%PK|QTzr}3s z=&jut@k0vG2mK0^1#4n)UNuhZwSS$u{|%IC?{Ap0i%i$3yPKZnUu#S8sPht!%-W~#E- z3Y*MYW`3DmNOdc{&ECFvaL=B#eUgHWAL*r5|B`QgW$H&uUB{Zz6p*n-W)liEHbx8u zbuuJ4+xk~`eV%CpD=X4;VsJhn3NTcgWZuAXsm!NGxIIo;njNfuY7Ta@5SguKHaM$X z6GR;{mh-?9TMZR>Q@1OJoMZx4zHRJfDCRVORg_OIr{C`khNpuD z!|J4)m_G1q;jW5X`Pw(9cK0Snc%ebFun*3TUYb(&0&kh106o_pFh$1IxL*#YW4cx= z6Dj9$zCAhN9rH?YbEpcl%1+-QsWW8}GWNl37ulfqb+mnKoNYO2dzpPzk8Tmd+9i61 zT)?9U$$mHZIyCw;^i5TZ{e4Mc;IWv_H2ePf&7w`)cDzRV!&JeW4>0G~`FENHZ|Bti%vKc6l=5gJEXB`cNDle?olUskHMdQ#+QYHc0CNsI-0+7W{LAE zg2&|bwdi+RcIZO-={&!qAqZ}Cr&{Y7@_8U`zoYA`%Q%OKn%yR zepEr5v~e5MSZV>NBScWv_NOK+#|2sMxO=Xuj{viIuhUo8(R|$bl$9$gF~gOLe$>6x zWzxs}>HH*>V=!T-?ANX8q;5WlYNt8xG9>y2 z0+=jMohW<lTYev))$KIdr=q$Qn3Gtuf2w;J6{4N zk1wll1EXr8Zh|14Ml6EEIlrqw;vm{wJMzGGJ`7b<)86H!P0hyK;?stKsLJDV%sW%r zk}< z-*XdHWrAK8uZ1}R2hHr||I88uu4y&7a7(SRkJu-d#zVz{?23@{>c2dz;y;l5V=28RuAfl9de>=2{OIm)iXX9}QGuBgX0z6sgVPx?s0?C@RIVJyai_!z7pzsuZU(EzJ4)35-7jSRO6|y zfM+BXW0w0v_qstPiT%yWy6ev%*4^`%E)L;GV_Q!`%`xZrrT|o|*gVmj$4TW69n&>=WYjy$;y@xevuBKcy0Ys!6$!RZ@b_b1 zQ0~`!$n;7ctnA+w^OLqWY{=iINcZ5Qw;b@Z#gp22fDNS!Slf z^%11TpUE|MQpQCcH@xD-ivKUpZWQyeJa*)vu#mEceFd3mmQlmi4%>+mbEbL9h-^G%ZyjgsyUt4Yn?z^ zZWQlq)RsX-GvG!sBXkGA>c8&WH#``w@B-Ez!{r%|OS@XpnIpfcotGoUI-r_;R@`iPSm)jm)P8tte zfy2L*Gq|P?F=jer!E`G;Ct@J`YtNW=G#z zkYy|LzAln6pVf5ul_WbbejV2J#Sb;}-jDbW(xc|h$|REZEU;x+6i4gIpPF}FO6Q^S zeo8u7q#jD)mr)5_d&!KNoY%oV^i1I1;|*c8v0A12wk0j+^{`*S_hfrZ)<2o<{PU7R=X_1KzpZL?|i0_TBjU-DU(iJ^W~%CJ)c9nT-8~64yXA;kqRfd_SvHM4+Qg-;pMd zhPs6oEy7u-Xq=_Xam}pPfl|gxXxrV;u7J<5)9kXYhYMgh(zcFdvqt4bxi}mYvwgS; ze=e!Lq~$I4D+jF0qpsU|<5t<7=Ca~8K;wZ$oyveEkhe3--UcXy@o&?}-D~(9+9XLz zGqd>3_a>Zp=xkZ&JmOPGvdXgLQPWv$aQ63H3Wd5X78^FrW8i^)`}GvxYP4NzFzhF} z*@7QX^&MTU(41tF>B+;!>7``JwKWjR+sL*EIF@WXot7ws>-U4@o}2|B*9~9hN4;w| zT_EMj^sKNhtZA1Ep|;B7v*6%d%6h(M^99_F0lix*ISSJgiq;piLysB?z; z)LmZKp4018pBIFEV|$UOViP_o46pRNF6wlEfW6hHmyVXr_BCu-+5L=Ucz@@%lGt_4 zukE3zjYSZB!XG3(eSPKh`S`jugzJED=^~R9&>6^blH)8adPiVc$L-9WQF_EI* zcy2qwL*sGNN4!jN@kh1gBH7{v#t+um9|I6TNzkzODG_%zL)$GI6O!8~mWych(d79S=wZ&4;_In&JPdi5glk-8ioXlv&|@cNGU zs&EWIO`sIm%2#3*IJ`HSvY!Kv3D$hM60WXCCg)oLbc2Me>$ayYw+Qffho{diZ7v6e z);WI}{$T+|j(Hdtd)l718C%YK%362%PSJ*huYglBMm3Zg1S&(mb^SV((8Vts&rbN) zThGzk=8L$pDXq3&N0zpWRxYiN%cngbh4%#O_}NRhOtUa%C9B$hn<(Bz8 z9~u*Yx0>1>@6ksyUhgx=BeNOm*PdXB?t0!d6~L3<7rOjKZ`@ZOS4`5QD-3G@vz^mF zMWvNpRHPX486M6Ovg%F`6%k%xY+8$3NzKyc3J@2-4%&Sys<-kqwv8r&ZXqP^^r7@L zyQGS7GkdWr!|Nne=SlyASeSj5$5E0SWHcsD8jVUYj~Z)|<%lOo+3&p_@SO z!8cZ6tLn`Mq>^08gTd6?b@@=6x}x8W31V<}^|56&CMGu<_$LU@3trvl1l)=3jT6^oGa!27^~V4Z6)_BO7}vm5r4w8rO_ z{rr?8nsX{CNJDmvJcXxq;upP{O<6`6lbxp4eP7NsD+YnIyf%$wHA7lje>VZY zlJ}bd-}$Kiu(XLldG26)Oc-%-P9qzvg%*7$L^Z|i{>N*{uBm3Hz4ND{3n23-dXaj9 zYCziSR(Ln1Rc-F{cKO8nwxlMsL|r+YtvMLtxY%c4IrllEr-Xe}xL-=KitN?W#yeB) zRAOfRK4p3n^=S*ep<~y7n7?ktevr>)^?Ar7{eEeeH*gC>Zq8v+MyKVmwCRSx$1{O9 zoO`DOmPqi;0b@65)U{{x@rLg@Z1S}6Fum=514jmrI>Qki+h(Wb0;>$d!=^*7&g=PR zF~k1BEcbqDc{2S$SpeFr^LkcFb*`-Kq|srNd-rbBd9YgBJxkDqVIS_QqW8B;^D6&x z!)7VZ=@fef+)2xBx)`xr*OJT8$;g7qLNmWNz&d)VmTFX3$-jA%w^19nMWwhO-faF_ z<&C|TT))|PXzhO1@z^nen7i93=d*xk2qDQ@gAve;oS;Q zS$g{Tr^R`wYGSS~!~IXH+Xis52wt>lvRXBGOm{k&FMC}lrV?otd7VzN7G_kT?DKlO z(a>_^QBGatR~Po$rk%xLP+ubaD{a~!Hbp2!`*v&Opz)TGA7_>qZOWawR@e%-`W;ag zT^o|L%%d-P)h`!jaNr+*G_GRi93^H_wZ)+OU~zgM?i;cc6m>i zO5bX=7HvkDsn6VHdX)Izv80 z>zhv;!SlKgLGi*P9{GnL$J#|6vQ0xEb%?jr3qySaPQpq~GEgj6a68WWq5I?RIAK`d?5x4#&HU{miMIQG-b(mL34NmDrBa( z_Gk>Xk;j=jB4=NTpj4AAE2>I}1TPsA;|d^&s^U{ENt-0UOMD!Po={70cT-^CvABv)SB+24e&O|{e_9%0+}2TO~S`WaupX&3;@;A`tx%|vDP z1E8*D9Z3B5bci)$himH39^dIbu{sa?LpG!Be=&6VUkoKJ6VhtTjot%N^XKQigf`zJ zDUM>Q-D^of1I!}<6f}&!xN$U{f%g=cqxajv(E!fe7^WS>co+Z?+D{7ZLY50fq)^R@ zYLqsK-=!is;-An}nMl6+g4|u)(W0`}{9YRFnjk|PSlsigippLFTXST;2@iPz+G8zE zQ|#BxKat_b{@^53Ca3NOXEL1Oqct!4hV8Qipk7^iR49}-fVhOAF8Afb%LVQFgWcD` zzDB=@(a?+H<&(QKbX+=owZCGLIvbyQoqC|bL1=1EVb<9dEhzYXi(T4r(#hHr|Dx$8 zF<>u=`dQW%H&enJD`_RC0nqL868zhvFc-v$fAu(sO<{3zPh}gbp_}fJUrY%JN?&DI z$Dc8V?RGqMg8`3zzU7(HhIbp9#XY&eH0ZlRr!a!z>A$>xHli8DVsxnkJ^Q>PW@WzTih4)E zZ(8BwjfJ}P*L?QrK+wJdc98l6O#UCG$o|$&ABg-tQsAzneKl4pt^Eck^F%#C<`aov~ru{1%|qerIA=+K}t%5<7`TEKfpMx?|j-Z zy!NGLSbdmTji)O-XdgKG+Hbj8{mB`|-z4J?yq^RUOR%W#oP!YK&Rw}b^MutXyxdrg zRTe+}**LjU3~RR$k{26Yy9@V+UBX9>^GMH~`o%!h)NhOa=dr$9N(oEDu>IJ@je&Lvf$eXN zIW5v@=@wI$7)!7`sP_FRm6xLzJ|kAuu2Rvi(B~F&+K(aY`aRHj2kumE#jbbR+Ewmo zh|qddYnj@*sp;Y8hqi}y;MnMS5v*m=^)vCKU0pkD2ey#tg~*({cxpa^l8DW?tYrAiYQ5^Z8IET8LhZZ%B*eP z9_`1UdED?`?B&2QNviiAEwxOJnxD?ypOMSTG%;c=o{rh?TE99IFH#;YTDNVYyzaeT z5_L&2G?O$9lzAOHSFI?e+czqZtFlUQMjmTKpB|UZw46&!W~BVKKN}- z5L>zHn63WeTt1SXfQ3IgBD#JI*XCJhnQWqlNB^VqJU`069mYza>b-jFL#(|{o%Fm9 z%fHrykbZMjfRM;@+BleplHRuWgwwJXz*v3z<>$`m%hBi!@toag%kAs=7d) zRLk^M%z5;-)2z=l4_!9g_7@ZZ_rJl*gFPE2n|}>MmiOLv>rfh*9_efvd=4$u7e22W zjNyGVR5QWDt-U1{q?Y3cmc6aLDiZqgCZb0jKl7@$OLoa-mhHo?NSJkR#fN_G`(uQ= z)>mRQ-LuKYS;DxC9%1*1(kMi-Hf90yc-W=g23_O?u7ZqbbX_>}( z?PGt@tI(}G{*je?0#}U)J0{cyT*HX4K})8BuMMtR-}O6J<0;eav&H?L$Q}JO=jhHw zar)PE_@gj~PYL;jMc~V{&hs|$^Exqb8U=FUw22zQLL$a0RyrPG1BVN1Y)TCX_ zRj$)hUHtEg_h!)zzdqRd>yjksaVy{Va1sGBY-jMSQj~TZU`ACcE>J`#{2%qRe>_sg*({Qn7SXOXR{6 zz*9(x2he7VlkkS2hn?8>pNVuFV)AGwB$GkH2-(xa0~Qes6Plmg$3v)&n4n>Ps!kyA zs*$;Tve3?PTb_HN1Vu%go#q7xdp7@u!c50@pQVaXyNJlYz=U82~k~g>w-Qe40%2H2=#w=fIwO&3G2pZnj zj6#AaiBSHNwYLK#$)-re*Ixky7=FTZ_#oYQAv{AA-MA-bz-E}JKX23`Ix=?igK~Jn z2;JUv1n&lCm^7Px37yf+I4EiP6sbQexjYA&_u=<(p^Wd-@_D?12;`@6`p&_fE#SbhKU0i}l@dy=Itz<-7!#|!iQ2yKqe z;uA&i)d^f;K}IZPI;K%opk$~UD;hKpo3#H`sp|j@u}lOO!d)~b85uIWT#E{xsgG?P z2`OoAyxr8w=pB7KyD(XBpb}TI&R!?GCu|N}n7=gx8k|5_aJIbux@I~hq%VsE0@6S| zQ8B8h@LCsyJ~Z(ELW)7DNmG?_j7gRp8vDtZ_~juom)1Q_9`D8aiw+O5@b?`VcY--|RA!Ay1-Zyf|1HvoXytlOdqQ2l zw4`BG$p%FGRenY~M{e9m6mlef5pQo~F=5>a)M;9BfPI@R)zbEZHEeByC94e@uFr^uH-8>y%x z(|5B(%6_pIlVUp%-HjWt@f?Oae5=OD9StxWk(ycKFSANj-GBNOxr2pfWKq$B>?LzAnxpa=W&M|^-*svJi=zk>7eGZo{ z*u29_JZCV5!=p|obpy!^#tHQC&6#{`gYx1M_no(J4lG~Bm#Wo}oS==IQA-AeCxZaO z1W--g-PtF0af;Y@>^F65lxdpGUr(U+dM-@hnWaI;(}zT%Y1h;urPStiniY>t_bA8L$(RgDBWcRCzK)%t%c`x6rM~%vZO?ex>hQ``nQ5K2VpcbB)9wymacjYW0=Y9ua}&Kc(`*%Yk$3Hd&y+{&3mGzgIRucA7j`h+&r*S(4XYV z!vCxl_X2J6o#CAi*87aYcXAf=hGNk95?q$UY!Z@si1G}og>kr-=UpDBO^*Ofb>)Sc z{Ih62o?Emw(kxv$^V@Jr-X**LzR7t#LsB?fTaOVT{qS2Ld~}$j-6aCzLGkRG4?IW* zEhI}$J3-| z0m-*sC&%%zAlBvi#OLyoQy02YtIwW$jqh_pW4>Xb(?LpXS=-CQ>+vs}3!ZljP3O)T zeuondKPUBimJ}>fs%rkG%rNRK;iL1@l~dWoct#*=7iED9KXc2DTWxpFeLUD_dW*>o%T4vD`j))9kx7b5(* zT=#$zX>LgiSa*}2$!0`j=9k?ZpUUt8ySHax9gD#fiv+Ib49g)nQy$w;k1i3f*=f?! zHGA$mq7B0}g7_(4Ia_?ToLSoPCY2TY5R2lMAQ?rJuga0^URg9HAdE_YR1F7H5=ol) zvAM4pGWK0kjp2s*1{pylgQS?5uoe@$0TRwi=VyF{Scb1bUwn9a2m{ShTHXDY+au<# zNx?_}t|k>bk#je0e3}!cehq<1aL3+Pgg2Z9T?Hac3L(EW@W*-5ak48ucwBEmDNo&= z6d5&1YdM1uN&Rq8UA*MmFUBj3s63}bNJRZUJSS#Z$S_W52(}KFL?ircY%T$d#&Bx@ zf+TGADG{G5_t-Q`i3tY7wbtLS+AS!d@wfefs~FQ|&>s&Q+p6}R%Jxc!^!06(B(9P3 zd^^Oix6{nekHZ)N(jwU07^nT;=?u{}D%t4uq(gGd@*JmVSdA(P;OOWkeX+(|X7*H3 zAQhEpl1<z703dsB2b(%AM*@$Hr|lY=K< z{y4`P;c=t$@3GkrnDV}cO6Da%P4VUDupxb>zo2HB5yoGlD+ke)H&@-AH{~S?PP!g) z!90JmDG6o?#rH43l=hWvXhl4uZ6)ZZRhoj9Fs0I^o$@6~$b$(>2=S2eGs@k}nScg_ zvYZIWA$p=PnDZtfFwF8vLscd*5xkRGoMPiwV7o-^A*nvGHq{u@5DNV)+9B+nYDTig zp2Nph>*u%e+`3LvQtyqjHtV}ER?pI8K?x-oLj8`0f36rf!3SlGrB`2}S55%;r#7L9Po|$LY^SD?a5TXa3EL$F2_u}4SM!J z@`yM^yf(AAugp94=FGDFlPxry&r=ZmbW+!nRbHEI`K|sm^^5BsJy~n06IiHCv{U`Ar z?{Ot|K#vi9`3}DPjEtB+Y%>sX1PXt#$<=1sL$7;s$Yk!#1M~scX(nKg4-+&ATgnI> z(9$E41W6SuDoZiTCaR3&ZwY0SA%kEPrL<-7$E}wU!3GIY?I0h3URavH4V=sehlcI< zLi6j}F9QC=8ZG%|nty#M6QWdB=K?GVK60K3-lZOGLa7GRJYaIy==+H^%?npThg~Am zaO^r&2=2~*VPs_RU=kf+8-Wz^YFKhj%)l}7cggv%2%br7ADlK^(B18FNegEy2Ag-A zdjbG@jxvx+`X$V5=bHTz8M_eDY!$>)rrE+UF^dzNd8wlotGUd$^2{SUa3X-DvR`{Q zX>mrX#R8tS%nakQ5gdH4G@SVAW%P zQwwRIH_UU&{J=d)p<5U{C~jeh#zW0CqT$X+9`f66fV;`ma2P7(FBI*N+xAcJGYPr5 zw;#I^5{}IG;?u1m#zKqA;6&kamG*l6OYxpKQfRfLNVm@mENsjQcq3~pcldah`RT*o z)FEs9@rmoJq0qGo5scv8gF7PLMM3EJ2D2GS@#6N0YzQk2A{HY{l{jY+C(JMbzc>X& z+wZo0lV~#35Fm{AH4(qC5c_u_Dh_Yv(WqoWq+|mDgp<;$9(Cih!<1RZh~VW;jG8b5xssQ!jyzN;Ijr57t|UCH(HY=H zR#|P0)Q%h1q1MCv4l3b%flbmCIt%`kZNRR7Gx2O@B7Zz=Q;$hD1~o~PvpqAxZm5gF zd5@5s7Q*YV*8D$PO;avrMFg`eiuOtehv>(P+Mu|d~CIds1 z7(R=jJYn!kje#1f#@|mR?=U!QGLm*PnW|(x1%%xJb-B&E`jv)RLN@0KbNX=fQ{3tE z;IG|yrACkZRc;V?gxaC-=Xs8`cL-m$=E+hEZ5Zjul%V*QKTFs_-i(5dXrJP&F}6sK z!??kN4l?a&6t+>w2YG!*_>bakyeGSAx5taH@8p5%eU3N1!|Kb)uEQ6+Ql|XF^2fM1 zSxp_7Q!?!M*S3~N-@4=92e9c|AbqHd1np@YLCQ>V40%EQJzX1d6d}wwL5d1}NYD|I zkRaMS3US!NT2weuka8>;3H(Kt8YHF6yVLF0*R;+tf#r)0M{?)SVLWrG|y^ zOl*zG&5=XFeWyX+I z0I@<`ZV)ByQ;p+QkP#tPiC43ot=n3nC40F>blTn3Df`!NSij)%LwW4=OJ8gr`;T4( zjlbe&|Al6=vN;#iuWk>oZ8zt)k&-DkQ*od(Z{(gG{k#&sx!yyuDPC-saTpW%LicEt zptUo~47duUB*Q^uIR!?u*X3{!NJDSKwMvSEr!m*i>2~NKzXWH4e1cL(-pW}xErqy* z|F8gBj^pXH!_3Q{-WLTmU?mRHMWGhy!jSQBf~lqt3KNp5^EU}_VNyBp_~-*oX~a+} zjkzHl;&N3eR}NCYu@q%>*Rjp}e{vb%n)8WMtjV)bd8OOc4JFtXL$LgSFa)lz)2x%u zb^ba%H9#k+Nbp^1Q+#9b4qcjP#QF#@BX79d2pvzfr`akc`G!bJlexb6E4$=RS}xb; z&fQXRt@{C0{_NMEb!-%z{S#y8pX9Z^NFI3&HRX<{s# zw#rerpe`C>5v*0LjOLk_lo3obtB7k!cm*^3*Umha=haVWjOt^y^GU~+zR zj6^K{75A-DuhMvPok|%1Tc~{ypz_D3}JYpV~&jTmsmP5 z1HqF0D5mV^uwdXDOEnk7W>z&f49A{pz*d{ywaN-e;s8S#Q?u4}1hj8mDOxV>*38F6 zv`%b4-sqT(jz0?2o%%2~Ow|r)q>gIcfM3^hBKXZmiEQ|8j^!#B>+WOBv}>eae+YGM zPGZ<5V%EmsEmKYQKq@KnZ3=B3C_B}1SGlWH&t=ww3uPdy$as=j7gs22(~g6L7Nu&J zuf#rq@Nn@yuzMSeDI1Y4pn4Aqjm`)_1gLye`=d%GfsDx@Sqf*ZN8ciDbuhD2`f)lW z?8?;7%EGV4Yb69jmwRZ-1dRc@m^b6r5SxlR@W#*+F>II^|0_D8)oKsp&&kt`o18`Y zMIl{$MQHpC^vCY`bdpy8LQL6Ghn^A9M;U6XtD(5$Y1ToBMc5~>)%wtdl&yq{o->0& z39Y`h2KM#_b!2jMjCS}l>=1Yxgo|3$R~zK3F+xX_Z&XW0|4Opo&>1!&wEs==JoI;Q z6fS*v3c#HhU36rDmXEQ*j!=>&3i`64pr$kwRgX)?c|vhZVWb7rb1OHTg5a(~nY-Ow z8Pi1N;d*2y1hI7{_$v=;-EkDreIyFr7p3RS{#iM2H;yu=9NHdbv3sxCr8&3WnOf6{ zF59S$7}G9qWk0ae_lpvNS3))dqw!dm7{-^0wsa|Y#Vmrh5vqI%9V~*GAJot{Ku=aq z(M`HD5I!^x)wBX-ao0R;rN@HOUd8%r@vH|qjC{SN%f_|y!^?ig3w(6K#spW&Q7J>i zRy6A<@8u@(E7vP5jjaHa2uu~{b_k^QO=W`DRSt0;>{mr0xPGB-4w+Asm5lD=p)cpe zuL^PL&qpS&gMb!#{&(`VOMO2f95EASGmnfeF;Gw?TjUF7$oq{KIzAp)KnMgRRG#UK5L4Gux=X5&`P@jK&#>lwqN?ATKPL$R3*$ecB z%c818g{(6l|4v7G70ty?BX}^@)Bm3Oy~!r}YL-8?S!Uw5FLit@rkJ@z#VgW`xEzKO z-CX+{A3GADt7Ia)-{(LRi$%wdli_`K1?%cp_Miy&MMhG5FVjq%LZybJ8bZyZ@K>`{ zm;K;b=43xF*rNCd}{D1JLdi&Lw~R#J%7SZgEB z)U9l%B2B=`GZBE5sbJ?*P&Ix~d^S)qQXrFhHRcuhQi?%K29n5zWU*I>c{!5=uCh>C?z$QI0Z5X)K{uK3)9W?q z1En4T^~><;)2|x%m{=Tdn(wlZuct33r^RR?9@j;Hw4u*^0*$N+Hoi#~ShI`xX$|&` z1KhSn$#~xf7#T~vz6BU}k-swS06iPFNOvBK3Y#bMRt7MG%tu_jW?kmjB_>{j#CSIV z7}EhjUd6+j+=7-wbvR~C5J&)Tyf@$H06%pmjmdoO!`(!=g4-mgnD;N4Qwf8|pzWWY zZN^8Wvhyd!>@;pStj=U6PlRk&r3@7&kc9Y{>u)bP27&phE~jCcoS|)kS@kAI_@HOe z5AT2pN?L=K%}SX@m0{a%2%3@xB}!$er|^}|=YH=!e`P3J?1&6x0739L)Z!{shFnHM zlI6`?F>D%euJK@OVabqD5Ad!)|GsqUecQBrY<&yBiTxJJ9{!X)0IA5#y?)*1 z?x&6Gu!Z(JaE%e)cpP8q${6z)lsqGXnCQ66<{oi3ELNEUg5o)$;FyQR+>E?PwW0g*fze z%HSe&S<~-$s&H6v0*bI0N={50IY*{`TDos5lw-clk+42!vVSSwLIn^7@M(g#REq0A zedy=jP3=reY?NiR8f+%=IQ3XP42R#n;#8tAG!oo(&R>>ea0G z@vOan_(!uG*onoi)4TA~$SFV)`900X+l;RHh!DSYsD0ZA7_v=&Bw$A~F!oDnD(;gB z2o<+S9i)fgbNDtKg2=_g)*xxHn2*QW__M0wUY4=zC9!4uvly1gz;r`m^xkaS%XLqI zUcSS-NGu&7H0}ZmQtjU(RYF)k=irISyMLnDvCA1#djOUOJM!VkoZ*#`2*XdZ^2E$Y zGzi+6BHc__emhEL$byJxCO$VNt53tL(sAdWyF9CSu!(=F2?e_1H>dPU?>cBRik#ne zE-Kh+i@Ws6tjqqVs@M*5(l0Bg&)NkE$>!EPuGJC`@izC^{6_!~^KId}dlNbEZ|h+% z)idZtX{a#MpCo9z0PuDRU+leu9BPWRj%noF-hWqTwo)OhXy*p=6TO@T`dq8}us;m8 z9t{#b&h-LZ&@v}eiHK~%FJQd3QvfWF1t10CsZ#|Bj!siHzd24vW?{nQOKmb)Mf$3r49i*SX- zgI|FpxtSD(LFtsTxMU1)W09FKle1A;LzOO7M(GNMMQz^WuJvvKTP*9h0M5Qi%me#Y z&YZDn20atJ08SgcDGf!M+n@MFBb$L=k_m3;D)i?EI15z-YEaTPHWy2g)njUeT{^c; zItf^Qn18=RS*ArBfwP3dO;w0J6ruC z{C)|kI%Qh#=lia#E|y$ONqztYDxv+jFiS7HIk=wNa;Fn!U*Wd8j(bO zPxwHoulO}UhJq@w{^9cvBwutPY{N5}LD!rZN40atTUb*3RO#e zjfYyN2M}Xo_Bfihel{>mPHe|j&Rl-gt?jNOX{m76Hu8xP>A2(Bf^pw`C!v0fl4Zkz z`n#(u+h-s2TP~HuF}A_%rd8offV}lp;g7tnq*2cuTAh1IT!awa!9LJSf5ubruH*R+ zqAQZ?tPB{@qO8wwT!mE+N>T*_5FbC|z6XhaR(chpb7`MvYXC*bIGs@W%8rAbZ%!CU zCyW~R#eA;BteUSv`Ry_026a7dbzZi0TF?F>FoWBf>t`GB#6O?Ep_x-n@H2?qnOIYC ziMhVO9Y3YQ^lk!3baKLx%P*2s%M~V(?bU7o1#9hSYBSd0QrCN1)mv1?w=jG4ONz^4 zPuBV$tgdsV_!z|>s7mdQjkYl6~NfWN}_!1O9#yZp9T1J`0oSJJs#2nr&^b&!@Z9j@#)uH?A;^02J(pL>Je2&`%rX zk6QnyrfZCjE84;n+qSKVZ99!^+fCBgw%OQLlQgz%TaA;(?VG;0-u#?(?#xI4X)!=6&5~ z@t%*VRK;5@doddv)K;~+$*gzFyDW*5D-3=4Czs@OA4E_{>y*0M4CsB|dbVKkt~9|W z2n@4645~GzZ)u>LZcBX$w%$d{xOuR`@9JHO6mtDL5+I048#Z46Tn7FDsbOIBjQ`+w zhe7KEPF=YQa|rB#q3nSo5%iVxQ?(q^aZx(zQUG3eo+EfxUhSs_-K`>hn1B0FJ_5H3 zJ^F1;VNS-0ghw^0+4&|VZXZvd5VfLD21%LpzCbBQfvVx-kzu>fw?0m|+qr`FwXl)&K*6SZr~>wqrp|gCU3`wQ zL;|ksx*f&;YB{3)FC%`5|8!S>vkG4Ex_r&{PW`V>jY(_whFCgZ$agMV7L_2lsQ<;~ zjx3(}1R@shdBh#6%%mATHcc~S3WGMy=T7`7M5r}_XtER{NKPyqm4>)qlgtwt+RRl2&& zejttTiw&XX^dY@wGHcdS3z&{be4W#Co}*ykA9)$QPk8U_YB=EC#P#qh77=aw!h_N% zE|Y)G?SIJqs}YiUbg@%oP)wv(16tc<1%IOVj-WTGOEnz@%eT|`uBm2BLm#`w=)Kjb z`D6P`in|H=pW#aWk2}9Q9F6E6(BpO^&o8ZNxPv+cPF6-1SG2o|gFWQvHusIE9+4^7 z5EZ@sJT!C`n`}BaE{iHXYnookMAhd`yybvNFp!9P+fH)>wlMPUTqE& z?umcSjw?Q3Y@A52a~tk>)QQ4q^WkN0BGT3VkH~*pbA@-NyV{u^hu;d20L+ZeDx;UDCrrzkqDu3hOh&3tZ(CUojd37F~94nNoNwvOh z%Sn!9ZPZ6{feemRGg@i}IHZ$rm|Nq1bm%nifziCdlp+GnTwD5-8b#VkbXmU8`j0_jUABmbg@8jF^Gw#BIv+g!oqeeLp1 za`g;(mB^vWJ@Kne$*+r8W{LRzI9Bn2MJ(_|0$p3--;D`mi0Fiasw+s2&Jb;-I=uvl zcz|R;n-Q?(&b*1|%dDVF#5UmUt9|{q?Mr(|KPUS0m17#=SJ?>2{)Y@9N)M(KGxqyD zUx|)PF0WF8_Gp=|p(gd@A3U=fZi;AYr1D`d$r^#=(0i=Bx_b>xeLn9$smfT3rwqxI zN2zPGeq|;N#>CuDO)i@Ibfu4@+=-mLT^V|3uzJO8E1*P;CT>`_lz>Q2ty|7T0AfEr0oZ1P+|W zaIu{kI*mAENIh|vw&5FlgJ9?UVgHPml15ZlyS@6R-y18bxNf2aY8h$qSOt4*GfQeI zn>+cLRzQ#Q_Kxhlo$NGL)}+4t6)Z7RLoMU2_k*_!z%nwdFSa!@bM}rT9fk0L%Z-4I zIx(~nTbnbNIr>zSOTOnfkhWpQJQG= z$PxJ3|EYi|q+lA@H?)-e2GW>0?2euKKJm_aBh$UZH7tRNVlxJp^oSHL?=Bd9Kd6&w zJ;$f2TVTc&gq`}sb@to2$LwKd`BIPqX7TPv`%h2TbHBrhe(JIv4t%(SZ^fR!Tt+)A z!?M!zK{`ekuT%o35#>&-J_1exo(}6#6oK3bE}Gxb6bLsK`D?@SGzC?s2CW!MyWmoV zgWjju6Zf@MYVJ>DlD%s@Q?bq^`vM^~Zv>5q~VZ078+xZHX0 zh(ajMC+{mpU32QoNIXYsh;QMokmfyEE#N}Pv6NX@FvxgH+ZK0lF}Lil@5lz0f<%O8J)NSx;G z{PwOtnOd~tV%gkMXAA9&zm^UrJJpQg9vunXWcIW{CI)k_4wC2~&qSN{I5+y*Wc>}K z)3qC0fBu$}!#5V}0`3G3tfjaR~aJ!$gdWPpbfQMau$(-@q zvglWPN#N_9+gk+c?X(oYMG4;?yK?(v=AJOVL4Cx;~-z%pEoY0=T}M1P4|3=wONEU{IYlb zhMy~(b+6xgpT7lsj0X4~MA39!ABEFUaZqZ)7|zL7&JT!oc2-_~a^YrYsHRUco_{qP z&p?rD+;6@qnW(I!0P`B$RC$bn*}>s6{;ESQZon>Hfu&`2XdQeT6OZb=;B16(AWyun zm*yd6ug&_UC38b@8M1@U9ukClMPaX*Cvm@3ELbQpUAwOj827u3@V(1 zdMhhqNQ_K_zJrl~iPtUey75u@C5C3mV~L@Ij^xhc#YIaxaGI&5p@oqS?zVU?=OK6hbH9ri_Po{*k3SXhX%y6!bzxT{qE^K~^VfmpHog46Kh2z(FY!2=bXDy@o7s&D3vL?v9g#$MA+7<#o zykLHBtoF`GmD^1%?U%A_Y>b5OOSHK%$P%hG@q{U}#C&!@I7! zEzOL7_s=}=XI{Je*#h>bpC3(^I@1+A+9O@W{3SldzkF0&Q zl^J|rIiG|F@g)V}vy19!EFpO%G!@xU=05qAER$W;)1`QBb;TkfjSiaP}d94oLb-5sB7!xx{oXq*rYF;#C zIE4;MVcs}+71$%ya0C{(oGglp6pq3&S8xD?y8QlDLh1uJil*y#UpSVRuq6Veh65ri ztgt1h7dWz^bjYxWbHK6Tt|f#nYnOK(?OQoy@zDpA>xcK2-)Z7GbCp+nED6r7c(_P&Dzl{-+N{^at4uY z%erj1P0*F|Ai17@0*oBvpMJs)!5E$gG zd(59L*61`;(65z&D~W}K@CRW8GiD%6oTe%*g0gu;&s;o=-q;Lf%^7P+k*;Zh!y|GA zFjTor3XM#Y#^M7=;oplL2MrlUz;j-HVr|+aXNFqhle;Y>Kz5#<#E)<8{N_UGu-?t= zz0jz?r$IKJ66}3|OaShu1&=9kbrX5MT5j2)G;io)9J|oF?gz0ddXd8(q$9c++Bc#C zXi*&Ug2|9spygc-XrrKwH(<+PwK#%YWJJSdH8cm$<_Vjc&KQkgQ4Zx9xv4*Xr%~pi z1iTD7ysU}VI#(=nd_cZ!Ccd@gf*F9yoxr@zEipZ4a#=PT&R(eRak&>gv4q<}fy%vS z(g$}`JmDBs^aY+UzCl5@tuT($PmP z8WutJk^GF*vRywK#JK&+PkED>-QI=9wOn-g372>_@2(oMoRZ&F#_xlqUhu@-HYvZ} z^gZ#C48G#e@*^Hl{{~gs(84W2hjkrMSNYI5c4Oh2@`PnI>|Z(f-=1WH*_sXhxY zCKG;q`~bl=+n_|ll%hXf!87YWPyKGX_ry<6N73o-zCQVWwV;w?dHaU^TDMMsQ)*UK z;sLpfKqG^Is&P?lYB;50{nZO3Lf*L|{@RXc6HNc5n!1kt$BVIDOnrB7hApzaAM> zE0M3O+S^uUTkDHkKYvD6yrJP=V@o8BT+~h}mRr<3x3}JFr|tdA(HG+4<1xX!0%OOJ zQG!~(_vb6#`?DC9Zb)d7LOqw?er?~-XiG4}W6tLbM~Hu_2D(&XDh%(7U%2mHMaEeH zz>HK_8-08>QjS*?F2ZHyj;TP&lzbFOyDyNQFdB7;57t3ZVvV^t5_s=#ZYY> z|7sB(o^%dswN-Tvj}G|VoCoLMo-_r%u03GfWCo?|Skoq_{#>dbMZF8>7i}bx?hS`= z*b+HzF zLbX7ixjPgGG;1Qh-Ffc50d;|mVSQkS{JVdUGd76GE33Z7`Lhh-)vUZ~uU&HQVm=h_ z2)!vH>1Z;K=If$u;20-6u6VN^%H{JNTF(oDygePyvwOl8;7J-DdZNcnL-(#eWFp>P z+GgQtD~{+9)ViRV8vpL+a(;C#;|H;IA_{;&M1qiq3c(bj-u!{kC?nBJx;)22`KC_2ZVy6i4v^lc(z<8MGNPn#gX%R>?)G>9$f~prT!a;~KhNfMY38)bT zmjhoW{9sooB0u%hrD7x+84Y@JtQ`(RoZiX-G@!2WyS8yDyVzR~*#Efzi5<%hG|(HN z9PQKGjSjdTQ2q13d^1*yHAEv4vv5cfpZ0?a);Uia?q~OU&>sv&%Oh zULm?3r7Kz`6S{AmbwkkCc@fwJ)idS791KJ+*wWk{6@lcM!Ags|6dl#__Di`0jfrXob;Aw!*g zs~)-1Z%~CeqzRuzW}hCWhhS0wuGlbl=1dn2c(b*hTUp4;=QwdE=RA9HkYN4KckZ61 zj@c!8e>>p| zlnyR&E%TA8AV^mn7Dll-x{yfnMUXv3ayVDnYp7(RLP?OlWZpqVE{9QfIGPwV(gF>( zt6zUkCfwSnKYOSLaCgNf+pw%FmKT43)lCYYGL5ChOUW4eAEC8(PS<`?;i#>OC8GFu zFIRV1As1uV!%Y9C9TF|aBrMO?NL-w$A>DxrPEsuul=d{D-aZJ;`L=qX9M9|bwq;Rp z^;aYlhh2K>J^$I~GG{mVq0DX9D}#UgbqGWGab@m3VD_ER5SwE7i^XtK3D@QK3-5B* zyi0>;S!M60#YuwQM{4e_S2&W+J>M%iv-%IoQD4v9)46m4eAudT+xI}xsjr^9@=s4+ z@Fo+nCaU16%{9a%AVn!TisXqqBGCM@PJ6kk-!*5gykG0vW3~)X**bBnDBe)*9ukdhwA&b?Q;`96o=@0t*>#sq$ za*jw^A-YwRW+`IeoLrJc-}^Y94xV~REQ4$&c)8Cla35!C-XfcKzJ5OA9U)p8?})_r zFO1>qy_Ueqe0det$+mLmCQ{wkPb6=$JBlbyKx$%P< zsc9#CrnO6^wM4!{v%>dgJ|3;qv|Y6L$;OZ=vsQ8Wswp?Hk&&6jc5(St6JMg^k!Uxv zb}dSaO3(pk8?=a8`VbkdStP7==@fUrZpivplQVj+<5C5qAR%gO`UhP&sGPD6{~|>a zNyHf2q7q%SR_1bu4(AIPO(72%5G@KuCqjb_+t@+R`Sb{y7(b<8Ovo_#lL+EvW?PXQ zhs8j`)RV~J@LGik3`Ht8QZcOn__(T-6>H91O z@rH5G4&wokhBVIMl)Zvsgu?t^21$M|G9ix?%h4rx4N>@h%P64%g51^9avPP6j;eUG ziMb1X8_OSK$<3e(Rd$wS+7i_HGUXO-K^E8qY|5b4?vSYB2uNKW}$!2$#Sr}$zwhu93xImv!jsilW z!drajWKkS=6Ou1gqZlu7sKId+$uSm++g7VxKWa9~+ueUF!sl8EgD61}1CrF5JwL_dq%(Bw=!`FNQ)A1~o| z$ob9os28I~q@?CSlm=ViOPx9uT6ocoo9}nA1hON)hEsMO%;@D&YYxkwrN8+Ln0?XQ z!9{x~yQ!0u=i@|r_s3r=19j1c$R=xjT7Su@XU7)9&YCkjd#8z%FPKf?10?|n$@`(S zK8ZO;xCz*67TDTPJ11q+Gog^ps@i9tt=82c<@1Zg585~50vDa>=d3F>3Hm^qbRn6B zPuG}a8R~qGA$n|)53n9yg$`aP>e2r1M3O#RZK{EVvqJCoTC^SQdxtiAOfWn<6uR5` z%(wo?i>Ihu4(V_OBHGM0A=26`2b|42qL!m4c)N5J+l36gIQ}Q>-f_~=Zcn=N4158= z?angNYaIoCU4jb*raUap5_fnZKl}R;T#z%<1&9n17!k1W)8tK_bczUf%3c!4 zZHnR=753r!KLT*kgTu5%@_AGr zX~n2)C@S=Q`N*DxHWDA8;#sqL&2oiBb7OioEFwQu%uhh3%4*I zN`_Cp*#>QpIKKxC(wPFZslL{YjS(_6P%t+97GkN*&I4M0AKre%o>EY*&(F@|_L=kS zIg-~4pxL14fFO4x?lV7;&&AZincA-b*CafM1~ix(!)HoNnJW!|hmMdPAi!LvJ`Iec z193o=Fg0M_SSRRFC3X-jWIklCt|l!yeQE;?H6o*=RhDyUg{(G&;+?%`9sF%=QRyL5 zxqM_{uR;@p63eQRN`B5TU2mCBGrADq^|kbyy;h=BX`GF{0$(IWjii^JzC37X8f1WeJlEl2Z;Ec`8VwLZKB-|%zI)5xi1dcQM{O3@p8uiisto z!upC?b%O6~OKSM7<$SYXLZBgdu38pfTlTMrb5gM>(cU}#{Pp_ibn2k5TeKBG8zUZpn9>8d)`C?ipO|WIl zp=;G$*x++J>9p*a_;|HL-EoouOGK1wB9!umv3z7oxgxO%eJGsTRW^>nm2p_KyExSy z6_Cun9w#hxbe)lA(=^OCe*4`T_-6qv9~c-X;;3wN$*G{4$!mW^f4f*H1U0OQ>1*$;r>iD{4CrGsf6>?EC!9^Pv?&iU8G40-%+hVk^KYMM%S&(m1pr%V$|b4 zKtJ;&xZc+3G}nRxdy1mHL^?oi3Gp%I_D|J1XiUx$lbR-m7zkuHL4_on)?@BhfBuk< zVq8?V?{9h}?Ylh=zxj+vI2AjkG5A%-W_FIai=RXVElOxKZ<}1iI`1;hAznp59ZUIB z3@u&+6wMBKd@v}XabeaQiF&j^kzv`G&Ew(`15-<_#F=NB=l^#8bZaO%rQ5^<8N+PK z-0X@1L*6CGwW@9T_;LWlWI%s+bYug)i@LzVCHN-x_d>e+u!4Pt?l8{Skm=L}7CK_~ zyIO+Uz)a##{0)rQdah61e1Z;t5#~+N!Q~lZDbW&4330y*Qdtya0DfPCD24T(vqzXw zBor88B?;&JuPYDr9(i`}B?-oIS^-|F$<~d=Mw1jiun;NoArpsdEcg`wP-2~c2sBWh z5r@zXG)=Qc@ZIqxX3!>LV;?Rnk_5wWK#CS_hSZjrGI`il2ti8@oocQv8MnuWF|JOv za=@?APe%a*+sm6)D|k~+7$HnXdHGque;CUyLq}B|UKEx(oI~EEKpW#_Jz;|&lu{6> zzbpbNP8LwMWiE;3K#zi#8F9B1?W($QFutvb2V=VAfW^}xFuzQocdH^cjB7}OJTR4owTg*wkE#urcecfSUm)L_caQUu?g z$48%pZkWjkKCjpm{Dq7!FF%5v#pl1N8Wjw>bXAm28VKN|<#8No>B(nl$>x8qc+ zF)v`Z^Os@2Fw-^5#wtC*l9OIpoS*7zP(z@TB&*GS2{pH?08=dCamN-!FNQ+KK&WwP zvk7m(C__xmqL>`QK;W@{XvR4IY1m%C{*9^@>V#5>8i?GpPM{;PB*pcj8W#Cuc)-*= z$XWO7B!{ZcB?TdCyp+si?5G@`OoBI$N8`0dQ+DFwVgu%7fOKpEiG5jy?Fo@oG4Kmg zhricw9GrY+h@!MMwjxtp01~ti5>4F{rZf-?#VQ*=pK~9k_x)f6Qx^vzB1`l6lf46P zDDWe~%t6?1PaOQ49WLOjh<*N+L6fB0()mgq4VG475vm9d58@Hs_U4jweik8+j|*}| z(FZ6)p@{Y~uXpfze!>agOYKZrd1>N~E>;h?m0z9Hmg8vF`4MH)QXNh{q`vOYi}qCQ z!OV`KJ_j!-z)7o9x}M=+(qVrVdSFP$E?m^hiu#E7W?vIg+x@ zrP%YVRzrFYfGYDRcW%*INF1sr`%+Rsp}T1tym))A<|}hq^?rUzRaUbn?C2o;DmfS8 z3xV+B@R%=PQ1>|w<~m}Q=QP*tF|12z2Xb>6?au2+KG1$|%l}?zR+`h(ONr58=;6}c zMN0}NlYFcD9(@&M?_BP1+G+kyyh(k5NNH2(6iG?QfxG+DV^9(sY2qMGU8?U{%WB)~ zeqw!QLAGWWpsEodkoWSBqEG-=m7l{%Bm8J$ZlhVDr<BD)-mAVO{t;GNW(^9XZ}S+^=^G3*&v_B@8FuU zG4+hXSxG)cXaFW+B559DInVt_IHAh;`3Oq!*%4PPiCR7c{8LlbJX&qKfzc8oxRhsgt(Wz7e`z zdW|XH%$S-=s`flCh$K;0&1Ha!tb{cb+zL^DXgj<)SpzMs4mh+e1)FhQCM81F530G! zS&;;INKFs5>Kbc51nSyI^-9Giz`XvrsAF=31L5JB9{~@?0~^H#aIYn^lQ1@;#+4Tl z049>WJ>&zk%inQinoi0sPM`#99C|>CC5;lF`4w%EBI}O^A02LfUsQNPhL%g$}%h|KNTIbC{+Chb|MiC(*ZnLQbxMeVi&U5&a%KP93@KxI z*HorW7aWCWsZ{i}hrw7)2R*@X7)5a)OsSreASgm&#WBt_b z4u;G?9xj(M9}LzPbH?)-c0-w!w#ExV0p0#aK(Bxu<&|o%&TYY+yiv9RH&%PT6BglA~Iv+FEVx}IH2He__|J|oHoYrPCTO&qoiafgrnL_nJPtS%oqQ4|B=;L~Oe)}2D zhcFyx9~W0YHhi1~Wo((#l)0Y@jTLDfy)Jz9I4>O9NiA5D~eQXw|E055D)t1?#A+vp%59bzjT^^KLeY=Zn)?>#lm zrz%-FzI1FBW{lJw6w_Q`|?e^WnvKgBUC6%B|!HF?O9y_~Y z7?Tkr9l{rEL>F~mblzUYLMkBRZr$e?<|i8QHQh7fRX0HO5E3mmFs0n>Rf!e~haj)L{s6Kb}-o?^IOy~0zMfJs$nZ~f;8*LWfj4wByl3WrL z-0be&rL?a_6e|)}F)~?YWGr7tVx~dfn_r2x=C#R6=Fg)5mhJ`CZ4M z)pBuE!Xm#oENq3-xr8o@5{h;t>$or?y?h}+>Yri))ly72L=KYbO~$q8mr38L4{9{i z3#*h9llLQC(2HtIVcsmTte(-_24k!muq;S0MBV#UNcWz3( z5O7tSh9o+XR+ga9W5rY_miHmz>3i>N#O6G3TN?FG1-^d7RC1Dqr0Vx(jZL^=G|zj2 zLZE~*hLbsojE3W5dsf=_!P_hcVn7&on7Rc5CW% z^LYIg!DMS-@q7zyY{hpK#m~PUe01m1l(kxfLiXJ{nl@pZeb`fNlkeSS5rT|Mzc zmH*$JZv1zrErLBAGqv{Hrw-H|$C=3^XM+dnrkew;8T|Lrs9GzA4xFNM5mY4f;Ijuh z#^|k?MN1ww{AiI%%Og9QB52sl3`?&V$Z`5jKQ4mF;e{tFYubEFptly7EU~if)7B17 zvEc!PAHaNtbtUO0T?Yz5UE@_fkknySwN?ajfn8cjcJ+PX}!O zWXUPcx$qwZ!w&ySH5L2{ z+dPx-NjAjU6;26Hp*fX&Noh<+4BV9gN5uqY^QWq6qe3FYVTSs-O~1uLFqRb>>zr^P zlAMiOibh&$E94e@+oNj|Jvdq?6IHGdu~1^Gppi`NA<^Mx=`Ws*X)xe6HzdxuI2Ov zwar@$t*7^+5q$L~$r{zac`=oi*o90)+XU9}pD6b9N8}3Cw<3kSm4TJn)ehgQh7p^J z@G;7obbY~2)QhaZ+IvkxwS1zrDfuT^&?LmFow0>oLHL{IKVsihIvZr?32=bmUJkn~ zQWH2uwU-Q!rsHLzsf++^ol-ql>YQ}n>dvgnriXE?6n)K%rf}wrE?rV;mG+BmCV512 zCHWe5q6bKY zI6xC(PW^=7`dzha6N1c+s*{zA2Gew%DiWCKT})tKeKKQS--JU7RXlIS0_i$=OuqQz zDg3^ZfHBvP^@SLWqPX$a4x()%Rwm10FG^_yLTv1{=-C((Hist2DWw}vRjek)Xf<-C zZCm8+0lA+RMVn3V?4&G`DT_a6*jY*3EL-3H)x>(G+)`)suKi`tz=s-1lhekjKQZ^p$UYjmjRa&CXW5g zMmwg4sZ)#mBRiTFx@bq?ueGuSlob2lCv}qvXcP(TXR}kj28lWv`(RNSO2|2-xSd1D z*=^A~TX;*W7S`Ib!a(-7ILZ={t4g-uM^|d&=K|WpJUc$uEhWNrYKx|=t9;*cmFJA9 z9!%CPT{o$ONA8evZSBI|tQv@x|N2GMNy4nSC+5~2!31X;%mB@@9UX*@k^XAnk;xpA z2#SoU>P;DgXU=_kQK;ywugj;Wo1@)8mLEE(3{kePs=W{|V$G%F_78;0Af~(ECRvtA zS;z5ENs&72zJ$6UaZM<9AK3zMeFt{XlfC@JgS=}6Zr3(DzYAXB6QK4xLf@6<{D2ZD1FyVBXI#W zph-~JZvqrND2`EAWJ|ZUT3z_<5t%btX|{ryAZ6R+FOKK+(q3apMiy7ihDF{7k*=aP zO;1v`YPT=L_k(|LvEZW?s{}8`W(>w1vY-r=&Zv9y({_vRCy6?hwV9i0T_>OIy0W#< z=U18;F~q5=GnrhCH}|Yc1*}_C85U~u(djJF3^a>-j*B(;bJLBcR0+sml#HYu%$qbb zv7C-L)%7GmPK-}7Pe&IO3p%f@&Eq#qHRYBP3D`f1m>T9zDrtANVW_|2@7j|?7 zCDF3ruZKjbk4w7Y_HO7J=UPUc-7{1VXY8RDL55VCZ{yjd)fbg>Oa*V&q${BvKO{4X zM1Hnb!+VfotFH4nt9@ADVcKD3YveGRMf@D8NC9yT9hHku~1jY4??1LoAI zH*7m|T$`{Nt#SXNi7@h7J+=hDF!ikR+5FW%Mz~mO zJij9th3)G8sA#mX2GP0XhuX!{sbTdE3>Au&={+-9m)xNW_!2r!@PWk*y4hRfHzwHa zWY7nd3WCZUeew4FFmr19nQ5HuY-+zodc#ocng%a-!IU{3IkXd_X!c@zAevp|}%H zZmhk~ted{#WQhIfR+iMOd(@grFD%~BmcCBRO9(o4f7Q!?Q#2!*#OYO;O|TfKUvzgx zHTZ_&Ys87A`@Z`t&OOU9x_zwRi-F!e8Twucr|5q6TvjPcEH!UMQZ87zLUh5_0p^d$ zpkGqLM44akb-4=E-Uk3DRcRU@sHL?{15R!+YJ0ZGrs;S;g{gHKoRe$rG&^8T0{);% zpm&onRo<>dcQNO1ahc6hOzPrG-C74R{Cjlr$BmybV}D5tUVMq<`%$mE5^8g5emvKx zBMAUN$jVBHs*`g;wd^79ehUra8edqvkicnzYVma!16;MkFXn3eRn&Qe)wr&4E)X;~ zYpbE;7ew0JMYm9MFZ#`{#4BWKguCdtBdZo;d#C|GuX41%kD zR80lC@e*PAtan&one)2rhv9fiDkWS?N3?J0n_YW0^obRm^3H=-MSB~!P-YQ?yfzcz z16NEspgO~vcI7l9m`9DkI&23RtSV~{W&|Flilj}YwT(AIwp)wU*WQ2@*OAz1Px;(GSx%x>1o zthO+B)I(^oQ=`v{$Q0K$!BX+Lx*eei6<@=FQFBMC^vig&F=DIVM; zGU&L}YqT%O7}3j#Jz-Q{DKl_N+FlhAJX@D!wB9D$Ro5X|Ci`lODtj%|d~=O`-pT>A z3#KA3w)@)dW53PZ(AVU*!)(2?7CH}J3?+pSi;c^qg1zQjpfv~L{egHcEq&HOAcTGc zym)qVew$m<|9@{3L#;IQojt4oS71TeW&c}7+H0RAHD5x?Ly;0 zpZnisAXkpF2T%9sxHOXgU&qM^eEZ26%PA)P->(NPlh)h&3-Jdf76p-R%^m=H$x13o J)QK4f{|A2v%cTGS literal 25939 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 z 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 bEH?TT;b*z!58B|rq@c`pTN)P|I!FHpVF#SR diff --git a/docs/images/1562406479787.png b/docs/images/1562406479787.png index 654348f074987207ad7f3b937713d116aa0dfa9b..8f63b729b6c5cccb16da52edbd95ca40c9980414 100644 GIT binary patch literal 49227 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
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb b/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb deleted file mode 100644 index 655a709c..00000000 --- a/jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb +++ /dev/null @@ -1,2214 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第四章 运算符\n", - "\n", - ">运算符操纵数据。\n", - "\n", - "Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。\n", - "\n", - "如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。\n", - "\n", - "\n", - "## 开始使用\n", - "\n", - "运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。\n", - "\n", - "几乎所有运算符都只能操作基本类型(Primitives)。唯一的例外是 `=`、`==` 和 `!=`,它们能操作所有对象(这也是令人混淆的一个地方)。除此以外,**String** 类支持 `+` 和 `+=`。\n", - "\n", - "\n", - "## 优先级\n", - "\n", - "运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Precedence.java\n", - "public class Precedence {\n", - " \n", - " public static void main(String[] args) {\n", - " int x = 1, y = 2, z = 3;\n", - " int a = x + y - 2/2 + z; // [1]\n", - " int b = x + (y - 2)/(2 + z); // [2]\n", - " System.out.println(\"a = \" + a);\n", - " System.out.println(\"b = \" + b);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " a = 5\n", - " b = 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些语句看起来大致相同,但从输出中我们可以看出它们具有非常不同的含义,具体取决于括号的使用。\n", - "\n", - "我们注意到,在 `System.out.println()` 语句中使用了 `+` 运算符。 但是在这里 `+` 代表的意思是字符串连接符。编译器会将 `+` 连接的非字符串尝试转换为字符串。上例中的输出结果说明了 a 和 b 都已经被转化成了字符串。\n", - "\n", - "\n", - "## 赋值\n", - "\n", - "运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 = A)。\n", - "\n", - "基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。\n", - "\n", - "如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java " - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Assignment.java\n", - "// Assignment with objects is a bit tricky\n", - "class Tank {\n", - " int level;\n", - "}\n", - "\n", - "public class Assignment {\n", - "\n", - " public static void main(String[] args) {\n", - " Tank t1 = new Tank();\n", - " Tank t2 = new Tank();\n", - " t1.level = 9;\n", - " t2.level = 47;\n", - " System.out.println(\"1: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " t1 = t2;\n", - " System.out.println(\"2: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " t1.level = 27;\n", - " System.out.println(\"3: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1: t1.level: 9, t2.level: 47\n", - "2: t1.level: 47, t2.level: 47\n", - "3: t1.level: 27, t2.level: 27" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个简单的 `Tank` 类,在 `main()` 方法创建了两个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。\n", - "\n", - "这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "t1.level = t2.level;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的“惊喜”。\n", - "\n", - " \n", - "### 方法调用中的别名现象\n", - "\n", - "当我们把对象传递给方法时,会发生别名现象。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/PassObject.java\n", - "// 正在传递的对象可能不是你之前使用的\n", - "class Letter {\n", - " char c;\n", - "}\n", - "\n", - "public class PassObject {\n", - " static void f(Letter y) {\n", - " y.c = 'z';\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Letter x = new Letter();\n", - " x.c = 'a';\n", - " System.out.println(\"1: x.c: \" + x.c);\n", - " f(x);\n", - " System.out.println(\"2: x.c: \" + x.c);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1: x.c: a\n", - "2: x.c: z" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在许多编程语言中,方法 `f()` 似乎会在内部复制其参数 **Letter y**。但是一旦传递了一个引用,那么实际上 `y.c ='z';` 是在方法 `f()` 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:[对象传递和返回](./Appendix-Passing-and-Returning-Objects.md)。意识到这一点,我们可以警惕类似的陷阱。\n", - "\n", - "\n", - "## 算术运算符\n", - "\n", - "Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 `+`、减号 `-`、除号 `/`、乘号 `*` 以及取模 `%`(从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。\n", - "\n", - "Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。 可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。更多代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/MathOps.java\n", - "// The mathematical operators\n", - "import java.util.*;\n", - "\n", - "public class MathOps {\n", - " public static void main(String[] args) {\n", - " // Create a seeded random number generator:\n", - " Random rand = new Random(47);\n", - " int i, j, k;\n", - " // Choose value from 1 to 100:\n", - " j = rand.nextInt(100) + 1;\n", - " System.out.println(\"j : \" + j);\n", - " k = rand.nextInt(100) + 1;\n", - " System.out.println(\"k : \" + k);\n", - " i = j + k;\n", - " System.out.println(\"j + k : \" + i);\n", - " i = j - k;\n", - " System.out.println(\"j - k : \" + i);\n", - " i = k / j;\n", - " System.out.println(\"k / j : \" + i);\n", - " i = k * j;\n", - " System.out.println(\"k * j : \" + i);\n", - " i = k % j;\n", - " System.out.println(\"k % j : \" + i);\n", - " j %= k;\n", - " System.out.println(\"j %= k : \" + j);\n", - " // 浮点运算测试\n", - " float u, v, w; // Applies to doubles, too\n", - " v = rand.nextFloat();\n", - " System.out.println(\"v : \" + v);\n", - " w = rand.nextFloat();\n", - " System.out.println(\"w : \" + w);\n", - " u = v + w;\n", - " System.out.println(\"v + w : \" + u);\n", - " u = v - w;\n", - " System.out.println(\"v - w : \" + u);\n", - " u = v * w;\n", - " System.out.println(\"v * w : \" + u);\n", - " u = v / w;\n", - " System.out.println(\"v / w : \" + u);\n", - " // 下面的操作同样适用于 char, \n", - " // byte, short, int, long, and double:\n", - " u += v;\n", - " System.out.println(\"u += v : \" + u);\n", - " u -= v;\n", - " System.out.println(\"u -= v : \" + u);\n", - " u *= v;\n", - " System.out.println(\"u *= v : \" + u);\n", - " u /= v;\n", - " System.out.println(\"u /= v : \" + u); \n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "j : 59\n", - "k : 56\n", - "j + k : 115\n", - "j - k : 3\n", - "k / j : 0\n", - "k * j : 3304\n", - "k % j : 56\n", - "j %= k : 3\n", - "v : 0.5309454\n", - "w : 0.0534122\n", - "v + w : 0.5843576\n", - "v - w : 0.47753322\n", - "v * w : 0.028358962\n", - "v / w : 9.940527\n", - "u += v : 10.471473\n", - "u -= v : 9.940527\n", - "u *= v : 5.2778773\n", - "u /= v : 9.940527" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了生成随机数字,程序首先创建一个 **Random** 对象。不带参数的 **Random** 对象会利用当前的时间用作随机数生成器的“种子”(seed),从而为程序的每次执行生成不同的输出。在本书的示例中,重要的是每个示例末尾的输出尽可能一致,以便可以使用外部工具进行验证。所以我们通过在创建 **Random** 对象时提供种子(随机数生成器的初始化值,其始终为特定种子值产生相同的序列),让程序每次执行都生成相同的随机数,如此以来输出结果就是可验证的 [^1]。 若需要生成随机值,可删除代码示例中的种子参数。该对象通过调用方法 `nextInt()` 和 `nextFloat()`(还可以调用 `nextLong()` 或 `nextDouble()`),使用 **Random** 对象生成许多不同类型的随机数。`nextInt()` 的参数设置生成的数字的上限,下限为零,为了避免零除的可能性,结果偏移1。\n", - "\n", - "\n", - "### 一元加减运算符\n", - "\n", - "一元加 `+` 减 `-` 运算符的操作和二元是相同的。编译器可自动识别使用何种方式解析运算:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = -a;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的代码表意清晰,编译器可正确识别。下面再看一个示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = a * -b;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然编译器可以正确的识别,但是程序员可能会迷惑。为了避免混淆,推荐下面的写法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = a * (-b);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一元减号可以得到数据的负值。一元加号的作用相反,不过它唯一能影响的就是把较小的数值类型自动转换为 **int** 类型。\n", - "\n", - "\n", - "## 递增和递减\n", - "\n", - "和 C 语言类似,Java 提供了许多快捷运算方式。快捷运算可使代码可读性,可写性都更强。其中包括递增 `++` 和递减 `--`,意为“增加或减少一个单位”。举个例子来说,假设 a 是一个 **int** 类型的值,则表达式 `++a` 就等价于 `a = a + 1`。 递增和递减运算符不仅可以修改变量,还可以生成变量的值。\n", - "\n", - "每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀”和“后缀”。“前递增”表示 `++` 运算符位于变量或表达式的前面;而“后递增”表示 `++` 运算符位于变量的后面。类似地,“前递减”意味着 `--` 运算符位于变量的前面;而“后递减”意味着 `--` 运算符位于变量的后面。对于前递增和前递减(如 `++a` 或 `--a`),会先执行递增/减运算,再返回值。而对于后递增和后递减(如 `a++` 或 `a--`),会先返回值,再执行递增/减运算。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/AutoInc.java\n", - "// 演示 ++ 和 -- 运算符\n", - "public class AutoInc {\n", - " public static void main(String[] args) {\n", - " int i = 1;\n", - " System.out.println(\"i: \" + i);\n", - " System.out.println(\"++i: \" + ++i); // 前递增\n", - " System.out.println(\"i++: \" + i++); // 后递增\n", - " System.out.println(\"i: \" + i);\n", - " System.out.println(\"--i: \" + --i); // 前递减\n", - " System.out.println(\"i--: \" + i--); // 后递减\n", - " System.out.println(\"i: \" + i);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i: 1\n", - "++i: 2\n", - "i++: 2\n", - "i: 3\n", - "--i: 2\n", - "i--: 2\n", - "i: 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于前缀形式,我们将在执行递增/减操作后获取值;使用后缀形式,我们将在执行递增/减操作之前获取值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外) —— 它们修改了操作数的值。\n", - "\n", - "C++ 名称来自于递增运算符,暗示着“比 C 更进一步”。在早期的 Java 演讲中,*Bill Joy*(Java 作者之一)说“**Java = C++ --**”(C++ 减减),意味着 Java 在 C++ 的基础上减少了许多不必要的东西,因此语言更简单。随着进一步地学习,我们会发现 Java 的确有许多地方相对 C++ 来说更简便,但是在其他方面,难度并不会比 C++ 小多少。\n", - "\n", - "\n", - "## 关系运算符\n", - "\n", - "关系运算符会通过产生一个布尔(**boolean**)结果来表示操作数之间的关系。如果关系为真,则结果为 **true**,如果关系为假,则结果为 **false**。关系运算符包括小于 `<`,大于 `>`,小于或等于 `<=`,大于或等于 `>=`,等于 `==` 和不等于 `!=`。`==` 和 `!=` 可用于所有基本类型,但其他运算符不能用于基本类型 **boolean**,因为布尔值只能表示 **true** 或 **false**,所以比较它们之间的“大于”或“小于”没有意义。\n", - "\n", - "\n", - "### 测试对象等价\n", - "\n", - "关系运算符 `==` 和 `!=` 同样适用于所有对象之间的比较运算,但它们比较的内容却经常困扰 Java 的初学者。下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Equivalence.java\n", - "public class Equivalence {\n", - " public static void main(String[] args) {\n", - " Integer n1 = 47;\n", - " Integer n2 = 47;\n", - " System.out.println(n1 == n2);\n", - " System.out.println(n1 != n2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "true\n", - "false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把 47 改成 128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。\n", - "\n", - "那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 `equals()` 方法,下面是如何使用 `equals()` 方法的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/EqualsMethod.java\n", - "public class EqualsMethod {\n", - " public static void main(String[] args) {\n", - " Integer n1 = 47;\n", - " Integer n2 = 47;\n", - " System.out.println(n1.equals(n2));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "true" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的结果看起来是我们所期望的。但其实事情并非那么简单。下面我们来创建自己的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/EqualsMethod2.java\n", - "// 默认的 equals() 方法没有比较内容\n", - "class Value {\n", - " int i;\n", - "}\n", - "\n", - "public class EqualsMethod2 {\n", - " public static void main(String[] args) {\n", - " Value v1 = new Value();\n", - " Value v2 = new Value();\n", - " v1.i = v2.i = 100;\n", - " System.out.println(v1.equals(v2));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的结果再次令人困惑:结果是 **false**。原因: `equals()` 的默认行为是比较对象的引用而非具体内容。因此,除非你在新类中覆写 `equals()` 方法,否则我们将获取不到想要的结果。不幸的是,在学习 [复用](./08-Reuse.md)(**Reuse**) 章节后我们才能接触到“覆写”(**Override**),并且直到 [附录:集合主题](./Appendix-Collection-Topics.md),才能知道定义 `equals()` 方法的正确方式,但是现在明白 `equals()` 行为方式也可能为你节省一些时间。\n", - "\n", - "大多数 Java 库类通过覆写 `equals()` 方法比较对象的内容而不是其引用。\n", - "\n", - "\n", - "## 逻辑运算符\n", - "\n", - "每个逻辑运算符 `&&` (**AND**)、`||`(**OR**)和 `!`(**非**)根据参数的逻辑关系生成布尔值 `true` 或 `false`。下面的代码示例使用了关系运算符和逻辑运算符:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Bool.java\n", - "// 关系运算符和逻辑运算符\n", - "import java.util.*;\n", - "public class Bool {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " int i = rand.nextInt(100);\n", - " int j = rand.nextInt(100);\n", - " System.out.println(\"i = \" + i);\n", - " System.out.println(\"j = \" + j);\n", - " System.out.println(\"i > j is \" + (i > j));\n", - " System.out.println(\"i < j is \" + (i < j));\n", - " System.out.println(\"i >= j is \" + (i >= j));\n", - " System.out.println(\"i <= j is \" + (i <= j));\n", - " System.out.println(\"i == j is \" + (i == j));\n", - " System.out.println(\"i != j is \" + (i != j));\n", - " // 将 int 作为布尔处理不是合法的 Java 写法\n", - " //- System.out.println(\"i && j is \" + (i && j));\n", - " //- System.out.println(\"i || j is \" + (i || j));\n", - " //- System.out.println(\"!i is \" + !i);\n", - " System.out.println(\"(i < 10) && (j < 10) is \"\n", - " + ((i < 10) && (j < 10)) );\n", - " System.out.println(\"(i < 10) || (j < 10) is \"\n", - " + ((i < 10) || (j < 10)) );\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 58\n", - "j = 55\n", - "i > j is true\n", - "i < j is false\n", - "i >= j is true\n", - "i <= j is false\n", - "i == j is false\n", - "i != j is true\n", - "(i < 10) && (j < 10) is false\n", - "(i < 10) || (j < 10) is false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、 **OR**、 **NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 \"true\" 或 \"false\" 字符串)。\n", - "\n", - "我们可以将前一个程序中 **int** 的定义替换为除 **boolean** 之外的任何其他基本数据类型。但请注意,**float** 类型的数值比较非常严格,只要两个数字的最小位不同则两个数仍然不相等;只要数字最小位是大于 0 的,那么它就不等于 0。\n", - "\n", - "\n", - "### 短路\n", - "\n", - "逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators / ShortCircuit.java \n", - "// 逻辑运算符的短路行为\n", - "public class ShortCircuit {\n", - "\n", - " static boolean test1(int val) {\n", - " System.out.println(\"test1(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 1));\n", - " return val < 1;\n", - " }\n", - "\n", - " static boolean test2(int val) {\n", - " System.out.println(\"test2(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 2));\n", - " return val < 2;\n", - " }\n", - "\n", - " static boolean test3(int val) {\n", - " System.out.println(\"test3(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 3));\n", - " return val < 3;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " boolean b = test1(0) && test2(2) && test3(2);\n", - " System.out.println(\"expression is \" + b);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test1(0)\n", - "result: true\n", - "test2(2)\n", - "result: false\n", - "expression is false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个测试都对参数执行比较并返回 `true` 或 `false`。同时控制台也会在方法执行时打印他们的执行状态。 下面的表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "test1(0)&& test2(2)&& test3(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可能你的预期是程序会执行 3 个 **test** 方法并返回。我们来分析一下:第一个方法的结果返回 `true`,因此表达式会继续走下去。紧接着,第二个方法的返回结果是 `false`。这就代表这整个表达式的结果肯定为 `false`,所以就没有必要再判断剩下的表达式部分了。\n", - "\n", - "所以,运用“短路”可以节省部分不必要的运算,从而提高程序潜在的性能。\n", - "\n", - "\n", - "## 字面值常量\n", - "\n", - "通常,当我们向程序中插入一个字面值常量(**Literal**)时,编译器会确切地识别它的类型。当类型不明确时,必须辅以字面值常量关联来帮助编译器识别。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Literals.java\n", - "public class Literals {\n", - " public static void main(String[] args) {\n", - " int i1 = 0x2f; // 16进制 (小写)\n", - " System.out.println(\n", - " \"i1: \" + Integer.toBinaryString(i1));\n", - " int i2 = 0X2F; // 16进制 (大写)\n", - " System.out.println(\n", - " \"i2: \" + Integer.toBinaryString(i2));\n", - " int i3 = 0177; // 8进制 (前导0)\n", - " System.out.println(\n", - " \"i3: \" + Integer.toBinaryString(i3));\n", - " char c = 0xffff; // 最大 char 型16进制值\n", - " System.out.println(\n", - " \"c: \" + Integer.toBinaryString(c));\n", - " byte b = 0x7f; // 最大 byte 型16进制值 10101111;\n", - " System.out.println(\n", - " \"b: \" + Integer.toBinaryString(b));\n", - " short s = 0x7fff; // 最大 short 型16进制值\n", - " System.out.println(\n", - " \"s: \" + Integer.toBinaryString(s));\n", - " long n1 = 200L; // long 型后缀\n", - " long n2 = 200l; // long 型后缀 (容易与数值1混淆)\n", - " long n3 = 200;\n", - " \n", - " // Java 7 二进制字面值常量:\n", - " byte blb = (byte)0b00110101;\n", - " System.out.println(\n", - " \"blb: \" + Integer.toBinaryString(blb));\n", - " short bls = (short)0B0010111110101111;\n", - " System.out.println(\n", - " \"bls: \" + Integer.toBinaryString(bls));\n", - " int bli = 0b00101111101011111010111110101111;\n", - " System.out.println(\n", - " \"bli: \" + Integer.toBinaryString(bli));\n", - " long bll = 0b00101111101011111010111110101111;\n", - " System.out.println(\n", - " \"bll: \" + Long.toBinaryString(bll));\n", - " float f1 = 1;\n", - " float f2 = 1F; // float 型后缀\n", - " float f3 = 1f; // float 型后缀\n", - " double d1 = 1d; // double 型后缀\n", - " double d2 = 1D; // double 型后缀\n", - " // (long 型的字面值同样适用于十六进制和8进制 )\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i1: 101111\n", - "i2: 101111\n", - "i3: 1111111\n", - "c: 1111111111111111\n", - "b: 1111111\n", - "s: 111111111111111\n", - "blb: 110101\n", - "bls: 10111110101111\n", - "bli: 101111101011111010111110101111\n", - "bll: 101111101011111010111110101111" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在文本值的后面添加字符可以让编译器识别该文本值的类型。对于 **Long** 型数值,结尾使用大写 `L` 或小写 `l` 皆可(不推荐使用 `l`,因为容易与阿拉伯数值 1 混淆)。大写 `F` 或小写 `f` 表示 **float** 浮点数。大写 `D` 或小写 `d` 表示 **double** 双精度。\n", - "\n", - "十六进制(以 16 为基数),适用于所有整型数据类型,由前导 `0x` 或 `0X` 表示,后跟 0-9 或 a-f (大写或小写)。如果我们在初始化某个类型的数值时,赋值超出其范围,那么编译器会报错(不管值的数字形式如何)。在上例的代码中,**char**、**byte** 和 **short** 的值已经是最大了。如果超过这些值,编译器将自动转型为 **int**,并且提示我们需要声明强制转换(强制转换将在本章后面定义),意味着我们已越过该类型的范围界限。\n", - "\n", - "八进制(以 8 为基数)由 0~7 之间的数字和前导零 `0` 表示。\n", - "\n", - "Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,它可以初始化所有的整数类型。\n", - "\n", - "使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。\n", - "\n", - "\n", - "### 下划线\n", - "\n", - "Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 `_`,以使结果更清晰。这对于大数值的分组特别有用。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Underscores.java\n", - "public class Underscores {\n", - " public static void main(String[] args) {\n", - " double d = 341_435_936.445_667;\n", - " System.out.println(d);\n", - " int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;\n", - " System.out.println(Integer.toBinaryString(bin));\n", - " System.out.printf(\"%x%n\", bin); // [1]\n", - " long hex = 0x7f_e9_b7_aa;\n", - " System.out.printf(\"%x%n\", hex);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "3.41435936445667E8\n", - "101111101011111010111110101111\n", - "2fafafaf\n", - "7fe9b7aa" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "下面是合理使用的规则:\n", - "\n", - "1. 仅限单 `_`,不能多条相连。\n", - "2. 数值开头和结尾不允许出现 `_`。\n", - "3. `F`、`D` 和 `L`的前后禁止出现 `_`。\n", - "4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。\n", - "\n", - "[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\\r\\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。\n", - "\n", - "\n", - "### 指数计数法\n", - "\n", - "指数总是采用一种我认为很不直观的记号方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Exponents.java\n", - "// \"e\" 表示 10 的几次幂\n", - "public class Exponents {\n", - " public static void main(String[] args) {\n", - " // 大写 E 和小写 e 的效果相同:\n", - " float expFloat = 1.39e-43f;\n", - " expFloat = 1.39E-43f;\n", - " System.out.println(expFloat);\n", - " double expDouble = 47e47d; // 'd' 是可选的\n", - " double expDouble2 = 47e47; // 自动转换为 double\n", - " System.out.println(expDouble);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1.39E-43\n", - "4.7E48" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 \"1.39 x e-43\",意味着 “1.39 × 2.718 的 -43 次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10 的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。\n", - "\n", - "理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是 “1.39 × 10 的 -43 次方”。\n", - "\n", - "注意如果编译器能够正确地识别类型,就不必使用后缀字符。对于下述语句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "long n3 = 200;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它并不存在含糊不清的地方,所以 200 后面的 L 大可省去。然而,对于下述语句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "float f4 = 1e-43f; //10 的幂数" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器通常会将指数作为 **double** 类型来处理,所以假若没有这个后缀字符 `f`,编译器就会报错,提示我们应该将 **double** 型转换成 **float** 型。\n", - "\n", - "\n", - "## 位运算符\n", - "\n", - "位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。\n", - "\n", - "位运算源自 C 语言的底层操作。我们经常要直接操纵硬件,频繁设置硬件寄存器内的二进制位。Java 的设计初衷是电视机顶盒嵌入式开发,所以这种底层的操作仍被保留了下来。但是,你可能不会使用太多位运算。\n", - "\n", - "若两个输入位都是 1,则按位“与运算符” `&` 运算后结果是 1,否则结果是 0。若两个输入位里至少有一个是 1,则按位“或运算符” `|` 运算后结果是 1;只有在两个输入位都是 0 的情况下,运算结果才是 0。若两个输入位的某一个是 1,另一个不是 1,那么按位“异或运算符” `^` 运算后结果才是 1。按位“非运算符” `~` 属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位非运算后结果与输入位相反。例如输入 0,则输出 1;输入 1,则输出 0。\n", - "\n", - "位运算符和逻辑运算符都使用了同样的字符,只不过数量不同。位短,所以位运算符只有一个字符。位运算符可与等号 `=` 联合使用以接收结果及赋值:`&=`,`|=` 和 `^=` 都是合法的(由于 `~` 是一元运算符,所以不可与 `=` 联合使用)。\n", - "\n", - "我们将 **Boolean** 类型被视为“单位值”(one-bit value),所以它多少有些独特的地方。我们可以对 boolean 型变量执行与、或、异或运算,但不能执行非运算(大概是为了避免与逻辑“非”混淆)。对于布尔值,位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的位运算为我们新增了一个“异或”逻辑运算符,它并未包括在逻辑运算符的列表中。在移位表达式中,禁止使用布尔值,原因将在下面解释。\n", - "\n", - "\n", - "## 移位运算符\n", - "\n", - "移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。\n", - "\n", - "如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。\n", - "\n", - "移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/URShift.java\n", - "// 测试无符号右移\n", - "public class URShift {\n", - " public static void main(String[] args) {\n", - " int i = -1;\n", - " System.out.println(Integer.toBinaryString(i));\n", - " i >>>= 10;\n", - " System.out.println(Integer.toBinaryString(i));\n", - " long l = -1;\n", - " System.out.println(Long.toBinaryString(l));\n", - " l >>>= 10;\n", - " System.out.println(Long.toBinaryString(l));\n", - " short s = -1;\n", - " System.out.println(Integer.toBinaryString(s));\n", - " s >>>= 10;\n", - " System.out.println(Integer.toBinaryString(s));\n", - " byte b = -1;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " b >>>= 10;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " b = -1;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " System.out.println(Integer.toBinaryString(b>>>10));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "11111111111111111111111111111111\n", - "1111111111111111111111\n", - "1111111111111111111111111111111111111111111111111111111111111111\n", - "111111111111111111111111111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "1111111111111111111111" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在上例中,结果并未重新赋值给变量 **b** ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/BitManipulation.java\n", - "// 使用位运算符\n", - "import java.util.*;\n", - "public class BitManipulation {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " int i = rand.nextInt();\n", - " int j = rand.nextInt();\n", - " printBinaryInt(\"-1\", -1);\n", - " printBinaryInt(\"+1\", +1);\n", - " int maxpos = 2147483647;\n", - " printBinaryInt(\"maxpos\", maxpos);\n", - " int maxneg = -2147483648;\n", - " printBinaryInt(\"maxneg\", maxneg);\n", - " printBinaryInt(\"i\", i);\n", - " printBinaryInt(\"~i\", ~i);\n", - " printBinaryInt(\"-i\", -i);\n", - " printBinaryInt(\"j\", j);\n", - " printBinaryInt(\"i & j\", i & j);\n", - " printBinaryInt(\"i | j\", i | j);\n", - " printBinaryInt(\"i ^ j\", i ^ j);\n", - " printBinaryInt(\"i << 5\", i << 5);\n", - " printBinaryInt(\"i >> 5\", i >> 5);\n", - " printBinaryInt(\"(~i) >> 5\", (~i) >> 5);\n", - " printBinaryInt(\"i >>> 5\", i >>> 5);\n", - " printBinaryInt(\"(~i) >>> 5\", (~i) >>> 5);\n", - " long l = rand.nextLong();\n", - " long m = rand.nextLong();\n", - " printBinaryLong(\"-1L\", -1L);\n", - " printBinaryLong(\"+1L\", +1L);\n", - " long ll = 9223372036854775807L;\n", - " printBinaryLong(\"maxpos\", ll);\n", - " long lln = -9223372036854775808L;\n", - " printBinaryLong(\"maxneg\", lln);\n", - " printBinaryLong(\"l\", l);\n", - " printBinaryLong(\"~l\", ~l);\n", - " printBinaryLong(\"-l\", -l);\n", - " printBinaryLong(\"m\", m);\n", - " printBinaryLong(\"l & m\", l & m);\n", - " printBinaryLong(\"l | m\", l | m);\n", - " printBinaryLong(\"l ^ m\", l ^ m);\n", - " printBinaryLong(\"l << 5\", l << 5);\n", - " printBinaryLong(\"l >> 5\", l >> 5);\n", - " printBinaryLong(\"(~l) >> 5\", (~l) >> 5);\n", - " printBinaryLong(\"l >>> 5\", l >>> 5);\n", - " printBinaryLong(\"(~l) >>> 5\", (~l) >>> 5);\n", - " }\n", - "\n", - " static void printBinaryInt(String s, int i) {\n", - " System.out.println(\n", - " s + \", int: \" + i + \", binary:\\n \" +\n", - " Integer.toBinaryString(i));\n", - " }\n", - "\n", - " static void printBinaryLong(String s, long l) {\n", - " System.out.println(\n", - " s + \", long: \" + l + \", binary:\\n \" +\n", - " Long.toBinaryString(l));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果(前 32 行):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-1, int: -1, binary:\n", - "11111111111111111111111111111111\n", - "+1, int: 1, binary:\n", - "1\n", - "maxpos, int: 2147483647, binary:\n", - "1111111111111111111111111111111\n", - "maxneg, int: -2147483648, binary:\n", - "10000000000000000000000000000000\n", - "i, int: -1172028779, binary:\n", - "10111010001001000100001010010101\n", - "~i, int: 1172028778, binary:\n", - " 1000101110110111011110101101010\n", - "-i, int: 1172028779, binary:\n", - "1000101110110111011110101101011\n", - "j, int: 1717241110, binary:\n", - "1100110010110110000010100010110\n", - "i & j, int: 570425364, binary:\n", - "100010000000000000000000010100\n", - "i | j, int: -25213033, binary:\n", - "11111110011111110100011110010111\n", - "i ^ j, int: -595638397, binary:\n", - "11011100011111110100011110000011\n", - "i << 5, int: 1149784736, binary:\n", - "1000100100010000101001010100000\n", - "i >> 5, int: -36625900, binary:\n", - "11111101110100010010001000010100\n", - "(~i) >> 5, int: 36625899, binary:\n", - "10001011101101110111101011\n", - "i >>> 5, int: 97591828, binary:\n", - "101110100010010001000010100\n", - "(~i) >>> 5, int: 36625899, binary:\n", - "10001011101101110111101011\n", - " ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。\n", - "\n", - "\n", - "## 三元运算符\n", - "\n", - "三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 **if-else** 语句是不同的。下面是它的表达式格式:\n", - "\n", - "**布尔表达式 ? 值 1 : 值 2**\n", - "\n", - "若表达式计算为 **true**,则返回结果 **值 1** ;如果表达式的计算为 **false**,则返回结果 **值 2**。\n", - "\n", - "当然,也可以换用普通的 **if-else** 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 **if-else** 不同的是,三元运算符是有返回结果的。请看下面的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/TernaryIfElse.java\n", - "public class TernaryIfElse {\n", - " \n", - "static int ternary(int i) {\n", - " return i < 10 ? i * 100 : i * 10;\n", - "}\n", - "\n", - "static int standardIfElse(int i) {\n", - " if(i < 10)\n", - " return i * 100;\n", - " else\n", - " return i * 10;\n", - "}\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(ternary(9));\n", - " System.out.println(ternary(10));\n", - " System.out.println(standardIfElse(9));\n", - " System.out.println(standardIfElse(10));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "900\n", - "100\n", - "900\n", - "100" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看出,`ternary()` 中的代码更简短。然而,**standardIfElse()** 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。\n", - "\n", - "\n", - "## 字符串运算符\n", - "\n", - "这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 `+` 的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。\n", - "\n", - "我们注意到运用 `String +` 时有一些有趣的现象。若表达式以一个 **String** 类型开头(编译器会自动将双引号 `\"\"` 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/StringOperators.java\n", - "public class StringOperators {\n", - " public static void main(String[] args) {\n", - " int x = 0, y = 1, z = 2;\n", - " String s = \"x, y, z \";\n", - " System.out.println(s + x + y + z);\n", - " // 将 x 转换为字符串\n", - " System.out.println(x + \" \" + s);\n", - " s += \"(summed) = \"; \n", - " // 级联操作\n", - " System.out.println(s + (x + y + z));\n", - " // Integer.toString()方法的简写:\n", - " System.out.println(\"\" + x);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x, y, z 012\n", - "0 x, y, z\n", - "x, y, z (summed) = 3\n", - "0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意**:上例中第 1 输出语句的执行结果是 `012` 而并非 `3`,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 **s** 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 `+=` 运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 `s`。括号 `()` 可以控制表达式的计算顺序,以便在显示 **int** 之前对其进行实际求和。\n", - "\n", - "请注意主方法中的最后一个例子:我们经常会看到一个空字符串 `\"\"` 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 **Integer.toString()**)。\n", - "\n", - "\n", - "## 常见陷阱\n", - "\n", - "使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 `()`。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "while(x = y) {\n", - "// ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。\n", - "\n", - "唯一有种情况例外:当变量 `x` 和 `y` 都是布尔值,例如 `x=y` 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。\n", - "\n", - "在 C/C++ 里,类似的一个问题还有使用按位“与” `&` 和“或” `|` 运算,而非逻辑“与” `&&` 和“或” `||`。就象 `=` 和 `==` 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。\n", - "\n", - "\n", - "## 类型转换\n", - "\n", - "“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 **float** 变量赋值一个整数值,计算机会将 **int** 自动转换成 **float**。我们可以在程序未自动转换时显式、强制地使此类型发生转换。\n", - "\n", - "要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Casting.java\n", - "public class Casting {\n", - " public static void main(String[] args) {\n", - " int i = 200;\n", - " long lng = (long)i;\n", - " lng = i; // 没有必要的类型提升\n", - " long lng2 = (long)200;\n", - " lng2 = 200;\n", - " // 类型收缩\n", - " i = (int)lng2; // Cast required\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 **int** 型数据为 **long** 型。\n", - "\n", - "当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。\n", - "\n", - "除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。\n", - "\n", - "\n", - "### 截断和舍入\n", - "\n", - "在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/CastingNumbers.java\n", - "// 尝试转换 float 和 double 型数据为整型数据\n", - "public class CastingNumbers {\n", - " public static void main(String[] args) {\n", - " double above = 0.7, below = 0.4;\n", - " float fabove = 0.7f, fbelow = 0.4f;\n", - " System.out.println(\"(int)above: \" + (int)above);\n", - " System.out.println(\"(int)below: \" + (int)below);\n", - " System.out.println(\"(int)fabove: \" + (int)fabove);\n", - " System.out.println(\"(int)fbelow: \" + (int)fbelow);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "(int)above: 0\n", - "(int)below: 0\n", - "(int)fabove: 0\n", - "(int)fbelow: 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,答案是,从 **float** 和 **double** 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 `java.lang.Math` 的 ` round()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/RoundingNumbers.java\n", - "// float 和 double 类型数据的四舍五入\n", - "public class RoundingNumbers {\n", - " public static void main(String[] args) {\n", - " double above = 0.7, below = 0.4;\n", - " float fabove = 0.7f, fbelow = 0.4f;\n", - " System.out.println(\n", - " \"Math.round(above): \" + Math.round(above));\n", - " System.out.println(\n", - " \"Math.round(below): \" + Math.round(below));\n", - " System.out.println(\n", - " \"Math.round(fabove): \" + Math.round(fabove));\n", - " System.out.println(\n", - " \"Math.round(fbelow): \" + Math.round(fbelow));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Math.round(above): 1\n", - "Math.round(below): 0\n", - "Math.round(fabove): 1\n", - "Math.round(fbelow): 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 `round()` 方法是 `java.lang` 的一部分,所以我们无需通过 `import` 就可以使用。\n", - "\n", - "\n", - "### 类型提升\n", - "\n", - "你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。\n", - "\n", - "\n", - "## Java没有sizeof\n", - "\n", - "在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。\n", - "\n", - "Java 不需要 ` sizeof()` 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。\n", - "\n", - "\n", - "## 运算符总结\n", - "\n", - "上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 `//` 过滤了。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/AllOps.java\n", - "// 测试所有基本类型的运算符操作\n", - "// 看看哪些是能被 Java 编译器接受的\n", - "public class AllOps {\n", - " // 布尔值的接收测试:\n", - " void f(boolean b) {}\n", - " void boolTest(boolean x, boolean y) {\n", - " // 算数运算符:\n", - " //- x = x * y;\n", - " //- x = x / y;\n", - " //- x = x % y;\n", - " //- x = x + y;\n", - " //- x = x - y;\n", - " //- x++;\n", - " //- x--;\n", - " //- x = +y;\n", - " //- x = -y;\n", - " // 关系运算符和逻辑运算符:\n", - " //- f(x > y);\n", - " //- f(x >= y);\n", - " //- f(x < y);\n", - " //- f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " f(!y);\n", - " x = x && y;\n", - " x = x || y;\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " //- x += y;\n", - " //- x -= y;\n", - " //- x *= y;\n", - " //- x /= y;\n", - " //- x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- char c = (char)x;\n", - " //- byte b = (byte)x;\n", - " //- short s = (short)x;\n", - " //- int i = (int)x;\n", - " //- long l = (long)x;\n", - " //- float f = (float)x;\n", - " //- double d = (double)x;\n", - " }\n", - "\n", - " void charTest(char x, char y) {\n", - " // 算数运算符:\n", - " x = (char)(x * y);\n", - " x = (char)(x / y);\n", - " x = (char)(x % y);\n", - " x = (char)(x + y);\n", - " x = (char)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (char) + y;\n", - " x = (char) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x= (char)~y;\n", - " x = (char)(x & y);\n", - " x = (char)(x | y);\n", - " x = (char)(x ^ y);\n", - " x = (char)(x << 1);\n", - " x = (char)(x >> 1);\n", - " x = (char)(x >>> 1);\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换\n", - " //- boolean bl = (boolean)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void byteTest(byte x, byte y) {\n", - " // 算数运算符:\n", - " x = (byte)(x* y);\n", - " x = (byte)(x / y);\n", - " x = (byte)(x % y);\n", - " x = (byte)(x + y);\n", - " x = (byte)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (byte) + y;\n", - " x = (byte) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " //按位运算符:\n", - " x = (byte)~y;\n", - " x = (byte)(x & y);\n", - " x = (byte)(x | y);\n", - " x = (byte)(x ^ y);\n", - " x = (byte)(x << 1);\n", - " x = (byte)(x >> 1);\n", - " x = (byte)(x >>> 1);\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void shortTest(short x, short y) {\n", - " // 算术运算符:\n", - " x = (short)(x * y);\n", - " x = (short)(x / y);\n", - " x = (short)(x % y);\n", - " x = (short)(x + y);\n", - " x = (short)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (short) + y;\n", - " x = (short) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = (short) ~ y;\n", - " x = (short)(x & y);\n", - " x = (short)(x | y);\n", - " x = (short)(x ^ y);\n", - " x = (short)(x << 1);\n", - " x = (short)(x >> 1);\n", - " x = (short)(x >>> 1);\n", - " // Compound assignment:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void intTest(int x, int y) {\n", - " // 算术运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " x = x << 1;\n", - " x = x >> 1;\n", - " x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void longTest(long x, long y) {\n", - " // 算数运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " x = x << 1;\n", - " x = x >> 1;\n", - " x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void floatTest(float x, float y) {\n", - " // 算数运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " //- x = x & y;\n", - " //- x = x | y;\n", - " //- x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " //- x &= y;\n", - " //- x ^= y;\n", - " //- x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void doubleTest(double x, double y) {\n", - " // 算术运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " //- x = x & y;\n", - " //- x = x | y;\n", - " //- x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " //- x &= y;\n", - " //- x ^= y;\n", - " //- x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意** :**boolean** 类型的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。\n", - "\n", - "在 **char**,**byte** 和 **short** 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 **int** 类型的运算则不用转换,因为默认就是 **int** 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 **int** 型的范围,这种情况下结果会发生溢出。下面的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Overflow.java\n", - "// 厉害了!内存溢出\n", - "public class Overflow {\n", - " public static void main(String[] args) {\n", - " int big = Integer.MAX_VALUE;\n", - " System.out.println(\"big = \" + big);\n", - " int bigger = big * 4;\n", - " System.out.println(\"bigger = \" + bigger);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "text" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "big = 2147483647\n", - "bigger = -4" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。\n", - "\n", - "对于 **char**,**byte** 或者 **short**,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 **boolean** 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "如果你已接触过一门 C 语法风格编程语言,那么你在学习 Java 的运算符时实际上没有任何曲线。如果你觉得有难度,那么我推荐你要先去 www.OnJava8.com 观看 《Thinking in C》 的视频教程来补充一些前置知识储备。\n", - "\n", - "[^1]: 我在 *Pomona College* 大学读过两年本科,在那里 47 被称之为“魔法数字”(*magic number*),详见 [维基百科](https://en.wikipedia.org/wiki/47_(number)) 。\n", - "\n", - "[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/00-Introduction.ipynb b/jupyter/00-Introduction.ipynb deleted file mode 100644 index 01651402..00000000 --- a/jupyter/00-Introduction.ipynb +++ /dev/null @@ -1,111 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 简介\n", - "\n", - "> “我的语言极限,即是我的世界的极限。” ——路德维希·维特根斯坦(*Wittgenstein*)\n", - "\n", - "这句话无论对于自然语言还是编程语言来说都是一样的。你所使用的编程语言会将你的思维模式固化并逐渐远离其他语言,而且往往发生在潜移默化中。Java 作为一门傲娇的语言尤其如此。\n", - "\n", - "Java 是一门派生语言,早期语言设计者为了不想在项目中使用 C++ 而创造了这种看起来很像 C++,却比 C++ 有了改进的新语言(原始的项目并未成功)。Java 最核心的变化就是加入了“虚拟机”和“垃圾回收机制”,这两个概念在之后的章节会有详细描述。 此外,Java 还在其他方面推动了行业发展。例如,现在绝大多数编程语言都支持文档注释语法和 HTML 文档生成工具。\n", - "\n", - "Java 最主要的概念之一“对象”来自 SmallTalk 语言。SmallTalk 语言恪守“对象”(在下一章中描述)是编程的最基本单元。于是,万物皆对象。历经时间的检验,人们发现这种信念太过狂热。有些人甚至认为“对象”的概念是完全错误的,应该舍弃。就我个人而言,把一切事物都抽象成对象不仅是一项不必要的负担,同时还会招致许多设计朝着不好的方向发展。尽管如此,“对象”的概念依然有其闪光点。固执地要求所有东西都是一个对象(特别是一直到最底层级别)是一种设计错误;相反,完全逃避“对象”的概念似乎同样太过苛刻。\n", - "\n", - "Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中,我将尝试解释这些原因,力争让读者知晓这些功能,并明白为什么这些功能最终并不适用。这无关 Java 是一种好语言或者坏语言,一旦你了解了该语言的缺陷和局限性,你就能够:\n", - "\n", - "1. 明白有些功能特性为什么会被“废弃”。\n", - "\n", - "2. 熟悉语言边界,更好地设计和编码。\n", - "\n", - "编程的过程就是复杂性管理的过程:业务问题的复杂性,以及依赖的计算机的复杂性。由于这种复杂性,我们的大多数软件项目都失败了。\n", - "\n", - "许多语言设计决策时都考虑到了复杂性,并试图降低语言的复杂性,但在设计过程中遇到了一些更棘手的问题,最终导致语言设计不可避免地“碰壁”,复杂性增加。例如,C++ 必须向后兼容 C(允许 C 程序员轻松迁移),并且效率很高。这些目标非常实用,并且也是 C++ 在编程界取得了成功的原因之一,但同时也引入了额外的复杂性,导致某些用 C++ 编写的项目开发失败。当然,你可以责怪程序员和管理人员手艺不精,但如果有一种编程语言可以帮助你在开发过程中发现错误,那岂不是更好?\n", - "\n", - "虽然 VB(Visual BASIC)绑定在 BASIC 上,但 BASIC 实际上并不是一种可扩展的语言。大量扩展的堆积造成 VB 的语法难以维护。Perl 向后兼容 awk、sed、grep 以及其它要替换的 Unix 工具。因此它常常被诟病产生了一堆“只写代码”(*write-only code*,写代码的人自己都看不懂的代码)。另一方面,C ++,VB,Perl 和其他语言(如 SmallTalk)在设计时重点放在了对某些复杂问题的处理上,因而在解决这些特定类型的问题方面非常成功。\n", - "\n", - "通信革命使我们相互沟通更加便利。无论是一对一沟通,还是团队里的互相沟通,甚至是地球上不同地区的沟通。据说下一次革命需要的是一种全球性的思维,这种思维源于足量的人以及足量相互连接。我不知道 Java 是否能成为这场革命的工具之一,但至少这种可能性让我觉得:我现在正在做的传道授业的事情是有意义的!\n", - "\n", - "## 前提条件\n", - "\n", - "阅读本书需要读者对编程有基本的了解:\n", - "\n", - "- 程序是一系列“陈述(语句、代码)”构成\n", - "\n", - "- 子程序、方法、宏的概念\n", - "\n", - "- 控制语句(例如 **if**),循环结构(例如 **while**)\n", - "\n", - "可能你已在学校、书籍或网络上了学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。\n", - "\n", - "你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面向对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。\n", - "\n", - "## JDK文档\n", - "\n", - "甲骨文公司已经提供了免费的标准 JDK 文档。除非有必要,否则本书中将不再赘述 API 相关的使用细节。使用浏览器来即时搜索最新最全的 JDK 文档好过翻阅本书来查找。只有在需要补充特定的示例时,我才会提供有关的额外描述。\n", - "\n", - "## C编程思想\n", - "\n", - "*Thinking in C* 已经可以在 [www.OnJava8.com](https://archive.org/details/ThinkingInC) 免费下载。Java 的基础语法是基于 C 语言的。*Thinking in C* 中有更适合初学者的编程基础介绍。 我已经委托 Chuck Allison 将这本 C 基础的书籍作为独立产品附赠于本书的 CD 中。希望大家在阅读本书时,都已具备了学习 Java 的良好基础。\n", - "\n", - "## 源码下载\n", - "\n", - "本书中所有源代码的示例都在版权保护的前提下通过 GitHub 免费提供。你可以将这些代码用于教育。任何人不得在未经正确引用代码来源的情况下随意重新发布此代码示例。在每个代码文件中,你都可以找到以下版权声明文件作为参考:\n", - "\n", - "**Copyright.txt**\n", - "\n", - "©2017 MindView LLC。版权所有。如果上述版权声明,本段和以下内容,特此授予免费使用,复制,修改和分发此计算机源代码(源代码)及其文档的许可,且无需出于下述目的的书面协议所有副本中都有五个编号的段落。\n", - "\n", - "1. 允许编译源代码并将编译代码仅以可执行格式包含在个人和商业软件程序中。\n", - "\n", - "2. 允许在课堂情况下使用源代码而不修改源代码,包括在演示材料中,前提是 “On Java 8” 一书被引用为原点。\n", - "\n", - "3. 可以通过以下方式获得将源代码合并到印刷媒体中的许可:MindView LLC,PO Box 969,Crested Butte,CO 81224 MindViewInc@gmail.com \n", - "\n", - "4. 源代码和文档的版权归 MindView LLC 所有。提供的源代码没有任何明示或暗示的担保,包括任何适销性,适用于特定用途或不侵权的默示担保。MindView LLC 不保证任何包含源代码的程序的运行不会中断或没有错误。MindView LLC 不对任何目的的源代码或包含源代码的任何软件的适用性做出任何陈述。包含源代码的任何程序的质量和性能的全部风险来自源代码的用户。用户理解源代码是为研究和教学目的而开发的,建议不要仅仅因任何原因依赖源代码或任何包含源代码的程序。如果源代码或任何产生的软件证明有缺陷,则用户承担所有必要的维修,修理或更正的费用。\n", - "\n", - "5. 在任何情况下,MINDVIEW LLC 或其出版商均不对任何一方根据任何法律理论对直接,间接,特殊,偶发或后果性损害承担任何责任,包括利润损失,业务中断,商业信息丢失或任何其他保险公司。由于 MINDVIEW LLC 或其出版商已被告知此类损害的可能性,因此使用本源代码及其文档或因无法使用任何结果程序而导致的个人受伤或者个人受伤。MINDVIEW LLC 特别声明不提供任何担保,包括但不限于对适销性和特定用途适用性的暗示担保。此处提供的源代码和文档基于“原样”基础,没有MINDVIEW LLC的任何随附服务,MINDVIEW LLC 没有义务提供维护,支持,更新,增强或修改。\n", - "\n", - "**请注意**,MindView LLC 仅提供以下唯一网址发布更新书中的代码示例,https://github.com/BruceEckel/OnJava8-examples 。你可在上述条款范围内将示例免费使用于项目和课堂中。\n", - "\n", - "如果你在源代码中发现错误,请在下面的网址提交更正:https://github.com/BruceEckel/OnJava8-examples/issues \n", - "\n", - "## 编码样式\n", - "\n", - "本书中代码标识符(关键字,方法,变量和类名)以粗体,固定宽度代码字体显示。像 “*class” 这种在代码中高频率出现的关键字可能让你觉得粗体有点乏味。(译者注:由于中英排版差异,中文翻译过程并未完全参照原作者的说明。具体排版格式请参考[此处](https://github.com/ruanyf/document-style-guide))其他显示为正常字体。本书文本格式尽可能遵循 Oracle 常见样式,并保证在大多数 Java 开发环境中被支持。书中我使用了自己喜欢的字体风格。Java 是一种自由的编程语言,你也可以使用 IDE(集成开发环境)工具(如 IntelliJ IDEA,Eclipse 或 NetBeans)将格式更改为适合你的格式。\n", - "\n", - "本书代码文件使用自动化工具进行测试,并在最新版本的 Java 编译通过(除了那些特别标记的错误之外)。本书重点介绍并使用 Java 8 进行测试。如果你必须了解更早的语言版本,可以在 [www.OnJava8.com](http://www.OnJava8.com) 免费下载 《Thinking in Java》。\n", - "\n", - "## BUG提交\n", - "\n", - "本书经过多重校订,但还是难免有所遗漏被新读者发现。如果你在正文或示例中发现任何错误的内容,请在[此处](https://github.com/BruceEckel/OnJava8-examples/issues)提交错误以及建议更正,作者感激不尽。\n", - "\n", - "## 邮箱订阅\n", - "\n", - "你可以在 [www.OnJava8.com上](http://www.OnJava8.com) 订阅邮件。邮件不含广告并尽量提供干货。\n", - "\n", - "## Java图形界面\n", - "\n", - "Java 在图形用户界面和桌面程序方面的发展可以说是一段悲伤的历史。Java 1.0 中图形用户界面(GUI)库的原始设计目标是让用户能在所有平台提供一个漂亮的界面。但遗憾的是,这个理想没有实现。相反,Java 1.0 AWT(抽象窗口工具包)在所有平台都表现平平,并且有诸多限制。你只能使用四种字体。另外,Java 1.0 AWT 编程模型也很笨拙且非面向对象。我的一个曾在 Java 设计期间工作过的学生道出了缘由:早期的 AWT 设计是在仅仅在一个月内构思、设计和实施的。不得不说这是一个“奇迹”,但同时更是“设计失败”的绝佳教材。\n", - "\n", - "在 Java 1.1 版本的 AWT 中 情况有所改善,事件模型带来更加清晰的面向对象方法,并添加了JavaBeans,致力于面向易于创建可视化编程环境的组件编程模型(已废弃)。\n", - "\n", - "Java 2(Java 1.2)通过使用 Java 基类(JFC)内容替换来完成从旧版 Java 1.0 AWT 的转换。其中 GUI 部分称为 Swing。这是一组丰富的 JavaBeans,它们创建了一个合理的 GUI。修订版 3(3之前都不好)比以往更适用于开发图形界面程序。\n", - "\n", - "Sun 在图形界面的最后一次尝试,称为 JavaFX。当 Oracle 收购 Sun 时,他们将原来雄心勃勃的项目(包括脚本语言)改为库,现在它似乎是 Java 官方唯一还在开发中的 UI 工具包(参见维基百科关于 JavaFX 的文章) - 但即使如此,JavaFX 最终似乎也失败了。\n", - "\n", - "现今 Swing 依然是 Java 发行版的一部分(只接受维护,不再有新功能开发)。而 Java 现在是一个开源项目,它应该始终可用。此外,Swing 和 JavaFX 有一些有限的交互性。这些可能是为了帮助开发者过渡到 JavaFX。\n", - "\n", - "桌面程序领域似乎从未尝勾起 Java 设计师的野心。Java 没有在图形界面取得该有的一席之地。另外,曾被大肆吹嘘的 JavaBeans 也没有获得任何影响力。(许多不幸的作者花了很多精力在 Swing 上编写书籍,甚至只用 JavaBeans 编写书籍)。Java 图形界面程序大多数情况下仅用于 IDE(集成开发环境)和一些企业内部应用程序。你可以采用 Java 开发图形界面,但这并非 Java 最擅长的领域。如果你必须学习 Swing,可以参考 *Thinking in Java* 第4版(可从 www.OnJava8.com 获得)或者通过其他专门的书籍学习。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/00-On-Java-8.ipynb b/jupyter/00-On-Java-8.ipynb deleted file mode 100644 index c16c3177..00000000 --- a/jupyter/00-On-Java-8.ipynb +++ /dev/null @@ -1,76 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - " \"cover\"\n", - "
\n", - "\n", - "
\n", - "\n", - "
\n", - "

On Java 8

\n", - "
\n", - "\n", - "
\n", - "

Bruce Eckel

\n", - "\n", - "
\n", - "\n", - "
MindView LLC
\n", - "\n", - "\n", - "
2017
\n", - "\n", - "\n", - "
©MindView LLC 版权所有
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", - "# On Java 8\n", - "\n", - "\n", - "\n", - "**版权©2017**\n", - "\n", - "\n", - "**作者 Bruce Eckel, President, MindView LLC.**\n", - "\n", - "\n", - "**版本号:7**\n", - "\n", - "\n", - "**ISBN 978-0-9818725-2-0**\n", - "\n", - "\n", - "**原书可在该网站购买 [www.OnJava8.com](http://www.OnJava8.com)** \n", - "\n", - "\n", - "\n", - "\n", - "本书出版自美国,版权所有,翻版必究。未经授权不得非法存储在检索系统中,或以电子,机械,影印,录制任何形式传输等。制造商和销售商使用商标用来区分其产品标识。如果这些名称出现在这本书中,并且出版商知道商标要求,则这些名称已经用大写字母或所有大写字母打印。\n", - "\n", - "Java 是甲骨文公司(Oracle. Inc.)的商标。Windows 95,Windows NT,Windows 2000,Windows XP,Windows 7,Windows 8 和 Windows 10 是微软公司(Microsoft Corporation)的商标。\n", - "此处提及的所有其他产品名称和公司名称均为其各自所有者的财产。作者和出版商在编写本书时已经仔细校对过,但不作任何明示或暗示的保证,对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损失,我们不承担任何责任。\n", - "\n", - "这本书是以平板电脑和计算机为载体的电子书,非传统纸质版书籍。 \n", - "故所有布局和格式设计旨在优化您在各种电子书阅读平台和系统上的观看体验。\n", - "封面由 Daniel Will-Harris 设计,[www.Will-Harris.com](http://www.Will-Harris.com)。\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/00-Preface.ipynb b/jupyter/00-Preface.ipynb deleted file mode 100644 index e97ab2c8..00000000 --- a/jupyter/00-Preface.ipynb +++ /dev/null @@ -1,112 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 前言\n", - "\n", - "> 本书基于 Java 8 版本来教授当前 Java 编程的最优实践。\n", - "\n", - "此前,我的另一本 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想》 第 4 版 Prentice Hall 2006)依然适用于 Java 5 编程。Android 编程就是始于此语言版本。\n", - "\n", - "随着 Java 8 的出现,这门语言在许多地方发生了翻天覆地的变化。在新的版本中,代码的运用和实现上与以往不尽相同。这也促使了我时隔多年后再次创作了这本新书。《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站上补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。\n", - "\n", - "与几年前我们依赖印刷媒体相比,YouTube,博客和 StackOverflow 等网站的出现让寻找答案变得简单。请结合这些学习途径和努力坚持下去。本书可作为编程入门书籍,同时也适用于想要扩展知识的在职程序员。每次在世界各地的演讲中,我都非常感谢 《*Thinking in Java*》 这本书给我带来的所有荣誉。它对于我重塑 [Reinventing Business](http://www.reinventing-business.com) 项目和促进交流是非常宝贵的。最后,写这本书的原因之一 希望这本书可以为我的这个项目众筹。似乎下一步要创建一个所谓的蓝绿色组织(Teal Organization)才合乎逻辑的。\n", - "\n", - "## 教学目标\n", - "\n", - "每章教授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。\n", - "\n", - "本书的教学目标:\n", - "\n", - "1. 循序渐进地呈现学习内容,以便于你在不依赖后置知识框架的情况下轻松完成现有的学习任务,同时尽量保证前面章节的内容在后面的学习中得到运用。如果确有必要引入我们还没学习到的知识概念,我会做个简短地介绍。\n", - "\n", - "2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意看到我的读者们因此能保持饶有兴趣地学习。\n", - "\n", - "3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。\n", - "\n", - "4. 希望本书能为你打下坚实的基础,方便你将来学习更难的课程和书籍。\n", - "\n", - "## 语言设计错误\n", - "\n", - "每种语言都有设计错误。当新手程序员涉足语言特性并猜测应用场景和使用方式时,他们体验到极大的不确定性和挫折感。承认错误令人尴尬,但这种糟糕的初学者经历比认识到你错在哪里还要糟糕。唉,每一种语言/库的设计错误都会永久地嵌入在 Java 的发行版中。\n", - "\n", - "诺贝尔经济学奖得主约瑟夫·斯蒂格利茨(*Joseph Stiglitz*)有一套适用于这里的人生哲学,叫做“承诺升级理论”:继续犯错误的成本由别人承担,而承认错误的成本由自己承担。\n", - "\n", - "看过我此前作品的读者们应该清楚,我一般倾向于指出这些错误。Java 拥有一批狂热的粉丝。他们把语言当成是阵营而不是纯粹的编程工具。我写过 Java 书籍,所以他们兀自认为我自然也是这个“阵营”的一份子。当我指出 Java 的这些错误时,会造成两种影响:\n", - "\n", - "1. 早先许多错误“阵营”的人成为了牺牲品。最终,时隔多年后,大家都意识到这是个设计上的错误。然而错误已然成为 Java 历史的一部分了。\n", - "\n", - "2. 更重要的是,新手程序员并没有经历过“语言为何采用某种方式实现”的争议过程。特别是那些隐约察觉不对却依然说服自己“我必须要这么做”或“我只是没学明白”从而继续错下去的人。更糟糕的是,教授这些编程知识的老师们没能深入地去研究这里是否有设计上的错误,而是继续错误的解读。总之,通过了解语言设计上的错误,能让开发者们更好地理解和意识到错误的本质,从而更快地进步。\n", - "\n", - "对编程语言的设计错误理解至关重要,甚至影响程序员的开发效率。部分公司在开发过程中避免使用语言的某些功能特性。这些功能特性表面上看起来高大上,但是弄不好却可能出现意料之外的错误,影响整个开发进程。\n", - "\n", - "已知的语言设计错误会给新的一门编程语言的作者提供参考。探索一门语言能做什么是很有趣的一件事,而语言设计错误能提醒你哪些“坑”是不能再趟的。多年以来,我一直感觉 Java 的设计者们有点脱离群众。Java 的有些设计错误错的太明显,我甚至怀疑设计者们到底是为出于服务用户还是其他动机设计了这些功能。Java 语言有许多臭名昭著的设计错误,很可能这也是诱惑所在。Java 似乎并不尊重开发者。为此我很长时间内不想与 Java 有任何瓜葛。很大程度上,这也是我不想碰 Java 的原因吧。\n", - "\n", - "如今再审视 Java 8,我发现了许多变化。设计者们对于语言和用户的态度似乎发生了根本性上的改变。忽视用户投诉多年之后,Java 的许多功能和类库都已被搞砸了。\n", - "\n", - "新功能的设计与以往有很大不同。掌舵者开始重视程序员的编程经验。新功能的开发都是在努力使语言变得更好,而非仅仅停留在快速堆砌功能而不去深入研究它们的含义。甚至有些新特性的实现方式非常优雅(至少在 Java 约束下尽可能优雅)。\n", - "\n", - "我猜测可能是部分设计者的离开让他们意识到了这点。说真的,我没想到会有这些变化!因为这些原因,写这本书的体验要比以往好很多。Java 8 包含了一系列基础和重要的改进。遗憾的是,为了严格地“向后兼容”,我们不大可能看到戏剧性的变化,当然我希望我是错的。尽管如此,我很赞赏那些敢于自我颠覆,并为 Java 设定更好路线的人。第一次,对于自己所写的部分 Java 8 代码我终于可以说“赞!”\n", - "\n", - "最后,本书所著时间似乎也还不错,因为 Java 8 引入的新功能已经强烈地影响了今后 Java 的编码方式。截止我在写这本书时,Java 9 似乎更专注于对语言底层的基础结构功能的重要更新,而非本书所关注的新编码方式。话说回来,得益于电子书出版形式的便捷,假如我发现本书有需要更新或添加的内容,我可以第一时间将新版本推送给现有读者。\n", - "\n", - "## 测试用例\n", - "\n", - "书中代码示例基于 Java 8 和 Gradle 编译构建,并且代码示例都保存在[这个自由访问的GitHub的仓库](https://github.com/BruceEckel/OnJava8-Examples) 中。我们需要内置的测试框架,以便于在每次构建系统时自动运行。否则,你将无法保证自己代码的可靠性。为了实现这一点,我创建了一个测试系统来显示和验证大多数示例的输出结果。这些输出结果我会附加在示例结尾的代码块中。有时仅显示必要的那几行或者首尾行。利用这种方式来改善读者的阅读和学习体验,同时也提供了一种验证示例正确性的方法。\n", - "\n", - "## 普及性\n", - "\n", - "Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你更容易找到工作。相关的培训材料,课程和其他可用的学习资源也很多。对于企业来说,招聘 Java 程序员相对容易。如果你不喜欢 Java 语言,那么最好不要拿他当作你谋生的工具,因为这种生活体验并不好。作为一家公司,在技术选型前一定不要单单只考虑 Java 程序员好招。每种语言都有其适用的范围,有可能你们的业务更适用于另一种编程语言来达到事半功倍的效果。如果你真的喜欢 Java,那么欢迎你。希望这本书能丰富你的编程经验!\n", - "\n", - "## 关于安卓\n", - "\n", - "本书基于 Java 8 版本。如果你是 Andriod 程序员,请务必学习 Java 5。在《On Java 8》出版的时候,我的另一本基于 Java 5 的著作 *Thinking in Java 4th Edition*(《Java编程思想》第四版)已经可以在[www.OnJava8.com](http://www.OnJava8.com)上免费下载了。此外,还有许多其他专用于 Andriod 编程的资源。\n", - "\n", - "## 电子版权声明\n", - "\n", - "《On Java 8》仅提供电子版,并且仅通过 [www.OnJava8.com](http://www.OnJava8.com) 提供。任何未经 授权的其他来源或流传送机构都是非法的。本作品受版权保护!未经许可,请勿通过以任何方式分享或发布。你可以使用这些示例进行教学,只要不对本书非法重新出版。有关完整详细信息,请参阅示例分发中的 Copyright.txt 文件。对于视觉障碍者,电子版本有可搜索性,字体大小调整或文本到语音等诸多好处。\n", - "\n", - "任何购买这本书的读者,还需要一台计算机来运行和写作代码。另外电子版在计算机上和移动设备上的显示效果俱佳,推荐使用平板设备阅读。相比购买传统纸质版的价格,平板电脑价格都足够便宜。在床上阅读电子版比看这样一本厚厚的实体书要方便得多。起初你可能会有些不习惯,但我相信很快你就会发现它带来的优点远胜过不适。我已经走过这个阶段,Google Play 图书的浏览器阅读体验非常好,包括在 Linux 和 iOS 设备上。作为一次尝试,我决定尝试通过 Google 图书进行出版。\n", - "\n", - "**注意**:在撰写本文时,通过 Google Play 图书网络浏览器应用阅读图书虽然可以忍受,但体验还是有点差强人意,我强烈推荐读者们使用平板电脑来阅读。\n", - "\n", - "## 版本说明\n", - "\n", - "本书采用 [Pandoc](http://pandoc.org) 风格的 Markdown 编写,使用 Pandoc 生成 ePub v3 格式。\n", - "\n", - "正文字体为 Georgia,标题字体为 Verdana。 代码字体使用的 Ubuntu Mono,因为它特别紧凑,单行能容纳更多的代码。 我选择将代码内联(而不是将列表放入图像,参照其他书籍),因为我觉得这个功能很重要:让代码块能适应字体大小得改变而改变(否则,买电子版,还图什么呢?)。\n", - "\n", - "书中的提取,编译和测试代码示例的构建过程都是自动化的。所有自动化操作都是通过我在 Python 3 中编写的程序来实现的。\n", - "\n", - "## 封面设计\n", - "\n", - "《On Java 8》的封面是根据 W.P.A.(Works Progress Administration 1935年至1943年美国大萧条期间的一个巨大项目,它使数百万失业人员重新就业)的马赛克创作的。它还让我想起了《绿野仙踪》(*The Wizard of Oz*)系列丛书中的插图。 我的好朋友、设计师丹 *Daniel Will-Harris*([www.will-harris.com](http://www.will-harris.com))和我都喜欢这个形象。\n", - "\n", - "## 感谢的人\n", - "\n", - "感谢 *Domain-Driven Design*(《领域驱动设计》 )的作者 *Eric Evans* 建议书名,以及其他新闻组校对的帮助。\n", - "\n", - "感谢 *James Ward* 为我开始使用 Gradle 工具构建这本书,以及他多年来的帮助和友谊。\n", - "\n", - "感谢 *Ben Muschko* 在整理构建文件方面的工作,还有感谢 *Hans Dockter* 给 *Ben* 提供了时间。\n", - "\n", - "感谢 *Jeremy Cerise* 和 *Bill Frasure* 来到开发商务聚会预订,并随后提供了宝贵的帮助。\n", - "\n", - "感谢所有花时间和精力来科罗拉多州克雷斯特德比特(Crested Butte, Colorado)镇参加我的研讨会,开发商务聚会和其他活动的人!你们的贡献可能不容易看到,但却非常重要!\n", - "\n", - "## 献礼\n", - "\n", - "> 谨以此书献给我敬爱的父亲 E. Wayne Eckel。\n", - "> 1924年4月1日至2016年11月23日\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/01-What-is-an-Object.ipynb b/jupyter/01-What-is-an-Object.ipynb deleted file mode 100644 index 252581ee..00000000 --- a/jupyter/01-What-is-an-Object.ipynb +++ /dev/null @@ -1,422 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 第一章 对象的概念\n", - "\n", - "> “我们没有意识到惯用语言的结构有多大的力量。可以毫不夸张地说,它通过语义反应机制奴役我们。语言表现出来并在无意识中给我们留下深刻印象的结构会自动投射到我们周围的世界。” -- Alfred Korzybski (1930)\n", - "\n", - "计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。\n", - "\n", - "面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构。本章讲述 OOP 的基本概述。如果读者对此不太理解,可先行跳过本章。等你具备一定编程基础后,请务必再回头看。只有这样你才能深刻理解面向对象编程的重要性及设计方式。\n", - "\n", - "## 抽象\n", - "\n", - "所有编程语言都提供抽象机制。从某种程度上来说,问题的复杂度直接取决于抽象的类型和质量。这里的“类型”意思是:抽象的内容是什么?汇编语言是对底层机器的轻微抽象。接着出现的“命令式”语言(如 FORTRAN,BASIC 和 C)是对汇编语言的抽象。与汇编相比,这类语言已有了长足的改进,但它们的抽象原理依然要求我们着重考虑计算机的结构,而非问题本身的结构。\n", - "\n", - "程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一个副产业“编程方法”学科。\n", - "\n", - "为机器建模的另一个方法是为要解决的问题制作模型。对一些早期语言来说,如 LISP 和 APL,它们的做法是“从不同的角度观察世界”——“所有问题都归纳为列表”或“所有问题都归纳为算法”。PROLOG 则将所有\n", - "问题都归纳为决策链。对于这些语言,我们认为它们一部分是“基于约束”的编程,另一部分则是专为\n", - "处理图形符号设计的(后者被证明限制性太强)。每种方法都有自己特殊的用途,适合解决某一类的问题。只要超出了它们力所能及的范围,就会显得非常笨拙。\n", - "\n", - "面向对象的程序设计在此基础上跨出了一大步,程序员可利用一些工具表达“问题空间”内的元素。由于这种表达非常具有普遍性,所以不必受限于特定类型的问题。我们将问题空间中的元素以及它们在解决方案空间的表示称作“对象”(**Object**)。当然,还有一些在问题空间没有对应的对象体。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。所以当你在阅读描述解决方案的代码时,也是在阅读问题的表述。与我们以前见过的相比,这无疑是一种更加灵活、更加强大的语言抽象方法。总之,OOP 允许我们根据问题来描述问题,而不是根据运行解决方案的计算机。然而,它仍然与计算机有联系,每个对象都类似一台小计算机:它们有自己的状态并且可以进行特定的操作。这与现实世界的“对象”或者“物体”相似:它们都有自己的特征和行为。\n", - "\n", - "Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:\n", - "\n", - "> 1. **万物皆对象**。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。\n", - "> 2. **程序是一组对象,通过消息传递来告知彼此该做什么**。要请求调用一个对象的方法,你需要向该对象发送消息。\n", - "> 3. **每个对象都有自己的存储空间,可容纳其他对象**。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。\n", - "> 4. **每个对象都有一种类型**。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。\n", - "> 5. **同一类所有对象都能接收相同的消息**。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收发送给\"形状”的消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。\n", - "\n", - "*Grady Booch* 提供了对对象更简洁的描述:一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。\n", - "\n", - "## 接口\n", - "\n", - "亚里士多德(*Aristotle*)大概是第一个认真研究“类型”的哲学家,他曾提出过“鱼类和鸟类”这样的概念。所有对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。这种思想被首次应用于第一个面向对象编程语言 Simula-67,它在程序中使用基本关键字 **class** 来引入新的类型(class 和 type 通常可互换使用,有些人对它们进行了进一步区分,他们强调 type 决定了接口,而 class 是那个接口的一种特殊实现方式)。\n", - "\n", - "Simula 是一个很好的例子。正如这个名字所暗示的,它的作用是“模拟”(Simulate)类似“银行出纳员”这样的经典问题。在这个例子里,我们有一系列出纳员、客户、帐号、交易和货币单位等许多\"对象”。每类成员(元素)都具有一些通用的特征:每个帐号都有一定的余额;每名出纳都能接收客户的存款;等等。与此同时,每个成员都有自己的状态;每个帐号都有不同的余额;每名出纳都有一个名字。所以在计算机程序中,能用独一无二的实体分别表示出纳员、客户、帐号以及交易。这个实体便是“对象”,而且每个对象都隶属一个特定的“类”,那个类具有自己的通用特征与行为。\n", - "\n", - "因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。\n", - "\n", - "创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。\n", - "\n", - "那么如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。\n", - "\n", - "下面让我们以电灯泡为例:\n", - "\n", - "![reader](../images/reader.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Light lt = new Light();\n", - "lt.on();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。\n", - "\n", - "为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。\n", - "\n", - "上图遵循 **UML**(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 **UML** 设计图中显示,因此中间部分未显示,如本例所示。如果你只对类名感兴趣,则也不需要显示方法信息。\n", - "\n", - "## 服务提供\n", - "\n", - "在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。你的程序本身将为用户提供服务,并且它能通过调用其他对象提供的服务来实现这一点。我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。\n", - "\n", - "那么问题来了:我们该选择哪个对象来解决问题呢?例如,你正在开发一个记事本程序。*你可能会想到在屏幕输入默认的记事本对象*,一个用于检测不同类型打印机并执行打印的对象。这些对象中的某些已经有了。那对于还没有的对象,我们该设计成啥样呢?这些对象需要提供哪些服务,以及还需要调用其他哪些对象?\n", - "\n", - "我们可以将这些问题一一分解,抽象成一组服务。软件设计的基本原则是高内聚:每个组件的内部作用明确,功能紧密相关。然而经常有人将太多功能塞进一个对象中。例如:在支票打印模块中,你需要设计一个可以同时读取文本格式又能正确识别不同打印机型号的对象。正确的做法是提供三个或更多对象:一个对象检查所有排版布局的目录;一个或一组可以识别不同打印机型号的对象展示通用的打印界面;第三个对象组合上述两个服务来完成任务。这样,每个对象都提供了一组紧密的服务。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。\n", - "\n", - "## 封装\n", - "\n", - "我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。当应用程序员调用研发程序员开发的工具类时,双方建立了关系。应用程序员通过使用现成的工具类组装应用程序或者构建更大的工具库。如果工具类的创建者将类的内部所有信息都公开给调用者,那么有些使用规则就不容易被遵守。因为前者无法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种情况的发生。\n", - "\n", - "因此,使用访问控制的原因有以下两点:\n", - "\n", - "1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);\n", - "\n", - "2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造。\n", - "\n", - "Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。\n", - "\n", - " 1. `public`(公开)表示任何人都可以访问和使用该元素;\n", - "\n", - " 2. `private`(私有)除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;\n", - "\n", - " 3. `protected`(受保护)类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;\n", - "\n", - " 4. `default`(默认)如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。\n", - "\n", - "## 复用\n", - "\n", - "一个类经创建和测试后,理应是可复用的。然而很多时候,由于程序员没有足够的编程经验和远见,我们的代码复用性并不强。\n", - "\n", - "代码和设计方案的复用性是面向对象程序设计的优点之一。我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。这里涉及到“组合”和“聚合”的概念:\n", - "\n", - "* **组合**(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。\n", - "\n", - "* **聚合**(Aggregation)动态的**组合**。\n", - "\n", - "![UML-example](../images/1545758268350.png)\n", - "\n", - "上图中实心三角形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心三角形。\n", - "\n", - "(**译者注**:组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义。至于是聚合还是组合,需要根据实际的业务需求来判断。可能相同超类和子类,在不同的业务场景,关联关系会发生变化。只看代码是无法区分聚合和组合的,具体是哪一种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。)\n", - "\n", - "使用“组合”关系给我们的程序带来极大的灵活性。通常新建的类中,成员对象会使用 `private` 访问权限,这样应用程序员则无法对其直接访问。我们就可以在不影响客户代码的前提下,从容地修改那些成员。我们也可以在“运行时\"改变成员对象从而动态地改变程序的行为,这进一步增大了灵活性。下面一节要讲到的“继承”并不具备这种灵活性,因为编译器对通过继承创建的类进行了限制。\n", - "\n", - "在面向对象编程中经常重点强调“继承”。在新手程序员的印象里,或许先入为主地认为“继承应当随处可见”。沿着这种思路产生的程序设计通常拙劣又复杂。相反,在创建新类时首先要考虑“组合”,因为它更简单灵活,而且设计更加清晰。等我们有一些编程经验后,一旦需要用到继承,就会明显意识到这一点。\n", - "\n", - "## 继承\n", - "\n", - "“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。\n", - "\n", - "通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。\n", - "\n", - "![Inheritance-example](../images/1545763399825.png)\n", - "\n", - "这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。\n", - "\n", - "![1545764724202](../images/1545764724202.png)\n", - "\n", - "例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。\n", - "\n", - "![1545764780795](../images/1545764780795.png)\n", - "\n", - "例如,某些形状可以翻转。有些行为可能不同,比如计算形状的面积。类型层次结构体现了形状之间的相似性和差异性。以相同的术语将解决方案转换成问题是有用的,因为你不需要在问题描述和解决方案描述之间建立许多中间模型。通过使用对象,类型层次结构成为了主要模型,因此你可以直接从真实世界中对系统的描述过渡到用代码对系统进行描述。事实上,有时候,那些善于寻找复杂解决方案的人会被面向对象设计的简单性难倒。从现有类型继承创建新类型。这种新类型不仅包含现有类型的所有成员(尽管私有成员被隐藏起来并且不可访问),而且更重要的是它复制了基类的接口。也就是说,基类对象接收的所有消息也能被派生类对象接收。根据类接收的消息,我们知道类的类型,因此派生类与基类是相同的类型。\n", - "\n", - "在前面的例子中,“圆是形状”。这种通过继承的类型等价性是理解面向对象编程含义的基本门槛之一。因为基类和派生类都具有相同的基本接口,所以伴随此接口的必定有某些具体实现。也就是说,当对象接收到特定消息时,必须有可执行代码。如果继承一个类而不做其他任何事,则来自基类接口的方法直接进入派生类。这意味着派生类和基类不仅具有相同的类型,而且具有相同的行为,这么做没什么特别意义。\n", - "\n", - "有两种方法可以区分新的派生类与原始的基类。第一种方法很简单:在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。然而,还是要仔细考虑是否在基类中也要有这些额外的方法。这种设计的发现与迭代过程在面向对象程序设计中会经常发生。\n", - "\n", - "尽管继承有时意味着你要在接口中添加新方法(尤其是在以 **extends** 关键字表示继承的 Java 中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。\n", - "\n", - "### \"是一个\"与\"像是一个\"的关系\n", - "\n", - "对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作\"纯粹替代\",也经常被称作\"替代原则\"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说\"圆是一个形状\"。判断是否继承,就看在你的类之间有无这种 is-a 关系。\n", - "\n", - "有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。\n", - "\n", - "![1545764820176](../images/1545764820176.png)\n", - "\n", - "以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为\"温度控制系统\",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。\n", - "\n", - "当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。\n", - "\n", - "## 多态\n", - "\n", - "我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。在上个“形状”的例子中,“方法”(method)操纵的是通用“形状”,而不关心它们是“圆”、“正方形”、“三角形”还是某种尚未定义的形状。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。\n", - "\n", - "这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 例如,你可以通过通用的“形状”基类派生出新的“五角形”形状的子类,而不需要修改通用\"形状\"基类的方法。通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。\n", - "\n", - "这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。\n", - "\n", - "如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?举个例子,下图的 **BirdController** 对象和通用 **Bird** 对象中,**BirdController** 不知道 **Bird** 的确切类型却还能一起工作。从 **BirdController** 的角度来看,这是很方便的,因为它不需要编写特别的代码来确定 **Bird** 对象的确切类型或行为。那么,在调用 **move()** 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?\n", - "\n", - "![Bird-example](../images/1545839316314.png)\n", - "\n", - "这个问题的答案,是面向对象程序设计的妙诀:在传统意义上,编译器不能进行函数调用。由非 OOP 编译器产生的函数调用会引起所谓的**早期绑定**,这个术语你可能从未听说过,不会想过其他的函数调用方式。这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。\n", - "\n", - "通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用**后期绑定**的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。\n", - "\n", - "为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 **virtual** 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。\n", - "\n", - "为了演示多态性,我们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。\n", - "\n", - "代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void doSomething(Shape shape) {\n", - " shape.erase();\n", - " // ...\n", - " shape.draw();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此方法与任何 **Shape** 对话,因此它与所绘制和擦除的对象的具体类型无关。如果程序的其他部分使用 `doSomething()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " Circle circle = new Circle();\n", - " Triangle triangle = new Triangle();\n", - " Line line = new Line();\n", - " doSomething(circle);\n", - " doSomething(triangle);\n", - " doSomething(line);\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到无论传入的“形状”是什么,程序都正确的执行了。\n", - "\n", - "![shape-example](../images/1545841270997.png)\n", - "\n", - "这是一个非常令人惊奇的编程技巧。分析下面这行代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " doSomething(circle);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当预期接收 **Shape** 的方法被传入了 **Circle**,会发生什么。由于 **Circle** 也是一种 **Shape**,所\n", - "以 `doSomething(circle)` 能正确地执行。也就是说,`doSomething()` 能接收任意发送给 **Shape** 的消息。这是完全安全和合乎逻辑的事情。\n", - "\n", - "这种把子类当成其基类来处理的过程叫做“向上转型”(**upcasting**)。在面向对象的编程里,经常利用这种方法来给程序解耦。再看下面的 `doSomething()` 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " shape.erase();\n", - " // ...\n", - " shape.draw();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()`,你自己去做吧,注意细节。”\n", - "\n", - "尽管我们没作出任何特殊指示,程序的操作也是完全正确和恰当的。我们知道,为 Circle 调用`draw()` 时执行的代码与为一个 Square 或 Line 调用 `draw()` 时执行的代码是不同的。但在将 `draw()` 信息发给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确的操作。这非常神奇,因为当 Java 编译器为 `doSomething()` 编译代码时,它并不知道自己要操作的准确类型是什么。\n", - "\n", - "尽管我们确实可以保证最终会为 Shape 调用 `erase()` 和 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?\n", - "\n", - "发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。\n", - "\n", - "## 单继承结构\n", - "\n", - "自从 C++ 引入以来,一个 OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。\n", - "\n", - "Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。\n", - "\n", - "对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。\n", - "\n", - "另外,单继承的结构使得垃圾收集器的实现更为容易。这也是 Java 在 C++ 基础上的根本改进之一。\n", - "\n", - "由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如[异常处理](#异常处理)。同时,这也让我们的编程具有更大的灵活性。\n", - "\n", - "## 集合\n", - "\n", - "通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?\n", - "\n", - "在面向对象的设计中,问题的解决方案有些过于轻率:创建一个新类型的对象来引用、容纳其他的对象。当然,我们也可以使用多数编程语言都支持的“数组”(array)。在 Java 中“集合”(Collection)的使用率更高。(也可称之为“容器”,但“集合”这个称呼更通用。)\n", - "\n", - "“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们不用关心过程是如何实现的。\n", - "\n", - "还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。\n", - "\n", - "在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:\n", - "\n", - "1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。\n", - "\n", - "2. 不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。\n", - "\n", - "我们可以一开始使用 LinkedList 构建程序,在优化系统性能时改用 ArrayList。通过对 List 接口的抽象,我们可以很容易地将 LinkedList 改为 ArrayList。\n", - "\n", - "在 Java 5 泛型出来之前,集合中保存的是通用类型 `Object`。Java 单继承的结构意味着所有元素都基于 `Object` 类,所以在集合中可以保存任何类型的数据,易于重用。要使用这样的集合,我们先要往集合添加元素。由于 Java 5 版本前的集合只保存 `Object`,当我们往集合中添加元素时,元素便向上转型成了 `Object`,从而丢失自己原有的类型特性。这时我们再从集合中取出该元素时,元素的类型变成了 `Object`。那么我们该怎么将其转回原先具体的类型呢?这里,我们使用了强制类型转换将其转为更具体的类型,这个过程称为对象的“向下转型”。通过“向上转型”,我们知道“圆形”也是一种“形状”,这个过程是安全的。可是我们不能从“Object”看出其就是“圆形”或“形状”,所以除非我们能确定元素的具体类型信息,否则“向下转型”就是不安全的。也不能说这样的错误就是完全危险的,因为一旦我们转化了错误的类型,程序就会运行出错,抛出“运行时异常”(RuntimeException)。(后面的章节会提到) 无论如何,我们要寻找一种在取出集合元素时确定其具体类型的方法。另外,每次取出元素都要做额外的“向下转型”对程序和程序员都是一种开销。以某种方式创建集合,以确认保存元素的具体类型,减少集合元素“向下转型”的开销和可能出现的错误难道不好吗?这种解决方案就是:参数化类型机制(Parameterized Type Mechanism)。\n", - "\n", - "参数化类型机制可以使得编译器能够自动识别某个 `class` 的具体类型并正确地执行。举个例子,对集合的参数化类型机制可以让集合仅接受“形状”这种类型的元素,并以“形状”类型取出元素。Java 5 版本支持了参数化类型机制,称之为“泛型”(Generic)。泛型是 Java 5 的主要特性之一。你可以按以下方式向 ArrayList 中添加 Shape(形状):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " ArrayList shapes = new ArrayList<>();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "泛型的应用,让 Java 的许多标准库和组件都发生了改变。在本书的代码示例中,你也会经常看到泛型的身影。\n", - "\n", - "## 对象创建与生命周期\n", - "\n", - "我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。\n", - "\n", - "在简单的编程场景下,对象的清理并不是问题。我们创建对象,按需使用,最后销毁它。然而,情况往往要比这更复杂:\n", - "\n", - "假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。\n", - "\n", - "现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。\n", - "\n", - "对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。\n", - "\n", - "然而相对的,我们也牺牲了程序的灵活性。因为在编写代码时,我们必须要弄清楚对象的数量、生存时间还有类型。如果我们要用它来解决一个相当普遍的问题时(如计算机辅助设计、仓库管理或空中交通管制等),限制就太大了。\n", - "\n", - "第二种方法是在堆内存(Heap)中动态地创建对象。在这种方式下,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。\n", - "\n", - "动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。\n", - "\n", - "Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字构建该对象的动态实例。这又带来另一个问题:对象的生命周期。较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;然而如果你在堆内存创建对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。\n", - "\n", - "Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其他方面)。垃圾收集器知道对象什么时候不再被使用并且自动释放内存。结合单继承和仅可在堆中创建对象的机制,Java 的编码过程比用 C++ 要简单得多。我们所要做的决定和要克服的障碍也会少很多!\n", - "\n", - "## 异常处理\n", - "\n", - "自编程语言被发明以来,程序的错误处理一直都是个难题。因为很难设计出一个好的错误处理方案,所以许多编程语言都忽略了这个问题,把这个问题丢给了程序类库的设计者。他们提出了在许多情况下都可以工作但很容易被规避的半途而废的措施,通常只需忽略错误。多数错误处理方案的主要问题是:它们依赖程序员之间的约定俗成而不是语言层面的限制。换句话说,如果程序员赶时间或没想起来,这些方案就很容易被忘记。\n", - "\n", - "异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。这让我们的编码更简单:不用再反复检查错误了。另外,异常不像方法返回的错误值和方法设置用来表示发生错误的标志位那样可以被忽略。异常的发生是不会被忽略的,它终究会在某一时刻被处理。\n", - "\n", - "最后,“异常机制”提供了一种可靠地从错误状况中恢复的方法,使得我们可以编写出更健壮的程序。有时你只要处理好抛出的异常情况并恢复程序的运行即可,无需退出。\n", - "\n", - "Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。\n", - "\n", - "## 本章小结\n", - "\n", - "面向过程程序包含数据定义和函数调用。要找到程序的意图,你必须要在脑中建立一个模型,弄清函数调用和更底层的概念。这些程序令人困扰,因为它们的表示更多地面向计算机而不是我们要解决的问题,这就是我们在设计程序时需要中间表示的原因。OOP 在面向过程编程的基础上增加了许多新的概念,所以有人会认为使用 Java 来编程会比同等的面向过程编程要更复杂。在这里,我想给大家一个惊喜:通常按照 Java 规范编写的程序会比面向过程程序更容易被理解。\n", - "\n", - "你看到的是对象的概念,这些概念是站在“问题空间”的(而不是站在计算机角度的“解决方案空间”),以及发送消息给对象以指示该空间中的活动。面向对象编程的一个优点是:设计良好的 Java 程序代码更容易被人阅读理解。由于 Java 类库的复用性,通常程序要写的代码也会少得多。\n", - "\n", - "OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Java", - "language": "java", - "name": "java" - }, - "language_info": { - "codemirror_mode": "java", - "file_extension": ".jshell", - "mimetype": "text/x-java-source", - "name": "Java", - "pygments_lexer": "java", - "version": "14.0.1+7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb b/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb deleted file mode 100644 index f9d35d37..00000000 --- a/jupyter/02-Installing-Java-and-the-Book-Examples.ipynb +++ /dev/null @@ -1,305 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "# 第二章 安装Java和本书用例\n", - "\n", - "现在,我们来为这次阅读之旅做些准备吧!\n", - "\n", - "在开始学习 Java 之前,你必须要先安装好 Java 和本书的源代码示例。因为考虑到可能有“专门的初学者”从本书开始学习编程,所以我会详细地教你如何使用命令行。 如果你已经有此方面的经验了,可以跳过这段安装说明。如果你对此处描述的任何术语或过程仍不清楚,还可以通过 [Google](https://google.com/) 搜索找到答案。具体的问题或困难请试着在 [StackOverflow](https://stackoverflow.com/) 上提问。或者去 [YouTube](https://youtube.com) 看有没有相关的安装说明。\n", - "\n", - "## 编辑器\n", - "\n", - "首先你需要安装一个编辑器来创建和修改本书用例里的 Java 代码。有可能你还需要使用编辑器来更改系统配置文件。\n", - "\n", - "相比一些重量级的 IDE(Integrated Development Environments,集成开发环境),如 Eclipse、NetBeans 和 IntelliJ IDEA (译者注:做项目强烈推荐IDEA),编辑器是一种更纯粹的文本编辑器。如果你已经有了一个用着顺手的 IDE,那就可以直接用了。为了方便后面的学习和统一下教学环境,我推荐大家使用 Atom 这个编辑器。大家可以在 [atom.io](https://atom.io) 上下载。\n", - "\n", - "Atom 是一个免费开源、易于安装且跨平台(支持 Window、Mac和Linux)的文本编辑器。内置支持 Java 文件。相比 IDE 的厚重,它比较轻量级,是学习本书的理想工具。Atom 包含了许多方便的编辑功能,相信你一定会爱上它!更多关于 Atom 使用的细节问题可以到它的网站上寻找。\n", - "\n", - "还有很多其他的编辑器。有一种亚文化的群体,他们热衷于争论哪个更好用!如果你找到一个你更喜欢的编辑器,换一种使用也没什么难度。重要的是,你要找一个用着舒服的。\n", - "\n", - "## Shell\n", - "\n", - "如果你之前没有接触过编程,那么有可能对 Shell(命令行窗口) 不太熟悉。shell 的历史可以追溯到早期的计算时代,当时在计算机上的操作是都通过输入命令进行的,计算机通过回显响应。所有的操作都是基于文本的。\n", - "\n", - "尽管和现在的图形用户界面相比,Shell 操作方式很原始。但是同时 shell 也为我们提供了许多有用的功能特性。在学习本书的过程中,我们会经常使用到 Shell,包括现在这部分的安装,还有运行 Java 程序。\n", - "\n", - "Mac:单击聚光灯(屏幕右上角的放大镜图标),然后键入 `terminal`。单击看起来像小电视屏幕的应用程序(你也可以单击“return”)。这就启动了你的用户下的 shell 窗口。\n", - "\n", - "windows:首先,通过目录打开 windows 资源管理器:\n", - "\n", - "- Windows 7: 单击屏幕左下角的“开始”图标,输入“explorer”后按回车键。\n", - "- Windows 8: 按 Windows+Q,输入 “explorer” 后按回车键。\n", - "- Windows 10: 按 Windows+E 打开资源管理器,导航到所需目录,单击窗口左上角的“文件“选项卡,选择“打开 Window PowerShell”启动 Shell。\n", - "\n", - "Linux: 在 home 目录打开 Shell。\n", - "\n", - "- Debian: 按 Alt+F2, 在弹出的对话框中输入“gnome-terminal”\n", - "- Ubuntu: 在屏幕中鼠标右击,选择 “打开终端”,或者按住 Ctrl+Alt+T\n", - "- Redhat: 在屏幕中鼠标右击,选择 “打开终端”\n", - "- Fedora: 按 Alt+F2,在弹出的对话框中输入“gnome-terminal”\n", - "\n", - "**目录**\n", - "\n", - "目录是 Shell 的基础元素之一。目录用来保存文件和其他目录。目录就好比树的分支。如果书籍是你系统上的一个目录,并且它有两个其他目录作为分支,例如数学和艺术,那么我们就可以说你有一个书籍目录,它包含数学和艺术两个子目录。注意:Windows 使用 `\\` 而不是 `/` 来分隔路径。\n", - "\n", - "**Shell基本操作**\n", - "\n", - "我在这展示的 Shell 操作和系统中大体相同。出于本书的原因,下面列举一些在 Shell 中的基本操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - "更改目录: cd <路径> \n", - " cd .. 移动到上级目录 \n", - " pushd <路径> 记住来源的同时移动到其他目录,popd 返回来源\n", - "\n", - "目录列举: ls 列举出当前目录下所有的文件和子目录名(不包含隐藏文件),\n", - " 可以选择使用通配符 * 来缩小搜索范围。\n", - " 示例(1): 列举所有以“.java”结尾的文件,输入 ls *.java (Windows: dir *.java)\n", - " 示例(2): 列举所有以“F”开头,“.java”结尾的文件,输入ls F*.java (Windows: dir F*.java)\n", - "\n", - "创建目录: \n", - " Mac/Linux 系统:mkdir \n", - " 示例:mkdir books \n", - " Windows 系统:md \n", - " 示例:md books\n", - "\n", - "移除文件: \n", - " Mac/Linux 系统:rm\n", - " 示例:rm somefile.java\n", - " Windows 系统:del \n", - " 示例:del somefile.java\n", - "\n", - "移除目录: \n", - " Mac/Linux 系统:rm -r\n", - " 示例:rm -r books\n", - " Windows 系统:deltree \n", - " 示例:deltree books\n", - "\n", - "重复命令: !! 重复上条命令\n", - " 示例:!n 重复倒数第n条命令\n", - "\n", - "命令历史: \n", - " Mac/Linux 系统:history\n", - " Windows 系统:按 F7 键\n", - "\n", - "文件解压:\n", - " Linux/Mac 都有命令行解压程序 unzip,你可以通过互联网为 Windows 安装命令行解压程序 unzip。\n", - " 图形界面下(Windows 资源管理器,Mac Finder,Linux Nautilus 或其他等效软件)右键单击该文件,\n", - " 在 Mac 上选择“open”,在 Linux 上选择“extract here”,或在 Windows 上选择“extract all…”。\n", - " 要了解关于 shell 的更多信息,请在维基百科中搜索 Windows shell,Mac/Linux用户可搜索 bash shell。\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Java安装\n", - "\n", - "为了编译和运行代码示例,首先你必须安装 JDK(Java Development Kit,JAVA 软件开发工具包)。本书中采用的是 JDK 8。\n", - "\n", - "\n", - "**Windows**\n", - "\n", - "1. 以下为 Chocolatey 的[安装说明](https://chocolatey.org/)。\n", - "2. 在命令行提示符下输入下面的命令,等待片刻,结束后 Java 安装完成并自动完成环境变量设置。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " choco install jdk8" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Macintosh**\n", - "\n", - "Mac 系统自带的 Java 版本太老,为了确保本书的代码示例能被正确执行,你必须将它先更新到 Java 8。我们需要管理员权限来运行下面的步骤:\n", - "\n", - "1. 以下为 HomeBrew 的[安装说明](https://brew.sh/)。安装完成后执行命令 `brew update` 更新到最新版本\n", - "2. 在命令行下执行下面的命令来安装 Java。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " brew cask install java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当以上安装都完成后,如果你有需要,可以使用游客账户来运行本书中的代码示例。\n", - "\n", - "**Linux**\n", - "\n", - "* **Ubuntu/Debian**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " sudo apt-get update\n", - " sudo apt-get install default-jdk" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* **Fedora/Redhat**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " su-c \"yum install java-1.8.0-openjdk\"(注:执行引号内的内容就可以安装)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 校验安装\n", - "\n", - "打开新的命令行输入:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - "java -version" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正常情况下 你应该看到以下类似信息(版本号信息可能不一样):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - "java version \"1.8.0_112\"\n", - "Java(TM) SE Runtime Environment (build 1.8.0_112-b15)\n", - "Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果提示命令找不到或者无法被识别,请根据安装说明重试;如果还不行,尝试到 [StackOverflow](https://stackoverflow.com/search?q=installing+java) 寻找答案。\n", - "\n", - "## 安装和运行代码示例\n", - "\n", - "当 Java 安装完毕,下一步就是安装本书的代码示例了。安装步骤所有平台一致:\n", - "\n", - "1. 从 [GitHub 仓库](https://github.com/BruceEckel/OnJava8-Examples/archive/master.zip)中下载本书代码示例\n", - "2. 解压到你所选目录里。\n", - "3. 使用 Windows 资源管理器,Mac Finder,Linux 的 Nautilus 或其他等效工具浏览,在该目录下打开 Shell。\n", - "4. 如果你在正确的目录中,你应该看到该目录中名为 gradlew 和 gradlew.bat 的文件,以及许多其他文件和目录。目录与书中的章节相对应。\n", - "5. 在shell中输入下面的命令运行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " Windows 系统:\n", - " gradlew run\n", - "\n", - " Mac/Linux 系统:\n", - " ./gradlew run" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第一次安装时 Gradle 需要安装自身和其他的相关的包,请稍等片刻。安装完成后,后续的安装将会快很多。\n", - "\n", - "**注意**: 第一次运行 gradlew 命令时必须连接互联网。\n", - "\n", - "**Gradle 基础任务**\n", - "\n", - "本书构建的大量 Gradle 任务都可以自动运行。Gradle 使用约定大于配置的方式,简单设置即可具备高可用性。本书中“一起去骑行”的某些任务不适用于此或无法执行成功。以下是你通常会使用上的 Gradle 任务列表:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - " 编译本书中的所有 java 文件,除了部分错误示范的\n", - " gradlew compileJava\n", - "\n", - " 编译并执行 java 文件(某些文件是库组件)\n", - " gradlew run\n", - "\n", - " 执行所有的单元测试(在本书第16章会有详细介绍)\n", - " gradlew test\n", - "\n", - " 编译并运行一个具体的示例程序\n", - " gradlew <本书章节>:<示例名称>\n", - " 示例:gradlew objects:HelloDate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/03-Objects-Everywhere.ipynb b/jupyter/03-Objects-Everywhere.ipynb deleted file mode 100644 index 6eabcaad..00000000 --- a/jupyter/03-Objects-Everywhere.ipynb +++ /dev/null @@ -1,1386 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "# 第三章 万物皆对象\n", - "\n", - "> 如果我们说另外一种不同的语言,我们会发觉一个不同的世界!— Ludwig Wittgenstein (1889-1951)\n", - "\n", - "相比 C++ ,Java 是一种更纯粹的面向对象编程语言。虽然它们都是混合语言,但在 Java 中,设计者们认为混合的作用并非像在 C++ 中那般重要。混合语言允许多种编程风格,这也是 C++ 支持向后兼容 C 的原因。正因为 C++ 是 C 语言的超集,所以它也同时包含了许多 C 语言不具备的特性,这使得 C++ 在某些方面过于复杂。\n", - "\n", - " Java 语言假设你只进行面向对象编程。开始学习之前,我们需要将思维置于面向对象的世界。本章你将了解到 Java 程序的基本组成,学习在 Java 中万物(几乎)皆对象的思想。\n", - "\n", - "\n", - "\n", - "## 对象操纵\n", - "\n", - "“名字代表什么?玫瑰即使不叫玫瑰,也依旧芬芳”。(引用自 莎士比亚,《罗密欧与朱丽叶》)。\n", - "\n", - "所有的编程语言都会操纵内存中的元素。有时程序员必须要有意识地直接或间接地操纵它们。在 C/C++ 中,对象的操纵是通过指针来完成的。\n", - "\n", - "Java 利用万物皆对象的思想和单一一致的语法方式来简化问题。虽万物皆可为对象,但我们所操纵的标识符实际上只是对对象的“引用” [^1]。 举例:我们可以用遥控器(引用)去操纵电视(对象)。只要拥有对象的“引用”,就可以操纵该“对象”。换句话说,我们无需直接接触电视,就可通过遥控器(引用)自由地控制电视(对象)的频道和音量。此外,没有电视,遥控器也可以单独存在。就是说,你仅仅有一个“引用”并不意味着你必然有一个与之关联的“对象”。 \n", - "\n", - "下面来创建一个 **String** 引用,用于保存单词或语句。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " String s;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里我们只是创建了一个 **String** 对象的引用,而非对象。直接拿来使用会出现错误:因为此时你并没有给变量 `s` 赋值--指向任何对象。通常更安全的做法是:创建一个引用的同时进行初始化。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " String s = \"asdf\";" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Java 语法允许我们使用带双引号的文本内容来初始化字符串。同样,其他类型的对象也有相应的初始化方式。\n", - "\n", - "\n", - "## 对象创建\n", - "\n", - "“引用”用来关联“对象”。在 Java 中,通常我们使用`new`操作符来创建一个新对象。`new` 关键字代表:创建一个新的对象实例。所以,我们也可以这样来表示前面的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " String s = new String(\"asdf\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "以上展示了字符串对象的创建过程,以及如何初始化生成字符串。除了 **String** 类型以外,Java 本身自带了许多现成的数据类型。除此之外,我们还可以创建自己的数据类型。事实上,这是 Java 程序设计中的一项基本行为。在本书后面的学习中将会接触到。\n", - "\n", - "\n", - "### 数据存储\n", - "\n", - "那么,程序在运行时是如何存储的呢?尤其是内存是怎么分配的。有5个不同的地方可以存储数据:\n", - "\n", - "1. **寄存器**(Registers)最快的存储区域,位于 CPU 内部 [^2]。然而,寄存器的数量十分有限,所以寄存器根据需求进行分配。我们对其没有直接的控制权,也无法在自己的程序里找到寄存器存在的踪迹(另一方面,C/C++ 允许开发者向编译器建议寄存器的分配)。\n", - "\n", - "2. **栈内存**(Stack)存在于常规内存 RAM(随机访问存储器,Random Access Memory)区域中,可通过栈指针获得处理器的直接支持。栈指针下移分配内存,上移释放内存,这是一种快速有效的内存分配方法,速度仅次于寄存器。创建程序时,Java 系统必须准确地知道栈内保存的所有项的生命周期。这种约束限制了程序的灵活性。因此,虽然在栈内存上存在一些 Java 数据,特别是对象引用,但 Java 对象却是保存在堆内存的。\n", - "\n", - "3. **堆内存**(Heap)这是一种通用的内存池(也在 RAM 区域),所有 Java 对象都存在于其中。与栈内存不同,编译器不需要知道对象必须在堆内存上停留多长时间。因此,用堆内存保存数据更具灵活性。创建一个对象时,只需用 `new` 命令实例化对象即可,当执行代码时,会自动在堆中进行内存分配。这种灵活性是有代价的:分配和清理堆内存要比栈内存需要更多的时间(如果可以用 Java 在栈内存上创建对象,就像在 C++ 中那样的话)。随着时间的推移,Java 的堆内存分配机制现在已经非常快,因此这不是一个值得关心的问题了。\n", - "\n", - "4. **常量存储**(Constant storage)常量值通常直接放在程序代码中,因为它们永远不会改变。如需严格保护,可考虑将它们置于只读存储器 ROM (只读存储器,Read Only Memory)中 [^3]。\n", - "\n", - "5. **非 RAM 存储**(Non-RAM storage)数据完全存在于程序之外,在程序未运行以及脱离程序控制后依然存在。两个主要的例子:(1)序列化对象:对象被转换为字节流,通常被发送到另一台机器;(2)持久化对象:对象被放置在磁盘上,即使程序终止,数据依然存在。这些存储的方式都是将对象转存于另一个介质中,并在需要时恢复成常规的、基于 RAM 的对象。Java 为轻量级持久化提供了支持。而诸如 JDBC 和 Hibernate 这些类库为使用数据库存储和检索对象信息提供了更复杂的支持。\n", - "\n", - "\n", - "### 基本类型的存储\n", - "\n", - "有一组类型在 Java 中使用频率很高,它们需要特殊对待,这就是 Java 的基本类型。之所以这么说,是因为它们的创建并不是通过 `new` 关键字来产生。通常 `new` 出来的对象都是保存在堆内存中的,以此方式创建小而简单的变量往往是不划算的。所以对于这些基本类型的创建方法,Java 使用了和 C/C++ 一样的策略。也就是说,不是使用 `new` 创建变量,而是使用一个“自动”变量。 这个变量直接存储\"值\",并置于栈内存中,因此更加高效。\n", - "\n", - "Java 确定了每种基本类型的内存占用大小。 这些大小不会像其他一些语言那样随着机器环境的变化而变化。这种不变性也是 Java 更具可移植性的一个原因。\n", - "\n", - "| 基本类型 | 大小 | 最小值 | 最大值 | 包装类型 |\n", - "| :------: | :------: | :------: | :------: | :------: |\n", - "| boolean | — | — | — | Boolean |\n", - "| char | 16 bits | Unicode 0 | Unicode 216 -1 | Character |\n", - "| byte | 8 bits | -128 | +127 | Byte |\n", - "| short | 16 bits | - 215 | + 215 -1 | Short |\n", - "| int | 32 bits | - 231 | + 231 -1 | Integer |\n", - "| long | 64 bits | - 263 | + 263 -1 | Long |\n", - "| float | 32 bits | IEEE754 | IEEE754 | Float |\n", - "| double | 64 bits |IEEE754 | IEEE754 | Double |\n", - "| void | — | — | — | Void |\n", - "\n", - "所有的数值类型都是有正/负符号的。布尔(boolean)类型的大小没有明确的规定,通常定义为取字面值 “true” 或 “false” 。基本类型有自己对应的包装类型,如果你希望在堆内存里表示基本类型的数据,就需要用到它们的包装类。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "char c = 'x';\n", - "Character ch = new Character(c);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "或者你也可以使用下面的形式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Character ch = new Character('x');" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "基本类型自动转换成包装类型(自动装箱)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Character ch = 'x';" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "相对的,包装类型转化为基本类型(自动拆箱):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "char c = ch;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "个中原因将在以后的章节里解释。\n", - "\n", - "\n", - "### 高精度数值\n", - "\n", - "在 Java 中有两种类型的数据可用于高精度的计算。它们是 `BigInteger` 和 `BigDecimal`。尽管它们大致可以划归为“包装类型”,但是它们并没有对应的基本类型。\n", - "\n", - "这两个类包含的方法提供的操作,与对基本类型执行的操作相似。也就是说,能对 int 或 float 做的运算,在 BigInteger 和 BigDecimal 这里也同样可以,只不过必须要通过调用它们的方法来实现而非运算符。此外,由于涉及到的计算量更多,所以运算速度会慢一些。诚然,我们牺牲了速度,但换来了精度。\n", - "\n", - "BigInteger 支持任意精度的整数。可用于精确表示任意大小的整数值,同时在运算过程中不会丢失精度。\n", - "BigDecimal 支持任意精度的定点数字。例如,可用它进行精确的货币计算。\n", - "\n", - "关于这两个类的详细信息,请参考 JDK 官方文档。\n", - "\n", - "\n", - "### 数组的存储\n", - "\n", - "许多编程语言都支持数组类型。在 C 和 C++ 中使用数组是危险的,因为那些数组只是内存块。如果程序访问了内存块之外的数组或在初始化之前使用该段内存(常见编程错误),则结果是不可预测的。\n", - "\n", - "Java 的设计主要目标之一是安全性,因此许多困扰 C 和 C++ 程序员的问题不会在 Java 中再现。在 Java 中,数组使用前需要被初始化,并且不能访问数组长度以外的数据。这种范围检查,是以每个数组上少量的内存开销及运行时检查下标的额外时间为代价的,但由此换来的安全性和效率的提高是值得的。(并且 Java 经常可以优化这些操作)。\n", - "\n", - "当我们创建对象数组时,实际上是创建了一个引用数组,并且每个引用的初始值都为 **null** 。在使用该数组之前,我们必须为每个引用指定一个对象 。如果我们尝试使用为 **null** 的引用,则会在运行时报错。因此,在 Java 中就防止了数组操作的常规错误。\n", - "\n", - "我们还可创建基本类型的数组。编译器通过将该数组的内存全部置零来保证初始化。本书稍后将详细介绍数组,特别是在数组章节中。\n", - "\n", - "\n", - "## 代码注释\n", - "\n", - "Java 中有两种类型的注释。第一种是传统的 C 风格的注释,以 `/*` 开头,可以跨越多行,到 `*/ ` 结束。注意,许多程序员在多行注释的每一行开头添加 `*`,所以你经常会看到:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "/* 这是\n", - "* 跨越多行的\n", - "* 注释\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但请记住,`/*` 和 `*/` 之间的内容都是被忽略的。所以你将其改为下面这样也是没有区别的。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "/* 这是跨越多\n", - "行的注释 */" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第二种注释形式来自 C++ 。它是单行注释,以 `//` 开头并一直持续到行结束。这种注释方便且常用,因为直观简单。所以你经常看到:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// 这是单行注释" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 对象清理\n", - "\n", - "在一些编程语言中,管理变量的生命周期需要大量的工作。一个变量需要存活多久?如果我们想销毁它,应该什么时候去做呢?变量生命周期的混乱会导致许多 bug,本小结向你介绍 Java 是如何通过释放存储来简化这个问题的。\n", - "\n", - "\n", - "### 作用域\n", - "\n", - "大多数程序语言都有作用域的概念。作用域决定了在该范围内定义的变量名的可见性和生存周期。在 C、 C++ 和 Java 中,作用域是由大括号 `{}` 的位置决定的。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "{\n", - " int x = 12;\n", - " // 仅 x 变量可用\n", - " {\n", - " int q = 96;\n", - " // x 和 q 变量皆可用\n", - " }\n", - " // 仅 x 变量可用\n", - " // 变量 q 不在作用域内\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Java 的变量只有在其作用域内才可用。缩进使得 Java 代码更易于阅读。由于 Java 是一种自由格式的语言,额外的空格、制表符和回车并不会影响程序的执行结果。在 Java 中,你不能执行以下操作,即使这在 C 和 C++ 中是合法的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "{\n", - " int x = 12;\n", - " {\n", - " int x = 96; // Illegal\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在上例中, Java 编译器会在提示变量 x 已经被定义过了。因此,在 C/C++ 中将一个较大作用域的变量\"隐藏\"起来的做法,在 Java 中是不被允许的。 因为 Java 的设计者认为这样做会导致程序混乱。\n", - "\n", - "\n", - "### 对象作用域\n", - "\n", - "Java 对象与基本类型具有不同的生命周期。当我们使用 `new` 关键字来创建 Java 对象时,它的生命周期将会超出作用域。因此,下面这段代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "{\n", - " String s = new String(\"a string\");\n", - "} \n", - "// 作用域终点" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中,引用 s 在作用域终点就结束了。但是,引用 s 指向的字符串对象依然还在占用内存。在这段代码中,我们无法在这个作用域之后访问这个对象,因为唯一对它的引用 s 已超出了作用域的范围。在后面的章节中,我们还会学习怎么在编程中传递和复制对象的引用。\n", - "\n", - "只要你需要,`new` 出来的对象就会一直存活下去。 相比在 C++ 编码中操作内存可能会出现的诸多问题,这些困扰在 Java 中都不复存在了。在 C++ 中你不仅要确保对象的内存在你操作的范围内存在,还必须在使用完它们之后,将其销毁。\n", - "\n", - "那么问题来了:我们在 Java 中并没有主动清理这些对象,那么它是如何避免 C++ 中出现的内存被填满从而阻塞程序的问题呢?答案是:Java 的垃圾收集器会检查所有 `new` 出来的对象并判断哪些不再可达,继而释放那些被占用的内存,供其他新的对象使用。也就是说,我们不必担心内存回收的问题了。你只需简单创建对象即可。当其不再被需要时,能自行被垃圾收集器释放。垃圾回收机制有效防止了因程序员忘记释放内存而造成的“内存泄漏”问题。\n", - "\n", - "\n", - "## 类的创建\n", - "\n", - "### 类型\n", - "\n", - "如果一切都是对象,那么是什么决定了某一类对象的外观和行为呢?换句话说,是什么确定了对象的类型?你可能很自然地想到 `type` 关键字。但是,事实上大多数面向对象的语言都使用 `class` 关键字类来描述一种新的对象。 通常在 `class` 关键字的后面的紧跟类的的名称。如下代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class ATypeName {\n", - " // 这里是类的内部\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在上例中,我们引入了一个新的类型,尽管这个类里只有一行注释。但是我们一样可以通过 `new` 关键字来创建一个这种类型的对象。如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "ATypeName a = new ATypeName();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "到现在为止,我们还不能用这个对象来做什么事(即不能向它发送任何有意义的消息),除非我们在这个类里定义一些方法。\n", - "\n", - "\n", - "### 字段\n", - "\n", - "当我们创建好一个类之后,我们可以往类里存放两种类型的元素:方法(method)和字段(field)。类的字段可以是基本类型,也可以是引用类型。如果类的字段是对某个对象的引用,那么必须要初始化该引用将其关联到一个实际的对象上(通过之前介绍的创建对象的方法)。每个对象都有用来存储其字段的空间。通常,字段不在对象间共享。下面是一个具有某些字段的类的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class DataOnly {\n", - " int i;\n", - " double d;\n", - " boolean b;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个类除了存储数据之外什么也不能做。但是,我们仍然可以通过下面的代码来创建它的一个对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " DataOnly data = new DataOnly();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们必须通过这个对象的引用来指定字段值。格式:对象名称.方法名称或字段名称。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " data.i = 47;\n", - " data.d = 1.1;\n", - " data.b = false;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你想修改对象内部包含的另一个对象的数据,可以通过这样的格式修改。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " myPlane.leftTank.capacity = 100;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以用这种方式嵌套许多对象(尽管这样的设计会带来混乱)。\n", - "\n", - "\n", - "### 基本类型默认值\n", - "\n", - "如果类的成员变量(字段)是基本类型,那么在类初始化时,这些类型将会被赋予一个初始值。\n", - "\n", - "| 基本类型 | 初始值 |\n", - "| :-----: |:-----: |\n", - "| boolean | false |\n", - "| char | \\u0000 (null) |\n", - "| byte | (byte) 0 |\n", - "| short |(short) 0 |\n", - "| int | 0 |\n", - "| long | 0L |\n", - "| float | 0.0f |\n", - "| double | 0.0d |\n", - "\n", - "这些默认值仅在 Java 初始化类的时候才会被赋予。这种方式确保了基本类型的字段始终能被初始化(在 C++ 中不会),从而减少了 bug 的来源。但是,这些初始值对于程序来说并不一定是合法或者正确的。 所以,为了安全,我们最好始终显式地初始化变量。\n", - "\n", - "这种默认值的赋予并不适用于局部变量 —— 那些不属于类的字段的变量。 因此,若在方法中定义的基本类型数据,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " int x;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里的变量 x 不会自动初始化为0,因而在使用变量 x 之前,程序员有责任主动地为其赋值(和 C 、C++ 一致)。如果我们忘记了这一步, Java 将会提示我们“编译时错误,该变量可能尚未被初始化”。 这一点做的比 C++ 更好,在后者中,编译器只是提示警告,而在 Java 中则直接报错。\n", - "\n", - "\n", - "### 方法使用\n", - "\n", - "在许多语言(如 C 和 C++)中,使用术语 *函数* (function) 用来命名子程序。在 Java 中,我们使用术语 *方法*(method)来表示“做某事的方式”。\n", - "\n", - "在 Java 中,方法决定对象能接收哪些消息。方法的基本组成部分包括名称、参数、返回类型、方法体。格式如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " [返回类型] [方法名](/*参数列表*/){\n", - " // 方法体\n", - " }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### 返回类型\n", - "\n", - "方法的返回类型表明了当你调用它时会返回的结果类型。参数列表则显示了可被传递到方法内部的参数类型及名称。方法名和参数列表统称为**方法签名**(signature of the method)。签名作为方法的唯一标识。\n", - "\n", - "Java 中的方法只能作为类的一部分创建。它只能被对象所调用 [^4],并且该对象必须有权限来执行调用。若对象调用错误的方法,则程序将在编译时报错。\n", - "\n", - "我们可以像下面这样调用一个对象的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[对象引用].[方法名](参数1, 参数2, 参数3);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "若方法不带参数,例如一个对象引用 `a` 的方法 `f` 不带参数并返回 **int** 型结果,我们可以如下表示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int x = a.f();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中方法 `f` 的返回值类型必须和变量 `x` 的类型兼容 。调用方法的行为有时被称为向对象发送消息。面向对象编程可以总结为:向对象发送消息。\n", - "\n", - "\n", - "#### 参数列表\n", - "\n", - "方法参数列表指定了传递给方法的信息。正如你可能猜到的,这些信息就像 Java 中的其他所有信息 ,以对象的形式传递。参数列表必须指定每个对象的类型和名称。同样,我们并没有直接处理对象,而是在传递对象引用 [^5] 。但是引用的类型必须是正确的。如果方法需要 String 参数,则必须传入 String,否则编译器将报错。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int storage(String s) {\n", - " return s.length() * 2;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此方法计算并返回某个字符串所占的字节数。参数 `s` 的类型为 **String** 。将 s 传递给 `storage()` 后,我们可以把它看作和任何其他对象一样,可以向它发送消息。在这里,我们调用 `length()` 方法,它是一个 String 方法,返回字符串中的字符数。字符串中每个字符的大小为 16 位或 2 个字节。你还看到了 **return** 关键字,它执行两项操作。首先,它意味着“方法执行结束”。其次,如果方法有返回值,那么该值就紧跟 **return** 语句之后。这里,返回值是通过计算" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "s.length() * 2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "产生的。在方法中,我们可以返回任何类型的数据。如果我们不想方法返回数据,则可以通过给方法标识 `void` 来表明这是一个无需返回值的方法。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "boolean flag() { \n", - " return true; \n", - "}\n", - "\n", - "double naturalLogBase() { \n", - " return 2.718; \n", - "}\n", - "\n", - "void nothing() {\n", - " return;\n", - "}\n", - "\n", - "void nothing2() {\n", - "\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当返回类型为 **void** 时, **return** 关键字仅用于退出方法,因此在方法结束处的 **return** 可被省略。我们可以随时从方法中返回,但若方法返回类型为非 `void`,则编译器会强制我们返回相应类型的值。\n", - "\n", - "上面的描述可能会让你感觉程序只不过是一堆包含各种方法的对象,在这些方法中,将对象作为参数并发送消息给其他对象。大部分情况下确实如此。但在下一章的运算符中我们将会学习如何在方法中做出决策来完成更底层、详细的工作。对于本章,知道如何发送消息就够了。\n", - "\n", - "\n", - "## 程序编写\n", - "\n", - "在看到第一个 Java 程序之前,我们还必须理解其他几个问题。\n", - "\n", - "### 命名可见性\n", - "\n", - "命名控制在任何一门编程语言中都是一个问题。如果你在两个模块中使用相同的命名,那么如何区分这两个名称,并防止两个名称发生“冲突”呢?在 C 语言编程中这是很具有挑战性的,因为程序通常是一个无法管理的名称海洋。C++ 将函数嵌套在类中,所以它们不会和嵌套在其他类中的函数名冲突。然而,C++ 还是允许全局数据和全局函数,因此仍有可能发生冲突。为了解决这个问题,C++ 使用附加的关键字引入了*命名空间*。\n", - "\n", - "Java 采取了一种新的方法避免了以上这些问题:为一个类库生成一个明确的名称,Java 创建者希望我们反向使用自己的网络域名,因为域名通常是唯一的。因此我的域名是 MindviewInc.com,所以我将我的 foibles 类库命名为 com.mindviewinc.utility.foibles。反转域名后,`.` 用来代表子目录的划分。\n", - "\n", - "在 Java 1.0 和 Java 1.1 中,域扩展名 com、 edu、 org 和 net 等按惯例大写,因此类库中会出现这样类似的名称:Com.mindviewinc.utility.foibles。然而,在 Java 2 的开发过程中,他们发现这会导致问题,所以现在整个包名都是小写的。此机制意味着所有文件都自动存在于自己的命名空间中,文件中的每个类都具有唯一标识符。这样,Java 语言可以防止名称冲突。\n", - "\n", - "使用反向 URL 是一种新的命名空间方法,在此之前尚未有其他语言这么做过。Java 中有许多这些“创造性”地解决问题的方法。正如你想象,如果我们未经测试就添加一个功能并用于生产,那么在将来发现该功能的问题再想纠正,通常为时已晚(有些错误太严重了就得从语言中删除新功能。)\n", - "\n", - "使用反向 URL 将命名空间与文件路径相关联不会导致BUG,但它却给源代码管理带来麻烦。例如在 `com.mindviewinc.utility.foibles` 这样的目录结构中,我们创建了 `com` 和 `mindviewinc` 空目录。它们存在的唯一目的就是用来表示这个反向的 URL。\n", - "\n", - "这种方式似乎为我们在编写 Java 程序中的某个问题打开了大门。空目录填充了深层次结构,它们不仅用于表示反向 URL,还用于捕获其他信息。这些长路径基本上用于存储有关目录中的内容的数据。如果你希望以最初设计的方式使用目录,这种方法可以从“令人沮丧”到“令人抓狂”,对于生产级的 Java 代码,你必须使用专门为此设计的 IDE 来管理代码。例如 NetBeans,Eclipse 或 IntelliJ IDEA。实际上,这些 IDE 都为我们管理和创建深层次空目录结构。\n", - "\n", - "对于这本书中的例子,我不想让深层次结构给你的学习带来额外的麻烦,这实际上需要你在开始之前学习熟悉一种重量级的 IDE。所以,我们的每个章节的示例都位于一个浅的子目录中,以章节标题为名。这导致我偶尔会与遵循深层次方法的工具发生冲突。\n", - "\n", - "\n", - "### 使用其他组件\n", - "\n", - "无论何时在程序中使用预先定义好的类,编译器都必须找到该类。最简单的情况下,该类存在于被调用的源代码文件中。此时我们使用该类 —— 即使该类在文件的后面才会被定义(Java 消除了所谓的“前向引用”问题)。而如果一个类位于其他文件中,又会怎样呢?你可能认为编译器应该足够智能去找到它,但这样是有问题的。想象一下,假如你要使用某个类,但目录中存在多个同名的类(可能用途不同)。或者更糟糕的是,假设你正在编写程序,在构建过程中,你想将某个新类添加到类库中,但却与已有的类名称冲突。\n", - "\n", - "要解决此问题,你必须通过使用 **import** 关键字来告诉 Java 编译器具体要使用的类。**import** 指示编译器导入一个包,也就是一个类库(在其他语言中,一个库不仅包含类,还可能包括函数和数据,但请记住 Java 中的所有代码都必须写在类里)。大多数时候,我们都在使用 Java 标准库中的组件。有了这些构件,你就不必写一长串的反转域名。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import java.util.ArrayList;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例可以告诉编译器使用位于标准库 **util** 下的 ArrayList 类。但是,**util** 中包含许多类,我们可以使用通配符 `*` 来导入其中部分类,而无需显式得逐一声明这些类。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import java.util.*;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "本书中的示例很小,为简单起见,我们通常会使用 `.*` 形式略过导入。然而,许多教程书籍都会要求程序员逐一导入每个类。 \n", - "\n", - "\n", - "### static关键字\n", - "\n", - "类是对象的外观及行为方式的描述。通常只有在使用 `new` 创建那个类的对象后,数据存储空间才被分配,对象的方法才能供外界调用。这种方式在两种情况下是不足的。\n", - "\n", - "1. 有时你只想为特定字段(注:也称为属性、域)分配一个共享存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建对象。\n", - "\n", - "2. 创建一个与此类的任何对象无关的方法。也就是说,即使没有创建对象,也能调用该方法。\n", - "\n", - "**static** 关键字(从 C++ 采用)就符合上述两点要求。当我们说某个事物是静态时,就意味着该字段或方法不依赖于任何特定的对象实例 。 即使我们从未创建过该类的对象,也可以调用其静态方法或访问其静态字段。相反,对于普通的非静态字段和方法,我们必须要先创建一个对象并使用该对象来访问字段或方法,因为非静态字段和方法必须与特定对象关联 [^6] 。\n", - "\n", - "一些面向对象的语言使用类数据(class data)和类方法(class method),表示静态数据和方法只是作为类,而不是类的某个特定对象而存在的。有时 Java 文献也使用这些术语。\n", - "\n", - "我们可以在类的字段或方法前添加 `static` 关键字来表示这是一个静态字段或静态方法。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class StaticTest {\n", - " static int i = 47;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,即使你创建了两个 `StaticTest` 对象,但是静态变量 `i` 仍只占一份存储空间。两个对象都会共享相同的变量 `i`。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "StaticTest st1 = new StaticTest();\n", - "StaticTest st2 = new StaticTest();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`st1.i` 和 `st2.i` 指向同一块存储空间,因此它们的值都是 47。引用静态变量有两种方法。在前面的示例中,我们通过一个对象来定位它,例如 `st2.i`。我们也可以通过类名直接引用它,这种方式对于非静态成员则不可行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "StaticTest.i++;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`++` 运算符将会使变量结果 + 1。此时 `st1.i` 和 `st2.i` 的值都变成了 48。\n", - "\n", - "使用类名直接引用静态变量是首选方法,因为它强调了变量的静态属性。类似的逻辑也适用于静态方法。我们可以通过对象引用静态方法,就像使用任何方法一样,也可以通过特殊的语法方式 `Classname.method()` 来直接调用静态字段或方法 [^7]。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class Incrementable {\n", - " static void increment() { \n", - " StaticTest.i++; \n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中,`Incrementable` 的 `increment()` 方法通过 `++` 运算符将静态数据 `i` 加 1。我们依然可以先实例化对象再调用该方法。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Incrementable sf = new Incrementable();\n", - "sf.increment();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当然了,首选的方法是直接通过类来调用它。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Incrementable.increment();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "相比非静态的对象,`static` 属性改变了数据创建的方式。同样,当 `static` 关键字修饰方法时,它允许我们无需创建对象就可以直接通过类的引用来调用该方法。正如我们所知,`static` 关键字的这些特性对于应用程序入口点的 `main()` 方法尤为重要。\n", - "\n", - "\n", - "## 小试牛刀\n", - "\n", - "最后,我们开始编写第一个完整的程序。我们使用 Java 标准库中的 **Date** 类来展示一个字符串和日期。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\n", - "// objects/HelloDate.java\n", - "import java.util.*;\n", - "\n", - "public class HelloDate {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Hello, it's: \");\n", - " System.out.println(new Date());\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在本书中,所有代码示例的第一行都是注释行,其中包含文件的路径信息(比如本章的目录名是 **objects**),后跟文件名。我的工具可以根据这些信息自动提取和测试书籍的代码,你也可以通过参考第一行注释轻松地在 Github 库中找到对应的代码示例。\n", - "\n", - "如果你想在代码中使用一些额外的类库,那么就必须在程序文件的开始处使用 **import** 关键字来导入它们。之所以说是额外的,因为有一些类库已经默认自动导入到每个文件里了。例如:`java.lang` 包。\n", - "\n", - "现在打开你的浏览器在 [Oracle](https://www.oracle.com/) 上查看文档。如果你还没有从 [Oracle](https://www.oracle.com/) 网站上下载 JDK 文档,那现在就去 [^8] 。查看包列表,你会看到 Java 附带的所有不同的类库。\n", - "\n", - "选择 `java.lang`,你会看到该库中所有类的列表。由于 `java.lang` 隐式包含在每个 Java 代码文件中,因此这些类是自动可用的。`java.lang` 类库中没有 **Date** 类,所以我们必须导入其他的类库(即 Date 所在的类库)。如果你不清楚某个类所在的类库或者想查看类库中所有的类,那么可以在 Java 文档中选择 “Tree” 查看。\n", - "\n", - "现在,我们可以找到 Java 附带的每个类。使用浏览器的“查找”功能查找 **Date**,搜索结果中将会列出 **java.util.Date**,我们就知道了 **Date** 在 **util** 库中,所以必须导入 **java.util.*** 才能使用 **Date**。\n", - "\n", - "如果你在文档中选择 **java.lang**,然后选择 **System**,你会看到 **System** 类中有几个字段,如果你选择了 **out**,你会发现它是一个静态的 **PrintStream** 对象。 所以,即使我们不使用 **new** 创建, **out** 对象就已经存在并可以使用。 **out** 对象可以执行的操作取决于它的类型: **PrintStream** ,其在文档中是一个超链接,如果单击该链接,我们将可以看到 **PrintStream** 对应的方法列表(更多详情,将在本书后面介绍)。 现在我们重点说的是 **println()** 这个方法。 它的作用是 “将信息输出到控制台,并以换行符结束”。既然如此,我们可以这样编码来输出信息到控制台。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "System.out.println(\"A String of things\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个 java 源文件中允许有多个类。同时,源文件的名称必须要和其中一个类名相同,否则编译器将会报错。每个独立的程序应该包含一个 `main()` 方法作为程序运行的入口。其方法签名和返回类型如下。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public static void main(String[] args) {\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "关键字 **public** 表示方法可以被外界访问到。( 更多详情将在 **隐藏实现** 章节讲到)\n", - "**main()** 方法的参数是一个 字符串(**String**) 数组。 参数 **args** 并没有在当前的程序中使用到,但是 Java 编译器强制要求必须要有, 这是因为它们被用于接收从命令行输入的参数。\n", - "\n", - "下面我们来看一段有趣的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "System.out.println(new Date());" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上面的示例中,我们创建了一个日期(**Date**)类型的对象并将其转化为字符串类型,输出到控制台中。 一旦这一行语句执行完毕,我们就不再需要该日期对象了。这时,Java 垃圾回收器就可以将其占用的内存回收,我们无需去主动清除它们。\n", - "\n", - "查看 JDK 文档时,我们可以看到在 **System** 类下还有很多其他有用的方法( Java 的牛逼之处还在于,它拥有一个庞大的标准库资源)。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// objects/ShowProperties.java\n", - "public class ShowProperties {\n", - " public static void main(String[] args) {\n", - " System.getProperties().list(System.out);\n", - " System.out.println(System.getProperty(\"user.name\"));\n", - " System.out.println(System.getProperty(\"java.library.path\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果(前20行):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "text" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "java.runtime.name=Java(TM) SE Runtime Environment\n", - "sun.boot.library.path=C:\\Program\n", - "Files\\Java\\jdk1.8.0_112\\jr...\n", - "java.vm.version=25.112-b15\n", - "java.vm.vendor=Oracle Corporation\n", - "java.vendor.url=http://java.oracle.com/\n", - "path.separator=;\n", - "java.vm.name=Java HotSpot(TM) 64-Bit Server VM\n", - "file.encoding.pkg=sun.io\n", - "user.script=\n", - "user.country=US\n", - "sun.java.launcher=SUN_STANDARD\n", - "sun.os.patch.level=\n", - "java.vm.specification.name=Java Virtual Machine\n", - "Specification\n", - "user.dir=C:\\Users\\Bruce\\Documents\\GitHub\\on-ja...\n", - "java.runtime.version=1.8.0_112-b15\n", - "java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment\n", - "java.endorsed.dirs=C:\\Program\n", - "Files\\Java\\jdk1.8.0_112\\jr...\n", - "os.arch=amd64\n", - "java.io.tmpdir=C:\\Users\\Bruce\\AppData\\Local\\Temp\\" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`main()` 方法中的第一行会输出所有的系统字段,也就是环境信息。 **list()** 方法将结果发送给它的参数 **System.out**。在本书的后面,我们还会接触到将结果输出到其他地方,例如文件中。另外,我们还可以请求特定的字段。该例中我们使用到了 **user.name** 和 **java.library.path**。 \n", - "\n", - "\n", - "### 编译和运行\n", - "\n", - "要编译和运行本书中的代码示例,首先必须具有 Java 编程环境。 第二章的示例中描述了安装过程。如果你遵循这些说明,那么你将会在不受 Oracle 的限制的条件下用到 Java 开发工具包(JDK)。如果你使用其他开发系统,请查看该系统的文档以确定如何编译和运行程序。 第二章还介绍了如何安装本书的示例。 \n", - "\n", - "移动到子目录 **objects** 下并键入:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%bash\n", - "javac HelloDate.java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此命令不应产生任何响应。如果我们收到任何类型的错误消息,则表示未正确安装 JDK,那就得检查这些问题。\n", - "\n", - "若执行不报错的话,此时可以键入:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "java HelloDate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们将会得到正确的日期输出。这是我们编译和运行本书中每个程序(包含 `main()` 方法)的过程 [^9]。此外,本书的源代码在根目录中也有一个名为 **build.gradle** 的文件,其中包含用于自动构建,测试和运行本书文件的 **Gradle** 配置。当你第一次运行 `gradlew` 命令时,**Gradle** 将自动安装(前提是已安装Java)。\n", - "\n", - "\n", - "## 编码风格\n", - "\n", - "Java 编程语言编码规范(Code Conventions for the Java Programming Language)[^10] 要求类名的首字母大写。 如果类名是由多个单词构成的,则每个单词的首字母都应大写(不采用下划线来分隔)例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class AllTheColorsOfTheRainbow {\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有时称这种命名风格叫“驼峰命名法”。对于几乎所有其他方法,字段(成员变量)和对象引用名都采用驼峰命名的方式,但是它们的首字母不需要大写。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class AllTheColorsOfTheRainbow {\n", - " int anIntegerRepresentingColors;\n", - " void changeTheHueOfTheColor(int newHue) {\n", - " // ...\n", - " }\n", - " // ...\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Oracle 的官方类库中,花括号的位置同样遵循和本书中上述示例相同的规范。\n", - "\n", - "## 本章小结\n", - "\n", - "本章向你展示了简单的 Java 程序编写以及该语言相关的基本概念。到目前为止,我们的示例都只是些简单的顺序执行。在接下来的两章里,我们将会接触到 Java 的一些基本操作符,以及如何去控制程序执行的流程。\n", - "\n", - "[^1]: 这里可能有争议。有人说这是一个指针,但这假定了一个潜在的实现。此外,Java 引用的语法更类似于 C++ 引用而非指针。在 《Thinking in Java》 的第 1 版中,我发明了一个新术语叫“句柄”(handle),因为 C++ 引用和Java 引用有一些重要的区别。作为一个从 C++ 的过来人,我不想混淆 Java 可能的最大受众 —— C++ 程序员。在《Thinking in Java》的第 2 版中,我认为“引用”(reference)是更常用的术语,从 C++ 转过来的人除了引用的术语之外,还有很多东西需要处理,所以他们不妨双脚都跳进去。但是,也有些人甚至不同意“引用”。在某书中我读到一个观点:Java 支持引用传递的说法是完全错误的,因为 Java 对象标识符(根据该作者)实际上是“对象引用”(object references),并且一切都是值传递。所以你不是通过引用传递,而是“通过值传递对象引用。人们可以质疑我的这种解释的准确性,但我认为我的方法简化了对概念的理解而又没对语言造成伤害(嗯,语言专家可能会说我骗你,但我会说我只是对此进行了适当的抽象。)\n", - "\n", - "[^2]: 大多数微处理器芯片都有额外的高速缓冲存储器,但这是按照传统存储器而不是寄存器。\n", - "\n", - "[^3]: 一个例子是字符串常量池。所有文字字符串和字符串值常量表达式都会自动放入特殊的静态存储中。\n", - "\n", - "[^4]: 静态方法,我们很快就能接触到,它可以在没有对象的情况下直接被类调用。\n", - "\n", - "[^5]: 通常除了前面提到的“特殊”数据类型 boolean、 char、 byte、 short、 int、 long、 float 和 double。通常来说,传递对象就意味者传递对象的引用。\n", - "\n", - "[^6]: 静态方法在使用之前不需要创建对象,因此它们不能直接调用非静态的成员或方法(因为非静态成员和方法必须要先实例化为对象才可以被使用)。\n", - "\n", - "[^7]: 在某些情况下,它还为编译器提供了更好的优化可能。\n", - "\n", - "[^8]: 请注意,此文档未包含在 JDK 中;你必须单独下载才能获得它。\n", - "\n", - "[^9]: 对于本书中编译和运行命令行的每个程序,你可能还需要设置 CLASSPATH 。\n", - "\n", - "[^10]: 为了保持本书的代码排版紧凑,我并没完全遵守规范,但我尽量会做到符合 Java 标准。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/04-Operators.ipynb b/jupyter/04-Operators.ipynb deleted file mode 100644 index 32004d06..00000000 --- a/jupyter/04-Operators.ipynb +++ /dev/null @@ -1,2247 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第四章 运算符\n", - "\n", - ">运算符操纵数据。\n", - "\n", - "Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。\n", - "\n", - "如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。\n", - "\n", - "\n", - "## 开始使用\n", - "\n", - "运算符接受一个或多个参数并生成新值。这个参数与普通方法调用的形式不同,但效果是相同的。加法 `+`、减法 `-`、乘法 `*`、除法 `/` 以及赋值 `=` 在任何编程语言中的工作方式都是类似的。所有运算符都能根据自己的运算对象生成一个值。除此以外,一些运算符可改变运算对象的值,这叫作“副作用”(**Side Effect**)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的值亦可由没有副作用的运算符生成。\n", - "\n", - "几乎所有运算符都只能操作基本类型(Primitives)。唯一的例外是 `=`、`==` 和 `!=`,它们能操作所有对象(这也是令人混淆的一个地方)。除此以外,**String** 类支持 `+` 和 `+=`。\n", - "\n", - "\n", - "## 优先级\n", - "\n", - "运算符的优先级决定了存在多个运算符时一个表达式各部分的运算顺序。Java 对运算顺序作出了特别的规定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所以应该用括号明确规定运算顺序。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a = 5\n", - "b = 1\n" - ] - } - ], - "source": [ - "// operators/Precedence.java\n", - "public class Precedence {\n", - " \n", - " public static void main(String[] args) {\n", - " int x = 1, y = 2, z = 3;\n", - " int a = x + y - 2/2 + z; // [1]\n", - " int b = x + (y - 2)/(2 + z); // [2]\n", - " System.out.println(\"a = \" + a);\n", - " System.out.println(\"b = \" + b);\n", - " }\n", - "}\n", - "\n", - "Precedence.main(new String [0]);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些语句看起来大致相同,但从输出中我们可以看出它们具有非常不同的含义,具体取决于括号的使用。\n", - "\n", - "我们注意到,在 `System.out.println()` 语句中使用了 `+` 运算符。 但是在这里 `+` 代表的意思是字符串连接符。编译器会将 `+` 连接的非字符串尝试转换为字符串。上例中的输出结果说明了 a 和 b 都已经被转化成了字符串。\n", - "\n", - "\n", - "## 赋值\n", - "\n", - "运算符的赋值是由符号 `=` 完成的。它代表着获取 `=` 右边的值并赋给左边的变量。右边可以是任何常量、变量或者可产生一个返回值的表达式。但左边必须是一个明确的、已命名的变量。也就是说,必须要有一个物理的空间来存放右边的值。举个例子来说,可将一个常数赋给一个变量(A = 4),但不可将任何东西赋给一个常数(比如不能 4 = A)。\n", - "\n", - "**基本类型的赋值都是直接的,而不像对象,赋予的只是其内存的引用。**\n", - "\n", - "举个例子,a = b ,如果 b 是基本类型,那么赋值操作会将 b 的值复制一份给变量 a, 此后若 a 的值发生改变是不会影响到 b 的。作为一名程序员,这应该成为我们的常识。\n", - "\n", - "如果是为对象赋值,那么结果就不一样了。对一个对象进行操作时,我们实际上操作的是它的引用。所以我们将右边的对象赋予给左边时,赋予的只是该对象的引用。此时,两者指向的堆中的对象还是同一个。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "attributes": { - "classes": [ - "java " - ], - "id": "" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1: t1.level: 9, t2.level: 47\n", - "2: t1.level: 47, t2.level: 47\n", - "3: t1.level: 27, t2.level: 27\n" - ] - } - ], - "source": [ - "// operators/Assignment.java\n", - "// Assignment with objects is a bit tricky\n", - "class Tank {\n", - " int level;\n", - "}\n", - "\n", - "public class Assignment {\n", - "\n", - " public static void main(String[] args) {\n", - " Tank t1 = new Tank();\n", - " Tank t2 = new Tank();\n", - " t1.level = 9;\n", - " t2.level = 47;\n", - " System.out.println(\"1: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " t1 = t2;\n", - " System.out.println(\"2: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " t1.level = 27;\n", - " System.out.println(\"3: t1.level: \" + t1.level +\n", - " \", t2.level: \" + t2.level);\n", - " }\n", - "}\n", - "\n", - "Assignment.main(new String [0]);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1: t1.level: 9, t2.level: 47\n", - "2: t1.level: 47, t2.level: 47\n", - "3: t1.level: 27, t2.level: 27" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个简单的 `Tank` 类,在 `main()` 方法创建了两个实例对象。 两个对象的 `level` 属性分别被赋予不同的值。 然后,t2 的值被赋予给 t1。在许多编程语言里,预期的结果是 t1 和 t2 的值会一直相对独立。但是,在 Java 中,由于赋予的只是对象的引用,改变 t1 也就改变了 t2。 这是因为 t1 和 t2 此时指向的是堆中同一个对象。(t1 原始对象的引用在 t2 赋值给其时丢失,它引用的对象会在垃圾回收时被清理)。\n", - "\n", - "这种现象通常称为别名(aliasing),这是 Java 处理对象的一种基本方式。但是假若你不想出现这里的别名引起混淆的话,你可以这么做。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "t1.level = t2.level;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "较之前的做法,这样做保留了两个单独的对象,而不是丢弃一个并将 t1 和 t2 绑定到同一个对象。但是这样的操作有点违背 Java 的设计原则。对象的赋值是个需要重视的环节,否则你可能收获意外的“惊喜”。\n", - "\n", - " \n", - "### 方法调用中的别名现象\n", - "\n", - "当我们把对象传递给方法时,会发生别名现象。" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1: x.c: a\n", - "2: x.c: z\n" - ] - } - ], - "source": [ - "// operators/PassObject.java\n", - "// 正在传递的对象可能不是你之前使用的\n", - "class Letter {\n", - " char c;\n", - "}\n", - "\n", - "public class PassObject {\n", - " static void f(Letter y) {\n", - " y.c = 'z';\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Letter x = new Letter();\n", - " x.c = 'a';\n", - " System.out.println(\"1: x.c: \" + x.c);\n", - " f(x);\n", - " System.out.println(\"2: x.c: \" + x.c);\n", - " }\n", - "}\n", - "\n", - "PassObject.main(new String[0])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1: x.c: a\n", - "2: x.c: z" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在许多编程语言中,方法 `f()` 似乎会在内部复制其参数 **Letter y**。但是一旦传递了一个引用,那么实际上 `y.c ='z';` 是在方法 `f()` 之外改变对象。别名现象以及其解决方案是个复杂的问题,在附录中有包含:[对象传递和返回](./Appendix-Passing-and-Returning-Objects.md)。意识到这一点,我们可以警惕类似的陷阱。\n", - "\n", - "\n", - "## 算术运算符\n", - "\n", - "Java 的基本算术运算符与其他大多编程语言是相同的。其中包括加号 `+`、减号 `-`、除号 `/`、乘号 `*` 以及取模 `%`(从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。\n", - "\n", - "Java 也用一种与 C++ 相同的简写形式同时进行运算和赋值操作,由运算符后跟等号表示,并且与语言中的所有运算符一致(只要有意义)。 可用 x += 4 来表示:将 x 的值加上4的结果再赋值给 x。更多代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/MathOps.java\n", - "// The mathematical operators\n", - "import java.util.*;\n", - "\n", - "public class MathOps {\n", - " public static void main(String[] args) {\n", - " // Create a seeded random number generator:\n", - " Random rand = new Random(47);\n", - " int i, j, k;\n", - " // Choose value from 1 to 100:\n", - " j = rand.nextInt(100) + 1;\n", - " System.out.println(\"j : \" + j);\n", - " k = rand.nextInt(100) + 1;\n", - " System.out.println(\"k : \" + k);\n", - " i = j + k;\n", - " System.out.println(\"j + k : \" + i);\n", - " i = j - k;\n", - " System.out.println(\"j - k : \" + i);\n", - " i = k / j;\n", - " System.out.println(\"k / j : \" + i);\n", - " i = k * j;\n", - " System.out.println(\"k * j : \" + i);\n", - " i = k % j;\n", - " System.out.println(\"k % j : \" + i);\n", - " j %= k;\n", - " System.out.println(\"j %= k : \" + j);\n", - " // 浮点运算测试\n", - " float u, v, w; // Applies to doubles, too\n", - " v = rand.nextFloat();\n", - " System.out.println(\"v : \" + v);\n", - " w = rand.nextFloat();\n", - " System.out.println(\"w : \" + w);\n", - " u = v + w;\n", - " System.out.println(\"v + w : \" + u);\n", - " u = v - w;\n", - " System.out.println(\"v - w : \" + u);\n", - " u = v * w;\n", - " System.out.println(\"v * w : \" + u);\n", - " u = v / w;\n", - " System.out.println(\"v / w : \" + u);\n", - " // 下面的操作同样适用于 char, \n", - " // byte, short, int, long, and double:\n", - " u += v;\n", - " System.out.println(\"u += v : \" + u);\n", - " u -= v;\n", - " System.out.println(\"u -= v : \" + u);\n", - " u *= v;\n", - " System.out.println(\"u *= v : \" + u);\n", - " u /= v;\n", - " System.out.println(\"u /= v : \" + u); \n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "j : 59\n", - "k : 56\n", - "j + k : 115\n", - "j - k : 3\n", - "k / j : 0\n", - "k * j : 3304\n", - "k % j : 56\n", - "j %= k : 3\n", - "v : 0.5309454\n", - "w : 0.0534122\n", - "v + w : 0.5843576\n", - "v - w : 0.47753322\n", - "v * w : 0.028358962\n", - "v / w : 9.940527\n", - "u += v : 10.471473\n", - "u -= v : 9.940527\n", - "u *= v : 5.2778773\n", - "u /= v : 9.940527" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了生成随机数字,程序首先创建一个 **Random** 对象。不带参数的 **Random** 对象会利用当前的时间用作随机数生成器的“种子”(seed),从而为程序的每次执行生成不同的输出。在本书的示例中,重要的是每个示例末尾的输出尽可能一致,以便可以使用外部工具进行验证。所以我们通过在创建 **Random** 对象时提供种子(随机数生成器的初始化值,其始终为特定种子值产生相同的序列),让程序每次执行都生成相同的随机数,如此以来输出结果就是可验证的 [^1]。 若需要生成随机值,可删除代码示例中的种子参数。该对象通过调用方法 `nextInt()` 和 `nextFloat()`(还可以调用 `nextLong()` 或 `nextDouble()`),使用 **Random** 对象生成许多不同类型的随机数。`nextInt()` 的参数设置生成的数字的上限,下限为零,为了避免零除的可能性,结果偏移1。\n", - "\n", - "\n", - "### 一元加减运算符\n", - "\n", - "一元加 `+` 减 `-` 运算符的操作和二元是相同的。编译器可自动识别使用何种方式解析运算:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = -a;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的代码表意清晰,编译器可正确识别。下面再看一个示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = a * -b;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然编译器可以正确的识别,但是程序员可能会迷惑。为了避免混淆,推荐下面的写法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x = a * (-b);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一元减号可以得到数据的负值。一元加号的作用相反,不过它唯一能影响的就是把较小的数值类型自动转换为 **int** 类型。\n", - "\n", - "\n", - "## 递增和递减\n", - "\n", - "和 C 语言类似,Java 提供了许多快捷运算方式。快捷运算可使代码可读性,可写性都更强。其中包括递增 `++` 和递减 `--`,意为“增加或减少一个单位”。举个例子来说,假设 a 是一个 **int** 类型的值,则表达式 `++a` 就等价于 `a = a + 1`。 递增和递减运算符不仅可以修改变量,还可以生成变量的值。\n", - "\n", - "每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀”和“后缀”。“前递增”表示 `++` 运算符位于变量或表达式的前面;而“后递增”表示 `++` 运算符位于变量的后面。类似地,“前递减”意味着 `--` 运算符位于变量的前面;而“后递减”意味着 `--` 运算符位于变量的后面。对于前递增和前递减(如 `++a` 或 `--a`),会先执行递增/减运算,再返回值。而对于后递增和后递减(如 `a++` 或 `a--`),会先返回值,再执行递增/减运算。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/AutoInc.java\n", - "// 演示 ++ 和 -- 运算符\n", - "public class AutoInc {\n", - " public static void main(String[] args) {\n", - " int i = 1;\n", - " System.out.println(\"i: \" + i);\n", - " System.out.println(\"++i: \" + ++i); // 前递增\n", - " System.out.println(\"i++: \" + i++); // 后递增\n", - " System.out.println(\"i: \" + i);\n", - " System.out.println(\"--i: \" + --i); // 前递减\n", - " System.out.println(\"i--: \" + i--); // 后递减\n", - " System.out.println(\"i: \" + i);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i: 1\n", - "++i: 2\n", - "i++: 2\n", - "i: 3\n", - "--i: 2\n", - "i--: 2\n", - "i: 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于前缀形式,我们将在执行递增/减操作后获取值;使用后缀形式,我们将在执行递增/减操作之前获取值。它们是唯一具有“副作用”的运算符(除那些涉及赋值的以外) —— 它们修改了操作数的值。\n", - "\n", - "C++ 名称来自于递增运算符,暗示着“比 C 更进一步”。在早期的 Java 演讲中,*Bill Joy*(Java 作者之一)说“**Java = C++ --**”(C++ 减减),意味着 Java 在 C++ 的基础上减少了许多不必要的东西,因此语言更简单。随着进一步地学习,我们会发现 Java 的确有许多地方相对 C++ 来说更简便,但是在其他方面,难度并不会比 C++ 小多少。\n", - "\n", - "\n", - "## 关系运算符\n", - "\n", - "关系运算符会通过产生一个布尔(**boolean**)结果来表示操作数之间的关系。如果关系为真,则结果为 **true**,如果关系为假,则结果为 **false**。关系运算符包括小于 `<`,大于 `>`,小于或等于 `<=`,大于或等于 `>=`,等于 `==` 和不等于 `!=`。`==` 和 `!=` 可用于所有基本类型,但其他运算符不能用于基本类型 **boolean**,因为布尔值只能表示 **true** 或 **false**,所以比较它们之间的“大于”或“小于”没有意义。\n", - "\n", - "\n", - "### 测试对象等价\n", - "\n", - "关系运算符 `==` 和 `!=` 同样适用于所有对象之间的比较运算,但它们比较的内容却经常困扰 Java 的初学者。下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Equivalence.java\n", - "public class Equivalence {\n", - " public static void main(String[] args) {\n", - " Integer n1 = 47;\n", - " Integer n2 = 47;\n", - " System.out.println(n1 == n2);\n", - " System.out.println(n1 != n2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "true\n", - "false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "表达式 `System.out.println(n1 == n2)` 将会输出比较的结果。因为两个 **Integer** 对象相同,所以先输出 **true**,再输出 **false**。但是,尽管对象的内容一样,对象的引用却不一样。`==` 和 `!=` 比较的是对象引用,所以输出实际上应该是先输出 **false**,再输出 **true**(译者注:如果你把 47 改成 128,那么打印的结果就是这样,因为 Integer 内部维护着一个 IntegerCache 的缓存,默认缓存范围是 [-128, 127],所以 [-128, 127] 之间的值用 `==` 和 `!=` 比较也能能到正确的结果,但是不推荐用关系运算符比较,具体见 JDK 中的 Integer 类源码)。\n", - "\n", - "那么怎么比较两个对象的内容是否相同呢?你必须使用所有对象(不包括基本类型)中都存在的 `equals()` 方法,下面是如何使用 `equals()` 方法的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/EqualsMethod.java\n", - "public class EqualsMethod {\n", - " public static void main(String[] args) {\n", - " Integer n1 = 47;\n", - " Integer n2 = 47;\n", - " System.out.println(n1.equals(n2));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "true" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的结果看起来是我们所期望的。但其实事情并非那么简单。下面我们来创建自己的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/EqualsMethod2.java\n", - "// 默认的 equals() 方法没有比较内容\n", - "class Value {\n", - " int i;\n", - "}\n", - "\n", - "public class EqualsMethod2 {\n", - " public static void main(String[] args) {\n", - " Value v1 = new Value();\n", - " Value v2 = new Value();\n", - " v1.i = v2.i = 100;\n", - " System.out.println(v1.equals(v2));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例的结果再次令人困惑:结果是 **false**。原因: `equals()` 的默认行为是比较对象的引用而非具体内容。因此,除非你在新类中覆写 `equals()` 方法,否则我们将获取不到想要的结果。不幸的是,在学习 [复用](./08-Reuse.md)(**Reuse**) 章节后我们才能接触到“覆写”(**Override**),并且直到 [附录:集合主题](./Appendix-Collection-Topics.md),才能知道定义 `equals()` 方法的正确方式,但是现在明白 `equals()` 行为方式也可能为你节省一些时间。\n", - "\n", - "大多数 Java 库类通过覆写 `equals()` 方法比较对象的内容而不是其引用。\n", - "\n", - "\n", - "## 逻辑运算符\n", - "\n", - "每个逻辑运算符 `&&` (**AND**)、`||`(**OR**)和 `!`(**非**)根据参数的逻辑关系生成布尔值 `true` 或 `false`。下面的代码示例使用了关系运算符和逻辑运算符:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Bool.java\n", - "// 关系运算符和逻辑运算符\n", - "import java.util.*;\n", - "public class Bool {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " int i = rand.nextInt(100);\n", - " int j = rand.nextInt(100);\n", - " System.out.println(\"i = \" + i);\n", - " System.out.println(\"j = \" + j);\n", - " System.out.println(\"i > j is \" + (i > j));\n", - " System.out.println(\"i < j is \" + (i < j));\n", - " System.out.println(\"i >= j is \" + (i >= j));\n", - " System.out.println(\"i <= j is \" + (i <= j));\n", - " System.out.println(\"i == j is \" + (i == j));\n", - " System.out.println(\"i != j is \" + (i != j));\n", - " // 将 int 作为布尔处理不是合法的 Java 写法\n", - " //- System.out.println(\"i && j is \" + (i && j));\n", - " //- System.out.println(\"i || j is \" + (i || j));\n", - " //- System.out.println(\"!i is \" + !i);\n", - " System.out.println(\"(i < 10) && (j < 10) is \"\n", - " + ((i < 10) && (j < 10)) );\n", - " System.out.println(\"(i < 10) || (j < 10) is \"\n", - " + ((i < 10) || (j < 10)) );\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 58\n", - "j = 55\n", - "i > j is true\n", - "i < j is false\n", - "i >= j is true\n", - "i <= j is false\n", - "i == j is false\n", - "i != j is true\n", - "(i < 10) && (j < 10) is false\n", - "(i < 10) || (j < 10) is false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Java 逻辑运算中,我们不能像 C/C++ 那样使用非布尔值, 而仅能使用 **AND**、 **OR**、 **NOT**。上面的例子中,我们将使用非布尔值的表达式注释掉了(你可以看到表达式前面是 //-)。但是,后续的表达式使用关系比较生成布尔值,然后对结果使用了逻辑运算。请注意,如果在预期为 **String** 类型的位置使用 **boolean** 类型的值,则结果会自动转为适当的文本格式(即 \"true\" 或 \"false\" 字符串)。\n", - "\n", - "我们可以将前一个程序中 **int** 的定义替换为除 **boolean** 之外的任何其他基本数据类型。但请注意,**float** 类型的数值比较非常严格,只要两个数字的最小位不同则两个数仍然不相等;只要数字最小位是大于 0 的,那么它就不等于 0。\n", - "\n", - "\n", - "### 短路\n", - "\n", - "逻辑运算符支持一种称为“短路”(short-circuiting)的现象。整个表达式会在运算到可以明确结果时就停止并返回结果,这意味着该逻辑表达式的后半部分不会被执行到。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators / ShortCircuit.java \n", - "// 逻辑运算符的短路行为\n", - "public class ShortCircuit {\n", - "\n", - " static boolean test1(int val) {\n", - " System.out.println(\"test1(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 1));\n", - " return val < 1;\n", - " }\n", - "\n", - " static boolean test2(int val) {\n", - " System.out.println(\"test2(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 2));\n", - " return val < 2;\n", - " }\n", - "\n", - " static boolean test3(int val) {\n", - " System.out.println(\"test3(\" + val + \")\");\n", - " System.out.println(\"result: \" + (val < 3));\n", - " return val < 3;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " boolean b = test1(0) && test2(2) && test3(2);\n", - " System.out.println(\"expression is \" + b);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "test1(0)\n", - "result: true\n", - "test2(2)\n", - "result: false\n", - "expression is false" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个测试都对参数执行比较并返回 `true` 或 `false`。同时控制台也会在方法执行时打印他们的执行状态。 下面的表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "test1(0)&& test2(2)&& test3(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可能你的预期是程序会执行 3 个 **test** 方法并返回。我们来分析一下:第一个方法的结果返回 `true`,因此表达式会继续走下去。紧接着,第二个方法的返回结果是 `false`。这就代表这整个表达式的结果肯定为 `false`,所以就没有必要再判断剩下的表达式部分了。\n", - "\n", - "所以,运用“短路”可以节省部分不必要的运算,从而提高程序潜在的性能。\n", - "\n", - "\n", - "## 字面值常量\n", - "\n", - "通常,当我们向程序中插入一个字面值常量(**Literal**)时,编译器会确切地识别它的类型。当类型不明确时,必须辅以字面值常量关联来帮助编译器识别。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Literals.java\n", - "public class Literals {\n", - " public static void main(String[] args) {\n", - " int i1 = 0x2f; // 16进制 (小写)\n", - " System.out.println(\n", - " \"i1: \" + Integer.toBinaryString(i1));\n", - " int i2 = 0X2F; // 16进制 (大写)\n", - " System.out.println(\n", - " \"i2: \" + Integer.toBinaryString(i2));\n", - " int i3 = 0177; // 8进制 (前导0)\n", - " System.out.println(\n", - " \"i3: \" + Integer.toBinaryString(i3));\n", - " char c = 0xffff; // 最大 char 型16进制值\n", - " System.out.println(\n", - " \"c: \" + Integer.toBinaryString(c));\n", - " byte b = 0x7f; // 最大 byte 型16进制值 10101111;\n", - " System.out.println(\n", - " \"b: \" + Integer.toBinaryString(b));\n", - " short s = 0x7fff; // 最大 short 型16进制值\n", - " System.out.println(\n", - " \"s: \" + Integer.toBinaryString(s));\n", - " long n1 = 200L; // long 型后缀\n", - " long n2 = 200l; // long 型后缀 (容易与数值1混淆)\n", - " long n3 = 200;\n", - " \n", - " // Java 7 二进制字面值常量:\n", - " byte blb = (byte)0b00110101;\n", - " System.out.println(\n", - " \"blb: \" + Integer.toBinaryString(blb));\n", - " short bls = (short)0B0010111110101111;\n", - " System.out.println(\n", - " \"bls: \" + Integer.toBinaryString(bls));\n", - " int bli = 0b00101111101011111010111110101111;\n", - " System.out.println(\n", - " \"bli: \" + Integer.toBinaryString(bli));\n", - " long bll = 0b00101111101011111010111110101111;\n", - " System.out.println(\n", - " \"bll: \" + Long.toBinaryString(bll));\n", - " float f1 = 1;\n", - " float f2 = 1F; // float 型后缀\n", - " float f3 = 1f; // float 型后缀\n", - " double d1 = 1d; // double 型后缀\n", - " double d2 = 1D; // double 型后缀\n", - " // (long 型的字面值同样适用于十六进制和8进制 )\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i1: 101111\n", - "i2: 101111\n", - "i3: 1111111\n", - "c: 1111111111111111\n", - "b: 1111111\n", - "s: 111111111111111\n", - "blb: 110101\n", - "bls: 10111110101111\n", - "bli: 101111101011111010111110101111\n", - "bll: 101111101011111010111110101111" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在文本值的后面添加字符可以让编译器识别该文本值的类型。对于 **Long** 型数值,结尾使用大写 `L` 或小写 `l` 皆可(不推荐使用 `l`,因为容易与阿拉伯数值 1 混淆)。大写 `F` 或小写 `f` 表示 **float** 浮点数。大写 `D` 或小写 `d` 表示 **double** 双精度。\n", - "\n", - "十六进制(以 16 为基数),适用于所有整型数据类型,由前导 `0x` 或 `0X` 表示,后跟 0-9 或 a-f (大写或小写)。如果我们在初始化某个类型的数值时,赋值超出其范围,那么编译器会报错(不管值的数字形式如何)。在上例的代码中,**char**、**byte** 和 **short** 的值已经是最大了。如果超过这些值,编译器将自动转型为 **int**,并且提示我们需要声明强制转换(强制转换将在本章后面定义),意味着我们已越过该类型的范围界限。\n", - "\n", - "八进制(以 8 为基数)由 0~7 之间的数字和前导零 `0` 表示。\n", - "\n", - "Java 7 引入了二进制的字面值常量,由前导 `0b` 或 `0B` 表示,它可以初始化所有的整数类型。\n", - "\n", - "使用整型数值类型时,显示其二进制形式会很有用。在 Long 型和 Integer 型中这很容易实现,调用其静态的 `toBinaryString()` 方法即可。 但是请注意,若将较小的类型传递给 **Integer.**`tobinarystring()` 时,类型将自动转换为 **int**。\n", - "\n", - "\n", - "### 下划线\n", - "\n", - "Java 7 中有一个深思熟虑的补充:我们可以在数字字面量中包含下划线 `_`,以使结果更清晰。这对于大数值的分组特别有用。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Underscores.java\n", - "public class Underscores {\n", - " public static void main(String[] args) {\n", - " double d = 341_435_936.445_667;\n", - " System.out.println(d);\n", - " int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;\n", - " System.out.println(Integer.toBinaryString(bin));\n", - " System.out.printf(\"%x%n\", bin); // [1]\n", - " long hex = 0x7f_e9_b7_aa;\n", - " System.out.printf(\"%x%n\", hex);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "3.41435936445667E8\n", - "101111101011111010111110101111\n", - "2fafafaf\n", - "7fe9b7aa" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "下面是合理使用的规则:\n", - "\n", - "1. 仅限单 `_`,不能多条相连。\n", - "2. 数值开头和结尾不允许出现 `_`。\n", - "3. `F`、`D` 和 `L`的前后禁止出现 `_`。\n", - "4. 二进制前导 `b` 和 十六进制 `x` 前后禁止出现 `_`。\n", - "\n", - "[1] 注意 `%n`的使用。熟悉 C 风格的程序员可能习惯于看到 `\\n` 来表示换行符。问题在于它给你的是一个“Unix风格”的换行符。此外,如果我们使用的是 Windows,则必须指定 `\\r\\n`。这种差异的包袱应该由编程语言来解决。这就是 Java 用 `%n` 实现的可以忽略平台间差异而生成适当的换行符,但只有当你使用 `System.out.printf()` 或 `System.out.format()` 时。对于 `System.out.println()`,我们仍然必须使用 `\\n`;如果你使用 `%n`,`println()` 只会输出 `%n` 而不是换行符。\n", - "\n", - "\n", - "### 指数计数法\n", - "\n", - "指数总是采用一种我认为很不直观的记号方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Exponents.java\n", - "// \"e\" 表示 10 的几次幂\n", - "public class Exponents {\n", - " public static void main(String[] args) {\n", - " // 大写 E 和小写 e 的效果相同:\n", - " float expFloat = 1.39e-43f;\n", - " expFloat = 1.39E-43f;\n", - " System.out.println(expFloat);\n", - " double expDouble = 47e47d; // 'd' 是可选的\n", - " double expDouble2 = 47e47; // 自动转换为 double\n", - " System.out.println(expDouble);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1.39E-43\n", - "4.7E48" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在科学与工程学领域,**e** 代表自然对数的基数,约等于 2.718 (Java 里用一种更精确的 **double** 值 **Math.E** 来表示自然对数)。指数表达式 \"1.39 x e-43\",意味着 “1.39 × 2.718 的 -43 次方”。然而,自 FORTRAN 语言发明后,人们自然而然地觉得e 代表 “10 的几次幂”。这种做法显得颇为古怪,因为 FORTRAN 最初是为科学与工程领域设计的。\n", - "\n", - "理所当然,它的设计者应对这样的混淆概念持谨慎态度 [^2]。但不管怎样,这种特别的表达方法在 C,C++ 以及现在的 Java 中顽固地保留下来了。所以倘若习惯 e 作为自然对数的基数使用,那么在 Java 中看到类似“1.39e-43f”这样的表达式时,请转换你的思维,从程序设计的角度思考它;它真正的含义是 “1.39 × 10 的 -43 次方”。\n", - "\n", - "注意如果编译器能够正确地识别类型,就不必使用后缀字符。对于下述语句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "long n3 = 200;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它并不存在含糊不清的地方,所以 200 后面的 L 大可省去。然而,对于下述语句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "float f4 = 1e-43f; //10 的幂数" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器通常会将指数作为 **double** 类型来处理,所以假若没有这个后缀字符 `f`,编译器就会报错,提示我们应该将 **double** 型转换成 **float** 型。\n", - "\n", - "\n", - "## 位运算符\n", - "\n", - "位运算符允许我们操作一个整型数字中的单个二进制位。位运算符会对两个整数对应的位执行布尔代数,从而产生结果。\n", - "\n", - "位运算源自 C 语言的底层操作。我们经常要直接操纵硬件,频繁设置硬件寄存器内的二进制位。Java 的设计初衷是电视机顶盒嵌入式开发,所以这种底层的操作仍被保留了下来。但是,你可能不会使用太多位运算。\n", - "\n", - "若两个输入位都是 1,则按位“与运算符” `&` 运算后结果是 1,否则结果是 0。若两个输入位里至少有一个是 1,则按位“或运算符” `|` 运算后结果是 1;只有在两个输入位都是 0 的情况下,运算结果才是 0。若两个输入位的某一个是 1,另一个不是 1,那么按位“异或运算符” `^` 运算后结果才是 1。按位“非运算符” `~` 属于一元运算符;它只对一个自变量进行操作(其他所有运算符都是二元运算符)。按位非运算后结果与输入位相反。例如输入 0,则输出 1;输入 1,则输出 0。\n", - "\n", - "位运算符和逻辑运算符都使用了同样的字符,只不过数量不同。位短,所以位运算符只有一个字符。位运算符可与等号 `=` 联合使用以接收结果及赋值:`&=`,`|=` 和 `^=` 都是合法的(由于 `~` 是一元运算符,所以不可与 `=` 联合使用)。\n", - "\n", - "我们将 **Boolean** 类型被视为“单位值”(one-bit value),所以它多少有些独特的地方。我们可以对 boolean 型变量执行与、或、异或运算,但不能执行非运算(大概是为了避免与逻辑“非”混淆)。对于布尔值,位运算符具有与逻辑运算符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的位运算为我们新增了一个“异或”逻辑运算符,它并未包括在逻辑运算符的列表中。在移位表达式中,禁止使用布尔值,原因将在下面解释。\n", - "\n", - "\n", - "## 移位运算符\n", - "\n", - "移位运算符面向的运算对象也是二进制的“位”。它们只能用于处理整数类型(基本类型的一种)。左移位运算符 `<<` 能将其左边的运算对象向左移动右侧指定的位数(在低位补 0)。右移位运算符 `>>` 则相反。右移位运算符有“正”、“负”值:若值为正,则在高位插入 0;若值为负,则在高位插入 1。Java 也添加了一种“不分正负”的右移位运算符(>>>),它使用了“零扩展”(zero extension):无论正负,都在高位插入 0。这一运算符是 C/C++ 没有的。\n", - "\n", - "如果移动 **char**、**byte** 或 **short**,则会在移动发生之前将其提升为 **int**,结果为 **int**。仅使用右值(rvalue)的 5 个低阶位。这可以防止我们移动超过 **int** 范围的位数。若对一个 **long** 值进行处理,最后得到的结果也是 **long**。\n", - "\n", - "移位可以与等号 `<<=` 或 `>>=` 或 `>>>=` 组合使用。左值被替换为其移位运算后的值。但是,问题来了,当无符号右移与赋值相结合时,若将其与 **byte** 或 **short** 一起使用的话,则结果错误。取而代之的是,它们被提升为 **int** 型并右移,但在重新赋值时被截断。在这种情况下,结果为 -1。下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/URShift.java\n", - "// 测试无符号右移\n", - "public class URShift {\n", - " public static void main(String[] args) {\n", - " int i = -1;\n", - " System.out.println(Integer.toBinaryString(i));\n", - " i >>>= 10;\n", - " System.out.println(Integer.toBinaryString(i));\n", - " long l = -1;\n", - " System.out.println(Long.toBinaryString(l));\n", - " l >>>= 10;\n", - " System.out.println(Long.toBinaryString(l));\n", - " short s = -1;\n", - " System.out.println(Integer.toBinaryString(s));\n", - " s >>>= 10;\n", - " System.out.println(Integer.toBinaryString(s));\n", - " byte b = -1;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " b >>>= 10;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " b = -1;\n", - " System.out.println(Integer.toBinaryString(b));\n", - " System.out.println(Integer.toBinaryString(b>>>10));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "11111111111111111111111111111111\n", - "1111111111111111111111\n", - "1111111111111111111111111111111111111111111111111111111111111111\n", - "111111111111111111111111111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "11111111111111111111111111111111\n", - "1111111111111111111111" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在上例中,结果并未重新赋值给变量 **b** ,而是直接打印出来,因此一切正常。下面是一个涉及所有位运算符的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/BitManipulation.java\n", - "// 使用位运算符\n", - "import java.util.*;\n", - "public class BitManipulation {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " int i = rand.nextInt();\n", - " int j = rand.nextInt();\n", - " printBinaryInt(\"-1\", -1);\n", - " printBinaryInt(\"+1\", +1);\n", - " int maxpos = 2147483647;\n", - " printBinaryInt(\"maxpos\", maxpos);\n", - " int maxneg = -2147483648;\n", - " printBinaryInt(\"maxneg\", maxneg);\n", - " printBinaryInt(\"i\", i);\n", - " printBinaryInt(\"~i\", ~i);\n", - " printBinaryInt(\"-i\", -i);\n", - " printBinaryInt(\"j\", j);\n", - " printBinaryInt(\"i & j\", i & j);\n", - " printBinaryInt(\"i | j\", i | j);\n", - " printBinaryInt(\"i ^ j\", i ^ j);\n", - " printBinaryInt(\"i << 5\", i << 5);\n", - " printBinaryInt(\"i >> 5\", i >> 5);\n", - " printBinaryInt(\"(~i) >> 5\", (~i) >> 5);\n", - " printBinaryInt(\"i >>> 5\", i >>> 5);\n", - " printBinaryInt(\"(~i) >>> 5\", (~i) >>> 5);\n", - " long l = rand.nextLong();\n", - " long m = rand.nextLong();\n", - " printBinaryLong(\"-1L\", -1L);\n", - " printBinaryLong(\"+1L\", +1L);\n", - " long ll = 9223372036854775807L;\n", - " printBinaryLong(\"maxpos\", ll);\n", - " long lln = -9223372036854775808L;\n", - " printBinaryLong(\"maxneg\", lln);\n", - " printBinaryLong(\"l\", l);\n", - " printBinaryLong(\"~l\", ~l);\n", - " printBinaryLong(\"-l\", -l);\n", - " printBinaryLong(\"m\", m);\n", - " printBinaryLong(\"l & m\", l & m);\n", - " printBinaryLong(\"l | m\", l | m);\n", - " printBinaryLong(\"l ^ m\", l ^ m);\n", - " printBinaryLong(\"l << 5\", l << 5);\n", - " printBinaryLong(\"l >> 5\", l >> 5);\n", - " printBinaryLong(\"(~l) >> 5\", (~l) >> 5);\n", - " printBinaryLong(\"l >>> 5\", l >>> 5);\n", - " printBinaryLong(\"(~l) >>> 5\", (~l) >>> 5);\n", - " }\n", - "\n", - " static void printBinaryInt(String s, int i) {\n", - " System.out.println(\n", - " s + \", int: \" + i + \", binary:\\n \" +\n", - " Integer.toBinaryString(i));\n", - " }\n", - "\n", - " static void printBinaryLong(String s, long l) {\n", - " System.out.println(\n", - " s + \", long: \" + l + \", binary:\\n \" +\n", - " Long.toBinaryString(l));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果(前 32 行):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-1, int: -1, binary:\n", - "11111111111111111111111111111111\n", - "+1, int: 1, binary:\n", - "1\n", - "maxpos, int: 2147483647, binary:\n", - "1111111111111111111111111111111\n", - "maxneg, int: -2147483648, binary:\n", - "10000000000000000000000000000000\n", - "i, int: -1172028779, binary:\n", - "10111010001001000100001010010101\n", - "~i, int: 1172028778, binary:\n", - " 1000101110110111011110101101010\n", - "-i, int: 1172028779, binary:\n", - "1000101110110111011110101101011\n", - "j, int: 1717241110, binary:\n", - "1100110010110110000010100010110\n", - "i & j, int: 570425364, binary:\n", - "100010000000000000000000010100\n", - "i | j, int: -25213033, binary:\n", - "11111110011111110100011110010111\n", - "i ^ j, int: -595638397, binary:\n", - "11011100011111110100011110000011\n", - "i << 5, int: 1149784736, binary:\n", - "1000100100010000101001010100000\n", - "i >> 5, int: -36625900, binary:\n", - "11111101110100010010001000010100\n", - "(~i) >> 5, int: 36625899, binary:\n", - "10001011101101110111101011\n", - "i >>> 5, int: 97591828, binary:\n", - "101110100010010001000010100\n", - "(~i) >>> 5, int: 36625899, binary:\n", - "10001011101101110111101011\n", - " ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "结尾的两个方法 `printBinaryInt()` 和 `printBinaryLong()` 分别操作一个 **int** 和 **long** 值,并转换为二进制格式输出,同时附有简要的文字说明。除了演示 **int** 和 **long** 的所有位运算符的效果之外,本示例还显示 **int** 和 **long** 的最小值、最大值、+1 和 -1 值,以便我们了解它们的形式。注意高位代表符号:0 表示正,1 表示负。上面显示了 **int** 部分的输出。以上数字的二进制表示形式是带符号的补码(2's complement)。\n", - "\n", - "\n", - "## 三元运算符\n", - "\n", - "三元运算符,也称为条件运算符。这种运算符比较罕见,因为它有三个运算对象。但它确实属于运算符的一种,因为它最终也会生成一个值。这与本章后一节要讲述的普通 **if-else** 语句是不同的。下面是它的表达式格式:\n", - "\n", - "**布尔表达式 ? 值 1 : 值 2**\n", - "\n", - "若表达式计算为 **true**,则返回结果 **值 1** ;如果表达式的计算为 **false**,则返回结果 **值 2**。\n", - "\n", - "当然,也可以换用普通的 **if-else** 语句(在后面介绍),但三元运算符更加简洁。作为三元运算符的创造者, C 自诩为一门简练的语言。三元运算符的引入多半就是为了高效编程,但假若我们打算频繁使用它的话,还是先多作一些思量: 它易于产生可读性差的代码。与 **if-else** 不同的是,三元运算符是有返回结果的。请看下面的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/TernaryIfElse.java\n", - "public class TernaryIfElse {\n", - " \n", - "static int ternary(int i) {\n", - " return i < 10 ? i * 100 : i * 10;\n", - "}\n", - "\n", - "static int standardIfElse(int i) {\n", - " if(i < 10)\n", - " return i * 100;\n", - " else\n", - " return i * 10;\n", - "}\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(ternary(9));\n", - " System.out.println(ternary(10));\n", - " System.out.println(standardIfElse(9));\n", - " System.out.println(standardIfElse(10));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "900\n", - "100\n", - "900\n", - "100" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看出,`ternary()` 中的代码更简短。然而,**standardIfElse()** 中的代码更易理解且不要求更多的录入。所以我们在挑选三元运算符时,请务必权衡一下利弊。\n", - "\n", - "\n", - "## 字符串运算符\n", - "\n", - "这个运算符在 Java 里有一项特殊用途:连接字符串。这点已在前面展示过了。尽管与 `+` 的传统意义不符,但如此使用也还是比较自然的。这一功能看起来还不错,于是在 C++ 里引入了“运算符重载”机制,以便 C++ 程序员为几乎所有运算符增加特殊的含义。但遗憾得是,与 C++ 的一些限制结合以后,它变得复杂。这要求程序员在设计自己的类时必须对此有周全的考虑。虽然在 Java 中实现运算符重载机制并非难事(如 C# 所展示的,它具有简单的运算符重载),但因该特性过于复杂,因此 Java 并未实现它。\n", - "\n", - "我们注意到运用 `String +` 时有一些有趣的现象。若表达式以一个 **String** 类型开头(编译器会自动将双引号 `\"\"` 标注的的字符序列转换为字符串),那么后续所有运算对象都必须是字符串。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/StringOperators.java\n", - "public class StringOperators {\n", - " public static void main(String[] args) {\n", - " int x = 0, y = 1, z = 2;\n", - " String s = \"x, y, z \";\n", - " System.out.println(s + x + y + z);\n", - " // 将 x 转换为字符串\n", - " System.out.println(x + \" \" + s);\n", - " s += \"(summed) = \"; \n", - " // 级联操作\n", - " System.out.println(s + (x + y + z));\n", - " // Integer.toString()方法的简写:\n", - " System.out.println(\"\" + x);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x, y, z 012\n", - "0 x, y, z\n", - "x, y, z (summed) = 3\n", - "0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意**:上例中第 1 输出语句的执行结果是 `012` 而并非 `3`,这是因为编译器将其分别转换为其字符串形式然后与字符串变量 **s** 连接。在第 2 条输出语句中,编译器将开头的变量转换为了字符串,由此可以看出,这种转换与数据的位置无关,只要当中有一条数据是字符串类型,其他非字符串数据都将被转换为字符串形式并连接。最后一条输出语句,我们可以看出 `+=` 运算符可以拼接其右侧的字符串连接结果并重赋值给自身变量 `s`。括号 `()` 可以控制表达式的计算顺序,以便在显示 **int** 之前对其进行实际求和。\n", - "\n", - "请注意主方法中的最后一个例子:我们经常会看到一个空字符串 `\"\"` 跟着一个基本类型的数据。这样可以隐式地将其转换为字符串,以代替繁琐的显式调用方法(如这里可以使用 **Integer.toString()**)。\n", - "\n", - "\n", - "## 常见陷阱\n", - "\n", - "使用运算符时很容易犯的一个错误是,在还没搞清楚表达式的计算方式时就试图忽略括号 `()`。在 Java 中也一样。 在 C++ 中你甚至可能犯这样极端的错误.代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "while(x = y) {\n", - "// ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "显然,程序员原意是测试等价性 `==`,而非赋值 `=`。若变量 **y** 非 0 的话,在 C/C++ 中,这样的赋值操作总会返回 `true`。于是,上面的代码示例将会无限循环。而在 Java 中,这样的表达式结果并不会转化为一个布尔值。 而编译器会试图把这个 **int** 型数据转换为预期应接收的布尔类型。最后,我们将会在试图运行前收到编译期错误。因此,Java 天生避免了这种陷阱发生的可能。\n", - "\n", - "唯一有种情况例外:当变量 `x` 和 `y` 都是布尔值,例如 `x=y` 是一个逻辑表达式。除此之外,之前的那个例子,很大可能是错误。\n", - "\n", - "在 C/C++ 里,类似的一个问题还有使用按位“与” `&` 和“或” `|` 运算,而非逻辑“与” `&&` 和“或” `||`。就象 `=` 和 `==` 一样,键入一个字符当然要比键入两个简单。在 Java 中,编译器同样可防止这一点,因为它不允许我们强行使用另一种并不符的类型。\n", - "\n", - "\n", - "## 类型转换\n", - "\n", - "“类型转换”(Casting)的作用是“与一个模型匹配”。在适当的时候,Java 会将一种数据类型自动转换成另一种。例如,假设我们为 **float** 变量赋值一个整数值,计算机会将 **int** 自动转换成 **float**。我们可以在程序未自动转换时显式、强制地使此类型发生转换。\n", - "\n", - "要执行强制转换,需要将所需的数据类型放在任何值左侧的括号内,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Casting.java\n", - "public class Casting {\n", - " public static void main(String[] args) {\n", - " int i = 200;\n", - " long lng = (long)i;\n", - " lng = i; // 没有必要的类型提升\n", - " long lng2 = (long)200;\n", - " lng2 = 200;\n", - " // 类型收缩\n", - " i = (int)lng2; // Cast required\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "诚然,你可以这样地去转换一个数值类型的变量。但是上例这种做法是多余的:因为编译器会在必要时自动提升 **int** 型数据为 **long** 型。\n", - "\n", - "当然,为了程序逻辑清晰或提醒自己留意,我们也可以显式地类型转换。在其他情况下,类型转换型只有在代码编译时才显出其重要性。在 C/C++ 中,类型转换有时会让人头痛。在 Java 里,类型转换则是一种比较安全的操作。但是,若将数据类型进行“向下转换”(**Narrowing Conversion**)的操作(将容量较大的数据类型转换成容量较小的类型),可能会发生信息丢失的危险。此时,编译器会强迫我们进行转型,好比在提醒我们:该操作可能危险,若你坚持让我这么做,那么对不起,请明确需要转换的类型。 对于“向上转换”(**Widening conversion**),则不必进行显式的类型转换,因为较大类型的数据肯定能容纳较小类型的数据,不会造成任何信息的丢失。\n", - "\n", - "除了布尔类型的数据,Java 允许任何基本类型的数据转换为另一种基本类型的数据。此外,类是不能进行类型转换的。为了将一个类转换为另一个类型,需要使用特殊的方法(后面将会学习到如何在父子类之间进行向上/向下转型,例如,“橡树”可以转换为“树”,反之亦然。而对于“岩石”是无法转换为“树”的)。\n", - "\n", - "\n", - "### 截断和舍入\n", - "\n", - "在执行“向下转换”时,必须注意数据的截断和舍入问题。若从浮点值转换为整型值,Java 会做什么呢?例如:浮点数 29.7 被转换为整型值,结果会是 29 还是 30 呢?下面是代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/CastingNumbers.java\n", - "// 尝试转换 float 和 double 型数据为整型数据\n", - "public class CastingNumbers {\n", - " public static void main(String[] args) {\n", - " double above = 0.7, below = 0.4;\n", - " float fabove = 0.7f, fbelow = 0.4f;\n", - " System.out.println(\"(int)above: \" + (int)above);\n", - " System.out.println(\"(int)below: \" + (int)below);\n", - " System.out.println(\"(int)fabove: \" + (int)fabove);\n", - " System.out.println(\"(int)fbelow: \" + (int)fbelow);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "(int)above: 0\n", - "(int)below: 0\n", - "(int)fabove: 0\n", - "(int)fbelow: 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,答案是,从 **float** 和 **double** 转换为整数值时,小数位将被截断。若你想对结果进行四舍五入,可以使用 `java.lang.Math` 的 ` round()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/RoundingNumbers.java\n", - "// float 和 double 类型数据的四舍五入\n", - "public class RoundingNumbers {\n", - " public static void main(String[] args) {\n", - " double above = 0.7, below = 0.4;\n", - " float fabove = 0.7f, fbelow = 0.4f;\n", - " System.out.println(\n", - " \"Math.round(above): \" + Math.round(above));\n", - " System.out.println(\n", - " \"Math.round(below): \" + Math.round(below));\n", - " System.out.println(\n", - " \"Math.round(fabove): \" + Math.round(fabove));\n", - " System.out.println(\n", - " \"Math.round(fbelow): \" + Math.round(fbelow));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Math.round(above): 1\n", - "Math.round(below): 0\n", - "Math.round(fabove): 1\n", - "Math.round(fbelow): 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 `round()` 方法是 `java.lang` 的一部分,所以我们无需通过 `import` 就可以使用。\n", - "\n", - "\n", - "### 类型提升\n", - "\n", - "你会发现,如果我们对小于 **int** 的基本数据类型(即 **char**、**byte** 或 **short**)执行任何算术或按位操作,这些值会在执行操作之前类型提升为 **int**,并且结果值的类型为 **int**。若想重新使用较小的类型,必须使用强制转换(由于重新分配回一个较小的类型,结果可能会丢失精度)。通常,表达式中最大的数据类型是决定表达式结果的数据类型。**float** 型和 **double** 型相乘,结果是 **double** 型的;**int** 和 **long** 相加,结果是 **long** 型。\n", - "\n", - "\n", - "## Java没有sizeof\n", - "\n", - "在 C/C++ 中,经常需要用到 `sizeof()` 方法来获取数据项被分配的字节大小。C/C++ 中使用 `sizeof()` 最有说服力的原因是为了移植性,不同数据在不同机器上可能有不同的大小,所以在进行大小敏感的运算时,程序员必须对这些类型有多大做到心中有数。例如,一台计算机可用 32 位来保存整数,而另一台只用 16 位保存。显然,在第一台机器中,程序可保存更大的值。所以,移植是令 C/C++ 程序员颇为头痛的一个问题。\n", - "\n", - "Java 不需要 ` sizeof()` 方法来满足这种需求,因为所有类型的大小在不同平台上是相同的。我们不必考虑这个层次的移植问题 —— Java 本身就是一种“与平台无关”的语言。\n", - "\n", - "\n", - "## 运算符总结\n", - "\n", - "上述示例分别向我们展示了哪些基本类型能被用于特定的运算符。基本上,下面的代码示例是对上述所有示例的重复,只不过概括了所有的基本类型。这个文件能被正确地编译,因为我已经把编译不通过的那部分用注释 `//` 过滤了。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/AllOps.java\n", - "// 测试所有基本类型的运算符操作\n", - "// 看看哪些是能被 Java 编译器接受的\n", - "public class AllOps {\n", - " // 布尔值的接收测试:\n", - " void f(boolean b) {}\n", - " void boolTest(boolean x, boolean y) {\n", - " // 算数运算符:\n", - " //- x = x * y;\n", - " //- x = x / y;\n", - " //- x = x % y;\n", - " //- x = x + y;\n", - " //- x = x - y;\n", - " //- x++;\n", - " //- x--;\n", - " //- x = +y;\n", - " //- x = -y;\n", - " // 关系运算符和逻辑运算符:\n", - " //- f(x > y);\n", - " //- f(x >= y);\n", - " //- f(x < y);\n", - " //- f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " f(!y);\n", - " x = x && y;\n", - " x = x || y;\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " //- x += y;\n", - " //- x -= y;\n", - " //- x *= y;\n", - " //- x /= y;\n", - " //- x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- char c = (char)x;\n", - " //- byte b = (byte)x;\n", - " //- short s = (short)x;\n", - " //- int i = (int)x;\n", - " //- long l = (long)x;\n", - " //- float f = (float)x;\n", - " //- double d = (double)x;\n", - " }\n", - "\n", - " void charTest(char x, char y) {\n", - " // 算数运算符:\n", - " x = (char)(x * y);\n", - " x = (char)(x / y);\n", - " x = (char)(x % y);\n", - " x = (char)(x + y);\n", - " x = (char)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (char) + y;\n", - " x = (char) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x= (char)~y;\n", - " x = (char)(x & y);\n", - " x = (char)(x | y);\n", - " x = (char)(x ^ y);\n", - " x = (char)(x << 1);\n", - " x = (char)(x >> 1);\n", - " x = (char)(x >>> 1);\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换\n", - " //- boolean bl = (boolean)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void byteTest(byte x, byte y) {\n", - " // 算数运算符:\n", - " x = (byte)(x* y);\n", - " x = (byte)(x / y);\n", - " x = (byte)(x % y);\n", - " x = (byte)(x + y);\n", - " x = (byte)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (byte) + y;\n", - " x = (byte) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " //按位运算符:\n", - " x = (byte)~y;\n", - " x = (byte)(x & y);\n", - " x = (byte)(x | y);\n", - " x = (byte)(x ^ y);\n", - " x = (byte)(x << 1);\n", - " x = (byte)(x >> 1);\n", - " x = (byte)(x >>> 1);\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void shortTest(short x, short y) {\n", - " // 算术运算符:\n", - " x = (short)(x * y);\n", - " x = (short)(x / y);\n", - " x = (short)(x % y);\n", - " x = (short)(x + y);\n", - " x = (short)(x - y);\n", - " x++;\n", - " x--;\n", - " x = (short) + y;\n", - " x = (short) - y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = (short) ~ y;\n", - " x = (short)(x & y);\n", - " x = (short)(x | y);\n", - " x = (short)(x ^ y);\n", - " x = (short)(x << 1);\n", - " x = (short)(x >> 1);\n", - " x = (short)(x >>> 1);\n", - " // Compound assignment:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void intTest(int x, int y) {\n", - " // 算术运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " x = x << 1;\n", - " x = x >> 1;\n", - " x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void longTest(long x, long y) {\n", - " // 算数运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " x = ~y;\n", - " x = x & y;\n", - " x = x | y;\n", - " x = x ^ y;\n", - " x = x << 1;\n", - " x = x >> 1;\n", - " x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " x <<= 1;\n", - " x >>= 1;\n", - " x >>>= 1;\n", - " x &= y;\n", - " x ^= y;\n", - " x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " float f = (float)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void floatTest(float x, float y) {\n", - " // 算数运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " //- x = x & y;\n", - " //- x = x | y;\n", - " //- x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " //- x &= y;\n", - " //- x ^= y;\n", - " //- x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " double d = (double)x;\n", - " }\n", - "\n", - " void doubleTest(double x, double y) {\n", - " // 算术运算符:\n", - " x = x * y;\n", - " x = x / y;\n", - " x = x % y;\n", - " x = x + y;\n", - " x = x - y;\n", - " x++;\n", - " x--;\n", - " x = +y;\n", - " x = -y;\n", - " // 关系和逻辑运算符:\n", - " f(x > y);\n", - " f(x >= y);\n", - " f(x < y);\n", - " f(x <= y);\n", - " f(x == y);\n", - " f(x != y);\n", - " //- f(!x);\n", - " //- f(x && y);\n", - " //- f(x || y);\n", - " // 按位运算符:\n", - " //- x = ~y;\n", - " //- x = x & y;\n", - " //- x = x | y;\n", - " //- x = x ^ y;\n", - " //- x = x << 1;\n", - " //- x = x >> 1;\n", - " //- x = x >>> 1;\n", - " // 联合赋值:\n", - " x += y;\n", - " x -= y;\n", - " x *= y;\n", - " x /= y;\n", - " x %= y;\n", - " //- x <<= 1;\n", - " //- x >>= 1;\n", - " //- x >>>= 1;\n", - " //- x &= y;\n", - " //- x ^= y;\n", - " //- x |= y;\n", - " // 类型转换:\n", - " //- boolean bl = (boolean)x;\n", - " char c = (char)x;\n", - " byte b = (byte)x;\n", - " short s = (short)x;\n", - " int i = (int)x;\n", - " long l = (long)x;\n", - " float f = (float)x;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意** :**boolean** 类型的运算是受限的。你能为其赋值 `true` 或 `false`,也可测试它的值是否是 `true` 或 `false`。但你不能对其作加减等其他运算。\n", - "\n", - "在 **char**,**byte** 和 **short** 类型中,我们可以看到算术运算符的“类型转换”效果。我们必须要显式强制类型转换才能将结果重新赋值为原始类型。对于 **int** 类型的运算则不用转换,因为默认就是 **int** 型。虽然我们不用再停下来思考这一切是否安全,但是两个大的 int 型整数相乘时,结果有可能超出 **int** 型的范围,这种情况下结果会发生溢出。下面的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// operators/Overflow.java\n", - "// 厉害了!内存溢出\n", - "public class Overflow {\n", - " public static void main(String[] args) {\n", - " int big = Integer.MAX_VALUE;\n", - " System.out.println(\"big = \" + big);\n", - " int bigger = big * 4;\n", - " System.out.println(\"bigger = \" + bigger);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "text" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "big = 2147483647\n", - "bigger = -4" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器没有报错或警告,运行时一切看起来都无异常。诚然,Java 是优秀的,但是还不足够优秀。\n", - "\n", - "对于 **char**,**byte** 或者 **short**,混合赋值并不需要类型转换。即使为它们执行转型操作,也会获得与直接算术运算相同的结果。另外,省略类型转换可以使代码显得更加简练。总之,除 **boolean** 以外,其他任何两种基本类型间都可进行类型转换。当我们进行向下转换类型时,需要注意结果的范围是否溢出,否则我们就很可能在不知不觉中丢失精度。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "如果你已接触过一门 C 语法风格编程语言,那么你在学习 Java 的运算符时实际上没有任何曲线。如果你觉得有难度,那么我推荐你要先去 www.OnJava8.com 观看 《Thinking in C》 的视频教程来补充一些前置知识储备。\n", - "\n", - "[^1]: 我在 *Pomona College* 大学读过两年本科,在那里 47 被称之为“魔法数字”(*magic number*),详见 [维基百科](https://en.wikipedia.org/wiki/47_(number)) 。\n", - "\n", - "[^2]: *John Kirkham* 说过:“自 1960 年我开始在 IBM 1620 上开始编程起,至 1970 年之间,FORTRAN 一直都是一种全大写的编程语言。这可能是因为许多早期的输入设备都是旧的电传打字机,使用了 5 位波特码,没有小写字母的功能。指数符号中的 e 也总是大写的,并且从未与自然对数底数 e 混淆,自然对数底数 e 总是小写的。 e 简单地代表指数,通常 10 是基数。那时,八进制也被程序员广泛使用。虽然我从未见过它的用法,但如果我看到一个指数符号的八进制数,我会认为它是以 8 为基数的。我记得第一次看到指数使用小写字母 e 是在 20 世纪 70 年代末,我也发现它令人困惑。这个问题出现的时候,小写字母悄悄进入了 Fortran。如果你真的想使用自然对数底,我们实际上有一些函数要使用,但是它们都是大写的。”\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Java", - "language": "java", - "name": "java" - }, - "language_info": { - "codemirror_mode": "java", - "file_extension": ".jshell", - "mimetype": "text/x-java-source", - "name": "Java", - "pygments_lexer": "java", - "version": "14.0.1+7" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/05-Control-Flow.ipynb b/jupyter/05-Control-Flow.ipynb deleted file mode 100644 index 7a0f129a..00000000 --- a/jupyter/05-Control-Flow.ipynb +++ /dev/null @@ -1,1436 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "# 第五章 控制流\n", - "\n", - "> 程序必须在执行过程中控制它的世界并做出选择。 在 Java 中,你需要执行控制语句来做出选择。\n", - "\n", - "Java 使用了 C 的所有执行控制语句,因此对于熟悉 C/C++ 编程的人来说,这部分内容轻车熟路。大多数面向过程编程语言都有共通的某种控制语句。在 Java 中,涉及的关键字包括 **if-else,while,do-while,for,return,break** 和选择语句 **switch**。 Java 并不支持备受诟病的 **goto**(尽管它在某些特殊场景中依然是最行之有效的方法)。 尽管如此,在 Java 中我们仍旧可以进行类似的逻辑跳转,但较之典型的 **goto** 用法限制更多。\n", - "\n", - "\n", - "## true和false\n", - "\n", - "所有的条件语句都利用条件表达式的“真”或“假”来决定执行路径。举例:\n", - "`a == b`。它利用了条件表达式 `==` 来比较 `a` 与 `b` 的值是否相等。 该表达式返回 `true` 或 `false`。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/TrueFalse.java\n", - "public class TrueFalse {\n", - "\tpublic static void main(String[] args) {\n", - "\t\tSystem.out.println(1 == 1);\n", - "\t\tSystem.out.println(1 == 2);\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "true false " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过上一章的学习,我们知道任何关系运算符都可以产生条件语句。 **注意**:在 Java 中使用数值作为布尔值是非法的,即便这种操作在 C/C++ 中是被允许的(在这些语言中,“真”为非零,而“假”是零)。如果想在布尔测试中使用一个非布尔值,那么首先需要使用条件表达式来产生 **boolean** 类型的结果,例如 `if(a != 0)`。\n", - "\n", - "## if-else\n", - "\n", - "**if-else** 语句是控制程序执行流程最基本的形式。 其中 `else` 是可选的,因此可以有两种形式的 `if`。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(Boolean-expression) \n", - "\t“statement” " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "或" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(Boolean-expression) \n", - "\t“statement”\n", - "else\n", - " “statement”" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "布尔表达式(Boolean-expression)必须生成 **boolean** 类型的结果,执行语句 `statement` 既可以是以分号 `;` 结尾的一条简单语句,也可以是包含在大括号 `{}` 内的的复合语句 —— 封闭在大括号内的一组简单语句。 凡本书中提及“statement”一词,皆表示类似的执行语句。\n", - "\n", - "下面是一个有关 **if-else** 语句的例子。`test()` 方法可以告知你两个数值之间的大小关系。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/IfElse.java\n", - "public class IfElse {\n", - " static int result = 0;\n", - " static void test(int testval, int target) {\n", - " if(testval > target)\n", - " result = +1;\n", - " else if(testval < target) // [1]\n", - " result = -1;\n", - " else\n", - " result = 0; // Match\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " test(10, 5);\n", - " System.out.println(result);\n", - " test(5, 10);\n", - " System.out.println(result);\n", - " test(5, 5);\n", - " System.out.println(result);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1\n", - "-1\n", - "0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注解**:`else if` 并非新关键字,它仅是 `else` 后紧跟的一条新 `if` 语句。\n", - "\n", - "Java 和 C/C++ 同属“自由格式”的编程语言,但通常我们会在 Java 控制流程语句中采用首部缩进的规范,以便代码更具可读性。\n", - "\n", - "\n", - "## 迭代语句\n", - "\n", - "**while**,**do-while** 和 **for** 用来控制循环语句(有时也称迭代语句)。只有控制循环的布尔表达式计算结果为 `false`,循环语句才会停止。 \n", - "\n", - "\n", - "### while\n", - "\n", - "**while** 循环的形式是:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "while(Boolean-expression) \n", - " statement" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "执行语句会在每一次循环前,判断布尔表达式返回值是否为 `true`。下例可产生随机数,直到满足特定条件。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/WhileTest.java\n", - "// 演示 while 循环\n", - "public class WhileTest {\n", - " static boolean condition() {\n", - " boolean result = Math.random() < 0.99;\n", - " System.out.print(result + \", \");\n", - " return result;\n", - " }\n", - " public static void main(String[] args) {\n", - " while(condition())\n", - " System.out.println(\"Inside 'while'\");\n", - " System.out.println(\"Exited 'while'\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "...________...________...________...________...\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "true, Inside 'while'\n", - "false, Exited 'while'" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`condition()` 方法使用到了 **Math** 库的**静态**方法 `random()`。该方法的作用是产生 0 和 1 之间 (包括 0,但不包括 1) 的一个 **double** 值。\n", - "\n", - "**result** 的值是通过比较运算符 `<` 产生的 **boolean** 类型的结果。当控制台输出 **boolean** 型值时,会自动将其转换为对应的文字形式 `true` 或 `false`。此处 `while` 条件表达式代表:“仅在 `condition()` 返回 `false` 时停止循环”。\n", - "\n", - "\n", - "### do-while\n", - "\n", - "**do-while** 的格式如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "do \n", - "\tstatement\n", - "while(Boolean-expression);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**while** 和 **do-while** 之间的唯一区别是:即使条件表达式返回结果为 `false`, **do-while** 语句也至少会执行一次。 在 **while** 循环体中,如布尔表达式首次返回的结果就为 `false`,那么循环体内的语句不会被执行。实际应用中,**while** 形式比 **do-while** 更为常用。\n", - "\n", - "\n", - "### for\n", - "\n", - "**for** 循环可能是最常用的迭代形式。 该循环在第一次迭代之前执行初始化。随后,它会执行布尔表达式,并在每次迭代结束时,进行某种形式的步进。**for** 循环的形式是:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "for(initialization; Boolean-expression; step)\n", - " statement" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "初始化 (initialization) 表达式、布尔表达式 (Boolean-expression) ,或者步进 (step) 运算,都可以为空。每次迭代之前都会判断布尔表达式的结果是否成立。一旦计算结果为 `false`,则跳出 **for** 循环体并继续执行后面代码。 每次循环结束时,都会执行一次步进。\n", - "\n", - "**for** 循环通常用于“计数”任务。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/ListCharacters.java\n", - "\n", - "public class ListCharacters {\n", - " public static void main(String[] args) {\n", - " for(char c = 0; c < 128; c++)\n", - " if(Character.isLowerCase(c))\n", - " System.out.println(\"value: \" + (int)c +\n", - " \" character: \" + c);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果(前 10 行):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "value: 97 character: a\n", - "value: 98 character: b\n", - "value: 99 character: c\n", - "value: 100 character: d\n", - "value: 101 character: e\n", - "value: 102 character: f\n", - "value: 103 character: g\n", - "value: 104 character: h\n", - "value: 105 character: i\n", - "value: 106 character: j\n", - " ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意**:变量 **c** 是在 **for** 循环执行时才被定义的,并不是在主方法的开头。**c** 的作用域范围仅在 **for** 循环体内。\n", - "\n", - "传统的面向过程语言如 C 需要先在代码块(block)前定义好所有变量才能够使用。这样编译器才能在创建块时,为这些变量分配内存空间。在 Java 和 C++ 中,我们可以在整个块使用变量声明,并且可以在需要时才定义变量。 这种自然的编码风格使我们的代码更容易被人理解 [^1]。\n", - "\n", - "上例使用了 **java.lang.Character** 包装类,该类不仅包含了基本类型 `char` 的值,还封装了一些有用的方法。例如这里就用到了静态方法 `isLowerCase()` 来判断字符是否为小写。\n", - "\n", - "\n", - "\n", - "#### 逗号操作符\n", - "\n", - "在 Java 中逗号运算符(这里并非指我们平常用于分隔定义和方法参数的逗号分隔符)仅有一种用法:在 **for** 循环的初始化和步进控制中定义多个变量。我们可以使用逗号分隔多个语句,并按顺序计算这些语句。**注意**:要求定义的变量类型相同。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/CommaOperator.java\n", - "\n", - "public class CommaOperator {\n", - " public static void main(String[] args) {\n", - " for(int i = 1, j = i + 10; i < 5; i++, j = i * 2) {\n", - " System.out.println(\"i = \" + i + \" j = \" + j);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 1 j = 11\n", - "i = 2 j = 4\n", - "i = 3 j = 6\n", - "i = 4 j = 8" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中 **int** 类型声明包含了 `i` 和 `j`。实际上,在初始化部分我们可以定义任意数量的同类型变量。**注意**:在 Java 中,仅允许 **for** 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。\n", - "\n", - "## for-in 语法 \n", - "\n", - "Java 5 引入了更为简洁的“增强版 **for** 循环”语法来操纵数组和集合。(更多细节,可参考 [数组](./21-Arrays.md) 和 [集合](./12-Collections.md) 章节内容)。大部分文档也称其为 **for-each** 语法,但因为了不与 Java 8 新添的 `forEach()` 产生混淆,因此我称之为 **for-in** 循环。 (Python 已有类似的先例,如:**for x in sequence**)。**注意**:你可能会在其他地方看到不同叫法。\n", - "\n", - "**for-in** 无需你去创建 **int** 变量和步进来控制循环计数。 下面我们来遍历获取 **float** 数组中的元素。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/ForInFloat.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class ForInFloat {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " float[] f = new float[10];\n", - " for(int i = 0; i < 10; i++)\n", - " f[i] = rand.nextFloat();\n", - " for(float x : f)\n", - " System.out.println(x);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0.72711575\n", - "0.39982635\n", - "0.5309454\n", - "0.0534122\n", - "0.16020656\n", - "0.57799757\n", - "0.18847865\n", - "0.4170137\n", - "0.51660204\n", - "0.73734957" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中我们展示了传统 **for** 循环的用法。接下来再来看下 **for-in** 的用法。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "for(float x : f) {" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这条语句定义了一个 **float** 类型的变量 `x`,继而将每一个 `f` 的元素赋值给它。\n", - "\n", - "任何一个返回数组的方法都可以使用 **for-in** 循环语法来遍历元素。例如 **String** 类有一个方法 `toCharArray()`,返回值类型为 **char** 数组,我们可以很容易地在 **for-in** 循环中遍历它。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/ForInString.java\n", - "\n", - "public class ForInString {\n", - " public static void main(String[] args) {\n", - " for(char c: \"An African Swallow\".toCharArray())\n", - " System.out.print(c + \" \");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "A n A f r i c a n S w a l l o w" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "很快我们能在 [集合](./12-Collections.md) 章节里学习到,**for-in** 循环适用于任何可迭代(*iterable*)的 对象。\n", - "\n", - "通常,**for** 循环语句都会在一个整型数值序列中步进。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "for(int i = 0; i < 100; i++)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正因如此,除非先创建一个 **int** 数组,否则我们无法使用 **for-in** 循环来操作。为简化测试过程,我已在 `onjava` 包中封装了 **Range** 类,利用其 `range()` 方法可自动生成恰当的数组。\n", - "\n", - "在 [封装](./07-Implementation-Hiding.md)(Implementation Hiding)这一章里我们介绍了静态导入(static import),无需了解细节就可以直接使用。 有关静态导入的语法,可以在 **import** 语句中看到:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/ForInInt.java\n", - "\n", - "import static onjava.Range.*;\n", - "\n", - "public class ForInInt {\n", - " public static void main(String[] args) {\n", - " for(int i : range(10)) // 0..9\n", - " System.out.print(i + \" \");\n", - " System.out.println();\n", - " for(int i : range(5, 10)) // 5..9\n", - " System.out.print(i + \" \");\n", - " System.out.println();\n", - " for(int i : range(5, 20, 3)) // 5..20 step 3\n", - " System.out.print(i + \" \");\n", - " System.out.println();\n", - " for(int i : range(20, 5, -3)) // Count down\n", - " System.out.print(i + \" \");\n", - " System.out.println();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0 1 2 3 4 5 6 7 8 9\n", - "5 6 7 8 9\n", - "5 8 11 14 17\n", - "20 17 14 11 8" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`range()` 方法已被 [重载](./06-Housekeeping.md#方法重载)(重载:同名方法,参数列表或类型不同)。上例中 `range()` 方法有多种重载形式:第一种产生从 0 至范围上限(不包含)的值;第二种产生参数一至参数二(不包含)范围内的整数值;第三种形式有一个步进值,因此它每次的增量为该值;第四种 `range()` 表明还可以递减。`range()` 无参方法是该生成器最简单的版本。有关内容会在本书稍后介绍。\n", - "\n", - "`range()` 的使用提高了代码可读性,让 **for-in** 循环在本书中适应更多的代码示例场景。\n", - "\n", - "请注意,`System.out.print()` 不会输出换行符,所以我们可以分段输出同一行。\n", - "\n", - "*for-in* 语法可以节省我们编写代码的时间。 更重要的是,它提高了代码可读性以及更好地描述代码意图(获取数组的每个元素)而不是详细说明这操作细节(创建索引,并用它来选择数组元素) 本书推荐使用 *for-in* 语法。\n", - "\n", - "## return\n", - "\n", - "在 Java 中有几个关键字代表无条件分支,这意味无需任何测试即可发生。这些关键字包括 **return**,**break**,**continue** 和跳转到带标签语句的方法,类似于其他语言中的 **goto**。\n", - "\n", - "**return** 关键字有两方面的作用:1.指定一个方法返回值 (在方法返回类型非 **void** 的情况下);2.退出当前方法,并返回作用 1 中值。我们可以利用 `return` 的这些特点来改写上例 `IfElse.java` 文件中的 `test()` 方法。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/TestWithReturn.java\n", - "\n", - "public class TestWithReturn {\n", - " static int test(int testval, int target) {\n", - " if(testval > target)\n", - " return +1;\n", - " if(testval < target)\n", - " return -1;\n", - " return 0; // Match\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(test(10, 5));\n", - " System.out.println(test(5, 10));\n", - " System.out.println(test(5, 5));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1\n", - "-1\n", - "0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里不需要 `else`,因为该方法执行到 `return` 就结束了。\n", - "\n", - "如果在方法签名中定义了返回值类型为 **void**,那么在代码执行结束时会有一个隐式的 **return**。 也就是说我们不用在总是在方法中显式地包含 **return** 语句。 **注意**:如果你的方法声明的返回值类型为非 **void** 类型,那么则必须确保每个代码路径都返回一个值。\n", - "\n", - "## break 和 continue\n", - "\n", - "在任何迭代语句的主体内,都可以使用 **break** 和 **continue** 来控制循环的流程。 其中,**break** 表示跳出当前循环体。而 **continue** 表示停止本次循环,开始下一次循环。\n", - "\n", - "下例向大家展示 **break** 和 **continue** 在 **for**、**while** 循环中的使用。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/BreakAndContinue.java\n", - "// Break 和 continue 关键字\n", - "\n", - "import static onjava.Range.*;\n", - "\n", - "public class BreakAndContinue {\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 100; i++) { // [1]\n", - " if(i == 74) break; // 跳出循环\n", - " if(i % 9 != 0) continue; // 下一次循环\n", - " System.out.print(i + \" \");\n", - " }\n", - " System.out.println();\n", - " // 使用 for-in 循环:\n", - " for(int i : range(100)) { // [2]\n", - " if(i == 74) break; // 跳出循环\n", - " if(i % 9 != 0) continue; // 下一次循环\n", - " System.out.print(i + \" \");\n", - " }\n", - " System.out.println();\n", - " int i = 0;\n", - " // \"无限循环\":\n", - " while(true) { // [3]\n", - " i++;\n", - " int j = i * 27;\n", - " if(j == 1269) break; // 跳出循环\n", - " if(i % 10 != 0) continue; // 循环顶部\n", - " System.out.print(i + \" \");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0 9 18 27 36 45 54 63 72\n", - "0 9 18 27 36 45 54 63 72\n", - "10 20 30 40" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**[1]** 在这个 **for** 循环中,`i` 的值永远不会达到 100,因为一旦 `i` 等于 74,**break** 语句就会中断循环。通常,只有在不知道中断条件何时满足时,才需要 **break**。因为 `i` 不能被 9 整除,**continue** 语句就会使循环从头开始。这使 **i** 递增)。如果能够整除,则将值显示出来。\n", - " **[2]** 使用 **for-in** 语法,结果相同。\n", - " **[3]** 无限 **while** 循环。循环内的 **break** 语句可中止循环。**注意**,**continue** 语句可将控制权移回循环的顶部,而不会执行 **continue** 之后的任何操作。 因此,只有当 `i` 的值可被 10 整除时才会输出。在输出中,显示值 0,因为 `0%9` 产生 0。还有一种无限循环的形式: `for(;;)`。 在编译器看来,它与 `while(true)` 无异,使用哪种完全取决于你的编程品味。\n", - "\n", - "\n", - "## 臭名昭著的 goto\n", - "\n", - "[**goto** 关键字](https://en.wikipedia.org/wiki/Goto) 很早就在程序设计语言中出现。事实上,**goto** 起源于[汇编](https://en.wikipedia.org/wiki/Assembly_language)(assembly language)语言中的程序控制:“若条件 A 成立,则跳到这里;否则跳到那里”。如果你读过由编译器编译后的代码,你会发现在其程序控制中充斥了大量的跳转。较之汇编产生的代码直接运行在硬件 CPU 中,Java 也会产生自己的“汇编代码”(字节码),只不过它是运行在 Java 虚拟机里的(Java Virtual Machine)。\n", - "\n", - "一个源码级别跳转的 **goto**,为何招致名誉扫地呢?若程序总是从一处跳转到另一处,还有什么办法能识别代码的控制流程呢?随着 *Edsger Dijkstra*发表著名的 “Goto 有害” 论(*Goto considered harmful*)以后,**goto** 便从此失宠。甚至有人建议将它从关键字中剔除。\n", - "\n", - "正如上述提及的经典情况,我们不应走向两个极端。问题不在 **goto**,而在于过度使用 **goto**。在极少数情况下,**goto** 实际上是控制流程的最佳方式。\n", - "\n", - "尽管 **goto** 仍是 Java 的一个保留字,但其并未被正式启用。可以说, Java 中并不支持 **goto**。然而,在 **break** 和 **continue** 这两个关键字的身上,我们仍能看出一些 **goto** 的影子。它们并不属于一次跳转,而是中断循环语句的一种方法。之所以把它们纳入 **goto** 问题中一起讨论,是由于它们使用了相同的机制:标签。\n", - "\n", - "“标签”是后面跟一个冒号的标识符。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "label1:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对 Java 来说,唯一用到标签的地方是在循环语句之前。进一步说,它实际需要紧靠在循环语句的前方 —— 在标签和循环之间置入任何语句都是不明智的。而在循环之前设置标签的唯一理由是:我们希望在其中嵌套另一个循环或者一个开关。这是由于 **break** 和 **continue** 关键字通常只中断当前循环,但若搭配标签一起使用,它们就会中断并跳转到标签所在的地方开始执行。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "label1:\n", - "outer-iteration { \n", - " inner-iteration {\n", - " // ...\n", - " break; // [1] \n", - " // ...\n", - " continue; // [2] \n", - " // ...\n", - " continue label1; // [3] \n", - " // ...\n", - " break label1; // [4] \n", - " } \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**[1]** **break** 中断内部循环,并在外部循环结束。\n", - "**[2]** **continue** 移回内部循环的起始处。但在条件 3 中,**continue label1** 却同时中断内部循环以及外部循环,并移至 **label1** 处。\n", - "**[3]** 随后,它实际是继续循环,但却从外部循环开始。\n", - "**[4]** **break label1** 也会中断所有循环,并回到 **label1** 处,但并不重新进入循环。也就是说,它实际是完全中止了两个循环。\n", - "\n", - "下面是 **for** 循环的一个例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/LabeledFor.java\n", - "// 搭配“标签 break”的 for 循环中使用 break 和 continue\n", - "\n", - "public class LabeledFor {\n", - " public static void main(String[] args) {\n", - " int i = 0;\n", - " outer: // 此处不允许存在执行语句\n", - " for(; true ;) { // 无限循环\n", - " inner: // 此处不允许存在执行语句\n", - " for(; i < 10; i++) {\n", - " System.out.println(\"i = \" + i);\n", - " if(i == 2) {\n", - " System.out.println(\"continue\");\n", - " continue;\n", - " }\n", - " if(i == 3) {\n", - " System.out.println(\"break\");\n", - " i++; // 否则 i 永远无法获得自增 \n", - " // 获得自增 \n", - " break;\n", - " }\n", - " if(i == 7) {\n", - " System.out.println(\"continue outer\");\n", - " i++; // 否则 i 永远无法获得自增 \n", - " // 获得自增 \n", - " continue outer;\n", - " }\n", - " if(i == 8) {\n", - " System.out.println(\"break outer\");\n", - " break outer;\n", - " }\n", - " for(int k = 0; k < 5; k++) {\n", - " if(k == 3) {\n", - " System.out.println(\"continue inner\");\n", - " continue inner;\n", - " }\n", - " }\n", - " }\n", - " }\n", - " // 在此处无法 break 或 continue 标签\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 0\n", - "continue inner\n", - "i = 1\n", - "continue inner\n", - "i = 2\n", - "continue\n", - "i = 3\n", - "break\n", - "i = 4\n", - "continue inner\n", - "i = 5\n", - "continue inner\n", - "i = 6\n", - "continue inner\n", - "i = 7\n", - "continue outer\n", - "i = 8\n", - "break outer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意 **break** 会中断 **for** 循环,而且在抵达 **for** 循环的末尾之前,递增表达式不会执行。由于 **break** 跳过了递增表达式,所以递增会在 `i==3` 的情况下直接执行。在 `i==7` 的情况下,`continue outer` 语句也会到达循环顶部,而且也会跳过递增,所以它也是直接递增的。\n", - "\n", - "如果没有 **break outer** 语句,就没有办法在一个内部循环里找到出外部循环的路径。这是由于 **break** 本身只能中断最内层的循环(对于 **continue** 同样如此)。 当然,若想在中断循环的同时退出方法,简单地用一个 **return** 即可。\n", - "\n", - "下面这个例子向大家展示了带标签的 **break** 以及 **continue** 语句在 **while** 循环中的用法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/LabeledWhile.java\n", - "// 带标签的 break 和 conitue 在 while 循环中的使用\n", - "\n", - "public class LabeledWhile {\n", - " public static void main(String[] args) {\n", - " int i = 0;\n", - " outer:\n", - " while(true) {\n", - " System.out.println(\"Outer while loop\");\n", - " while(true) {\n", - " i++;\n", - " System.out.println(\"i = \" + i);\n", - " if(i == 1) {\n", - " System.out.println(\"continue\");\n", - " continue;\n", - " }\n", - " if(i == 3) {\n", - " System.out.println(\"continue outer\");\n", - " continue outer;\n", - " }\n", - " if(i == 5) {\n", - " System.out.println(\"break\");\n", - " break;\n", - " }\n", - " if(i == 7) {\n", - " System.out.println(\"break outer\");\n", - " break outer;\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Outer while loop\n", - "i = 1\n", - "continue\n", - "i = 2\n", - "i = 3\n", - "continue outer\n", - "Outer while loop\n", - "i = 4\n", - "i = 5\n", - "break\n", - "Outer while loop\n", - "i = 6\n", - "i = 7\n", - "break outer" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "同样的规则亦适用于 **while**:\n", - "\n", - "1. 简单的一个 **continue** 会退回最内层循环的开头(顶部),并继续执行。\n", - "\n", - "2. 带有标签的 **continue** 会到达标签的位置,并重新进入紧接在那个标签后面的循环。\n", - "\n", - "3. **break** 会中断当前循环,并移离当前标签的末尾。\n", - "\n", - "4. 带标签的 **break** 会中断当前循环,并移离由那个标签指示的循环的末尾。\n", - "\n", - "大家要记住的重点是:在 Java 里需要使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中 **break** 或 **continue**。\n", - "\n", - "**break** 和 **continue** 标签在编码中的使用频率相对较低 (此前的语言中很少使用或没有先例),所以我们很少在代码里看到它们。\n", - "\n", - "在 *Dijkstra* 的 **“Goto 有害”** 论文中,他最反对的就是标签,而非 **goto**。他观察到 BUG 的数量似乎随着程序中标签的数量而增加[^2]。标签和 **goto** 使得程序难以分析。但是,Java 标签不会造成这方面的问题,因为它们的应用场景受到限制,无法用于以临时方式传输控制。由此也引出了一个有趣的情形:对语言能力的限制,反而使它这项特性更加有价值。\n", - "\n", - "\n", - "## switch\n", - "\n", - "**switch** 有时也被划归为一种选择语句。根据整数表达式的值,**switch** 语句可以从一系列代码中选出一段去执行。它的格式如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "switch(integral-selector) {\n", - "\tcase integral-value1 : statement; break;\n", - "\tcase integral-value2 : statement;\tbreak;\n", - "\tcase integral-value3 : statement;\tbreak;\n", - "\tcase integral-value4 : statement;\tbreak;\n", - "\tcase integral-value5 : statement;\tbreak;\n", - "\t// ...\n", - "\tdefault: statement;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中,**integral-selector** (整数选择因子)是一个能够产生整数值的表达式,**switch** 能够将这个表达式的结果与每个 **integral-value** (整数值)相比较。若发现相符的,就执行对应的语句(简单或复合语句,其中并不需要括号)。若没有发现相符的,就执行 **default** 语句。\n", - "\n", - "在上面的定义中,大家会注意到每个 **case** 均以一个 **break** 结尾。这样可使执行流程跳转至 **switch** 主体的末尾。这是构建 **switch** 语句的一种传统方式,但 **break** 是可选的。若省略 **break,** 会继续执行后面的 **case** 语句的代码,直到遇到一个 **break** 为止。通常我们不想出现这种情况,但对有经验的程序员来说,也许能够善加利用。注意最后的 **default** 语句没有 **break**,因为执行流程已到了 **break** 的跳转目的地。当然,如果考虑到编程风格方面的原因,完全可以在 **default** 语句的末尾放置一个 **break**,尽管它并没有任何实际的作用。\n", - "\n", - "**switch** 语句是一种实现多路选择的干净利落的一种方式(比如从一系列执行路径中挑选一个)。但它要求使用一个选择因子,并且必须是 **int** 或 **char** 那样的整数值。例如,假若将一个字串或者浮点数作为选择因子使用,那么它们在 switch 语句里是不会工作的。对于非整数类型(Java 7 以上版本中的 String 型除外),则必须使用一系列 **if** 语句。 在[下一章的结尾](./06-Housekeeping.md#枚举类型) 中,我们将会了解到**枚举类型**被用来搭配 **switch** 工作,并优雅地解决了这种限制。\n", - "\n", - "下面这个例子可随机生成字母,并判断它们是元音还是辅音字母:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/VowelsAndConsonants.java\n", - "\n", - "// switch 执行语句的演示\n", - "import java.util.*;\n", - "\n", - "public class VowelsAndConsonants {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " for(int i = 0; i < 100; i++) {\n", - " int c = rand.nextInt(26) + 'a';\n", - " System.out.print((char)c + \", \" + c + \": \");\n", - " switch(c) {\n", - " case 'a':\n", - " case 'e':\n", - " case 'i':\n", - " case 'o':\n", - " case 'u': System.out.println(\"vowel\");\n", - " break;\n", - " case 'y':\n", - " case 'w': System.out.println(\"Sometimes vowel\");\n", - " break;\n", - " default: System.out.println(\"consonant\");\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "y, 121: Sometimes vowel\n", - "n, 110: consonant\n", - "z, 122: consonant\n", - "b, 98: consonant\n", - "r, 114: consonant\n", - "n, 110: consonant\n", - "y, 121: Sometimes vowel\n", - "g, 103: consonant\n", - "c, 99: consonant\n", - "f, 102: consonant\n", - "o, 111: vowel\n", - "w, 119: Sometimes vowel\n", - "z, 122: consonant\n", - " ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于 `Random.nextInt(26)` 会产生 0 到 25 之间的一个值,所以在其上加上一个偏移量 `a`,即可产生小写字母。在 **case** 语句中,使用单引号引起的字符也会产生用于比较的整数值。\n", - "\n", - "请注意 **case** 语句能够堆叠在一起,为一段代码形成多重匹配,即只要符合多种条件中的一种,就执行那段特别的代码。这时也应该注意将 **break** 语句置于特定 **case** 的末尾,否则控制流程会继续往下执行,处理后面的 **case**。在下面的语句中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int c = rand.nextInt(26) + 'a';" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此处 `Random.nextInt()` 将产生 0~25 之间的一个随机 **int** 值,它将被加到 `a` 上。这表示 `a` 将自动被转换为 **int** 以执行加法。为了把 `c` 当作字符打印,必须将其转型为 **char**;否则,将会输出整数。\n", - "\n", - "\n", - "\n", - "## switch 字符串\n", - "\n", - "Java 7 增加了在字符串上 **switch** 的用法。 下例展示了从一组 **String** 中选择可能值的传统方法,以及新式方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/StringSwitch.java\n", - "\n", - "public class StringSwitch {\n", - " public static void main(String[] args) {\n", - " String color = \"red\";\n", - " // 老的方式: 使用 if-then 判断\n", - " if(\"red\".equals(color)) {\n", - " System.out.println(\"RED\");\n", - " } else if(\"green\".equals(color)) {\n", - " System.out.println(\"GREEN\");\n", - " } else if(\"blue\".equals(color)) {\n", - " System.out.println(\"BLUE\");\n", - " } else if(\"yellow\".equals(color)) {\n", - " System.out.println(\"YELLOW\");\n", - " } else {\n", - " System.out.println(\"Unknown\");\n", - " }\n", - " // 新的方法: 字符串搭配 switch\n", - " switch(color) {\n", - " case \"red\":\n", - " System.out.println(\"RED\");\n", - " break;\n", - " case \"green\":\n", - " System.out.println(\"GREEN\");\n", - " break;\n", - " case \"blue\":\n", - " System.out.println(\"BLUE\");\n", - " break;\n", - " case \"yellow\":\n", - " System.out.println(\"YELLOW\");\n", - " break;\n", - " default:\n", - " System.out.println(\"Unknown\");\n", - " break;\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "RED\n", - "RED" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦理解了 **switch**,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。\n", - "\n", - "作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、 [0,1)、(0,1] 、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”)\n", - "\n", - "下面是一个可能提供答案的测试程序。 所有命令行参数都作为 **String** 对象传递,因此我们可以 **switch** 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 `args` 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用**空字符串** `\"\"` 替代;否则,选择 `args` 数组中的第一个元素:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// control/RandomBounds.java\n", - "\n", - "// Math.random() 会产生 0.0 和 1.0 吗?\n", - "// {java RandomBounds lower}\n", - "import onjava.*;\n", - "\n", - "public class RandomBounds {\n", - " public static void main(String[] args) {\n", - " new TimedAbort(3);\n", - " switch(args.length == 0 ? \"\" : args[0]) {\n", - " case \"lower\":\n", - " while(Math.random() != 0.0)\n", - " ; // 保持重试\n", - " System.out.println(\"Produced 0.0!\");\n", - " break;\n", - " case \"upper\":\n", - " while(Math.random() != 1.0)\n", - " ; // 保持重试\n", - " System.out.println(\"Produced 1.0!\");\n", - " break;\n", - " default:\n", - " System.out.println(\"Usage:\");\n", - " System.out.println(\"\\tRandomBounds lower\");\n", - " System.out.println(\"\\tRandomBounds upper\");\n", - " System.exit(1);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要运行该程序,请键入以下任一命令:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "java RandomBounds lower \n", - "// 或者\n", - "java RandomBounds upper" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 `onjava` 包中的 **TimedAbort** 类可使程序在三秒后中止。从结果来看,似乎 `Math.random()` 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 **double** 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:`Math.random()` 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1)来表示。由此可知,我们必须小心分析实验并了解它们的局限性。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "本章总结了我们对大多数编程语言中出现的基本特性的探索:计算,运算符优先级,类型转换,选择和迭代。 现在让我们准备好,开始步入面向对象和函数式编程的世界吧。 下一章的内容涵盖了 Java 编程中的重要问题:对象的[初始化和清理](./06-Housekeeping.md)。紧接着,还会介绍[封装](./07-Implementation-Hiding.md)(implementation hiding)的核心概念。\n", - "\n", - "\n", - "[^1]: 在早期的语言中,许多决策都是基于让编译器设计者的体验更好。 但在现代语言设计中,许多决策都是为了提高语言使用者的体验,尽管有时会有妥协 —— 这通常会让语言设计者后悔。\n", - "\n", - "[^2]: **注意**,此处观点似乎难以让人信服,很可能只是一个因认知偏差而造成的[因果关系谬误](https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation) 的例子。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/06-Housekeeping.ipynb b/jupyter/06-Housekeeping.ipynb deleted file mode 100644 index c64222a9..00000000 --- a/jupyter/06-Housekeeping.ipynb +++ /dev/null @@ -1,3097 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第六章 初始化和清理\n", - "\n", - "\"不安全\"的编程是造成编程代价昂贵的罪魁祸首之一。有两个安全性问题:初始化和清理。C 语言中很多的 bug 都是因为程序员忘记初始化导致的。尤其是很多类库的使用者不知道如何初始化类库组件,甚至他们必须得去初始化。清理则是另一个特殊的问题,因为当你使用一个元素做完事后就不会去关心这个元素,所以你很容易忘记清理它。这样就造成了元素使用的资源滞留不会被回收,直到程序消耗完所有的资源(特别是内存)。\n", - "\n", - "C++ 引入了构造器的概念,这是一个特殊的方法,每创建一个对象,这个方法就会被自动调用。Java 采用了构造器的概念,另外还使用了垃圾收集器(Garbage Collector, GC)去自动回收不再被使用的对象所占的资源。这一章将讨论初始化和清理的问题,以及在 Java 中对它们的支持。\n", - "\n", - "\n", - "\n", - "## 利用构造器保证初始化\n", - "\n", - "你可能想为每个类创建一个 `initialize()` 方法,该方法名暗示着在使用类之前需要先调用它。不幸的是,用户必须得记得去调用它。在 Java 中,类的设计者通过构造器保证每个对象的初始化。如果一个类有构造器,那么 Java 会在用户使用对象之前(即对象刚创建完成)自动调用对象的构造器方法,从而保证初始化。下个挑战是如何命名构造器方法。存在两个问题:第一个是任何命名都可能与类中其他已有元素的命名冲突;第二个是编译器必须始终知道构造器方法名称,从而调用它。C++ 的解决方法看起来是最简单且最符合逻辑的,所以 Java 中使用了同样的方式:构造器名称与类名相同。在初始化过程中自动调用构造器方法是有意义的。\n", - "\n", - "以下示例是包含了一个构造器的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/SimpleConstructor.java\n", - "// Demonstration of a simple constructor\n", - "\n", - "class Rock {\n", - " Rock() { // 这是一个构造器\n", - " System.out.print(\"Rock \");\n", - " }\n", - "}\n", - "\n", - "public class SimpleConstructor {\n", - " public static void main(String[] args) {\n", - " for (int i = 0; i < 10; i++) {\n", - " new Rock();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,当创建一个对象时:`new Rock()` ,内存被分配,构造器被调用。构造器保证了对象在你使用它之前进行了正确的初始化。\n", - "\n", - "有一点需要注意,构造器方法名与类名相同,不需要符合首字母小写的编程风格。在 C++ 中,无参构造器被称为默认构造器,这个术语在 Java 出现之前使用了很多年。但是,出于一些原因,Java 设计者们决定使用无参构造器这个名称,我(作者)认为这种叫法笨拙而且没有必要,所以我打算继续使用默认构造器。Java 8 引入了 **default** 关键字修饰方法,所以算了,我还是用无参构造器的叫法吧。\n", - "\n", - "跟其他方法一样,构造器方法也可以传入参数来定义如何创建一个对象。之前的例子稍作修改,使得构造器接收一个参数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/SimpleConstructor2.java\n", - "// Constructors can have arguments\n", - "\n", - "class Rock2 {\n", - " Rock2(int i) {\n", - " System.out.print(\"Rock \" + i + \" \");\n", - " }\n", - "}\n", - "\n", - "public class SimpleConstructor2 {\n", - " public static void main(String[] args) {\n", - " for (int i = 0; i < 8; i++) {\n", - " new Rock2(i);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果类 **Tree** 有一个构造方法,只接收一个参数用来表示树的高度,那么你可以像下面这样创建一棵树:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Tree t = new Tree(12); // 12-foot 树" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果 **Tree(int)** 是唯一的构造器,那么编译器就不允许你以其他任何方式创建 **Tree** 类型的对象。\n", - "\n", - "构造器消除了一类重要的问题,使得代码更易读。例如,在上面的代码块中,你看不到对 `initialize()` 方法的显式调用,而从概念上来看,`initialize()` 方法应该与对象的创建分离。在 Java 中,对象的创建与初始化是统一的概念,二者不可分割。\n", - "\n", - "构造器没有返回值,它是一种特殊的方法。但它和返回类型为 `void` 的普通方法不同,普通方法可以返回空值,你还能选择让它返回别的类型;而构造器没有返回值,却同时也没有给你选择的余地(`new` 表达式虽然返回了刚创建的对象的引用,但构造器本身却没有返回任何值)。如果它有返回值,并且你也可以自己选择让它返回什么,那么编译器就还得知道接下来该怎么处理那个返回值(这个返回值没有接收者)。\n", - "\n", - "\n", - "\n", - "\n", - "## 方法重载\n", - "\n", - "任何编程语言中都具备的一项重要特性就是命名。当你创建一个对象时,就会给此对象分配的内存空间命名。方法是行为的命名。你通过名字指代所有的对象,属性和方法。良好命名的系统易于理解和修改。就好比写散文——目的是与读者沟通。\n", - "\n", - "将人类语言细微的差别映射到编程语言中会产生一个问题。通常,相同的词可以表达多种不同的含义——它们被\"重载\"了。特别是当含义的差别很小时,这会更加有用。你会说\"清洗衬衫\"、\"清洗车\"和\"清洗狗\"。而如果硬要这么说就会显得很愚蠢:\"以洗衬衫的方式洗衬衫\"、\"以洗车的方式洗车\"和\"以洗狗的方式洗狗\",因为听众根本不需要区分行为的动作。大多数人类语言都具有\"冗余\"性,所以即使漏掉几个词,你也能明白含义。你不需要对每个概念都使用不同的词汇——可以从上下文推断出含义。\n", - "\n", - "大多数编程语言(尤其是 C 语言)要求为每个方法(在这些语言中经常称为函数)提供一个独一无二的标识符。所以,你不能有一个 `print()` 函数既能打印整型,也能打印浮点型——每个函数名都必须不同。\n", - "\n", - "在 Java (C++) 中,还有一个因素也促使了必须使用方法重载:构造器。因为构造器方法名肯定是与类名相同,所以一个类中只会有一个构造器名。那么你怎么通过不同的方式创建一个对象呢?例如,你想创建一个类,这个类的初始化方式有两种:一种是标准化方式,另一种是从文件中读取信息的方式。你需要两个构造器:无参构造器和有一个 **String** 类型参数的构造器,该参数传入文件名。两个构造器具有相同的名字——与类名相同。因此,方法重载是必要的,它允许方法具有相同的方法名但接收的参数不同。尽管方法重载对于构造器是重要的,但是也可以对任何方法很方便地进行重载。\n", - "\n", - "下例展示了如何重载构造器和方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Overloading.java\n", - "// Both constructor and ordinary method overloading\n", - "\n", - "class Tree {\n", - " int height;\n", - " Tree() {\n", - " System.out.println(\"Planting a seedling\");\n", - " height = 0;\n", - " }\n", - " Tree(int initialHeight) {\n", - " height = initialHeight;\n", - " System.out.println(\"Creating new Tree that is \" + height + \" feet tall\");\n", - " }\n", - " void info() {\n", - " System.out.println(\"Tree is \" + height + \" feet tall\");\n", - " }\n", - " void info(String s) {\n", - " System.out.println(s + \": Tree is \" + height + \" feet tall\");\n", - " }\n", - "}\n", - "public class Overloading {\n", - " public static void main(String[] args) {\n", - " for (int i = 0; i < 5; i++) {\n", - " Tree t = new Tree(i);\n", - " t.info();\n", - " t.info(\"overloaded method\");\n", - " }\n", - " new Tree(); \n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Creating new Tree that is 0 feet tall\n", - "Tree is 0 feet tall\n", - "overloaded method: Tree is 0 feet tall\n", - "Creating new Tree that is 1 feet tall\n", - "Tree is 1 feet tall\n", - "overloaded method: Tree is 1 feet tall\n", - "Creating new Tree that is 2 feet tall\n", - "Tree is 2 feet tall\n", - "overloaded method: Tree is 2 feet tall\n", - "Creating new Tree that is 3 feet tall\n", - "Tree is 3 feet tall\n", - "overloaded method: Tree is 3 feet tall\n", - "Creating new Tree that is 4 feet tall\n", - "Tree is 4 feet tall\n", - "overloaded method: Tree is 4 feet tall\n", - "Planting a seedling" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一个 **Tree** 对象既可以是一颗树苗,使用无参构造器创建,也可以是一颗在温室中已长大的树,已经有一定高度,这时候,就需要使用有参构造器创建。\n", - "\n", - "你也许想以多种方式调用 `info()` 方法。比如,如果你想打印额外的消息,就可以使用 `info(String)` 方法。如果你无话可说,就可以使用 `info()` 方法。用两个命名定义完全相同的概念看起来很奇怪,而使用方法重载,你就可以使用一个命名来定义一个概念。\n", - "\n", - "### 区分重载方法\n", - "\n", - "如果两个方法命名相同,Java是怎么知道你调用的是哪个呢?有一条简单的规则:每个被重载的方法必须有独一无二的参数列表。你稍微思考下,就会很明了了,除了通过参数列表的不同来区分两个相同命名的方法,其他也没什么方式了。你甚至可以根据参数列表中的参数顺序来区分不同的方法,尽管这会造成代码难以维护。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OverloadingOrder.java\n", - "// Overloading based on the order of the arguments\n", - "\n", - "public class OverloadingOrder {\n", - " static void f(String s, int i) {\n", - " System.out.println(\"String: \" + s + \", int: \" + i);\n", - " }\n", - "\n", - " static void f(int i, String s) {\n", - " System.out.println(\"int: \" + i + \", String: \" + s);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " f(\"String first\", 1);\n", - " f(99, \"Int first\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "String: String first, int: 1\n", - "int: 99, String: Int first" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "两个 `f()` 方法具有相同的参数,但是参数顺序不同,根据这个就可以区分它们。\n", - "\n", - "### 重载与基本类型\n", - "\n", - "基本类型可以自动从较小的类型转型为较大的类型。当这与重载结合时,这会令人有点困惑,下面是一个这样的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/PrimitiveOverloading.java\n", - "// Promotion of primitives and overloading\n", - "\n", - "public class PrimitiveOverloading {\n", - " void f1(char x) {\n", - " System.out.print(\"f1(char)\");\n", - " }\n", - " void f1(byte x) {\n", - " System.out.print(\"f1(byte)\");\n", - " }\n", - " void f1(short x) {\n", - " System.out.print(\"f1(short)\");\n", - " }\n", - " void f1(int x) {\n", - " System.out.print(\"f1(int)\");\n", - " }\n", - " void f1(long x) {\n", - " System.out.print(\"f1(long)\");\n", - " }\n", - " void f1(float x) {\n", - " System.out.print(\"f1(float)\");\n", - " }\n", - " void f1(double x) {\n", - " System.out.print(\"f1(double)\");\n", - " }\n", - " void f2(byte x) {\n", - " System.out.print(\"f2(byte)\");\n", - " }\n", - " void f2(short x) {\n", - " System.out.print(\"f2(short)\");\n", - " }\n", - " void f2(int x) {\n", - " System.out.print(\"f2(int)\");\n", - " }\n", - " void f2(long x) {\n", - " System.out.print(\"f2(long)\");\n", - " }\n", - " void f2(float x) {\n", - " System.out.print(\"f2(float)\");\n", - " }\n", - " void f2(double x) {\n", - " System.out.print(\"f2(double)\");\n", - " }\n", - " void f3(short x) {\n", - " System.out.print(\"f3(short)\");\n", - " }\n", - " void f3(int x) {\n", - " System.out.print(\"f3(int)\");\n", - " }\n", - " void f3(long x) {\n", - " System.out.print(\"f3(long)\");\n", - " }\n", - " void f3(float x) {\n", - " System.out.print(\"f3(float)\");\n", - " }\n", - " void f3(double x) {\n", - " System.out.print(\"f3(double)\");\n", - " }\n", - " void f4(int x) {\n", - " System.out.print(\"f4(int)\");\n", - " }\n", - " void f4(long x) {\n", - " System.out.print(\"f4(long)\");\n", - " }\n", - " void f4(float x) {\n", - " System.out.print(\"f4(float)\");\n", - " }\n", - " void f4(double x) {\n", - " System.out.print(\"f4(double)\");\n", - " }\n", - " void f5(long x) {\n", - " System.out.print(\"f5(long)\");\n", - " }\n", - " void f5(float x) {\n", - " System.out.print(\"f5(float)\");\n", - " }\n", - " void f5(double x) {\n", - " System.out.print(\"f5(double)\");\n", - " }\n", - " void f6(float x) {\n", - " System.out.print(\"f6(float)\");\n", - " }\n", - " void f6(double x) {\n", - " System.out.print(\"f6(double)\");\n", - " }\n", - " void f7(double x) {\n", - " System.out.print(\"f7(double)\");\n", - " }\n", - " void testConstVal() {\n", - " System.out.print(\"5: \");\n", - " f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);\n", - " System.out.println();\n", - " }\n", - " void testChar() {\n", - " char x = 'x';\n", - " System.out.print(\"char: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testByte() {\n", - " byte x = 0;\n", - " System.out.print(\"byte: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testShort() {\n", - " short x = 0;\n", - " System.out.print(\"short: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testInt() {\n", - " int x = 0;\n", - " System.out.print(\"int: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testLong() {\n", - " long x = 0;\n", - " System.out.print(\"long: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testFloat() {\n", - " float x = 0;\n", - " System.out.print(\"float: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - " void testDouble() {\n", - " double x = 0;\n", - " System.out.print(\"double: \");\n", - " f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);\n", - " System.out.println();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " PrimitiveOverloading p = new PrimitiveOverloading();\n", - " p.testConstVal();\n", - " p.testChar();\n", - " p.testByte();\n", - " p.testShort();\n", - " p.testInt();\n", - " p.testLong();\n", - " p.testFloat();\n", - " p.testDouble();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "5: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", - "char: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", - "byte: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double)\n", - "short: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double)\n", - "int: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)\n", - "long: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double)\n", - "float: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double)\n", - "double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果传入的参数类型大于方法期望接收的参数类型,你必须首先做下转换,如果你不做的话,编译器就会报错。\n", - "\n", - "### 返回值的重载\n", - "\n", - "经常会有人困惑,\"为什么只能通过类名和参数列表,不能通过方法的返回值区分方法呢?\"。例如以下两个方法,它们有相同的命名和参数,但是很容易区分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void f(){}\n", - "int f() {return 1;}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有些情况下,编译器很容易就可以从上下文准确推断出该调用哪个方法,如 `int x = f()`。\n", - "\n", - "但是,你可以调用一个方法且忽略返回值。这叫做调用一个函数的副作用,因为你不在乎返回值,只是想利用方法做些事。所以如果你直接调用 `f()`,Java 编译器就不知道你想调用哪个方法,阅读者也不明所以。因为这个原因,所以你不能根据返回值类型区分重载的方法。为了支持新特性,Java 8 在一些具体情形下提高了猜测的准确度,但是通常来说并不起作用。\n", - "\n", - "\n", - "\n", - "## 无参构造器\n", - "\n", - "如前文所说,一个无参构造器就是不接收参数的构造器,用来创建一个\"默认的对象\"。如果你创建一个类,类中没有构造器,那么编译器就会自动为你创建一个无参构造器。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/DefaultConstructor.java\n", - "class Bird {}\n", - "public class DefaultConstructor {\n", - " public static void main(String[] args) {\n", - " Bird bird = new Bird(); // 默认的\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "表达式 `new Bird()` 创建了一个新对象,调用了无参构造器,尽管在 **Bird** 类中并没有显式的定义无参构造器。试想如果没有构造器,我们如何创建一个对象呢。但是,一旦你显式地定义了构造器(无论有参还是无参),编译器就不会自动为你创建无参构造器。如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/NoSynthesis.java\n", - "class Bird2 {\n", - " Bird2(int i) {}\n", - " Bird2(double d) {}\n", - "}\n", - "public class NoSynthesis {\n", - " public static void main(String[] args) {\n", - " //- Bird2 b = new Bird2(); // No default\n", - " Bird2 b2 = new Bird2(1);\n", - " Bird2 b3 = new Bird2(1.0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你调用了 `new Bird2()` ,编译器会提示找不到匹配的构造器。当类中没有构造器时,编译器会说\"你一定需要构造器,那么让我为你创建一个吧\"。但是如果类中有构造器,编译器会说\"你已经写了构造器了,所以肯定知道你在做什么,如果你没有创建默认构造器,说明你本来就不需要\"。\n", - "\n", - "\n", - "\n", - "## this关键字\n", - "\n", - "对于两个相同类型的对象 **a** 和 **b**,你可能在想如何调用这两个对象的 `peel()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/BananaPeel.java\n", - "\n", - "class Banana {\n", - " void peel(int i) {\n", - " /*...*/\n", - " }\n", - "}\n", - "public class BananaPeel {\n", - " public static void main(String[] args) {\n", - " Banana a = new Banana(), b = new Banana();\n", - " a.peel(1);\n", - " b.peel(2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果只有一个方法 `peel()` ,那么怎么知道调用的是对象 **a** 的 `peel()`方法还是对象 **b** 的 `peel()` 方法呢?编译器做了一些底层工作,所以你可以像这样编写代码。`peel()` 方法中第一个参数隐密地传入了一个指向操作对象的\n", - "\n", - "引用。因此,上述例子中的方法调用像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Banana.peel(a, 1)\n", - "Banana.peel(b, 1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,但能说明到底发生了什么。假设现在在方法内部,你想获得对当前对象的引用。但是,对象引用是被秘密地传达给编译器——并不在参数列表中。方便的是,有一个关键字: **this** 。**this** 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,**this** 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用其他该类中的方法,不要使用 **this**,直接调用即可,**this** 自动地应用于其他方法上了。因此你可以像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Apricot.java\n", - "\n", - "public class Apricot {\n", - " void pick() {\n", - " /* ... */\n", - " }\n", - "\n", - " void pit() {\n", - " pick();\n", - " /* ... */\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `pit()` 方法中,你可以使用 `this.pick()`,但是没有必要。编译器自动为你做了这些。**this** 关键字只用在一些必须显式使用当前对象引用的特殊场合。例如,用在 **return** 语句中返回对当前对象的引用。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Leaf.java\n", - "// Simple use of the \"this\" keyword\n", - "\n", - "public class Leaf {\n", - "\n", - " int i = 0;\n", - "\n", - " Leaf increment() {\n", - " i++;\n", - " return this;\n", - " }\n", - "\n", - " void print() {\n", - " System.out.println(\"i = \" + i);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Leaf x = new Leaf();\n", - " x.increment().increment().increment().print();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 `increment()` 通过 **this** 关键字返回当前对象的引用,因此在相同的对象上可以轻易地执行多次操作。\n", - "\n", - "**this** 关键字在向其他方法传递当前对象时也很有用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/PassingThis.java\n", - "\n", - "class Person {\n", - " public void eat(Apple apple) {\n", - " Apple peeled = apple.getPeeled();\n", - " System.out.println(\"Yummy\");\n", - " }\n", - "}\n", - "\n", - "public class Peeler {\n", - " static Apple peel(Apple apple) {\n", - " // ... remove peel\n", - " return apple; // Peeled\n", - " }\n", - "}\n", - "\n", - "public class Apple {\n", - " Apple getPeeled() {\n", - " return Peeler.peel(this);\n", - " }\n", - "}\n", - "\n", - "public class PassingThis {\n", - " public static void main(String[] args) {\n", - " new Person().eat(new Apple());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Yummy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Apple** 因为某些原因(比如说工具类中的方法在多个类中重复出现,你不想代码重复),必须调用一个外部工具方法 `Peeler.peel()` 做一些行为。必须使用 **this** 才能将自身传递给外部方法。\n", - "\n", - "### 在构造器中调用构造器\n", - "\n", - "当你在一个类中写了多个构造器,有时你想在一个构造器中调用另一个构造器来避免代码重复。你通过 **this** 关键字实现这样的调用。\n", - "\n", - "通常当你说 **this**,意味着\"这个对象\"或\"当前对象\",它本身生成对当前对象的引用。在一个构造器中,当你给 **this** 一个参数列表时,它是另一层意思。它通过最直接的方式显式地调用匹配参数列表的构造器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Flower.java\n", - "// Calling constructors with \"this\"\n", - "\n", - "public class Flower {\n", - " int petalCount = 0;\n", - " String s = \"initial value\";\n", - "\n", - " Flower(int petals) {\n", - " petalCount = petals;\n", - " System.out.println(\"Constructor w/ int arg only, petalCount = \" + petalCount);\n", - " }\n", - "\n", - " Flower(String ss) {\n", - " System.out.println(\"Constructor w/ string arg only, s = \" + ss);\n", - " s = ss;\n", - " }\n", - "\n", - " Flower(String s, int petals) {\n", - " this(petals);\n", - " //- this(s); // Can't call two!\n", - " this.s = s; // Another use of \"this\"\n", - " System.out.println(\"String & int args\");\n", - " }\n", - "\n", - " Flower() {\n", - " this(\"hi\", 47);\n", - " System.out.println(\"no-arg constructor\");\n", - " }\n", - "\n", - " void printPetalCount() {\n", - " //- this(11); // Not inside constructor!\n", - " System.out.println(\"petalCount = \" + petalCount + \" s = \" + s);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Flower x = new Flower();\n", - " x.printPetalCount();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Constructor w/ int arg only, petalCount = 47\n", - "String & int args\n", - "no-arg constructor\n", - "petalCount = 47 s = hi" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从构造器 `Flower(String s, int petals)` 可以看出,其中只能通过 **this** 调用一次构造器。另外,必须首先调用构造器,否则编译器会报错。这个例子同样展示了 **this** 的另一个用法。参数列表中的变量名 **s** 和成员变量名 **s** 相同,会引起混淆。你可以通过 `this.s` 表明你指的是成员变量 **s**,从而避免重复。你经常会在 Java 代码中看到这种用法,同时本书中也会多次出现这种写法。在 `printPetalCount()` 方法中,编译器不允许你在一个构造器之外的方法里调用构造器。\n", - "\n", - "### static 的含义\n", - "\n", - "记住了 **this** 关键字的内容,你会对 **static** 修饰的方法有更加深入的理解:**static** 方法中不会存在 **this**。你不能在静态方法中调用非静态方法(反之可以)。静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以被其他的静态方法和静态属性访问。一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 **this**,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 **static** 方法,就该重新考虑自己的设计了。然而,**static** 的概念很实用,许多时候都要用到它。至于它是否真的\"面向对象\",就留给理论家去讨论吧。\n", - "\n", - "\n", - "\n", - "## 垃圾回收器\n", - "\n", - "程序员都了解初始化的重要性,但通常会忽略清理的重要性。毕竟,谁会去清理一个 **int** 呢?但是使用完一个对象就不管它并非总是安全的。Java 中有垃圾回收器回收无用对象占用的内存。但现在考虑一种特殊情况:你创建的对象不是通过 **new** 来分配内存的,而垃圾回收器只知道如何释放用 **new** 创建的对象的内存,所以它不知道如何回收不是 **new** 分配的内存。为了处理这种情况,Java 允许在类中定义一个名为 `finalize()` 的方法。\n", - "\n", - "它的工作原理\"假定\"是这样的:当垃圾回收器准备回收对象的内存时,首先会调用其 `finalize()` 方法,并在下一轮的垃圾回收动作发生时,才会真正回收对象占用的内存。所以如果你打算使用 `finalize()` ,就能在垃圾回收时做一些重要的清理工作。`finalize()` 是一个潜在的编程陷阱,因为一些程序员(尤其是 C++ 程序员)会一开始把它误认为是 C++ 中的析构函数(C++ 在销毁对象时会调用这个函数)。所以有必要明确区分一下:在 C++ 中,对象总是被销毁的(在一个 bug-free 的程序中),而在 Java 中,对象并非总是被垃圾回收,或者换句话说:\n", - "\n", - "1. 对象可能不被垃圾回收。\n", - "2. 垃圾回收不等同于析构。\n", - "\n", - "这意味着在你不再需要某个对象之前,如果必须执行某些动作,你得自己去做。Java 没有析构器或类似的概念,所以你必须得自己创建一个普通的方法完成这项清理工作。例如,对象在创建的过程中会将自己绘制到屏幕上。如果不是明确地从屏幕上将其擦除,它可能永远得不到清理。如果在 `finalize()` 方法中加入某种擦除功能,那么当垃圾回收发生时,`finalize()` 方法被调用(不保证一定会发生),图像就会被擦除,要是\"垃圾回收\"没有发生,图像则仍会保留下来。\n", - "\n", - "也许你会发现,只要程序没有濒临内存用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,而垃圾回收器一直没有释放你创建的任何对象的内存,则当程序退出时,那些资源会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。\n", - "\n", - "### `finalize()` 的用途\n", - "\n", - "如果你不能将 `finalize()` 作为通用的清理方法,那么这个方法有什么用呢?\n", - "\n", - "这引入了要记住的第3点:\n", - "\n", - "3. 垃圾回收只与内存有关。\n", - "\n", - "也就是说,使用垃圾回收的唯一原因就是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是 `finalize()` 方法),它们也必须同内存及其回收有关。\n", - "\n", - "但这是否意味着如果对象中包括其他对象,`finalize()` 方法就应该明确释放那些对象呢?不是,无论对象是如何创建的,垃圾回收器都会负责释放对象所占用的所有内存。这就将对 `finalize()` 的需求限制到一种特殊情况,即通过某种创建对象方式之外的方式为对象分配了存储空间。不过,你可能会想,Java 中万物皆对象,这种情况怎么可能发生?\n", - "\n", - "看起来之所以有 `finalize()` 方法,是因为在分配内存时可能采用了类似 C 语言中的做法,而非 Java 中的通常做法。这种情况主要发生在使用\"本地方法\"的情况下,本地方法是一种用 Java 语言调用非 Java 语言代码的形式(关于本地方法的讨论,见本书电子版第2版的附录B)。本地方法目前只支持 C 和 C++,但是它们可以调用其他语言写的代码,所以实际上可以调用任何代码。在非 Java 代码中,也许会调用 C 的 `malloc()` 函数系列来分配存储空间,而且除非调用 `free()` 函数,不然存储空间永远得不到释放,造成内存泄露。但是,`free()` 是 C 和 C++ 中的函数,所以你需要在 `finalize()` 方法里用本地方法调用它。\n", - "\n", - "读到这里,你可能明白了不会过多使用 `finalize()` 方法。对,它确实不是进行普通的清理工作的合适场所。那么,普通的清理工作在哪里执行呢?\n", - "\n", - "### 你必须实施清理\n", - "\n", - "要清理一个对象,用户必须在需要清理的时候调用执行清理动作的方法。这听上去相当直接,但却与 C++ 中的\"析构函数\"的概念稍有抵触。在 C++ 中,所有对象都会被销毁,或者说应该被销毁。如果在 C++ 中创建了一个局部对象(在栈上创建,在 Java 中不行),此时的销毁动作发生在以\"右花括号\"为边界的、此对象作用域的末尾处。如果对象是用 **new** 创建的(类似于 Java 中),那么当程序员调用 C++ 的 **delete** 操作符时(Java 中不存在),就会调用相应的析构函数。如果程序员忘记调用 **delete**,那么永远不会调用析构函数,这样就会导致内存泄露,对象的其他部分也不会得到清理。这种 bug 很难跟踪,也是让 C++ 程序员转向 Java 的一个主要因素。相反,在 Java 中,没有用于释放对象的 **delete**,因为垃圾回收器会帮助你释放存储空间。甚至可以肤浅地认为,正是由于垃圾回收的存在,使得 Java 没有析构函数。然而,随着学习的深入,你会明白垃圾回收器的存在并不能完全替代析构函数(而且绝对不能直接调用 `finalize()`,所以这也不是一种解决方案)。如果希望进行除释放存储空间之外的清理工作,还是得明确调用某个恰当的 Java 方法:这就等同于使用析构函数了,只是没有它方便。\n", - "\n", - "记住,无论是\"垃圾回收\"还是\"终结\",都不保证一定会发生。如果 Java 虚拟机(JVM)并未面临内存耗尽的情形,它可能不会浪费时间执行垃圾回收以恢复内存。\n", - "\n", - "### 终结条件\n", - "\n", - "通常,不能指望 `finalize()` ,你必须创建其他的\"清理\"方法,并明确地调用它们。所以看起来,`finalize()` 只对大部分程序员很难用到的一些晦涩内存清理里有用了。但是,`finalize()` 还有一个有趣的用法,它不依赖于每次都要对 `finalize()` 进行调用,这就是对象终结条件的验证。\n", - "\n", - "当对某个对象不感兴趣时——也就是它将被清理了,这个对象应该处于某种状态,这种状态下它占用的内存可以被安全地释放掉。例如,如果对象代表了一个打开的文件,在对象被垃圾回收之前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的 bug。`finalize()` 可以用来最终发现这个情况,尽管它并不总是被调用。如果某次 `finalize()` 的动作使得 bug 被发现,那么就可以据此找出问题所在——这才是人们真正关心的。以下是个简单的例子,示范了 `finalize()` 的可能使用方式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/TerminationCondition.java\n", - "// Using finalize() to detect a object that\n", - "// hasn't been properly cleaned up\n", - "\n", - "import onjava.*;\n", - "\n", - "class Book {\n", - " boolean checkedOut = false;\n", - "\n", - " Book(boolean checkOut) {\n", - " checkedOut = checkOut;\n", - " }\n", - "\n", - " void checkIn() {\n", - " checkedOut = false;\n", - " }\n", - "\n", - " @Override\n", - " protected void finalize() throws Throwable {\n", - " if (checkedOut) {\n", - " System.out.println(\"Error: checked out\");\n", - " }\n", - " // Normally, you'll also do this:\n", - " // super.finalize(); // Call the base-class version\n", - " }\n", - "}\n", - "\n", - "public class TerminationCondition {\n", - "\n", - " public static void main(String[] args) {\n", - " Book novel = new Book(true);\n", - " // Proper cleanup:\n", - " novel.checkIn();\n", - " // Drop the reference, forget to clean up:\n", - " new Book(true);\n", - " // Force garbage collection & finalization:\n", - " System.gc();\n", - " new Nap(1); // One second delay\n", - " }\n", - "\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Error: checked out" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "本例的终结条件是:所有的 **Book** 对象在被垃圾回收之前必须被登记。但在 `main()` 方法中,有一本书没有登记。要是没有 `finalize()` 方法来验证终结条件,将会很难发现这个 bug。\n", - "\n", - "你可能注意到使用了 `@Override`。`@` 意味着这是一个注解,注解是关于代码的额外信息。在这里,该注解告诉编译器这不是偶然地重定义在每个对象中都存在的 `finalize()` 方法——程序员知道自己在做什么。编译器确保你没有拼错方法名,而且确保那个方法存在于基类中。注解也是对读者的提醒,`@Override` 在 Java 5 引入,在 Java 7 中改善,本书通篇会出现。\n", - "\n", - "注意,`System.gc()` 用于强制进行终结动作。但是即使不这么做,只要重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的 **Book** 对象。\n", - "\n", - "你应该总是假设基类版本的 `finalize()` 也要做一些重要的事情,使用 **super** 调用它,就像在 `Book.finalize()` 中看到的那样。本例中,它被注释掉了,因为它需要进行异常处理,而我们到现在还没有涉及到。\n", - "\n", - "### 垃圾回收器如何工作\n", - "\n", - "如果你以前用过的语言,在堆上分配对象的代价十分高昂,你可能自然会觉得 Java 中所有对象(基本类型除外)在堆上分配的方式也十分高昂。然而,垃圾回收器能很明显地提高对象的创建速度。这听起来很奇怪——存储空间的释放影响了存储空间的分配,但这确实是某些 Java 虚拟机的工作方式。这也意味着,Java 从堆空间分配的速度可以和其他语言在栈上分配空间的速度相媲美。\n", - "\n", - "例如,你可以把 C++ 里的堆想象成一个院子,里面每个对象都负责管理自己的地盘。一段时间后,对象可能被销毁,但地盘必须复用。在某些 Java 虚拟机中,堆的实现截然不同:它更像一个传送带,每分配一个新对象,它就向前移动一格。这意味着对象存储空间的分配速度特别快。Java 的\"堆指针\"只是简单地移动到尚未分配的区域,所以它的效率与 C++ 在栈上分配空间的效率相当。当然实际过程中,在簿记工作方面还有少量额外开销,但是这部分开销比不上查找可用空间开销大。\n", - "\n", - "你可能意识到了,Java 中的堆并非完全像传送带那样工作。要是那样的话,势必会导致频繁的内存页面调度——将其移进移出硬盘,因此会显得需要拥有比实际需要更多的内存。页面调度会显著影响性能。最终,在创建了足够多的对象后,内存资源被耗尽。其中的秘密在于垃圾回收器的介入。当它工作时,一边回收内存,一边使堆中的对象紧凑排列,这样\"堆指针\"就可以很容易地移动到更靠近传送带的开始处,也就尽量避免了页面错误。垃圾回收器通过重新排列对象,实现了一种高速的、有无限空间可分配的堆模型。\n", - "\n", - "要想理解 Java 中的垃圾回收,先了解其他系统中的垃圾回收机制将会很有帮助。一种简单但速度很慢的垃圾回收机制叫做*引用计数*。每个对象中含有一个引用计数器,每当有引用指向该对象时,引用计数加 1。当引用离开作用域或被置为 **null** 时,引用计数减 1。因此,管理引用计数是一个开销不大但是在程序的整个生命周期频繁发生的负担。垃圾回收器会遍历含有全部对象的列表,当发现某个对象的引用计数为 0 时,就释放其占用的空间(但是,引用计数模式经常会在计数为 0 时立即释放对象)。这个机制存在一个缺点:如果对象之间存在循环引用,那么它们的引用计数都不为 0,就会出现应该被回收但无法被回收的情况。对垃圾回收器而言,定位这样的循环引用所需的工作量极大。引用计数常用来说明垃圾回收的工作方式,但似乎从未被应用于任何一种 Java 虚拟机实现中。\n", - "\n", - "在更快的策略中,垃圾回收器并非基于引用计数。它们依据的是:对于任意\"活\"的对象,一定能最终追溯到其存活在栈或静态存储区中的引用。这个引用链条可能会穿过数个对象层次,由此,如果从栈或静态存储区出发,遍历所有的引用,你将会发现所有\"活\"的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是该对象包含的所有引用,如此反复进行,直到访问完\"根源于栈或静态存储区的引用\"所形成的整个网络。你所访问过的对象一定是\"活\"的。注意,这解决了对象间循环引用的问题,这些对象不会被发现,因此也就被自动回收了。\n", - "\n", - "在这种方式下,Java 虚拟机采用了一种*自适应*的垃圾回收技术。至于如何处理找到的存活对象,取决于不同的 Java 虚拟机实现。其中有一种做法叫做停止-复制(stop-and-copy)。顾名思义,这需要先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另一个堆,没有复制的就是需要被垃圾回收的。另外,当对象被复制到新堆时,它们是一个挨着一个紧凑排列,然后就可以按照前面描述的那样简单、直接地分配新空间了。\n", - "\n", - "当对象从一处复制到另一处,所有指向它的引用都必须修正。位于栈或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到(可以想象成一个表格,将旧地址映射到新地址)。\n", - "\n", - "这种所谓的\"复制回收器\"效率低下主要因为两个原因。其一:得有两个堆,然后在这两个分离的堆之间来回折腾,得维护比实际需要多一倍的空间。某些 Java 虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。\n", - "\n", - "其二在于复制本身。一旦程序进入稳定状态之后,可能只会产生少量垃圾,甚至没有垃圾。尽管如此,复制回收器仍然会将所有内存从一处复制到另一处,这很浪费。为了避免这种状况,一些 Java 虚拟机会进行检查:要是没有新垃圾产生,就会转换到另一种模式(即\"自适应\")。这种模式称为标记-清扫(mark-and-sweep),Sun 公司早期版本的 Java 虚拟机一直使用这种技术。对一般用途而言,\"标记-清扫\"方式速度相当慢,但是当你知道程序只会产生少量垃圾甚至不产生垃圾时,它的速度就很快了。\n", - "\n", - "\"标记-清扫\"所依据的思路仍然是从栈和静态存储区出发,遍历所有的引用,找出所有存活的对象。但是,每当找到一个存活对象,就给对象设一个标记,并不回收它。只有当标记过程完成后,清理动作才开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制动作。\"标记-清扫\"后剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就需要重新整理剩下的对象。\n", - "\n", - "\"停止-复制\"指的是这种垃圾回收动作不是在后台进行的;相反,垃圾回收动作发生的同时,程序将会暂停。在 Oracle 公司的文档中会发现,许多参考文献将垃圾回收视为低优先级的后台进程,但是早期版本的 Java 虚拟机并不是这么实现垃圾回收器的。当可用内存较低时,垃圾回收器会暂停程序。同样,\"标记-清扫\"工作也必须在程序暂停的情况下才能进行。\n", - "\n", - "如前文所述,这里讨论的 Java 虚拟机中,内存分配以较大的\"块\"为单位。如果对象较大,它会占用单独的块。严格来说,\"停止-复制\"要求在释放旧对象之前,必须先将所有存活对象从旧堆复制到新堆,这导致了大量的内存复制行为。有了块,垃圾回收器就可以把对象复制到废弃的块。每个块都有年代数来记录自己是否存活。通常,如果块在某处被引用,其年代数加 1,垃圾回收器会对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理动作——大型对象仍然不会复制(只是年代数会增加),含有小型对象的那些块则被复制并整理。Java 虚拟机会监视,如果所有对象都很稳定,垃圾回收的效率降低的话,就切换到\"标记-清扫\"方式。同样,Java 虚拟机会跟踪\"标记-清扫\"的效果,如果堆空间出现很多碎片,就会切换回\"停止-复制\"方式。这就是\"自适应\"的由来,你可以给它个啰嗦的称呼:\"自适应的、分代的、停止-复制、标记-清扫\"式的垃圾回收器。\n", - "\n", - "Java 虚拟机中有许多附加技术用来提升速度。尤其是与加载器操作有关的,被称为\"即时\"(Just-In-Time, JIT)编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码,所以不需要 JVM 来进行翻译,因此运行得更快。当需要装载某个类(通常是创建该类的第一个对象)时,编译器会先找到其 **.class** 文件,然后将该类的字节码装入内存。你可以让即时编译器编译所有代码,但这种做法有两个缺点:一是这种加载动作贯穿整个程序生命周期内,累加起来需要花更多时间;二是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序速度。另一种做法称为*惰性评估*,意味着即时编译器只有在必要的时候才编译代码。这样,从未被执行的代码也许就压根不会被 JIT 编译。新版 JDK 中的 Java HotSpot 技术就采用了类似的做法,代码每被执行一次就优化一些,所以执行的次数越多,它的速度就越快。\n", - "\n", - "\n", - "\n", - "## 成员初始化\n", - "\n", - "Java 尽量保证所有变量在使用前都能得到恰当的初始化。对于方法的局部变量,这种保证会以编译时错误的方式呈现,所以如果写成:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void f() {\n", - " int i;\n", - " i++;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你会得到一条错误信息,告诉你 **i** 可能尚未初始化。编译器可以为 **i** 赋一个默认值,但是未初始化的局部变量更有可能是程序员的疏忽,所以采用默认值反而会掩盖这种失误。强制程序员提供一个初始值,往往能帮助找出程序里的 bug。\n", - "\n", - "要是类的成员变量是基本类型,情况就会变得有些不同。正如在\"万物皆对象\"一章中所看到的,类的每个基本类型数据成员保证都会有一个初始值。下面的程序可以验证这类情况,并显示它们的值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/InitialValues.java\n", - "// Shows default initial values\n", - "\n", - "public class InitialValues {\n", - " boolean t;\n", - " char c;\n", - " byte b;\n", - " short s;\n", - " int i;\n", - " long l;\n", - " float f;\n", - " double d;\n", - " InitialValues reference;\n", - "\n", - " void printInitialValues() {\n", - " System.out.println(\"Data type Initial value\");\n", - " System.out.println(\"boolean \" + t);\n", - " System.out.println(\"char[\" + c + \"]\");\n", - " System.out.println(\"byte \" + b);\n", - " System.out.println(\"short \" + s);\n", - " System.out.println(\"int \" + i);\n", - " System.out.println(\"long \" + l);\n", - " System.out.println(\"float \" + f);\n", - " System.out.println(\"double \" + d);\n", - " System.out.println(\"reference \" + reference);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " new InitialValues().printInitialValues();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Data type Initial value\n", - "boolean false\n", - "char[NUL]\n", - "byte 0\n", - "short 0\n", - "int 0\n", - "long 0\n", - "float 0.0\n", - "double 0.0\n", - "reference null" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可见尽管数据成员的初值没有给出,但它们确实有初值(char 值为 0,所以显示为空白)。所以这样至少不会出现\"未初始化变量\"的风险了。\n", - "\n", - "在类里定义一个对象引用时,如果不将其初始化,那么引用就会被赋值为 **null**。\n", - "\n", - "### 指定初始化\n", - "\n", - "怎么给一个变量赋初值呢?一种很直接的方法是在定义类成员变量的地方为其赋值。以下代码修改了 InitialValues 类成员变量的定义,直接提供了初值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/InitialValues2.java\n", - "// Providing explicit initial values\n", - "\n", - "public class InitialValues2 {\n", - " boolean bool = true;\n", - " char ch = 'x';\n", - " byte b = 47;\n", - " short s = 0xff;\n", - " int i = 999;\n", - " long lng = 1;\n", - " float f = 3.14f;\n", - " double d = 3.14159;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你也可以用同样的方式初始化非基本类型的对象。如果 **Depth** 是一个类,那么可以像下面这样创建一个对象并初始化它:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Measurement.java\n", - "\n", - "class Depth {}\n", - "\n", - "public class Measurement {\n", - " Depth d = new Depth();\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有为 **d** 赋予初值就尝试使用它,就会出现运行时错误,告诉你产生了一个异常(详细见\"异常\"章节)。\n", - "\n", - "你也可以通过调用某个方法来提供初值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/MethodInit.java\n", - "\n", - "public class MethodInit {\n", - " int i = f();\n", - " \n", - " int f() {\n", - " return 11;\n", - " }\n", - " \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个方法可以带有参数,但这些参数不能是未初始化的类成员变量。因此,可以这么写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/MethodInit2.java\n", - "\n", - "public class MethodInit2 {\n", - " int i = f();\n", - " int j = g(i);\n", - " \n", - " int f() {\n", - " return 11;\n", - " }\n", - " \n", - " int g(int n) {\n", - " return n * 10;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是你不能这么写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/MethodInit3.java\n", - "\n", - "public class MethodInit3 {\n", - " //- int j = g(i); // Illegal forward reference\n", - " int i = f();\n", - "\n", - " int f() {\n", - " return 11;\n", - " }\n", - "\n", - " int g(int n) {\n", - " return n * 10;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "显然,上述程序的正确性取决于初始化的顺序,而与其编译方式无关。所以,编译器恰当地对\"向前引用\"发出了警告。\n", - "\n", - "这种初始化方式简单直观,但有个限制:类 **InitialValues** 的每个对象都有相同的初值,有时这的确是我们需要的,但有时却需要更大的灵活性。\n", - "\n", - "\n", - "\n", - "## 构造器初始化\n", - "\n", - "可以用构造器进行初始化,这种方式给了你更大的灵活性,因为你可以在运行时调用方法进行初始化。但是,这无法阻止自动初始化的进行,他会在构造器被调用之前发生。因此,如果使用如下代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Counter.java\n", - "\n", - "public class Counter {\n", - " int i;\n", - " \n", - " Counter() {\n", - " i = 7;\n", - " }\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**i** 首先会被初始化为 **0**,然后变为 **7**。对于所有的基本类型和引用,包括在定义时已明确指定初值的变量,这种情况都是成立的。因此,编译器不会强制你一定要在构造器的某个地方或在使用它们之前初始化元素——初始化早已得到了保证。, \n", - "\n", - "### 初始化的顺序\n", - "\n", - "在类中变量定义的顺序决定了它们初始化的顺序。即使变量定义散布在方法定义之间,它们仍会在任何方法(包括构造器)被调用之前得到初始化。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OrderOfInitialization.java\n", - "// Demonstrates initialization order\n", - "// When the constructor is called to create a\n", - "// Window object, you'll see a message:\n", - "\n", - "class Window {\n", - " Window(int marker) {\n", - " System.out.println(\"Window(\" + marker + \")\");\n", - " }\n", - "}\n", - "\n", - "class House {\n", - " Window w1 = new Window(1); // Before constructor\n", - "\n", - " House() {\n", - " // Show that we're in the constructor:\n", - " System.out.println(\"House()\");\n", - " w3 = new Window(33); // Reinitialize w3\n", - " }\n", - "\n", - " Window w2 = new Window(2); // After constructor\n", - "\n", - " void f() {\n", - " System.out.println(\"f()\");\n", - " }\n", - "\n", - " Window w3 = new Window(3); // At end\n", - "}\n", - "\n", - "public class OrderOfInitialization {\n", - " public static void main(String[] args) {\n", - " House h = new House();\n", - " h.f(); // Shows that construction is done\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Window(1)\n", - "Window(2)\n", - "Window(3)\n", - "House()\n", - "Window(33)\n", - "f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **House** 类中,故意把几个 **Window** 对象的定义散布在各处,以证明它们全都会在调用构造器或其他方法之前得到初始化。此外,**w3** 在构造器中被再次赋值。\n", - "\n", - "由输出可见,引用 **w3** 被初始化了两次:一次在调用构造器前,一次在构造器调用期间(第一次引用的对象将被丢弃,并作为垃圾回收)。这乍一看可能觉得效率不高,但保证了正确的初始化。试想,如果定义了一个重载构造器,在其中没有初始化 **w3**,同时在定义 **w3** 时没有赋予初值,那会产生怎样的后果呢?\n", - "\n", - "### 静态数据的初始化\n", - "\n", - "无论创建多少个对象,静态数据都只占用一份存储区域。**static** 关键字不能应用于局部变量,所以只能作用于属性(字段、域)。如果一个字段是静态的基本类型,你没有初始化它,那么它就会获得基本类型的标准初值。如果它是对象引用,那么它的默认初值就是 **null**。\n", - "\n", - "如果在定义时进行初始化,那么静态变量看起来就跟非静态变量一样。\n", - "\n", - "下面例子显示了静态存储区是何时初始化的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/StaticInitialization.java\n", - "// Specifying initial values in a class definition\n", - "\n", - "class Bowl {\n", - " Bowl(int marker) {\n", - " System.out.println(\"Bowl(\" + marker + \")\");\n", - " }\n", - " \n", - " void f1(int marker) {\n", - " System.out.println(\"f1(\" + marker + \")\");\n", - " }\n", - "}\n", - "\n", - "class Table {\n", - " static Bowl bowl1 = new Bowl(1);\n", - " \n", - " Table() {\n", - " System.out.println(\"Table()\");\n", - " bowl2.f1(1);\n", - " }\n", - " \n", - " void f2(int marker) {\n", - " System.out.println(\"f2(\" + marker + \")\");\n", - " }\n", - " \n", - " static Bowl bowl2 = new Bowl(2);\n", - "}\n", - "\n", - "class Cupboard {\n", - " Bowl bowl3 = new Bowl(3);\n", - " static Bowl bowl4 = new Bowl(4);\n", - " \n", - " Cupboard() {\n", - " System.out.println(\"Cupboard()\");\n", - " bowl4.f1(2);\n", - " }\n", - " \n", - " void f3(int marker) {\n", - " System.out.println(\"f3(\" + marker + \")\");\n", - " }\n", - " \n", - " static Bowl bowl5 = new Bowl(5);\n", - "}\n", - "\n", - "public class StaticInitialization {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"main creating new Cupboard()\");\n", - " new Cupboard();\n", - " System.out.println(\"main creating new Cupboard()\");\n", - " new Cupboard();\n", - " table.f2(1);\n", - " cupboard.f3(1);\n", - " }\n", - " \n", - " static Table table = new Table();\n", - " static Cupboard cupboard = new Cupboard();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Bowl(1)\n", - "Bowl(2)\n", - "Table()\n", - "f1(1)\n", - "Bowl(4)\n", - "Bowl(5)\n", - "Bowl(3)\n", - "Cupboard()\n", - "f1(2)\n", - "main creating new Cupboard()\n", - "Bowl(3)\n", - "Cupboard()\n", - "f1(2)\n", - "main creating new Cupboard()\n", - "Bowl(3)\n", - "Cupboard()\n", - "f1(2)\n", - "f2(1)\n", - "f3(1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Bowl** 类展示类的创建,而 **Table** 和 **Cupboard** 在它们的类定义中包含 **Bowl** 类型的静态数据成员。注意,在静态数据成员定义之前,**Cupboard** 类中先定义了一个 **Bowl** 类型的非静态成员 **b3**。\n", - "\n", - "由输出可见,静态初始化只有在必要时刻才会进行。如果不创建 **Table** 对象,也不引用 **Table.bowl1** 或 **Table.bowl2**,那么静态的 **Bowl** 类对象 **bowl1** 和 **bowl2** 永远不会被创建。只有在第一个 Table 对象被创建(或被访问)时,它们才会被初始化。此后,静态对象不会再次被初始化。\n", - "\n", - "初始化的顺序先是静态对象(如果它们之前没有被初始化的话),然后是非静态对象,从输出中可以看出。要执行 `main()` 方法,必须加载 **StaticInitialization** 类,它的静态属性 **table** 和 **cupboard** 随后被初始化,这会导致它们对应的类也被加载,而由于它们都包含静态的 **Bowl** 对象,所以 **Bowl** 类也会被加载。因此,在这个特殊的程序中,所有的类都会在 `main()` 方法之前被加载。实际情况通常并非如此,因为在典型的程序中,不会像本例中所示的那样,将所有事物通过 **static** 联系起来。\n", - "\n", - "概括一下创建对象的过程,假设有个名为 **Dog** 的类:\n", - "\n", - "1. 即使没有显式地使用 **static** 关键字,构造器实际上也是静态方法。所以,当首次创建 **Dog** 类型的对象或是首次访问 **Dog** 类的静态方法或属性时,Java 解释器必须在类路径中查找,以定位 **Dog.class**。\n", - "2. 当加载完 **Dog.class** 后(后面会学到,这将创建一个 **Class** 对象),有关静态初始化的所有动作都会执行。因此,静态初始化只会在首次加载 **Class** 对象时初始化一次。\n", - "3. 当用 `new Dog()` 创建对象时,首先会在堆上为 **Dog** 对象分配足够的存储空间。\n", - "4. 分配的存储空间首先会被清零,即会将 **Dog** 对象中的所有基本类型数据设置为默认值(数字会被置为 0,布尔型和字符型也相同),引用被置为 **null**。\n", - "5. 执行所有出现在字段定义处的初始化动作。\n", - "6. 执行构造器。你将会在\"复用\"这一章看到,这可能会牵涉到很多动作,尤其当涉及继承的时候。\n", - "\n", - "### 显式的静态初始化\n", - "\n", - "你可以将一组静态初始化动作放在类里面一个特殊的\"静态子句\"(有时叫做静态块)中。像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Spoon.java\n", - "\n", - "public class Spoon {\n", - " static int i;\n", - " \n", - " static {\n", - " i = 47;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这看起来像个方法,但实际上它只是一段跟在 **static** 关键字后面的代码块。与其他静态初始化动作一样,这段代码仅执行一次:当首次创建这个类的对象或首次访问这个类的静态成员(甚至不需要创建该类的对象)时。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/ExplicitStatic.java\n", - "// Explicit static initialization with \"static\" clause\n", - "\n", - "class Cup {\n", - " Cup(int marker) {\n", - " System.out.println(\"Cup(\" + marker + \")\");\n", - " }\n", - " \n", - " void f(int marker) {\n", - " System.out.println(\"f(\" + marker + \")\");\n", - " }\n", - "}\n", - "\n", - "class Cups {\n", - " static Cup cup1;\n", - " static Cup cup2;\n", - " \n", - " static {\n", - " cup1 = new Cup(1);\n", - " cup2 = new Cup(2);\n", - " }\n", - " \n", - " Cups() {\n", - " System.out.println(\"Cups()\");\n", - " }\n", - "}\n", - "\n", - "public class ExplicitStatic {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Inside main()\");\n", - " Cups.cup1.f(99); // [1]\n", - " }\n", - " \n", - " // static Cups cups1 = new Cups(); // [2]\n", - " // static Cups cups2 = new Cups(); // [2]\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Inside main\n", - "Cup(1)\n", - "Cup(2)\n", - "f(99)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "无论是通过标为 [1] 的行访问静态的 **cup1** 对象,还是把标为 [1] 的行去掉,让它去运行标为 [2] 的那行代码(去掉 [2] 的注释),**Cups** 的静态初始化动作都会执行。如果同时注释 [1] 和 [2] 处,那么 **Cups** 的静态初始化就不会进行。此外,把标为 [2] 处的注释都去掉还是只去掉一个,静态初始化只会执行一次。\n", - "\n", - "### 非静态实例初始化\n", - "\n", - "Java 提供了被称为*实例初始化*的类似语法,用来初始化每个对象的非静态变量,例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Mugs.java\n", - "// Instance initialization\n", - "\n", - "class Mug {\n", - " Mug(int marker) {\n", - " System.out.println(\"Mug(\" + marker + \")\");\n", - " }\n", - "}\n", - "\n", - "public class Mugs {\n", - " Mug mug1;\n", - " Mug mug2;\n", - " { // [1]\n", - " mug1 = new Mug(1);\n", - " mug2 = new Mug(2);\n", - " System.out.println(\"mug1 & mug2 initialized\");\n", - " }\n", - " \n", - " Mugs() {\n", - " System.out.println(\"Mugs()\");\n", - " }\n", - " \n", - " Mugs(int i) {\n", - " System.out.println(\"Mugs(int)\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " System.out.println(\"Inside main()\");\n", - " new Mugs();\n", - " System.out.println(\"new Mugs() completed\");\n", - " new Mugs(1);\n", - " System.out.println(\"new Mugs(1) completed\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Inside main\n", - "Mug(1)\n", - "Mug(2)\n", - "mug1 & mug2 initialized\n", - "Mugs()\n", - "new Mugs() completed\n", - "Mug(1)\n", - "Mug(2)\n", - "mug1 & mug2 initialized\n", - "Mugs(int)\n", - "new Mugs(1) completed" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "看起来它很像静态代码块,只不过少了 **static** 关键字。这种语法对于支持\"匿名内部类\"(参见\"内部类\"一章)的初始化是必须的,但是你也可以使用它保证某些操作一定会发生,而不管哪个构造器被调用。从输出看出,实例初始化子句是在两个构造器之前执行的。\n", - "\n", - "\n", - "\n", - "## 数组初始化\n", - "\n", - "数组是相同类型的、用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。数组是通过方括号下标操作符 [] 来定义和使用的。要定义一个数组引用,只需要在类型名加上方括号:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int[] a1;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "方括号也可放在标识符的后面,两者的含义是一样的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int a1[];" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这种格式符合 C 和 C++ 程序员的习惯。不过前一种格式或许更合理,毕竟它表明类型是\"一个 **int** 型数组\"。本书中采用这种格式。\n", - "\n", - "编译器不允许指定数组的大小。这又把我们带回有关\"引用\"的问题上。你所拥有的只是对数组的一个引用(你已经为该引用分配了足够的存储空间),但是还没有给数组对象本身分配任何空间。为了给数组创建相应的存储空间,必须写初始化表达式。对于数组,初始化动作可以出现在代码的任何地方,但是也可以使用一种特殊的初始化表达式,它必须在创建数组的地方出现。这种特殊的初始化是由一对花括号括起来的值组成。这种情况下,存储空间的分配(相当于使用 **new**) 将由编译器负责。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int[] a1 = {1, 2, 3, 4, 5};" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "那么为什么在还没有数组的时候定义一个数组引用呢?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int[] a2;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Java 中可以将一个数组赋值给另一个数组,所以可以这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "a2 = a1;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其实真正做的只是复制了一个引用,就像下面演示的这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/ArraysOfPrimitives.java\n", - "\n", - "public class ArraysOfPrimitives {\n", - " public static void main(String[] args) {\n", - " int[] a1 = {1, 2, 3, 4, 5};\n", - " int[] a2;\n", - " a2 = a1;\n", - " for (int i = 0; i < a2.length; i++) {\n", - " a2[i] += 1;\n", - " }\n", - " for (int i = 0; i < a1.length; i++) {\n", - " System.out.println(\"a1[\" + i + \"] = \" + a1[i]);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "a1[0] = 2;\n", - "a1[1] = 3;\n", - "a1[2] = 4;\n", - "a1[3] = 5;\n", - "a1[4] = 6;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**a1** 初始化了,但是 **a2** 没有;这里,**a2** 在后面被赋给另一个数组。由于 **a1** 和 **a2** 是相同数组的别名,因此通过 **a2** 所做的修改在 **a1** 中也能看到。\n", - "\n", - "所有的数组(无论是对象数组还是基本类型数组)都有一个固定成员 **length**,告诉你这个数组有多少个元素,你不能对其修改。与 C 和 C++ 类似,Java 数组计数也是从 0 开始的,所能使用的最大下标数是 **length - 1**。超过这个边界,C 和 C++ 会默认接受,允许你访问所有内存,许多声名狼藉的 bug 都是由此而生。但是 Java 在你访问超出这个边界时,会报运行时错误(异常),从而避免此类问题。\n", - "\n", - "### 动态数组创建\n", - "\n", - "如果在编写程序时,不确定数组中需要多少个元素,那么该怎么办呢?你可以直接使用 **new** 在数组中创建元素。下面例子中,尽管创建的是基本类型数组,**new** 仍然可以工作(不能用 **new** 创建单个的基本类型数组):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/ArrayNew.java\n", - "// Creating arrays with new\n", - "import java.util.*;\n", - "\n", - "public class ArrayNew {\n", - " public static void main(String[] args) {\n", - " int[] a;\n", - " Random rand = new Random(47);\n", - " a = new int[rand.nextInt(20)];\n", - " System.out.println(\"length of a = \" + a.length);\n", - " System.out.println(Arrays.toString(a));\n", - " } \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "length of a = 18\n", - "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "数组的大小是通过 `Random.nextInt()` 随机确定的,这个方法会返回 0 到输入参数之间的一个值。 由于随机性,很明显数组的创建确实是在运行时进行的。此外,程序输出表明,数组元素中的基本数据类型值会自动初始化为空值(对于数字和字符是 0;对于布尔型是 **false**)。`Arrays.toString()` 是 **java.util** 标准类库中的方法,会产生一维数组的可打印版本。\n", - "\n", - "本例中,数组也可以在定义的同时进行初始化:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "int[] a = new int[rand.nextInt(20)];" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果可能的话,应该尽量这么做。\n", - "\n", - "如果你创建了一个非基本类型的数组,那么你创建的是一个引用数组。以整型的包装类型 **Integer** 为例,它是一个类而非基本类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/ArrayClassObj.java\n", - "// Creating an array of nonprimitive objects\n", - "\n", - "import java.util.*;\n", - "\n", - "public class ArrayClassObj {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " Integer[] a = new Integer[rand.nextInt(20)];\n", - " System.out.println(\"length of a = \" + a.length);\n", - " for (int i = 0; i < a.length; i++) {\n", - " a[i] = rand.nextInt(500); // Autoboxing\n", - " }\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "length of a = 18\n", - "[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,即使使用 new 创建数组之后:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Integer[] a = new Integer[rand.nextInt(20)];\t" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它只是一个引用数组,直到通过创建新的 **Integer** 对象(通过自动装箱),并把对象赋值给引用,初始化才算结束:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "a[i] = rand.nextInt(500);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果忘记了创建对象,但试图使用数组中的空引用,就会在运行时产生异常。\n", - "\n", - "也可以用花括号括起来的列表来初始化数组,有两种形式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/ArrayInit.java\n", - "// Array initialization\n", - "import java.util.*;\n", - "\n", - "public class ArrayInit {\n", - " public static void main(String[] args) {\n", - " Integer[] a = {\n", - " 1, 2,\n", - " 3, // Autoboxing\n", - " };\n", - " Integer[] b = new Integer[] {\n", - " 1, 2,\n", - " 3, // Autoboxing\n", - " };\n", - " System.out.println(Arrays.toString(a));\n", - " System.out.println(Arrays.toString(b));\n", - "\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[1, 2, 3]\n", - "[1, 2, 3]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这两种形式中,初始化列表的最后一个逗号是可选的(这一特性使维护长列表变得更容易)。\n", - "\n", - "尽管第一种形式很有用,但是它更加受限,因为它只能用于数组定义处。第二种和第三种形式可以用在任何地方,甚至用在方法的内部。例如,你创建了一个 **String** 数组,将其传递给另一个类的 `main()` 方法,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/DynamicArray.java\n", - "// Array initialization\n", - "\n", - "public class DynamicArray {\n", - " public static void main(String[] args) {\n", - " Other.main(new String[] {\"fiddle\", \"de\", \"dum\"});\n", - " }\n", - "}\n", - "\n", - "class Other {\n", - " public static void main(String[] args) {\n", - " for (String s: args) {\n", - " System.out.print(s + \" \");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fiddle de dum " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Other.main()` 的参数是在调用处创建的,因此你甚至可以在方法调用处提供可替换的参数。\n", - "\n", - "### 可变参数列表\n", - "\n", - "你可以以一种类似 C 语言中的可变参数列表(C 通常把它称为\"varargs\")来创建和调用方法。这可以应用在参数个数或类型未知的场合。由于所有的类都最后继承于 **Object** 类(随着本书的进展,你会对此有更深的认识),所以你可以创建一个以 Object 数组为参数的方法,并像下面这样调用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/VarArgs.java\n", - "// Using array syntax to create variable argument lists\n", - "\n", - "class A {}\n", - "\n", - "public class VarArgs {\n", - " static void printArray(Object[] args) {\n", - " for (Object obj: args) {\n", - " System.out.print(obj + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " printArray(new Object[] {47, (float) 3.14, 11.11});\n", - " printArray(new Object[] {\"one\", \"two\", \"three\"});\n", - " printArray(new Object[] {new A(), new A(), new A()});\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "47 3.14 11.11 \n", - "one two three \n", - "A@15db9742 A@6d06d69c A@7852e922" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`printArray()` 的参数是 **Object** 数组,使用 for-in 语法遍历和打印数组的每一项。标准 Java 库能输出有意义的内容,但这里创建的是类的对象,打印出的内容是类名,后面跟着一个 **@** 符号以及多个十六进制数字。因而,默认行为(如果没有定义 `toString()` 方法的话,后面会讲这个方法)就是打印类名和对象的地址。\n", - "\n", - "你可能看到像上面这样编写的 Java 5 之前的代码,它们可以产生可变的参数列表。在 Java 5 中,这种期盼已久的特性终于添加了进来,就像在 `printArray()` 中看到的那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/NewVarArgs.java\n", - "// Using array syntax to create variable argument lists\n", - "\n", - "public class NewVarArgs {\n", - " static void printArray(Object... args) {\n", - " for (Object obj: args) {\n", - " System.out.print(obj + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " // Can take individual elements:\n", - " printArray(47, (float) 3.14, 11.11);\n", - " printArray(47, 3.14F, 11.11);\n", - " printArray(\"one\", \"two\", \"three\");\n", - " printArray(new A(), new A(), new A());\n", - " // Or an array:\n", - " printArray((Object[]) new Integer[] {1, 2, 3, 4});\n", - " printArray(); // Empty list is OK\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "47 3.14 11.11 \n", - "47 3.14 11.11 \n", - "one two three \n", - "A@15db9742 A@6d06d69c A@7852e922 \n", - "1 2 3 4 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有了可变参数,你就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你填充数组。你获取的仍然是一个数组,这就是为什么 `printArray()` 可以使用 for-in 迭代数组的原因。但是,这不仅仅只是从元素列表到数组的自动转换。注意程序的倒数第二行,一个 **Integer** 数组(通过自动装箱创建)被转型为一个 **Object** 数组(为了移除编译器的警告),并且传递给了 `printArray()`。显然,编译器会发现这是一个数组,不会执行转换。因此,如果你有一组事物,可以把它们当作列表传递,而如果你已经有了一个数组,该方法会把它们当作可变参数列表来接受。\n", - "\n", - "程序的最后一行表明,可变参数的个数可以为 0。当具有可选的尾随参数时,这一特性会有帮助:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OptionalTrailingArguments.java\n", - "\n", - "public class OptionalTrailingArguments {\n", - " static void f(int required, String... trailing) {\n", - " System.out.print(\"required: \" + required + \" \");\n", - " for (String s: trailing) {\n", - " System.out.print(s + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f(1, \"one\");\n", - " f(2, \"two\", \"three\");\n", - " f(0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "required: 1 one \n", - "required: 2 two three \n", - "required: 0 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这段程序展示了如何使用除了 **Object** 类之外类型的可变参数列表。这里,所有的可变参数都是 **String** 对象。可变参数列表中可以使用任何类型的参数,包括基本类型。下面例子展示了可变参数列表变为数组的情形,并且如果列表中没有任何元素,那么转变为大小为 0 的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/VarargType.java\n", - "\n", - "public class VarargType {\n", - " static void f(Character... args) {\n", - " System.out.print(args.getClass());\n", - " System.out.println(\" length \" + args.length);\n", - " }\n", - " \n", - " static void g(int... args) {\n", - " System.out.print(args.getClass());\n", - " System.out.println(\" length \" + args.length)\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f('a');\n", - " f();\n", - " g(1);\n", - " g();\n", - " System.out.println(\"int[]: \"+ new int[0].getClass());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class [Ljava.lang.Character; length 1\n", - "class [Ljava.lang.Character; length 0\n", - "class [I length 1\n", - "class [I length 0\n", - "int[]: class [I" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`getClass()` 方法属于 Object 类,将在\"类型信息\"一章中全面介绍。它会产生对象的类,并在打印该类时,看到表示该类类型的编码字符串。前导的 **[** 代表这是一个后面紧随的类型的数组,**I** 表示基本类型 **int**;为了进行双重检查,我在最后一行创建了一个 **int** 数组,打印了其类型。这样也验证了使用可变参数列表不依赖于自动装箱,而使用的是基本类型。\n", - "\n", - "然而,可变参数列表与自动装箱可以和谐共处,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/AutoboxingVarargs.java\n", - "\n", - "public class AutoboxingVarargs {\n", - " public static void f(Integer... args) {\n", - " for (Integer i: args) {\n", - " System.out.print(i + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f(1, 2);\n", - " f(4, 5, 6, 7, 8, 9);\n", - " f(10, 11, 12);\n", - " \n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1 2\n", - "4 5 6 7 8 9\n", - "10 11 12" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意吗,你可以在单个参数列表中将类型混合在一起,自动装箱机制会有选择地把 **int** 类型的参数提升为 **Integer**。\n", - "\n", - "可变参数列表使得方法重载更加复杂了,尽管乍看之下似乎足够安全:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OverloadingVarargs.java\n", - "\n", - "public class OverloadingVarargs {\n", - " static void f(Character... args) {\n", - " System.out.print(\"first\");\n", - " for (Character c: args) {\n", - " System.out.print(\" \" + c);\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " static void f(Integer... args) {\n", - " System.out.print(\"second\");\n", - " for (Integer i: args) {\n", - " System.out.print(\" \" + i);\n", - " }\n", - " System.out.println();\n", - " }\n", - " \n", - " static void f(Long... args) {\n", - " System.out.println(\"third\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f('a', 'b', 'c');\n", - " f(1);\n", - " f(2, 1);\n", - " f(0);\n", - " f(0L);\n", - " //- f(); // Won's compile -- ambiguous\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "first a b c\n", - "second 1\n", - "second 2 1\n", - "second 0\n", - "third" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在每种情况下,编译器都会使用自动装箱来匹配重载的方法,然后调用最明确匹配的方法。\n", - "\n", - "但是如果调用不含参数的 `f()`,编译器就无法知道应该调用哪个方法了。尽管这个错误可以弄清楚,但是它可能会使客户端程序员感到意外。\n", - "\n", - "你可能会通过在某个方法中增加一个非可变参数解决这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OverloadingVarargs2.java\n", - "// {WillNotCompile}\n", - "\n", - "public class OverloadingVarargs2 {\n", - " static void f(float i, Character... args) {\n", - " System.out.println(\"first\");\n", - " }\n", - " \n", - " static void f(Character... args) {\n", - " System.out.println(\"second\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f(1, 'a');\n", - " f('a', 'b');\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**{WillNotCompile}** 注释把该文件排除在了本书的 Gradle 构建之外。如果你手动编译它,会得到下面的错误信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "OverloadingVarargs2.java:14:error:reference to f is ambiguous f('a', 'b');\n", - "\\^\n", - "both method f(float, Character...) in OverloadingVarargs2 and method f(Character...) in OverloadingVarargs2 match 1 error" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你给这两个方法都添加一个非可变参数,就可以解决问题了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/OverloadingVarargs3\n", - "\n", - "public class OverloadingVarargs3 {\n", - " static void f(float i, Character... args) {\n", - " System.out.println(\"first\");\n", - " }\n", - " \n", - " static void f(char c, Character... args) {\n", - " System.out.println(\"second\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f(1, 'a');\n", - " f('a', 'b');\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "first\n", - "second" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你应该总是在重载方法的一个版本上使用可变参数列表,或者压根不用它。\n", - "\n", - "\n", - "\n", - "## 枚举类型\n", - "\n", - "Java 5 中添加了一个看似很小的特性 **enum** 关键字,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。以前,你需要创建一个整数常量集,但是这些值并不会将自身限制在这个常量集的范围内,因此使用它们更有风险,而且更难使用。枚举类型属于非常普遍的需求,C、C++ 和其他许多语言都已经拥有它了。在 Java 5 之前,Java 程序员必须了解许多细节并格外仔细地去达成 **enum** 的效果。现在 Java 也有了 **enum**,并且它的功能比 C/C++ 中的完备得多。下面是个简单的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Spiciness.java\n", - "\n", - "public enum Spiciness {\n", - " NOT, MILD, MEDIUM, HOT, FLAMING\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里创建了一个名为 **Spiciness** 的枚举类型,它有5个值。由于枚举类型的实例是常量,因此按照命名惯例,它们都用大写字母表示(如果名称中含有多个单词,使用下划线分隔)。\n", - "\n", - "要使用 **enum**,需要创建一个该类型的引用,然后将其赋值给某个实例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/SimpleEnumUse.java\n", - "\n", - "public class SimpleEnumUse {\n", - " public static void main(String[] args) {\n", - " Spiciness howHot = Spiciness.MEDIUM;\n", - " System.out.println(howHot);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "MEDIUM" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在你创建 **enum** 时,编译器会自动添加一些有用的特性。例如,它会创建 `toString()` 方法,以便你方便地显示某个 **enum** 实例的名称,这从上面例子中的输出可以看出。编译器还会创建 `ordinal()` 方法表示某个特定 **enum** 常量的声明顺序,`static values()` 方法按照 enum 常量的声明顺序,生成这些常量值构成的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/EnumOrder.java\n", - "\n", - "public class EnumOrder {\n", - " public static void main(String[] args) {\n", - " for (Spiciness s: Spiciness.values()) {\n", - " System.out.println(s + \", ordinal \" + s.ordinal());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NOT, ordinal 0\n", - "MILD, ordinal 1\n", - "MEDIUM, ordinal 2\n", - "HOT, ordinal 3\n", - "FLAMING, ordinal 4" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管 **enum** 看起来像是一种新的数据类型,但是这个关键字只是在生成 **enum** 的类时,产生了某些编译器行为,因此在很大程度上你可以将 **enum** 当作其他任何类。事实上,**enum** 确实是类,并且具有自己的方法。\n", - "\n", - "**enum** 有一个很实用的特性,就是在 **switch** 语句中使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// housekeeping/Burrito.java\n", - "\n", - "public class Burrito {\n", - " Spiciness degree;\n", - " \n", - " public Burrito(Spiciness degree) {\n", - " this.degree = degree;\n", - " }\n", - " \n", - " public void describe() {\n", - " System.out.print(\"This burrito is \");\n", - " switch(degree) {\n", - " case NOT:\n", - " System.out.println(\"not spicy at all.\");\n", - " break;\n", - " case MILD:\n", - " case MEDIUM:\n", - " System.out.println(\"a little hot.\");\n", - " break;\n", - " case HOT:\n", - " case FLAMING:\n", - " default:\n", - " System.out.println(\"maybe too hot\");\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Burrito plain = new Burrito(Spiciness.NOT),\n", - " greenChile = new Burrito(Spiciness.MEDIUM),\n", - " jalapeno = new Burrito(Spiciness.HOT);\n", - " plain.describe();\n", - " greenChile.describe();\n", - " jalapeno.describe();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "This burrito is not spicy at all.\n", - "This burrito is a little hot.\n", - "This burrito is maybe too hot." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于 **switch** 是在有限的可能值集合中选择,因此它与 **enum** 是绝佳的组合。注意,enum 的名称是如何能够倍加清楚地表明程序的目的的。\n", - "\n", - "通常,你可以将 **enum** 用作另一种创建数据类型的方式,然后使用所得到的类型。这正是关键所在,所以你不用过多地考虑它们。在 **enum** 被引入之前,你必须花费大量的精力去创建一个等同的枚举类型,并是安全可用的。\n", - "\n", - "这些介绍对于你理解和使用基本的 **enum** 已经足够了,我们会在\"枚举\"一章中进行更深入的探讨。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "构造器,这种看起来精巧的初始化机制,应该给了你很强的暗示:初始化在编程语言中的重要地位。C++ 的发明者 Bjarne Stroustrup 在设计 C++ 期间,在针对 C 语言的生产效率进行的最初调查中发现,错误的初始化会导致大量编程错误。这些错误很难被发现,同样,不合理的清理也会如此。因为构造器能保证进行正确的初始化和清理(没有正确的构造器调用,编译器就不允许创建对象),所以你就有了完全的控制和安全。\n", - "\n", - "在 C++ 中,析构器很重要,因为用 **new** 创建的对象必须被明确地销毁。在 Java 中,垃圾回收器会自动地释放所有对象的内存,所以很多时候类似的清理方法就不太需要了(但是当要用到的时候,你得自己动手)。在不需要类似析构器行为的时候,Java 的垃圾回收器极大地简化了编程,并加强了内存管理上的安全性。一些垃圾回收器甚至能清理其他资源,如图形和文件句柄。然而,垃圾回收器确实增加了运行时开销,由于 Java 解释器从一开始就很慢,所以这种开销到底造成多大的影响很难看出来。随着时间的推移,Java 在性能方面提升了很多,但是速度问题仍然是它涉足某些特定编程领域的障碍。\n", - "\n", - "由于要保证所有对象被创建,实际上构造器比这里讨论得更加复杂。特别是当通过*组合*或*继承*创建新类的时候,这种保证仍然成立,并且需要一些额外的语法来支持。在后面的章节中,你会学习组合,继承以及它们如何影响构造器。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/07-Implementation-Hiding.ipynb b/jupyter/07-Implementation-Hiding.ipynb deleted file mode 100644 index ad55bfa3..00000000 --- a/jupyter/07-Implementation-Hiding.ipynb +++ /dev/null @@ -1,1325 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第七章 封装\n", - "\n", - "> *访问控制(Access control)*(或者*隐藏实现(implementation hiding)*)与“最初的实现不恰当”有关。\n", - "\n", - "所有优秀的作者——包括那些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是*重构*(refactoring)的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。\n", - "\n", - "但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,一些用户(*客户端程序员(client programmers)*)希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:“如何区分变动的事物和不变的事物”。\n", - "\n", - "这个问题对于类库(library)而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。\n", - "\n", - "这可以通过约定解决。例如,类库开发者必须同意在修改类库中的一个类时,不会移除已有的方法,因为那样将会破坏客户端程序员的代码。与之相反的情况更加复杂。在有成员属性的情况下,类库开发者如何知道哪些属性被客户端程序员使用?这同样会发生在那些只为实现类库类而创建的方法上,它们也不是设计成可供客户端程序员调用的。如果类库开发者想删除旧的实现,添加新的实现,结果会怎样呢?任何这些成员的改动都可能破环客户端程序员的代码。因此类库开发者会被束缚,不能修改任何事物。\n", - "\n", - "为了解决这一问题,Java 提供了*访问修饰符*(access specifier)供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从“最大权限”到“最小权限”依次是:**public**,**protected**,*包访问权限(package access)*(没有关键字)和 **private**。根据上一段的内容,你可能会想,作为一名类库设计者,你会尽可能将一切都设为 **private**,仅向客户端程序员暴露你愿意他们使用的方法。这就是你通常所做的,尽管这与那些使用其他语言(尤其是 C)编程以及习惯了不受限制地访问任何东西的人们的直觉相违背。\n", - "\n", - "然而,类库组件的概念和对类库组件访问的控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚的类库单元中。Java 中通过 **package** 关键字加以控制,类在相同包下还是在不同包下,会影响访问修饰符。所以在这章开始,你将会学习如何将类库组件置于同一个包下,之后你就能明白访问修饰符的全部含义。\n", - "\n", - "\n", - "\n", - "## 包的概念\n", - "\n", - "包内包含一组类,它们被组织在一个单独的*命名空间*(namespace)下。\n", - "\n", - "例如,标准 Java 发布中有一个工具库,它被组织在 **java.util** 命名空间下。**java.util** 中含有一个类,叫做 **ArrayList**。使用 **ArrayList** 的一种方式是用其全名 **java.util.ArrayList**。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/FullQualification.java\n", - "\n", - "public class FullQualification {\n", - " public static void main(String[] args) {\n", - " java.util.ArrayList list = new java.util.ArrayList();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这种方式使得程序冗长乏味,因此你可以换一种方式,使用 **import** 关键字。如果需要导入某个类,就需要在 **import** 语句中声明:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/SingleImport.java\n", - "import java.util.ArrayList;\n", - "\n", - "public class SingleImport {\n", - " public static void main(String[] args) {\n", - " ArrayList list = new ArrayList();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在你就可以不加限定词,直接使用 **ArrayList** 了。但是对于 **java.util** 包下的其他类,你还是不能用。要导入其中所有的类,只需使用 **\\*** ,就像本书中其他示例那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import java.util.*" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "之所以使用导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。类 **A** 中的方法 `f()` 不会与类 **B** 中具有相同签名的方法 `f()` 冲突。但是如果类名冲突呢?假设你创建了一个 **Stack** 类,打算安装在一台已经有别人所写的 **Stack** 类的机器上,该怎么办呢?这种类名的潜在冲突,正是我们需要在 Java 中对命名空间进行完全控制的原因。为了解决冲突,我们为每个类创建一个唯一标识符组合。\n", - "\n", - "到目前为止的大部分示例都只存在单个文件,并为本地使用的,所以尚未受到包名的干扰。但是,这些示例其实已经位于包中了,叫做“未命名”包或*默认包*(default package)。这当然是一种选择,为了简单起见,本书其余部分会尽可能采用这种方式。但是,如果你打算为相同机器上的其他 Java 程序创建友好的类库或程序时,就必须仔细考虑以防类名冲突。\n", - "\n", - "一个 Java 源代码文件称为一个*编译单元(compilation unit)*(有时也称*翻译单元(translation unit)*)。每个编译单元的文件名后缀必须是 **.java**。在编译单元中可以有一个 **public** 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 **.java**)。每个编译单元中只能有一个 **public** 类,否则编译器不接受。如果这个编译单元中还有其他类,那么在包之外是无法访问到这些类的,因为它们不是 **public** 类,此时它们为主 **public** 类提供“支持”类 。\n", - "\n", - "### 代码组织\n", - "\n", - "当编译一个 **.java** 文件时,**.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为“obj”文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。\n", - "\n", - "类库是一组类文件。每个源文件通常都含有一个 **public** 类和任意数量的非 **public** 类,因此每个文件都有一个 **public** 组件。如果把这些组件集中在一起,就需要使用关键字 **package**。\n", - "\n", - "如果你使用了 **package** 语句,它必须是文件中除了注释之外的第一行代码。当你如下这样写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "package hiding;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "意味着这个编译单元是一个名为 **hiding** 类库的一部分。换句话说,你正在声明的编译单元中的 **public** 类名称位于名为 **hiding** 的保护伞下。任何人想要使用该名称,必须指明完整的类名或者使用 **import** 关键字导入 **hiding** 。(注意,Java 包名按惯例一律小写,即使中间的单词也需要小写,与驼峰命名不同)\n", - "\n", - "例如,假设文件名是 **MyClass.java** ,这意味着文件中只能有一个 **public** 类,且类名必须是 **MyClass**(大小写也与文件名相同):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/mypackage/MyClass.java\n", - "package hiding.mypackage\n", - "\n", - "public class MyClass {\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,如果有人想使用 **MyClass** 或 **hiding.mypackage** 中的其他 **public** 类,就必须使用关键字 **import** 来使 **hiding.mypackage** 中的名称可用。还有一种选择是使用完整的名称:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/QualifiedMyClass.java\n", - "\n", - "public class QualifiedMyClass {\n", - " public static void main(String[] args) {\n", - " hiding.mypackage.MyClass m = new hiding.mypackage.MyClass();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "关键字 **import** 使之更简洁:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/ImportedMyClass.java\n", - "import hiding.mypackage.*;\n", - "\n", - "public class ImportedMyClass {\n", - " public static void main(String[] args) {\n", - " MyClass m = new MyClass();\n", - " }\n", - "}\t" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**package** 和 **import** 这两个关键字将单一的全局命名空间分隔开,从而避免名称冲突。\n", - "\n", - "### 创建独一无二的包名\n", - "\n", - "你可能注意到,一个包从未真正被打包成单一的文件,它可以由很多 **.class** 文件构成,因而事情就变得有点复杂了。为了避免这种情况,一种合乎逻辑的做法是将特定包下的所有 **.class** 文件都放在一个目录下。也就是说,利用操作系统的文件结构的层次性。这是 Java 解决混乱问题的一种方式;稍后你还会在我们介绍 **jar** 工具时看到另一种方式。\n", - "\n", - "将所有的文件放在一个子目录还解决了其他的两个问题:创建独一无二的包名和查找可能隐藏于目录结构某处的类。这是通过将 **.class** 文件所在的路径位置编码成 **package** 名称来实现的。按照惯例,**package** 名称是类的创建者的反顺序的 Internet 域名。如果你遵循惯例,因为 Internet 域名是独一无二的,所以你的 **package** 名称也应该是独一无二的,不会发生名称冲突。如果你没有自己的域名,你就得构造一组不大可能与他人重复的组合(比如你的姓名),来创建独一无二的 package 名称。如果你打算发布 Java 程序代码,那么花些力气去获取一个域名是值得的。\n", - "\n", - "此技巧的第二部分是把 **package** 名称分解成你机器上的一个目录,所以当 Java 解释器必须要加载一个 .class 文件时,它能定位到 **.class** 文件所在的位置。首先,它找出环境变量 **CLASSPATH**(通过操作系统设置,有时也能通过 Java 的安装程序或基于 Java 的工具设置)。**CLASSPATH** 包含一个或多个目录,用作查找 .**class** 文件的根目录。从根目录开始,Java 解释器获取包名并将每个句点替换成反斜杠,生成一个基于根目录的路径名(取决于你的操作系统,包名 **foo.bar.baz** 变成 **foo\\bar\\baz** 或 **foo/bar/baz** 或其它)。然后这个路径与 **CLASSPATH** 的不同项连接,解释器就在这些目录中查找与你所创建的类名称相关的 **.class** 文件(解释器还会查找某些涉及 Java 解释器所在位置的标准目录)。\n", - "\n", - "为了理解这点,比如说我的域名 **MindviewInc.com**,将之反转并全部改为小写后就是 **com.mindviewinc**,这将作为我创建的类的独一无二的全局名称。(com、edu、org等扩展名之前在 Java 包中都是大写,但是 Java 2 之后都统一用小写。)我决定再创建一个名为 **simple** 的类库,从而细分名称:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "package com.mindviewinc.simple;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个包名可以用作下面两个文件的命名空间保护伞:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// com/mindviewinc/simple/Vector.java\n", - "// Creating a package\n", - "package com.mindviewinc.simple;\n", - "\n", - "public class Vector {\n", - " public Vector() {\n", - " System.out.println(\"com.mindviewinc.simple.Vector\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如前所述,**package** 语句必须是文件的第一行非注释代码。第二个文件看上去差不多:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// com/mindviewinc/simple/List.java\n", - "// Creating a package\n", - "package com.mindviewinc.simple;\n", - "\n", - "public class List {\n", - " System.out.println(\"com.mindview.simple.List\");\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这两个文件都位于我机器上的子目录中,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "C:\\DOC\\Java\\com\\mindviewinc\\simple" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(注意,本书的每个文件的第一行注释都指明了文件在源代码目录树中的位置——供本书的自动代码提取工具使用。)\n", - "\n", - "如果你回头看这个路径,会看到包名 **com.mindviewinc.simple**,但是路径的第一部分呢?CLASSPATH 环境变量会处理它。我机器上的环境变量部分如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CLASSPATH=.;D:\\JAVA\\LIB;C:\\DOC\\Java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "CLASSPATH 可以包含多个不同的搜索路径。\n", - "\n", - "但是在使用 JAR 文件时,有点不一样。你必须在类路径写清楚 JAR 文件的实际名称,不能仅仅是 JAR 文件所在的目录。因此,对于一个名为 **grape.jar** 的 JAR 文件,类路径应包括:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CLASSPATH=.;D\\JAVA\\LIB;C:\\flavors\\grape.jar" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦设置好类路径,下面的文件就可以放在任意目录:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/LibTest.java\n", - "// Uses the library\n", - "import com.mindviewinc.simple.*;\n", - "\n", - "public class LibTest {\n", - " public static void main(String[] args) {\n", - " Vector v = new Vector();\n", - " List l = new List();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "com.mindviewinc.simple.Vector\n", - "com.mindviewinc.simple.List" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当编译器遇到导入 **simple** 库的 **import** 语句时,它首先会在 CLASSPATH 指定的目录中查找子目录 **com/mindviewinc/simple**,然后从已编译的文件中找出名称相符者(对 **Vector** 而言是 **Vector.class**,对 **List** 而言是 **List.class**)。注意,这两个类和其中要访问的方法都必须是 **public** 修饰的。\n", - "\n", - "对于 Java 新手而言,设置 CLASSPATH 是一件麻烦的事(我最初使用时是这么觉得的),后面版本的 JDK 更加智能。你会发现当你安装好 JDK 时,即使不设置 CLASSPATH,也能够编译和运行基本的 Java 程序。但是,为了编译和运行本书的代码示例(从[https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 取得),你必须将本书程序代码树的基本目录加入到 CLASSPATH 中( gradlew 命令管理自身的 CLASSPATH,所以如果你想直接使用 javac 和 java,不用 Gradle 的话,就需要设置 CLASSPATH)。\n", - "\n", - "### 冲突\n", - "\n", - "如果通过 **\\*** 导入了两个包含相同名字类名的类库,会发生什么?例如,假设程序如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import com.mindviewinc.simple.*;\n", - "import java.util.*;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 **java.util.*** 也包含了 **Vector** 类,这就存在潜在的冲突。但是只要你不写导致冲突的代码,就不会有问题——这样很好,否则就得做很多类型检查工作来防止那些根本不会出现的冲突。\n", - "\n", - "现在如果要创建一个 Vector 类,就会出现冲突:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Vector v = new Vector();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里的 **Vector** 类指的是谁呢?编译器不知道,读者也不知道。所以编译器报错,强制你明确指明。对于标准的 Java 类 **Vector**,你可以这么写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "java.util.Vector v = new java.util.Vector();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这种写法完全指明了 **Vector** 类的位置(配合 CLASSPATH),那么就没有必要写 **import java.util.*** 语句,除非使用其他来自 **java.util** 中的类。\n", - "\n", - "或者,可以导入单个类以防冲突——只要不在同一个程序中使用有冲突的名字(若使用了有冲突的名字,必须明确指明全名)。\n", - "\n", - "### 定制工具库\n", - "\n", - "具备了以上知识,现在就可以创建自己的工具库来减少重复的程序代码了。\n", - "\n", - "一般来说,我会使用反转后的域名来命名要创建的工具包,比如 **com.mindviewinc.util** ,但为了简化,这里我把工具包命名为 **onjava**。\n", - "\n", - "比如,下面是“控制流”一章中使用到的 `range()` 方法,采用了 for-in 语法进行简单的遍历:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Range.java\n", - "// Array creation methods that can be used without\n", - "// qualifiers, using static imports:\n", - "package onjava;\n", - "\n", - "public class Range {\n", - " // Produce a sequence [0,n)\n", - " public static int[] range(int n) {\n", - " int[] result = new int[n];\n", - " for (int i = 0; i < n; i++) {\n", - " result[i] = i;\n", - " }\n", - " return result;\n", - " }\n", - " // Produce a sequence [start..end)\n", - " public static int[] range(int start, int end) {\n", - " int sz = end - start;\n", - " int[] result = new int[sz];\n", - " for (int i = 0; i < sz; i++) {\n", - " result[i] = start + i;\n", - " }\n", - " return result;\n", - " }\n", - " // Produce sequence [start..end) incrementing by step\n", - " public static int[] range(int start, int end, int step) {\n", - " int sz = (end - start) / step;\n", - " int[] result = new int[sz];\n", - " for (int i = 0; i < sz; i++) {\n", - " result[i] = start + (i * step);\n", - " }\n", - " return result;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个文件的位置一定是在某个以一个 CLASSPATH 位置开始,然后接着是 **onjava** 的目录下。编译完之后,就可以在系统的任何地方使用 **import static** 语句来使用这些方法了。\n", - "\n", - "从现在开始,无论何时你创建了有用的新工具,都可以把它加入到自己的类库中。在本书中,你将会看到更多的组件加入到 **onjava** 库。\n", - "\n", - "### 使用 import 改变行为\n", - "\n", - "Java 没有 C 的*条件编译*(conditional compilation)功能,该功能使你不必更改任何程序代码而能够切换开关产生不同的行为。Java 之所以去掉此功能,可能是因为 C 在绝大多数情况下使用该功能解决跨平台问题:程序代码的不同部分要根据不同的平台来编译。而 Java 自身就是跨平台设计的,这个功能就没有必要了。\n", - "\n", - "但是,条件编译还有其他的用途。调试是一个很常见的用途,调试功能在开发过程中是开启的,在发布的产品中是禁用的。可以通过改变导入的 **package** 来实现这一目的,修改的方法是将程序中的代码从调试版改为发布版。这个技术可用于任何种类的条件代码。\n", - "\n", - "### 使用包的忠告\n", - "\n", - "当创建一个包时,包名就隐含了目录结构。这个包必须位于包名指定的目录中,该目录必须在以 CLASSPATH 开始的目录中可以查询到。 最初使用关键字 **package** 可能会有点不顺,因为除非遵守“包名对应目录路径”的规则,否则会收到很多意外的运行时错误信息如找不到特定的类,即使这个类就位于同一目录中。如果你收到类似信息,尝试把 **package** 语句注释掉,如果程序能运行的话,你就知道问题出现在哪里了。\n", - "\n", - "注意,编译过的代码通常位于与源代码的不同目录中。这是很多工程的标准,而且集成开发环境(IDE)通常会自动为我们做这些。必须保证 JVM 通过 CLASSPATH 能找到编译后的代码。\n", - "\n", - "\n", - "\n", - "## 访问权限修饰符\n", - "\n", - "Java 访问权限修饰符 **public**,**protected** 和 **private** 位于定义的类名,属性名和方法名之前。每个访问权限修饰符只能控制它所修饰的对象。\n", - "\n", - "如果不提供访问修饰符,就意味着\"包访问权限\"。所以无论如何,万物都有某种形式的访问控制权。接下来的几节中,你将学习各种类型的访问权限。\n", - "\n", - "### 包访问权限\n", - "\n", - "本章之前的所有示例要么使用 **public** 访问修饰符,要么就没使用修饰符(*默认访问权限(default access)*)。默认访问权限没有关键字,通常被称为*包访问权限(package access)*(有时也称为 **friendly**)。这意味着当前包中的所有其他类都可以访问那个成员。对于这个包之外的类,这个成员看上去是 **private** 的。由于一个编译单元(即一个文件)只能隶属于一个包,所以通过包访问权限,位于同一编译单元中的所有类彼此之间都是可访问的。\n", - "\n", - "包访问权限可以把相关类聚到一个包下,以便它们能轻易地相互访问。包里的类赋予了它们包访问权限的成员相互访问的权限,所以你\"拥有”了包内的程序代码。只能通过你所拥有的代码去访问你所拥有的其他代码,这样规定很有意义。构建包访问权限机制是将类聚集在包中的重要原因之一。在许多语言中,在文件中组织定义的方式是任意的,但是在 Java 中你被强制以一种合理的方式组织它们。另外,你可能会将不应该对当前包中的类具有访问权限的类排除在包外。\n", - "\n", - "类控制着哪些代码有权访问自己的成员。其他包中的代码不能一上来就说\"嗨,我是 **Bob** 的朋友!\",然后想看到 **Bob** 的 **protected**、包访问权限和 **private** 成员。取得对成员的访问权的唯一方式是:\n", - "\n", - "1. 使成员成为 **public**。那么无论是谁,无论在哪,都可以访问它。\n", - "2. 赋予成员默认包访问权限,不用加任何访问修饰符,然后将其他类放在相同的包内。这样,其他类就可以访问该成员。\n", - "3. 在\"复用\"这一章你将看到,继承的类既可以访问 **public** 成员,也可以访问 **protected** 成员(但不能访问 **private** 成员)。只有当两个类处于同一个包内,它才可以访问包访问权限的成员。但现在不用担心继承和 **protected**。\n", - "4. 提供访问器(accessor)和修改器(mutator)方法(有时也称为\"get/set\" 方法),从而读取和改变值。\n", - "\n", - "### public: 接口访问权限\n", - "\n", - "当你使用关键字 **public**,就意味着紧随 public 后声明的成员对于每个人都是可用的,尤其是使用类库的客户端程序员更是如此。假设定义了一个包含下面编译单元的 **dessert** 包:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/dessert/Cookie.java\n", - "// Creates a library\n", - "package hiding.dessert;\n", - "\n", - "public class Cookie {\n", - " public Cookie() {\n", - " System.out.println(\"Cookie constructor\");\n", - " }\n", - " \n", - " void bite() {\n", - " System.out.println(\"bite\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的\"封装\"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找当前目录。\n", - "\n", - "现在,使用 **Cookie** 创建一个程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/Dinner.java\n", - "// Uses the library\n", - "import hiding.dessert.*;\n", - "\n", - "public class Dinner {\n", - " public static void main(String[] args) {\n", - " Cookie x = new Cookie();\n", - " // -x.bite(); // Can't access\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Cookie constructor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以创建一个 **Cookie** 对象,因为它构造器和类都是 **public** 的。(后面会看到更多 **public** 的概念)但是,在 **Dinner.java** 中无法访问到 **Cookie** 对象中的 `bite()` 方法,因为 `bite()` 只提供了包访问权限,因而在 **dessert** 包之外无法访问,编译器禁止你使用它。\n", - "\n", - "### 默认包\n", - "\n", - "你可能惊讶地发现,以下代码尽管看上去破坏了规则,但是仍然可以编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/Cake.java\n", - "// Accesses a class in a separate compilation unit\n", - "class Cake {\n", - " public static void main(String[] args) {\n", - " Pie x = new Pie();\n", - " x.f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Pie.f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "同一目录下的第二个文件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/Pie.java\n", - "// The other class\n", - "class Pie {\n", - " void f() {\n", - " System.out.println(\"Pie.f()\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 **.**,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。\n", - "\n", - "### private: 你无法访问\n", - "\n", - "关键字 **private** 意味着除了包含该成员的类,其他任何类都无法访问这个成员。同一包中的其他类无法访问 **private** 成员,因此这等于说是自己隔离自己。另一方面,让许多人合作创建一个包也是有可能的。使用 **private**,你可以自由地修改那个被修饰的成员,无需担心会影响同一包下的其他类。\n", - "\n", - "默认的包访问权限通常提供了足够的隐藏措施;记住,使用类的客户端程序员无法访问包访问权限成员。这样做很好,因为默认访问权限是一种我们常用的权限(同时也是一种在忘记添加任何访问权限时自动得到的权限)。因此,通常考虑的是把哪些成员声明成 **public** 供客户端程序员使用。所以,最初不常使用关键字 **private**,因为程序没有它也可以照常工作。然而,使用 **private** 是非常重要的,尤其是在多线程环境中。(在\"并发编程\"一章中将看到)。\n", - "\n", - "以下是一个使用 **private** 的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/IceCream.java\n", - "// Demonstrates \"private\" keyword\n", - "\n", - "class Sundae {\n", - " private Sundae() {}\n", - " static Sundae makeASundae() {\n", - " return new Sundae();\n", - " }\n", - "}\n", - "\n", - "public class IceCream {\n", - " public static void main(String[] args) {\n", - " //- Sundae x = new Sundae();\n", - " Sundae x = Sundae.makeASundae();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "以上展示了 **private** 的用武之地:控制如何创建对象,防止别人直接访问某个特定的构造器(或全部构造器)。例子中,你无法通过构造器创建一个 **Sundae** 对象,而必须调用 `makeASundae()` 方法创建对象。\n", - "\n", - "任何可以肯定只是该类的\"助手\"方法,都可以声明为 **private**,以确保不会在包中的其他地方误用它,也防止了你会去改变或删除它。将方法声明为 **private** 确保了你拥有这种选择权。\n", - "\n", - "对于类中的 **private** 属性也是一样。除非必须公开底层实现(这种情况很少见),否则就将属性声明为 **private**。然而,不能因为类中某个对象的引用是 **private**,就认为其他对象也无法拥有该对象的 **public** 引用(参见附录:对象传递和返回)。\n", - "\n", - "### protected: 继承访问权限\n", - "\n", - "要理解 **protected** 的访问权限,我们在内容上需要作一点跳跃。首先,在介绍本书\"复用\"章节前,你不必真正理解本节的内容。但为了内容的完整性,这里作了简要介绍,举了个使用 **protected** 的例子。\n", - "\n", - "关键字 **protected** 处理的是继承的概念,通过继承可以利用一个现有的类——我们称之为基类,然后添加新成员到现有类中而不必碰现有类。我们还可以改变类的现有成员的行为。为了从一个类中继承,需要声明新类 extends 一个现有类,像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class Foo extends Bar {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "类定义的其他部分看起来是一样的。\n", - "\n", - "如果你创建了一个新包,并从另一个包继承类,那么唯一能访问的就是被继承类的 **public** 成员。(如果在同一个包中继承,就可以操作所有的包访问权限的成员。)有时,基类的创建者会希望某个特定成员能被继承类访问,但不能被其他类访问。这时就需要使用 **protected**。**protected** 也提供包访问权限,也就是说,相同包内的其他类可以访问 **protected** 元素。\n", - "\n", - "回顾下先前的文件 **Cookie.java**,下面的类不能调用包访问权限的方法 `bite()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/ChocolateChip.java\n", - "// Can't use package-access member from another package\n", - "import hiding.dessert.*;\n", - "\n", - "public class ChocolateChip extends Cookie {\n", - " public ChocolateChip() {\n", - " System.out.println(\"ChocolateChip constructor\");\n", - " } \n", - " \n", - " public void chomp() {\n", - " //- bite(); // Can't access bite\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " ChocolateChip x = new ChocolateChip();\n", - " x.chomp();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Cookie constructor\n", - "ChocolateChip constructor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果类 **Cookie** 中存在一个方法 `bite()`,那么它的任何子类中都存在 `bite()` 方法。但是因为 `bite()` 具有包访问权限并且位于另一个包中,所以我们在这个包中无法使用它。你可以把它声明为 **public**,但这样一来每个人都能访问它,这可能也不是你想要的。如果你将 **Cookie** 改成如下这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/cookie2/Cookie.java\n", - "package hiding.cookie2;\n", - "\n", - "public class Cookie {\n", - " public Cookie() {\n", - " System.out.println(\"Cookie constructor\");\n", - " }\n", - " \n", - " protected void bite() {\n", - " System.out.println(\"bite\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这样,`bite()` 对于所有继承 **Cookie** 的类,都是可访问的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/ChocolateChip2.java\n", - "import hiding.cookie2.*;\n", - "\n", - "public class ChocolateChip2 extends Cookie {\n", - " public ChocoalteChip2() {\n", - " System.out.println(\"ChocolateChip2 constructor\");\n", - " }\n", - " \n", - " public void chomp() {\n", - " bite(); // Protected method\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " ChocolateChip2 x = new ChocolateChip2();\n", - " x.chomp();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Cookie constructor\n", - "ChocolateChip2 constructor\n", - "bite" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管 `bite()` 也具有包访问权限,但它不是 **public** 的。\n", - "\n", - "### 包访问权限 Vs Public 构造器\n", - "\n", - "当你定义一个具有包访问权限的类时,你可以在类中定义一个 public 构造器,编译器不会报错:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/packageaccess/PublicConstructor.java\n", - "package hiding.packageaccess;\n", - "\n", - "class PublicConstructor {\n", - " public PublicConstructor() {}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有一个 Checkstyle 工具,你可以运行命令 **gradlew hiding:checkstyleMain** 使用它,它会指出这种写法是虚假的,而且从技术上来说是错误的。实际上你不能从包外访问到这个 **public** 构造器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/CreatePackageAccessObject.java\n", - "// {WillNotCompile}\n", - "import hiding.packageaccess.*;\n", - "\n", - "public class CreatePackageAcessObject {\n", - " public static void main(String[] args) {\n", - " new PublicConstructor();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你编译下这个类,会得到编译错误信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "CreatePackageAccessObject.java:6:error:\n", - "PublicConstructor is not public in hiding.packageaccess;\n", - "cannot be accessed from outside package\n", - "new PublicConstructor();\n", - "^\n", - "1 error" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,在一个具有包访问权限的类中定义一个 **public** 的构造器并不能真的使这个构造器成为 **public**,在声明的时候就应该标记为编译时错误。\n", - "\n", - "\n", - "\n", - "## 接口和实现\n", - "\n", - "访问控制通常被称为*隐藏实现*(implementation hiding)。将数据和方法包装进类中并把具体实现隐藏被称作是*封装*(encapsulation)。其结果就是一个同时带有特征和行为的数据类型。\n", - "\n", - "出于两个重要的原因,访问控制在数据类型内部划定了边界。第一个原因是确立客户端程序员可以使用和不能使用的边界。可以在结构中建立自己的内部机制而不必担心客户端程序员偶尔将内部实现作为他们可以使用的接口的一部分。\n", - "\n", - "这直接引出了第二个原因:将接口与实现分离。如果在一组程序中使用接口,而客户端程序员只能向 **public** 接口发送消息的话,那么就可以自由地修改任何不是 **public** 的事物(例如包访问权限,protected,或 private 修饰的事物),却不会破坏客户端代码。\n", - "\n", - "为了清晰起见,你可以采用一种创建类的风格:**public** 成员放在类的开头,接着是 **protected** 成员,包访问权限成员,最后是 **private** 成员。这么做的好处是类的使用者可以从头读起,首先会看到对他们而言最重要的部分(public 成员,因为可以从文件外访问它们),直到遇到非 **public** 成员时停止阅读,下面就是内部实现了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/OrganizedByAccess.java\n", - "\n", - "public class OrganizedByAccess {\n", - " public void pub1() {/* ... */}\n", - " public void pub2() {/* ... */}\n", - " public void pub3() {/* ... */}\n", - " private void priv1() {/* ... */}\n", - " private void priv2() {/* ... */}\n", - " private void priv3() {/* ... */}\n", - " private int i;\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这么做只能是程序阅读起来稍微容易一些,因为实现和接口还是混合在一起。也就是说,你仍然能看到源代码——实现部分,因为它就在类中。另外,javadoc 提供的注释文档功能降低了程序代码的可读性对客户端程序员的重要性。将接口展现给类的使用者实际上是类浏览器的任务,类浏览器会展示所有可用的类,并告诉你如何使用它们(比如说哪些成员可用)。在 Java 中,JDK 文档起到了类浏览器的作用。\n", - "\n", - "\n", - "\n", - "## 类访问权限\n", - "\n", - "访问权限修饰符也可以用于确定类库中的哪些类对于类库的使用者是可用的。如果希望某个类可以被客户端程序员使用,就把关键字 **public** 作用于整个类的定义。这甚至控制着客户端程序员能否创建类的对象。\n", - "\n", - "为了控制一个类的访问权限,修饰符必须出现在关键字 **class** 之前:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public class Widget {" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你的类库名是 **hiding**,那么任何客户端程序员都可以通过如下声明访问 **Widget**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import hiding.Widget;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "或者" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import hiding.*;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里有一些额外的限制:\n", - "\n", - "1. 每个编译单元(即每个文件)中只能有一个 **public** 类。这表示,每个编译单元有一个公共的接口用 **public** 类表示。该接口可以包含许多支持包访问权限的类。一旦一个编译单元中出现一个以上的 **public** 类,编译就会报错。\n", - "2. **public** 类的名称必须与含有该编译单元的文件名相同,包括大小写。所以对于 **Widget** 来说,文件名必须是 **Widget.java**,不能是 **widget.java** 或 **WIDGET.java**。再次强调,如果名字不匹配,编译器会报错。\n", - "3. 虽然不是很常见,但是编译单元内没有 **public** 类也是可能的。这时可以随意命名文件(尽管随意命名会让代码的阅读者和维护者感到困惑)。\n", - "\n", - "如果获取了一个在 **hiding** 包中的类,只用来完成 **Widget** 或 **hiding** 包下一些其他 **public** 类所要执行的任务,怎么办呢? 你不想自找麻烦为客户端程序员创建说明文档,并且你认为不久后会完全改变原有方案并将旧版本删除,替换成新版本。为了保留此灵活性,需要确保客户端程序员不依赖隐藏在 **hiding** 中的任何特定细节,那么把 **public** 关键字从类中去掉,给予它包访问权限,就可以了。\n", - "\n", - "当你创建了一个包访问权限的类,把类中的属性声明为 **private** 仍然是有意义的——应该尽可能将所有属性都声明为 **private**,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。一个包访问权限的类只能被用于包内,除非强制将某些方法声明为 **public**,这种情况下,编译器会告诉你。\n", - "\n", - "注意,类既不能是 **private** 的(这样除了该类自身,任何类都不能访问它),也不能是 **protected** 的。所以对于类的访问权限只有两种选择:包访问权限或者 **public**。为了防止类被外界访问,可以将所有的构造器声明为 **private**,这样只有你自己能创建对象(在类的 static 成员中):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// hiding/Lunch.java\n", - "// Demonstrates class access specifiers. Make a class\n", - "// effectively private with private constructors:\n", - "\n", - "class Soup1 {\n", - " private Soup1() {}\n", - " \n", - " public static Soup1 makeSoup() { // [1]\n", - " return new Soup1();\n", - " }\n", - "}\n", - "\n", - "class Soup2 {\n", - " private Soup2() {}\n", - " \n", - " private static Soup2 ps1 = new Soup2(); // [2]\n", - " \n", - " public static Soup2 access() {\n", - " return ps1;\n", - " }\n", - " \n", - " public void f() {}\n", - "}\n", - "// Only one public class allowed per file:\n", - "public class Lunch {\n", - " void testPrivate() {\n", - " // Can't do this! Private constructor:\n", - " //- Soup1 soup = new Soup1();\n", - " }\n", - " \n", - " void testStatic() {\n", - " Soup1 soup = Soup1.makeSoup();\n", - " }\n", - " \n", - " void testSingleton() {\n", - " Soup2.access().f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以像 [1] 那样通过 **static** 方法创建对象,也可以像 [2] 那样先创建一个静态对象,当用户需要访问它时返回对象的引用即可。\n", - "\n", - "到目前为止,大部分的方法要么返回 void,要么返回基本类型,所以 [1] 处的定义乍看之下会有点困惑。方法名(**makeSoup**)前面的 **Soup1** 表明了方法返回的类型。到目前为止,这里经常是 **void**,即不返回任何东西。然而也可以返回对象的引用,就像这里一样。这个方法返回了对 **Soup1** 类对象的引用。\n", - "\n", - "**Soup1** 和 **Soup2** 展示了如何通过将你所有的构造器声明为 **private** 的方式防止直接创建某个类的对象。记住,如果你不显式地创建构造器,编译器会自动为你创建一个无参构造器(没有参数的构造器)。如果我们编写了无参构造器,那么编译器就不会自动创建构造器了。将构造器声明为 **private**,那么谁也无法创建该类的对象了。但是现在别人该怎么使用这个类呢?上述例子给出了两个选择。在 **Soup1** 中,有一个 **static** 方法,它的作用是创建一个新的 **Soup1** 对象并返回对象的引用。如果想要在返回引用之前在 **Soup1** 上做一些额外操作,或是记录创建了多少个 **Soup1** 对象(可以用来限制数量),这种做法是有用的。\n", - "\n", - "**Soup2** 用到了所谓的*设计模式*(design pattern)。这种模式叫做*单例模式*(singleton),因为它只允许创建类的一个对象。**Soup2** 类的对象是作为 **Soup2** 的 **static** **private** 成员而创建的,所以有且只有一个,你只能通过 **public** 修饰的 `access()` 方法访问到这个对象。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "无论在什么样的关系中,划定一些供各成员共同遵守的界限是很重要的。当你创建了一个类库,也就与该类库的使用者产生了联系,他们是类库的客户端程序员,需要使用你的类库创建应用或更大的类库。\n", - "\n", - "没有规则,客户端程序员就可以对类的所有成员为所欲为,即使你希望他们不要操作部分成员。这种情况下,所有事物都是公开的。\n", - "\n", - "本章讨论了类库是如何通过类构建的:首先,介绍了将一组类打包到类库的方式,其次介绍了类如何控制对其成员的访问。\n", - "\n", - "据估计,用 C 语言开发项目,当代码量达到 5 万行和 10 万行时就会出现问题,因为 C 语言只有单一的命名空间,名称开始冲突造成额外的管理开销。在 Java 中,关键字 **package**,包命名模式和关键字 **import** 给了你对于名称的完全控制权,因此可以轻易地避免名称冲突的问题。\n", - "\n", - "控制成员访问权限有两个原因。第一个原因是使用户不要接触他们不该接触的部分,这部分对于类内部来说是必要的,但是不属于客户端程序员所需接口的一部分。因此将方法和属性声明为 **private** 对于客户端程序员来说是一种服务,可以让他们清楚地看到什么是重要的,什么可以忽略。这可以简化他们对类的理解。\n", - "\n", - "第二个也是最重要的原因是为了让类库设计者更改类内部的工作方式,而不用担心会影响到客户端程序员。比如最初以某种方式创建一个类,随后发现如果更改代码结构可以极大地提高运行速度。如果接口与实现被明确地隔离和保护,你可以实现这一目的,而不必强制客户端程序员重新编写代码。访问权限控制确保客户端程序员不会依赖某个类的底层实现的任何部分。\n", - "\n", - "当你具备更改底层实现的能力时,不但可以自由地改善设计,还可能会随意地犯错。无论如何细心地计划和设计,都有可能犯错。当了解到犯错是相对安全的时候,你可以更加放心地实验,更快地学会,更快地完成项目。\n", - "\n", - "类的 **public** 接口是用户真正看到的,所以在分析和设计阶段决定这部分接口是最重要的部分。尽管如此,你仍然有改变的空间。如果最初没有创建出正确的接口,可以添加更多的方法,只要你不删除那些客户端程序员已经在他们的代码中使用的东西。\n", - "\n", - "注意到访问权限控制关注的是类库创建者和外部使用者之间的关系,一种交流方式。很多情况下,事实并非如此。例如,你自己编写了所有的代码,或者在一个小组中工作,所有的东西都放在同一个包下。这些情况下,交流方式则是另外一种,此时严格地遵循访问权限规则也许不是最佳选择,默认(包)访问权限也许就足够好了。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/08-Reuse.ipynb b/jupyter/08-Reuse.ipynb deleted file mode 100644 index ea06c47b..00000000 --- a/jupyter/08-Reuse.ipynb +++ /dev/null @@ -1,1661 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第八章 复用\n", - "\n", - "\n", - "> 代码复用是面向对象编程(OOP)最具魅力的原因之一。\n", - "\n", - "对于像 C 语言等面向过程语言来说,“复用”通常指的就是“复制代码”。任何语言都可通过简单复制来达到代码复用的目的,但是这样做的效果并不好。Java 围绕“类”(Class)来解决问题。我们可以直接使用别人构建或调试过的代码,而非创建新类、重新开始。\n", - "\n", - "如何在不污染源代码的前提下使用现存代码是需要技巧的。在本章里,你将学习到两种方式来达到这个目的:\n", - "\n", - "1. 第一种方式直接了当。在新类中创建现有类的对象。这种方式叫做“组合”(Composition),通过这种方式复用代码的功能,而非其形式。\n", - "\n", - "2. 第二种方式更为微妙。创建现有类类型的新类。照字面理解:采用现有类形式,又无需在编码时改动其代码,这种方式就叫做“继承”(Inheritance),编译器会做大部分的工作。**继承**是面向对象编程(OOP)的重要基础之一。更多功能相关将在[多态](./09-Polymorphism.md)(Polymorphism)章节中介绍。\n", - "\n", - "组合与继承的语法、行为上有许多相似的地方(这其实是有道理的,毕竟都是基于现有类型构建新的类型)。在本章中,你会学到这两种代码复用的方法。\n", - "\n", - "\n", - "\n", - "## 组合语法\n", - "\n", - "在前面的学习中,“组合”(Composition)已经被多次使用。你仅需要把对象的引用(object references)放置在一个新的类里,这就使用了组合。例如,假设你需要一个对象,其中内置了几个 **String** 对象,两个基本类型(primitives)的属性字段,一个其他类的对象。对于非基本类型对象,将引用直接放置在新类中,对于基本类型属性字段则仅进行声明。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/SprinklerSystem.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Composition for code reuse\n", - "\n", - "class WaterSource {\n", - " private String s;\n", - " WaterSource() {\n", - " System.out.println(\"WaterSource()\");\n", - " s = \"Constructed\";\n", - " }\n", - " @Override\n", - " public String toString() { return s; }\n", - "}\n", - "\n", - "public class SprinklerSystem {\n", - " private String valve1, valve2, valve3, valve4;\n", - " private WaterSource source = new WaterSource();\n", - " private int i;\n", - " private float f;\n", - " @Override\n", - " public String toString() {\n", - " return\n", - " \"valve1 = \" + valve1 + \" \" +\n", - " \"valve2 = \" + valve2 + \" \" +\n", - " \"valve3 = \" + valve3 + \" \" +\n", - " \"valve4 = \" + valve4 + \"\\n\" +\n", - " \"i = \" + i + \" \" + \"f = \" + f + \" \" +\n", - " \"source = \" + source; // [1]\n", - " }\n", - " public static void main(String[] args) {\n", - " SprinklerSystem sprinklers = new SprinklerSystem();\n", - " System.out.println(sprinklers);\n", - " }\n", - "}\n", - "/* Output:\n", - "WaterSource()\n", - "valve1 = null valve2 = null valve3 = null valve4 = null\n", - "i = 0 f = 0.0 source = Constructed\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注释来告诉编译器,以确保正确地覆盖。**@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。\n", - "\n", - "编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。初始化引用有四种方法:\n", - "\n", - "1. 当对象被定义时。这意味着它们总是在调用构造函数之前初始化。\n", - "2. 在该类的构造函数中。\n", - "3. 在实际使用对象之前。这通常称为*延迟初始化*。在对象创建开销大且不需要每次都创建对象的情况下,它可以减少开销。\n", - "4. 使用实例初始化。\n", - "\n", - "以上四种实例创建的方法例子在这:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Bath.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Constructor initialization with composition\n", - "\n", - "class Soap {\n", - " private String s;\n", - " Soap() {\n", - " System.out.println(\"Soap()\");\n", - " s = \"Constructed\";\n", - " }\n", - " @Override\n", - " public String toString() { return s; }\n", - "}\n", - "\n", - "public class Bath {\n", - " private String // Initializing at point of definition:\n", - " s1 = \"Happy\",\n", - " s2 = \"Happy\",\n", - " s3, s4;\n", - " private Soap castille;\n", - " private int i;\n", - " private float toy;\n", - " public Bath() {\n", - " System.out.println(\"Inside Bath()\");\n", - " s3 = \"Joy\";\n", - " toy = 3.14f;\n", - " castille = new Soap();\n", - " }\n", - " // Instance initialization:\n", - " { i = 47; }\n", - " @Override\n", - " public String toString() {\n", - " if(s4 == null) // Delayed initialization:\n", - " s4 = \"Joy\";\n", - " return\n", - " \"s1 = \" + s1 + \"\\n\" +\n", - " \"s2 = \" + s2 + \"\\n\" +\n", - " \"s3 = \" + s3 + \"\\n\" +\n", - " \"s4 = \" + s4 + \"\\n\" +\n", - " \"i = \" + i + \"\\n\" +\n", - " \"toy = \" + toy + \"\\n\" +\n", - " \"castille = \" + castille;\n", - " }\n", - " public static void main(String[] args) {\n", - " Bath b = new Bath();\n", - " System.out.println(b);\n", - " }\n", - "}\n", - "/* Output:\n", - "Inside Bath()\n", - "Soap()\n", - "s1 = Happy\n", - "s2 = Happy\n", - "s3 = Joy\n", - "s4 = Joy\n", - "i = 47\n", - "toy = 3.14\n", - "castille = Constructed\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **Bath** 构造函数中,有一个代码块在所有初始化发生前就已经执行了。当你不在定义处初始化时,仍然不能保证在向对象引用发送消息之前执行任何初始化——如果你试图对未初始化的引用调用方法,则未初始化的引用将产生运行时异常。\n", - "\n", - "当调用 `toString()` 时,它将赋值 s4,以便在使用字段的时候所有的属性都已被初始化。\n", - "\n", - "\n", - "\n", - "## 继承语法\n", - "\n", - "继承是所有面向对象语言的一个组成部分。事实证明,在创建类时总是要继承,因为除非显式地继承其他类,否则就隐式地继承 Java 的标准根类对象(Object)。\n", - "\n", - "组合的语法很明显,但是继承使用了一种特殊的语法。当你继承时,你说,“这个新类与那个旧类类似。你可以在类主体的左大括号前的代码中声明这一点,使用关键字 **extends** 后跟基类的名称。当你这样做时,你将自动获得基类中的所有字段和方法。这里有一个例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Detergent.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Inheritance syntax & properties\n", - "\n", - "class Cleanser {\n", - " private String s = \"Cleanser\";\n", - " public void append(String a) { s += a; }\n", - " public void dilute() { append(\" dilute()\"); }\n", - " public void apply() { append(\" apply()\"); }\n", - " public void scrub() { append(\" scrub()\"); }\n", - " @Override\n", - " public String toString() { return s; }\n", - " public static void main(String[] args) {\n", - " Cleanser x = new Cleanser();\n", - " x.dilute(); x.apply(); x.scrub();\n", - " System.out.println(x);\n", - " }\n", - "}\n", - "\n", - "public class Detergent extends Cleanser {\n", - " // Change a method:\n", - " @Override\n", - " public void scrub() {\n", - " append(\" Detergent.scrub()\");\n", - " super.scrub(); // Call base-class version\n", - " }\n", - " // Add methods to the interface:\n", - " public void foam() { append(\" foam()\"); }\n", - " // Test the new class:\n", - " public static void main(String[] args) {\n", - " Detergent x = new Detergent();\n", - " x.dilute();\n", - " x.apply();\n", - " x.scrub();\n", - " x.foam();\n", - " System.out.println(x);\n", - " System.out.println(\"Testing base class:\");\n", - " Cleanser.main(args);\n", - " }\n", - "}\n", - "/* Output:\n", - "Cleanser dilute() apply() Detergent.scrub() scrub()\n", - "foam()\n", - "Testing base class:\n", - "Cleanser dilute() apply() scrub()\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这演示了一些特性。首先,在 **Cleanser** 的 `append()` 方法中,使用 `+=` 操作符将字符串连接到 **s**,这是 Java 设计人员“重载”来处理字符串的操作符之一 (还有 + )。\n", - "\n", - "第二,**Cleanser** 和 **Detergent** 都包含一个 `main()` 方法。你可以为每个类创建一个 `main()` ; 这允许对每个类进行简单的测试。当你完成测试时,不需要删除 `main()`; 你可以将其留在以后的测试中。即使程序中有很多类都有 `main()` 方法,惟一运行的只有在命令行上调用的 `main()`。这里,当你使用 **java Detergent** 时候,就调用了 `Detergent.main()`。但是你也可以使用 **java Cleanser** 来调用 `Cleanser.main()`,即使 **Cleanser** 不是一个公共类。即使类只具有包访问权,也可以访问 `public main()`。\n", - "\n", - "在这里,`Detergent.main()` 显式地调用 `Cleanser.main()`,从命令行传递相同的参数(当然,你可以传递任何字符串数组)。\n", - "\n", - "**Cleanser** 中的所有方法都是公开的。请记住,如果不使用任何访问修饰符,则成员默认为包访问权限,这只允许包内成员访问。因此,如果没有访问修饰符,那么包内的任何人都可以使用这些方法。例如,**Detergent** 就没有问题。但是,如果其他包中的类继承 **Cleanser**,则该类只能访问 **Cleanser** 的公共成员。因此,为了允许继承,一般规则是所有字段为私有,所有方法为公共。(受保护成员也允许派生类访问;你以后会知道的。)在特定的情况下,你必须进行调整,但这是一个有用的指南。\n", - "\n", - "**Cleanser** 的接口中有一组方法: `append()`、`dilute()`、`apply()`、`scrub()` 和 `toString()`。因为 **Detergent** 是从 **Cleanser** 派生的(通过 **extends** 关键字),所以它会在其接口中自动获取所有这些方法,即使你没有在 **Detergent** 中看到所有这些方法的显式定义。那么,可以把继承看作是复用类。如在 `scrub()` 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 `scrub()` 内部,不能简单地调用 `scrub()`,因为这会产生递归调用。为了解决这个问题,Java的 **super** 关键字引用了当前类继承的“超类”(基类)。因此表达式 `super.scrub()` 调用方法 `scrub()` 的基类版本。\n", - "\n", - "继承时,你不受限于使用基类的方法。你还可以像向类添加任何方法一样向派生类添加新方法:只需定义它。方法 `foam()` 就是一个例子。`Detergent.main()` 中可以看到,对于 **Detergent** 对象,你可以调用 **Cleanser** 和 **Detergent** 中可用的所有方法 (如 `foam()` )。\n", - "\n", - "\n", - "\n", - "### 初始化基类\n", - "\n", - "现在涉及到两个类:基类和派生类。想象派生类生成的结果对象可能会让人感到困惑。从外部看,新类与基类具有相同的接口,可能还有一些额外的方法和字段。但是继承并不只是复制基类的接口。当你创建派生类的对象时,它包含基类的子对象。这个子对象与你自己创建基类的对象是一样的。只是从外部看,基类的子对象被包装在派生类的对象中。\n", - "\n", - "必须正确初始化基类子对象,而且只有一种方法可以保证这一点 : 通过调用基类构造函数在构造函数中执行初始化,该构造函数具有执行基类初始化所需的所有适当信息和特权。Java 自动在派生类构造函数中插入对基类构造函数的调用。下面的例子展示了三个层次的继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Cartoon.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Constructor calls during inheritance\n", - "\n", - "class Art {\n", - " Art() {\n", - " System.out.println(\"Art constructor\");\n", - " }\n", - "}\n", - "\n", - "class Drawing extends Art {\n", - " Drawing() {\n", - " System.out.println(\"Drawing constructor\");\n", - " }\n", - "}\n", - "\n", - "public class Cartoon extends Drawing {\n", - " public Cartoon() {\n", - " System.out.println(\"Cartoon constructor\");\n", - " }\n", - " public static void main(String[] args) {\n", - " Cartoon x = new Cartoon();\n", - " }\n", - "}\n", - "/* Output:\n", - "Art constructor\n", - "Drawing constructor\n", - "Cartoon constructor\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "构造从基类“向外”进行,因此基类在派生类构造函数能够访问它之前进行初始化。即使不为 **Cartoon** 创建构造函数,编译器也会为你合成一个无参数构造函数,调用基类构造函数。尝试删除 **Cartoon** 构造函数来查看这个。\n", - "\n", - "\n", - "\n", - "### 带参数的构造函数\n", - "\n", - "上面的所有例子中构造函数都是无参数的 ; 编译器很容易调用这些构造函数,因为不需要参数。如果没有无参数的基类构造函数,或者必须调用具有参数的基类构造函数,则必须使用 **super** 关键字和适当的参数列表显式地编写对基类构造函数的调用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Chess.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Inheritance, constructors and arguments\n", - "\n", - "class Game {\n", - " Game(int i) {\n", - " System.out.println(\"Game constructor\");\n", - " }\n", - "}\n", - "\n", - "class BoardGame extends Game {\n", - " BoardGame(int i) {\n", - " super(i);\n", - " System.out.println(\"BoardGame constructor\");\n", - " }\n", - "}\n", - "\n", - "public class Chess extends BoardGame {\n", - " Chess() {\n", - " super(11);\n", - " System.out.println(\"Chess constructor\");\n", - " }\n", - " public static void main(String[] args) {\n", - " Chess x = new Chess();\n", - " }\n", - "}\n", - "/* Output:\n", - "Game constructor\n", - "BoardGame constructor\n", - "Chess constructor\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有在 **BoardGame** 构造函数中调用基类构造函数,编译器就会报错找不到 `Game()` 的构造函数。此外,对基类构造函数的调用必须是派生类构造函数中的第一个操作。(如果你写错了,编译器会提醒你。)\n", - "\n", - "\n", - "\n", - "## 委托\n", - "\n", - "Java不直接支持的第三种重用关系称为委托。这介于继承和组合之间,因为你将一个成员对象放在正在构建的类中(比如组合),但同时又在新类中公开来自成员对象的所有方法(比如继承)。例如,宇宙飞船需要一个控制模块:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/SpaceShipControls.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "\n", - "public class SpaceShipControls {\n", - " void up(int velocity) {}\n", - " void down(int velocity) {}\n", - " void left(int velocity) {}\n", - " void right(int velocity) {}\n", - " void forward(int velocity) {}\n", - " void back(int velocity) {}\n", - " void turboBoost() {}\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "建造宇宙飞船的一种方法是使用继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/DerivedSpaceShip.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "\n", - "public class\n", - "DerivedSpaceShip extends SpaceShipControls {\n", - " private String name;\n", - " public DerivedSpaceShip(String name) {\n", - " this.name = name;\n", - " }\n", - " @Override\n", - " public String toString() { return name; }\n", - " public static void main(String[] args) {\n", - " DerivedSpaceShip protector =\n", - " new DerivedSpaceShip(\"NSEA Protector\");\n", - " protector.forward(100);\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然而, **DerivedSpaceShip** 并不是真正的“一种” **SpaceShipControls** ,即使你“告诉” **DerivedSpaceShip** 调用 `forward()`。更准确地说,一艘宇宙飞船包含了 **SpaceShipControls **,同时 **SpaceShipControls** 中的所有方法都暴露在宇宙飞船中。委托解决了这个难题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/SpaceShipDelegation.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "\n", - "public class SpaceShipDelegation {\n", - " private String name;\n", - " private SpaceShipControls controls =\n", - " new SpaceShipControls();\n", - " public SpaceShipDelegation(String name) {\n", - " this.name = name;\n", - " }\n", - " // Delegated methods:\n", - " public void back(int velocity) {\n", - " controls.back(velocity);\n", - " }\n", - " public void down(int velocity) {\n", - " controls.down(velocity);\n", - " }\n", - " public void forward(int velocity) {\n", - " controls.forward(velocity);\n", - " }\n", - " public void left(int velocity) {\n", - " controls.left(velocity);\n", - " }\n", - " public void right(int velocity) {\n", - " controls.right(velocity);\n", - " }\n", - " public void turboBoost() {\n", - " controls.turboBoost();\n", - " }\n", - " public void up(int velocity) {\n", - " controls.up(velocity);\n", - " }\n", - " public static void main(String[] args) {\n", - " SpaceShipDelegation protector =\n", - " new SpaceShipDelegation(\"NSEA Protector\");\n", - " protector.forward(100);\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "方法被转发到底层 **control** 对象,因此接口与继承的接口是相同的。但是,你对委托有更多的控制,因为你可以选择只在成员对象中提供方法的子集。\n", - "\n", - "虽然Java语言不支持委托,但是开发工具常常支持。例如,上面的例子是使用 JetBrains Idea IDE 自动生成的。\n", - "\n", - "\n", - "\n", - "## 结合组合与继承\n", - "\n", - "你将经常同时使用组合和继承。下面的例子展示了使用继承和组合创建类,以及必要的构造函数初始化:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/PlaceSetting.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Combining composition & inheritance\n", - "\n", - "class Plate {\n", - " Plate(int i) {\n", - " System.out.println(\"Plate constructor\");\n", - " }\n", - "}\n", - "\n", - "class DinnerPlate extends Plate {\n", - " DinnerPlate(int i) {\n", - " super(i);\n", - " System.out.println(\"DinnerPlate constructor\");\n", - " }\n", - "}\n", - "\n", - "class Utensil {\n", - " Utensil(int i) {\n", - " System.out.println(\"Utensil constructor\");\n", - " }\n", - "}\n", - "\n", - "class Spoon extends Utensil {\n", - " Spoon(int i) {\n", - " super(i);\n", - " System.out.println(\"Spoon constructor\");\n", - " }\n", - "}\n", - "\n", - "class Fork extends Utensil {\n", - " Fork(int i) {\n", - " super(i);\n", - " System.out.println(\"Fork constructor\");\n", - " }\n", - "}\n", - "\n", - "class Knife extends Utensil {\n", - " Knife(int i) {\n", - " super(i);\n", - " System.out.println(\"Knife constructor\");\n", - " }\n", - "}\n", - "\n", - "// A cultural way of doing something:\n", - "class Custom {\n", - " Custom(int i) {\n", - " System.out.println(\"Custom constructor\");\n", - " }\n", - "}\n", - "\n", - "public class PlaceSetting extends Custom {\n", - " private Spoon sp;\n", - " private Fork frk;\n", - " private Knife kn;\n", - " private DinnerPlate pl;\n", - " public PlaceSetting(int i) {\n", - " super(i + 1);\n", - " sp = new Spoon(i + 2);\n", - " frk = new Fork(i + 3);\n", - " kn = new Knife(i + 4);\n", - " pl = new DinnerPlate(i + 5);\n", - " System.out.println(\"PlaceSetting constructor\");\n", - " }\n", - " public static void main(String[] args) {\n", - " PlaceSetting x = new PlaceSetting(9);\n", - " }\n", - "}\n", - "/* Output:\n", - "Custom constructor\n", - "Utensil constructor\n", - "Spoon constructor\n", - "Utensil constructor\n", - "Fork constructor\n", - "Utensil constructor\n", - "Knife constructor\n", - "Plate constructor\n", - "DinnerPlate constructor\n", - "PlaceSetting constructor\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管编译器强制你初始化基类,并要求你在构造函数的开头就初始化基类,但它并不监视你以确保你初始化了成员对象。注意类是如何干净地分离的。你甚至不需要方法重用代码的源代码。你最多只导入一个包。(这对于继承和组合都是正确的。)\n", - "\n", - "\n", - "\n", - "### 保证适当的清理\n", - "\n", - "Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在\"异常\"章节中描述的——你必须通过在 **finally **子句中放置此类清理来防止异常。\n", - "\n", - "请考虑一个在屏幕上绘制图片的计算机辅助设计系统的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/CADSystem.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Ensuring proper cleanup\n", - "// {java reuse.CADSystem}\n", - "package reuse;\n", - "\n", - "class Shape {\n", - " Shape(int i) {\n", - " System.out.println(\"Shape constructor\");\n", - " }\n", - " void dispose() {\n", - " System.out.println(\"Shape dispose\");\n", - " }\n", - "}\n", - "\n", - "class Circle extends Shape {\n", - " Circle(int i) {\n", - " super(i);\n", - " System.out.println(\"Drawing Circle\");\n", - " }\n", - " @Override\n", - " void dispose() {\n", - " System.out.println(\"Erasing Circle\");\n", - " super.dispose();\n", - " }\n", - "}\n", - "\n", - "class Triangle extends Shape {\n", - " Triangle(int i) {\n", - " super(i);\n", - " System.out.println(\"Drawing Triangle\");\n", - " }\n", - " @Override\n", - " void dispose() {\n", - " System.out.println(\"Erasing Triangle\");\n", - " super.dispose();\n", - " }\n", - "}\n", - "\n", - "class Line extends Shape {\n", - " private int start, end;\n", - " Line(int start, int end) {\n", - " super(start);\n", - " this.start = start;\n", - " this.end = end;\n", - " System.out.println(\n", - " \"Drawing Line: \" + start + \", \" + end);\n", - " }\n", - " @Override\n", - " void dispose() {\n", - " System.out.println(\n", - " \"Erasing Line: \" + start + \", \" + end);\n", - " super.dispose();\n", - " }\n", - "}\n", - "\n", - "public class CADSystem extends Shape {\n", - " private Circle c;\n", - " private Triangle t;\n", - " private Line[] lines = new Line[3];\n", - " public CADSystem(int i) {\n", - " super(i + 1);\n", - " for(int j = 0; j < lines.length; j++)\n", - " lines[j] = new Line(j, j*j);\n", - " c = new Circle(1);\n", - " t = new Triangle(1);\n", - " System.out.println(\"Combined constructor\");\n", - " }\n", - " @Override\n", - " public void dispose() {\n", - " System.out.println(\"CADSystem.dispose()\");\n", - " // The order of cleanup is the reverse\n", - " // of the order of initialization:\n", - " t.dispose();\n", - " c.dispose();\n", - " for(int i = lines.length - 1; i >= 0; i--)\n", - " lines[i].dispose();\n", - " super.dispose();\n", - " }\n", - " public static void main(String[] args) {\n", - " CADSystem x = new CADSystem(47);\n", - " try {\n", - " // Code and exception handling...\n", - " } finally {\n", - " x.dispose();\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "Shape constructor\n", - "Shape constructor\n", - "Drawing Line: 0, 0\n", - "Shape constructor\n", - "Drawing Line: 1, 1\n", - "Shape constructor\n", - "Drawing Line: 2, 4\n", - "Shape constructor\n", - "Drawing Circle\n", - "Shape constructor\n", - "Drawing Triangle\n", - "Combined constructor\n", - "CADSystem.dispose()\n", - "Erasing Triangle\n", - "Shape dispose\n", - "Erasing Circle\n", - "Shape dispose\n", - "Erasing Line: 2, 4\n", - "Shape dispose\n", - "Erasing Line: 1, 1\n", - "Shape dispose\n", - "Erasing Line: 0, 0\n", - "Shape dispose\n", - "Shape dispose\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个系统中的所有东西都是某种 **Shape** (它本身是一种 **Object**,因为它是从根类隐式继承的) 。除了使用 **super** 调用该方法的基类版本外,每个类还覆盖 `dispose()` 方法。特定的 **Shape** 类——**Circle**、**Triangle** 和 **Line**,都有 “draw” 构造函数,尽管在对象的生命周期中调用的任何方法都可以负责做一些需要清理的事情。每个类都有自己的 `dispose()` 方法来将非内存的内容恢复到对象存在之前的状态。\n", - "\n", - "在 `main()` 中,有两个关键字是你以前没有见过的,在\"异常\"一章之前不会详细解释: **try** 和 **finally**。**try** 关键字表示后面的块 (用花括号分隔 )是一个受保护的区域,这意味着它得到了特殊处理。其中一个特殊处理是,无论 **try** 块如何退出,在这个保护区域之后的 **finally** 子句中的代码总是被执行。(通过异常处理,可以用许多不同寻常的方式留下 **try** 块。)这里,**finally** 子句的意思是,“无论发生什么,始终调用 `x.dispose()`。”\n", - "\n", - "在清理方法 (在本例中是 `dispose()` ) 中,还必须注意基类和成员对象清理方法的调用顺序,以防一个子对象依赖于另一个子对象。首先,按与创建的相反顺序执行特定于类的所有清理工作。(一般来说,这要求基类元素仍然是可访问的。) 然后调用基类清理方法,如这所示。\n", - "\n", - "在很多情况下,清理问题不是问题;你只需要让垃圾收集器来完成这项工作。但是,当你必须执行显式清理时,就需要多做努力,更加细心,因为在垃圾收集方面没有什么可以依赖的。可能永远不会调用垃圾收集器。如果调用,它可以按照它想要的任何顺序回收对象。除了内存回收外,你不能依赖垃圾收集来做任何事情。如果希望进行清理,可以使用自己的清理方法,不要使用 `finalize()`。\n", - "\n", - "\n", - "\n", - "### 名称隐藏\n", - "\n", - "如果 Java 基类的方法名多次重载,则在派生类中重新定义该方法名不会隐藏任何基类版本。不管方法是在这个级别定义的,还是在基类中定义的,重载都会起作用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Hide.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Overloading a base-class method name in a derived\n", - "// class does not hide the base-class versions\n", - "\n", - "class Homer {\n", - " char doh(char c) {\n", - " System.out.println(\"doh(char)\");\n", - " return 'd';\n", - " }\n", - " float doh(float f) {\n", - " System.out.println(\"doh(float)\");\n", - " return 1.0f;\n", - " }\n", - "}\n", - "\n", - "class Milhouse {}\n", - "\n", - "class Bart extends Homer {\n", - " void doh(Milhouse m) {\n", - " System.out.println(\"doh(Milhouse)\");\n", - " }\n", - "}\n", - "\n", - "public class Hide {\n", - " public static void main(String[] args) {\n", - " Bart b = new Bart();\n", - " b.doh(1);\n", - " b.doh('x');\n", - " b.doh(1.0f);\n", - " b.doh(new Milhouse());\n", - " }\n", - "}\n", - "/* Output:\n", - "doh(float)\n", - "doh(char)\n", - "doh(float)\n", - "doh(Milhouse)\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。在下一章中你将看到,使用与基类中完全相同的签名和返回类型覆盖相同名称的方法要常见得多。否则就会令人困惑。\n", - "\n", - "你已经看到了Java 5 **@Override **注释,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法时,你可以选择添加这个注释,如果你不小心用了重载而不是重写,编译器会产生一个错误消息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Lisa.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// {WillNotCompile}\n", - "\n", - "class Lisa extends Homer {\n", - " @Override void doh(Milhouse m) {\n", - " System.out.println(\"doh(Milhouse)\");\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:方法不会覆盖超类中的方法, **@Override** 注释防止你意外地重载。\n", - "\n", - "\n", - "\n", - "## 组合与继承的选择\n", - "\n", - "组合和继承都允许在新类中放置子对象(组合是显式的,而继承是隐式的)。你或许想知道这二者之间的区别,以及怎样在二者间做选择。\n", - "\n", - "当你想在新类中包含一个已有类的功能时,使用组合,而非继承。也就是说,在新类中嵌入一个对象(通常是私有的),以实现其功能。新类的使用者看到的是你所定义的新类的接口,而非嵌入对象的接口。\n", - "\n", - "有时让类的用户直接访问到新类中的组合成分是有意义的。只需将成员对象声明为 **public** 即可(可以把这当作“半委托”的一种)。成员对象隐藏了具体实现,所以这是安全的。当用户知道你正在组装一组部件时,会使得接口更加容易理解。下面的 car 对象是个很好的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Car.java\n", - "// Composition with public objects\n", - "class Engine {\n", - " public void start() {}\n", - " public void rev() {}\n", - " public void stop() {}\n", - "}\n", - "\n", - "class Wheel {\n", - " public void inflate(int psi) {}\n", - "}\n", - "\n", - "class Window {\n", - " public void rollup() {}\n", - " public void rolldown() {}\n", - "}\n", - "\n", - "class Door {\n", - " public Window window = new Window();\n", - " \n", - " public void open() {}\n", - " public void close() {}\n", - "}\n", - "\n", - "public class Car {\n", - " public Engine engine = new Engine();\n", - " public Wheel[] wheel = new Wheel[4];\n", - " public Door left = new Door(), right = new Door(); // 2-door\n", - " \n", - " public Car() {\n", - " for (int i = 0; i < 4; i++) {\n", - " wheel[i] = new Wheel();\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Car car = new Car();\n", - " car.left.window.rollup();\n", - " car.wheel[0].inflate(72);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为在这个例子中 car 的组合也是问题分析的一部分(不是底层设计的部分),所以声明成员为 **public** 有助于客户端程序员理解如何使用类,且降低了类创建者面临的代码复杂度。但是,记住这是一个特例。通常来说,属性还是应该声明为 **private**。\n", - "\n", - "当使用继承时,使用一个现有类并开发出它的新版本。通常这意味着使用一个通用类,并为了某个特殊需求将其特殊化。稍微思考下,你就会发现,用一个交通工具对象来组成一部车是毫无意义的——车不包含交通工具,它就是交通工具。这种“是一个”的关系是用继承来表达的,而“有一个“的关系则用组合来表达。\n", - "\n", - "\n", - "\n", - "## protected\n", - "\n", - "即然你已经接触到继承,关键字 **protected** 就变得有意义了。在理想世界中,仅靠关键字 **private** 就足够了。在实际项目中,却经常想把一个事物尽量对外界隐藏,而允许派生类的成员访问。\n", - "\n", - "关键字 **protected** 就起这个作用。它表示“就类的用户而言,这是 **private** 的。但对于任何继承它的子类或在同一包中的类,它是可访问的。”(**protected** 也提供了包访问权限)\n", - "\n", - "尽管可以创建 **protected** 属性,但是最好的方式是将属性声明为 **private** 以一直保留更改底层实现的权利。然后通过 **protected** 控制类的继承者的访问权限。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Orc.java\n", - "// The protected keyword\n", - "class Villain {\n", - " private String name;\n", - " \n", - " protected void set(String nm) {\n", - " name = nm;\n", - " }\n", - " \n", - " Villain(String name) {\n", - " this.name = name;\n", - " }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"I'm a Villain and my name is \" + name;\n", - " }\n", - "}\n", - "\n", - "public class Orc extends Villain {\n", - " private int orcNumber;\n", - " \n", - " public Orc(String name, int orcNumber) {\n", - " super(name);\n", - " this.orcNumber = orcNumber;\n", - " }\n", - " \n", - " public void change(String name, int orcNumber) {\n", - " set(name); // Available because it's protected\n", - " this.orcNumber = orcNumber;\n", - " }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"Orc \" + orcNumber + \": \" + super.toString();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Orc orc = new Orc(\"Limburger\", 12);\n", - " System.out.println(orc);\n", - " orc.change(\"Bob\", 19);\n", - " System.out.println(orc);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Orc 12: I'm a Villain and my name is Limburger\n", - "Orc 19: I'm a Villain and my name is Bob" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`change()` 方法可以访问 `set()` 方法,因为 `set()` 方法是 **protected**。注意到,类 **Orc** 的 `toString()` 方法也使用了基类的版本。\n", - "\n", - "\n", - "\n", - "## 向上转型\n", - "\n", - "继承最重要的方面不是为新类提供方法。它是新类与基类的一种关系。简而言之,这种关系可以表述为“新类是已有类的一种类型”。\n", - "\n", - "这种描述并非是解释继承的一种花哨方式,这是直接由语言支持的。例如,假设有一个基类 **Instrument** 代表音乐乐器和一个派生类 **Wind**。 因为继承保证了基类的所有方法在派生类中也是可用的,所以任意发送给该基类的消息也能发送给派生类。如果 **Instrument** 有一个 `play()` 方法,那么 **Wind** 也有该方法。这意味着你可以准确地说 **Wind** 对象也是一种类型的 **Instrument**。下面例子展示了编译器是如何支持这一概念的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Wind.java\n", - "// Inheritance & upcasting\n", - "class Instrument {\n", - " public void play() {}\n", - " \n", - " static void tune(Instrument i) {\n", - " // ...\n", - " i.play();\n", - " }\n", - "}\n", - "\n", - "// Wind objects are instruments\n", - "// because they have the same interface:\n", - "public class Wind extends Instrument {\n", - " public static void main(String[] args) {\n", - " Wind flute = new Wind();\n", - " Instrument.tune(flute); // Upcasting\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`tune()` 方法接受了一个 **Instrument** 类型的引用。但是,在 **Wind** 的 `main()` 方法里,`tune()` 方法却传入了一个 **Wind** 引用。鉴于 Java 对类型检查十分严格,一个接收一种类型的方法接受了另一种类型看起来很奇怪,除非你意识到 **Wind** 对象同时也是一个 **Instrument** 对象,而且 **Instrument** 的 `tune` 方法一定会存在于 **Wind** 中。在 `tune()` 中,代码对 **Instrument** 和 所有 **Instrument** 的派生类起作用,这种把 **Wind** 引用转换为 **Instrument** 引用的行为称作*向上转型*。\n", - "\n", - "该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是:\n", - "\n", - "![Wind 类图](../images/1561774164644.png)\n", - "\n", - "继承图中派生类转型为基类是向上的,所以通常称作*向上转型*。因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的。也就是说,派生类是基类的一个超集。它可能比基类包含更多的方法,但它必须至少具有与基类一样的方法。在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因。\n", - "\n", - "也可以执行与向上转型相反的向下转型,但是会有问题,对于该问题会放在下一章和“类型信息”一章进行更深入的探讨。\n", - "\n", - "### 再论组合和继承\n", - "\n", - "在面向对象编程中,创建和使用代码最有可能的方法是将数据和方法一起打包到类中,然后使用该类的对象。也可以使用已有的类通过组合来创建新类。继承其实不太常用。因此尽管在教授 OOP 的过程中我们多次强调继承,但这并不意味着要尽可能使用它。恰恰相反,尽量少使用它,除非确实使用继承是有帮助的。一种判断使用组合还是继承的最清晰的方法是问一问自己是否需要把新类向上转型为基类。如果必须向上转型,那么继承就是必要的,但如果不需要,则要进一步考虑是否该采用继承。“多态”一章提出了一个使用向上转型的最有力的理由,但是只要记住问一问“我需要向上转型吗?”,就能在这两者中作出较好的选择。\n", - "\n", - "\n", - "\n", - "## final关键字\n", - "\n", - "根据上下文环境,Java 的关键字 **final** 的含义有些微的不同,但通常它指的是“这是不能被改变的”。防止改变有两个原因:设计或效率。因为这两个原因相差很远,所以有可能误用关键字 **final**。\n", - "\n", - "以下几节讨论了可能使用 **final** 的三个地方:数据、方法和类。\n", - "\n", - "### final 数据\n", - "\n", - "许多编程语言都有某种方法告诉编译器有一块数据是恒定不变的。恒定是有用的,如:\n", - "\n", - "1. 一个永不改变的编译时常量。\n", - "2. 一个在运行时初始化就不会改变的值。\n", - "\n", - "对于编译时常量这种情况,编译器可以把常量带入计算中;也就是说,可以在编译时计算,减少了一些运行时的负担。在 Java 中,这类常量必须是基本类型,而且用关键字 **final** 修饰。你必须在定义常量的时候进行赋值。\n", - "\n", - "一个被 **static** 和 **final** 同时修饰的属性只会占用一段不能改变的存储空间。\n", - "\n", - "当用 **final** 修饰对象引用而非基本类型时,其含义会有一点令人困惑。对于基本类型,**final** 使数值恒定不变,而对于对象引用,**final** 使引用恒定不变。一旦引用被初始化指向了某个对象,它就不能改为指向其他对象。但是,对象本身是可以修改的,Java 没有提供将任意对象设为常量的方法。(你可以自己编写类达到使对象恒定不变的效果)这一限制同样适用数组,数组也是对象。\n", - "\n", - "下面例子展示了 **final** 属性的使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/FinalData.java\n", - "// The effect of final on fields\n", - "import java.util.*;\n", - "\n", - "class Value {\n", - " int i; // package access\n", - " \n", - " Value(int i) {\n", - " this.i = i;\n", - " }\n", - "}\n", - "\n", - "public class FinalData {\n", - " private static Random rand = new Random(47);\n", - " private String id;\n", - " \n", - " public FinalData(String id) {\n", - " this.id = id;\n", - " }\n", - " // Can be compile-time constants:\n", - " private final int valueOne = 9;\n", - " private static final int VALUE_TWO = 99;\n", - " // Typical public constant:\n", - " public static final int VALUE_THREE = 39;\n", - " // Cannot be compile-time constants:\n", - " private final int i4 = rand.nextInt(20);\n", - " static final int INT_5 = rand.nextInt(20);\n", - " private Value v1 = new Value(11);\n", - " private final Value v2 = new Value(22);\n", - " private static final Value VAL_3 = new Value(33);\n", - " // Arrays:\n", - " private final int[] a = {1, 2, 3, 4, 5, 6};\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return id + \": \" + \"i4 = \" + i4 + \", INT_5 = \" + INT_5;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " FinalData fd1 = new FinalData(\"fd1\");\n", - " //- fd1.valueOne++; // Error: can't change value\n", - " fd1.v2.i++; // Object isn't constant\n", - " fd1.v1 = new Value(9); // OK -- not final\n", - " for (int i = 0; i < fd1.a.length; i++) {\n", - " fd1.a[i]++; // Object isn't constant\n", - " }\n", - " //- fd1.v2 = new Value(0); // Error: Can't\n", - " //- fd1.VAL_3 = new Value(1); // change reference\n", - " //- fd1.a = new int[3];\n", - " System.out.println(fd1);\n", - " System.out.println(\"Creating new FinalData\");\n", - " FinalData fd2 = new FinalData(\"fd2\");\n", - " System.out.println(fd1);\n", - " System.out.println(fd2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fd1: i4 = 15, INT_5 = 18\n", - "Creating new FinalData\n", - "fd1: i4 = 15, INT_5 = 18\n", - "fd2: i4 = 13, INT_5 = 18" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 **valueOne** 和 **VALUE_TWO** 都是带有编译时值的 **final** 基本类型,它们都可用作编译时常量,没有多大区别。**VALUE_THREE** 是一种更加典型的常量定义的方式:**public** 意味着可以在包外访问,**static** 强调只有一个,**final** 说明是一个常量。\n", - "\n", - "按照惯例,带有恒定初始值的 **final** **static** 基本变量(即编译时常量)命名全部使用大写,单词之间用下划线分隔。(源于 C 语言中定义常量的方式。)\n", - "\n", - "我们不能因为某数据被 **final** 修饰就认为在编译时可以知道它的值。由上例中的 **i4** 和 **INT_5** 可以看出,它们在运行时才会赋值随机数。示例部分也展示了将 **final** 值定义为 **static** 和非 **static** 的区别。此区别只有当值在运行时被初始化时才会显现,因为编译器对编译时数值一视同仁。(而且编译时数值可能因优化而消失。)当运行程序时就能看到这个区别。注意到 **fd1** 和 **fd2** 的 **i4** 值不同,但 **INT_5** 的值并没有因为创建了第二个 **FinalData** 对象而改变,这是因为它是 **static** 的,在加载时已经被初始化,并不是每次创建新对象时都初始化。\n", - "\n", - "**v1** 到 **VAL_3** 变量说明了 **final** 引用的意义。正如你在 `main()` 中所见,**v2** 是 **final** 的并不意味着你不能修改它的值。因为它是引用,所以只是说明它不能指向一个新的对象。这对于数组具有同样的意义,数组只不过是另一种引用。(我不知道有什么方法能使数组引用本身成为 **final**。)看起来,声明引用为 **final** 没有声明基本类型 **final** 有用。\n", - "\n", - "### 空白 final\n", - "\n", - "空白 final 指的是没有初始化值的 **final** 属性。编译器确保空白 final 在使用前必须被初始化。这样既能使一个类的每个对象的 **final** 属性值不同,也能保持它的不变性。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/BlankFinal.java\n", - "// \"Blank\" final fields\n", - "class Poppet {\n", - " private int i;\n", - " \n", - " Poppet(int ii) {\n", - " i = ii;\n", - " }\n", - "}\n", - "\n", - "public class BlankFinal {\n", - " private final int i = 0; // Initialized final\n", - " private final int j; // Blank final\n", - " private final Poppet p; // Blank final reference\n", - " // Blank finals MUST be initialized in constructor\n", - " public BlankFinal() {\n", - " j = 1; // Initialize blank final\n", - " p = new Poppet(1); // Init blank final reference\n", - " }\n", - " \n", - " public BlankFinal(int x) {\n", - " j = x; // Initialize blank final\n", - " p = new Poppet(x); // Init blank final reference\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " new BlankFinal();\n", - " new BlankFinal(47);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你必须在定义时或在每个构造器中执行 final 变量的赋值操作。这保证了 final 属性在使用前已经被初始化过。\n", - "\n", - "### final 参数\n", - "\n", - "在参数列表中,将参数声明为 final 意味着在方法中不能改变参数指向的对象或基本变量:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/FinalArguments.java\n", - "// Using \"final\" with method arguments\n", - "class Gizmo {\n", - " public void spin() {\n", - " \n", - " }\n", - "}\n", - "\n", - "public class FinalArguments {\n", - " void with(final Gizmo g) {\n", - " //-g = new Gizmo(); // Illegal -- g is final\n", - " }\n", - " \n", - " void without(Gizmo g) {\n", - " g = new Gizmo(); // OK -- g is not final\n", - " g.spin();\n", - " }\n", - " \n", - " //void f(final int i) { i++; } // Can't change\n", - " // You can only read from a final primitive\n", - " int g(final int i) {\n", - " return i + 1;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " FinalArguments bf = new FinalArguments();\n", - " bf.without(null);\n", - " bf.with(null);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "方法 `f()` 和 `g()` 展示了 **final** 基本类型参数的使用情况。你只能读取而不能修改参数。这个特性主要用于传递数据给匿名内部类。这将在”内部类“章节中详解。\n", - "\n", - "### final 方法\n", - "\n", - "使用 **final** 方法的原因有两个。第一个原因是给方法上锁,防止子类通过覆写改变方法的行为。这是出于继承的考虑,确保方法的行为不会因继承而改变。\n", - "\n", - "过去建议使用 **final** 方法的第二个原因是效率。在早期的 Java 实现中,如果将一个方法指明为 **final**,就是同意编译器把对该方法的调用转化为内嵌调用。当编译器遇到 **final** 方法的调用时,就会很小心地跳过普通的插入代码以执行方法的调用机制(将参数压栈,跳至方法代码处执行,然后跳回并清理栈中的参数,最终处理返回值),而用方法体内实际代码的副本替代方法调用。这消除了方法调用的开销。但是如果一个方法很大代码膨胀,你也许就看不到内嵌带来的性能提升,因为内嵌调用带来的性能提高被花费在方法里的时间抵消了。\n", - "\n", - "在最近的 Java 版本中,虚拟机可以探测到这些情况(尤其是 *hotspot* 技术),并优化去掉这些效率反而降低的内嵌调用方法。有很长一段时间,使用 **final** 来提高效率都被阻止。你应该让编译器和 JVM 处理性能问题,只有在为了明确禁止覆写方法时才使用 **final**。\n", - "\n", - "### final 和 private\n", - "\n", - "类中所有的 **private** 方法都隐式地指定为 **final**。因为不能访问 **private** 方法,所以不能覆写它。可以给 **private** 方法添加 **final** 修饰,但是并不能给方法带来额外的含义。\n", - "\n", - "以下情况会令人困惑,当你试图覆写一个 **private** 方法(隐式是 **final** 的)时,看上去奏效,而且编译器不会给出错误信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/FinalOverridingIllusion.java\n", - "// It only looks like you can override\n", - "// a private or private final method\n", - "class WithFinals {\n", - " // Identical to \"private\" alone:\n", - " private final void f() {\n", - " System.out.println(\"WithFinals.f()\");\n", - " }\n", - " // Also automatically \"final\":\n", - " private void g() {\n", - " System.out.println(\"WithFinals.g()\");\n", - " }\n", - "}\n", - "\n", - "class OverridingPrivate extends WithFinals {\n", - " private final void f() {\n", - " System.out.println(\"OverridingPrivate.f()\");\n", - " }\n", - " \n", - " private void g() {\n", - " System.out.println(\"OverridingPrivate.g()\");\n", - " }\n", - "}\n", - "\n", - "class OverridingPrivate2 extends OverridingPrivate {\n", - " public final void f() {\n", - " System.out.println(\"OverridingPrivate2.f()\");\n", - " } \n", - " \n", - " public void g() {\n", - " System.out.println(\"OverridingPrivate2.g()\");\n", - " }\n", - "}\n", - "\n", - "public class FinalOverridingIllusion {\n", - " public static void main(String[] args) {\n", - " OverridingPrivate2 op2 = new OverridingPrivate2();\n", - " op2.f();\n", - " op2.g();\n", - " // You can upcast:\n", - " OverridingPrivate op = op2;\n", - " // But you can't call the methods:\n", - " //- op.f();\n", - " //- op.g();\n", - " // Same here:\n", - " WithFinals wf = op2;\n", - " //- wf.f();\n", - " //- wf.g();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "OverridingPrivate2.f()\n", - "OverridingPrivate2.g()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"覆写\"只发生在方法是基类的接口时。也就是说,必须能将一个对象向上转型为基类并调用相同的方法(这一点在下一章阐明)。如果一个方法是 **private** 的,它就不是基类接口的一部分。它只是隐藏在类内部的代码,且恰好有相同的命名而已。但是如果你在派生类中以相同的命名创建了 **public**,**protected** 或包访问权限的方法,这些方法与基类中的方法没有联系,你没有覆写方法,只是在创建新的方法而已。由于 **private** 方法无法触及且能有效隐藏,除了把它看作类中的一部分,其他任何事物都不需要考虑到它。\n", - "\n", - "### final 类\n", - "\n", - "当说一个类是 **final** (**final** 关键字在类定义之前),就意味着它不能被继承。之所以这么做,是因为类的设计就是永远不需要改动,或者是出于安全考虑不希望它有子类。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Jurassic.java\n", - "// Making an entire class final\n", - "class SmallBrain {}\n", - "\n", - "final class Dinosaur {\n", - " int i = 7;\n", - " int j = 1;\n", - " SmallBrain x = new SmallBrain();\n", - " \n", - " void f() {}\n", - "}\n", - "\n", - "//- class Further extends Dinosaur {}\n", - "// error: Cannot extend final class 'Dinosaur'\n", - "public class Jurassic {\n", - " public static void main(String[] args) {\n", - " Dinosaur n = new Dinosaur();\n", - " n.f();\n", - " n.i = 40;\n", - " n.j++;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。\n", - "\n", - "### final 忠告\n", - "\n", - "在设计类时将一个方法指明为 final 看上去是明智的。你可能会觉得没人会覆写那个方法。有时这是对的。\n", - "\n", - "但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。\n", - "\n", - "Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,而且从效率考虑(这近乎是个幻想),如果它的所有方法没有被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。\n", - "\n", - "第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。\n", - "\n", - "Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。\n", - "\n", - "\n", - "\n", - "## 类初始化和加载\n", - "\n", - "在许多传统语言中,程序在启动时一次性全部加载。接着初始化,然后程序开始运行。必须仔细控制这些语言的初始化过程,以确保 **statics** 初始化的顺序不会造成麻烦。在 C++ 中,如果一个 **static** 期望使用另一个 **static**,而另一个 **static** 还没有初始化,就会出现问题。\n", - "\n", - "Java 中不存在这样的问题,因为它采用了一种不同的方式加载。因为 Java 中万物皆对象,所以加载活动就容易得多。记住每个类的编译代码都存在于它自己独立的文件中。该文件只有在使用程序代码时才会被加载。一般可以说“类的代码在首次使用时加载“。这通常是指创建类的第一个对象,或者是访问了类的 **static** 属性或方法。构造器也是一个 **static** 方法尽管它的 **static** 关键字是隐式的。因此,准确地说,一个类当它任意一个 **static** 成员被访问时,就会被加载。\n", - "\n", - "首次使用时就是 **static** 初始化发生时。所有的 **static** 对象和 **static** 代码块在加载时按照文本的顺序(在类中定义的顺序)依次初始化。**static** 变量只被初始化一次。\n", - "\n", - "### 继承和初始化\n", - "\n", - "了解包括继承在内的整个初始化过程是有帮助的,这样可以对所发生的一切有全局性的把握。考虑下面的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// reuse/Beetle.java\n", - "// The full process of initialization\n", - "class Insect {\n", - " private int i = 9;\n", - " protected int j;\n", - " \n", - " Insect() {\n", - " System.out.println(\"i = \" + i + \", j = \" + j);\n", - " j = 39;\n", - " }\n", - " \n", - " private static int x1 = printInit(\"static Insect.x1 initialized\");\n", - " \n", - " static int printInit(String s) {\n", - " System.out.println(s);\n", - " return 47;\n", - " }\n", - "}\n", - "\n", - "public class Beetle extends Insect {\n", - " private int k = printInit(\"Beetle.k.initialized\");\n", - " \n", - " public Beetle() {\n", - " System.out.println(\"k = \" + k);\n", - " System.out.println(\"j = \" + j);\n", - " }\n", - " \n", - " private static int x2 = printInit(\"static Beetle.x2 initialized\");\n", - " \n", - " public static void main(String[] args) {\n", - " System.out.println(\"Beetle constructor\");\n", - " Beetle b = new Beetle();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "static Insect.x1 initialized\n", - "static Beetle.x2 initialized\n", - "Beetle constructor\n", - "i = 9, j = 0\n", - "Beetle.k initialized\n", - "k = 47\n", - "j = 39" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当执行 **java Beetle**,首先会试图访问 **Beetle** 类的 `main()` 方法(一个静态方法),加载器启动并找出 **Beetle** 类的编译代码(在名为 **Beetle.class** 的文件中)。在加载过程中,编译器注意到有一个基类,于是继续加载基类。不论是否创建了基类的对象,基类都会被加载。(可以尝试把创建基类对象的代码注释掉证明这点。)\n", - "\n", - "如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 **Insect**)的 **static** 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 **static** 的初始化可能依赖基类成员是否被正确地初始化。\n", - "\n", - "至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "继承和组合都是从已有类型创建新类型。组合将已有类型作为新类型底层实现的一部分,继承复用的是接口。\n", - "\n", - "使用继承时,派生类具有基类接口,因此可以向上转型为基类,这对于多态至关重要,在下一章你将看到。\n", - "\n", - "尽管在面向对象编程时极力强调继承,但在开始设计时,优先使用组合(或委托),只有当确实需要时再使用继承。组合更具灵活性。另外,通过对成员类型使用继承的技巧,可以在运行时改变成员的类型和行为。因此,可以在运行时改变组合对象的行为。\n", - "\n", - "在设计一个系统时,目标是发现或创建一系列类,每个类有特定的用途,而且既不应太大(包括太多功能难以复用),也不应太小(不添加其他功能就无法使用)。如果设计变得过于复杂,通过将现有类拆分为更小的部分而添加更多的对象,通常是有帮助的。\n", - "\n", - "当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/09-Polymorphism.ipynb b/jupyter/09-Polymorphism.ipynb deleted file mode 100644 index f06488fb..00000000 --- a/jupyter/09-Polymorphism.ipynb +++ /dev/null @@ -1,1809 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第九章 多态\n", - "\n", - "> 曾经有人请教我 “ Babbage 先生,如果输入错误的数字到机器中,会得出正确结果吗?” 我无法理解产生如此问题的概念上的困惑。 —— Charles Babbage (1791 - 1871)\n", - "\n", - "多态是面向对象编程语言中,继数据抽象和继承之外的第三个重要特性。\n", - "\n", - "多态提供了另一个维度的接口与实现分离,以解耦做什么和怎么做。多态不仅能改善代码的组织,提高代码的可读性,而且能创建有扩展性的程序——无论在最初创建项目时还是在添加新特性时都可以“生长”的程序。\n", - "\n", - "封装通过合并特征和行为来创建新的数据类型。隐藏实现通过将细节**私有化**把接口与实现分离。这种类型的组织机制对于有面向过程编程背景的人来说,更容易理解。而多态是消除类型之间的耦合。在上一章中,继承允许把一个对象视为它本身的类型或它的基类类型。这样就能把很多派生自一个基类的类型当作同一类型处理,因而一段代码就可以无差别地运行在所有不同的类型上了。多态方法调用允许一种类型表现出与相似类型的区别,只要这些类型派生自一个基类。这种区别是当你通过基类调用时,由方法的不同行为表现出来的。\n", - "\n", - "在本章中,通过一些基本、简单的例子(这些例子中只保留程序中与多态有关的行为),你将逐步学习多态(也称为*动态绑定*或*后期绑定*或*运行时绑定*)。\n", - "\n", - "\n", - "\n", - "## 向上转型回顾\n", - "\n", - "在上一章中,你看到了如何把一个对象视作它的自身类型或它的基类类型。这种把一个对象引用当作它的基类引用的做法称为向上转型,因为继承图中基类一般都位于最上方。\n", - "\n", - "同样你也在下面的音乐乐器例子中发现了问题。即然几个例子都要演奏乐符(**Note**),首先我们先在包中单独创建一个 Note 枚举类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/music/Note.java\n", - "// Notes to play on musical instruments\n", - "package polymorphism.music;\n", - "\n", - "public enum Note {\n", - " MIDDLE_C, C_SHARP, B_FLAT; // Etc.\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "枚举已经在”第 6 章初始化和清理“一章中介绍过了。\n", - "\n", - "这里,**Wind** 是一种 **Instrument**;因此,**Wind** 继承 **Instrument**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/music/Instrument.java\n", - "package polymorphism.music;\n", - "\n", - "class Instrument {\n", - " public void play(Note n) {\n", - " System.out.println(\"Instrument.play()\");\n", - " }\n", - "}\n", - "\n", - "// polymorphism/music/Wind.java\n", - "package polymorphism.music;\n", - "// Wind objects are instruments\n", - "// because they have the same interface:\n", - "public class Wind extends Instrument {\n", - " // Redefine interface method:\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Wind.play() \" + n);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Music** 的方法 `tune()` 接受一个 **Instrument** 引用,同时也接受任何派生自 **Instrument** 的类引用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/music/Music.java\n", - "// Inheritance & upcasting\n", - "// {java polymorphism.music.Music}\n", - "package polymorphism.music;\n", - "\n", - "public class Music {\n", - " public static void tune(Instrument i) {\n", - " // ...\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Wind flute = new Wind();\n", - " tune(flute); // Upcasting\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Wind.play() MIDDLE_C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `main()` 中你看到了 `tune()` 方法传入了一个 **Wind** 引用,而没有做类型转换。这样做是允许的—— **Instrument** 的接口一定存在于 **Wind** 中,因此 **Wind** 继承了 **Instrument**。从 **Wind** 向上转型为 **Instrument** 可能“缩小”接口,但不会比 **Instrument** 的全部接口更少。\n", - "\n", - "### 忘掉对象类型\n", - "\n", - "**Music.java** 看起来似乎有点奇怪。为什么所有人都故意忘记掉对象类型呢?当向上转型时,就会发生这种情况,而且看起来如果 `tune()` 接受的参数是一个 **Wind** 引用会更为直观。这会带来一个重要问题:如果你那么做,就要为系统内 **Instrument** 的每种类型都编写一个新的 `tune()` 方法。假设按照这种推理,再增加 **Stringed** 和 **Brass** 这两种 **Instrument** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/music/Music2.java\n", - "// Overloading instead of upcasting\n", - "// {java polymorphism.music.Music2}\n", - "package polymorphism.music;\n", - "\n", - "class Stringed extends Instrument {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Stringed.play() \" + n);\n", - " }\n", - "}\n", - "\n", - "class Brass extends Instrument {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Brass.play() \" + n);\n", - " }\n", - "}\n", - "\n", - "public class Music2 {\n", - " public static void tune(Wind i) {\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " public static void tune(Stringed i) {\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " public static void tune(Brass i) {\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Wind flute = new Wind();\n", - " Stringed violin = new Stringed();\n", - " Brass frenchHorn = new Brass();\n", - " tune(flute); // No upcasting\n", - " tune(violin);\n", - " tune(frenchHorn);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Wind.play() MIDDLE_C\n", - "Stringed.play() MIDDLE_C\n", - "Brass.play() MIDDLE_C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这样行得通,但是有一个主要缺点:必须为添加的每个新 **Instrument** 类编写特定的方法。这意味着开始时就需要更多的编程,而且以后如果添加类似 `tune()` 的新方法或 **Instrument** 的新类型时,还有大量的工作要做。考虑到如果你忘记重载某个方法,编译器也不会提示你,这会造成类型的整个处理过程变得难以管理。\n", - "\n", - "如果只写一个方法以基类作为参数,而不用管是哪个具体派生类,这样会变得更好吗?也就是说,如果忘掉派生类,编写的代码只与基类打交道,会不会更好呢?\n", - "\n", - "这正是多态所允许的。但是大部分拥有面向过程编程背景的程序员会对多态的运作方式感到一些困惑。\n", - "\n", - "\n", - "\n", - "## 转机\n", - "\n", - "运行程序后会看到 **Music.java** 的难点。**Wind.play()** 的输出结果正是我们期望的,然而它看起来似乎不应该得出这样的结果。观察 `tune()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public static void tune(Instrument i) {\n", - " // ...\n", - " i.play(Note.MIDDLE_C);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它接受一个 **Instrument** 引用。那么编译器是如何知道这里的 **Instrument** 引用指向的是 **Wind**,而不是 **Brass** 或 **Stringed** 呢?编译器无法得知。为了深入理解这个问题,有必要研究一下*绑定*这个主题。\n", - "\n", - "### 方法调用绑定\n", - "\n", - "将一个方法调用和一个方法主体关联起来称作*绑定*。若绑定发生在程序运行前(如果有的话,由编译器和链接器实现),叫做*前期绑定*。你可能从来没有听说这个术语,因为它是面向过程语言不需选择默认的绑定方式,例如在 C 语言中就只有*前期绑定*这一种方法调用。\n", - "\n", - "上述程序让人困惑的地方就在于前期绑定,因为编译器只知道一个 **Instrument** 引用,它无法得知究竟会调用哪个方法。\n", - "\n", - "解决方法就是*后期绑定*,意味着在运行时根据对象的类型进行绑定。后期绑定也称为*动态绑定*或*运行时绑定*。当一种语言实现了后期绑定,就必须具有某种机制在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器仍然不知道对象的类型,但是方法调用机制能找到正确的方法体并调用。每种语言的后期绑定机制都不同,但是可以想到,对象中一定存在某种类型信息。\n", - "\n", - "Java 中除了 **static** 和 **final** 方法(**private** 方法也是隐式的 **final**)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。\n", - "\n", - "为什么将一个对象指明为 **final** ?正如前一章所述,它可以防止方法被重写。但更重要的一点可能是,它有效地”关闭了“动态绑定,或者说告诉编译器不需要对其进行动态绑定。这可以让编译器为 **final** 方法生成更高效的代码。然而,大部分情况下这样做不会对程序的整体性能带来什么改变,因此最好是为了设计使用 **final**,而不是为了提升性能而使用。\n", - "\n", - "### 产生正确的行为\n", - "\n", - "一旦当你知道 Java 中所有方法都是通过后期绑定来实现多态时,就可以编写只与基类打交道的代码,而且代码对于派生类来说都能正常地工作。或者换种说法,你向对象发送一条消息,让对象自己做正确的事。\n", - "\n", - "面向对象编程中的经典例子是形状 **Shape**。这个例子很直观,但不幸的是,它可能让初学者困惑,认为面向对象编程只适合图形化程序设计,实际上不是这样。\n", - "\n", - "形状的例子中,有一个基类称为 **Shape** ,多个不同的派生类型分别是:**Circle**,**Square**,**Triangle** 等等。这个例子之所以好用,是因为我们可以直接说“圆(Circle)是一种形状(Shape)”,这很容易理解。继承图展示了它们之间的关系:\n", - "\n", - "![形状继承图](../images/1562204648023.png)\n", - "\n", - "向上转型就像下面这么简单:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Shape s = new Circle();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这会创建一个 **Circle** 对象,引用被赋值给 **Shape** 类型的变量 s,这看似错误(将一种类型赋值给另一种类型),然而是没问题的,因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。\n", - "\n", - "假设你调用了一个基类方法(在各个派生类中都被重写):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "s.draw()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可能再次认为 **Shape** 的 `draw()` 方法被调用,因为 s 是一个 **Shape** 引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是 **Circle** 的 `draw()` 方法,这是正确的。\n", - "\n", - "下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 为它的所有子类建立了公共接口——所有的形状都可以被绘画和擦除:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/shape/Shape.java\n", - "package polymorphism.shape;\n", - "\n", - "public class Shape {\n", - " public void draw() {}\n", - " public void erase() {}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "派生类通过重写这些方法为每个具体的形状提供独一无二的方法行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/shape/Circle.java\n", - "package polymorphism.shape;\n", - "\n", - "public class Circle extends Shape {\n", - " @Override\n", - " public void draw() {\n", - " System.out.println(\"Circle.draw()\");\n", - " }\n", - " @Override\n", - " public void erase() {\n", - " System.out.println(\"Circle.erase()\");\n", - " }\n", - "}\n", - "\n", - "// polymorphism/shape/Square.java\n", - "package polymorphism.shape;\n", - "\n", - "public class Square extends Shape {\n", - " @Override\n", - " public void draw() {\n", - " System.out.println(\"Square.draw()\");\n", - " }\n", - " @Override\n", - " public void erase() {\n", - " System.out.println(\"Square.erase()\");\n", - " }\n", - " }\n", - "\n", - "// polymorphism/shape/Triangle.java\n", - "package polymorphism.shape;\n", - "\n", - "public class Triangle extends Shape {\n", - " @Override\n", - " public void draw() {\n", - " System.out.println(\"Triangle.draw()\");\n", - " }\n", - " @Override\n", - " public void erase() {\n", - " System.out.println(\"Triangle.erase()\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**RandomShapes** 是一种工厂,每当我们调用 `get()` 方法时,就会产生一个指向随机创建的 **Shape** 对象的引用。注意,向上转型发生在 **return** 语句中,每条 **return** 语句取得一个指向某个 **Circle**,**Square** 或 **Triangle** 的引用, 并将其以 **Shape** 类型从 `get()` 方法发送出去。因此无论何时调用 `get()` 方法,你都无法知道具体的类型是什么,因为你总是得到一个简单的 **Shape** 引用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/shape/RandomShapes.java\n", - "// A \"factory\" that randomly creates shapes\n", - "package polymorphism.shape;\n", - "import java.util.*;\n", - "\n", - "public class RandomShapes {\n", - " private Random rand = new Random(47);\n", - " \n", - " public Shape get() {\n", - " switch(rand.nextInt(3)) {\n", - " default:\n", - " case 0: return new Circle();\n", - " case 1: return new Square();\n", - " case 2: return new Triangle();\n", - " }\n", - " }\n", - " \n", - " public Shape[] array(int sz) {\n", - " Shape[] shapes = new Shape[sz];\n", - " // Fill up the array with shapes:\n", - " for (int i = 0; i < shapes.length; i++) {\n", - " shapes[i] = get();\n", - " }\n", - " return shapes;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`array()` 方法分配并填充了 **Shape** 数组,这里使用了 for-in 表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/Shapes.java\n", - "// Polymorphism in Java\n", - "import polymorphism.shape.*;\n", - "\n", - "public class Shapes {\n", - " public static void main(String[] args) {\n", - " RandomShapes gen = new RandomShapes();\n", - " // Make polymorphic method calls:\n", - " for (Shape shape: gen.array(9)) {\n", - " shape.draw();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Triangle.draw()\n", - "Triangle.draw()\n", - "Square.draw()\n", - "Triangle.draw()\n", - "Square.draw()\n", - "Triangle.draw()\n", - "Square.draw()\n", - "Triangle.draw()\n", - "Circle.draw()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`main()` 方法中包含了一个 **Shape** 引用组成的数组,其中每个元素通过调用 **RandomShapes** 类的 `get()` 方法生成。现在你只知道拥有一些形状,但除此之外一无所知(编译器也是如此)。然而当遍历这个数组为每个元素调用 `draw()` 方法时,从运行程序的结果中可以看到,与类型有关的特定行为奇迹般地发生了。\n", - "\n", - "随机生成形状是为了让大家理解:在编译时,编译器不需要知道任何具体信息以进行正确的调用。所有对方法 `draw()` 的调用都是通过动态绑定进行的。\n", - "\n", - "### 可扩展性\n", - "\n", - "现在让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 `tune()` 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 `tune()` 的模型,只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。\n", - "\n", - "考虑一下乐器的例子,如果在基类中添加更多的方法,并加入一些新类,将会发生什么呢:\n", - "\n", - "![乐器继承图](../images/1562252767216.png)\n", - "\n", - "所有的新类都可以和原有类正常运行,不需要改动 `tune()` 方法。即使 `tune()` 方法单独存放在某个文件中,而且向 **Instrument** 接口中添加了新的方法,`tune()` 方法也无需再编译就能正确运行。下面是类图的实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/music3/Music3.java\n", - "// An extensible program\n", - "// {java polymorphism.music3.Music3}\n", - "package polymorphism.music3;\n", - "import polymorphism.music.Note;\n", - "\n", - "class Instrument {\n", - " void play(Note n) {\n", - " System.out.println(\"Instrument.play() \" + n);\n", - " }\n", - " \n", - " String what() {\n", - " return \"Instrument\";\n", - " }\n", - " \n", - " void adjust() {\n", - " System.out.println(\"Adjusting Instrument\");\n", - " }\n", - "}\n", - "\n", - "class Wind extends Instrument {\n", - " @Override\n", - " void play(Note n) {\n", - " System.out.println(\"Wind.play() \" + n);\n", - " }\n", - " @Override\n", - " String what() {\n", - " return \"Wind\";\n", - " }\n", - " @Override\n", - " void adjust() {\n", - " System.out.println(\"Adjusting Wind\");\n", - " }\n", - "}\n", - "\n", - "class Percussion extends Instrument {\n", - " @Override\n", - " void play(Note n) {\n", - " System.out.println(\"Percussion.play() \" + n);\n", - " }\n", - " @Override\n", - " String what() {\n", - " return \"Percussion\";\n", - " }\n", - " @Override\n", - " void adjust() {\n", - " System.out.println(\"Adjusting Percussion\");\n", - " }\n", - "}\n", - "\n", - "class Stringed extends Instrument {\n", - " @Override\n", - " void play(Note n) {\n", - " System.out.println(\"Stringed.play() \" + n);\n", - " } \n", - " @Override\n", - " String what() {\n", - " return \"Stringed\";\n", - " }\n", - " @Override\n", - " void adjust() {\n", - " System.out.println(\"Adjusting Stringed\");\n", - " }\n", - "}\n", - "\n", - "class Brass extends Wind {\n", - " @Override\n", - " void play(Note n) {\n", - " System.out.println(\"Brass.play() \" + n);\n", - " }\n", - " @Override\n", - " void adjust() {\n", - " System.out.println(\"Adjusting Brass\");\n", - " }\n", - "}\n", - "\n", - "class Woodwind extends Wind {\n", - " @Override\n", - " void play(Note n) {\n", - " System.out.println(\"Woodwind.play() \" + n);\n", - " }\n", - " @Override\n", - " String what() {\n", - " return \"Woodwind\";\n", - " }\n", - "}\n", - "\n", - "public class Music3 {\n", - " // Doesn't care about type, so new types\n", - " // added to the system still work right:\n", - " public static void tune(Instrument i) {\n", - " // ...\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " public static void tuneAll(Instrument[] e) {\n", - " for (Instrument i: e) {\n", - " tune(i);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " // Upcasting during addition to the array:\n", - " Instrument[] orchestra = {\n", - " new Wind(),\n", - " new Percussion(),\n", - " new Stringed(),\n", - " new Brass(),\n", - " new Woodwind()\n", - " };\n", - " tuneAll(orchestra);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Wind.play() MIDDLE_C\n", - "Percussion.play() MIDDLE_C\n", - "Stringed.play() MIDDLE_C\n", - "Brass.play() MIDDLE_C\n", - "Woodwind.play() MIDDLE_C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "新方法 `what()` 返回一个带有类描述的 **String** 引用,`adjust()` 提供一些乐器调音的方法。\n", - "\n", - "在 `main()` 方法中,当向 **orchestra** 数组添加元素时,元素会自动向上转型为 **Instrument**。\n", - "\n", - "`tune()` 方法可以忽略周围所有代码发生的变化,仍然可以正常运行。这正是我们期待多态能提供的特性。代码中的修改不会破坏程序中其他不应受到影响的部分。换句话说,多态是一项“将改变的事物与不变的事物分离”的重要技术。\n", - "\n", - "### 陷阱:“重写”私有方法\n", - "\n", - "你可能天真地试图像下面这样做:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/PrivateOverride.java\n", - "// Trying to override a private method\n", - "// {java polymorphism.PrivateOverride}\n", - "package polymorphism;\n", - "\n", - "public class PrivateOverride {\n", - " private void f() {\n", - " System.out.println(\"private f()\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " PrivateOverride po = new Derived();\n", - " po.f();\n", - " }\n", - "}\n", - "\n", - "public Derived extends PrivateOverride {\n", - " public void f() {\n", - " System.out.println(\"public f()\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "private f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可能期望输出是 **public f()**,然而 **private** 方法可以当作是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。\n", - "\n", - "结论是只有非 **private** 方法才能被重写,但是得小心重写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。\n", - "\n", - "如果使用了 `@Override` 注解,就能检测出问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/PrivateOverride2.java\n", - "// Detecting a mistaken override using @Override\n", - "// {WillNotCompile}\n", - "package polymorphism;\n", - "\n", - "public class PrivateOverride2 {\n", - " private void f() {\n", - " System.out.println(\"private f()\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " PrivateOverride2 po = new Derived2();\n", - " po.f();\n", - " }\n", - "}\n", - "\n", - "class Derived2 extends PrivateOverride2 {\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"public f()\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器报错信息是:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "error: method does not override or\n", - "implement a method from a supertype" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 陷阱:属性与静态方法\n", - "\n", - "一旦学会了多态,就可以以多态的思维方式考虑每件事。然而,只有普通的方法调用可以是多态的。例如,如果你直接访问一个属性,该访问会在编译时解析:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/FieldAccess.java\n", - "// Direct field access is determined at compile time\n", - "class Super {\n", - " public int field = 0;\n", - " \n", - " public int getField() {\n", - " return field;\n", - " }\n", - "}\n", - "\n", - "class Sub extends Super {\n", - " public int field = 1;\n", - " \n", - " @Override\n", - " public int getField() {\n", - " return field;\n", - " }\n", - " \n", - " public int getSuperField() {\n", - " return super.field;\n", - " }\n", - "}\n", - "\n", - "public class FieldAccess {\n", - " public static void main(String[] args) {\n", - " Super sup = new Sub(); // Upcast\n", - " System.out.println(\"sup.field = \" + sup.field + \n", - " \", sup.getField() = \" + sup.getField());\n", - " Sub sub = new Sub();\n", - " System.out.println(\"sub.field = \" + sub.field + \n", - " \", sub.getField() = \" + sub.getField()\n", - " + \", sub.getSuperField() = \" + sub.getSuperField())\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sup.field = 0, sup.getField() = 1\n", - "sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当 **Sub** 对象向上转型为 **Super** 引用时,任何属性访问都被编译器解析,因此不是多态的。在这个例子中,**Super.field** 和 **Sub.field** 被分配了不同的存储空间,因此,**Sub** 实际上包含了两个称为 **field** 的属性:它自己的和来自 **Super** 的。然而,在引用 **Sub** 的 **field** 时,默认的 **field** 属性并不是 **Super** 版本的 **field** 属性。为了获取 **Super** 的 **field** 属性,需要显式地指明 **super.field**。\n", - "\n", - "尽管这看起来是个令人困惑的问题,实际上基本不会发生。首先,通常会将所有的属性都指明为 **private**,因此不能直接访问它们,只能通过方法来访问。此外,你可能也不会给基类属性和派生类属性起相同的名字,这样做会令人困惑。\n", - "\n", - "如果一个方法是静态(**static**)的,它的行为就不具有多态性:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/StaticPolymorphism.java\n", - "// static methods are not polymorphic\n", - "class StaticSuper {\n", - " public static String staticGet() {\n", - " return \"Base staticGet()\";\n", - " }\n", - " \n", - " public String dynamicGet() {\n", - " return \"Base dynamicGet()\";\n", - " }\n", - "}\n", - "\n", - "class StaticSub extends StaticSuper {\n", - " public static String staticGet() {\n", - " return \"Derived staticGet()\";\n", - " }\n", - " @Override\n", - " public String dynamicGet() {\n", - " return \"Derived dynamicGet()\";\n", - " }\n", - "}\n", - "\n", - "public class StaticPolymorphism {\n", - " public static void main(String[] args) {\n", - " StaticSuper sup = new StaticSub(); // Upcast\n", - " System.out.println(StaticSuper.staticGet());\n", - " System.out.println(sup.dynamicGet());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Base staticGet()\n", - "Derived dynamicGet()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "静态的方法只与类关联,与单个的对象无关。\n", - "\n", - "\n", - "\n", - "## 构造器和多态\n", - "\n", - "通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。这个理解可以帮助你避免一些不愉快的困扰。\n", - "\n", - "### 构造器调用顺序\n", - "\n", - "在“初始化和清理”和“复用”两章中已经简单地介绍过构造器的调用顺序,但那时还没有介绍多态。\n", - "\n", - "在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。这么做是有意义的,因为构造器有着特殊的任务:检查对象是否被正确地构造。由于属性通常声明为 **private**,你必须假定派生类只能访问自己的成员而不能访问基类的成员。只有基类的构造器拥有恰当的知识和权限来初始化自身的元素。因此,必须得调用所有构造器;否则就不能构造完整的对象。这就是为什么编译器会强制调用每个派生类中的构造器的原因。如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)。\n", - "\n", - "下面的例子展示了组合、继承和多态在构建顺序上的作用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/Sandwich.java\n", - "// Order of constructor calls\n", - "// {java polymorphism.Sandwich}\n", - "package polymorphism;\n", - "\n", - "class Meal {\n", - " Meal() {\n", - " System.out.println(\"Meal()\");\n", - " }\n", - "}\n", - "\n", - "class Bread {\n", - " Bread() {\n", - " System.out.println(\"Bread()\");\n", - " }\n", - "}\n", - "\n", - "class Cheese {\n", - " Cheese() {\n", - " System.out.println(\"Cheese()\");\n", - " }\n", - "}\n", - "\n", - "class Lettuce {\n", - " Lettuce() {\n", - " System.out.println(\"Lettuce()\");\n", - " }\n", - "}\n", - "\n", - "class Lunch extends Meal {\n", - " Lunch() {\n", - " System.out.println(\"Lunch()\");\n", - " }\n", - "}\n", - "\n", - "class PortableLunch extends Lunch {\n", - " PortableLunch() {\n", - " System.out.println(\"PortableLunch()\");\n", - " }\n", - "}\n", - "\n", - "public class Sandwich extends PortableLunch {\n", - " private Bread b = new Bread();\n", - " private Cheese c = new Cheese();\n", - " private Lettuce l = new Lettuce();\n", - " \n", - " public Sandwich() {\n", - " System.out.println(\"Sandwich()\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " new Sandwich();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Meal()\n", - "Lunch()\n", - "PortableLunch()\n", - "Bread()\n", - "Cheese()\n", - "Lettuce()\n", - "Sandwich()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个例子用其他类创建了一个复杂的类。每个类都在构造器中声明自己。重要的类是 **Sandwich**,它反映了三层继承(如果算上 **Object** 的话,就是四层),包含了三个成员对象。\n", - "\n", - "从创建 **Sandwich** 对象的输出中可以看出对象的构造器调用顺序如下:\n", - "\n", - "1. 基类构造器被调用。这个步骤被递归地重复,这样一来类层次的顶级父类会被最先构造,然后是它的派生类,以此类推,直到最底层的派生类。\n", - "2. 按声明顺序初始化成员。\n", - "3. 调用派生类构造器的方法体。\n", - "\n", - "构造器的调用顺序很重要。当使用继承时,就已经知道了基类的一切,并可以访问基类中任意 **public** 和 **protected** 的成员。这意味着在派生类中可以假定所有的基类成员都是有效的。在一个标准方法中,构造动作已经发生过,对象其他部分的所有成员都已经创建好。\n", - "\n", - "在构造器中必须确保所有的成员都已经构建完。唯一能保证这点的方法就是首先调用基类的构造器。接着,在派生类的构造器中,所有你可以访问的基类成员都已经初始化。另一个在构造器中能知道所有成员都是有效的理由是:无论何时有可能的话,你应该在所有成员对象(通过组合将对象置于类中)定义处初始化它们(例如,例子中的 **b**、**c** 和 **l**)。如果遵循这条实践,就可以帮助确保所有的基类成员和当前对象的成员对象都已经初始化。\n", - "\n", - "不幸的是,这不能处理所有情况,在下一节会看到。\n", - "\n", - "### 继承和清理\n", - "\n", - "在使用组合和继承创建新类时,大部分时候你无需关心清理。子对象通常会留给垃圾收集器处理。如果你存在清理问题,那么必须用心地为新类创建一个 `dispose()` 方法(这里用的是我选择的名称,你可以使用更好的名称)。由于继承,如果有其他特殊的清理工作的话,就必须在派生类中重写 `dispose()` 方法。当重写 `dispose()` 方法时,记得调用基类的 `dispose()` 方法,否则基类的清理工作不会发生:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/Frog.java\n", - "// Cleanup and inheritance\n", - "// {java polymorphism.Frog}\n", - "package polymorphism;\n", - "\n", - "class Characteristic {\n", - " private String s;\n", - " \n", - " Characteristic(String s) {\n", - " this.s = s;\n", - " System.out.println(\"Creating Characteristic \" + s);\n", - " }\n", - " \n", - " protected void dispose() {\n", - " System.out.println(\"disposing Characteristic \" + s);\n", - " }\n", - "}\n", - "\n", - "class Description {\n", - " private String s;\n", - " \n", - " Description(String s) {\n", - " this.s = s;\n", - " System.out.println(\"Creating Description \" + s);\n", - " }\n", - " \n", - " protected void dispose() {\n", - " System.out.println(\"disposing Description \" + s);\n", - " }\n", - "}\n", - "\n", - "class LivingCreature {\n", - " private Characteristic p = new Characteristic(\"is alive\");\n", - " private Description t = new Description(\"Basic Living Creature\");\n", - " \n", - " LivingCreature() {\n", - " System.out.println(\"LivingCreature()\");\n", - " }\n", - " \n", - " protected void dispose() {\n", - " System.out.println(\"LivingCreature dispose\");\n", - " t.dispose();\n", - " p.dispose();\n", - " }\n", - "}\n", - "\n", - "class Animal extends LivingCreature {\n", - " private Characteristic p = new Characteristic(\"has heart\");\n", - " private Description t = new Description(\"Animal not Vegetable\");\n", - " \n", - " Animal() {\n", - " System.out.println(\"Animal()\");\n", - " }\n", - " \n", - " @Override\n", - " protected void dispose() {\n", - " System.out.println(\"Animal dispose\");\n", - " t.dispose();\n", - " p.dispose();\n", - " super.dispose();\n", - " }\n", - "}\n", - "\n", - "class Amphibian extends Animal {\n", - " private Characteristic p = new Characteristic(\"can live in water\");\n", - " private Description t = new Description(\"Both water and land\");\n", - " \n", - " Amphibian() {\n", - " System.out.println(\"Amphibian()\");\n", - " }\n", - " \n", - " @Override\n", - " protected void dispose() {\n", - " System.out.println(\"Amphibian dispose\");\n", - " t.dispose();\n", - " p.dispose();\n", - " super.dispose();\n", - " }\n", - "}\n", - "\n", - "public class Frog extends Amphibian {\n", - " private Characteristic p = new Characteristic(\"Croaks\");\n", - " private Description t = new Description(\"Eats Bugs\");\n", - " \n", - " public Frog() {\n", - " System.out.println(\"Frog()\");\n", - " }\n", - " \n", - " @Override\n", - " protected void dispose() {\n", - " System.out.println(\"Frog dispose\");\n", - " t.dispose();\n", - " p.dispose();\n", - " super.dispose();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Frog frog = new Frog();\n", - " System.out.println(\"Bye!\");\n", - " frog.dispose();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Creating Characteristic is alive\n", - "Creating Description Basic Living Creature\n", - "LivingCreature()\n", - "Creating Characteristiv has heart\n", - "Creating Description Animal not Vegetable\n", - "Animal()\n", - "Creating Characteristic can live in water\n", - "Creating Description Both water and land\n", - "Amphibian()\n", - "Creating Characteristic Croaks\n", - "Creating Description Eats Bugs\n", - "Frog()\n", - "Bye!\n", - "Frog dispose\n", - "disposing Description Eats Bugs\n", - "disposing Characteristic Croaks\n", - "Amphibian dispose\n", - "disposing Description Both wanter and land\n", - "disposing Characteristic can live in water\n", - "Animal dispose\n", - "disposing Description Animal not Vegetable\n", - "disposing Characteristic has heart\n", - "LivingCreature dispose\n", - "disposing Description Basic Living Creature\n", - "disposing Characteristic is alive" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "层级结构中的每个类都有 **Characteristic** 和 **Description** 两个类型的成员对象,它们必须得被销毁。销毁的顺序应该与初始化的顺序相反,以防一个对象依赖另一个对象。对于属性来说,就意味着与声明的顺序相反(因为属性是按照声明顺序初始化的)。对于基类(遵循 C++ 析构函数的形式),首先进行派生类的清理工作,然后才是基类的清理。这是因为派生类的清理可能调用基类的一些方法,所以基类组件这时得存活,不能过早地被销毁。输出显示了,**Frog** 对象的所有部分都是按照创建的逆序销毁的。\n", - "\n", - "尽管通常不必进行清理工作,但万一需要时,就得谨慎小心地执行。\n", - "\n", - "**Frog** 对象拥有自己的成员对象,它创建了这些成员对象,并且知道它们能存活多久,所以它知道何时调用 `dispose()` 方法。然而,一旦某个成员对象被其它一个或多个对象共享时,问题就变得复杂了,不能只是简单地调用 `dispose()`。这里,也许就必须使用*引用计数*来跟踪仍然访问着共享对象的对象数量,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/ReferenceCounting.java\n", - "// Cleaning up shared member objects\n", - "class Shared {\n", - " private int refcount = 0;\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - " \n", - " Shared() {\n", - " System.out.println(\"Creating \" + this);\n", - " }\n", - " \n", - " public void addRef() {\n", - " refcount++;\n", - " }\n", - " \n", - " protected void dispose() {\n", - " if (--refcount == 0) {\n", - " System.out.println(\"Disposing \" + this);\n", - " }\n", - " }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"Shared \" + id;\n", - " }\n", - "}\n", - "\n", - "class Composing {\n", - " private Shared shared;\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - " \n", - " Composing(Shared shared) {\n", - " System.out.println(\"Creating \" + this);\n", - " this.shared = shared;\n", - " this.shared.addRef();\n", - " }\n", - " \n", - " protected void dispose() {\n", - " System.out.println(\"disposing \" + this);\n", - " shared.dispose();\n", - " }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"Composing \" + id;\n", - " }\n", - "}\n", - "\n", - "public class ReferenceCounting {\n", - " public static void main(String[] args) {\n", - " Shared shared = new Shared();\n", - " Composing[] composing = {\n", - " new Composing(shared),\n", - " new Composing(shared),\n", - " new Composing(shared),\n", - " new Composing(shared),\n", - " new Composing(shared),\n", - " };\n", - " for (Composing c: composing) {\n", - " c.dispose();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Creating Shared 0\n", - "Creating Composing 0\n", - "Creating Composing 1\n", - "Creating Composing 2\n", - "Creating Composing 3\n", - "Creating Composing 4\n", - "disposing Composing 0\n", - "disposing Composing 1\n", - "disposing Composing 2\n", - "disposing Composing 3\n", - "disposing Composing 4\n", - "Disposing Shared 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**static long counter** 跟踪所创建的 **Shared** 实例数量,还提供了 **id** 的值。**counter** 的类型是 **long** 而不是 **int**,以防溢出(这只是个良好实践,对于本书的所有示例,**counter** 不会溢出)。**id** 是 **final** 的,因为它的值在初始化时确定后不应该变化。\n", - "\n", - "在将一个 **shared** 对象附着在类上时,必须记住调用 `addRef()`,而 `dispose()` 方法会跟踪引用数,以确定在何时真正地执行清理工作。使用这种技巧需要加倍细心,但是如果正在共享需要被清理的对象,就没有太多选择了。\n", - "\n", - "### 构造器内部多态方法的行为\n", - "\n", - "构造器调用的层次结构带来了一个困境。如果在构造器中调用了正在构造的对象的动态绑定方法,会发生什么呢?\n", - "\n", - "在普通的方法中,动态绑定的调用是在运行时解析的,因为对象不知道它属于方法所在的类还是类的派生类。\n", - "\n", - "如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用,这使得一些 bug 很隐蔽,难以发现。\n", - "\n", - "从概念上讲,构造器的工作就是创建对象(这并非是平常的工作)。在构造器内部,整个对象可能只是部分形成——只知道基类对象已经初始化。如果构造器只是构造对象过程中的一个步骤,且构造的对象所属的类是从构造器所属的类派生出的,那么派生部分在当前构造器被调用时还没有初始化。然而,一个动态绑定的方法调用向外深入到继承层次结构中,它可以调用派生类的方法。如果你在构造器中这么做,就可能调用一个方法,该方法操纵的成员可能还没有初始化——这肯定会带来灾难。\n", - "\n", - "下面例子展示了这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/PolyConstructors.java\n", - "// Constructors and polymorphism\n", - "// don't produce what you might expect\n", - "class Glyph {\n", - " void draw() {\n", - " System.out.println(\"Glyph.draw()\");\n", - " }\n", - "\n", - " Glyph() {\n", - " System.out.println(\"Glyph() before draw()\");\n", - " draw();\n", - " System.out.println(\"Glyph() after draw()\");\n", - " }\n", - "}\n", - "\n", - "class RoundGlyph extends Glyph {\n", - " private int radius = 1;\n", - "\n", - " RoundGlyph(int r) {\n", - " radius = r;\n", - " System.out.println(\"RoundGlyph.RoundGlyph(), radius = \" + radius);\n", - " }\n", - "\n", - " @Override\n", - " void draw() {\n", - " System.out.println(\"RoundGlyph.draw(), radius = \" + radius);\n", - " }\n", - "}\n", - "\n", - "public class PolyConstructors {\n", - " public static void main(String[] args) {\n", - " new RoundGlyph(5);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Glyph() before draw()\n", - "RoundGlyph.draw(), radius = 0\n", - "Glyph() after draw()\n", - "RoundGlyph.RoundGlyph(), radius = 5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Glyph** 的 `draw()` 被设计为可重写,在 **RoundGlyph** 这个方法被重写。但是 **Glyph** 的构造器里调用了这个方法,结果调用了 **RoundGlyph** 的 `draw()` 方法,这看起来正是我们的目的。输出结果表明,当 **Glyph** 构造器调用了 `draw()` 时,**radius** 的值不是默认初始值 1 而是 0。这可能会导致在屏幕上只画了一个点或干脆什么都不画,于是我们只能干瞪眼,试图找到程序不工作的原因。\n", - "\n", - "前一小节描述的初始化顺序并不十分完整,而这正是解决谜团的关键所在。初始化的实际过程是:\n", - "\n", - "1. 在所有事发生前,分配给对象的存储空间会被初始化为二进制 0。\n", - "2. 如前所述调用基类构造器。此时调用重写后的 `draw()` 方法(是的,在调用 **RoundGraph** 构造器之前调用),由步骤 1 可知,**radius** 的值为 0。\n", - "3. 按声明顺序初始化成员。\n", - "4. 最终调用派生类的构造器。\n", - "\n", - "这么做有个优点:所有事物至少初始化为 0(或某些特殊数据类型与 0 等价的值),而不是仅仅留作垃圾。这包括了通过组合嵌入类中的对象引用,被赋予 **null**。如果忘记初始化该引用,就会在运行时出现异常。观察输出结果,就会发现所有事物都是 0。\n", - "\n", - "另一方面,应该震惊于输出结果。逻辑方面我们已经做得非常完美,然而行为仍不可思议的错了,编译器也没有报错(C++ 在这种情况下会产生更加合理的行为)。像这样的 bug 很容易被忽略,需要花很长时间才能发现。\n", - "\n", - "因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的 **final** 方法(这也适用于可被看作是 **final** 的 **private** 方法)。这些方法不能被重写,因此不会产生意想不到的结果。你可能无法永远遵循这条规范,但应该朝着它努力。\n", - "\n", - "\n", - "\n", - "## 协变返回类型\n", - "\n", - "Java 5 中引入了协变返回类型,这表示派生类的被重写方法可以返回基类方法返回类型的派生类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/CovariantReturn.java\n", - "class Grain {\n", - " @Override\n", - " public String toString() {\n", - " return \"Grain\";\n", - " }\n", - "}\n", - "\n", - "class Wheat extends Grain {\n", - " @Override\n", - " public String toString() {\n", - " return \"Wheat\";\n", - " }\n", - "}\n", - "\n", - "class Mill {\n", - " Grain process() {\n", - " return new Grain();\n", - " }\n", - "}\n", - "\n", - "class WheatMill extends Mill {\n", - " @Override\n", - " Wheat process() {\n", - " return new Wheat();\n", - " }\n", - "}\n", - "\n", - "public class CovariantReturn {\n", - " public static void main(String[] args) {\n", - " Mill m = new Mill();\n", - " Grain g = m.process();\n", - " System.out.println(g);\n", - " m = new WheatMill();\n", - " g = m.process();\n", - " System.out.println(g);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Grain\n", - "Wheat" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "关键区别在于 Java 5 之前的版本强制要求被重写的 `process()` 方法必须返回 **Grain** 而不是 **Wheat**,即使 **Wheat** 派生自 **Grain**,因而也应该是一种合法的返回类型。协变返回类型允许返回更具体的 **Wheat** 类型。\n", - "\n", - "\n", - "\n", - "## 使用继承设计\n", - "\n", - "学习过多态之后,一切看似都可以被继承,因为多态是如此巧妙的工具。这会给设计带来负担。事实上,如果利用已有类创建新类首先选择继承的话,事情会变得莫名的复杂。\n", - "\n", - "更好的方法是首先选择组合,特别是不知道该使用哪种方法时。组合不会强制设计是继承层次结构,而且组合更加灵活,因为可以动态地选择类型(因而选择相应的行为),而继承要求必须在编译时知道确切类型。下面例子说明了这点:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/Transmogrify.java\n", - "// Dynamically changing the behavior of an object\n", - "// via composition (the \"State\" design pattern)\n", - "class Actor {\n", - " public void act() {}\n", - "}\n", - "\n", - "class HappyActor extends Actor {\n", - " @Override\n", - " public void act() {\n", - " System.out.println(\"HappyActor\");\n", - " }\n", - "}\n", - "\n", - "class SadActor extends Actor {\n", - " @Override\n", - " public void act() {\n", - " System.out.println(\"SadActor\");\n", - " }\n", - "}\n", - "\n", - "class Stage {\n", - " private Actor actor = new HappyActor();\n", - " \n", - " public void change() {\n", - " actor = new SadActor();\n", - " }\n", - " \n", - " public void performPlay() {\n", - " actor.act();\n", - " }\n", - "}\n", - "\n", - "public class Transmogrify {\n", - " public static void main(String[] args) {\n", - " Stage stage = new Stage();\n", - " stage.performPlay();\n", - " stage.change();\n", - " stage.performPlay();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HappyActor\n", - "SadActor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Stage** 对象中包含了 **Actor** 引用,该引用被初始化为指向一个 **HappyActor** 对象,这意味着 `performPlay()` 会产生一个特殊行为。但是既然引用可以在运行时与其他不同的对象绑定,那么它就可以被替换成对 **SadActor** 的引用,`performPlay()` 的行为随之改变。这样你就获得了运行时的动态灵活性(这被称为状态模式)。与之相反,我们不能在运行时决定继承不同的对象,那在编译时就完全确定下来了。\n", - "\n", - "有一条通用准则:使用继承表达行为的差异,使用属性表达状态的变化。在上个例子中,两者都用到了。通过继承得到的两个不同类在 `act()` 方法中表达了不同的行为,**Stage** 通过组合使自己的状态发生变化。这里状态的改变产生了行为的改变。\n", - "\n", - "### 替代 vs 扩展\n", - "\n", - "采用“纯粹”的方式创建继承层次结构看上去是最清晰的方法。即只有基类的方法才能在派生类中被重写,就像下图这样:\n", - "\n", - "![类图](../images/1562406479787.png)\n", - "\n", - "这被称作纯粹的“is - a\"关系,因为类的接口已经确定了它是什么。继承可以确保任何派生类都拥有基类的接口,绝对不会少。如果按图上这么做,派生类将只拥有基类的接口。\n", - "\n", - "纯粹的替代意味着派生类可以完美地替代基类,当使用它们时,完全不需要知道这些子类的信息。也就是说,基类可以接收任意发送给派生类的消息,因为它们具有完全相同的接口。只需将派生类向上转型,不要关注对象的具体类型。所有一切都可以通过多态处理。\n", - "\n", - "按这种方式思考,似乎只有纯粹的“is - a”关系才是唯一明智的做法,其他任何设计只会导致混乱且注定失败。这其实也是个陷阱。一旦按这种方式开始思考,就会转而发现继承扩展接口(遗憾的是,extends 关键字似乎怂恿我们这么做)才是解决特定问题的完美方案。这可以称为“is - like - a” 关系,因为派生类就像是基类——它有着相同的基本接口,但还具有需要额外方法实现的其他特性:\n", - "\n", - "![](../images/1562409366637.png)\n", - "\n", - "虽然这是一种有用且明智的方法(依赖具体情况),但是也存在缺点。派生类中接口的扩展部分在基类中不存在(不能通过基类访问到这些扩展接口),因此一旦向上转型,就不能通过基类调用这些新方法:\n", - "\n", - "![](../images/1562409926765.png)\n", - "\n", - "如果不向上转型,就不会遇到这个问题。但是通常情况下,我们需要重新查明对象的确切类型,从而能够访问该类型中的扩展方法。下一节说明如何做到这点。\n", - "\n", - "### 向下转型与运行时类型信息\n", - "\n", - "由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,那么为了重新获取类型信息,就需要在继承层次中向下移动,使用*向下转型*。\n", - "\n", - "向上转型永远是安全的,因为基类不会具有比派生类更多的接口。因此,每条发送给基类接口的消息都能被接收。但是对于向下转型,你无法知道一个形状是圆,它有可能是三角形、正方形或其他一些类型。\n", - "\n", - "为了解决这个问题,必须得有某种方法确保向下转型是正确的,防止意外转型到一个错误类型,进而发送对象无法接收的消息。这么做是不安全的。\n", - "\n", - "在某些语言中(如 C++),必须执行一个特殊的操作来获得安全的向下转型,但是在 Java 中,每次转型都会被检查!所以即使只是进行一次普通的加括号形式的类型转换,在运行时这个转换仍会被检查,以确保它的确是希望的那种类型。如果不是,就会得到 ClassCastException (类转型异常)。这种在运行时检查类型的行为称作运行时类型信息。下面例子展示了 RTTI 的行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// polymorphism/RTTI.java\n", - "// Downcasting & Runtime type information (RTTI)\n", - "// {ThrowsException}\n", - "class Useful {\n", - " public void f() {}\n", - " public void g() {}\n", - "}\n", - "\n", - "class MoreUseful extends Useful {\n", - " @Override\n", - " public void f() {}\n", - " @Override\n", - " public void g() {}\n", - " public void u() {}\n", - " public void v() {}\n", - " public void w() {}\n", - "}\n", - "\n", - "public class RTTI {\n", - " public static void main(String[] args) {\n", - " Useful[] x = {\n", - " new Useful(),\n", - " new MoreUseful()\n", - " };\n", - " x[0].f();\n", - " x[1].g();\n", - " // Compile time: method not found in Useful:\n", - " //- x[1].u();\n", - " ((MoreUseful) x[1]).u(); // Downcast/RTTI\n", - " ((MoreUseful) x[0]).u(); // Exception thrown\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Exception in thread \"main\"\n", - "java.lang.ClassCastException: Useful cannot be cast to\n", - "MoreUseful\n", - "at RTTI.main" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正如前面类图所示,**MoreUseful** 扩展了 **Useful** 的接口。而 **MoreUseful** 也继承了 **Useful**,所以它可以向上转型为 **Useful**。在 `main()` 方法中可以看到这种情况的发生。因为两个对象都是 **Useful** 类型,所以对它们都可以调用 `f()` 和 `g()` 方法。如果试图调用 `u()` 方法(只存在于 **MoreUseful** 中),就会得到编译时错误信息。\n", - "\n", - "为了访问 **MoreUseful** 对象的扩展接口,就得尝试向下转型。如果转型为正确的类型,就转型成功。否则,就会得到 ClassCastException 异常。你不必为这个异常编写任何特殊代码,因为它指出了程序员在程序的任何地方都可能犯的错误。**{ThrowsException}** 注释标签告知本书的构建系统:在运行程序时,预期抛出一个异常。\n", - "\n", - "RTTI 不仅仅包括简单的转型。例如,它还提供了一种方法,使你可以在试图向下转型前检查所要处理的类型。“类型信息”一章中会详细阐述运行时类型信息的方方面面。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "多态意味着“不同的形式”。在面向对象编程中,我们持有从基类继承而来的相同接口和使用该接口的不同形式:不同版本的动态绑定方法。\n", - "\n", - "在本章中,你可以看到,如果不使用数据抽象和继承,就不可能理解甚至创建多态的例子。多态是一种不能单独看待的特性(比如像 **switch** 语句那样),它只能作为类关系全景中的一部分,与其他特性协同工作。\n", - "\n", - "为了在程序中有效地使用多态乃至面向对象的技术,就必须扩展自己的编程视野,不能只看到单一类中的成员和消息,而要看到类之间的共同特性和它们之间的关系。尽管这需要很大的努力,但是这么做是值得的。它能带来更快的程序开发、更好的代码组织、扩展性更好的程序和更易维护的代码。\n", - "\n", - "但是记住,多态可能被滥用。仔细分析代码以确保多态确实能带来好处。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/10-Interfaces.ipynb b/jupyter/10-Interfaces.ipynb deleted file mode 100644 index 30453dc4..00000000 --- a/jupyter/10-Interfaces.ipynb +++ /dev/null @@ -1,2640 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十章 接口\n", - "\n", - "接口和抽象类提供了一种将接口与实现分离的更加结构化的方法。\n", - "\n", - "这种机制在编程语言中不常见,例如 C++ 只对这种概念有间接的支持。而在 Java 中存在这些关键字,说明这些思想很重要,Java 为它们提供了直接支持。\n", - "\n", - "首先,我们将学习抽象类,一种介于普通类和接口之间的折中手段。尽管你的第一想法是创建接口,但是对于构建具有属性和未实现方法的类来说,抽象类也是重要且必要的工具。你不可能总是使用纯粹的接口。\n", - "\n", - "\n", - "\n", - "## 抽象类和方法\n", - "\n", - "在上一章的乐器例子中,基类 **Instrument** 中的方法往往是“哑”方法。如果调用了这些方法,就会出现一些错误。这是因为接口的目的是为它的派生类创建一个通用接口。\n", - "\n", - "在那些例子中,创建这个通用接口的唯一理由是,不同的子类可以用不同的方式表示此接口。通用接口建立了一个基本形式,以此表达所有派生类的共同部分。另一种说法把 **Instrument** 称为抽象基类,或简称抽象类。\n", - "\n", - "对于像 **Instrument** 那样的抽象类来说,它的对象几乎总是没有意义的。创建一个抽象类是为了通过通用接口操纵一系列类。因此,**Instrument** 只是表示接口,不是具体实现,所以创建一个 **Instrument** 的对象毫无意义,我们可能希望阻止用户这么做。通过让 **Instrument** 所有的方法产生错误,就可以达到这个目的,但是这么做会延迟到运行时才能得知错误信息,并且需要用户进行可靠、详尽的测试。最好能在编译时捕捉问题。\n", - "\n", - "Java 提供了一个叫做*抽象方法*的机制,这个方法是不完整的:它只有声明没有方法体。下面是抽象方法的声明语法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "abstract void f();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "包含抽象方法的类叫做*抽象类*。如果一个类包含一个或多个抽象方法,那么类本身也必须限定为抽象的,否则,编译器会报错。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interface/Basic.java\n", - "abstract class Basic {\n", - " abstract void unimplemented();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果一个抽象类是不完整的,当试图创建这个类的对象时,Java 会怎么做呢?它不会创建抽象类的对象,所以我们只会得到编译器的错误信息。这样保证了抽象类的纯粹性,我们不用担心误用它。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AttemptToUseBasic.java\n", - "// {WillNotCompile}\n", - "public class AttemptToUseBasic {\n", - " Basic b = new Basic();\n", - " // error: Basic is abstract; cannot be instantiated\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果创建一个继承抽象类的新类并为之创建对象,那么就必须为基类的所有抽象方法提供方法定义。如果不这么做(可以选择不做),新类仍然是一个抽象类,编译器会强制我们为新类加上 **abstract** 关键字。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Basic2.java\n", - "abstract class Basic2 extends Basic {\n", - " int f() {\n", - " return 111;\n", - " }\n", - " \n", - " abstract void g() {\n", - " // unimplemented() still not implemented\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以将一个不包含任何抽象方法的类指明为 **abstract**,在类中的抽象方法没啥意义但想阻止创建类的对象时,这么做就很有用。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AbstractWithoutAbstracts.java\n", - "abstract class Basic3 {\n", - " int f() {\n", - " return 111;\n", - " }\n", - " \n", - " // No abstract methods\n", - "}\n", - "\n", - "public class AbstractWithoutAbstracts {\n", - " // Basic b3 = new Basic3();\n", - " // error: Basic 3 is abstract; cannot be instantiated\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了创建可初始化的类,就要继承抽象类,并提供所有抽象方法的定义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Instantiable.java\n", - "abstract class Uninstantiable {\n", - " abstract void f();\n", - " abstract int g();\n", - "}\n", - "\n", - "public class Instantiable extends Uninstantiable {\n", - " @Override\n", - " void f() {\n", - " System.out.println(\"f()\");\n", - " }\n", - " \n", - " @Override\n", - " int g() {\n", - " return 22;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Uninstantiable ui = new Instantiable();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "留意 `@Override` 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 `@Override` 是多余的。但是,`@Override` 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 `@Override`,即使在没有这个注解,编译器告诉我错误的时候。 \n", - "\n", - "记住,事实上的访问权限是“friendly”。你很快会看到接口自动将其方法指明为 **public**。事实上,接口只允许 **public** 方法,如果不加访问修饰符的话,接口的方法不是 **friendly** 而是 **public**。然而,抽象类允许每件事:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AbstractAccess.java\n", - "abstract class AbstractAccess {\n", - " private void m1() {}\n", - " \n", - " // private abstract void m1a(); // illegal\n", - " \n", - " protected void m2() {}\n", - " \n", - " protected abstract void m2a();\n", - " \n", - " void m3() {}\n", - " \n", - " abstract void m3a();\n", - " \n", - " public void m4() {}\n", - " \n", - " public abstract void m4a();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**private abstract** 被禁止了是有意义的,因为你不可能在 **AbstractAccess** 的任何子类中合法地定义它。\n", - "\n", - "上一章的 **Instrument** 类可以很轻易地转换为一个抽象类。只需要部分方法是 **abstract** 即可。将一个类指明为 **abstract** 并不强制类中的所有方法必须都是抽象方法。如下图所示:\n", - "\n", - "![类图](../images/1562653648586.png)\n", - "\n", - "下面是修改成使用抽象类和抽象方法的管弦乐器的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/music4/Music4.java\n", - "// Abstract classes and methods\n", - "// {java interfaces.music4.Music4}\n", - "package interfaces.music4;\n", - "import polymorphism.music.Note;\n", - "\n", - "abstract class Instrument {\n", - " private int i; // Storage allocated for each\n", - " \n", - " public abstract void play(Note n);\n", - " \n", - " public String what() {\n", - " return \"Instrument\";\n", - " }\n", - " \n", - " public abstract void adjust();\n", - "}\n", - "\n", - "class Wind extends Instrument {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Wind.play() \" + n);\n", - " }\n", - " \n", - " @Override\n", - " public String what() {\n", - " return \"Wind\";\n", - " }\n", - " \n", - " @Override\n", - " public void adjust() {\n", - " System.out.println(\"Adjusting Wind\");\n", - " }\n", - "}\n", - "\n", - "class Percussion extends Instrument {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Percussion.play() \" + n);\n", - " }\n", - " \n", - " @Override\n", - " public String what() {\n", - " return \"Percussion\";\n", - " }\n", - " \n", - " @Override\n", - " public void adjust() {\n", - " System.out.println(\"Adjusting Percussion\");\n", - " }\n", - "}\n", - "\n", - "class Stringed extends Instrument {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Stringed.play() \" + n);\n", - " }\n", - " \n", - " @Override\n", - " public String what() {\n", - " return \"Stringed\";\n", - " }\n", - " \n", - " @Override\n", - " public void adjust() {\n", - " System.out.println(\"Adjusting Stringed\");\n", - " }\n", - "}\n", - "\n", - "class Brass extends Wind {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Brass.play() \" + n);\n", - " }\n", - " \n", - " @Override\n", - " public void adjust() {\n", - " System.out.println(\"Adjusting Brass\");\n", - " }\n", - "}\n", - "\n", - "class Woodwind extends Wind {\n", - " @Override\n", - " public void play(Note n) {\n", - " System.out.println(\"Woodwind.play() \" + n);\n", - " }\n", - " \n", - " @Override\n", - " public String what() {\n", - " return \"Woodwind\";\n", - " }\n", - "}\n", - "\n", - "public class Music4 {\n", - " // Doesn't care about type, so new types\n", - " // added to system still work right:\n", - " static void tune(Instrument i) {\n", - " // ...\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " static void tuneAll(Instrument[] e) {\n", - " for (Instrument i: e) {\n", - " tune(i);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " // Upcasting during addition to the array:\n", - " Instrument[] orchestra = {\n", - " new Wind(),\n", - " new Percussion(),\n", - " new Stringed(),\n", - " new Brass(),\n", - " new Woodwind()\n", - " };\n", - " tuneAll(orchestra);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Wind.play() MIDDLE_C\n", - "Percussion.play() MIDDLE_C\n", - "Stringed.play() MIDDLE_C\n", - "Brass.play() MIDDLE_C\n", - "Woodwind.play() MIDDLE_C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "除了 **Instrument**,基本没区别。\n", - "\n", - "创建抽象类和抽象方法是有帮助的,因为它们使得类的抽象性很明确,并能告知用户和编译器使用意图。抽象类同时也是一种有用的重构工具,使用它们使得我们很容易地将沿着继承层级结构上移公共方法。\n", - "\n", - "\n", - "\n", - "## 接口创建\n", - "\n", - "使用 **interface** 关键字创建接口。在本书中,interface 和 class 一样随处常见,除非特指关键字 **interface**,其他情况下都采用正常字体书写 interface。\n", - "\n", - "描述 Java 8 之前的接口更加容易,因为它们只允许抽象方法。像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/PureInterface.java\n", - "// Interface only looked like this before Java 8\n", - "public interface PureInterface {\n", - " int m1(); \n", - " void m2();\n", - " double m3();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们甚至不用为方法加上 **abstract** 关键字,因为方法在接口中。Java 知道这些方法不能有方法体(仍然可以为方法加上 **abstract** 关键字,但是看起来像是不明白接口,徒增难堪罢了)。\n", - "\n", - "因此,在 Java 8之前我们可以这么说:**interface** 关键字产生一个完全抽象的类,没有提供任何实现。我们只能描述类应该像什么,做什么,但不能描述怎么做,即只能决定方法名、参数列表和返回类型,但是无法确定方法体。接口只提供形式,通常来说没有实现,尽管在某些受限制的情况下可以有实现。\n", - "\n", - "一个接口表示:所有实现了该接口的类看起来都像这样。因此,任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。所以,接口被用来建立类之间的协议。(一些面向对象编程语言中,使用 protocol 关键字完成相同的功能。)\n", - "\n", - "Java 8 中接口稍微有些变化,因为 Java 8 允许接口包含默认方法和静态方法——基于某些重要原因,看到后面你会理解。接口的基本概念仍然没变,介于类型之上、实现之下。接口与抽象类最明显的区别可能就是使用上的惯用方式。接口的典型使用是代表一个类的类型或一个形容词,如 Runnable 或 Serializable,而抽象类通常是类层次结构的一部分或一件事物的类型,如 String 或 ActionHero。\n", - "\n", - "使用关键字 **interface** 而不是 **class** 来创建接口。和类一样,需要在关键字 **interface** 前加上 **public** 关键字(但只是在接口名与文件名相同的情况下),否则接口只有包访问权限,只能在接口相同的包下才能使用它。\n", - "\n", - "接口同样可以包含属性,这些属性被隐式指明为 **static** 和 **final**。\n", - "\n", - "使用 **implements** 关键字使一个类遵循某个特定接口(或一组接口),它表示:接口只是外形,现在我要说明它是如何工作的。除此之外,它看起来像继承。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/ImplementingAnInterface.java\n", - "interface Concept { // Package access\n", - " void idea1();\n", - " void idea2();\n", - "}\n", - "\n", - "class Implementation implements Concept {\n", - " @Override\n", - " public void idea1() {\n", - " System.out.println(\"idea1\");\n", - " }\n", - " \n", - " @Override\n", - " public void idea2() {\n", - " System.out.println(\"idea2\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以选择显式地声明接口中的方法为 **public**,但是即使你不这么做,它们也是 **public** 的。所以当实现一个接口时,来自接口中的方法必须被定义为 **public**。否则,它们只有包访问权限,这样在继承时,它们的可访问权限就被降低了,这是 Java 编译器所不允许的。\n", - "\n", - "### 默认方法\n", - "\n", - "Java 8 为关键字 **default** 增加了一个新的用途(之前只用于 **switch** 语句和注解中)。当在接口中使用它时,任何实现接口却没有定义方法的时候可以使用 **default** 创建的方法体。默认方法比抽象类中的方法受到更多的限制,但是非常有用,我们将在“流式编程”一章中看到。现在让我们看下如何使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AnInterface.java\n", - "interface AnInterface {\n", - " void firstMethod();\n", - " void secondMethod();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以像这样实现接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AnImplementation.java\n", - "public class AnImplementation implements AnInterface {\n", - " public void firstMethod() {\n", - " System.out.println(\"firstMethod\");\n", - " }\n", - " \n", - " public void secondMethod() {\n", - " System.out.println(\"secondMethod\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " AnInterface i = new AnImplementation();\n", - " i.firstMethod();\n", - " i.secondMethod();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "firstMethod\n", - "secondMethod" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们在 **AnInterface** 中增加一个新方法 `newMethod()`,而在 **AnImplementation** 中没有实现它,编译器就会报错:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "AnImplementation.java:3:error: AnImplementation is not abstract and does not override abstract method newMethod() in AnInterface\n", - "public class AnImplementation implements AnInterface {\n", - "^\n", - "1 error" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们使用关键字 **default** 为 `newMethod()` 方法提供默认的实现,那么所有与接口有关的代码能正常工作,不受影响,而且这些代码还可以调用新的方法 `newMethod()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/InterfaceWithDefault.java\n", - "interface InterfaceWithDefault {\n", - " void firstMethod();\n", - " void secondMethod();\n", - " \n", - " default void newMethod() {\n", - " System.out.println(\"newMethod\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "关键字 **default** 允许在接口中提供方法实现——在 Java 8 之前被禁止。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Implementation2.java\n", - "public class Implementation2 implements InterfaceWithDefault {\n", - " @Override\n", - " public void firstMethod() {\n", - " System.out.println(\"firstMethod\");\n", - " }\n", - " \n", - " @Override\n", - " public void secondMethod() {\n", - " System.out.println(\"secondMethod\")\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " InterfaceWithDefault i = new Implementation2();\n", - " i.firstMethod();\n", - " i.secondMethod();\n", - " i.newMethod();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "firstMethod\n", - "secondMethod\n", - "newMethod" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管 **Implementation2** 中未定义 `newMethod()`,但是可以使用 `newMethod()` 了。 \n", - "\n", - "增加默认方法的极具说服力的理由是它允许在不破坏已使用接口的代码的情况下,在接口中增加新的方法。默认方法有时也被称为*守卫方法*或*虚拟扩展方法*。\n", - "\n", - "### 多继承\n", - "\n", - "多继承意味着一个类可能从多个父类型中继承特征和特性。\n", - "\n", - "Java 在设计之初,C++ 的多继承机制饱受诟病。Java 过去是一种严格要求单继承的语言:只能继承自一个类(或抽象类),但可以实现任意多个接口。在 Java 8 之前,接口没有包袱——它只是方法外貌的描述。\n", - "\n", - "多年后的现在,Java 通过默认方法具有了某种多继承的特性。结合带有默认方法的接口意味着结合了多个基类中的行为。因为接口中仍然不允许存在属性(只有静态属性,不适用),所以属性仍然只会来自单个基类或抽象类,也就是说,不会存在状态的多继承。正如下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/MultipleInheritance.java\n", - "import java.util.*;\n", - "\n", - "interface One {\n", - " default void first() {\n", - " System.out.println(\"first\");\n", - " }\n", - "}\n", - "\n", - "interface Two {\n", - " default void second() {\n", - " System.out.println(\"second\");\n", - " }\n", - "}\n", - "\n", - "interface Three {\n", - " default void third() {\n", - " System.out.println(\"third\");\n", - " }\n", - "}\n", - "\n", - "class MI implements One, Two, Three {}\n", - "\n", - "public class MultipleInheritance {\n", - " public static void main(String[] args) {\n", - " MI mi = new MI();\n", - " mi.first();\n", - " mi.second();\n", - " mi.third();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "first\n", - "second\n", - "third" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在我们做些在 Java 8 之前不可能完成的事:结合多个源的实现。只要基类方法中的方法名和参数列表不同,就能工作得很好,否则会得到编译器错误:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interface/MICollision.java\n", - "import java.util.*;\n", - "\n", - "interface Bob1 {\n", - " default void bob() {\n", - " System.out.println(\"Bob1::bob\");\n", - " }\n", - "}\n", - "\n", - "interface Bob2 {\n", - " default void bob() {\n", - " System.out.println(\"Bob2::bob\");\n", - " }\n", - "}\n", - "\n", - "// class Bob implements Bob1, Bob2 {}\n", - "/* Produces:\n", - "error: class Bob inherits unrelated defaults\n", - "for bob() from types Bob1 and Bob2\n", - "class Bob implements Bob1, Bob2 {}\n", - "^\n", - "1 error\n", - "*/\n", - "\n", - "interface Sam1 {\n", - " default void sam() {\n", - " System.out.println(\"Sam1::sam\");\n", - " }\n", - "}\n", - "\n", - "interface Sam2 {\n", - " default void sam(int i) {\n", - " System.out.println(i * 2);\n", - " }\n", - "}\n", - "\n", - "// This works because the argument lists are distinct:\n", - "class Sam implements Sam1, Sam2 {}\n", - "\n", - "interface Max1 {\n", - " default void max() {\n", - " System.out.println(\"Max1::max\");\n", - " }\n", - "}\n", - "\n", - "interface Max2 {\n", - " default int max() {\n", - " return 47;\n", - " }\n", - "}\n", - "\n", - "// class Max implements Max1, Max2 {}\n", - "/* Produces:\n", - "error: types Max2 and Max1 are imcompatible;\n", - "both define max(), but with unrelated return types\n", - "class Max implements Max1, Max2 {}\n", - "^\n", - "1 error\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Sam** 类中的两个 `sam()` 方法有相同的方法名但是签名不同——方法签名包括方法名和参数类型,编译器也是用它来区分方法。但是从 **Max** 类可看出,返回类型不是方法签名的一部分,因此不能用来区分方法。为了解决这个问题,需要覆写冲突的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Jim.java\n", - "import java.util.*;\n", - "\n", - "interface Jim1 {\n", - " default void jim() {\n", - " System.out.println(\"Jim1::jim\");\n", - " }\n", - "}\n", - "\n", - "interface Jim2 {\n", - " default void jim() {\n", - " System.out.println(\"Jim2::jim\");\n", - " }\n", - "}\n", - "\n", - "public class Jim implements Jim1, Jim2 {\n", - " @Override\n", - " public void jim() {\n", - " Jim2.super.jim();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " new Jim().jim();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Jim2::jim" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当然,你可以重定义 `jim()` 方法,但是也能像上例中那样使用 **super** 关键字选择基类实现中的一种。\n", - "\n", - "### 接口中的静态方法\n", - "\n", - "Java 8 允许在接口中添加静态方法。这么做能恰当地把工具功能置于接口中,从而操作接口,或者成为通用的工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Operations.java\n", - "package onjava;\n", - "import java.util.*;\n", - "\n", - "public interface Operations {\n", - " void execute();\n", - " \n", - " static void runOps(Operations... ops) {\n", - " for (Operations op: ops) {\n", - " op.execute();\n", - " }\n", - " }\n", - " \n", - " static void show(String msg) {\n", - " System.out.println(msg);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是模版方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interface/Machine.java\n", - "import java.util.*;\n", - "import onjava.Operations;\n", - "\n", - "class Bing implements Operations {\n", - " @Override\n", - " public void execute() {\n", - " Operations.show(\"Bing\");\n", - " }\n", - "}\n", - "\n", - "class Crack implements Operations {\n", - " @Override\n", - " public void execute() {\n", - " Operations.show(\"Crack\");\n", - " }\n", - "}\n", - "\n", - "class Twist implements Operations {\n", - " @Override\n", - " public void execute() {\n", - " Operations.show(\"Twist\");\n", - " }\n", - "}\n", - "\n", - "public class Machine {\n", - " public static void main(String[] args) {\n", - " Operations.runOps(\n", - " \tnew Bing(), new Crack(), new Twist());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Bing\n", - "Crack\n", - "Twist" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里展示了创建 **Operations** 的不同方式:一个外部类(Bing),一个匿名类,一个方法引用和 lambda 表达式——毫无疑问用在这里是最好的解决方法。\n", - "\n", - "这个特性是一项改善,因为它允许把静态方法放在更合适的地方。\n", - "\n", - "### Instrument 作为接口\n", - "\n", - "回顾下乐器的例子,使用接口的话:\n", - "\n", - "![类图](../images/1562737974623.png)\n", - "\n", - "类 **Woodwind** 和 **Brass** 说明一旦实现了某个接口,那么其实现就变成一个普通类,可以按常规方式扩展它。\n", - "\n", - "接口的工作方式使得我们不需要显式声明其中的方法为 **public**,它们自动就是 **public** 的。`play()` 和 `adjust()` 使用 **default** 关键字定义实现。在 Java 8 之前,这些定义要在每个实现中重复实现,显得多余且令人烦恼:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/music5/Music5.java\n", - "// {java interfaces.music5.Music5}\n", - "package interfaces.music5;\n", - "import polymorphism.music.Note;\n", - "\n", - "interface Instrument {\n", - " // Compile-time constant:\n", - " int VALUE = 5; // static & final\n", - " \n", - " default void play(Note n) // Automatically public \n", - " System.out.println(this + \".play() \" + n);\n", - " }\n", - " \n", - " default void adjust() {\n", - " System.out.println(\"Adjusting \" + this);\n", - " }\n", - "}\n", - "\n", - "class Wind implements Instrument {\n", - " @Override\n", - " public String toString() {\n", - " return \"Wind\";\n", - " }\n", - "}\n", - "\n", - "class Percussion implements Instrument {\n", - " @Override\n", - " public String toString() {\n", - " return \"Percussion\";\n", - " }\n", - "}\n", - "\n", - "class Stringed implements Instrument {\n", - " @Override\n", - " public String toString() {\n", - " return \"Stringed\";\n", - " }\n", - "}\n", - "\n", - "class Brass extends Wind {\n", - " @Override\n", - " public String toString() {\n", - " return \"Brass\";\n", - " }\n", - "}\n", - "\n", - "class Woodwind extends Wind {\n", - " @Override\n", - " public String toString() {\n", - " return \"Woodwind\";\n", - " }\n", - "}\n", - "\n", - "public class Music5 {\n", - " // Doesn't care about type, so new types\n", - " // added to the system still work right:\n", - " static void tune(Instrument i) {\n", - " // ...\n", - " i.play(Note.MIDDLE_C);\n", - " }\n", - " \n", - " static void tuneAll(Instrument[] e) {\n", - " for (Instrument i: e) {\n", - " tune(i);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " // Upcasting during addition to the array:\n", - " Instrument[] orchestra = {\n", - " new Wind(),\n", - " new Percussion(),\n", - " new Stringed(),\n", - " new Brass(),\n", - " new Woodwind()\n", - " }\n", - " tuneAll(orchestra);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Wind.play() MIDDLE_C\n", - "Percussion.play() MIDDLE_C\n", - "Stringed.play() MIDDLE_C\n", - "Brass.play() MIDDLE_C\n", - "Woodwind.play() MIDDLE_C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个版本的例子的另一个变化是:`what()` 被修改为 `toString()` 方法,因为 `toString()` 实现的正是 `what()` 方法要实现的逻辑。因为 `toString()` 是根基类 **Object** 的方法,所以它不需要出现在接口中。\n", - "\n", - "注意到,无论是将其向上转型为称作 **Instrument** 的普通类,或称作 **Instrument** 的抽象类,还是叫作 **Instrument** 的接口,其行为都是相同的。事实上,从 `tune()` 方法上看不出来 **Instrument** 到底是一个普通类、抽象类,还是一个接口。\n", - "\n", - "\n", - "\n", - "## 抽象类和接口\n", - "\n", - "尤其是在 Java 8 引入 **default** 方法之后,选择用抽象类还是用接口变得更加令人困惑。下表做了明确的区分:\n", - "\n", - "| 特性 | 接口 | 抽象类 |\n", - "| :------------------: | :--------------------------------------------------------: | :--------------------------------------: |\n", - "| 组合 | 新类可以组合多个接口 | 只能继承单一抽象类 |\n", - "| 状态 | 不能包含属性(除了静态属性,不支持对象状态) | 可以包含属性,非抽象方法可能引用这些属性 |\n", - "| 默认方法 和 抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 |\n", - "| 构造器 | 没有构造器 | 可以有构造器 |\n", - "| 可见性 | 隐式 **public** | 可以是 **protected** 或友元 |\n", - "\n", - "抽象类仍然是一个类,在创建新类时只能继承它一个。而创建类的过程中可以实现多个接口。\n", - "\n", - "有一条实际经验:尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。\n", - "\n", - "\n", - "\n", - "## 完全解耦\n", - "\n", - "当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类。如果想把方法应用于那个继承层级结构之外的类,就会触霉头。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。\n", - "\n", - "例如有一个类 **Process** 有两个方法 `name()` 和 `process()`。`process()` 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 **Processor**。下例中,**Processor** 的各个子类修改 String 对象(注意,返回类型可能是协变类型而非参数类型):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Applicator.java\n", - "import java.util.*;\n", - "\n", - "class Processor {\n", - " public String name() {\n", - " return getClass().getSimpleName();\n", - " }\n", - " \n", - " public Object process(Object input) {\n", - " return input;\n", - " }\n", - "}\n", - "\n", - "class Upcase extends Processor {\n", - " // 返回协变类型\n", - " @Override \n", - " public String process(Object input) {\n", - " return ((String) input).toUpperCase();\n", - " }\n", - "}\n", - "\n", - "class Downcase extends Processor {\n", - " @Override\n", - " public String process(Object input) {\n", - " return ((String) input).toLowerCase();\n", - " }\n", - "}\n", - "\n", - "class Splitter extends Processor {\n", - " @Override\n", - " public String process(Object input) {\n", - " // split() divides a String into pieces:\n", - " return Arrays.toString(((String) input).split(\" \"));\n", - " }\n", - "}\n", - "\n", - "public class Applicator {\n", - " public static void apply(Processor p, Object s) {\n", - " System.out.println(\"Using Processor \" + p.name());\n", - " System.out.println(p.process(s));\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " String s = \"We are such stuff as dreams are made on\";\n", - " apply(new Upcase(), s);\n", - " apply(new Downcase(), s);\n", - " apply(new Splitter(), s);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Using Processor Upcase\n", - "WE ARE SUCH STUFF AS DREAMS ARE MADE ON\n", - "Using Processor Downcase\n", - "we are such stuff as dreams are made on\n", - "Using Processor Splitter\n", - "[We, are, such, stuff, as, dreams, are, made, on]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Applicator** 的 `apply()` 方法可以接受任何类型的 **Processor**,并将其应用到一个 **Object** 对象上输出结果。像本例中这样,创建一个能根据传入的参数类型从而具备不同行为的方法称为*策略*设计模式。方法包含算法中不变的部分,策略包含变化的部分。策略就是传入的对象,它包含要执行的代码。在这里,**Processor** 对象是策略,`main()` 方法展示了三种不同的应用于 **String s** 上的策略。\n", - "\n", - "`split()` 是 **String** 类中的方法,它接受 **String** 类型的对象并以传入的参数作为分割界限,返回一个数组 **String[]**。在这里用它是为了更快地创建 **String** 数组。\n", - "\n", - "假设现在发现了一组电子滤波器,它们看起来好像能使用 **Applicator** 的 `apply()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/filters/Waveform.java\n", - "package interfaces.filters;\n", - "\n", - "public class Waveform {\n", - " private static long counter;\n", - " private final long id = counter++;\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"Waveform \" + id;\n", - " }\n", - "}\n", - "\n", - "// interfaces/filters/Filter.java\n", - "package interfaces.filters;\n", - "\n", - "public class Filter {\n", - " public String name() {\n", - " return getClass().getSimpleName();\n", - " }\n", - " \n", - " public Waveform process(Waveform input) {\n", - " return input;\n", - " }\n", - "}\n", - "\n", - "// interfaces/filters/LowPass.java\n", - "package interfaces.filters;\n", - "\n", - "public class LowPass extends Filter {\n", - " double cutoff;\n", - " \n", - " public LowPass(double cutoff) {\n", - " this.cutoff = cutoff;\n", - " }\n", - " \n", - " @Override\n", - " public Waveform process(Waveform input) {\n", - " return input; // Dummy processing 哑处理\n", - " }\n", - "}\n", - "\n", - "// interfaces/filters/HighPass.java\n", - "package interfaces.filters;\n", - "\n", - "public class HighPass extends Filter {\n", - " double cutoff;\n", - " \n", - " public HighPass(double cutoff) {\n", - " this.cutoff = cutoff;\n", - " }\n", - " \n", - " @Override\n", - " public Waveform process(Waveform input) {\n", - " return input;\n", - " }\n", - "}\n", - "\n", - "// interfaces/filters/BandPass.java\n", - "package interfaces.filters;\n", - "\n", - "public class BandPass extends Filter {\n", - " double lowCutoff, highCutoff;\n", - " \n", - " public BandPass(double lowCut, double highCut) {\n", - " lowCutoff = lowCut;\n", - " highCutoff = highCut;\n", - " }\n", - " \n", - " @Override\n", - " public Waveform process(Waveform input) {\n", - " return input;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Filter** 类与 **Processor** 类具有相同的接口元素,但是因为它不是继承自 **Processor** —— 因为 **Filter** 类的创建者根本不知道你想将它当作 **Processor** 使用 —— 因此你不能将 **Applicator** 的 `apply()` 方法应用在 **Filter** 类上,即使这样做也能正常运行。主要是因为 **Applicator** 的 `apply()` 方法和 **Processor** 过于耦合,这阻止了 **Applicator** 的 `apply()` 方法被复用。另外要注意的一点是 Filter 类中 `process()` 方法的输入输出都是 **Waveform**。\n", - "\n", - "但如果 **Processor** 是一个接口,那么限制就会变得松动到足以复用 **Applicator** 的 `apply()` 方法,用来接受那个接口参数。下面是修改后的 **Processor** 和 **Applicator** 版本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/interfaceprocessor/Processor.java\n", - "package interfaces.interfaceprocessor;\n", - "\n", - "public interface Processor {\n", - " default String name() {\n", - " return getClass().getSimpleName();\n", - " }\n", - " \n", - " Object process(Object input);\n", - "}\n", - "\n", - "// interfaces/interfaceprocessor/Applicator.java\n", - "package interfaces.interfaceprocessor;\n", - "\n", - "public class Applicator {\n", - " public static void apply(Processor p, Object s) {\n", - " System.out.println(\"Using Processor \" + p.name());\n", - " System.out.println(p.process(s));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "复用代码的第一种方式是客户端程序员遵循接口编写类,像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/interfaceprocessor/StringProcessor.java\n", - "// {java interfaces.interfaceprocessor.StringProcessor}\n", - "package interfaces.interfaceprocessor;\n", - "import java.util.*;\n", - "\n", - "interface StringProcessor extends Processor {\n", - " @Override\n", - " String process(Object input); // [1]\n", - " String S = \"If she weighs the same as a duck, she's made of wood\"; // [2]\n", - " \n", - " static void main(String[] args) { // [3]\n", - " Applicator.apply(new Upcase(), S);\n", - " Applicator.apply(new Downcase(), S);\n", - " Applicator.apply(new Splitter(), S);\n", - " }\n", - "}\n", - "\n", - "class Upcase implements StringProcessor {\n", - " // 返回协变类型\n", - " @Override\n", - " public String process(Object input) {\n", - " return ((String) input).toUpperCase();\n", - " }\n", - "}\n", - "\n", - "class Downcase implements StringProcessor {\n", - " @Override\n", - " public String process(Object input) {\n", - " return ((String) input).toLowerCase();\n", - " }\n", - "}\n", - "\n", - "class Splitter implements StringProcessor {\n", - " @Override\n", - " public String process(Object input) {\n", - " return Arrays.toString(((String) input).split(\" \"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Using Processor Upcase\n", - "IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD\n", - "Using Processor Downcase\n", - "if she weighs the same as a duck, she's made of wood\n", - "Using Processor Splitter\n", - "[If, she, weighs, the, same, as, a, duck,, she's, made, of, wood]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ">[1] 该声明不是必要的,即使移除它,编译器也不会报错。但是注意这里的协变返回类型从 Object 变成了 String。\n", - ">\n", - ">[2] S 自动就是 final 和 static 的,因为它是在接口中定义的。\n", - ">\n", - ">[3] 可以在接口中定义 `main()` 方法。\n", - "\n", - "这种方式运作得很好,然而你经常遇到的情况是无法修改类。例如在电子滤波器的例子中,类库是被发现而不是创建的。在这些情况下,可以使用*适配器*设计模式。适配器允许代码接受已有的接口产生需要的接口,如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/interfaceprocessor/FilterProcessor.java\n", - "// {java interfaces.interfaceprocessor.FilterProcessor}\n", - "package interfaces.interfaceprocessor;\n", - "import interfaces.filters.*;\n", - "\n", - "class FilterAdapter implements Processor {\n", - " Filter filter;\n", - " \n", - " FilterAdapter(Filter filter) {\n", - " this.filter = filter;\n", - " }\n", - " \n", - " @Override\n", - " public String name() {\n", - " return filter.name();\n", - " }\n", - " \n", - " @Override\n", - " public Waveform process(Object input) {\n", - " return filter.process((Waveform) input);\n", - " }\n", - "}\n", - "\n", - "public class FilterProcessor {\n", - " public static void main(String[] args) {\n", - " Waveform w = new Waveform();\n", - " Applicator.apply(new FilterAdapter(new LowPass(1.0)), w);\n", - " Applicator.apply(new FilterAdapter(new HighPass(2.0)), w);\n", - " Applicator.apply(new FilterAdapter(new BandPass(3.0, 4.0)), w);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Using Processor LowPass\n", - "Waveform 0\n", - "Using Processor HighPass\n", - "Waveform 0\n", - "Using Processor BandPass\n", - "Waveform 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这种使用适配器的方式中,**FilterAdapter** 的构造器接受已有的接口 **Filter**,继而产生需要的 **Processor** 接口的对象。你可能还注意到 **FilterAdapter** 中使用了委托。\n", - "\n", - "协变允许我们从 `process()` 方法中产生一个 **Waveform** 而非 **Object** 对象。\n", - "\n", - "将接口与实现解耦使得接口可以应用于多种不同的实现,因而代码更具可复用性。\n", - "\n", - "\n", - "\n", - "## 多接口结合\n", - "\n", - "接口没有任何实现——也就是说,没有任何与接口相关的存储——因此无法阻止结合的多接口。这是有价值的,因为你有时需要表示“一个 **x** 是一个 **a** 和一个 **b** 以及一个 **c**”。\n", - "\n", - "![类图](../images/1562999314238.png)\n", - "\n", - "派生类并不要求必须继承自抽象的或“具体的”(没有任何抽象方法)的基类。如果继承一个非接口的类,那么只能继承一个类,其余的基元素必须都是接口。需要将所有的接口名称置于 **implements** 关键字之后且用逗号分隔。可以有任意多个接口,并可以向上转型为每个接口,因为每个接口都是独立的类型。下例展示了一个由多个接口组合而成的具体类产生的新类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Adventure.java\n", - "// Multiple interfaces\n", - "interface CanFight {\n", - " void fight();\n", - "}\n", - "\n", - "interface CanSwim {\n", - " void swim();\n", - "}\n", - "\n", - "interface CanFly {\n", - " void fly();\n", - "}\n", - "\n", - "class ActionCharacter {\n", - " public void fight(){}\n", - "}\n", - "\n", - "class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly {\n", - " public void swim() {}\n", - " \n", - " public void fly() {}\n", - "}\n", - "\n", - "public class Adventure {\n", - " public static void t(CanFight x) {\n", - " x.fight();\n", - " }\n", - " \n", - " public static void u(CanSwim x) {\n", - " x.swim();\n", - " }\n", - " \n", - " public static void v(CanFly x) {\n", - " x.fly();\n", - " }\n", - " \n", - " public static void w(ActionCharacter x) {\n", - " x.fight();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Hero h = new Hero();\n", - " t(h); // Treat it as a CanFight\n", - " u(h); // Treat it as a CanSwim\n", - " v(h); // Treat it as a CanFly\n", - " w(h); // Treat it as an ActionCharacter\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "类 **Hero** 结合了具体类 **ActionCharacter** 和接口 **CanFight**、**CanSwim** 和 **CanFly**。当通过这种方式结合具体类和接口时,需要将具体类放在前面,后面跟着接口(否则编译器会报错)。\n", - "\n", - "接口 **CanFight** 和类 **ActionCharacter** 中的 `fight()` 方法签名相同,而在类 Hero 中也没有提供 `fight()` 的定义。可以扩展一个接口,但是得到的是另一个接口。当想创建一个对象时,所有的定义必须首先都存在。类 **Hero** 中没有显式地提供 `fight()` 的定义,是由于该方法在类 **ActionCharacter** 中已经定义过,这样才使得创建 **Hero** 对象成为可能。\n", - "\n", - "在类 **Adventure** 中可以看到四个方法,它们把不同的接口和具体类作为参数。当创建一个 **Hero** 对象时,它可以被传入这些方法中的任意一个,意味着它可以依次向上转型为每个接口。Java 中这种接口的设计方式,使得程序员不需要付出特别的努力。\n", - "\n", - "记住,前面例子展示了使用接口的核心原因之一:为了能够向上转型为多个基类型(以及由此带来的灵活性)。然而,使用接口的第二个原因与使用抽象基类相同:防止客户端程序员创建这个类的对象,确保这仅仅只是一个接口。这带来了一个问题:应该使用接口还是抽象类呢?如果创建不带任何方法定义或成员变量的基类,就选择接口而不是抽象类。事实上,如果知道某事物是一个基类,可以考虑用接口实现它(这个主题在本章总结会再次讨论)。\n", - "\n", - "\n", - "\n", - "## 使用继承扩展接口\n", - "\n", - "通过继承,可以很容易在接口中增加方法声明,还可以在新接口中结合多个接口。这两种情况都可以得到新接口,如下例所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/HorrorShow.java\n", - "// Extending an interface with inheritance\n", - "interface Monster {\n", - " void menace();\n", - "}\n", - "\n", - "interface DangerousMonster extends Monster {\n", - " void destroy();\n", - "}\n", - "\n", - "interface Lethal {\n", - " void kill();\n", - "}\n", - "\n", - "class DragonZilla implements DangerousMonster {\n", - " @Override\n", - " public void menace() {}\n", - " \n", - " @Override\n", - " public void destroy() {}\n", - "}\n", - "\n", - "interface Vampire extends DangerousMonster, Lethal {\n", - " void drinkBlood();\n", - "}\n", - "\n", - "class VeryBadVampire implements Vampire {\n", - " @Override\n", - " public void menace() {}\n", - " \n", - " @Override\n", - " public void destroy() {}\n", - " \n", - " @Override\n", - " public void kill() {}\n", - " \n", - " @Override\n", - " public void drinkBlood() {}\n", - "}\n", - "\n", - "public class HorrorShow {\n", - " static void u(Monster b) {\n", - " b.menace();\n", - " }\n", - " \n", - " static void v(DangerousMonster d) {\n", - " d.menace();\n", - " d.destroy();\n", - " }\n", - " \n", - " static void w(Lethal l) {\n", - " l.kill();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " DangerousMonster barney = new DragonZilla();\n", - " u(barney);\n", - " v(barney);\n", - " Vampire vlad = new VeryBadVampire();\n", - " u(vlad);\n", - " v(vlad);\n", - " w(vlad);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接口 **DangerousMonster** 是 **Monster** 简单扩展的一个新接口,类 **DragonZilla** 实现了这个接口。\n", - "\n", - "**Vampire** 中使用的语法仅适用于接口继承。通常来说,**extends** 只能用于单一类,但是在构建接口时可以引用多个基类接口。注意到,接口名之间用逗号分隔。\n", - "\n", - "### 结合接口时的命名冲突\n", - "\n", - "当实现多个接口时可能会存在一个小陷阱。在前面的例子中,**CanFight** 和 **ActionCharacter** 具有完全相同的 `fight()` 方法。完全相同的方法没有问题,但是如果它们的签名或返回类型不同会怎么样呢?这里有一个例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/InterfaceCollision.java\n", - "interface I1 {\n", - " void f();\n", - "}\n", - "\n", - "interface I2 {\n", - " int f(int i);\n", - "}\n", - "\n", - "interface I3 {\n", - " int f();\n", - "}\n", - "\n", - "class C {\n", - " public int f() {\n", - " return 1;\n", - " }\n", - "}\n", - "\n", - "class C2 implements I1, I2 {\n", - " @Override\n", - " public void f() {}\n", - " \n", - " @Override\n", - " public int f(int i) {\n", - " return 1; // 重载\n", - " }\n", - "}\n", - "\n", - "class C3 extends C implements I2 {\n", - " @Override\n", - " public int f(int i) {\n", - " return 1; // 重载\n", - " }\n", - "}\n", - "\n", - "class C4 extends C implements I3 {\n", - " // 完全相同,没问题\n", - " @Override\n", - " public int f() {\n", - " return 1;\n", - " }\n", - "}\n", - "\n", - "// 方法的返回类型不同\n", - "//- class C5 extends C implements I1 {}\n", - "//- interface I4 extends I1, I3 {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "覆写、实现和重载令人不快地搅和在一起带来了困难。同时,重载方法仅根据返回类型是区分不了的。当不注释最后两行时,报错信息如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "error: C5 is not abstract and does not override abstract\n", - "method f() in I1\n", - "class C5 extends C implements I1 {}\n", - "error: types I3 and I1 are incompatible; both define f(),\n", - "but with unrelated return types\n", - "interfacce I4 extends I1, I3 {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当打算组合接口时,在不同的接口中使用相同的方法名通常会造成代码可读性的混乱,尽量避免这种情况。\n", - "\n", - "\n", - "\n", - "## 接口适配\n", - "\n", - "接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象给方法则交由你来做。\n", - "\n", - "因此,接口的一种常见用法是前面提到的*策略*设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性。\n", - "\n", - "例如,类 **Scanner** 的构造器接受的是一个 **Readable** 接口(在“字符串”一章中学习更多相关内容)。你会发现 **Readable** 没有用作 Java 标准库中其他任何方法的参数——它是单独为 **Scanner** 创建的,因此 **Scanner** 没有将其参数限制为某个特定类。通过这种方式,**Scanner** 可以与更多的类型协作。如果你创建了一个新类并想让 **Scanner** 作用于它,就让它实现 **Readable** 接口,像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/RandomStrings.java\n", - "// Implementing an interface to conform to a method\n", - "import java.nio.*;\n", - "import java.util.*;\n", - "\n", - "public class RandomStrings implements Readable {\n", - " private static Random rand = new Random(47);\n", - " private static final char[] CAPITALS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", - " private static final char[] LOWERS = \"abcdefghijklmnopqrstuvwxyz\".toCharArray();\n", - " private static final char[] VOWELS = \"aeiou\".toCharArray();\n", - " private int count;\n", - " \n", - " public RandomStrings(int count) {\n", - " this.count = count;\n", - " }\n", - " \n", - " @Override\n", - " public int read(CharBuffer cb) {\n", - " if (count-- == 0) {\n", - " return -1; // indicates end of input\n", - " }\n", - " cb.append(CAPITALS[rand.nextInt(CAPITALS.length)]);\n", - " for (int i = 0; i < 4; i++) {\n", - " cb.append(VOWELS[rand.nextInt(VOWELS.length)]);\n", - " cb.append(LOWERS[rand.nextInt(LOWERS.length)]);\n", - " }\n", - " cb.append(\" \");\n", - " return 10; // Number of characters appended\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Scanner s = new Scanner(new RandomStrings(10));\n", - " while (s.hasNext()) {\n", - " System.out.println(s.next());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Yazeruyac\n", - "Fowenucor\n", - "Goeazimom\n", - "Raeuuacio\n", - "Nuoadesiw\n", - "Hageaikux\n", - "Ruqicibui\n", - "Numasetih\n", - "Kuuuuozog\n", - "Waqizeyoy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Readable** 接口只需要实现 `read()` 方法(注意 `@Override` 注解的突出方法)。在 `read()` 方法里,将输入内容添加到 **CharBuffer** 参数中(有多种方法可以实现,查看 **CharBuffer** 文档),或在没有输入时返回 **-1**。\n", - "\n", - "假设你有一个类没有实现 **Readable** 接口,怎样才能让 **Scanner** 作用于它呢?下面是一个产生随机浮点数的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/RandomDoubles.java\n", - "import java.util.*;\n", - "\n", - "public interface RandomDoubles {\n", - " Random RAND = new Random(47);\n", - " \n", - " default double next() {\n", - " return RAND.nextDouble();\n", - " }\n", - " \n", - " static void main(String[] args) {\n", - " RandomDoubles rd = new RandomDoubles(){};\n", - " for (int i = 0; i < 7; i++) {\n", - " System.out.println(rd.next() + \" \");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0.7271157860730044 \n", - "0.5309454508634242 \n", - "0.16020656493302599 \n", - "0.18847866977771732 \n", - "0.5166020801268457 \n", - "0.2678662084200585 \n", - "0.2613610344283964" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以再次使用适配器模式,但这里适配器类可以实现两个接口。因此,通过关键字 **interface** 提供的多继承,我们可以创建一个既是 **RandomDoubles**,又是 **Readable** 的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/AdaptedRandomDoubles.java\n", - "// creating an adapter with inheritance\n", - "import java.nio.*;\n", - "import java.util.*;\n", - "\n", - "public class AdaptedRandomDoubles implements RandomDoubles, Readable {\n", - " private int count;\n", - " \n", - " public AdaptedRandomDoubles(int count) {\n", - " this.count = count;\n", - " }\n", - " \n", - " @Override\n", - " public int read(CharBuffer cb) {\n", - " if (count-- == 0) {\n", - " return -1;\n", - " }\n", - " String result = Double.toString(next()) + \" \";\n", - " cb.append(result);\n", - " return result.length();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Scanner s = new Scanner(new AdaptedRandomDoubles(7));\n", - " while (s.hasNextDouble()) {\n", - " System.out.print(s.nextDouble() + \" \");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0.7271157860730044 0.5309454508634242 \n", - "0.16020656493302599 0.18847866977771732 \n", - "0.5166020801268457 0.2678662084200585 \n", - "0.2613610344283964" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为你可以以这种方式在已有类中增加新接口,所以这就意味着一个接受接口类型的方法提供了一种让任何类都可以与该方法进行适配的方式。这就是使用接口而不是类的强大之处。\n", - "\n", - "\n", - "\n", - "## 接口字段\n", - "\n", - "因为接口中的字段都自动是 **static** 和 **final** 的,所以接口就成为了创建一组常量的方便的工具。在 Java 5 之前,这是产生与 C 或 C++ 中的 enum (枚举类型) 具有相同效果的唯一方式。所以你可能在 Java 5 之前的代码中看到:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Months.java\n", - "// Using interfaces to create groups of constants\n", - "public interface Months {\n", - " int \n", - " JANUARY = 1, FEBRUARY = 2, MARCH = 3,\n", - " APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,\n", - " AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,\n", - " NOVEMBER = 11, DECEMBER = 12;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意 Java 中使用大写字母的风格定义具有初始化值的 **static** **final** 变量。接口中的字段自动是 **public** 的,所以没有显式指明这点。\n", - "\n", - "自 Java 5 开始,我们有了更加强大和灵活的关键字 **enum**,那么在接口中定义常量组就显得没什么意义了。然而当你阅读遗留的代码时,在很多场合你还会碰到这种旧的习惯用法。在“枚举”一章中你会学习到更多关于枚举的内容。\n", - "\n", - "### 初始化接口中的字段\n", - "\n", - "接口中定义的字段不能是“空 **final**\",但是可以用非常量表达式初始化。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/RandVals.java\n", - "// Initializing interface fields with\n", - "// non-constant initializers\n", - "import java.util.*;\n", - "\n", - "public interface RandVals {\n", - " Random RAND = new Random(47);\n", - " int RANDOM_INT = RAND.nextInt(10);\n", - " long RANDOM_LONG = RAND.nextLong() * 10;\n", - " float RANDOM_FLOAT = RAND.nextLong() * 10;\n", - " double RANDOM_DOUBLE = RAND.nextDouble() * 10;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为字段是 **static** 的,所以它们在类第一次被加载时初始化,这发生在任何字段首次被访问时。下面是个简单的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/TestRandVals.java\n", - "public class TestRandVals {\n", - " public static void main(String[] args) {\n", - " System.out.println(RandVals.RANDOM_INT);\n", - " System.out.println(RandVals.RANDOM_LONG);\n", - " System.out.println(RandVals.RANDOM_FLOAT);\n", - " System.out.println(RandVals.RANDOM_DOUBLE);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "8\n", - "-32032247016559954\n", - "-8.5939291E18\n", - "5.779976127815049" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些字段不是接口的一部分,它们的值被存储在接口的静态存储区域中。\n", - "\n", - "\n", - "\n", - "## 接口嵌套\n", - "\n", - "接口可以嵌套在类或其他接口中。下面揭示一些有趣的特性:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/nesting/NestingInterfaces.java\n", - "// {java interfaces.nesting.NestingInterfaces}\n", - "package interfaces.nesting;\n", - "\n", - "class A {\n", - " interface B {\n", - " void f();\n", - " }\n", - " \n", - " public class BImp implements B {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " public class BImp2 implements B {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " public interface C {\n", - " void f();\n", - " }\n", - " \n", - " class CImp implements C {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " private class CImp2 implements C {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " private interface D {\n", - " void f();\n", - " }\n", - " \n", - " private class DImp implements D {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " public class DImp2 implements D {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " public D getD() {\n", - " return new DImp2();\n", - " }\n", - " \n", - " private D dRef;\n", - " \n", - " public void receiveD(D d) {\n", - " dRef = d;\n", - " dRef.f();\n", - " }\n", - "}\n", - "\n", - "interface E {\n", - " interface G {\n", - " void f();\n", - " }\n", - " // Redundant \"public\"\n", - " public interface H {\n", - " void f();\n", - " }\n", - " \n", - " void g();\n", - " // Cannot be private within an interface\n", - " //- private interface I {}\n", - "}\n", - "\n", - "public class NestingInterfaces {\n", - " public class BImp implements A.B {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " class CImp implements A.C {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " // Cannot implements a private interface except\n", - " // within that interface's defining class:\n", - " //- class DImp implements A.D {\n", - " //- public void f() {}\n", - " //- }\n", - " class EImp implements E {\n", - " @Override\n", - " public void g() {}\n", - " }\n", - " \n", - " class EGImp implements E.G {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " \n", - " class EImp2 implements E {\n", - " @Override\n", - " public void g() {}\n", - " \n", - " class EG implements E.G {\n", - " @Override\n", - " public void f() {}\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " A a = new A();\n", - " // Can't access to A.D:\n", - " //- A.D ad = a.getD();\n", - " // Doesn't return anything but A.D:\n", - " //- A.DImp2 di2 = a.getD();\n", - " // cannot access a member of the interface:\n", - " //- a.getD().f();\n", - " // Only another A can do anything with getD():\n", - " A a2 = new A();\n", - " a2.receiveD(a.getD());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在类中嵌套接口的语法是相当显而易见的。就像非嵌套接口一样,它们具有 **public** 或包访问权限的可见性。\n", - "\n", - "作为一种新添加的方式,接口也可以是 **private** 的,例如 **A.D**(同样的语法同时适用于嵌套接口和嵌套类)。那么 **private** 嵌套接口有什么好处呢?你可能猜测它只是被用来实现一个 **private** 内部类,就像 **DImp**。然而 **A.DImp2** 展示了它可以被实现为 **public** 类,但是 **A.DImp2** 只能被自己使用,你无法说它实现了 **private** 接口 **D**,所以实现 **private** 接口是一种可以强制该接口中的方法定义不会添加任何类型信息(即不可以向上转型)的方式。\n", - "\n", - "`getD()` 方法产生了一个与 **private** 接口有关的窘境。它是一个 **public** 方法却返回了对 **private** 接口的引用。能对这个返回值做些什么呢?`main()` 方法里进行了一些使用返回值的尝试但都失败了。返回值必须交给有权使用它的对象,本例中另一个 **A** 通过 `receiveD()` 方法接受了它。\n", - "\n", - "接口 **E** 说明了接口之间也能嵌套。然而,作用于接口的规则——尤其是,接口中的元素必须是 **public** 的——在此都会被严格执行,所以嵌套在另一个接口中的接口自动就是 **public** 的,不能指明为 **private**。\n", - "\n", - "类 **NestingInterfaces** 展示了嵌套接口的不同实现方式。尤其是当实现某个接口时,并不需要实现嵌套在其内部的接口。同时,**private** 接口不能在定义它的类之外被实现。\n", - "\n", - "添加这些特性的最初原因看起来像是出于对严格的语法一致性的考虑,但是我通常认为,一旦你了解了某种特性,就总能找到其用武之地。\n", - "\n", - "\n", - "\n", - "## 接口和工厂方法模式\n", - "\n", - "接口是多实现的途径,而生成符合某个接口的对象的典型方式是*工厂方法*设计模式。不同于直接调用构造器,只需调用工厂对象中的创建方法就能生成对象的实现——理论上,通过这种方式可以将接口与实现的代码完全分离,使得可以透明地将某个实现替换为另一个实现。这里是一个展示工厂方法结构的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Factories.java\n", - "interface Service {\n", - " void method1();\n", - " void method2();\n", - "}\n", - "\n", - "interface ServiceFactory {\n", - " Service getService();\n", - "}\n", - "\n", - "class Service1 implements Service {\n", - " Service1() {} // Package access\n", - " \n", - " @Override\n", - " public void method1() {\n", - " System.out.println(\"Service1 method1\");\n", - " }\n", - " \n", - " @Override\n", - " public void method2() {\n", - " System.out.println(\"Service1 method2\");\n", - " }\n", - "}\n", - "\n", - "class Service1Factory implements ServiceFactory {\n", - " @Override\n", - " public Service getService() {\n", - " return new Service1();\n", - " }\n", - "}\n", - "\n", - "class Service2 implements Service {\n", - " Service2() {} // Package access\n", - " \n", - " @Override\n", - " public void method1() {\n", - " System.out.println(\"Service2 method1\");\n", - " }\n", - " \n", - " @Override\n", - " public void method2() {\n", - " System.out.println(\"Service2 method2\");\n", - " }\n", - "}\n", - "\n", - "class Service2Factory implements ServiceFactory {\n", - " @Override\n", - " public Service getService() {\n", - " return new Service2();\n", - " }\n", - "}\n", - "\n", - "public class Factories {\n", - " public static void serviceConsumer(ServiceFactory fact) {\n", - " Service s = fact.getService();\n", - " s.method1();\n", - " s.method2();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " serviceConsumer(new Service1Factory());\n", - " // Services are completely interchangeable:\n", - " serviceConsumer(new Service2Factory());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Service1 method1\n", - "Service1 method2\n", - "Service2 method1\n", - "Service2 method2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有工厂方法,代码就必须在某处指定将要创建的 **Service** 的确切类型,从而调用恰当的构造器。\n", - "\n", - "为什么要添加额外的间接层呢?一个常见的原因是创建框架。假设你正在创建一个游戏系统;例如,在相同的棋盘下国际象棋和西洋跳棋:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// interfaces/Games.java\n", - "// A Game framework using Factory Methods\n", - "interface Game {\n", - " boolean move();\n", - "}\n", - "\n", - "interface GameFactory {\n", - " Game getGame();\n", - "}\n", - "\n", - "class Checkers implements Game {\n", - " private int moves = 0;\n", - " private static final int MOVES = 3;\n", - " \n", - " @Override\n", - " public boolean move() {\n", - " System.out.println(\"Checkers move \" + moves);\n", - " return ++moves != MOVES;\n", - " }\n", - "}\n", - "\n", - "class CheckersFactory implements GameFactory {\n", - " @Override\n", - " public Game getGame() {\n", - " return new Checkers();\n", - " }\n", - "}\n", - "\n", - "class Chess implements Game {\n", - " private int moves = 0;\n", - " private static final int MOVES = 4;\n", - " \n", - " @Override\n", - " public boolean move() {\n", - " System.out.println(\"Chess move \" + moves);\n", - " return ++moves != MOVES;\n", - " }\n", - "}\n", - "\n", - "class ChessFactory implements GameFactory {\n", - " @Override\n", - " public Game getGame() {\n", - " return new Chess();\n", - " }\n", - "}\n", - "\n", - "public class Games {\n", - " public static void playGame(GameFactory factory) {\n", - " Game s = factory.getGame();\n", - " while (s.move()) {\n", - " ;\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " playGame(new CheckersFactory());\n", - " playGame(new ChessFactory());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Checkers move 0\n", - "Checkers move 1\n", - "Checkers move 2\n", - "Chess move 0\n", - "Chess move 1\n", - "Chess move 2\n", - "Chess move 3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果类 **Games** 表示一段很复杂的代码,那么这种方式意味着你可以在不同类型的游戏里复用这段代码。你可以再想象一些能够从这个模式中受益的更加精巧的游戏。\n", - "\n", - "在下一章,你将会看到一种更加优雅的使用匿名内部类的工厂实现方式。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "认为接口是好的选择,从而使用接口不用具体类,这具有诱惑性。几乎任何时候,创建类都可以替代为创建一个接口和工厂。\n", - "\n", - "很多人都掉进了这个陷阱,只要有可能就创建接口和工厂。这种逻辑看起来像是可能会使用不同的实现,所以总是添加这种抽象性。这变成了一种过早的设计优化。\n", - "\n", - "任何抽象性都应该是由真正的需求驱动的。当有必要时才应该使用接口进行重构,而不是到处添加额外的间接层,从而带来额外的复杂性。这种复杂性非常显著,如果你让某人去处理这种复杂性,只是因为你意识到“以防万一”而添加新接口,而没有其他具有说服力的原因——好吧,如果我碰上了这种设计,就会质疑此人所作的所有其他设计了。\n", - "\n", - "恰当的原则是优先使用类而不是接口。从类开始,如果使用接口的必要性变得很明确,那么就重构。接口是一个伟大的工具,但它们容易被滥用。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/11-Inner-Classes.ipynb b/jupyter/11-Inner-Classes.ipynb deleted file mode 100644 index 5d361004..00000000 --- a/jupyter/11-Inner-Classes.ipynb +++ /dev/null @@ -1,2189 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第十一章 内部类\n", - "\n", - "> 一个定义在另一个类中的类,叫作内部类。\n", - "\n", - "内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。\n", - "\n", - "最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,\"Why inner classes?\"就应该使得内部类的益处明确显现了。\n", - "\n", - "本章剩余部分包含了对内部类语法更加详尽的探索,这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始不需要。因此,本章最初的部分也许就是你现在所需的全部,你可以将更详尽的探索当作参考资料。\n", - "\n", - "\n", - "\n", - "## 创建内部类\n", - "\n", - "创建内部类的方式就如同你想的一样——把类的定义置于外围类的里面:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel1.java\n", - "// Creating inner classes\n", - "public class Parcel1 {\n", - " class Contents {\n", - " private int i = 11;\n", - " \n", - " public int value() { return i; }\n", - " }\n", - " \n", - " class Destination {\n", - " private String label;\n", - " \n", - " Destination(String whereTo) {\n", - " label = whereTo;\n", - " }\n", - " \n", - " String readLabel() { return label; }\n", - " }\n", - " // Using inner classes looks just like\n", - " // using any other class, within Parcel1:\n", - " public void ship(String dest) {\n", - " Contents c = new Contents();\n", - " Destination d = new Destination(dest);\n", - " System.out.println(d.readLabel());\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Parcel1 p = new Parcel1();\n", - " p.ship(\"Tasmania\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Tasmania" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当我们在 `ship()` 方法里面使用内部类的时候,与使用普通类没什么不同。在这里,明显的区别只是内部类的名字是嵌套在 **Parcel1** 里面的。\n", - "\n", - "更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用,就像在 `to()` 和 `contents()` 方法中看到的那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel2.java\n", - "// Returning a reference to an inner class\n", - "public class Parcel2 {\n", - " class Contents {\n", - " private int i = 11;\n", - " \n", - " public int value() { return i; }\n", - " }\n", - " \n", - " class Destination {\n", - " private String label;\n", - " \n", - " Destination(String whereTo) {\n", - " label = whereTo;\n", - " }\n", - " \n", - " String readLabel() { return label; }\n", - " }\n", - " \n", - " public Destination to(String s) {\n", - " return new Destination(s);\n", - " }\n", - " \n", - " public Contents contents() {\n", - " return new Contents();\n", - " }\n", - " \n", - " public void ship(String dest) {\n", - " Contents c = contents();\n", - " Destination d = to(dest);\n", - " System.out.println(d.readLabel());\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Parcel2 p = new Parcel2();\n", - " p.ship(\"Tasmania\");\n", - " Parcel2 q = new Parcel2();\n", - " // Defining references to inner classes:\n", - " Parcel2.Contents c = q.contents();\n", - " Parcel2.Destination d = q.to(\"Borneo\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Tasmania" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在 `main()` 方法中那样,具体地指明这个对象的类型:*OuterClassName.InnerClassName*。(译者注:在外部类的静态方法中也可以直接指明类型 *InnerClassName*,在其他类中需要指明 *OuterClassName.InnerClassName*。)\n", - "\n", - "\n", - "\n", - "## 链接外部类\n", - "\n", - "到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Sequence.java\n", - "// Holds a sequence of Objects\n", - "interface Selector {\n", - " boolean end();\n", - " Object current();\n", - " void next();\n", - "}\n", - "public class Sequence {\n", - " private Object[] items;\n", - " private int next = 0;\n", - " public Sequence(int size) {\n", - " items = new Object[size];\n", - " }\n", - " public void add(Object x) {\n", - " if(next < items.length)\n", - " items[next++] = x;\n", - " }\n", - " private class SequenceSelector implements Selector {\n", - " private int i = 0;\n", - " @Override\n", - " public boolean end() { return i == items.length; }\n", - " @Override\n", - " public Object current() { return items[i]; }\n", - " @Override\n", - " public void next() { if(i < items.length) i++; }\n", - " }\n", - " public Selector selector() {\n", - " return new SequenceSelector();\n", - " }\n", - " public static void main(String[] args) {\n", - " Sequence sequence = new Sequence(10);\n", - " for(int i = 0; i < 10; i++)\n", - " sequence.add(Integer.toString(i));\n", - " Selector selector = sequence.selector();\n", - " while(!selector.end()) {\n", - " System.out.print(selector.current() + \" \");\n", - " selector.next();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0 1 2 3 4 5 6 7 8 9" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。\n", - "\n", - "这里,**SequenceSelector** 是提供 **Selector** 功能的 **private** 类。可以看到,在 `main()` 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 `selector()` 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。\n", - "最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外围类中的一个 **private** 字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。\n", - "\n", - "所以内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。\n", - "\n", - "\n", - "## 使用 .this 和 .new\n", - "\n", - "如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和 **this**。这样产生的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 **.this**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/DotThis.java\n", - "// Accessing the outer-class object\n", - "public class DotThis {\n", - " void f() { System.out.println(\"DotThis.f()\"); }\n", - " \n", - " public class Inner {\n", - " public DotThis outer() {\n", - " return DotThis.this;\n", - " // A plain \"this\" would be Inner's \"this\"\n", - " }\n", - " }\n", - " \n", - " public Inner inner() { return new Inner(); }\n", - " \n", - " public static void main(String[] args) {\n", - " DotThis dt = new DotThis();\n", - " DotThis.Inner dti = dt.inner();\n", - " dti.outer().f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "DotThis.f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有时你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在 **new** 表达式中提供对其他外部类对象的引用,这是需要使用 **.new** 语法,就像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/DotNew.java\n", - "// Creating an inner class directly using .new syntax\n", - "public class DotNew {\n", - " public class Inner {}\n", - " public static void main(String[] args) {\n", - " DotNew dn = new DotNew();\n", - " DotNew.Inner dni = dn.new Inner();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要想直接创建内部类的对象,你不能按照你想象的方式,去引用外部类的名字 **DotNew**,而是必须使用外部类的对象来创建该内部类对象,就像在上面的程序中所看到的那样。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你不能声明)dn.new DotNew.Inner。\n", - "\n", - "下面你可以看到将 **.new** 应用于 Parcel 的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel3.java\n", - "// Using .new to create instances of inner classes\n", - "public class Parcel3 {\n", - " class Contents {\n", - " private int i = 11;\n", - " public int value() { return i; }\n", - " }\n", - " class Destination {\n", - " private String label;\n", - " Destination(String whereTo) { label = whereTo; }\n", - " String readLabel() { return label; }\n", - " }\n", - " public static void main(String[] args) {\n", - " Parcel3 p = new Parcel3();\n", - " // Must use instance of outer class\n", - " // to create an instance of the inner class:\n", - " Parcel3.Contents c = p.new Contents();\n", - " Parcel3.Destination d =\n", - " p.new Destination(\"Tasmania\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。\n", - "\n", - "\n", - "\n", - "## 内部类与向上转型\n", - "\n", - "当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。)这是因为此内部类-某个接口的实现-能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。\n", - "\n", - "我们可以创建前一个示例的接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Destination.java\n", - "public interface Destination {\n", - " String readLabel();\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Contents.java\n", - "public interface Contents {\n", - " int value();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在 **Contents** 和 **Destination** 表示客户端程序员可用的接口。记住,接口的所有成员自动被设置为 **public**。\n", - "\n", - "当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型,看下面的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/TestParcel.java\n", - "class Parcel4 {\n", - " private class PContents implements Contents {\n", - " private int i = 11;\n", - " @Override\n", - " public int value() { return i; }\n", - " }\n", - " protected final class PDestination implements Destination {\n", - " private String label;\n", - " private PDestination(String whereTo) {\n", - " label = whereTo;\n", - " }\n", - " @Override\n", - " public String readLabel() { return label; }\n", - " }\n", - " public Destination destination(String s) {\n", - " return new PDestination(s);\n", - " }\n", - " public Contents contents() {\n", - " return new PContents();\n", - " }\n", - "}\n", - "public class TestParcel {\n", - " public static void main(String[] args) {\n", - " Parcel4 p = new Parcel4();\n", - " Contents c = p.contents();\n", - " Destination d = p.destination(\"Tasmania\");\n", - " // Illegal -- can't access private class:\n", - " //- Parcel4.PContents pc = p.new PContents();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **Parcel4** 中,内部类 **PContents** 是 **private**,所以除了 **Parcel4**,没有人能访问它。普通(非内部)类的访问权限不能被设为 **private** 或者 **protected**;他们只能设置为 **public** 或 **package** 访问权限。\n", - "\n", - "**PDestination** 是 **protected**,所以只有 **Parcel4** 及其子类、还有与 **Parcel4** 同一个包中的类(因为 **protected** 也给予了包访问权)能访问 **PDestination**,其他类都不能访问 **PDestination**,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成 **private** 内部类(或 **protected** 内部类,除非是继承自它的子类),因为不能访问其名字,就像在 **TestParcel** 类中看到的那样。\n", - "\n", - "**private** 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给 Java 编译器提供了生成高效代码的机会。\n", - "\n", - "\n", - "\n", - "## 内部类方法和作用域\n", - "\n", - "到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。\n", - "\n", - "这么做有两个理由:\n", - "\n", - "1. 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。\n", - "2. 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。\n", - "\n", - "在后面的例子中,先前的代码将被修改,以用来实现:\n", - "\n", - "1. 一个定义在方法中的类。\n", - "2. 一个定义在作用域内的类,此作用域在方法的内部。\n", - "3. 一个实现了接口的匿名类。\n", - "4. 一个匿名类,它扩展了没有默认构造器的类。\n", - "5. 一个匿名类,它执行字段初始化。\n", - "6. 一个匿名类,它通过实例初始化实现构造(匿名内部类不可能有构造器)。\n", - "\n", - "第一个例子展示了在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类。这被称作局部内部类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel5.java\n", - "// Nesting a class within a method\n", - "public class Parcel5 {\n", - " public Destination destination(String s) {\n", - " final class PDestination implements Destination {\n", - " private String label;\n", - " \n", - " private PDestination(String whereTo) {\n", - " label = whereTo;\n", - " }\n", - " \n", - " @Override\n", - " public String readLabel() { return label; }\n", - " }\n", - " return new PDestination(s);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Parcel5 p = new Parcel5();\n", - " Destination d = p.destination(\"Tasmania\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**PDestination** 类是 `destination()` 方法的一部分,而不是 **Parcel5** 的一部分。所以,在 `destination()` 之外不能访问 **PDestination**,注意出现在 **return** 语句中的向上转型-返回的是 **Destination** 的引用,它是 **PDestination** 的基类。当然,在 `destination()` 中定义了内部类 **PDestination**,并不意味着一旦 `destination()` 方法执行完毕,**PDestination** 就不可用了。\n", - "\n", - "你可以在同一个子目录下的任意类中对某个内部类使用类标识符 **PDestination**,这并不会有命名冲突。\n", - "\n", - "下面的例子展示了如何在任意的作用域内嵌入一个内部类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel6.java\n", - "// Nesting a class within a scope\n", - "public class Parcel6 {\n", - " private void internalTracking(boolean b) {\n", - " if(b) {\n", - " class TrackingSlip {\n", - " private String id;\n", - " TrackingSlip(String s) {\n", - " id = s;\n", - " }\n", - " String getSlip() { return id; }\n", - " }\n", - " TrackingSlip ts = new TrackingSlip(\"slip\");\n", - " String s = ts.getSlip();\n", - " }\n", - " // Can't use it here! Out of scope:\n", - " //- TrackingSlip ts = new TrackingSlip(\"x\");\n", - " }\n", - " public void track() { internalTracking(true); }\n", - " public static void main(String[] args) {\n", - " Parcel6 p = new Parcel6();\n", - " p.track();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**TrackingSlip** 类被嵌入在 **if** 语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义 **Trackingslip** 的作用域之外,它是不可用的,除此之外,它与普通的类一样。\n", - "\n", - "\n", - "\n", - "## 匿名内部类\n", - "\n", - "下面的例子看起来有点奇怪:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel7.java\n", - "// Returning an instance of an anonymous inner class\n", - "public class Parcel7 {\n", - " public Contents contents() {\n", - " return new Contents() { // Insert class definition\n", - " private int i = 11;\n", - " \n", - " @Override\n", - " public int value() { return i; }\n", - " }; // Semicolon required\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Parcel7 p = new Parcel7();\n", - " Contents c = p.contents();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`contents()` 方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个 **Contents** 对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义。”\n", - "\n", - "这种奇怪的语法指的是:“创建一个继承自 **Contents** 的匿名类的对象。”通过 **new** 表达式返回的引用被自动向上转型为对 **Contents** 的引用。上述匿名内部类的语法是下述形式的简化形式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel7b.java\n", - "// Expanded version of Parcel7.java\n", - "public class Parcel7b {\n", - " class MyContents implements Contents {\n", - " private int i = 11;\n", - " @Override\n", - " public int value() { return i; }\n", - " }\n", - " \n", - " public Contents contents() {\n", - " return new MyContents();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Parcel7b p = new Parcel7b();\n", - " Contents c = p.contents();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个匿名内部类中,使用了默认的构造器来生成 **Contents**。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel8.java\n", - "// Calling the base-class constructor\n", - "public class Parcel8 {\n", - " public Wrapping wrapping(int x) {\n", - " // Base constructor call:\n", - " return new Wrapping(x) { // [1]\n", - " @Override\n", - " public int value() {\n", - " return super.value() * 47;\n", - " }\n", - " }; // [2]\n", - " }\n", - " public static void main(String[] args) {\n", - " Parcel8 p = new Parcel8();\n", - " Wrapping w = p.wrapping(10);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- \\[1\\] 将合适的参数传递给基类的构造器。\n", - "- \\[2\\] 在匿名内部类末尾的分号,并不是用来标记此内部类结束的。实际上,它标记的是表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。\n", - "\n", - "尽管 **Wrapping** 只是一个具有具体实现的普通类,但它还是被导出类当作公共“接口”来使用。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Wrapping.java\n", - "public class Wrapping {\n", - " private int i;\n", - " public Wrapping(int x) { i = x; }\n", - " public int value() { return i; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了多样性,**Wrapping** 拥有一个要求传递一个参数的构造器。\n", - "\n", - "在匿名类中定义字段时,还能够对其执行初始化操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel9.java\n", - "public class Parcel9 {\n", - " // Argument must be final or \"effectively final\"\n", - " // to use within the anonymous inner class:\n", - " public Destination destination(final String dest) {\n", - " return new Destination() {\n", - " private String label = dest;\n", - " @Override\n", - " public String readLabel() { return label; }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " Parcel9 p = new Parcel9();\n", - " Destination d = p.destination(\"Tasmania\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 **final** 的(也就是说,它在初始化后不会改变,所以可以被当作 **final**),就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但是通常最好加上 **final** 作为一种暗示。\n", - "\n", - "如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/AnonymousConstructor.java\n", - "// Creating a constructor for an anonymous inner class\n", - "abstract class Base {\n", - " Base(int i) {\n", - " System.out.println(\"Base constructor, i = \" + i);\n", - " }\n", - " public abstract void f();\n", - "}\n", - "public class AnonymousConstructor {\n", - " public static Base getBase(int i) {\n", - " return new Base(i) {\n", - " { System.out.println(\n", - " \"Inside instance initializer\"); }\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"In anonymous f()\");\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " Base base = getBase(47);\n", - " base.f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Base constructor, i = 47\n", - "Inside instance initializer\n", - "In anonymous f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在此例中,不要求变量一定是 **final** 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。\n", - "\n", - "下例是带实例初始化的\"parcel\"形式。注意 `destination()` 的参数必须是 **final** 的,因为它们是在匿名类内部使用的(译者注:即使不加 **final**, Java 8 的编译器也会为我们自动加上 **final**,以保证数据的一致性)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel10.java\n", - "// Using \"instance initialization\" to perform\n", - "// construction on an anonymous inner class\n", - "public class Parcel10 {\n", - " public Destination\n", - " destination(final String dest, final float price) {\n", - " return new Destination() {\n", - " private int cost;\n", - " // Instance initialization for each object:\n", - " {\n", - " cost = Math.round(price);\n", - " if(cost > 100)\n", - " System.out.println(\"Over budget!\");\n", - " }\n", - " private String label = dest;\n", - " @Override\n", - " public String readLabel() { return label; }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " Parcel10 p = new Parcel10();\n", - " Destination d = p.destination(\"Tasmania\", 101.395F);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Over budget!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 **if** 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。\n", - "\n", - "匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。\n", - "\n", - "\n", - "\n", - "## 嵌套类\n", - "\n", - "如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着:\n", - "\n", - "1. 要创建嵌套类的对象,并不需要其外围类的对象。\n", - "2. 不能从嵌套类的对象中访问非静态的外围类对象。\n", - "\n", - "嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Parcel11.java\n", - "// Nested classes (static inner classes)\n", - "public class Parcel11 {\n", - " private static class ParcelContents implements Contents {\n", - " private int i = 11;\n", - " @Override\n", - " public int value() { return i; }\n", - " }\n", - " protected static final class ParcelDestination\n", - " implements Destination {\n", - " private String label;\n", - " private ParcelDestination(String whereTo) {\n", - " label = whereTo;\n", - " }\n", - " @Override\n", - " public String readLabel() { return label; }\n", - " // Nested classes can contain other static elements:\n", - " public static void f() {}\n", - " static int x = 10;\n", - " static class AnotherLevel {\n", - " public static void f() {}\n", - " static int x = 10;\n", - " }\n", - " }\n", - " public static Destination destination(String s) {\n", - " return new ParcelDestination(s);\n", - " }\n", - " public static Contents contents() {\n", - " return new ParcelContents();\n", - " }\n", - " public static void main(String[] args) {\n", - " Contents c = contents();\n", - " Destination d = destination(\"Tasmania\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `main()` 中,没有任何 **Parcel11** 的对象是必需的;而是使用选取 **static** 成员的普通语法来调用方法-这些方法返回对 **Contents** 和 **Destination** 的引用。\n", - "\n", - "就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外围类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。\n", - "\n", - "### 接口内部的类\n", - "\n", - "嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/ClassInInterface.java\n", - "// {java ClassInInterface$Test}\n", - "public interface ClassInInterface {\n", - " void howdy();\n", - " class Test implements ClassInInterface {\n", - " @Override\n", - " public void howdy() {\n", - " System.out.println(\"Howdy!\");\n", - " }\n", - " public static void main(String[] args) {\n", - " new Test().howdy();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Howdy!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。\n", - "\n", - "我曾在本书中建议过,在每个类中都写一个 `main()` 方法,用来测试这个类。这样做有一个缺点,那就是必须带着那些已编译过的额外代码。如果这对你是个麻烦,那就可以使用嵌套类来放置测试代码。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/TestBed.java\n", - "// Putting test code in a nested class\n", - "// {java TestBed$Tester}\n", - "public class TestBed {\n", - " public void f() { System.out.println(\"f()\"); }\n", - " public static class Tester {\n", - " public static void main(String[] args) {\n", - " TestBed t = new TestBed();\n", - " t.f();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这生成了一个独立的类 **TestBed$Tester**(要运行这个程序,执行 **java TestBed$Tester**,在 Unix/Linux 系统中需要转义 **$**)。你可以使用这个类测试,但是不必在发布的产品中包含它,可以在打包产品前删除 **TestBed$Tester.class**。\n", - "\n", - "### 从多层嵌套类中访问外部类的成员\n", - "\n", - "一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/MultiNestingAccess.java\n", - "// Nested classes can access all members of all\n", - "// levels of the classes they are nested within\n", - "class MNA {\n", - " private void f() {}\n", - " class A {\n", - " private void g() {}\n", - " public class B {\n", - " void h() {\n", - " g();\n", - " f();\n", - " }\n", - " }\n", - " }\n", - "}\n", - "public class MultiNestingAccess {\n", - " public static void main(String[] args) {\n", - " MNA mna = new MNA();\n", - " MNA.A mnaa = mna.new A();\n", - " MNA.A.B mnaab = mnaa.new B();\n", - " mnaab.h();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到在 **MNA.A.B** 中,调用方法 `g()` 和 `f()` 不需要任何条件(即使它们被定义为 **private**)。这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。\"**.new**\"语法能产生正确的作用域,所以不必在调用构造器时限定类名。\n", - "\n", - "\n", - "\n", - "## 为什么需要内部类\n", - "\n", - "至此,我们已经看到了许多描述内部类的语法和语义,但是这并不能同答“为什么需要内部类”这个问题。那么,Java 设计者们为什么会如此费心地增加这项基本的语言特性呢?\n", - "\n", - "一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。\n", - "\n", - "内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是:\n", - "\n", - "> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。\n", - "\n", - "如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(译注:类或抽象类)。\n", - "\n", - "为了看到更多的细节,让我们考虑这样一种情形:即必须在一个类中以某种方式实现两个接口。由于接口的灵活性,你有两种选择;使用单一类,或者使用内部类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/mui/MultiInterfaces.java\n", - "// Two ways a class can implement multiple interfaces\n", - "// {java innerclasses.mui.MultiInterfaces}\n", - "package innerclasses.mui;\n", - "interface A {}\n", - "interface B {}\n", - "class X implements A, B {}\n", - "class Y implements A {\n", - " B makeB() {\n", - " // Anonymous inner class:\n", - " return new B() {};\n", - " }\n", - "}\n", - "public class MultiInterfaces {\n", - " static void takesA(A a) {}\n", - " static void takesB(B b) {}\n", - " public static void main(String[] args) {\n", - " X x = new X();\n", - " Y y = new Y();\n", - " takesA(x);\n", - " takesA(y);\n", - " takesB(x);\n", - " takesB(y.makeB());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当然,这里假设在两种方式下的代码结构都确实有逻辑意义。然而遇到问题的时候,通常问题本身就能给出某些指引,告诉你是应该使用单一类,还是使用内部类。但如果没有任何其他限制,从实现的观点来看,前面的例子并没有什么区别,它们都能正常运作。\n", - "\n", - "如果拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/MultiImplementation.java\n", - "// For concrete or abstract classes, inner classes\n", - "// produce \"multiple implementation inheritance\"\n", - "// {java innerclasses.MultiImplementation}\n", - "package innerclasses;\n", - "\n", - "class D {}\n", - "\n", - "abstract class E {}\n", - "\n", - "class Z extends D {\n", - " E makeE() {\n", - " return new E() {}; \n", - " }\n", - "}\n", - "\n", - "public class MultiImplementation {\n", - " static void takesD(D d) {}\n", - " static void takesE(E e) {}\n", - " \n", - " public static void main(String[] args) {\n", - " Z z = new Z();\n", - " takesD(z);\n", - " takesE(z.makeE());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性:\n", - "\n", - "1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。\n", - "2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。\n", - "稍后就会展示一个这样的例子。\n", - "3. 创建内部类对象的时刻并不依赖于外围类对象的创建\n", - "4. 内部类并没有令人迷惑的\"is-a”关系,它就是一个独立的实体。\n", - "\n", - "举个例子,如果 **Sequence.java** 不使用内部类,就必须声明\"**Sequence** 是一个 **Selector**\",对于某个特定的 **Sequence** 只能有一个 **Selector**,然而使用内部类很容易就能拥有另一个方法 `reverseSelector()`,用它来生成一个反方向遍历序列的 **Selector**,只有内部类才有这种灵活性。\n", - "\n", - "### 闭包与回调\n", - "\n", - "闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。\n", - "\n", - "在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁,你将会在 [函数式编程 ]() 这一章节中学习相关细节。尽管相对于内部类,你可能更喜欢使用 lambda 表达式实现闭包,但是你会看到并需要理解那些在 Java 8 之前通过内部类方式实现闭包的代码,因此仍然有必要来理解这种方式。\n", - "\n", - "Java 最引人争议的问题之一就是,人们认为 Java 应该包含某种类似指针的机制,以允许回调(callback)。通过回调,对象能够携带一些信息,这些信息允许它在稍后的某个时刻调用初始的对象。稍后将会看到这是一个非常有用的概念。如果回调是通过指针实现的,那么就只能寄希望于程序员不会误用该指针。然而,读者应该已经了解到,Java 更小心仔细,所以没有在语言中包括指针。\n", - "\n", - "通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全。见下例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/Callbacks.java\n", - "// Using inner classes for callbacks\n", - "// {java innerclasses.Callbacks}\n", - "package innerclasses;\n", - "interface Incrementable {\n", - " void increment();\n", - "}\n", - "// Very simple to just implement the interface:\n", - "class Callee1 implements Incrementable {\n", - " private int i = 0;\n", - " @Override\n", - " public void increment() {\n", - " i++;\n", - " System.out.println(i);\n", - " }\n", - "}\n", - "class MyIncrement {\n", - " public void increment() {\n", - " System.out.println(\"Other operation\");\n", - " }\n", - " static void f(MyIncrement mi) { mi.increment(); }\n", - "}\n", - "// If your class must implement increment() in\n", - "// some other way, you must use an inner class:\n", - "class Callee2 extends MyIncrement {\n", - " private int i = 0;\n", - " @Override\n", - " public void increment() {\n", - " super.increment();\n", - " i++;\n", - " System.out.println(i);\n", - " }\n", - " private class Closure implements Incrementable {\n", - " @Override\n", - " public void increment() {\n", - " // Specify outer-class method, otherwise\n", - " // you'll get an infinite recursion:\n", - " Callee2.this.increment();\n", - " }\n", - " }\n", - " Incrementable getCallbackReference() {\n", - " return new Closure();\n", - " }\n", - "}\n", - "class Caller {\n", - " private Incrementable callbackReference;\n", - " Caller(Incrementable cbh) {\n", - " callbackReference = cbh;\n", - " }\n", - " void go() { callbackReference.increment(); }\n", - "}\n", - "public class Callbacks {\n", - " public static void main(String[] args) {\n", - " Callee1 c1 = new Callee1();\n", - " Callee2 c2 = new Callee2();\n", - " MyIncrement.f(c2);\n", - " Caller caller1 = new Caller(c1);\n", - " Caller caller2 =\n", - " new Caller(c2.getCallbackReference());\n", - " caller1.go();\n", - " caller1.go();\n", - " caller2.go();\n", - " caller2.go();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Other operation\n", - "1\n", - "1\n", - "2\n", - "Other operation\n", - "2\n", - "Other operation\n", - "3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。\n", - "\n", - "注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。\n", - "内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。\n", - "\n", - "**Caller** 的构造器需要一个 **Incrementable** 的引用作为参数(虽然可以在任意时刻捕获回调引用),然后在以后的某个时刻,**Caller** 对象可以使用此引用回调 **Callee** 类。\n", - "\n", - "回调的价值在于它的灵活性-可以在运行时动态地决定需要调用什么方法。例如,在图形界面实现 GUI 功能的时候,到处都用到回调。\n", - "\n", - "### 内部类与控制框架\n", - "\n", - "在将要介绍的控制框架(control framework)中,可以看到更多使用内部类的具体例子。\n", - "\n", - "应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题。这是设计模式中模板方法的一个例子,模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。\n", - "\n", - "控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作*事件驱动*系统。应用程序设计中常见的问题之一是图形用户接口(GUI),它几乎完全是事件驱动的系统。\n", - "\n", - "要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。接下来的问题就是,对于要控制什么,控制框架并不包含任何具体的信息。那些信息是在实现算法的 `action()` 部分时,通过继承来提供的。\n", - "\n", - "首先,接口描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。下面的例子包含了某些实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/controller/Event.java\n", - "// The common methods for any control event\n", - "package innerclasses.controller;\n", - "import java.time.*; // Java 8 time classes\n", - "public abstract class Event {\n", - " private Instant eventTime;\n", - " protected final Duration delayTime;\n", - " public Event(long millisecondDelay) {\n", - " delayTime = Duration.ofMillis(millisecondDelay);\n", - " start();\n", - " }\n", - " public void start() { // Allows restarting\n", - " eventTime = Instant.now().plus(delayTime);\n", - " }\n", - " public boolean ready() {\n", - " return Instant.now().isAfter(eventTime);\n", - " }\n", - " public abstract void action();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当希望运行 **Event** 并随后调用 `start()` 时,那么构造器就会捕获(从对象创建的时刻开始的)时间,此时间是这样得来的:`start()` 获取当前时间,然后加上一个延迟时间,这样生成触发事件的时间。`start()` 是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用 **Event** 对象。例如,如果想要重复一个事件,只需简单地在 `action()` 中调用 `start()` 方法。\n", - "\n", - "`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中覆盖 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。\n", - "\n", - "下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\\<**Event**\\> 类型(读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数,foreach 语法用来连续获取 **List** 中的 **Event**,`remove()` 方法用来从 **List** 中移除指定的 **Event**。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/controller/Controller.java\n", - "// The reusable framework for control systems\n", - "package innerclasses.controller;\n", - "import java.util.*;\n", - "public class Controller {\n", - " // A class from java.util to hold Event objects:\n", - " private List eventList = new ArrayList<>();\n", - " public void addEvent(Event c) { eventList.add(c); }\n", - " public void run() {\n", - " while(eventList.size() > 0)\n", - " // Make a copy so you're not modifying the list\n", - " // while you're selecting the elements in it:\n", - " for(Event e : new ArrayList<>(eventList))\n", - " if(e.ready()) {\n", - " System.out.println(e);\n", - " e.action();\n", - " eventList.remove(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`run()` 方法循环遍历 **eventList**,寻找就绪的(`ready()`)、要运行的 **Event** 对象。对找到的每一个就绪的(`ready()`)事件,使用对象的 `toString()` 打印其信息,调用其 `action()` 方法,然后从列表中移除此 **Event**。\n", - "\n", - "注意,在目前的设计中你并不知道 **Event** 到底做了什么。这正是此设计的关键所在—\"使变化的事物与不变的事物相互分离”。用我的话说,“变化向量”就是各种不同的 **Event** 对象所具有的不同行为,而你通过创建不同的 **Event** 子类来表现不同的行为。\n", - "\n", - "这正是内部类要做的事情,内部类允许:\n", - "\n", - "1. 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的 `action()`。\n", - "2. 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。\n", - "\n", - "考虑此控制框架的一个特定实现,如控制温室的运作:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类 **Event** 的多种派生版本。对于温室系统的每一种行为,都继承创建一个新的 **Event** 内部类,并在要实现的 `action()` 中编写控制代码。\n", - "\n", - "作为典型的应用程序框架,**GreenhouseControls** 类继承自 **Controller**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/GreenhouseControls.java\n", - "// This produces a specific application of the\n", - "// control system, all in a single class. Inner\n", - "// classes allow you to encapsulate different\n", - "// functionality for each type of event.\n", - "import innerclasses.controller.*;\n", - "public class GreenhouseControls extends Controller {\n", - " private boolean light = false;\n", - " public class LightOn extends Event {\n", - " public LightOn(long delayTime) {\n", - " super(delayTime); \n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here to\n", - " // physically turn on the light.\n", - " light = true;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Light is on\";\n", - " }\n", - " }\n", - " public class LightOff extends Event {\n", - " public LightOff(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here to\n", - " // physically turn off the light.\n", - " light = false;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Light is off\";\n", - " }\n", - " }\n", - " private boolean water = false;\n", - " public class WaterOn extends Event {\n", - " public WaterOn(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here.\n", - " water = true;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Greenhouse water is on\";\n", - " }\n", - " }\n", - " public class WaterOff extends Event {\n", - " public WaterOff(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here.\n", - " water = false;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Greenhouse water is off\";\n", - " }\n", - " }\n", - " private String thermostat = \"Day\";\n", - " public class ThermostatNight extends Event {\n", - " public ThermostatNight(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here.\n", - " thermostat = \"Night\";\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Thermostat on night setting\";\n", - " }\n", - " }\n", - " public class ThermostatDay extends Event {\n", - " public ThermostatDay(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " // Put hardware control code here.\n", - " thermostat = \"Day\";\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Thermostat on day setting\";\n", - " }\n", - " }\n", - " // An example of an action() that inserts a\n", - " // new one of itself into the event list:\n", - " public class Bell extends Event {\n", - " public Bell(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " addEvent(new Bell(delayTime.toMillis()));\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Bing!\";\n", - " }\n", - " }\n", - " public class Restart extends Event {\n", - " private Event[] eventList;\n", - " public\n", - " Restart(long delayTime, Event[] eventList) {\n", - " super(delayTime);\n", - " this.eventList = eventList;\n", - " for(Event e : eventList)\n", - " addEvent(e);\n", - " }\n", - " @Override\n", - " public void action() {\n", - " for(Event e : eventList) {\n", - " e.start(); // Rerun each event\n", - " addEvent(e);\n", - " }\n", - " start(); // Rerun this Event\n", - " addEvent(this);\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Restarting system\";\n", - " }\n", - " }\n", - " public static class Terminate extends Event {\n", - " public Terminate(long delayTime) {\n", - " super(delayTime);\n", - " }\n", - " @Override\n", - " public void action() { System.exit(0); }\n", - " @Override\n", - " public String toString() {\n", - " return \"Terminating\";\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,**light**,**water** 和 **thermostat** 都属于外围类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。\n", - "\n", - "大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外围类 **GreenhouseContrlos** 的所有方法。\n", - "\n", - "一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。\n", - "\n", - "下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是命令设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/GreenhouseController.java\n", - "// Configure and execute the greenhouse system\n", - "import innerclasses.controller.*;\n", - "public class GreenhouseController {\n", - " public static void main(String[] args) {\n", - " GreenhouseControls gc = new GreenhouseControls();\n", - " // Instead of using code, you could parse\n", - " // configuration information from a text file:\n", - " gc.addEvent(gc.new Bell(900));\n", - " Event[] eventList = {\n", - " gc.new ThermostatNight(0),\n", - " gc.new LightOn(200),\n", - " gc.new LightOff(400),\n", - " gc.new WaterOn(600),\n", - " gc.new WaterOff(800),\n", - " gc.new ThermostatDay(1400)\n", - " };\n", - " gc.addEvent(gc.new Restart(2000, eventList));\n", - " gc.addEvent(\n", - " new GreenhouseControls.Terminate(5000));\n", - " gc.run();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Thermostat on night setting\n", - "Light is on\n", - "Light is off\n", - "Greenhouse water is on\n", - "Greenhouse water is off\n", - "Bing!\n", - "Thermostat on day setting\n", - "Bing!\n", - "Restarting system\n", - "Thermostat on night setting\n", - "Light is on\n", - "Light is off\n", - "Greenhouse water is on\n", - "Bing!\n", - "Greenhouse water is off\n", - "Thermostat on day setting\n", - "Bing!\n", - "Restarting system\n", - "Thermostat on night setting\n", - "Light is on\n", - "Light is off\n", - "Bing!\n", - "Greenhouse water is on\n", - "Greenhouse water is off\n", - "Terminating" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个类的作用是初始化系统,所以它添加了所有相应的事件。**Restart** 事件反复运行,而且它每次都会将 **eventList** 加载到 **GreenhouseControls** 对象中。如果提供了命令行参数,系统会以它作为毫秒数,决定什么时候终止程序(这是测试程序时使用的)。\n", - "\n", - "当然,更灵活的方法是避免对事件进行硬编码。\n", - "\n", - "这个例子应该使读者更了解内部类的价值了,特别是在控制框架中使用内部类的时候。\n", - "\n", - "\n", - "\n", - "## 继承内部类\n", - "\n", - "因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/InheritInner.java\n", - "// Inheriting an inner class\n", - "class WithInner {\n", - " class Inner {}\n", - "}\n", - "public class InheritInner extends WithInner.Inner {\n", - " //- InheritInner() {} // Won't compile\n", - " InheritInner(WithInner wi) {\n", - " wi.super();\n", - " }\n", - " public static void main(String[] args) {\n", - " WithInner wi = new WithInner();\n", - " InheritInner ii = new InheritInner(wi);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,**InheritInner** 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "enclosingClassReference.super();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这样才提供了必要的引用,然后程序才能编译通过。\n", - "\n", - "\n", - "\n", - "## 内部类可以被覆盖么?\n", - "\n", - "如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外围类的一个方法,其实并不起什么作用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/BigEgg.java\n", - "// An inner class cannot be overridden like a method\n", - "class Egg {\n", - " private Yolk y;\n", - " protected class Yolk {\n", - " public Yolk() {\n", - " System.out.println(\"Egg.Yolk()\");\n", - " }\n", - " }\n", - " Egg() {\n", - " System.out.println(\"New Egg()\");\n", - " y = new Yolk();\n", - " }\n", - "}\n", - "public class BigEgg extends Egg {\n", - " public class Yolk {\n", - " public Yolk() {\n", - " System.out.println(\"BigEgg.Yolk()\");\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " new BigEgg();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "New Egg()\n", - "Egg.Yolk()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。\n", - "\n", - "这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/BigEgg2.java\n", - "// Proper inheritance of an inner class\n", - "class Egg2 {\n", - " protected class Yolk {\n", - " public Yolk() {\n", - " System.out.println(\"Egg2.Yolk()\");\n", - " }\n", - " public void f() {\n", - " System.out.println(\"Egg2.Yolk.f()\");\n", - " }\n", - " }\n", - " private Yolk y = new Yolk();\n", - " Egg2() { System.out.println(\"New Egg2()\"); }\n", - " public void insertYolk(Yolk yy) { y = yy; }\n", - " public void g() { y.f(); }\n", - "}\n", - "public class BigEgg2 extends Egg2 {\n", - " public class Yolk extends Egg2.Yolk {\n", - " public Yolk() {\n", - " System.out.println(\"BigEgg2.Yolk()\");\n", - " }\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"BigEgg2.Yolk.f()\");\n", - " }\n", - " }\n", - " public BigEgg2() { insertYolk(new Yolk()); }\n", - " public static void main(String[] args) {\n", - " Egg2 e2 = new BigEgg2();\n", - " e2.g();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Egg2.Yolk()\n", - "New Egg2()\n", - "Egg2.Yolk()\n", - "BigEgg2.Yolk()\n", - "BigEgg2.Yolk.f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且覆盖了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,覆盖后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。\n", - "\n", - "\n", - "\n", - "## 局部内部类\n", - "\n", - "前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// innerclasses/LocalInnerClass.java\n", - "// Holds a sequence of Objects\n", - "interface Counter {\n", - " int next();\n", - "}\n", - "public class LocalInnerClass {\n", - " private int count = 0;\n", - " Counter getCounter(final String name) {\n", - " // A local inner class:\n", - " class LocalCounter implements Counter {\n", - " LocalCounter() {\n", - " // Local inner class can have a constructor\n", - " System.out.println(\"LocalCounter()\");\n", - " }\n", - " @Override\n", - " public int next() {\n", - " System.out.print(name); // Access local final\n", - " return count++;\n", - " }\n", - " }\n", - " return new LocalCounter();\n", - " }\n", - " // Repeat, but with an anonymous inner class:\n", - " Counter getCounter2(final String name) {\n", - " return new Counter() {\n", - " // Anonymous inner class cannot have a named\n", - " // constructor, only an instance initializer:\n", - " {\n", - " System.out.println(\"Counter()\");\n", - " }\n", - " @Override\n", - " public int next() {\n", - " System.out.print(name); // Access local final\n", - " return count++;\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " LocalInnerClass lic = new LocalInnerClass();\n", - " Counter\n", - " c1 = lic.getCounter(\"Local inner \"),\n", - " c2 = lic.getCounter2(\"Anonymous inner \");\n", - " for(int i = 0; i < 5; i++)\n", - " System.out.println(c1.next());\n", - " for(int i = 0; i < 5; i++)\n", - " System.out.println(c2.next());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "LocalCounter()\n", - "Counter()\n", - "Local inner 0\n", - "Local inner 1\n", - "Local inner 2\n", - "Local inner 3\n", - "Local inner 4\n", - "Anonymous inner 5\n", - "Anonymous inner 6\n", - "Anonymous inner 7\n", - "Anonymous inner 8\n", - "Anonymous inner 9" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Counter** 返回的是序列中的下一个值。我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。\n", - "\n", - "所以使用局部内部类而不使用匿名内部类的另一个理由就是,需要不止一个该内部类的对象。\n", - "\n", - "\n", - "\n", - "## 内部类标识符\n", - "\n", - "由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个\"meta-class\",叫做 **Class** 对象)。\n", - "\n", - "你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“**$**\",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Counter.class\n", - "LocalInnerClass$1.class\n", - "LocalInnerClass$LocalCounter.class\n", - "LocalInnerClass.class" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“**$**”的后面。\n", - "\n", - "虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。)\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "比起面向对象编程中其他的概念来,接口和内部类更深奥复杂,比如 C++ 就没有这些。将两者结合起来,同样能够解决 C++ 中的用多重继承所能解决的问题。然而,多重继承在 C++ 中被证明是相当难以使用的,相比较而言,Java 的接口和内部类就容易理解多了。\n", - "\n", - "虽然这些特性本身是相当直观的,但是就像多态机制一样,这些特性的使用应该是设计阶段考虑的问题。随着时间的推移,读者将能够更好地识别什么情况下应该使用接口,什么情况使用内部类,或者两者同时使用。但此时,读者至少应该已经完全理解了它们的语法和语义。\n", - "\n", - "当读者见到这些语言特性的实际应用时,就能最终理解它们了。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/12-Collections.ipynb b/jupyter/12-Collections.ipynb deleted file mode 100644 index c2141709..00000000 --- a/jupyter/12-Collections.ipynb +++ /dev/null @@ -1,2589 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十二章 集合\n", - "\n", - "> 如果一个程序只包含固定数量的对象且对象的生命周期都是已知的,那么这是一个非常简单的程序。\n", - "\n", - "通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。因此,不能依靠创建命名的引用来持有每一个对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "MyType aReference;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为从来不会知道实际需要多少个这样的引用。\n", - "\n", - "大多数编程语言都提供了某种方法来解决这个基本问题。Java有多种方式保存对象(确切地说,是对象的引用)。例如前边曾经学习过的数组,它是编译器支持的类型。数组是保存一组对象的最有效的方式,如果想要保存一组基本类型数据,也推荐使用数组。但是数组具有固定的大小尺寸,而且在更一般的情况下,在写程序的时候并不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制就显得太过受限了。\n", - "\n", - "**java.util** 库提供了一套相当完整的*集合类*(collection classes)来解决这个问题,其中基本的类型有 **List** 、 **Set** 、 **Queue** 和 **Map**。这些类型也被称作*容器类*(container classes),但我将使用Java类库使用的术语。集合提供了完善的方法来保存对象,可以使用这些工具来解决大量的问题。\n", - "\n", - "集合还有一些其它特性。例如, **Set** 对于每个值都只保存一个对象, **Map** 是一个关联数组,允许将某些对象与其他对象关联起来。Java集合类都可以自动地调整自己的大小。因此,与数组不同,在编程时,可以将任意数量的对象放置在集合中,而不用关心集合应该有多大。\n", - "\n", - "尽管在 Java 中没有直接的关键字支持,[^1]但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。\n", - "\n", - "\n", - "## 泛型和类型安全的集合\n", - "\n", - "使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个 **Apple** 对象的集合,这里使用最基本最可靠的 **ArrayList** 。现在,可以把 **ArrayList** 看作“可以自动扩充自身尺寸的数组”来看待。使用 **ArrayList** 相当简单:创建一个实例,用 `add()` 插入对象;然后用 `get()` 来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2] **ArrayList** 还有一个 `size()` 方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常]()章节介绍了异常)。\n", - "\n", - "在本例中, **Apple** 和 **Orange** 都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的 `@SuppressWarning` 注解及其参数表示只抑制“unchecked”类型的警告([注解]()章节将介绍更多有关注解的信息):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ApplesAndOrangesWithoutGenerics.java\n", - "// Simple collection use (suppressing compiler warnings)\n", - "// {ThrowsException}\n", - "import java.util.*;\n", - "\n", - "class Apple {\n", - " private static long counter;\n", - " private final long id = counter++;\n", - " public long id() { return id; }\n", - "}\n", - "\n", - "class Orange {}\n", - "\n", - "public class ApplesAndOrangesWithoutGenerics {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " ArrayList apples = new ArrayList();\n", - " for(int i = 0; i < 3; i++)\n", - " apples.add(new Apple());\n", - " // No problem adding an Orange to apples:\n", - " apples.add(new Orange());\n", - " for(Object apple : apples) {\n", - " ((Apple) apple).id();\n", - " // Orange is detected only at run time\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"main\"\n", - "java.lang.ClassCastException: Orange cannot be cast to\n", - "Apple\n", - " at ApplesAndOrangesWithoutGenerics.main(ApplesA\n", - "ndOrangesWithoutGenerics.java:23)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Apple** 和 **Orange** 是截然不同的,它们除了都是 **Object** 之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自 **Object**)。因为 **ArrayList** 保存的是 **Object** ,所以不仅可以通过 **ArrayList** 的 `add()` 方法将 **Apple** 对象放入这个集合,而且可以放入 **Orange** 对象,这无论在编译期还是运行时都不会有问题。当使用 **ArrayList** 的 `get()` 方法来取出你认为是 **Apple** 的对象时,得到的只是 **Object** 引用,必须将其转型为 **Apple**。然后需要将整个表达式用括号括起来,以便在调用 **Apple** 的 `id()` 方法之前,强制执行转型。否则,将会产生语法错误。\n", - "\n", - "在运行时,当尝试将 **Orange** 对象转为 **Apple** 时,会出现输出中显示的错误。\n", - "\n", - "在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。\n", - "\n", - "通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ApplesAndOrangesWithGenerics.java\n", - "import java.util.*;\n", - "\n", - "public class ApplesAndOrangesWithGenerics {\n", - " public static void main(String[] args) {\n", - " ArrayList apples = new ArrayList<>();\n", - " for(int i = 0; i < 3; i++)\n", - " apples.add(new Apple());\n", - " // Compile-time error:\n", - " // apples.add(new Orange());\n", - " for(Apple apple : apples) {\n", - " System.out.println(apple.id());\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "0\n", - "1\n", - "2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **apples** 定义的右侧,可以看到 `new ArrayList<>()` 。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "ArrayList apples = new ArrayList();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。虽然*类型推断*(type inference)只是个很小的请求,Java 语言团队仍然欣然接受并进行了改进。\n", - "\n", - "有了 **ArrayList** 声明中的类型指定,编译器会阻止将 **Orange** 放入 **apples** ,因此,这会成为一个编译期错误而不是运行时错误。\n", - "\n", - "使用泛型,从 **List** 中获取元素不需要强制类型转换。因为 **List** 知道它持有什么类型,因此当调用 `get()` 时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。\n", - "\n", - "当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/GenericsAndUpcasting.java\n", - "import java.util.*;\n", - "\n", - "class GrannySmith extends Apple {}\n", - "class Gala extends Apple {}\n", - "class Fuji extends Apple {}\n", - "class Braeburn extends Apple {}\n", - "\n", - "public class GenericsAndUpcasting {\n", - " public static void main(String[] args) {\n", - " ArrayList apples = new ArrayList<>();\n", - " apples.add(new GrannySmith());\n", - " apples.add(new Gala());\n", - " apples.add(new Fuji());\n", - " apples.add(new Braeburn());\n", - " for(Apple apple : apples)\n", - " System.out.println(apple);\n", - " }\n", - "}\n", - "/* Output:\n", - "GrannySmith@15db9742\n", - "Gala@6d06d69c\n", - "Fuji@7852e922\n", - "Braeburn@4e25154f\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,可以将 **Apple** 的子类型添加到被指定为保存 **Apple** 对象的集合中。\n", - "\n", - "程序的输出是从 **Object** 默认的 `toString()` 方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过 `hashCode()` 方法产生的)。将在[附录:理解equals和hashCode方法]()中了解有关散列码的内容。\n", - "\n", - "\n", - "## 基本概念\n", - "\n", - "Java集合类库采用“持有对象”(holding objects)的思想,并将其分为两个不同的概念,表示为类库的基本接口:\n", - "\n", - "1. **集合(Collection)** :一个独立元素的序列,这些元素都服从一条或多条规则。**List** 必须以插入的顺序保存元素, **Set** 不能包含重复元素, **Queue** 按照*排队规则*来确定对象产生的顺序(通常与它们被插入的顺序相同)。\n", - "2. **映射(Map)** : 一组成对的“键值对”对象,允许使用键来查找值。 **ArrayList** 使用数字来查找对象,因此在某种意义上讲,它是将数字和对象关联在一起。 **map** 允许我们使用一个对象来查找另一个对象,它也被称作*关联数组*(associative array),因为它将对象和其它对象关联在一起;或者称作*字典*(dictionary),因为可以使用一个键对象来查找值对象,就像在字典中使用单词查找定义一样。 **Map** 是强大的编程工具。\n", - "\n", - "尽管并非总是可行,但在理想情况下,你编写的大部分代码都在与这些接口打交道,并且唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,可以像下面这样创建一个 **List** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "List apples = new ArrayList<>();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意, **ArrayList** 已经被向上转型为了 **List** ,这与之前示例中的处理方式正好相反。使用接口的目的是,如果想要改变具体实现,只需在创建时修改它就行了,就像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "List apples = new LinkedList<>();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,应该创建一个具体类的对象,将其向上转型为对应的接口,然后在其余代码中都是用这个接口。\n", - "\n", - "这种方式并非总是有效的,因为某些具体类有额外的功能。例如, **LinkedList** 具有 **List** 接口中未包含的额外方法,而 **TreeMap** 也具有在 **Map** 接口中未包含的方法。如果需要使用这些方法,就不能将它们向上转型为更通用的接口。\n", - "\n", - "**Collection** 接口概括了*序列*的概念——一种存放一组对象的方式。下面是个简单的示例,用 **Integer** 对象填充了一个 **Collection** (这里用 **ArrayList** 表示),然后打印集合中的每个元素:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SimpleCollection.java\n", - "import java.util.*;\n", - "\n", - "public class SimpleCollection {\n", - " public static void main(String[] args) {\n", - " Collection c = new ArrayList<>();\n", - " for(int i = 0; i < 10; i++)\n", - " c.add(i); // Autoboxing\n", - " for(Integer i : c)\n", - " System.out.print(i + \", \");\n", - " }\n", - "}\n", - "/* Output:\n", - "0, 1, 2, 3, 4, 5, 6, 7, 8, 9,\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个例子仅使用了 **Collection** 中的方法(即 `add()` ),所以使用任何继承自 **Collection** 的类的对象都可以正常工作。但是 **ArrayList** 是最基本的序列类型。\n", - "\n", - "`add()` 方法的名称就表明它是在 **Collection** 中添加一个新元素。但是,文档中非常详细地叙述到 `add()` “要确保这个 **Collection** 包含指定的元素。”这是因为考虑到了 **Set** 的含义,因为在 **Set**中,只有当元素不存在时才会添加元素。在使用 **ArrayList** ,或任何其他类型的 **List** 时,`add()` 总是表示“把它放进去”,因为 **List** 不关心是否存在重复元素。\n", - "\n", - "可以使用 *for-in* 语法来遍历所有的 **Collection** ,就像这里所展示的那样。在本章的后续部分,还将学习到一个更灵活的概念,*迭代器*。\n", - "\n", - "\n", - "\n", - "## 添加元素组\n", - "\n", - "在 **java.util** 包中的 **Arrays** 和 **Collections** 类中都有很多实用的方法,可以在一个 **Collection** 中添加一组元素。\n", - "\n", - "`Arrays.asList()` 方法接受一个数组或是逗号分隔的元素列表(使用可变参数),并将其转换为 **List** 对象。 `Collections.addAll()` 方法接受一个 **Collection** 对象,以及一个数组或是一个逗号分隔的列表,将其中元素添加到 **Collection** 中。下边的示例展示了这两个方法,以及更通用的 、所有 **Collection** 类型都包含的`addAll()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/AddingGroups.java\n", - "// Adding groups of elements to Collection objects\n", - "import java.util.*;\n", - "\n", - "public class AddingGroups {\n", - " public static void main(String[] args) {\n", - " Collection collection =\n", - " new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));\n", - " Integer[] moreInts = { 6, 7, 8, 9, 10 };\n", - " collection.addAll(Arrays.asList(moreInts));\n", - " // Runs significantly faster, but you can't\n", - " // construct a Collection this way:\n", - " Collections.addAll(collection, 11, 12, 13, 14, 15);\n", - " Collections.addAll(collection, moreInts);\n", - " // Produces a list \"backed by\" an array:\n", - " List list = Arrays.asList(16,17,18,19,20);\n", - " list.set(1, 99); // OK -- modify an element\n", - " // list.add(21); // Runtime error; the underlying\n", - " // array cannot be resized.\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Collection** 的构造器可以接受另一个 **Collection**,用它来将自身初始化。因此,可以使用 `Arrays.asList()` 来为这个构造器产生输入。但是, `Collections.addAll()` 运行得更快,而且很容易构建一个不包含元素的 **Collection** ,然后调用 `Collections.addAll()` ,因此这是首选方式。\n", - "\n", - "`Collection.addAll()` 方法只能接受另一个 **Collection** 作为参数,因此它没有 `Arrays.asList()` 或 `Collections.addAll()` 灵活。这两个方法都使用可变参数列表。\n", - "\n", - "也可以直接使用 `Arrays.asList()` 的输出作为一个 **List** ,但是这里的底层实现是数组,没法调整大小。如果尝试在这个 **List** 上调用 `add()` 或 `remove()`,由于这两个方法会尝试修改数组大小,所以会在运行时得到“Unsupported Operation(不支持的操作)”错误:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/AsListInference.java\n", - "import java.util.*;\n", - "\n", - "class Snow {}\n", - "class Powder extends Snow {}\n", - "class Light extends Powder {}\n", - "class Heavy extends Powder {}\n", - "class Crusty extends Snow {}\n", - "class Slush extends Snow {}\n", - "\n", - "public class AsListInference {\n", - " public static void main(String[] args) {\n", - " List snow1 = Arrays.asList(\n", - " new Crusty(), new Slush(), new Powder());\n", - " //- snow1.add(new Heavy()); // Exception\n", - "\n", - " List snow2 = Arrays.asList(\n", - " new Light(), new Heavy());\n", - " //- snow2.add(new Slush()); // Exception\n", - "\n", - " List snow3 = new ArrayList<>();\n", - " Collections.addAll(snow3,\n", - " new Light(), new Heavy(), new Powder());\n", - " snow3.add(new Crusty());\n", - "\n", - " // Hint with explicit type argument specification:\n", - " List snow4 = Arrays.asList(\n", - " new Light(), new Heavy(), new Slush());\n", - " //- snow4.add(new Powder()); // Exception\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **snow4** 中,注意 `Arrays.asList()` 中间的“暗示”(即 `` ),告诉编译器 `Arrays.asList()` 生成的结果 **List** 类型的实际目标类型是什么。这称为*显式类型参数说明*(explicit type argument specification)。\n", - "\n", - "\n", - "## 集合的打印\n", - "\n", - "必须使用 `Arrays.toString()` 来生成数组的可打印形式。但是打印集合无需任何帮助。下面是一个例子,这个例子中也介绍了基本的Java集合:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/PrintingCollections.java\n", - "// Collections print themselves automatically\n", - "import java.util.*;\n", - "\n", - "public class PrintingCollections {\n", - " static Collection\n", - " fill(Collection collection) {\n", - " collection.add(\"rat\");\n", - " collection.add(\"cat\");\n", - " collection.add(\"dog\");\n", - " collection.add(\"dog\");\n", - " return collection;\n", - " }\n", - " static Map fill(Map map) {\n", - " map.put(\"rat\", \"Fuzzy\");\n", - " map.put(\"cat\", \"Rags\");\n", - " map.put(\"dog\", \"Bosco\");\n", - " map.put(\"dog\", \"Spot\");\n", - " return map;\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(fill(new ArrayList<>()));\n", - " System.out.println(fill(new LinkedList<>()));\n", - " System.out.println(fill(new HashSet<>()));\n", - " System.out.println(fill(new TreeSet<>()));\n", - " System.out.println(fill(new LinkedHashSet<>()));\n", - " System.out.println(fill(new HashMap<>()));\n", - " System.out.println(fill(new TreeMap<>()));\n", - " System.out.println(fill(new LinkedHashMap<>()));\n", - " }\n", - "}\n", - "/* Output:\n", - "[rat, cat, dog, dog]\n", - "[rat, cat, dog, dog]\n", - "[rat, cat, dog]\n", - "[cat, dog, rat]\n", - "[rat, cat, dog]\n", - "{rat=Fuzzy, cat=Rags, dog=Spot}\n", - "{cat=Rags, dog=Spot, rat=Fuzzy}\n", - "{rat=Fuzzy, cat=Rags, dog=Spot}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。\n", - "\n", - "默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。\n", - "\n", - "第一个 `fill()` 方法适用于所有类型的 **Collection** ,这些类型都实现了 `add()` 方法以添加新元素。\n", - "\n", - "**ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。\n", - "\n", - "**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。\n", - "\n", - "**Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。\n", - "\n", - "`Map.put(key, value)` 添加一个所想要添加的值并将它与一个键(用来查找值)相关联。 `Map.get(key)` 生成与该键相关联的值。上面的示例仅添加键值对,并没有执行查找。这将在稍后展示。\n", - "\n", - "请注意,这里没有指定(或考虑) **Map** 的大小,因为它会自动调整大小。 此外, **Map** 还知道如何打印自己,它会显示相关联的键和值。\n", - "\n", - "本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。\n", - "\n", - "键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。\n", - "\n", - "\n", - "\n", - "## 列表List\n", - "\n", - "**List**s承诺将元素保存在特定的序列中。 **List** 接口在 **Collection** 的基础上添加了许多方法,允许在 **List** 的中间插入和删除元素。\n", - "\n", - "有两种类型的 **List** :\n", - "\n", - "- 基本的 **ArrayList** ,擅长随机访问元素,但在 **List** 中间插入和删除元素时速度较慢。\n", - "- **LinkedList** ,它通过代价较低的在 **List** 中间进行的插入和删除操作,提供了优化的顺序访问。 **LinkedList** 对于随机访问来说相对较慢,但它具有比 **ArrayList** 更大的特征集。\n", - "\n", - "下面的示例导入 **typeinfo.pets** ,超前使用了[类型信息]()一章中的类库。这个类库包含了 **Pet** 类层次结构,以及用于随机生成 **Pet** 对象的一些工具类。此时不需要了解完整的详细信息,只需要知道两点:\n", - "\n", - "1. 有一个 **Pet** 类,以及 **Pet** 的各种子类型。\n", - "2. 静态的 `Pets.arrayList()` 方法返回一个填充了随机选取的 **Pet** 对象的 **ArrayList**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ListFeatures.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class ListFeatures {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " List pets = Pets.list(7);\n", - " System.out.println(\"1: \" + pets);\n", - " Hamster h = new Hamster();\n", - " pets.add(h); // Automatically resizes\n", - " System.out.println(\"2: \" + pets);\n", - " System.out.println(\"3: \" + pets.contains(h));\n", - " pets.remove(h); // Remove by object\n", - " Pet p = pets.get(2);\n", - " System.out.println(\n", - " \"4: \" + p + \" \" + pets.indexOf(p));\n", - " Pet cymric = new Cymric();\n", - " System.out.println(\"5: \" + pets.indexOf(cymric));\n", - " System.out.println(\"6: \" + pets.remove(cymric));\n", - " // Must be the exact object:\n", - " System.out.println(\"7: \" + pets.remove(p));\n", - " System.out.println(\"8: \" + pets);\n", - " pets.add(3, new Mouse()); // Insert at an index\n", - " System.out.println(\"9: \" + pets);\n", - " List sub = pets.subList(1, 4);\n", - " System.out.println(\"subList: \" + sub);\n", - " System.out.println(\"10: \" + pets.containsAll(sub));\n", - " Collections.sort(sub); // In-place sort\n", - " System.out.println(\"sorted subList: \" + sub);\n", - " // Order is not important in containsAll():\n", - " System.out.println(\"11: \" + pets.containsAll(sub));\n", - " Collections.shuffle(sub, rand); // Mix it up\n", - " System.out.println(\"shuffled subList: \" + sub);\n", - " System.out.println(\"12: \" + pets.containsAll(sub));\n", - " List copy = new ArrayList<>(pets);\n", - " sub = Arrays.asList(pets.get(1), pets.get(4));\n", - " System.out.println(\"sub: \" + sub);\n", - " copy.retainAll(sub);\n", - " System.out.println(\"13: \" + copy);\n", - " copy = new ArrayList<>(pets); // Get a fresh copy\n", - " copy.remove(2); // Remove by index\n", - " System.out.println(\"14: \" + copy);\n", - " copy.removeAll(sub); // Only removes exact objects\n", - " System.out.println(\"15: \" + copy);\n", - " copy.set(1, new Mouse()); // Replace an element\n", - " System.out.println(\"16: \" + copy);\n", - " copy.addAll(2, sub); // Insert a list in the middle\n", - " System.out.println(\"17: \" + copy);\n", - " System.out.println(\"18: \" + pets.isEmpty());\n", - " pets.clear(); // Remove all elements\n", - " System.out.println(\"19: \" + pets);\n", - " System.out.println(\"20: \" + pets.isEmpty());\n", - " pets.addAll(Pets.list(4));\n", - " System.out.println(\"21: \" + pets);\n", - " Object[] o = pets.toArray();\n", - " System.out.println(\"22: \" + o[3]);\n", - " Pet[] pa = pets.toArray(new Pet[0]);\n", - " System.out.println(\"23: \" + pa[3].id());\n", - " }\n", - "}\n", - "/* Output:\n", - "1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]\n", - "2: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]\n", - "3: true\n", - "4: Cymric 2\n", - "5: -1\n", - "6: false\n", - "7: true\n", - "8: [Rat, Manx, Mutt, Pug, Cymric, Pug]\n", - "9: [Rat, Manx, Mutt, Mouse, Pug, Cymric, Pug]\n", - "subList: [Manx, Mutt, Mouse]\n", - "10: true\n", - "sorted subList: [Manx, Mouse, Mutt]\n", - "11: true\n", - "shuffled subList: [Mouse, Manx, Mutt]\n", - "12: true\n", - "sub: [Mouse, Pug]\n", - "13: [Mouse, Pug]\n", - "14: [Rat, Mouse, Mutt, Pug, Cymric, Pug]\n", - "15: [Rat, Mutt, Cymric, Pug]\n", - "16: [Rat, Mouse, Cymric, Pug]\n", - "17: [Rat, Mouse, Mouse, Pug, Cymric, Pug]\n", - "18: false\n", - "19: []\n", - "20: true\n", - "21: [Manx, Cymric, Rat, EgyptianMau]\n", - "22: EgyptianMau\n", - "23: 14\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "打印行都编了号,因此可从输出追溯到源代码。 第 1 行输出展示了原始的由 **Pet** 组成的 **List** 。 与数组不同, **List** 可以在创建后添加或删除元素,并自行调整大小。这正是它的重要价值:一种可修改的序列。在第 2 行输出中可以看到添加一个 **Hamster** 的结果,该对象将被追加到列表的末尾。\n", - "\n", - "可以使用 `contains()` 方法确定对象是否在列表中。如果要删除一个对象,可以将该对象的引用传递给 `remove()` 方法。同样,如果有一个对象的引用,可以使用 `indexOf()` 在 **List** 中找到该对象所在位置的下标号,如第 4 行输出所示中所示。\n", - "\n", - "当确定元素是否是属于某个 **List** ,寻找某个元素的索引,以及通过引用从 **List** 中删除元素时,都会用到 `equals()` 方法(根类 **Object** 的一个方法)。每个 **Pet** 被定义为一个唯一的对象,所以即使列表中已经有两个 **Cymrics** ,如果再创建一个新的 **Cymric** 对象并将其传递给 `indexOf()` 方法,结果仍为 **-1** (表示未找到),并且尝试调用 `remove()` 方法来删除这个对象将返回 **false** 。对于其他类, `equals()` 的定义可能有所不同。例如,如果两个 **String** 的内容相同,则这两个 **String** 相等。因此,为了防止出现意外,请务必注意 **List** 行为会根据 `equals()` 行为而发生变化。\n", - "\n", - "第 7、8 行输出展示了删除与 **List** 中的对象完全匹配的对象是成功的。\n", - "\n", - "可以在 **List** 的中间插入一个元素,就像在第 9 行输出和它之前的代码那样。但这会带来一个问题:对于 **LinkedList** ,在列表中间插入和删除都是廉价操作(在本例中,除了对列表中间进行的真正的随机访问),但对于 **ArrayList** ,这可是代价高昂的操作。这是否意味着永远不应该在 **ArrayList** 的中间插入元素,并最好是转换为 **LinkedList** ?不,它只是意味着你应该意识到这个问题,如果你开始在某个 **ArrayList** 中间执行很多插入操作,并且程序开始变慢,那么你应该看看你的 **List** 实现有可能就是罪魁祸首(发现此类瓶颈的最佳方式是使用分析器 profiler)。优化是一个很棘手的问题,最好的策略就是置之不顾,直到发现必须要去担心它了(尽管去理解这些问题总是一个很好的主意)。\n", - "\n", - "`subList()` 方法可以轻松地从更大的列表中创建切片,当将切片结果传递给原来这个较大的列表的 `containsAll()` 方法时,很自然地会得到 **true**。请注意,顺序并不重要,在第 11、12 行输出中可以看到,在 **sub** 上调用直观命名的 `Collections.sort()` 和 `Collections.shuffle()` 方法,不会影响 `containsAll()` 的结果。 `subList()` 所产生的列表的幕后支持就是原始列表。因此,对所返回列表的更改都将会反映在原始列表中,反之亦然。\n", - "\n", - "`retainAll()` 方法实际上是一个“集合交集”操作,在本例中,它保留了同时在 **copy** 和 **sub** 中的所有元素。请再次注意,所产生的结果行为依赖于 `equals()` 方法。\n", - "\n", - "第 14 行输出展示了使用索引号来删除元素的结果,与通过对象引用来删除元素相比,它显得更加直观,因为在使用索引时,不必担心 `equals()` 的行为。\n", - "\n", - "`removeAll()` 方法也是基于 `equals()` 方法运行的。 顾名思义,它会从 **List** 中删除在参数 **List** 中的所有元素。\n", - "\n", - "`set()` 方法的命名显得很不合时宜,因为它与 **Set** 类存在潜在的冲突。在这里使用“replace”可能更适合,因为它的功能是用第二个参数替换索引处的元素(第一个参数)。\n", - "\n", - "第 17 行输出表明,对于 **List** ,有一个重载的 `addAll()` 方法可以将新列表插入到原始列表的中间位置,而不是仅能用 **Collection** 的 `addAll()` 方法将其追加到列表的末尾。\n", - "\n", - "第 18 - 20 行输出展示了 `isEmpty()` 和 `clear()` 方法的效果。\n", - "\n", - "第 22、23 行输出展示了如何使用 `toArray()` 方法将任意的 **Collection** 转换为数组。这是一个重载方法,其无参版本返回一个 **Object** 数组,但是如果将目标类型的数组传递给这个重载版本,那么它会生成一个指定类型的数组(假设它通过了类型检查)。如果参数数组太小而无法容纳 **List** 中的所有元素(就像本例一样),则 `toArray()` 会创建一个具有合适尺寸的新数组。 **Pet** 对象有一个 `id()` 方法,可以在所产生的数组中的对象上调用这个方法。\n", - "\n", - "\n", - "\n", - "## 迭代器Iterators\n", - "\n", - "在任何集合中,都必须有某种方式可以插入元素并再次获取它们。毕竟,保存事物是集合最基本的工作。对于 **List** , `add()` 是插入元素的一种方式, `get()` 是获取元素的一种方式。\n", - "\n", - "如果从更高层次的角度考虑,会发现这里有个缺点:要使用集合,必须对集合的确切类型编程。这一开始可能看起来不是很糟糕,但是考虑下面的情况:如果原本是对 **List** 编码的,但是后来发现如果能够将相同的代码应用于 **Set** 会更方便,此时应该怎么做?或者假设想从一开始就编写一段通用代码,它不知道或不关心它正在使用什么类型的集合,因此它可以用于不同类型的集合,那么如何才能不重写代码就可以应用于不同类型的集合?\n", - "\n", - "*迭代器*(也是一种设计模式)的概念实现了这种抽象。迭代器是一个对象,它在一个序列中移动并选择该序列中的每个对象,而客户端程序员不知道或不关心该序列的底层结构。另外,迭代器通常被称为*轻量级对象*(lightweight object):创建它的代价小。因此,经常可以看到一些对迭代器有些奇怪的约束。例如,Java 的 **Iterator** 只能单向移动。这个 **Iterator** 只能用来:\n", - "\n", - "1. 使用 `iterator()` 方法要求集合返回一个 **Iterator**。 **Iterator** 将准备好返回序列中的第一个元素。\n", - "2. 使用 `next()` 方法获得序列中的下一个元素。\n", - "3. 使用 `hasNext()` 方法检查序列中是否还有元素。\n", - "4. 使用 `remove()` 方法将迭代器最近返回的那个元素删除。\n", - "\n", - "为了观察它的工作方式,这里再次使用[类型信息]()章节中的 **Pet** 工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SimpleIteration.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class SimpleIteration {\n", - " public static void main(String[] args) {\n", - " List pets = Pets.list(12);\n", - " Iterator it = pets.iterator();\n", - " while(it.hasNext()) {\n", - " Pet p = it.next();\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " }\n", - " System.out.println();\n", - " // A simpler approach, when possible:\n", - " for(Pet p : pets)\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " System.out.println();\n", - " // An Iterator can also remove elements:\n", - " it = pets.iterator();\n", - " for(int i = 0; i < 6; i++) {\n", - " it.next();\n", - " it.remove();\n", - " }\n", - " System.out.println(pets);\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx 8:Cymric 9:Rat 10:EgyptianMau 11:Hamster\n", - "[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有了 **Iterator** ,就不必再为集合中元素的数量操心了。这是由 `hasNext()` 和 `next()` 关心的事情。\n", - "\n", - "如果只是想向前遍历 **List** ,并不打算修改 **List** 对象本身,那么使用 *for-in* 语法更加简洁。\n", - "\n", - "**Iterator** 还可以删除由 `next()` 生成的最后一个元素,这意味着在调用 `remove()` 之前必须先调用 `next()` 。[^4]\n", - "\n", - "在集合中的每个对象上执行操作,这种思想十分强大,并且贯穿于本书。\n", - "\n", - "现在考虑创建一个 `display()` 方法,它不必知晓集合的确切类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/CrossCollectionIteration.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class CrossCollectionIteration {\n", - " public static void display(Iterator it) {\n", - " while(it.hasNext()) {\n", - " Pet p = it.next();\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " public static void main(String[] args) {\n", - " List pets = Pets.list(8);\n", - " LinkedList petsLL = new LinkedList<>(pets);\n", - " HashSet petsHS = new HashSet<>(pets);\n", - " TreeSet petsTS = new TreeSet<>(pets);\n", - " display(pets.iterator());\n", - " display(petsLL.iterator());\n", - " display(petsHS.iterator());\n", - " display(petsTS.iterator());\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug\n", - "0:Rat\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`display()` 方法不包含任何有关它所遍历的序列的类型信息。这也展示了 **Iterator** 的真正威力:能够将遍历序列的操作与该序列的底层结构分离。出于这个原因,我们有时会说:迭代器统一了对集合的访问方式。\n", - "\n", - "我们可以使用 **Iterable** 接口生成上一个示例的更简洁版本,该接口描述了“可以产生 **Iterator** 的任何东西”:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/CrossCollectionIteration2.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class CrossCollectionIteration2 {\n", - " public static void display(Iterable ip) {\n", - " Iterator it = ip.iterator();\n", - " while(it.hasNext()) {\n", - " Pet p = it.next();\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " public static void main(String[] args) {\n", - " List pets = Pets.list(8);\n", - " LinkedList petsLL = new LinkedList<>(pets);\n", - " HashSet petsHS = new HashSet<>(pets);\n", - " TreeSet petsTS = new TreeSet<>(pets);\n", - " display(pets);\n", - " display(petsLL);\n", - " display(petsHS);\n", - " display(petsTS);\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug\n", - "0:Rat\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里所有的类都是 **Iterable** ,所以现在对 `display()` 的调用显然更简单。\n", - "\n", - "\n", - "### ListIterator\n", - "\n", - "**ListIterator** 是一个更强大的 **Iterator** 子类型,它只能由各种 **List** 类生成。 **Iterator** 只能向前移动,而 **ListIterator** 可以双向移动。它还可以生成相对于迭代器在列表中指向的当前位置的后一个和前一个元素的索引,并且可以使用 `set()` 方法替换它访问过的最近一个元素。可以通过调用 `listIterator()` 方法来生成指向 **List** 开头处的 **ListIterator** ,还可以通过调用 `listIterator(n)` 创建一个一开始就指向列表索引号为 **n** 的元素处的 **ListIterator** 。 下面的示例演示了所有这些能力:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ListIteration.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class ListIteration {\n", - " public static void main(String[] args) {\n", - " List pets = Pets.list(8);\n", - " ListIterator it = pets.listIterator();\n", - " while(it.hasNext())\n", - " System.out.print(it.next() +\n", - " \", \" + it.nextIndex() +\n", - " \", \" + it.previousIndex() + \"; \");\n", - " System.out.println();\n", - " // Backwards:\n", - " while(it.hasPrevious())\n", - " System.out.print(it.previous().id() + \" \");\n", - " System.out.println();\n", - " System.out.println(pets);\n", - " it = pets.listIterator(3);\n", - " while(it.hasNext()) {\n", - " it.next();\n", - " it.set(Pets.get());\n", - " }\n", - " System.out.println(pets);\n", - " }\n", - "}\n", - "/* Output:\n", - "Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug,\n", - "5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7;\n", - "7 6 5 4 3 2 1 0\n", - "[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx]\n", - "[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster,\n", - "EgyptianMau]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Pets.get()` 方法用来从位置 3 开始替换 **List** 中的所有 Pet 对象。\n", - "\n", - "\n", - "\n", - "## 链表LinkedList\n", - "\n", - "**LinkedList** 也像 **ArrayList** 一样实现了基本的 **List** 接口,但它在 **List** 中间执行插入和删除操作时比 **ArrayList** 更高效。然而,它在随机访问操作效率方面却要逊色一些。\n", - "\n", - "**LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque)** 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 **Queue** 中)。例如:\n", - "\n", - "- `getFirst()` 和 `element()` 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 **List** 为空,则抛出 **NoSuchElementException** 异常。 `peek()` 方法与这两个方法只是稍有差异,它在列表为空时返回 **null** 。\n", - "- `removeFirst()` 和 `remove()` 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 **NoSuchElementException** 异常。 `poll()` 稍有差异,它在列表为空时返回 **null** 。\n", - "- `addFirst()` 在列表的开头插入一个元素。\n", - "- `offer()` 与 `add()` 和 `addLast()` 相同。 它们都在列表的尾部(末尾)添加一个元素。\n", - "- `removeLast()` 删除并返回列表的最后一个元素。\n", - "\n", - "下面的示例展示了这些功能之间基本的相似性和差异性。它并不是重复执行 **ListFeatures.java** 中所示的行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/LinkedListFeatures.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class LinkedListFeatures {\n", - " public static void main(String[] args) {\n", - " LinkedList pets =\n", - " new LinkedList<>(Pets.list(5));\n", - " System.out.println(pets);\n", - " // Identical:\n", - " System.out.println(\n", - " \"pets.getFirst(): \" + pets.getFirst());\n", - " System.out.println(\n", - " \"pets.element(): \" + pets.element());\n", - " // Only differs in empty-list behavior:\n", - " System.out.println(\"pets.peek(): \" + pets.peek());\n", - " // Identical; remove and return the first element:\n", - " System.out.println(\n", - " \"pets.remove(): \" + pets.remove());\n", - " System.out.println(\n", - " \"pets.removeFirst(): \" + pets.removeFirst());\n", - " // Only differs in empty-list behavior:\n", - " System.out.println(\"pets.poll(): \" + pets.poll());\n", - " System.out.println(pets);\n", - " pets.addFirst(new Rat());\n", - " System.out.println(\"After addFirst(): \" + pets);\n", - " pets.offer(Pets.get());\n", - " System.out.println(\"After offer(): \" + pets);\n", - " pets.add(Pets.get());\n", - " System.out.println(\"After add(): \" + pets);\n", - " pets.addLast(new Hamster());\n", - " System.out.println(\"After addLast(): \" + pets);\n", - " System.out.println(\n", - " \"pets.removeLast(): \" + pets.removeLast());\n", - " }\n", - "}\n", - "/* Output:\n", - "[Rat, Manx, Cymric, Mutt, Pug]\n", - "pets.getFirst(): Rat\n", - "pets.element(): Rat\n", - "pets.peek(): Rat\n", - "pets.remove(): Rat\n", - "pets.removeFirst(): Manx\n", - "pets.poll(): Cymric\n", - "[Mutt, Pug]\n", - "After addFirst(): [Rat, Mutt, Pug]\n", - "After offer(): [Rat, Mutt, Pug, Cymric]\n", - "After add(): [Rat, Mutt, Pug, Cymric, Pug]\n", - "After addLast(): [Rat, Mutt, Pug, Cymric, Pug, Hamster]\n", - "pets.removeLast(): Hamster\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Pets.list()` 的结果被传递给 **LinkedList** 的构造器,以便使用它来填充 **LinkedList** 。如果查看 **Queue** 接口就会发现,它在 **LinkedList** 的基础上添加了 `element()` , `offer()` , `peek()` , `poll()` 和 `remove()` 方法,以使其可以成为一个 **Queue** 的实现。 **Queue** 的完整示例将在本章稍后给出。\n", - "\n", - "\n", - "\n", - "## 堆栈Stack\n", - "\n", - "堆栈是“后进先出”(LIFO)集合。它有时被称为*叠加栈*(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。\n", - "\n", - "Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们永远坚持 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/StackTest.java\n", - "import java.util.*;\n", - "\n", - "public class StackTest {\n", - " public static void main(String[] args) {\n", - " Deque stack = new ArrayDeque<>();\n", - " for(String s : \"My dog has fleas\".split(\" \"))\n", - " stack.push(s);\n", - " while(!stack.isEmpty())\n", - " System.out.print(stack.pop() + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "fleas has dog My\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "即使它是作为一个堆栈在使用,我们仍然必须将其声明为 **Deque** 。有时一个名为 **Stack** 的类更能把事情讲清楚:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Stack.java\n", - "// A Stack class built with an ArrayDeque\n", - "package onjava;\n", - "import java.util.Deque;\n", - "import java.util.ArrayDeque;\n", - "\n", - "public class Stack {\n", - " private Deque storage = new ArrayDeque<>();\n", - " public void push(T v) { storage.push(v); }\n", - " public T peek() { return storage.peek(); }\n", - " public T pop() { return storage.pop(); }\n", - " public boolean isEmpty() { return storage.isEmpty(); }\n", - " @Override\n", - " public String toString() {\n", - " return storage.toString();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。\n", - "\n", - "如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。\n", - "\n", - "下面将使用 **StackTest.java** 中的相同代码来演示这个新的 **Stack** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/StackTest2.java\n", - "import onjava.*;\n", - "\n", - "public class StackTest2 {\n", - " public static void main(String[] args) {\n", - " Stack stack = new Stack<>();\n", - " for(String s : \"My dog has fleas\".split(\" \"))\n", - " stack.push(s);\n", - " while(!stack.isEmpty())\n", - " System.out.print(stack.pop() + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "fleas has dog My\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果想在自己的代码中使用这个 **Stack** 类,当在创建其实例时,就需要完整指定包名,或者更改这个类的名称;否则,就有可能会与 **java.util** 包中的 **Stack** 发生冲突。例如,如果我们在上面的例子中导入 **java.util.***,那么就必须使用包名来防止冲突:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/StackCollision.java\n", - "\n", - "public class StackCollision {\n", - " public static void main(String[] args) {\n", - " onjava.Stack stack = new onjava.Stack<>();\n", - " for(String s : \"My dog has fleas\".split(\" \"))\n", - " stack.push(s);\n", - " while(!stack.isEmpty())\n", - " System.out.print(stack.pop() + \" \");\n", - " System.out.println();\n", - " java.util.Stack stack2 =\n", - " new java.util.Stack<>();\n", - " for(String s : \"My dog has fleas\".split(\" \"))\n", - " stack2.push(s);\n", - " while(!stack2.empty())\n", - " System.out.print(stack2.pop() + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "fleas has dog My\n", - "fleas has dog My\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管已经有了 **java.util.Stack** ,但是 **ArrayDeque** 可以产生更好的 **Stack** ,因此更可取。\n", - "\n", - "还可以使用显式导入来控制对“首选” **Stack** 实现的选择:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "import onjava.Stack;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,任何对 **Stack** 的引用都将选择 **onjava** 版本,而在选择 **java.util.Stack** 时,必须使用全限定名称(full qualification)。\n", - "\n", - "\n", - "## 集合Set\n", - "\n", - "**Set** 不保存重复的元素。 如果试图将相同对象的多个实例添加到 **Set** 中,那么它会阻止这种重复行为。 **Set** 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 **Set** 中。因此,查找通常是 **Set** 最重要的操作,因此通常会选择 **HashSet** 实现,该实现针对快速查找进行了优化。\n", - "\n", - "**Set** 具有与 **Collection** 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 **List** 那样。实际上, **Set** 就是一个 **Collection** ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)**Set** 根据对象的“值”确定归属性,更复杂的内容将在[附录:集合主题]()中介绍。\n", - "\n", - "下面是使用存放 **Integer** 对象的 **HashSet** 的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SetOfInteger.java\n", - "import java.util.*;\n", - "\n", - "public class SetOfInteger {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " Set intset = new HashSet<>();\n", - " for(int i = 0; i < 10000; i++)\n", - " intset.add(rand.nextInt(30));\n", - " System.out.println(intset);\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", - "16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 0 到 29 之间的 10000 个随机整数被添加到 **Set** 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。\n", - "\n", - "早期 Java 版本中的 **HashSet** 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, **HashSet** 使用了散列,请参阅[附录:集合主题]()一章。由 **HashSet** 维护的顺序与 **TreeSet** 或 **LinkedHashSet** 不同,因为它们的实现具有不同的元素存储方式。 **TreeSet** 将元素存储在红-黑树数据结构中,而 **HashSet** 使用散列函数。 **LinkedHashSet** 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 **Integer** 按顺序排序。但是,您不应该依赖此行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SetOfString.java\n", - "import java.util.*;\n", - "\n", - "public class SetOfString {\n", - " public static void main(String[] args) {\n", - " Set colors = new HashSet<>();\n", - " for(int i = 0; i < 100; i++) {\n", - " colors.add(\"Yellow\");\n", - " colors.add(\"Blue\");\n", - " colors.add(\"Red\");\n", - " colors.add(\"Red\");\n", - " colors.add(\"Orange\");\n", - " colors.add(\"Yellow\");\n", - " colors.add(\"Blue\");\n", - " colors.add(\"Purple\");\n", - " }\n", - " System.out.println(colors);\n", - " }\n", - "}\n", - "/* Output:\n", - "[Red, Yellow, Blue, Purple, Orange]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**String** 对象似乎没有排序。要对结果进行排序,一种方法是使用 **TreeSet** 而不是 **HashSet** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SortedSetOfString.java\n", - "import java.util.*;\n", - "\n", - "public class SortedSetOfString {\n", - " public static void main(String[] args) {\n", - " Set colors = new TreeSet<>();\n", - " for(int i = 0; i < 100; i++) {\n", - " colors.add(\"Yellow\");\n", - " colors.add(\"Blue\");\n", - " colors.add(\"Red\");\n", - " colors.add(\"Red\");\n", - " colors.add(\"Orange\");\n", - " colors.add(\"Yellow\");\n", - " colors.add(\"Blue\");\n", - " colors.add(\"Purple\");\n", - " }\n", - " System.out.println(colors);\n", - " }\n", - "}\n", - "/* Output:\n", - "[Blue, Orange, Purple, Red, Yellow]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最常见的操作之一是使用 `contains()` 测试成员归属性,但也有一些其它操作,这可能会让你想起在小学学过的维恩图(译者注:利用图形的交合表示多个集合之间的逻辑关系):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/SetOperations.java\n", - "import java.util.*;\n", - "\n", - "public class SetOperations {\n", - " public static void main(String[] args) {\n", - " Set set1 = new HashSet<>();\n", - " Collections.addAll(set1,\n", - " \"A B C D E F G H I J K L\".split(\" \"));\n", - " set1.add(\"M\");\n", - " System.out.println(\"H: \" + set1.contains(\"H\"));\n", - " System.out.println(\"N: \" + set1.contains(\"N\"));\n", - " Set set2 = new HashSet<>();\n", - " Collections.addAll(set2, \"H I J K L\".split(\" \"));\n", - " System.out.println(\n", - " \"set2 in set1: \" + set1.containsAll(set2));\n", - " set1.remove(\"H\");\n", - " System.out.println(\"set1: \" + set1);\n", - " System.out.println(\n", - " \"set2 in set1: \" + set1.containsAll(set2));\n", - " set1.removeAll(set2);\n", - " System.out.println(\n", - " \"set2 removed from set1: \" + set1);\n", - " Collections.addAll(set1, \"X Y Z\".split(\" \"));\n", - " System.out.println(\n", - " \"'X Y Z' added to set1: \" + set1);\n", - " }\n", - "}\n", - "/* Output:\n", - "H: true\n", - "N: false\n", - "set2 in set1: true\n", - "set1: [A, B, C, D, E, F, G, I, J, K, L, M]\n", - "set2 in set1: false\n", - "set2 removed from set1: [A, B, C, D, E, F, G, M]\n", - "'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y,\n", - "Z]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些方法名都是自解释的,JDK 文档中还有一些其它的方法。\n", - "\n", - "能够产生每个元素都唯一的列表是相当有用的功能。例如,假设想要列出上面的 **SetOperations.java** 文件中的所有单词,通过使用本书后面介绍的 `java.nio.file.Files.readAllLines()` 方法,可以打开一个文件,并将其作为一个 **List\\** 读取,每个 **String** 都是输入文件中的一行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/UniqueWords.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "\n", - "public class UniqueWords {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " List lines = Files.readAllLines(\n", - " Paths.get(\"SetOperations.java\"));\n", - " Set words = new TreeSet<>();\n", - " for(String line : lines)\n", - " for(String word : line.split(\"\\\\W+\"))\n", - " if(word.trim().length() > 0)\n", - " words.add(word);\n", - " System.out.println(words);\n", - " }\n", - "}\n", - "/* Output:\n", - "[A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K,\n", - "L, M, N, Output, Set, SetOperations, String, System, X,\n", - "Y, Z, add, addAll, added, args, class, collections,\n", - "contains, containsAll, false, from, import, in, java,\n", - "main, new, out, println, public, remove, removeAll,\n", - "removed, set1, set2, split, static, to, true, util,\n", - "void]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们逐步浏览文件中的每一行,并使用 `String.split()` 将其分解为单词,这里使用正则表达式 **\\\\\\ W +** ,这意味着它会依据一个或多个(即 **+** )非单词字母来拆分字符串(正则表达式将在[字符串]()章节介绍)。每个结果单词都会添加到 **Set words** 中。因为它是 **TreeSet** ,所以对结果进行排序。这里,排序是按*字典顺序*(lexicographically)完成的,因此大写和小写字母位于不同的组中。如果想按*字母顺序*(alphabetically)对其进行排序,可以向 **TreeSet** 构造器传入 **String.CASE_INSENSITIVE_ORDER** 比较器(比较器是一个建立排序顺序的对象):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/UniqueWordsAlphabetic.java\n", - "// Producing an alphabetic listing\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "\n", - "public class UniqueWordsAlphabetic {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " List lines = Files.readAllLines(\n", - " Paths.get(\"SetOperations.java\"));\n", - " Set words =\n", - " new TreeSet<>(String.CASE_INSENSITIVE_ORDER);\n", - " for(String line : lines)\n", - " for(String word : line.split(\"\\\\W+\"))\n", - " if(word.trim().length() > 0)\n", - " words.add(word);\n", - " System.out.println(words);\n", - " }\n", - "}\n", - "/* Output:\n", - "[A, add, addAll, added, args, B, C, class, collections,\n", - "contains, containsAll, D, E, F, false, from, G, H,\n", - "HashSet, I, import, in, J, java, K, L, M, main, N, new,\n", - "out, Output, println, public, remove, removeAll,\n", - "removed, Set, set1, set2, SetOperations, split, static,\n", - "String, System, to, true, util, void, X, Y, Z]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Comparator** 比较器将在[数组]()章节详细介绍。\n", - "\n", - "\n", - "## 映射Map\n", - "\n", - "将对象映射到其他对象的能力是解决编程问题的有效方法。例如,考虑一个程序,它被用来检查 Java 的 **Random** 类的随机性。理想情况下, **Random** 会产生完美的数字分布,但为了测试这一点,则需要生成大量的随机数,并计算落在各种范围内的数字个数。 **Map** 可以很容易地解决这个问题。在本例中,键是 **Random** 生成的数字,而值是该数字出现的次数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/Statistics.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Simple demonstration of HashMap\n", - "import java.util.*;\n", - "\n", - "public class Statistics {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " Map m = new HashMap<>();\n", - " for(int i = 0; i < 10000; i++) {\n", - " // Produce a number between 0 and 20:\n", - " int r = rand.nextInt(20);\n", - " Integer freq = m.get(r); // [1]\n", - " m.put(r, freq == null ? 1 : freq + 1);\n", - " }\n", - " System.out.println(m);\n", - " }\n", - "}\n", - "/* Output:\n", - "{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519,\n", - "7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506,\n", - "14=477, 15=497, 16=533, 17=509, 18=478, 19=464}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- **[1]** 自动包装机制将随机生成的 **int** 转换为可以与 **HashMap** 一起使用的 **Integer** 引用(不能使用基本类型的集合)。如果键不在集合中,则 `get()` 返回 **null** (这意味着该数字第一次出现)。否则, `get()` 会为键生成与之关联的 **Integer** 值,然后该值被递增(自动包装机制再次简化了表达式,但实际上确实发生了对 **Integer** 的装箱和拆箱)。\n", - "\n", - "接下来的示例将使用一个 **String** 描述来查找 **Pet** 对象。它还展示了通过使用 `containsKey()` 和 `containsValue()` 方法去测试一个 **Map** ,以查看它是否包含某个键或某个值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/PetMap.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class PetMap {\n", - " public static void main(String[] args) {\n", - " Map petMap = new HashMap<>();\n", - " petMap.put(\"My Cat\", new Cat(\"Molly\"));\n", - " petMap.put(\"My Dog\", new Dog(\"Ginger\"));\n", - " petMap.put(\"My Hamster\", new Hamster(\"Bosco\"));\n", - " System.out.println(petMap);\n", - " Pet dog = petMap.get(\"My Dog\");\n", - " System.out.println(dog);\n", - " System.out.println(petMap.containsKey(\"My Dog\"));\n", - " System.out.println(petMap.containsValue(dog));\n", - " }\n", - "}\n", - "/* Output:\n", - "{My Dog=Dog Ginger, My Cat=Cat Molly, My\n", - "Hamster=Hamster Bosco}\n", - "Dog Ginger\n", - "true\n", - "true\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\\>** 即可:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\n", - "// collections/MapOfList.java\n", - "// {java collections.MapOfList}\n", - "package collections;\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class MapOfList {\n", - " public static final Map>\n", - " petPeople = new HashMap<>();\n", - " static {\n", - " petPeople.put(new Person(\"Dawn\"),\n", - " Arrays.asList(\n", - " new Cymric(\"Molly\"),\n", - " new Mutt(\"Spot\")));\n", - " petPeople.put(new Person(\"Kate\"),\n", - " Arrays.asList(new Cat(\"Shackleton\"),\n", - " new Cat(\"Elsie May\"), new Dog(\"Margrett\")));\n", - " petPeople.put(new Person(\"Marilyn\"),\n", - " Arrays.asList(\n", - " new Pug(\"Louie aka Louis Snorkelstein Dupree\"),\n", - " new Cat(\"Stanford\"),\n", - " new Cat(\"Pinkola\")));\n", - " petPeople.put(new Person(\"Luke\"),\n", - " Arrays.asList(\n", - " new Rat(\"Fuzzy\"), new Rat(\"Fizzy\")));\n", - " petPeople.put(new Person(\"Isaac\"),\n", - " Arrays.asList(new Rat(\"Freckly\")));\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(\"People: \" + petPeople.keySet());\n", - " System.out.println(\"Pets: \" + petPeople.values());\n", - " for(Person person : petPeople.keySet()) {\n", - " System.out.println(person + \" has:\");\n", - " for(Pet pet : petPeople.get(person))\n", - " System.out.println(\" \" + pet);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "People: [Person Dawn, Person Kate, Person Isaac, Person\n", - "Marilyn, Person Luke]\n", - "Pets: [[Cymric Molly, Mutt Spot], [Cat Shackleton, Cat\n", - "Elsie May, Dog Margrett], [Rat Freckly], [Pug Louie aka\n", - "Louis Snorkelstein Dupree, Cat Stanford, Cat Pinkola],\n", - "[Rat Fuzzy, Rat Fizzy]]\n", - "Person Dawn has:\n", - " Cymric Molly\n", - " Mutt Spot\n", - "Person Kate has:\n", - " Cat Shackleton\n", - " Cat Elsie May\n", - " Dog Margrett\n", - "Person Isaac has:\n", - " Rat Freckly\n", - "Person Marilyn has:\n", - " Pug Louie aka Louis Snorkelstein Dupree\n", - " Cat Stanford\n", - " Cat Pinkola\n", - "Person Luke has:\n", - " Rat Fuzzy\n", - " Rat Fizzy\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Map** 可以返回由其键组成的 **Set** ,由其值组成的 **Collection** ,或者其键值对的 **Set** 。 `keySet()` 方法生成由在 **petPeople** 中的所有键组成的 **Set** ,它在 *for-in* 语句中被用来遍历该 **Map** 。\n", - "\n", - "\n", - "\n", - "## 队列Queue\n", - "\n", - "队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在[并发编程]()中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。\n", - "\n", - "**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中与 **Queue** 相关(Queue-specific)的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/QueueDemo.java\n", - "// Upcasting to a Queue from a LinkedList\n", - "import java.util.*;\n", - "\n", - "public class QueueDemo {\n", - " public static void printQ(Queue queue) {\n", - " while(queue.peek() != null)\n", - " System.out.print(queue.remove() + \" \");\n", - " System.out.println();\n", - " }\n", - " public static void main(String[] args) {\n", - " Queue queue = new LinkedList<>();\n", - " Random rand = new Random(47);\n", - " for(int i = 0; i < 10; i++)\n", - " queue.offer(rand.nextInt(i + 10));\n", - " printQ(queue);\n", - " Queue qc = new LinkedList<>();\n", - " for(char c : \"Brontosaurus\".toCharArray())\n", - " qc.offer(c);\n", - " printQ(qc);\n", - " }\n", - "}\n", - "/* Output:\n", - "8 1 1 1 5 14 3 1 0 1\n", - "B r o n t o s a u r u s\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`offer()` 是与 **Queue** 相关的方法之一,它在允许的情况下,在队列的尾部插入一个元素,或者返回 **false** 。 `peek()` 和 `element()` 都返回队头元素而不删除它,但是如果队列为空,则 `element()` 抛出 **NoSuchElementException** ,而 `peek()` 返回 **null** 。 `poll()` 和 `remove()`* 都删除并返回队头元素,但如果队列为空,`poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。\n", - "\n", - "自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。\n", - "\n", - "与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。\n", - "\n", - "\n", - "### 优先级队列PriorityQueue\n", - "\n", - "先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个弹出的元素应该是等待时间最长的元素。\n", - "\n", - "优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。\n", - "\n", - "当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。\n", - "\n", - "让 **PriorityQueue** 与 **Integer** , **String** 和 **Character** 这样的内置类型一起工作易如反掌。在下面的示例中,第一组值与前一个示例中的随机值相同,可以看到它们从 **PriorityQueue** 中弹出的顺序与前一个示例不同:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/PriorityQueueDemo.java\n", - "import java.util.*;\n", - "\n", - "public class PriorityQueueDemo {\n", - " public static void main(String[] args) {\n", - " PriorityQueue priorityQueue =\n", - " new PriorityQueue<>();\n", - " Random rand = new Random(47);\n", - " for(int i = 0; i < 10; i++)\n", - " priorityQueue.offer(rand.nextInt(i + 10));\n", - " QueueDemo.printQ(priorityQueue);\n", - "\n", - " List ints = Arrays.asList(25, 22, 20,\n", - " 18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25);\n", - " priorityQueue = new PriorityQueue<>(ints);\n", - " QueueDemo.printQ(priorityQueue);\n", - " priorityQueue = new PriorityQueue<>(\n", - " ints.size(), Collections.reverseOrder());\n", - " priorityQueue.addAll(ints);\n", - " QueueDemo.printQ(priorityQueue);\n", - "\n", - " String fact = \"EDUCATION SHOULD ESCHEW OBFUSCATION\";\n", - " List strings =\n", - " Arrays.asList(fact.split(\"\"));\n", - " PriorityQueue stringPQ =\n", - " new PriorityQueue<>(strings);\n", - " QueueDemo.printQ(stringPQ);\n", - " stringPQ = new PriorityQueue<>(\n", - " strings.size(), Collections.reverseOrder());\n", - " stringPQ.addAll(strings);\n", - " QueueDemo.printQ(stringPQ);\n", - "\n", - " Set charSet = new HashSet<>();\n", - " for(char c : fact.toCharArray())\n", - " charSet.add(c); // Autoboxing\n", - " PriorityQueue characterPQ =\n", - " new PriorityQueue<>(charSet);\n", - " QueueDemo.printQ(characterPQ);\n", - " }\n", - "}\n", - "/* Output:\n", - "0 1 1 1 1 1 3 5 8 14\n", - "1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25\n", - "25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1\n", - " A A B C C C D D E E E F H H I I L N N O O O O S S\n", - "S T T U U U W\n", - "W U U U T T S S S O O O O N N L I I H H F E E E D D C C\n", - "C B A A\n", - " A B C D E F H I L N O S T U W\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**PriorityQueue** 是允许重复的,最小的值具有最高的优先级(如果是 **String** ,空格也可以算作值,并且比字母的优先级高)。为了展示如何通过提供自己的 **Comparator** 对象来改变顺序,第三个对 **PriorityQueue\\** 构造器的调用,和第二个对 **PriorityQueue\\** 的调用使用了由 `Collections.reverseOrder()` (Java 5 中新添加的)产生的反序的 **Comparator** 。\n", - "\n", - "最后一部分添加了一个 **HashSet** 来消除重复的 **Character**。\n", - "\n", - "**Integer** , **String** 和 **Character** 可以与 **PriorityQueue** 一起使用,因为这些类已经内置了自然排序。如果想在 **PriorityQueue** 中使用自己的类,则必须包含额外的功能以产生自然排序,或者必须提供自己的 **Comparator** 。在[附录:集合主题]()中有一个更复杂的示例来演示这种情况。\n", - "\n", - "\n", - "## 集合与迭代器\n", - "\n", - "**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。\n", - "\n", - "使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的代码可以应用于更多类型的对象。[^6]因此,如果所编写的方法接受一个 **Collection** ,那么该方法可以应用于任何实现了 **Collection** 的类——这也就使得一个新类可以选择去实现 **Collection** 接口,以便该方法可以使用它。标准 C++ 类库中的集合并没有共同的基类——集合之间的所有共性都是通过迭代器实现的。在 Java 中,遵循 C++ 的方式看起来似乎很明智,即用迭代器而不是 **Collection** 来表示集合之间的共性。但是,这两种方法绑定在了一起,因为实现 **Collection** 就意味着需要提供 `iterator()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/InterfaceVsIterator.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class InterfaceVsIterator {\n", - " public static void display(Iterator it) {\n", - " while(it.hasNext()) {\n", - " Pet p = it.next();\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " public static void display(Collection pets) {\n", - " for(Pet p : pets)\n", - " System.out.print(p.id() + \":\" + p + \" \");\n", - " System.out.println();\n", - " }\n", - " public static void main(String[] args) {\n", - " List petList = Pets.list(8);\n", - " Set petSet = new HashSet<>(petList);\n", - " Map petMap = new LinkedHashMap<>();\n", - " String[] names = (\"Ralph, Eric, Robin, Lacey, \" +\n", - " \"Britney, Sam, Spot, Fluffy\").split(\", \");\n", - " for(int i = 0; i < names.length; i++)\n", - " petMap.put(names[i], petList.get(i));\n", - " display(petList);\n", - " display(petSet);\n", - " display(petList.iterator());\n", - " display(petSet.iterator());\n", - " System.out.println(petMap);\n", - " System.out.println(petMap.keySet());\n", - " display(petMap.values());\n", - " display(petMap.values().iterator());\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt,\n", - "Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx}\n", - "[Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy]\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "两个版本的 `display()` 方法都可以使用 **Map** 或 **Collection** 的子类型来工作。 而且**Collection** 接口和 **Iterator** 都将 `display()` 方法与低层集合的特定实现解耦。\n", - "\n", - "在本例中,这两种方式都可以奏效。事实上, **Collection** 要更方便一点,因为它是 **Iterable** 类型,因此在 `display(Collection)` 的实现中可以使用 *for-in* 构造,这使得代码更加清晰。\n", - "\n", - "当需要实现一个不是 **Collection** 的外部类时,由于让它去实现 **Collection** 接口可能非常困难或麻烦,因此使用 **Iterator** 就会变得非常吸引人。例如,如果我们通过继承一个持有 **Pet** 对象的类来创建一个 **Collection** 的实现,那么我们必须实现 **Collection** 所有的方法,即使我们不在 `display()` 方法中使用它们,也必须这样做。虽然这可以通过继承 **AbstractCollection** 而很容易地实现,但是无论如何还是要被强制去实现 `iterator()` 和 `size()` 方法,这些方法 **AbstractCollection** 没有实现,但是 **AbstractCollection** 中的其它方法会用到:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/CollectionSequence.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class CollectionSequence\n", - "extends AbstractCollection {\n", - " private Pet[] pets = Pets.array(8);\n", - " @Override\n", - " public int size() { return pets.length; }\n", - " @Override\n", - " public Iterator iterator() {\n", - " return new Iterator() { // [1]\n", - " private int index = 0;\n", - " @Override\n", - " public boolean hasNext() {\n", - " return index < pets.length;\n", - " }\n", - " @Override\n", - " public Pet next() { return pets[index++]; }\n", - " @Override\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " CollectionSequence c = new CollectionSequence();\n", - " InterfaceVsIterator.display(c);\n", - " InterfaceVsIterator.display(c.iterator());\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`remove()` 方法是一个“可选操作”,在[附录:集合主题]()中详细介绍。 这里可以不必实现它,如果你调用它,它将抛出异常。\n", - "\n", - "- **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。\n", - "\n", - "这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/NonCollectionSequence.java\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "class PetSequence {\n", - " protected Pet[] pets = Pets.array(8);\n", - "}\n", - "\n", - "public class NonCollectionSequence extends PetSequence {\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " private int index = 0;\n", - " @Override\n", - " public boolean hasNext() {\n", - " return index < pets.length;\n", - " }\n", - " @Override\n", - " public Pet next() { return pets[index++]; }\n", - " @Override\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " NonCollectionSequence nc =\n", - " new NonCollectionSequence();\n", - " InterfaceVsIterator.display(nc.iterator());\n", - " }\n", - "}\n", - "/* Output:\n", - "0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug\n", - "7:Manx\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "生成 **Iterator** 是将序列与消费该序列的方法连接在一起耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。\n", - "\n", - "\n", - "## for-in和迭代器\n", - "\n", - "到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是一个更通用的证明:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ForInCollections.java\n", - "// All collections work with for-in\n", - "import java.util.*;\n", - "\n", - "public class ForInCollections {\n", - " public static void main(String[] args) {\n", - " Collection cs = new LinkedList<>();\n", - " Collections.addAll(cs,\n", - " \"Take the long way home\".split(\" \"));\n", - " for(String s : cs)\n", - " System.out.print(\"'\" + s + \"' \");\n", - " }\n", - "}\n", - "/* Output:\n", - "'Take' 'the' 'long' 'way' 'home'\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于 **cs** 是一个 **Collection** ,因此该代码展示了使用 *for-in* 是所有 **Collection** 对象的特征。\n", - "\n", - "这样做的原因是 Java 5 引入了一个名为 **Iterable** 的接口,该接口包含一个能够生成 **Iterator** 的 `iterator()` 方法。*for-in* 使用此 **Iterable** 接口来遍历序列。因此,如果创建了任何实现了 **Iterable** 的类,都可以将它用于 *for-in* 语句中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/IterableClass.java\n", - "// Anything Iterable works with for-in\n", - "import java.util.*;\n", - "\n", - "public class IterableClass implements Iterable {\n", - " protected String[] words = (\"And that is how \" +\n", - " \"we know the Earth to be banana-shaped.\"\n", - " ).split(\" \");\n", - " @Override\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " private int index = 0;\n", - " @Override\n", - " public boolean hasNext() {\n", - " return index < words.length;\n", - " }\n", - " @Override\n", - " public String next() { return words[index++]; }\n", - " @Override\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " for(String s : new IterableClass())\n", - " System.out.print(s + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "And that is how we know the Earth to be banana-shaped.\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`iterator()` 返回的是实现了 **Iterator\\** 的匿名内部类的实例,该匿名内部类可以遍历数组中的每个单词。在主方法中,可以看到 **IterableClass** 确实可以用于 *for-in* 语句。\n", - "\n", - "在 Java 5 中,许多类都是 **Iterable** ,主要包括所有的 **Collection** 类(但不包括各种 **Maps** )。 例如,下面的代码可以显示所有的操作系统环境变量:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/EnvironmentVariables.java\n", - "// {VisuallyInspectOutput}\n", - "import java.util.*;\n", - "\n", - "public class EnvironmentVariables {\n", - " public static void main(String[] args) {\n", - " for(Map.Entry entry: System.getenv().entrySet()) {\n", - " System.out.println(entry.getKey() + \": \" +\n", - " entry.getValue());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`System.getenv()` [^7]返回一个 **Map** , `entrySet()` 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。\n", - "\n", - "*for-in* 语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ArrayIsNotIterable.java\n", - "import java.util.*;\n", - "\n", - "public class ArrayIsNotIterable {\n", - " static void test(Iterable ib) {\n", - " for(T t : ib)\n", - " System.out.print(t + \" \");\n", - " }\n", - " public static void main(String[] args) {\n", - " test(Arrays.asList(1, 2, 3));\n", - " String[] strings = { \"A\", \"B\", \"C\" };\n", - " // An array works in for-in, but it's not Iterable:\n", - " //- test(strings);\n", - " // You must explicitly convert it to an Iterable:\n", - " test(Arrays.asList(strings));\n", - " }\n", - "}\n", - "/* Output:\n", - "1 2 3 A B C\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尝试将数组作为一个 **Iterable** 参数传递会导致失败。这说明不存在任何从数组到 **Iterable** 的自动转换; 必须手工执行这种转换。\n", - "\n", - "\n", - "### 适配器方法惯用法\n", - "\n", - "如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。\n", - "\n", - "一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。\n", - "在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/AdapterMethodIdiom.java\n", - "// The \"Adapter Method\" idiom uses for-in\n", - "// with additional kinds of Iterables\n", - "import java.util.*;\n", - "\n", - "class ReversibleArrayList extends ArrayList {\n", - " ReversibleArrayList(Collection c) {\n", - " super(c);\n", - " }\n", - " public Iterable reversed() {\n", - " return new Iterable() {\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " int current = size() - 1;\n", - " public boolean hasNext() {\n", - " return current > -1;\n", - " }\n", - " public T next() { return get(current--); }\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " };\n", - " }\n", - "}\n", - "\n", - "public class AdapterMethodIdiom {\n", - " public static void main(String[] args) {\n", - " ReversibleArrayList ral =\n", - " new ReversibleArrayList(\n", - " Arrays.asList(\"To be or not to be\".split(\" \")));\n", - " // Grabs the ordinary iterator via iterator():\n", - " for(String s : ral)\n", - " System.out.print(s + \" \");\n", - " System.out.println();\n", - " // Hand it the Iterable of your choice\n", - " for(String s : ral.reversed())\n", - " System.out.print(s + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "To be or not to be\n", - "be to not or be To\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在主方法中,如果直接将 **ral** 对象放在 *for-in* 语句中,则会得到(默认的)正向迭代器。但是如果在该对象上调用 `reversed()` 方法,它会产生不同的行为。\n", - "\n", - "通过使用这种方式,可以在 **IterableClass.java** 示例中添加两种适配器方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/MultiIterableClass.java\n", - "// Adding several Adapter Methods\n", - "import java.util.*;\n", - "\n", - "public class MultiIterableClass extends IterableClass {\n", - " public Iterable reversed() {\n", - " return new Iterable() {\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " int current = words.length - 1;\n", - " public boolean hasNext() {\n", - " return current > -1;\n", - " }\n", - " public String next() {\n", - " return words[current--];\n", - " }\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " };\n", - " }\n", - " public Iterable randomized() {\n", - " return new Iterable() {\n", - " public Iterator iterator() {\n", - " List shuffled =\n", - " new ArrayList(Arrays.asList(words));\n", - " Collections.shuffle(shuffled, new Random(47));\n", - " return shuffled.iterator();\n", - " }\n", - " };\n", - " }\n", - " public static void main(String[] args) {\n", - " MultiIterableClass mic = new MultiIterableClass();\n", - " for(String s : mic.reversed())\n", - " System.out.print(s + \" \");\n", - " System.out.println();\n", - " for(String s : mic.randomized())\n", - " System.out.print(s + \" \");\n", - " System.out.println();\n", - " for(String s : mic)\n", - " System.out.print(s + \" \");\n", - " }\n", - "}\n", - "/* Output:\n", - "banana-shaped. be to Earth the know we how is that And\n", - "is banana-shaped. Earth that how the be And we know to\n", - "And that is how we know the Earth to be banana-shaped.\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,第二个方法 `random()` 没有创建它自己的 **Iterator** ,而是直接返回被打乱的 **List** 中的 **Iterator** 。\n", - "\n", - "从输出中可以看到, `Collections.shuffle()` 方法不会影响到原始数组,而只是打乱了 **shuffled** 中的引用。之所以这样,是因为 `randomized()` 方法用一个 **ArrayList** 将 `Arrays.asList()` 的结果包装了起来。如果这个由 `Arrays.asList()` 生成的 **List** 被直接打乱,那么它将修改底层数组,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/ModifyingArraysAsList.java\n", - "import java.util.*;\n", - "\n", - "public class ModifyingArraysAsList {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " Integer[] ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };\n", - " List list1 =\n", - " new ArrayList<>(Arrays.asList(ia));\n", - " System.out.println(\"Before shuffling: \" + list1);\n", - " Collections.shuffle(list1, rand);\n", - " System.out.println(\"After shuffling: \" + list1);\n", - " System.out.println(\"array: \" + Arrays.toString(ia));\n", - "\n", - " List list2 = Arrays.asList(ia);\n", - " System.out.println(\"Before shuffling: \" + list2);\n", - " Collections.shuffle(list2, rand);\n", - " System.out.println(\"After shuffling: \" + list2);\n", - " System.out.println(\"array: \" + Arrays.toString(ia));\n", - " }\n", - "}\n", - "/* Output:\n", - "Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", - "After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9]\n", - "array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", - "Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", - "After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]\n", - "array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在第一种情况下, `Arrays.asList()` 的输出被传递给了 **ArrayList** 的构造器,这将创建一个引用 **ia** 的元素的 **ArrayList** ,因此打乱这些引用不会修改该数组。但是,如果直接使用 `Arrays.asList(ia)` 的结果,这种打乱就会修改 **ia** 的顺序。重要的是要注意 `Arrays.asList()` 生成一个 **List** 对象,该对象使用底层数组作为其物理实现。如果执行的操作会修改这个 **List** ,并且不希望修改原始数组,那么就应该在另一个集合中创建一个副本。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "Java 提供了许多保存对象的方法:\n", - "\n", - "1. 数组将数字索引与对象相关联。它保存类型明确的对象,因此在查找对象时不必对结果做类型转换。它可以是多维的,可以保存基本类型的数据。虽然可以在运行时创建数组,但是一旦创建数组,就无法更改数组的大小。\n", - "\n", - "2. **Collection** 保存单一的元素,而 **Map** 包含相关联的键值对。使用 Java 泛型,可以指定集合中保存的对象的类型,因此不能将错误类型的对象放入集合中,并且在从集合中获取元素时,不必进行类型转换。各种 **Collection** 和各种 **Map** 都可以在你向其中添加更多的元素时,自动调整其尺寸大小。集合不能保存基本类型,但自动装箱机制会负责执行基本类型和集合中保存的包装类型之间的双向转换。\n", - "\n", - "3. 像数组一样, **List** 也将数字索引与对象相关联,因此,数组和 **List** 都是有序集合。\n", - "\n", - "4. 如果要执行大量的随机访问,则使用 **ArrayList** ,如果要经常从表中间插入或删除元素,则应该使用 **LinkedList** 。\n", - "\n", - "5. 队列和堆栈的行为是通过 **LinkedList** 提供的。\n", - "\n", - "6. **Map** 是一种将对象(而非数字)与对象相关联的设计。 **HashMap** 专为快速访问而设计,而 **TreeMap** 保持键始终处于排序状态,所以没有 **HashMap** 快。 **LinkedHashMap** 按插入顺序保存其元素,但使用散列提供快速访问的能力。\n", - "\n", - "7. **Set** 不接受重复元素。 **HashSet** 提供最快的查询速度,而 **TreeSet** 保持元素处于排序状态。 **LinkedHashSet** 按插入顺序保存其元素,但使用散列提供快速访问的能力。\n", - "\n", - "8. 不要在新代码中使用遗留类 **Vector** ,**Hashtable** 和 **Stack** 。\n", - "\n", - "浏览一下Java集合的简图(不包含抽象类或遗留组件)会很有帮助。这里仅包括在一般情况下会碰到的接口和类。(译者注:下图为原著PDF中的截图,可能由于未知原因存在问题。这里可参考译者绘制版[^8])\n", - "\n", - "![simple collection taxonomy](../images/simple-collection-taxonomy.png)\n", - "\n", - "### 简单集合分类\n", - "\n", - "可以看到,实际上只有四个基本的集合组件: **Map** , **List** , **Set** 和 **Queue** ,它们各有两到三个实现版本(**Queue** 的 **java.util.concurrent** 实现未包含在此图中)。最常使用的集合用黑色粗线线框表示。\n", - "\n", - "虚线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的虚线表示特定的类实现了一个接口。实心箭头表示某个类可以生成箭头指向的类的对象。例如,任何 **Collection** 都可以生成 **Iterator** , **List** 可以生成 **ListIterator** (也能生成普通的 **Iterator** ,因为 **List** 继承自 **Collection** )。\n", - "\n", - "下面的示例展示了各种不同的类在方法上的差异。实际代码来自[泛型]()章节,在这里只是调用它来产生输出。程序的输出还展示了在每个类或接口中所实现的接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collections/CollectionDifferences.java\n", - "import onjava.*;\n", - "\n", - "public class CollectionDifferences {\n", - " public static void main(String[] args) {\n", - " CollectionMethodDifferences.main(args);\n", - " }\n", - "}\n", - "/* Output:\n", - "Collection: [add, addAll, clear, contains, containsAll,\n", - "equals, forEach, hashCode, isEmpty, iterator,\n", - "parallelStream, remove, removeAll, removeIf, retainAll,\n", - "size, spliterator, stream, toArray]\n", - "Interfaces in Collection: [Iterable]\n", - "Set extends Collection, adds: []\n", - "Interfaces in Set: [Collection]\n", - "HashSet extends Set, adds: []\n", - "Interfaces in HashSet: [Set, Cloneable, Serializable]\n", - "LinkedHashSet extends HashSet, adds: []\n", - "Interfaces in LinkedHashSet: [Set, Cloneable,\n", - "Serializable]\n", - "TreeSet extends Set, adds: [headSet,\n", - "descendingIterator, descendingSet, pollLast, subSet,\n", - "floor, tailSet, ceiling, last, lower, comparator,\n", - "pollFirst, first, higher]\n", - "Interfaces in TreeSet: [NavigableSet, Cloneable,\n", - "Serializable]\n", - "List extends Collection, adds: [replaceAll, get,\n", - "indexOf, subList, set, sort, lastIndexOf, listIterator]\n", - "Interfaces in List: [Collection]\n", - "ArrayList extends List, adds: [trimToSize,\n", - "ensureCapacity]\n", - "Interfaces in ArrayList: [List, RandomAccess,\n", - "Cloneable, Serializable]\n", - "LinkedList extends List, adds: [offerFirst, poll,\n", - "getLast, offer, getFirst, removeFirst, element,\n", - "removeLastOccurrence, peekFirst, peekLast, push,\n", - "pollFirst, removeFirstOccurrence, descendingIterator,\n", - "pollLast, removeLast, pop, addLast, peek, offerLast,\n", - "addFirst]\n", - "Interfaces in LinkedList: [List, Deque, Cloneable,\n", - "Serializable]\n", - "Queue extends Collection, adds: [poll, peek, offer,\n", - "element]\n", - "Interfaces in Queue: [Collection]\n", - "PriorityQueue extends Queue, adds: [comparator]\n", - "Interfaces in PriorityQueue: [Serializable]\n", - "Map: [clear, compute, computeIfAbsent,\n", - "computeIfPresent, containsKey, containsValue, entrySet,\n", - "equals, forEach, get, getOrDefault, hashCode, isEmpty,\n", - "keySet, merge, put, putAll, putIfAbsent, remove,\n", - "replace, replaceAll, size, values]\n", - "HashMap extends Map, adds: []\n", - "Interfaces in HashMap: [Map, Cloneable, Serializable]\n", - "LinkedHashMap extends HashMap, adds: []\n", - "Interfaces in LinkedHashMap: [Map]\n", - "SortedMap extends Map, adds: [lastKey, subMap,\n", - "comparator, firstKey, headMap, tailMap]\n", - "Interfaces in SortedMap: [Map]\n", - "TreeMap extends Map, adds: [descendingKeySet,\n", - "navigableKeySet, higherEntry, higherKey, floorKey,\n", - "subMap, ceilingKey, pollLastEntry, firstKey, lowerKey,\n", - "headMap, tailMap, lowerEntry, ceilingEntry,\n", - "descendingMap, pollFirstEntry, lastKey, firstEntry,\n", - "floorEntry, comparator, lastEntry]\n", - "Interfaces in TreeMap: [NavigableMap, Cloneable,\n", - "Serializable]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "除 **TreeSet** 之外的所有 **Set** 都具有与 **Collection** 完全相同的接口。**List** 和 **Collection** 存在着明显的不同,尽管 **List** 所要求的方法都在 **Collection** 中。另一方面,在 **Queue** 接口中的方法是独立的,在创建具有 **Queue** 功能的实现时,不需要使用 **Collection** 方法。最后, **Map** 和 **Collection** 之间唯一的交集是 **Map** 可以使用 `entrySet()` 和 `values()` 方法来产生 **Collection** 。\n", - "\n", - "请注意,标记接口 **java.util.RandomAccess** 附加到了 **ArrayList** 上,但不附加到 **LinkedList** 上。这为根据特定 **List** 动态改变其行为的算法提供了信息。\n", - "\n", - "从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了 **java.util** 中更多的有关集合的内容后(特别是在[附录:集合主题]()中的内容),就会发现出了继承结构有点奇怪外,还有更多的问题。集合类库一直以来都是设计难题——解决这些问题涉及到要去满足经常彼此之间互为牵制的各方面需求。所以要做好准备,在各处做出妥协。\n", - "\n", - "尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面,但我想你很快就会找到自己的路子,来获得和使用这个类库中的类。\n", - "\n", - "[^1]: 许多语言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。\n", - "\n", - "[^2]: 这里是操作符重载的用武之地,C++和C#的集合类都使用操作符重载生成了更简洁的语法。\n", - "\n", - "[^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。\n", - "\n", - "[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。\n", - "\n", - "[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。\n", - "\n", - "[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。\n", - "\n", - "[^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。\n", - "\n", - "[^8]: 下面是译者绘制的 Java 集合框架简图,黄色为接口,绿色为抽象类,蓝色为具体类。虚线箭头表示实现关系,实线箭头表示继承关系。\n", - "![collection](../images/collection.png)\n", - "![map](../images/map.png)\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/13-Functional-Programming.ipynb b/jupyter/13-Functional-Programming.ipynb deleted file mode 100644 index d0ec74d0..00000000 --- a/jupyter/13-Functional-Programming.ipynb +++ /dev/null @@ -1,2395 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十三章 函数式编程\n", - "\n", - "\n", - "\n", - "> 函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。\n", - "\n", - "在计算机时代早期,内存是稀缺和昂贵的。几乎每个人都用汇编语言编程。人们对编译器有所了解,但仅仅想到编译生成的代码肯定会比手工编码多很多字节。\n", - "\n", - "通常,只是为了使程序适合有限的内存,程序员通过修改内存中的代码来节省代码空间,以便在程序执行时执行不同的操作。这种技术被称为**自修改代码** (self-modifying code)。只要程序足够小,少数人可以维护所有棘手和神秘的汇编代码,你就可以让它运行起来。\n", - "\n", - "随着内存和处理器变得更便宜、更快。C 语言出现并被大多数汇编程序员认为更“高级”。人们发现使用 C 可以显著提高生产力。同时,使用 C 创建自修改代码仍然不难。\n", - "\n", - "随着硬件越来越便宜,程序的规模和复杂性都在增长。这一切只是让程序工作变得困难。我们想方设法使代码更加一致和易懂。使用纯粹的自修改代码造成的结果就是:我们很难确定程序在做什么。它也难以测试:除非你想一点点测试输出,代码转换和修改等等过程?\n", - "\n", - "然而,使用代码以某种方式操纵其他代码的想法也很有趣,只要能保证它更安全。从代码创建,维护和可靠性的角度来看,这个想法非常吸引人。我们不用从头开始编写大量代码,而是从易于理解、充分测试及可靠的现有小块开始,最后将它们组合在一起以创建新代码。难道这不会让我们更有效率,同时创造更健壮的代码吗?\n", - "\n", - "这就是**函数式编程**(FP)的意义所在。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,一些非函数式语言已经习惯了使用函数式编程产生的优雅的语法。\n", - "\n", - "你也可以这样想:\n", - "\n", - "OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。\n", - "\n", - "纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。将值传递给函数,该函数然后生成新值但从不修改自身外部的任何东西(包括其参数或该函数范围之外的元素)。当强制执行此操作时,你知道任何错误都不是由所谓的副作用引起的,因为该函数仅创建并返回结果,而不是其他任何错误。\n", - "\n", - "更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。\n", - "\n", - "需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:为“并行编程”,“代码可靠性”和“代码创建和库复用”。[^1] 关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例[^2],但还不足以证明纯函数式语言就是解决编程问题的最佳方法。\n", - "\n", - "FP 思想值得融入非 FP 语言,如 Python。Java 8 也从中吸收并支持了 FP。我们将在此章探讨。\n", - "\n", - "\n", - "\n", - "## 新旧对比\n", - "\n", - "\n", - "通常,传递给方法的数据不同,结果不同。如果我们希望方法在调用时行为不同,该怎么做呢?结论是:只要能将代码传递给方法,我们就可以控制它的行为。此前,我们通过在方法中创建包含所需行为的对象,然后将该对象传递给我们想要控制的方法来完成此操作。下面我们用传统形式和 Java 8 的方法引用、Lambda 表达式分别演示。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Strategize.java\n", - "\n", - "interface Strategy {\n", - " String approach(String msg);\n", - "}\n", - "\n", - "class Soft implements Strategy {\n", - " public String approach(String msg) {\n", - " return msg.toLowerCase() + \"?\";\n", - " }\n", - "}\n", - "\n", - "class Unrelated {\n", - " static String twice(String msg) {\n", - " return msg + \" \" + msg;\n", - " }\n", - "}\n", - "\n", - "public class Strategize {\n", - " Strategy strategy;\n", - " String msg;\n", - " Strategize(String msg) {\n", - " strategy = new Soft(); // [1]\n", - " this.msg = msg;\n", - " }\n", - "\n", - " void communicate() {\n", - " System.out.println(strategy.approach(msg));\n", - " }\n", - "\n", - " void changeStrategy(Strategy strategy) {\n", - " this.strategy = strategy;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Strategy[] strategies = {\n", - " new Strategy() { // [2]\n", - " public String approach(String msg) {\n", - " return msg.toUpperCase() + \"!\";\n", - " }\n", - " },\n", - " msg -> msg.substring(0, 5), // [3]\n", - " Unrelated::twice // [4]\n", - " };\n", - " Strategize s = new Strategize(\"Hello there\");\n", - " s.communicate();\n", - " for(Strategy newStrategy : strategies) {\n", - " s.changeStrategy(newStrategy); // [5]\n", - " s.communicate(); // [6]\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "hello there?\n", - "HELLO THERE!\n", - "Hello\n", - "Hello there Hello there" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Strategy** 接口提供了单一的 `approach()` 方法来承载函数式功能。通过创建不同的 **Strategy** 对象,我们可以创建不同的行为。\n", - "\n", - "传统上,我们通过创建一个实现 **Strategy** 接口的类来实现此行为,比如在 **Soft**。\n", - "\n", - "- **[1]** 在 **Strategize** 中,**Soft** 作为默认策略,在构造函数中赋值。\n", - "\n", - "- **[2]** 一种略显简短且更自发的方法是创建一个**匿名内部类**。即使这样,仍有相当数量的冗余代码。你总是要仔细观察:“哦,原来这样,这里使用了匿名内部类。”\n", - "\n", - "- **[3]** Java 8 的 Lambda 表达式。由箭头 `->` 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。\n", - "\n", - "- **[4]** Java 8 的**方法引用**,由 `::` 区分。在 `::` 的左边是类或对象的名称,在 `::` 的右边是方法的名称,但没有参数列表。\n", - "\n", - "- **[5]** 在使用默认的 **Soft** **strategy** 之后,我们逐步遍历数组中的所有 **Strategy**,并使用 `changeStrategy()` 方法将每个 **Strategy** 放入 变量 `s` 中。\n", - "\n", - "- **[6]** 现在,每次调用 `communicate()` 都会产生不同的行为,具体取决于此刻正在使用的策略**代码对象**。我们传递的是行为,而非仅数据。[^3]\n", - "\n", - "在 Java 8 之前,我们能够通过 **[1]** 和 **[2]** 的方式传递功能。然而,这种语法的读写非常笨拙,并且我们别无选择。方法引用和 Lambda 表达式的出现让我们可以在需要时**传递功能**,而不是仅在必要才这么做。\n", - "\n", - "\n", - "\n", - "## Lambda表达式\n", - "\n", - "\n", - "Lambda 表达式是使用**最小可能**语法编写的函数定义:\n", - "\n", - "1. Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。\n", - "\n", - "2. Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。\n", - "\n", - "我们在 **Strategize.java** 中看到了一个 Lambda 表达式,但还有其他语法变体:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/LambdaExpressions.java\n", - "\n", - "interface Description {\n", - " String brief();\n", - "}\n", - "\n", - "interface Body {\n", - " String detailed(String head);\n", - "}\n", - "\n", - "interface Multi {\n", - " String twoArg(String head, Double d);\n", - "}\n", - "\n", - "public class LambdaExpressions {\n", - "\n", - " static Body bod = h -> h + \" No Parens!\"; // [1]\n", - "\n", - " static Body bod2 = (h) -> h + \" More details\"; // [2]\n", - "\n", - " static Description desc = () -> \"Short info\"; // [3]\n", - "\n", - " static Multi mult = (h, n) -> h + n; // [4]\n", - "\n", - " static Description moreLines = () -> { // [5]\n", - " System.out.println(\"moreLines()\");\n", - " return \"from moreLines()\";\n", - " };\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(bod.detailed(\"Oh!\"));\n", - " System.out.println(bod2.detailed(\"Hi!\"));\n", - " System.out.println(desc.brief());\n", - " System.out.println(mult.twoArg(\"Pi! \", 3.14159));\n", - " System.out.println(moreLines.brief());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Oh! No Parens!\n", - "Hi! More details\n", - "Short info\n", - "Pi! 3.14159\n", - "moreLines()\n", - "from moreLines()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们从三个接口开始,每个接口都有一个单独的方法(很快就会理解它的重要性)。但是,每个方法都有不同数量的参数,以便演示 Lambda 表达式语法。\n", - "\n", - "任何 Lambda 表达式的基本语法是:\n", - "\n", - "1. 参数。\n", - "\n", - "2. 接着 `->`,可视为“产出”。\n", - "\n", - "3. `->` 之后的内容都是方法体。\n", - "\n", - " - **[1]** 当只用一个参数,可以不需要括号 `()`。 然而,这是一个特例。\n", - "\n", - " - **[2]** 正常情况使用括号 `()` 包裹参数。 为了保持一致性,也可以使用括号 `()` 包裹单个参数,虽然这种情况并不常见。\n", - "\n", - " - **[3]** 如果没有参数,则必须使用括号 `()` 表示空参数列表。\n", - "\n", - " - **[4]** 对于多个参数,将参数列表放在括号 `()` 中。\n", - "\n", - "到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 **return** 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。\n", - "\n", - "**[5]** 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 **return**。\n", - "\n", - "Lambda 表达式通常比**匿名内部类**产生更易读的代码,因此我们将在本书中尽可能使用它们。\n", - "\n", - "### 递归\n", - "\n", - "递归函数是一个自我调用的函数。可以编写递归的 Lambda 表达式,但需要注意:递归方法必须是实例变量或静态变量,否则会出现编译时错误。 我们将为每个案例创建一个示例。\n", - "\n", - "这两个示例都需要一个接受 **int** 型参数并生成 **int** 的接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/IntCall.java\n", - "\n", - "interface IntCall {\n", - " int call(int arg);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "整数 n 的阶乘将所有小于或等于 n 的正整数相乘。 阶乘函数是一个常见的递归示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/RecursiveFactorial.java\n", - "\n", - "public class RecursiveFactorial {\n", - " static IntCall fact;\n", - " public static void main(String[] args) {\n", - " fact = n -> n == 0 ? 1 : n * fact.call(n - 1);\n", - " for(int i = 0; i <= 10; i++)\n", - " System.out.println(fact.call(i));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1\n", - "1\n", - "2\n", - "6\n", - "24\n", - "120\n", - "720\n", - "5040\n", - "40320\n", - "362880\n", - "3628800" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则将无限递归并产生异常。\n", - "\n", - "我们可以将 `Fibonacci` 序列改为使用递归 Lambda 表达式来实现,这次使用实例变量:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/RecursiveFibonacci.java\n", - "\n", - "public class RecursiveFibonacci {\n", - " IntCall fib;\n", - "\n", - " RecursiveFibonacci() {\n", - " fib = n -> n == 0 ? 0 :\n", - " n == 1 ? 1 :\n", - " fib.call(n - 1) + fib.call(n - 2);\n", - " }\n", - " \n", - " int fibonacci(int n) { return fib.call(n); }\n", - "\n", - " public static void main(String[] args) {\n", - " RecursiveFibonacci rf = new RecursiveFibonacci();\n", - " for(int i = 0; i <= 10; i++)\n", - " System.out.println(rf.fibonacci(i));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0\n", - "1\n", - "1\n", - "2\n", - "3\n", - "5\n", - "8\n", - "13\n", - "21\n", - "34\n", - "55" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "将 `Fibonacci` 序列中的最后两个元素求和来产生下一个元素。\n", - "\n", - "\n", - "\n", - "## 方法引用\n", - "\n", - "\n", - "Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 `::` [^4],然后跟方法名称。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/MethodReferences.java\n", - "\n", - "import java.util.*;\n", - "\n", - "interface Callable { // [1]\n", - " void call(String s);\n", - "}\n", - "\n", - "class Describe {\n", - " void show(String msg) { // [2]\n", - " System.out.println(msg);\n", - " }\n", - "}\n", - "\n", - "public class MethodReferences {\n", - " static void hello(String name) { // [3]\n", - " System.out.println(\"Hello, \" + name);\n", - " }\n", - " static class Description {\n", - " String about;\n", - " Description(String desc) { about = desc; }\n", - " void help(String msg) { // [4]\n", - " System.out.println(about + \" \" + msg);\n", - " }\n", - " }\n", - " static class Helper {\n", - " static void assist(String msg) { // [5]\n", - " System.out.println(msg);\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " Describe d = new Describe();\n", - " Callable c = d::show; // [6]\n", - " c.call(\"call()\"); // [7]\n", - "\n", - " c = MethodReferences::hello; // [8]\n", - " c.call(\"Bob\");\n", - "\n", - " c = new Description(\"valuable\")::help; // [9]\n", - " c.call(\"information\");\n", - "\n", - " c = Helper::assist; // [10]\n", - " c.call(\"Help!\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "call()\n", - "Hello, Bob\n", - "valuable information\n", - "Help!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**[1]** 我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。\n", - "\n", - "**[2]** `show()` 的签名(参数类型和返回类型)符合 **Callable** 的 `call()` 的签名。\n", - "\n", - "**[3]** `hello()` 也符合 `call()` 的签名。 \n", - "\n", - "**[4]** `help()` 也符合,它是静态内部类中的非静态方法。\n", - "\n", - "**[5]** `assist()` 是静态内部类中的静态方法。\n", - "\n", - "**[6]** 我们将 **Describe** 对象的方法引用赋值给 **Callable** ,它没有 `show()` 方法,而是 `call()` 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 **Callable** 的 `call()` 方法的签名。\n", - "\n", - "**[7]** 我们现在可以通过调用 `call()` 来调用 `show()`,因为 Java 将 `call()` 映射到 `show()`。\n", - "\n", - "**[8]** 这是一个**静态**方法引用。\n", - "\n", - "**[9]** 这是 **[6]** 的另一个版本:对已实例化对象的方法的引用,有时称为*绑定方法引用*。\n", - "\n", - "**[10]** 最后,获取静态内部类的方法引用的操作与 **[8]** 中外部类方式一样。\n", - "\n", - "上例只是简短的介绍,我们很快就能看到方法引用的全部变化。\n", - "\n", - "### Runnable接口\n", - "\n", - "**Runnable** 接口自 1.0 版以来一直在 Java 中,因此不需要导入。它也符合特殊的单方法接口格式:它的方法 `run()` 不带参数,也没有返回值。因此,我们可以使用 Lambda 表达式和方法引用作为 **Runnable**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/RunnableMethodReference.java\n", - "\n", - "// 方法引用与 Runnable 接口的结合使用\n", - "\n", - "class Go {\n", - " static void go() {\n", - " System.out.println(\"Go::go()\");\n", - " }\n", - "}\n", - "\n", - "public class RunnableMethodReference {\n", - " public static void main(String[] args) {\n", - "\n", - " new Thread(new Runnable() {\n", - " public void run() {\n", - " System.out.println(\"Anonymous\");\n", - " }\n", - " }).start();\n", - "\n", - " new Thread(\n", - " () -> System.out.println(\"lambda\")\n", - " ).start();\n", - "\n", - " new Thread(Go::go).start();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Anonymous\n", - "lambda\n", - "Go::go()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Thread** 对象将 **Runnable** 作为其构造函数参数,并具有会调用 `run()` 的方法 `start()`。 **注意**,只有**匿名内部类**才需要具有名为 `run()` 的方法。\n", - "\n", - "\n", - "\n", - "### 未绑定的方法引用\n", - "\n", - "\n", - "未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/UnboundMethodReference.java\n", - "\n", - "// 没有方法引用的对象\n", - "\n", - "class X {\n", - " String f() { return \"X::f()\"; }\n", - "}\n", - "\n", - "interface MakeString {\n", - " String make();\n", - "}\n", - "\n", - "interface TransformX {\n", - " String transform(X x);\n", - "}\n", - "\n", - "public class UnboundMethodReference {\n", - " public static void main(String[] args) {\n", - " // MakeString ms = X::f; // [1]\n", - " TransformX sp = X::f;\n", - " X x = new X();\n", - " System.out.println(sp.transform(x)); // [2]\n", - " System.out.println(x.f()); // 同等效果\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "X::f()\n", - "X::f()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "截止目前,我们已经知道了与接口方法同名的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果:即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。\n", - "\n", - "要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数的接口,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,这在 Java 中是允许的。这次我们需要调整下心里预期——使用未绑定的引用时,函数方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 理由是:你需要一个对象来调用方法。\n", - "\n", - "**[2]** 的结果有点像脑筋急转弯。 我接受未绑定的引用并对其调用 `transform()`,将其传递给 `X`,并以某种方式导致对 `x.f()` 的调用。 Java 知道它必须采用第一个参数,这实际上就是 `this`,并在其上调用方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/MultiUnbound.java\n", - "\n", - "// 未绑定的方法与多参数的结合运用\n", - "\n", - "class This {\n", - " void two(int i, double d) {}\n", - " void three(int i, double d, String s) {}\n", - " void four(int i, double d, String s, char c) {}\n", - "}\n", - "\n", - "interface TwoArgs {\n", - " void call2(This athis, int i, double d);\n", - "}\n", - "\n", - "interface ThreeArgs {\n", - " void call3(This athis, int i, double d, String s);\n", - "}\n", - "\n", - "interface FourArgs {\n", - " void call4(\n", - " This athis, int i, double d, String s, char c);\n", - "}\n", - "\n", - "public class MultiUnbound {\n", - " public static void main(String[] args) {\n", - " TwoArgs twoargs = This::two;\n", - " ThreeArgs threeargs = This::three;\n", - " FourArgs fourargs = This::four;\n", - " This athis = new This();\n", - " twoargs.call2(athis, 11, 3.14);\n", - " threeargs.call3(athis, 11, 3.14, \"Three\");\n", - " fourargs.call4(athis, 11, 3.14, \"Four\", 'Z');\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了说明这一点,我将类命名为 **This** ,函数方法的第一个参数则是 **athis**,但是你应该选择其他名称以防止生产代码混淆。\n", - "\n", - "### 构造函数引用\n", - "\n", - "你还可以捕获构造函数的引用,然后通过引用调用该构造函数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/CtorReference.java\n", - "\n", - "class Dog {\n", - " String name;\n", - " int age = -1; // For \"unknown\"\n", - " Dog() { name = \"stray\"; }\n", - " Dog(String nm) { name = nm; }\n", - " Dog(String nm, int yrs) { name = nm; age = yrs; }\n", - "}\n", - "\n", - "interface MakeNoArgs {\n", - " Dog make();\n", - "}\n", - "\n", - "interface Make1Arg {\n", - " Dog make(String nm);\n", - "}\n", - "\n", - "interface Make2Args {\n", - " Dog make(String nm, int age);\n", - "}\n", - "\n", - "public class CtorReference {\n", - " public static void main(String[] args) {\n", - " MakeNoArgs mna = Dog::new; // [1]\n", - " Make1Arg m1a = Dog::new; // [2]\n", - " Make2Args m2a = Dog::new; // [3]\n", - "\n", - " Dog dn = mna.make();\n", - " Dog d1 = m1a.make(\"Comet\");\n", - " Dog d2 = m2a.make(\"Ralph\", 4);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Dog** 有三个构造函数,函数接口内的 `make()` 方法反映了构造函数参数列表( `make()` 方法名称可以不同)。\n", - "\n", - "**注意**我们如何对 **[1]**,**[2]** 和 **[3]** 中的每一个使用 `Dog :: new`。 这 3 个构造函数只有一个相同名称:`:: new`,但在每种情况下都赋值给不同的接口。编译器可以检测并知道从哪个构造函数引用。\n", - "\n", - "编译器能识别并调用你的构造函数( 在本例中为 `make()`)。\n", - "\n", - "\n", - "## 函数式接口\n", - "\n", - "\n", - "方法引用和 Lambda 表达式必须被赋值,同时编译器需要识别类型信息以确保类型正确。 Lambda 表达式特别引入了新的要求。 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "x -> x.toString()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们清楚这里返回类型必须是 **String**,但 `x` 是什么类型呢?\n", - "\n", - "Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 `x` 的类型。\n", - "\n", - "下面是第 2 个代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "(x, y) -> x + y" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在 `x` 和 `y` 可以是任何支持 `+` 运算符连接的数据类型,可以是两个不同的数值类型或者是 1 个 **String** 加任意一种可自动转换为 **String** 的数据类型(这包括了大多数类型)。 但是,当 Lambda 表达式被赋值时,编译器必须确定 `x` 和 `y` 的确切类型以生成正确的代码。\n", - "\n", - "该问题也适用于方法引用。 假设你要传递 `System.out :: println` 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型?\n", - "\n", - "为了解决这个问题,Java 8 引入了 `java.util.function` 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为函数式方法。\n", - "\n", - "在编写接口时,可以使用 `@FunctionalInterface` 注解强制执行此“函数式方法”模式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/FunctionalAnnotation.java\n", - "\n", - "@FunctionalInterface\n", - "interface Functional {\n", - " String goodbye(String arg);\n", - "}\n", - "\n", - "interface FunctionalNoAnn {\n", - " String goodbye(String arg);\n", - "}\n", - "\n", - "/*\n", - "@FunctionalInterface\n", - "interface NotFunctional {\n", - " String goodbye(String arg);\n", - " String hello(String arg);\n", - "}\n", - "产生错误信息:\n", - "NotFunctional is not a functional interface\n", - "multiple non-overriding abstract methods\n", - "found in interface NotFunctional\n", - "*/\n", - "\n", - "public class FunctionalAnnotation {\n", - " public String goodbye(String arg) {\n", - " return \"Goodbye, \" + arg;\n", - " }\n", - " public static void main(String[] args) {\n", - " FunctionalAnnotation fa =\n", - " new FunctionalAnnotation();\n", - " Functional f = fa::goodbye;\n", - " FunctionalNoAnn fna = fa::goodbye;\n", - " // Functional fac = fa; // Incompatible\n", - " Functional fl = a -> \"Goodbye, \" + a;\n", - " FunctionalNoAnn fnal = a -> \"Goodbye, \" + a;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 `@FunctionalInterface` 的值在 `NotFunctional` 的定义中可见:接口中如果有多个方法则会产生编译时错误消息。\n", - "\n", - "仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。Java 8 在这里添加了一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。\n", - "\n", - "尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java 不允许我们将 `FunctionalAnnotation` 像 `fac` 定义一样直接赋值给 `Functional`,因为它没有明确地实现 `Functional` 接口。 令人惊奇的是 ,Java 8 允许我们以简便的语法为接口赋值函数。\n", - "\n", - "`java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。\n", - "\n", - " 以下是基本命名准则:\n", - "\n", - "1. 如果只处理对象而非基本类型,名称则为 `Function`,`Consumer`,`Predicate` 等。参数类型通过泛型添加。\n", - "\n", - "2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但基本 `Supplier` 类型例外。\n", - "\n", - "3. 如果返回值为基本类型,则用 `To` 表示,如 `ToLongFunction ` 和 `IntToLongFunction`。\n", - "\n", - "4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。\n", - "\n", - "5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。\n", - "\n", - "6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。\n", - "\n", - "下表描述了 `java.util.function` 中的目标类型(包括例外情况):\n", - "\n", - "| **特征** |**函数式方法名**|**示例**|\n", - "| :---- | :----: | :----: |\n", - "|无参数;
无返回值|**Runnable**
(java.lang)
`run()`|**Runnable**|\n", - "|无参数;
返回类型任意|**Supplier**
`get()`
`getAs类型()`| **Supplier``
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier**|\n", - "|无参数;
返回类型任意|**Callable**
(java.util.concurrent)
`call()`|**Callable``**|\n", - "|1 参数;
无返回值|**Consumer**
`accept()`|**`Consumer`
IntConsumer
LongConsumer
DoubleConsumer**|\n", - "|2 参数 **Consumer**|**BiConsumer**
`accept()`|**`BiConsumer`**|\n", - "|2 参数 **Consumer**;
1 引用;
1 基本类型|**Obj类型Consumer**
`accept()`|**`ObjIntConsumer`
`ObjLongConsumer`
`ObjDoubleConsumer`**|\n", - "|1 参数;
返回类型不同|**Function**
`apply()`
**To类型** 和 **类型To类型**
`applyAs类型()`|**Function``
IntFunction``
`LongFunction`
DoubleFunction``
ToIntFunction``
`ToLongFunction`
`ToDoubleFunction`
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**|\n", - "|1 参数;
返回类型相同|**UnaryOperator**
`apply()`|**`UnaryOperator`
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator**|\n", - "|2 参数类型相同;
返回类型相同|**BinaryOperator**
`apply()`|**`BinaryOperator`
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator**|\n", - "|2 参数类型相同;
返回整型|Comparator
(java.util)
`compare()`|**`Comparator`**|\n", - "|2 参数;
返回布尔型|**Predicate**
`test()`|**`Predicate`
`BiPredicate`
IntPredicate
LongPredicate
DoublePredicate**|\n", - "|参数基本类型;
返回基本类型|**类型To类型Function**
`applyAs类型()`|**IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**|\n", - "|2 参数类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**|\n", - "\n", - "\n", - "此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出更多行的函数式接口。\n", - "\n", - "可以看出,在创建 `java.util.function` 时,设计者们做出了一些选择。 \n", - "\n", - "例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我对他们放弃的原因表示同情)。这些选择是疏忽还是有人认为其他组合的使用情况出现得很少(他们是如何得出这个结论的)?\n", - "\n", - "你还可以看到基本类型给 Java 添加了多少复杂性。为了缓和效率问题,该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然受到语言设计选择不佳的影响。\n", - "\n", - "下面枚举了基于 Lambda 表达式的所有不同 **Function** 变体的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/FunctionVariants.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "class Foo {}\n", - "\n", - "class Bar {\n", - " Foo f;\n", - " Bar(Foo f) { this.f = f; }\n", - "}\n", - "\n", - "class IBaz {\n", - " int i;\n", - " IBaz(int i) {\n", - " this.i = i;\n", - " }\n", - "}\n", - "\n", - "class LBaz {\n", - " long l;\n", - " LBaz(long l) {\n", - " this.l = l;\n", - " }\n", - "}\n", - "\n", - "class DBaz {\n", - " double d;\n", - " DBaz(double d) {\n", - " this.d = d;\n", - " }\n", - "}\n", - "\n", - "public class FunctionVariants {\n", - " static Function f1 = f -> new Bar(f);\n", - " static IntFunction f2 = i -> new IBaz(i);\n", - " static LongFunction f3 = l -> new LBaz(l);\n", - " static DoubleFunction f4 = d -> new DBaz(d);\n", - " static ToIntFunction f5 = ib -> ib.i;\n", - " static ToLongFunction f6 = lb -> lb.l;\n", - " static ToDoubleFunction f7 = db -> db.d;\n", - " static IntToLongFunction f8 = i -> i;\n", - " static IntToDoubleFunction f9 = i -> i;\n", - " static LongToIntFunction f10 = l -> (int)l;\n", - " static LongToDoubleFunction f11 = l -> l;\n", - " static DoubleToIntFunction f12 = d -> (int)d;\n", - " static DoubleToLongFunction f13 = d -> (long)d;\n", - "\n", - " public static void main(String[] args) {\n", - " Bar b = f1.apply(new Foo());\n", - " IBaz ib = f2.apply(11);\n", - " LBaz lb = f3.apply(11);\n", - " DBaz db = f4.apply(11);\n", - " int i = f5.applyAsInt(ib);\n", - " long l = f6.applyAsLong(lb);\n", - " double d = f7.applyAsDouble(db);\n", - " l = f8.applyAsLong(12);\n", - " d = f9.applyAsDouble(12);\n", - " i = f10.applyAsInt(12);\n", - " d = f11.applyAsDouble(12);\n", - " i = f12.applyAsInt(13.0);\n", - " l = f13.applyAsLong(13.0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。\n", - "\n", - "主方法中的每个测试都显示了 `Function` 接口中不同类型的 `apply()` 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。\n", - "\n", - "方法引用有自己的小魔法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "/ functional/MethodConversion.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "class In1 {}\n", - "class In2 {}\n", - "\n", - "public class MethodConversion {\n", - " static void accept(In1 i1, In2 i2) {\n", - " System.out.println(\"accept()\");\n", - " }\n", - " static void someOtherName(In1 i1, In2 i2) {\n", - " System.out.println(\"someOtherName()\");\n", - " }\n", - " public static void main(String[] args) {\n", - " BiConsumer bic;\n", - "\n", - " bic = MethodConversion::accept;\n", - " bic.accept(new In1(), new In2());\n", - "\n", - " bic = MethodConversion::someOtherName;\n", - " // bic.someOtherName(new In1(), new In2()); // Nope\n", - " bic.accept(new In1(), new In2());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "accept()\n", - "someOtherName()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "查看 `BiConsumer` 的文档,你会看到 `accept()` 方法。 实际上,如果我们将方法命名为 `accept()`,它就可以作为方法引用。 但是我们也可用不同的名称,比如 `someOtherName()`。只要参数类型、返回类型与 `BiConsumer` 的 `accept()` 相同即可。\n", - "\n", - "因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 `accept()`),而不是你的方法名。\n", - "\n", - "现在我们来看看所有基于类的函数式,应用于方法引用(即那些不涉及基本类型的函数)。下例我们创建了一个最简单的函数式签名。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/ClassFunctionals.java\n", - "\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "class AA {}\n", - "class BB {}\n", - "class CC {}\n", - "\n", - "public class ClassFunctionals {\n", - " static AA f1() { return new AA(); }\n", - " static int f2(AA aa1, AA aa2) { return 1; }\n", - " static void f3(AA aa) {}\n", - " static void f4(AA aa, BB bb) {}\n", - " static CC f5(AA aa) { return new CC(); }\n", - " static CC f6(AA aa, BB bb) { return new CC(); }\n", - " static boolean f7(AA aa) { return true; }\n", - " static boolean f8(AA aa, BB bb) { return true; }\n", - " static AA f9(AA aa) { return new AA(); }\n", - " static AA f10(AA aa1, AA aa2) { return new AA(); }\n", - " public static void main(String[] args) {\n", - " Supplier s = ClassFunctionals::f1;\n", - " s.get();\n", - " Comparator c = ClassFunctionals::f2;\n", - " c.compare(new AA(), new AA());\n", - " Consumer cons = ClassFunctionals::f3;\n", - " cons.accept(new AA());\n", - " BiConsumer bicons = ClassFunctionals::f4;\n", - " bicons.accept(new AA(), new BB());\n", - " Function f = ClassFunctionals::f5;\n", - " CC cc = f.apply(new AA());\n", - " BiFunction bif = ClassFunctionals::f6;\n", - " cc = bif.apply(new AA(), new BB());\n", - " Predicate p = ClassFunctionals::f7;\n", - " boolean result = p.test(new AA());\n", - " BiPredicate bip = ClassFunctionals::f8;\n", - " result = bip.test(new AA(), new BB());\n", - " UnaryOperator uo = ClassFunctionals::f9;\n", - " AA aa = uo.apply(new AA());\n", - " BinaryOperator bo = ClassFunctionals::f10;\n", - " aa = bo.apply(new AA(), new AA());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请**注意**,每个方法名称都是随意的(如 `f1()`,`f2()`等)。正如你刚才看到的,一旦将方法引用赋值给函数接口,我们就可以调用与该接口关联的函数方法。 在此示例中为 `get()`、`compare()`、`accept()`、`apply()` 和 `test()`。\n", - "\n", - "\n", - "\n", - "### 多参数函数式接口\n", - "\n", - "`java.util.functional` 中的接口是有限的。比如有了 `BiFunction`,但它不能变化。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/TriFunction.java\n", - "\n", - "@FunctionalInterface\n", - "public interface TriFunction {\n", - " R apply(T t, U u, V v);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "简单测试,验证它是否有效:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/TriFunctionTest.java\n", - "\n", - "public class TriFunctionTest {\n", - " static int f(int i, long l, double d) { return 99; }\n", - " public static void main(String[] args) {\n", - " TriFunction tf =\n", - " TriFunctionTest::f;\n", - " tf = (i, l, d) -> 12;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里我们测试了方法引用和 Lambda 表达式。\n", - "\n", - "### 缺少基本类型的函数\n", - "\n", - "让我们重温一下 `BiConsumer`,看看我们如何创建缺少 **int**,**long** 和 **double** 的各种排列:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/BiConsumerPermutations.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class BiConsumerPermutations {\n", - " static BiConsumer bicid = (i, d) ->\n", - " System.out.format(\"%d, %f%n\", i, d);\n", - " static BiConsumer bicdi = (d, i) ->\n", - " System.out.format(\"%d, %f%n\", i, d);\n", - " static BiConsumer bicil = (i, l) ->\n", - " System.out.format(\"%d, %d%n\", i, l);\n", - " public static void main(String[] args) {\n", - " bicid.accept(47, 11.34);\n", - " bicdi.accept(22.45, 92);\n", - " bicil.accept(1, 11L);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "47, 11.340000\n", - "92, 22.450000\n", - "1, 11" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里使用 `System.out.format()` 来显示。它类似于 `System.out.println()` 但提供了更多的显示选项。 这里,`%f` 表示我将 `n` 作为浮点值给出,`%d` 表示 `n` 是一个整数值。 这其中可以包含空格,输入 `%n` 会换行 — 当然使用传统的 `\\n` 也能换行,但 `%n` 是自动跨平台的,这是使用 `format()` 的另一个原因。\n", - "\n", - "上例简单使用了包装类型,装箱和拆箱用于在基本类型之间来回转换。 我们也可以使用包装类型,如 `Function`,而不是预定义的基本类型。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/FunctionWithWrapped.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class FunctionWithWrapped {\n", - " public static void main(String[] args) {\n", - " Function fid = i -> (double)i;\n", - " IntToDoubleFunction fid2 = i -> i;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有强制转换,则会收到错误消息:“Integer cannot be converted to Double”(**Integer** 无法转换为 **Double**),而使用 **IntToDoubleFunction** 就没有此类问题。 **IntToDoubleFunction** 接口的源代码是这样的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@FunctionalInterface \n", - "public interface IntToDoubleFunction { \n", - " double applyAsDouble(int value); \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "之所以我们可以简单地编写 `Function ` 并返回合适的结果,很明显是为了性能。使用基本类型可以防止传递参数和返回结果过程中的自动装箱和自动拆箱。\n", - "\n", - "似乎是考虑到使用频率,某些函数类型并没有预定义。\n", - "\n", - "当然,如果因缺少基本类型而造成的性能问题,你也可以轻松编写自己的接口( 参考 Java 源代码)——尽管这里出现性能瓶颈的可能性不大。\n", - "\n", - "\n", - "## 高阶函数\n", - "\n", - "\n", - "这个名字可能听起来令人生畏,但是:[高阶函数](https://en.wikipedia.org/wiki/Higher-order_function)(Higher-order Function)只是一个消费或产生函数的函数。\n", - "\n", - "我们先来看看如何产生一个函数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/ProduceFunction.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "interface\n", - "FuncSS extends Function {} // [1]\n", - "\n", - "public class ProduceFunction {\n", - " static FuncSS produce() {\n", - " return s -> s.toLowerCase(); // [2]\n", - " }\n", - " public static void main(String[] args) {\n", - " FuncSS f = produce();\n", - " System.out.println(f.apply(\"YELLING\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "yelling" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,`produce()` 是高阶函数。\n", - "\n", - "**[1]** 使用继承,可以轻松地为专用接口创建别名。\n", - "\n", - "**[2]** 使用 Lambda 表达式,可以轻松地在方法中创建和返回一个函数。\n", - "\n", - "要消费一个函数,消费函数需要在参数列表正确地描述函数类型。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/ConsumeFunction.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "class One {}\n", - "class Two {}\n", - "\n", - "public class ConsumeFunction {\n", - " static Two consume(Function onetwo) {\n", - " return onetwo.apply(new One());\n", - " }\n", - " public static void main(String[] args) {\n", - " Two two = consume(one -> new Two());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当基于消费函数生成新函数时,事情就变得相当有趣了。代码示例如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/TransformFunction.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "class I {\n", - " @Override\n", - " public String toString() { return \"I\"; }\n", - "}\n", - "\n", - "class O {\n", - " @Override\n", - " public String toString() { return \"O\"; }\n", - "}\n", - "\n", - "public class TransformFunction {\n", - " static Function transform(Function in) {\n", - " return in.andThen(o -> {\n", - " System.out.println(o);\n", - " return o;\n", - " });\n", - " }\n", - " public static void main(String[] args) {\n", - " Function f2 = transform(i -> {\n", - " System.out.println(i);\n", - " return new O();\n", - " });\n", - " O o = f2.apply(new I());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "I\n", - "O" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里,`transform()` 生成一个与传入的函数具有相同签名的函数,但是你可以生成任何你想要的类型。\n", - "\n", - "这里使用到了 `Function` 接口中名为 `andThen()` 的默认方法,该方法专门用于操作函数。 顾名思义,在调用 `in` 函数之后调用 `toThen()`(还有个 `compose()` 方法,它在 `in` 函数之前应用新函数)。 要附加一个 `andThen()` 函数,我们只需将该函数作为参数传递。 `transform()` 产生的是一个新函数,它将 `in` 的动作与 `andThen()` 参数的动作结合起来。\n", - "\n", - "\n", - "\n", - "## 闭包\n", - "\n", - "\n", - "在上一节的 `ProduceFunction.java` 中,我们从方法中返回 Lambda 函数。 虽然过程简单,但是有些问题必须再回过头来探讨一下。\n", - "\n", - "**闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。\n", - "\n", - "考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。\n", - "\n", - "首先,下例函数中,方法返回访问对象字段和方法参数。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure1.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Closure1 {\n", - " int i;\n", - " IntSupplier makeFun(int x) {\n", - " return () -> x + i++;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,垃圾收集器几乎肯定会保留一个对象,并将现有的函数以这种方式绑定到该对象上[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/SharedStorage.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class SharedStorage {\n", - " public static void main(String[] args) {\n", - " Closure1 c1 = new Closure1();\n", - " IntSupplier f1 = c1.makeFun(0);\n", - " IntSupplier f2 = c1.makeFun(0);\n", - " IntSupplier f3 = c1.makeFun(0);\n", - " System.out.println(f1.getAsInt());\n", - " System.out.println(f2.getAsInt());\n", - " System.out.println(f3.getAsInt());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0\n", - "1\n", - "2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每次调用 `getAsInt()` 都会增加 `i`,表明存储是共享的。\n", - "\n", - "如果 `i` 是 `makeFun()` 的局部变量怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍可以编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure2.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Closure2 {\n", - " IntSupplier makeFun(int x) {\n", - " int i = 0;\n", - " return () -> x + i;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由 `makeFun()` 返回的 `IntSupplier` “关闭” `i` 和 `x`,因此当你调用返回的函数时两者仍然有效。 但请**注意**,我没有像 `Closure1.java` 那样递增 `i`,因为会产生编译时错误。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure3.java\n", - "\n", - "// {WillNotCompile}\n", - "import java.util.function.*;\n", - "\n", - "public class Closure3 {\n", - " IntSupplier makeFun(int x) {\n", - " int i = 0;\n", - " // x++ 和 i++ 都会报错:\n", - " return () -> x++ + i++;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。\n", - "\n", - "如果使用 `final` 修饰 `x`和 `i`,就不能再递增它们的值了。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure4.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Closure4 {\n", - " IntSupplier makeFun(final int x) {\n", - " final int i = 0;\n", - " return () -> x + i;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "那么为什么在 `Closure2.java` 中, `x` 和 `i` 非 `final` 却可以运行呢?\n", - "\n", - "这就叫做**等同 final 效果**(Effectively Final)。这个术语是在 Java 8 才开始出现的,表示虽然没有明确地声明变量是 `final` 的,但是因变量值没被改变过而实际有了 `final` 同等的效果。 如果局部变量的初始值永远不会改变,那么它实际上就是 `final` 的。\n", - "\n", - "如果 `x` 和 `i` 的值在方法中的其他位置发生改变(但不在返回的函数内部),则编译器仍将视其为错误。每个递增操作则会分别产生错误消息。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "/ functional/Closure5.java\n", - "\n", - "// {无法编译成功}\n", - "import java.util.function.*;\n", - "\n", - "public class Closure5 {\n", - " IntSupplier makeFun(int x) {\n", - " int i = 0;\n", - " i++;\n", - " x++;\n", - " return () -> x + i;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**等同 final 效果**意味着可以在变量声明前加上 **final** 关键字而不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。\n", - "\n", - "通过在闭包中使用 `final` 关键字提前修饰变量 `x` 和 `i` , 我们解决了 `Closure5.java` 中的问题。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\n", - "// functional/Closure6.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Closure6 {\n", - " IntSupplier makeFun(int x) {\n", - " int i = 0;\n", - " i++;\n", - " x++;\n", - " final int iFinal = i;\n", - " final int xFinal = x;\n", - " return () -> xFinal + iFinal;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中 `iFinal` 和 `xFinal` 的值在赋值后并没有改变过,因此在这里使用 `final` 是多余的。\n", - "\n", - "如果这里是引用的话,需要把 **int** 型更改为 **Integer** 型。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure7.java\n", - "\n", - "// {无法编译成功}\n", - "import java.util.function.*;\n", - "\n", - "public class Closure7 {\n", - " IntSupplier makeFun(int x) {\n", - " Integer i = 0;\n", - " i = i + 1;\n", - " return () -> x + i;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器非常智能,它能识别变量 `i` 的值被更改过了。 对于包装类型的处理可能比较特殊,因此我们尝试下 **List**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure8.java\n", - "\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "public class Closure8 {\n", - " Supplier> makeFun() {\n", - " final List ai = new ArrayList<>();\n", - " ai.add(1);\n", - " return () -> ai;\n", - " }\n", - " public static void main(String[] args) {\n", - " Closure8 c7 = new Closure8();\n", - " List\n", - " l1 = c7.makeFun().get(),\n", - " l2 = c7.makeFun().get();\n", - " System.out.println(l1);\n", - " System.out.println(l2);\n", - " l1.add(42);\n", - " l2.add(96);\n", - " System.out.println(l1);\n", - " System.out.println(l2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[1]\n", - "[1]\n", - "[1, 42]\n", - "[1, 96]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,这次一切正常。我们改变了 **List** 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList`, 它们之间互不干扰。\n", - "\n", - "请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。\n", - "\n", - "下面我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是**等同 final 效果**错误消息的触发点。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Closure9.java\n", - "\n", - "// {无法编译成功}\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "public class Closure9 {\n", - " Supplier> makeFun() {\n", - " List ai = new ArrayList<>();\n", - " ai = new ArrayList<>(); // Reassignment\n", - " return () -> ai;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例,重新赋值引用会触发错误消息。如果只修改指向的对象则没问题,只要没有其他人获得对该对象的引用(这意味着你有多个实体可以修改对象,此时事情会变得非常混乱),基本上就是安全的[^6]。\n", - "\n", - "让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是“在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。\n", - "\n", - "\n", - "\n", - "### 作为闭包的内部类\n", - "\n", - "我们可以使用匿名内部类重写之前的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/AnonymousClosure.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class AnonymousClosure {\n", - " IntSupplier makeFun(int x) {\n", - " int i = 0;\n", - " // 同样规则的应用:\n", - " // i++; // 非等同 final 效果\n", - " // x++; // 同上\n", - " return new IntSupplier() {\n", - " public int getAsInt() { return x + i; }\n", - " };\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "实际上只要有内部类,就会有闭包(Java 8 只是简化了闭包操作)。在 Java 8 之前,变量 `x` 和 `i` 必须被明确声明为 `final`。在 Java 8 中,内部类的规则放宽,包括**等同 final 效果**。\n", - "\n", - "\n", - "## 函数组合\n", - "\n", - "\n", - "函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,有一个使用 `andThen()` 的函数组合示例。一些 `java.util.function` 接口中包含支持函数组合的方法 [^7]。\n", - "\n", - "| 组合方法 | 支持接口 |\n", - "| :----- | :----- |\n", - "| `andThen(argument)`
根据参数执行原始操作 | **Function
BiFunction
Consumer
BiConsumer
IntConsumer
LongConsumer
DoubleConsumer
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator** |\n", - "| `compose(argument)`
根据参数执行原始操作 | **Function
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator** |\n", - "| `and(argument)`
短路**逻辑与**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", - "| `or(argument)`
短路**逻辑或**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", - "| `negate()`
该谓词的**逻辑否**谓词| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** |\n", - "\n", - "\n", - "下例使用了 `Function` 里的 `compose()`和 `andThen()`。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/FunctionComposition.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class FunctionComposition {\n", - " static Function\n", - " f1 = s -> {\n", - " System.out.println(s);\n", - " return s.replace('A', '_');\n", - " },\n", - " f2 = s -> s.substring(3),\n", - " f3 = s -> s.toLowerCase(),\n", - " f4 = f1.compose(f2).andThen(f3);\n", - " public static void main(String[] args) {\n", - " System.out.println(\n", - " f4.apply(\"GO AFTER ALL AMBULANCES\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "AFTER ALL AMBULANCES\n", - "_fter _ll _mbul_nces" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里我们重点看正在创建的新函数 `f4`。它调用 `apply()` 的方式与常规几乎无异[^8]。\n", - "\n", - "当 `f1` 获得字符串时,它已经被`f2` 剥离了前三个字符。这是因为 `compose(f2)` 表示 `f2` 的调用发生在 `f1` 之前。\n", - "\n", - "下例是 `Predicate` 的逻辑运算演示.代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/PredicateComposition.java\n", - "\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class PredicateComposition {\n", - " static Predicate\n", - " p1 = s -> s.contains(\"bar\"),\n", - " p2 = s -> s.length() < 5,\n", - " p3 = s -> s.contains(\"foo\"),\n", - " p4 = p1.negate().and(p2).or(p3);\n", - " public static void main(String[] args) {\n", - " Stream.of(\"bar\", \"foobar\", \"foobaz\", \"fongopuckey\")\n", - " .filter(p4)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "foobar\n", - "foobaz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`p4` 获取到了所有谓词并组合成一个更复杂的谓词。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。\n", - "\n", - "正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。\n", - "\n", - "从输出结果我们可以看到 `p4` 的工作流程:任何带有 `foo` 的东西都会留下,即使它的长度大于 5。 `fongopuckey` 因长度超出和不包含 `bar` 而被丢弃。\n", - "\n", - "\n", - "## 柯里化和部分求值\n", - "\n", - "[柯里化](https://en.wikipedia.org/wiki/Currying)(Currying)的名称来自于其发明者之一 *Haskell Curry*。他可能是计算机领域唯一名字被命名重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/CurryingAndPartials.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class CurryingAndPartials {\n", - " // 未柯里化:\n", - " static String uncurried(String a, String b) {\n", - " return a + b;\n", - " }\n", - " public static void main(String[] args) {\n", - " // 柯里化的函数:\n", - " Function> sum =\n", - " a -> b -> a + b; // [1]\n", - "\n", - " System.out.println(uncurried(\"Hi \", \"Ho\"));\n", - "\n", - " Function\n", - " hi = sum.apply(\"Hi \"); // [2]\n", - " System.out.println(hi.apply(\"Ho\"));\n", - "\n", - " // 部分应用:\n", - " Function sumHi =\n", - " sum.apply(\"Hup \");\n", - " System.out.println(sumHi.apply(\"Ho\"));\n", - " System.out.println(sumHi.apply(\"Hey\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Hi Ho\n", - "Hi Ho\n", - "Hup Ho\n", - "Hup Hey" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**[1]** 这一连串的箭头很巧妙。*注意*,在函数接口声明中,第二个参数是另一个函数。\n", - "\n", - "**[2]** 柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “无参函数” 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。\n", - "\n", - "我们可以通过添加级别来柯里化一个三参数函数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/Curry3Args.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Curry3Args {\n", - " public static void main(String[] args) {\n", - " Function>> sum =\n", - " a -> b -> c -> a + b + c;\n", - " Function> hi =\n", - " sum.apply(\"Hi \");\n", - " Function ho =\n", - " hi.apply(\"Ho \");\n", - " System.out.println(ho.apply(\"Hup\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Hi Ho Hup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于每个级别的箭头级联(Arrow-cascading),你在类型声明中包裹了另一个 **Function**。\n", - "\n", - "处理基本类型和装箱时,请使用适当的 **Function** 接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// functional/CurriedIntAdd.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class CurriedIntAdd {\n", - " public static void main(String[] args) {\n", - " IntFunction\n", - " curriedIntAdd = a -> b -> a + b;\n", - " IntUnaryOperator add4 = curriedIntAdd.apply(4);\n", - " System.out.println(add4.applyAsInt(5));\n", - "\t }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "9" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以在互联网上找到更多的柯里化示例。通常它们是用 Java 之外的语言实现的,但如果理解了柯里化的基本概念,你可以很轻松地用 Java 实现它们。\n", - "\n", - "\n", - "## 纯函数式编程\n", - "\n", - "\n", - "即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。\n", - "\n", - "这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些规则) 或 Clojure (需要的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "\n", - "Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。在下一章中,你会看到它们在流式编程中的应用。相信你会像我一样,喜欢上流式编程。\n", - "\n", - "这些特性满足大部分 Java 程序员的需求。他们开始羡慕嫉妒 Clojure、Scala 这类新语言的功能,并试图阻止 Java 程序员流失到其他阵营 (就算不能阻止,起码提供了更好的选择)。\n", - "\n", - "但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它的使用还是会让人感觉沮丧和鸡肋。\n", - "\n", - "当你遇到学习困难时,请记住通过 IDE(NetBeans、IntelliJ Idea 和 Eclipse)获得帮助,因为 IDE 可以智能提示你何时使用 Lambda 表达式或方法引用,甚至有时还能为你优化代码。\n", - "\n", - "\n", - "\n", - "[^1]: 功能粘贴在一起的方法的确有点与众不同,但它仍不失为一个库。\n", - "[^2]: 例如,这个电子书是利用 [Pandoc](http://pandoc.org/) 制作出来的,它是用纯函数式语言 [Haskell](https://www.haskell.org/) 编写的一个程序 。\n", - "[^3]: 有时函数式语言将其描述为“代码即数据”。\n", - "[^4]: 这个语法来自 C++。\n", - "[^5]: 我还没有验证过这种说法。\n", - "[^6]: 当你理解了[并发编程](./24-Concurrent-Programming.md)章节的内容,你就能明白为什么更改共享变量 “不是线程安全的” 的了。\n", - "[^7]: 接口能够支持方法的原因是它们是 Java 8 默认方法,你将在下一章中了解到。\n", - "[^8]: 一些语言,如 Python,允许像调用其他函数一样调用组合函数。但这是 Java,所以我们做做可为之事。\n", - "[^9]: 例如,[Immutables](https://immutables.github.io/) 和 [Mutability Detector](https://mutabilitydetector.github.io/MutabilityDetector/)。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/14-Streams.ipynb b/jupyter/14-Streams.ipynb deleted file mode 100644 index 4e5ca7c7..00000000 --- a/jupyter/14-Streams.ipynb +++ /dev/null @@ -1,3492 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十四章 流式编程\n", - "\n", - "> 集合优化了对象的存储,而流和对象的处理有关。\n", - "\n", - "流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。\n", - "\n", - "利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。\n", - "\n", - "在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。\n", - "\n", - "举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。实际上,你的关注点首先是创建一个有序集合。围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你想做什么:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Randoms.java\n", - "import java.util.*;\n", - "public class Randoms {\n", - " public static void main(String[] args) {\n", - " new Random(47)\n", - " .ints(5, 20)\n", - " .distinct()\n", - " .limit(7)\n", - " .sorted()\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "6\n", - "10\n", - "13\n", - "16\n", - "17\n", - "18\n", - "19" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了数值产生的边界。这将生成一个整数流。我们可以使用中间流操作(intermediate stream operation) `distinct()` 来获取它们的非重复值,然后使用 `limit()` 方法获取前 7 个元素。接下来,我们使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对每个流对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用。`System.out::println` 。\n", - "\n", - "注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。\n", - "\n", - "声明式编程(Declarative programming)是一种:声明要做什么,而非怎么做的编程风格。正如我们在函数式编程中所看到的。**注意**,命令式编程的形式更难以理解。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/ImperativeRandoms.java\n", - "import java.util.*;\n", - "public class ImperativeRandoms {\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " SortedSet rints = new TreeSet<>();\n", - " while(rints.size() < 7) {\n", - " int r = rand.nextInt(20);\n", - " if(r < 5) continue;\n", - " rints.add(r);\n", - " }\n", - " System.out.println(rints);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[7, 8, 9, 11, 13, 15, 18]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `Randoms.java` 中,我们无需定义任何变量,但在这里我们定义了 3 个变量: `rand`,`rints` 和 `r`。由于 `nextInt()` 方法没有下限的原因(其内置的下限永远为 0),这段代码实现起来更复杂。所以我们要生成额外的值来过滤小于 5 的结果。\n", - "\n", - "**注意**,你必须要研究程序的真正意图,而在 `Randoms.java` 中,代码只是告诉了你它正在做什么。这种语义清晰性也是 Java 8 的流式编程更受推崇的重要原因。\n", - "\n", - "在 `ImperativeRandoms.java` 中显式地编写迭代机制称为外部迭代。而在 `Randoms.java` 中,流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。\n", - "\n", - "另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。\n", - "\n", - "\n", - "\n", - "## 流支持\n", - "\n", - "Java 设计者面临着这样一个难题:现存的大量类库不仅为 Java 所用,同时也被应用在整个 Java 生态圈数百万行的代码中。如何将一个全新的流的概念融入到现有类库中呢?\n", - "\n", - "比如在 **Random** 中添加更多的方法。只要不改变原有的方法,现有代码就不会受到干扰。\n", - "\n", - "问题是,接口部分怎么改造呢?特别是涉及集合类接口的部分。如果你想把一个集合转换为流,直接向接口添加新方法会破坏所有老的接口实现类。\n", - "\n", - "Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `default`(`默认`)修饰的方法。通过这种方案,设计者们可以将流式(*stream*)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。\n", - "\n", - "下面我们来看下每种类型的流操作。\n", - "\n", - "\n", - "## 流创建\n", - "\n", - "你可以通过 `Stream.of()` 很容易地将一组元素转化成为流(`Bubble` 类在本章的后面定义):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/StreamOf.java\n", - "import java.util.stream.*;\n", - "public class StreamOf {\n", - " public static void main(String[] args) {\n", - " Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))\n", - " .forEach(System.out::println);\n", - " Stream.of(\"It's \", \"a \", \"wonderful \", \"day \", \"for \", \"pie!\")\n", - " .forEach(System.out::print);\n", - " System.out.println();\n", - " Stream.of(3.14159, 2.718, 1.618)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Bubble(1)\n", - "Bubble(2)\n", - "Bubble(3)\n", - "It's a wonderful day for pie!\n", - "3.14159\n", - "2.718\n", - "1.618" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "除此之外,每个集合都可以通过调用 `stream()` 方法来产生一个流。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/CollectionToStream.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class CollectionToStream {\n", - " public static void main(String[] args) {\n", - " List bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));\n", - " System.out.println(bubbles.stream()\n", - " .mapToInt(b -> b.i)\n", - " .sum());\n", - " \n", - " Set w = new HashSet<>(Arrays.asList(\"It's a wonderful day for pie!\".split(\" \")));\n", - " w.stream()\n", - " .map(x -> x + \" \")\n", - " .forEach(System.out::print);\n", - " System.out.println();\n", - " \n", - " Map m = new HashMap<>();\n", - " m.put(\"pi\", 3.14159);\n", - " m.put(\"e\", 2.718);\n", - " m.put(\"phi\", 1.618);\n", - " m.entrySet().stream()\n", - " .map(e -> e.getKey() + \": \" + e.getValue())\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "6\n", - "a pie! It's for wonderful day\n", - "phi: 1.618\n", - "e: 2.718\n", - "pi: 3.14159" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在创建 `List` 对象之后,我们只需要简单地调用所有集合中都有的 `stream()`。中间操作 `map()` 会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到后续的流中。通常 `map()` 会获取对象并产生新的对象,但在这里产生了特殊的用于数值类型的流。例如,`mapToInt()` 方法将一个对象流(object stream)转换成为包含整型数字的 `IntStream`。同样,针对 `Float` 和 `Double` 也有类似名字的操作。\n", - "\n", - "我们通过调用字符串的 `split()`(该方法会根据参数来拆分字符串)来获取元素用于定义变量 `w`。稍后你会知道 `split()` 参数可以是十分复杂,但在这里我们只是根据空格来分割字符串。\n", - "\n", - "为了从 **Map** 集合中产生流数据,我们首先调用 `entrySet()` 产生一个对象流,每个对象都包含一个 `key` 键以及与其相关联的 `value` 值。然后分别调用 `getKey()` 和 `getValue()` 获取值。\n", - "\n", - "### 随机数流\n", - "\n", - "`Random` 类被一组生成流的方法增强了。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/RandomGenerators.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class RandomGenerators {\n", - " public static void show(Stream stream) {\n", - " stream\n", - " .limit(4)\n", - " .forEach(System.out::println);\n", - " System.out.println(\"++++++++\");\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " show(rand.ints().boxed());\n", - " show(rand.longs().boxed());\n", - " show(rand.doubles().boxed());\n", - " // 控制上限和下限:\n", - " show(rand.ints(10, 20).boxed());\n", - " show(rand.longs(50, 100).boxed());\n", - " show(rand.doubles(20, 30).boxed());\n", - " // 控制流大小:\n", - " show(rand.ints(2).boxed());\n", - " show(rand.longs(2).boxed());\n", - " show(rand.doubles(2).boxed());\n", - " // 控制流的大小和界限\n", - " show(rand.ints(3, 3, 9).boxed());\n", - " show(rand.longs(3, 12, 22).boxed());\n", - " show(rand.doubles(3, 11.5, 12.3).boxed());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "-1172028779\n", - "1717241110\n", - "-2014573909\n", - "229403722\n", - "++++++++\n", - "2955289354441303771\n", - "3476817843704654257\n", - "-8917117694134521474\n", - "4941259272818818752\n", - "++++++++\n", - "0.2613610344283964\n", - "0.0508673570556899\n", - "0.8037155449603999\n", - "0.7620665811558285\n", - "++++++++\n", - "16\n", - "10\n", - "11\n", - "12\n", - "++++++++\n", - "65\n", - "99\n", - "54\n", - "58\n", - "++++++++\n", - "29.86777681078574\n", - "24.83968447804611\n", - "20.09247112332014\n", - "24.046793846338723\n", - "++++++++\n", - "1169976606\n", - "1947946283\n", - "++++++++\n", - "2970202997824602425\n", - "-2325326920272830366\n", - "++++++++\n", - "0.7024254510631527\n", - "0.6648552384607359\n", - "++++++++\n", - "6\n", - "7\n", - "7\n", - "++++++++\n", - "17\n", - "12\n", - "20\n", - "++++++++\n", - "12.27872414236691\n", - "11.732085449736195\n", - "12.196509449817267\n", - "++++++++" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了消除冗余代码,我创建了一个泛型方法 `show(Stream stream)` (在讲解泛型之前就使用这个特性,确实有点作弊,但是回报是值得的)。类型参数 `T` 可以是任何类型,所以这个方法对 **Integer**、**Long** 和 **Double** 类型都生效。但是 **Random** 类只能生成基本类型 **int**, **long**, **double** 的流。幸运的是, `boxed()` 流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得 `show()` 能够接受流。\n", - "\n", - "我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个文本文件提供字符串对象的例子。\n", - "\n", - "Cheese.dat 文件内容:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "// streams/Cheese.dat\n", - "Not much of a cheese shop really, is it?\n", - "Finest in the district, sir.\n", - "And what leads you to that conclusion?\n", - "Well, it's so clean.\n", - "It's certainly uncontaminated by cheese." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们通过 **File** 类将 Cheese.dat 文件的所有行读取到 `List` 中。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/RandomWords.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "public class RandomWords implements Supplier {\n", - " List words = new ArrayList<>();\n", - " Random rand = new Random(47);\n", - " RandomWords(String fname) throws IOException {\n", - " List lines = Files.readAllLines(Paths.get(fname));\n", - " // 略过第一行\n", - " for (String line : lines.subList(1, lines.size())) {\n", - " for (String word : line.split(\"[ .?,]+\"))\n", - " words.add(word.toLowerCase());\n", - " }\n", - " }\n", - " public String get() {\n", - " return words.get(rand.nextInt(words.size()));\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return words.stream()\n", - " .collect(Collectors.joining(\" \"));\n", - " }\n", - " public static void main(String[] args) throws Exception {\n", - " System.out.println(\n", - " Stream.generate(new RandomWords(\"Cheese.dat\"))\n", - " .limit(10)\n", - " .collect(Collectors.joining(\" \")));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "it shop sir the much cheese by conclusion district is" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里你可以看到更为复杂的 `split()` 运用。在构造器中,每一行都被 `split()` 通过空格或者被方括号包裹的任意标点符号进行分割。在结束方括号后面的 `+` 代表 `+` 前面的东西可以出现一次或者多次。\n", - "\n", - "我们注意到在构造函数中循环体使用命令式编程(外部迭代)。在以后的例子中,你甚至会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。\n", - "\n", - "在 `toString()` 和主方法中你看到了 `collect()` 收集操作,它根据参数来组合所有流中的元素。\n", - "\n", - "当你使用 **Collectors.**`joining()`,你将会得到一个 `String` 类型的结果,每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于产生不同的结果。\n", - "\n", - "在主方法中,我们提前看到了 **Stream.**`generate()` 的用法,它可以把任意 `Supplier` 用于生成 `T` 类型的流。\n", - "\n", - "\n", - "### int 类型的范围\n", - "\n", - "`IntStream` 类提供了 `range()` 方法用于生成整型序列的流。编写循环时,这个方法会更加便利:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Ranges.java\n", - "import static java.util.stream.IntStream.*;\n", - "public class Ranges {\n", - " public static void main(String[] args) {\n", - " // 传统方法:\n", - " int result = 0;\n", - " for (int i = 10; i < 20; i++)\n", - " result += i;\n", - " System.out.println(result);\n", - " // for-in 循环:\n", - " result = 0;\n", - " for (int i : range(10, 20).toArray())\n", - " result += i;\n", - " System.out.println(result);\n", - " // 使用流:\n", - " System.out.println(range(10, 20).sum());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "145\n", - "145\n", - "145" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在主方法中的第一种方式是我们传统编写 `for` 循环的方式;第二种方式,我们使用 `range()` 创建了流并将其转化为数组,然后在 `for-in` 代码块中使用。但是,如果你能像第三种方法那样全程使用流是更好的。我们对范围中的数字进行求和。在流中可以很方便的使用 `sum()` 操作求和。\n", - "\n", - "注意 **IntStream.**`range()` 相比 `onjava.Range.range()` 拥有更多的限制。这是由于其可选的第三个参数,后者允许步长大于 1,并且可以从大到小来生成。\n", - "\n", - "实用小功能 `repeat()` 可以用来替换简单的 `for` 循环。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Repeat.java\n", - "package onjava;\n", - "import static java.util.stream.IntStream.*;\n", - "public class Repeat {\n", - " public static void repeat(int n, Runnable action) {\n", - " range(0, n).forEach(i -> action.run());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其产生的循环更加清晰:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Looping.java\n", - "import static onjava.Repeat.*;\n", - "public class Looping {\n", - " static void hi() {\n", - " System.out.println(\"Hi!\");\n", - " }\n", - " public static void main(String[] args) {\n", - " repeat(3, () -> System.out.println(\"Looping!\"));\n", - " repeat(2, Looping::hi);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Looping!\n", - "Looping!\n", - "Looping!\n", - "Hi!\n", - "Hi!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "原则上,在代码中包含并解释 `repeat()` 并不值得。诚然它是一个相当透明的工具,但结果取决于你的团队和公司的运作方式。\n", - "\n", - "### generate()\n", - "\n", - "参照 `RandomWords.java` 中 **Stream.**`generate()` 搭配 `Supplier` 使用的例子。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Generator.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class Generator implements Supplier {\n", - " Random rand = new Random(47);\n", - " char[] letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", - " \n", - " public String get() {\n", - " return \"\" + letters[rand.nextInt(letters.length)];\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " String word = Stream.generate(new Generator())\n", - " .limit(30)\n", - " .collect(Collectors.joining());\n", - " System.out.println(word);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "YNZBRNYGCFOWZNTCQRGSEGZMMJMROE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围,所以使用数组边界是经过深思熟虑的。\n", - "\n", - "如果要创建包含相同对象的流,只需要传递一个生成那些对象的 `lambda` 到 `generate()` 中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Duplicator.java\n", - "import java.util.stream.*;\n", - "public class Duplicator {\n", - " public static void main(String[] args) {\n", - " Stream.generate(() -> \"duplicate\")\n", - " .limit(3)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "duplicate\n", - "duplicate\n", - "duplicate" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如下是在本章之前例子中使用过的 `Bubble` 类。**注意**它包含了自己的静态生成器(Static generator)方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Bubble.java\n", - "import java.util.function.*;\n", - "public class Bubble {\n", - " public final int i;\n", - " \n", - " public Bubble(int n) {\n", - " i = n;\n", - " }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"Bubble(\" + i + \")\";\n", - " }\n", - " \n", - " private static int count = 0;\n", - " public static Bubble bubbler() {\n", - " return new Bubble(count++);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于 `bubbler()` 与 `Supplier` 是接口兼容的,我们可以将其方法引用直接传递给 **Stream.**`generate()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Bubbles.java\n", - "import java.util.stream.*;\n", - "public class Bubbles {\n", - " public static void main(String[] args) {\n", - " Stream.generate(Bubble::bubbler)\n", - " .limit(5)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Bubble(0)\n", - "Bubble(1)\n", - "Bubble(2)\n", - "Bubble(3)\n", - "Bubble(4)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这是一个对于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。\n", - "\n", - "### iterate()\n", - "\n", - "**Stream.**`iterate()` 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 `iterate()`,依次类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Fibonacci.java\n", - "import java.util.stream.*;\n", - "public class Fibonacci {\n", - " int x = 1;\n", - " \n", - " Stream numbers() {\n", - " return Stream.iterate(0, i -> {\n", - " int result = x + i;\n", - " x = i;\n", - " return result;\n", - " });\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " new Fibonacci().numbers()\n", - " .skip(20) // 过滤前 20 个\n", - " .limit(10) // 然后取 10 个\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "6765\n", - "10946\n", - "17711\n", - "28657\n", - "46368\n", - "75025\n", - "121393\n", - "196418\n", - "317811\n", - "514229" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "斐波那契数列将数列中最后两个元素进行求和以产生下一个元素。`iterate()` 只能记忆结果,因此我们需要利用一个变量 `x` 追踪另外一个元素。\n", - "\n", - "在主方法中,我们使用了一个之前没有见过的 `skip()` 操作。它根据参数丢弃指定数量的流元素。在这里,我们丢弃了前 20 个元素。\n", - "\n", - "### 流的建造者模式\n", - "\n", - "在建造者设计模式(也称构造器模式)中,首先创建一个 `builder` 对象,传递给它多个构造器信息,最后执行“构造”。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FileToWordsBuilder.java\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class FileToWordsBuilder {\n", - " Stream.Builder builder = Stream.builder();\n", - " \n", - " public FileToWordsBuilder(String filePath) throws Exception {\n", - " Files.lines(Paths.get(filePath))\n", - " .skip(1) // 略过开头的注释行\n", - " .forEach(line -> {\n", - " for (String w : line.split(\"[ .?,]+\"))\n", - " builder.add(w);\n", - " });\n", - " }\n", - " \n", - " Stream stream() {\n", - " return builder.build();\n", - " }\n", - " \n", - " public static void main(String[] args) throws Exception {\n", - " new FileToWordsBuilder(\"Cheese.dat\")\n", - " .stream()\n", - " .limit(7)\n", - " .map(w -> w + \" \")\n", - " .forEach(System.out::print);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Not much of a cheese shop really" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**注意**,构造器会添加文件中的所有单词(除了第一行,它是包含文件路径信息的注释),但是其并没有调用 `build()`。只要你不调用 `stream()` 方法,就可以继续向 `builder` 对象中添加单词。\n", - "\n", - "在该类的更完整形式中,你可以添加一个标志位用于查看 `build()` 是否被调用,并且可能的话增加一个可以添加更多单词的方法。在 `Stream.Builder` 调用 `build()` 方法后继续尝试添加单词会产生一个异常。\n", - "\n", - "### Arrays\n", - "\n", - "`Arrays` 类中含有一个名为 `stream()` 的静态方法用于把数组转换成为流。我们可以重写 `interfaces/Machine.java` 中的主方法用于创建一个流,并将 `execute()` 应用于每一个元素。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Machine2.java\n", - "import java.util.*;\n", - "import onjava.Operations;\n", - "public class Machine2 {\n", - " public static void main(String[] args) {\n", - " Arrays.stream(new Operations[] {\n", - " () -> Operations.show(\"Bing\"),\n", - " () -> Operations.show(\"Crack\"),\n", - " () -> Operations.show(\"Twist\"),\n", - " () -> Operations.show(\"Pop\")\n", - " }).forEach(Operations::execute);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Bing\n", - "Crack\n", - "Twist\n", - "Pop" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`new Operations[]` 表达式动态创建了 `Operations` 对象的数组。\n", - "\n", - "`stream()` 同样可以产生 **IntStream**,**LongStream** 和 **DoubleStream**。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/ArrayStreams.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class ArrayStreams {\n", - " public static void main(String[] args) {\n", - " Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })\n", - " .forEach(n -> System.out.format(\"%f \", n));\n", - " System.out.println();\n", - " \n", - " Arrays.stream(new int[] { 1, 3, 5 })\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " \n", - " Arrays.stream(new long[] { 11, 22, 44, 66 })\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " \n", - " // 选择一个子域:\n", - " Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "3.141590 2.718000 1.618000\n", - "1 3 5\n", - "11 22 44 66\n", - "7 15 28" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最后一次 `stream()` 的调用有两个额外的参数。第一个参数告诉 `stream()` 从数组的哪个位置开始选择元素,第二个参数用于告知在哪里停止。每种不同类型的 `stream()` 都有类似的操作。\n", - "\n", - "### 正则表达式\n", - "\n", - "Java 的正则表达式将在[字符串](18-Strings.md)这一章节详细介绍。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 **CharSequence**,因此不能将流作为 `splitAsStream()` 的参数。\n", - "\n", - "我们再一次查看将文件处理为单词流的过程。这一次,我们使用流将文件分割为单独的字符串,接着使用正则表达式将字符串转化为单词流。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FileToWordsRegexp.java\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "import java.util.regex.Pattern;\n", - "public class FileToWordsRegexp {\n", - " private String all;\n", - " public FileToWordsRegexp(String filePath) throws Exception {\n", - " all = Files.lines(Paths.get(filePath))\n", - " .skip(1) // First (comment) line\n", - " .collect(Collectors.joining(\" \"));\n", - " }\n", - " public Stream stream() {\n", - " return Pattern\n", - " .compile(\"[ .,?]+\").splitAsStream(all);\n", - " }\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " FileToWordsRegexp fw = new FileToWordsRegexp(\"Cheese.dat\");\n", - " fw.stream()\n", - " .limit(7)\n", - " .map(w -> w + \" \")\n", - " .forEach(System.out::print);\n", - " fw.stream()\n", - " .skip(7)\n", - " .limit(2)\n", - " .map(w -> w + \" \")\n", - " .forEach(System.out::print);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Not much of a cheese shop really is it" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在构造器中我们读取了文件中的所有内容(跳过第一行注释,并将其转化成为单行字符串)。现在,当你调用 `stream()` 的时候,可以像往常一样获取一个流,但这次你可以多次调用 `stream()` 在已存储的字符串中创建一个新的流。这里有个限制,整个文件必须存储在内存中;在大多数情况下这并不是什么问题,但是这损失了流操作非常重要的优势:\n", - "\n", - "1. 流“不需要存储”。当然它们需要一些内部存储,但是这只是序列的一小部分,和持有整个序列并不相同。\n", - "2. 它们是懒加载计算的。\n", - "\n", - "幸运的是,我们稍后就会知道如何解决这个问题。\n", - "\n", - "\n", - "\n", - "## 中间操作\n", - "\n", - "中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。\n", - "\n", - "### 跟踪和调试\n", - "\n", - "`peek()` 操作的目的是帮助调试。它允许你无修改地查看流中的元素。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Peeking.java\n", - "class Peeking {\n", - " public static void main(String[] args) throws Exception {\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .skip(21)\n", - " .limit(4)\n", - " .map(w -> w + \" \")\n", - " .peek(System.out::print)\n", - " .map(String::toUpperCase)\n", - " .peek(System.out::print)\n", - " .map(String::toLowerCase)\n", - " .forEach(System.out::print);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Well WELL well it IT it s S s so SO so" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`FileToWords` 稍后定义,但它的功能实现貌似和之前我们看到的差不多:产生字符串对象的流。之后在其通过管道时调用 `peek()` 进行处理。\n", - "\n", - "因为 `peek()` 符合无返回值的 **Consumer** 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。\n", - "\n", - "### 流元素排序\n", - "\n", - "在 `Randoms.java` 中,我们熟识了 `sorted()` 的默认比较器实现。其实它还有另一种形式的实现:传入一个 **Comparator** 参数。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/SortedComparator.java\n", - "import java.util.*;\n", - "public class SortedComparator {\n", - " public static void main(String[] args) throws Exception {\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .skip(10)\n", - " .limit(10)\n", - " .sorted(Comparator.reverseOrder())\n", - " .map(w -> w + \" \")\n", - " .forEach(System.out::print);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "you what to the that sir leads in district And" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`sorted()` 预设了一些默认的比较器。这里我们使用的是反转“自然排序”。当然你也可以把 Lambda 函数作为参数传递给 `sorted()`。\n", - "\n", - "### 移除元素\n", - "\n", - "* `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。\n", - "\n", - "* `filter(Predicate)`:过滤操作会保留与传递进去的过滤器函数计算结果为 `true` 元素。\n", - "\n", - "在下例中,`isPrime()` 作为过滤器函数,用于检测质数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Prime.java\n", - "import java.util.stream.*;\n", - "import static java.util.stream.LongStream.*;\n", - "public class Prime {\n", - " public static Boolean isPrime(long n) {\n", - " return rangeClosed(2, (long)Math.sqrt(n))\n", - " .noneMatch(i -> n % i == 0);\n", - " }\n", - " public LongStream numbers() {\n", - " return iterate(2, i -> i + 1)\n", - " .filter(Prime::isPrime);\n", - " }\n", - " public static void main(String[] args) {\n", - " new Prime().numbers()\n", - " .limit(10)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " new Prime().numbers()\n", - " .skip(90)\n", - " .limit(10)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "2 3 5 7 11 13 17 19 23 29\n", - "467 479 487 491 499 503 509 521 523 541" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`rangeClosed()` 包含了上限值。如果不能整除,即余数不等于 0,则 `noneMatch()` 操作返回 `true`,如果出现任何等于 0 的结果则返回 `false`。 `noneMatch()` 操作一旦有失败就会退出。\n", - "\n", - "### 应用函数到元素\n", - "\n", - "- `map(Function)`:将函数操作应用在输入流的元素中,并将返回值传递到输出流中。\n", - "\n", - "- `mapToInt(ToIntFunction)`:操作同上,但结果是 **IntStream**。\n", - "\n", - "- `mapToLong(ToLongFunction)`:操作同上,但结果是 **LongStream**。\n", - "\n", - "- `mapToDouble(ToDoubleFunction)`:操作同上,但结果是 **DoubleStream**。\n", - "\n", - "在这里,我们使用 `map()` 映射多种函数到一个字符串流中。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FunctionMap.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "class FunctionMap {\n", - " static String[] elements = { \"12\", \"\", \"23\", \"45\" };\n", - " static Stream\n", - " testStream() {\n", - " return Arrays.stream(elements);\n", - " }\n", - " static void test(String descr, Function func) {\n", - " System.out.println(\" ---( \" + descr + \" )---\");\n", - " testStream()\n", - " .map(func)\n", - " .forEach(System.out::println);\n", - " }\n", - " public static void main(String[] args) {\n", - " test(\"add brackets\", s -> \"[\" + s + \"]\");\n", - " test(\"Increment\", s -> {\n", - " try {\n", - " return Integer.parseInt(s) + 1 + \"\";\n", - " }\n", - " catch(NumberFormatException e) {\n", - " return s;\n", - " }\n", - " }\n", - " );\n", - " test(\"Replace\", s -> s.replace(\"2\", \"9\"));\n", - " test(\"Take last digit\", s -> s.length() > 0 ?\n", - " s.charAt(s.length() - 1) + \"\" : s);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "---( add brackets )---\n", - "[12]\n", - "[]\n", - "[23]\n", - "[45]\n", - "---( Increment )---\n", - "13\n", - "24\n", - "46\n", - "---( Replace )---\n", - "19\n", - "93\n", - "45\n", - "---( Take last digit )---\n", - "2\n", - "3\n", - "5" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在上面的自增示例中,我们使用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出 **NumberFormatException** 异常,我们只须回过头来将原始字符串放回到输出流中。\n", - "\n", - "在以上例子中,`map()` 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FunctionMap2.java\n", - "// Different input and output types (不同的输入输出类型)\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class Numbered {\n", - " final int n;\n", - " Numbered(int n) {\n", - " this.n = n;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Numbered(\" + n + \")\";\n", - " }\n", - "}\n", - "class FunctionMap2 {\n", - " public static void main(String[] args) {\n", - " Stream.of(1, 5, 7, 9, 11, 13)\n", - " .map(Numbered::new)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Numbered(1)\n", - "Numbered(5)\n", - "Numbered(7)\n", - "Numbered(9)\n", - "Numbered(11)\n", - "Numbered(13)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们将获取到的整数通过构造器 `Numbered::new` 转化成为 `Numbered` 类型。\n", - "\n", - "如果使用 **Function** 返回的结果是数值类型的一种,我们必须使用合适的 `mapTo数值类型` 进行替代。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FunctionMap3.java\n", - "// Producing numeric output streams( 产生数值输出流)\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class FunctionMap3 {\n", - " public static void main(String[] args) {\n", - " Stream.of(\"5\", \"7\", \"9\")\n", - " .mapToInt(Integer::parseInt)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " Stream.of(\"17\", \"19\", \"23\")\n", - " .mapToLong(Long::parseLong)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " Stream.of(\"17\", \"1.9\", \".23\")\n", - " .mapToDouble(Double::parseDouble)\n", - " .forEach(n -> System.out.format(\"%f \", n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "5 7 9\n", - "17 19 23\n", - "17.000000 1.900000 0.230000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "遗憾的是,Java 设计者并没有尽最大努力去消除基本类型。\n", - "\n", - "### 在 `map()` 中组合流\n", - "\n", - "假设我们现在有了一个传入的元素流,并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。\n", - "\n", - "`flatMap()` 做了两件事:将产生流的函数应用在每个元素上(与 `map()` 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。\n", - "\n", - "`flatMap(Function)`:当 `Function` 产生流时使用。\n", - "\n", - "`flatMapToInt(Function)`:当 `Function` 产生 `IntStream` 时使用。\n", - "\n", - "`flatMapToLong(Function)`:当 `Function` 产生 `LongStream` 时使用。\n", - "\n", - "`flatMapToDouble(Function)`:当 `Function` 产生 `DoubleStream` 时使用。\n", - "\n", - "为了弄清它的工作原理,我们从传入一个刻意设计的函数给 `map()` 开始。该函数接受一个整数并产生一个字符串流:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/StreamOfStreams.java\n", - "import java.util.stream.*;\n", - "public class StreamOfStreams {\n", - " public static void main(String[] args) {\n", - " Stream.of(1, 2, 3)\n", - " .map(i -> Stream.of(\"Gonzo\", \"Kermit\", \"Beaker\"))\n", - " .map(e-> e.getClass().getName())\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "java.util.stream.ReferencePipeline$Head\n", - "java.util.stream.ReferencePipeline$Head\n", - "java.util.stream.ReferencePipeline$Head" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用 `flatMap()` 解决这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FlatMap.java\n", - "import java.util.stream.*;\n", - "public class FlatMap {\n", - " public static void main(String[] args) {\n", - " Stream.of(1, 2, 3)\n", - " .flatMap(i -> Stream.of(\"Gonzo\", \"Fozzie\", \"Beaker\"))\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Gonzo\n", - "Fozzie\n", - "Beaker\n", - "Gonzo\n", - "Fozzie\n", - "Beaker\n", - "Gonzo\n", - "Fozzie\n", - "Beaker" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从映射返回的每个流都会自动扁平为组成它的字符串。\n", - "\n", - "下面是另一个演示,我们从一个整数流开始,然后使用每一个整数去创建更多的随机数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/StreamOfRandoms.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class StreamOfRandoms {\n", - " static Random rand = new Random(47);\n", - " public static void main(String[] args) {\n", - " Stream.of(1, 2, 3, 4, 5)\n", - " .flatMapToInt(i -> IntStream.concat(\n", - " rand.ints(0, 100).limit(i), IntStream.of(-1)))\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里我们引入了 `concat()`,它以参数顺序组合两个流。 如此,我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。\n", - "\n", - "因为 `rand.ints()` 产生的是一个 `IntStream`,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数形式。\n", - "\n", - "让我们再看一下将文件划分为单词流的任务。我们最后使用到的是 **FileToWordsRegexp.java**,它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。\n", - "\n", - "下面,我们再使用 ` flatMap()` 来解决这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FileToWords.java\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "import java.util.regex.Pattern;\n", - "public class FileToWords {\n", - " public static Stream stream(String filePath) throws Exception {\n", - " return Files.lines(Paths.get(filePath))\n", - " .skip(1) // First (comment) line\n", - " .flatMap(line ->\n", - " Pattern.compile(\"\\\\W+\").splitAsStream(line));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`stream()` 现在是一个静态方法,因为它可以自己完成整个流创建过程。\n", - "\n", - "**注意**:`\\\\W+` 是一个正则表达式。他表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\\\w` 表示“单词字符”。\n", - "\n", - "我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ".flatMap(line -> Arrays.stream(line.split(\"\\\\W+\"))))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有了真正的、而非 `FileToWordsRegexp.java` 中基于集合存储的流,我们每次使用都必须从头创建,因为流并不能被复用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/FileToWordsTest.java\n", - "import java.util.stream.*;\n", - "public class FileToWordsTest {\n", - " public static void main(String[] args) throws Exception {\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .limit(7)\n", - " .forEach(s -> System.out.format(\"%s \", s));\n", - " System.out.println();\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .skip(7)\n", - " .limit(2)\n", - " .forEach(s -> System.out.format(\"%s \", s));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Not much of a cheese shop really\n", - "is it" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。\n", - "\n", - "\n", - "## Optional类\n", - "\n", - "在我们学习终端操作之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)?\n", - "\n", - "**Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括:\n", - "\n", - "- `findFirst()` 返回一个包含第一个元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", - "- `findAny()` 返回包含任意元素的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", - "- `max()` 和 `min()` 返回一个包含最大值或者最小值的 **Optional** 对象,如果流为空则返回 **Optional.empty**\n", - "\n", - " `reduce()` 不再以 `identity` 形式开头,而是将其返回值包装在 **Optional** 中。(`identity` 对象成为其他形式的 `reduce()` 的默认结果,因此不存在空结果的风险)\n", - "\n", - "对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**,`average()` 会将结果包装在 **Optional** 以防止流为空。\n", - "\n", - "以下是对空流进行所有这些操作的简单测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/OptionalsFromEmptyStreams.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class OptionalsFromEmptyStreams {\n", - " public static void main(String[] args) {\n", - " System.out.println(Stream.empty()\n", - " .findFirst());\n", - " System.out.println(Stream.empty()\n", - " .findAny());\n", - " System.out.println(Stream.empty()\n", - " .max(String.CASE_INSENSITIVE_ORDER));\n", - " System.out.println(Stream.empty()\n", - " .min(String.CASE_INSENSITIVE_ORDER));\n", - " System.out.println(Stream.empty()\n", - " .reduce((s1, s2) -> s1 + s2));\n", - " System.out.println(IntStream.empty()\n", - " .average());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "OptionalDouble.empty" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当流为空的时候你会获得一个 **Optional.empty** 对象,而不是抛出异常。**Optional** 拥有 `toString()` 方法可以用于展示有用信息。\n", - "\n", - "注意,空流是通过 `Stream.empty()` 创建的。如果你在没有任何上下文环境的情况下调用 `Stream.empty()`,Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Stream s = Stream.empty();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "就可以在调用 `empty()` 时推断类型。\n", - "\n", - "这个示例展示了 **Optional** 的两个基本用法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/OptionalBasics.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class OptionalBasics {\n", - " static void test(Optional optString) {\n", - " if(optString.isPresent())\n", - " System.out.println(optString.get()); \n", - " else\n", - " System.out.println(\"Nothing inside!\");\n", - " }\n", - " public static void main(String[] args) {\n", - " test(Stream.of(\"Epithets\").findFirst());\n", - " test(Stream.empty().findFirst());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Epithets\n", - "Nothing inside!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当你接收到 **Optional** 对象时,应首先调用 `isPresent()` 检查其中是否包含元素。如果存在,可使用 `get()` 获取。\n", - "\n", - "\n", - "\n", - "### 便利函数\n", - "\n", - "有许多便利函数可以解包 **Optional** ,这简化了上述“对所包含的对象的检查和执行操作”的过程:\n", - "\n", - "- `ifPresent(Consumer)`:当值存在时调用 **Consumer**,否则什么也不做。\n", - "- `orElse(otherObject)`:如果值存在则直接返回,否则生成 **otherObject**。\n", - "- `orElseGet(Supplier)`:如果值存在则直接返回,否则使用 **Supplier** 函数生成一个可替代对象。\n", - "- `orElseThrow(Supplier)`:如果值存在直接返回,否则使用 **Supplier** 函数生成一个异常。\n", - "\n", - "如下是针对不同便利函数的简单演示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Optionals.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "public class Optionals {\n", - " static void basics(Optional optString) {\n", - " if(optString.isPresent())\n", - " System.out.println(optString.get()); \n", - " else\n", - " System.out.println(\"Nothing inside!\");\n", - " }\n", - " static void ifPresent(Optional optString) {\n", - " optString.ifPresent(System.out::println);\n", - " }\n", - " static void orElse(Optional optString) {\n", - " System.out.println(optString.orElse(\"Nada\"));\n", - " }\n", - " static void orElseGet(Optional optString) {\n", - " System.out.println(\n", - " optString.orElseGet(() -> \"Generated\"));\n", - " }\n", - " static void orElseThrow(Optional optString) {\n", - " try {\n", - " System.out.println(optString.orElseThrow(\n", - " () -> new Exception(\"Supplied\")));\n", - " } catch(Exception e) {\n", - " System.out.println(\"Caught \" + e);\n", - " }\n", - " }\n", - " static void test(String testName, Consumer> cos) {\n", - " System.out.println(\" === \" + testName + \" === \");\n", - " cos.accept(Stream.of(\"Epithets\").findFirst());\n", - " cos.accept(Stream.empty().findFirst());\n", - " }\n", - " public static void main(String[] args) {\n", - " test(\"basics\", Optionals::basics);\n", - " test(\"ifPresent\", Optionals::ifPresent);\n", - " test(\"orElse\", Optionals::orElse);\n", - " test(\"orElseGet\", Optionals::orElseGet);\n", - " test(\"orElseThrow\", Optionals::orElseThrow);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "=== basics ===\n", - "Epithets\n", - "Nothing inside!\n", - "=== ifPresent ===\n", - "Epithets\n", - "=== orElse ===\n", - "Epithets\n", - "Nada\n", - "=== orElseGet ===\n", - "Epithets\n", - "Generated\n", - "=== orElseThrow ===\n", - "Epithets\n", - "Caught java.lang.Exception: Supplied" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。\n", - "\n", - "`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。更多细节,将在 [异常](./15-Exceptions.md) 这一章节中学习。\n", - "\n", - "\n", - "\n", - "### 创建 Optional\n", - "\n", - "当我们在自己的代码中加入 **Optional** 时,可以使用下面 3 个静态方法:\n", - "\n", - "- `empty()`:生成一个空 **Optional**。\n", - "- `of(value)`:将一个非空值包装到 **Optional** 里。\n", - "- `ofNullable(value)`:针对一个可能为空的值,为空时自动生成 **Optional.empty**,否则将值包装在 **Optional** 中。\n", - "\n", - "下面来看看它是如何工作的。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/CreatingOptionals.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "class CreatingOptionals {\n", - " static void test(String testName, Optional opt) {\n", - " System.out.println(\" === \" + testName + \" === \");\n", - " System.out.println(opt.orElse(\"Null\"));\n", - " }\n", - " public static void main(String[] args) {\n", - " test(\"empty\", Optional.empty());\n", - " test(\"of\", Optional.of(\"Howdy\"));\n", - " try {\n", - " test(\"of\", Optional.of(null));\n", - " } catch(Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " test(\"ofNullable\", Optional.ofNullable(\"Hi\"));\n", - " test(\"ofNullable\", Optional.ofNullable(null));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "=== empty ===\n", - "Null\n", - "=== of ===\n", - "Howdy\n", - "java.lang.NullPointerException\n", - "=== ofNullable ===\n", - "Hi\n", - "=== ofNullable ===\n", - "Null" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们不能通过传递 `null` 到 `of()` 来创建 `Optional` 对象。最安全的方法是, 使用 `ofNullable()` 来优雅地处理 `null`。\n", - "\n", - "### Optional 对象操作\n", - "\n", - "当我们的流管道生成了 **Optional** 对象,下面 3 个方法可使得 **Optional** 的后续能做更多的操作:\n", - "\n", - "- `filter(Predicate)`:将 **Predicate** 应用于 **Optional** 中的内容并返回结果。当 **Optional** 不满足 **Predicate** 时返回空。如果 **Optional** 为空,则直接返回。\n", - "\n", - "- `map(Function)`:如果 **Optional** 不为空,应用 **Function** 于 **Optional** 中的内容,并返回结果。否则直接返回 **Optional.empty**。\n", - "\n", - "- `flatMap(Function)`:同 `map()`,但是提供的映射函数将结果包装在 **Optional** 对象中,因此 `flatMap()` 不会在最后进行任何包装。\n", - "\n", - "以上方法都不适用于数值型 **Optional**。一般来说,流的 `filter()` 会在 **Predicate** 返回 `false` 时移除流元素。而 `Optional.filter()` 在失败时不会删除 **Optional**,而是将其保留下来,并转化为空。下面请看代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/OptionalFilter.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "class OptionalFilter {\n", - " static String[] elements = {\n", - " \"Foo\", \"\", \"Bar\", \"Baz\", \"Bingo\"\n", - " };\n", - " static Stream testStream() {\n", - " return Arrays.stream(elements);\n", - " }\n", - " static void test(String descr, Predicate pred) {\n", - " System.out.println(\" ---( \" + descr + \" )---\");\n", - " for(int i = 0; i <= elements.length; i++) {\n", - " System.out.println(\n", - " testStream()\n", - " .skip(i)\n", - " .findFirst()\n", - " .filter(pred));\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " test(\"true\", str -> true);\n", - " test(\"false\", str -> false);\n", - " test(\"str != \\\"\\\"\", str -> str != \"\");\n", - " test(\"str.length() == 3\", str -> str.length() == 3);\n", - " test(\"startsWith(\\\"B\\\")\",\n", - " str -> str.startsWith(\"B\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "---( true )---\n", - "Optional[Foo]\n", - "Optional[]\n", - "Optional[Bar]\n", - "Optional[Baz]\n", - "Optional[Bingo]\n", - "Optional.empty\n", - "---( false )---\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional.empty\n", - "---( str != \"\" )---\n", - "Optional[Foo]\n", - "Optional.empty\n", - "Optional[Bar]\n", - "Optional[Baz]\n", - "Optional[Bingo]\n", - "Optional.empty\n", - "---( str.length() == 3 )---\n", - "Optional[Foo]\n", - "Optional.empty\n", - "Optional[Bar]\n", - "Optional[Baz]\n", - "Optional.empty\n", - "Optional.empty\n", - "---( startsWith(\"B\") )---\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional[Bar]\n", - "Optional[Baz]\n", - "Optional[Bingo]\n", - "Optional.empty" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "即使输出看起来像流,特别是 `test()` 中的 for 循环。每一次的 for 循环时重新启动流,然后根据 for 循环的索引跳过指定个数的元素,这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素,结果会包装在 **Optional** 中。\n", - "\n", - "**注意**,不同于普通 for 循环,这里的索引值范围并不是 `i < elements.length`, 而是 `i <= elements.length`。所以最后一个元素实际上超出了流。方便的是,这将自动成为 **Optional.empty**,你可以在每一个测试的结尾中看到。\n", - "\n", - "同 `map()` 一样 , `Optional.map()` 应用于函数。它仅在 **Optional** 不为空时才应用映射函数,并将 **Optional** 的内容提取到映射函数。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/OptionalMap.java\n", - "import java.util.Arrays;\n", - "import java.util.function.Function;\n", - "import java.util.stream.Stream;\n", - "\n", - "class OptionalMap {\n", - " static String[] elements = {\"12\", \"\", \"23\", \"45\"};\n", - "\n", - " static Stream testStream() {\n", - " return Arrays.stream(elements);\n", - " }\n", - "\n", - " static void test(String descr, Function func) {\n", - " System.out.println(\" ---( \" + descr + \" )---\");\n", - " for (int i = 0; i <= elements.length; i++) {\n", - " System.out.println(\n", - " testStream()\n", - " .skip(i)\n", - " .findFirst() // Produces an Optional\n", - " .map(func));\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " // If Optional is not empty, map() first extracts\n", - " // the contents which it then passes\n", - " // to the function:\n", - " test(\"Add brackets\", s -> \"[\" + s + \"]\");\n", - " test(\"Increment\", s -> {\n", - " try {\n", - " return Integer.parseInt(s) + 1 + \"\";\n", - " } catch (NumberFormatException e) {\n", - " return s;\n", - " }\n", - " });\n", - " test(\"Replace\", s -> s.replace(\"2\", \"9\"));\n", - " test(\"Take last digit\", s -> s.length() > 0 ?\n", - " s.charAt(s.length() - 1) + \"\" : s);\n", - " }\n", - " // After the function is finished, map() wraps the\n", - " // result in an Optional before returning it:\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "---( Add brackets )---\n", - "Optional[[12]]\n", - "Optional[[]]\n", - "Optional[[23]]\n", - "Optional[[45]]\n", - "Optional.empty\n", - "---( Increment )---\n", - "Optional[13]\n", - "Optional[]\n", - "Optional[24]\n", - "Optional[46]\n", - "Optional.empty\n", - "---( Replace )---\n", - "Optional[19]\n", - "Optional[]\n", - "Optional[93]\n", - "Optional[45]\n", - "Optional.empty\n", - "---( Take last digit )---\n", - "Optional[2]\n", - "Optional[]\n", - "Optional[3]\n", - "Optional[5]\n", - "Optional.empty" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "映射函数的返回结果会自动包装成为 **Optional**。**Optional.empty** 会被直接跳过。\n", - "\n", - "**Optional** 的 `flatMap()` 应用于已生成 **Optional** 的映射函数,所以 `flatMap()` 不会像 `map()` 那样将结果封装在 **Optional** 中。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/OptionalFlatMap.java\n", - "import java.util.Arrays;\n", - "import java.util.Optional;\n", - "import java.util.function.Function;\n", - "import java.util.stream.Stream;\n", - "\n", - "class OptionalFlatMap {\n", - " static String[] elements = {\"12\", \"\", \"23\", \"45\"};\n", - "\n", - " static Stream testStream() {\n", - " return Arrays.stream(elements);\n", - " }\n", - "\n", - " static void test(String descr,\n", - " Function> func) {\n", - " System.out.println(\" ---( \" + descr + \" )---\");\n", - " for (int i = 0; i <= elements.length; i++) {\n", - " System.out.println(\n", - " testStream()\n", - " .skip(i)\n", - " .findFirst()\n", - " .flatMap(func));\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " test(\"Add brackets\",\n", - " s -> Optional.of(\"[\" + s + \"]\"));\n", - " test(\"Increment\", s -> {\n", - " try {\n", - " return Optional.of(\n", - " Integer.parseInt(s) + 1 + \"\");\n", - " } catch (NumberFormatException e) {\n", - " return Optional.of(s);\n", - " }\n", - " });\n", - " test(\"Replace\",\n", - " s -> Optional.of(s.replace(\"2\", \"9\")));\n", - " test(\"Take last digit\",\n", - " s -> Optional.of(s.length() > 0 ?\n", - " s.charAt(s.length() - 1) + \"\"\n", - " : s));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "---( Add brackets )---\n", - "Optional[[12]]\n", - "Optional[[]]\n", - "Optional[[23]]\n", - "Optional[[45]]\n", - "Optional.empty\n", - " ---( Increment )---\n", - "Optional[13]\n", - "Optional[]\n", - "Optional[24]\n", - "Optional[46]\n", - "Optional.empty\n", - " ---( Replace )---\n", - "Optional[19]\n", - "Optional[]\n", - "Optional[93]\n", - "Optional[45]\n", - "Optional.empty\n", - " ---( Take last digit )---\n", - "Optional[2]\n", - "Optional[]\n", - "Optional[3]\n", - "Optional[5]\n", - "Optional.empty" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "同 `map()`,`flatMap()` 将提取非空 **Optional** 的内容并将其应用在映射函数。唯一的区别就是 `flatMap()` 不会把结果包装在 **Optional** 中,因为映射函数已经被包装过了。在如上示例中,我们已经在每一个映射函数中显式地完成了包装,但是很显然 `Optional.flatMap()` 是为那些自己已经生成 **Optional** 的函数而设计的。\n", - "\n", - "\n", - "### Optional 流\n", - "\n", - "假设你的生成器可能产生 `null` 值,那么当用它来创建流时,你会自然地想到用 **Optional** 来包装元素。如下是它的样子,代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Signal.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "public class Signal {\n", - " private final String msg;\n", - " public Signal(String msg) { this.msg = msg; }\n", - " public String getMsg() { return msg; }\n", - " @Override\n", - " public String toString() {\n", - " return \"Signal(\" + msg + \")\";\n", - " }\n", - " static Random rand = new Random(47);\n", - " public static Signal morse() {\n", - " switch(rand.nextInt(4)) {\n", - " case 1: return new Signal(\"dot\");\n", - " case 2: return new Signal(\"dash\");\n", - " default: return null;\n", - " }\n", - " }\n", - " public static Stream> stream() {\n", - " return Stream.generate(Signal::morse)\n", - " .map(signal -> Optional.ofNullable(signal));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当我们使用这个流的时候,必须要弄清楚如何解包 **Optional**。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/StreamOfOptionals.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class StreamOfOptionals {\n", - " public static void main(String[] args) {\n", - " Signal.stream()\n", - " .limit(10)\n", - " .forEach(System.out::println);\n", - " System.out.println(\" ---\");\n", - " Signal.stream()\n", - " .limit(10)\n", - " .filter(Optional::isPresent)\n", - " .map(Optional::get)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Optional[Signal(dash)]\n", - "Optional[Signal(dot)]\n", - "Optional[Signal(dash)]\n", - "Optional.empty\n", - "Optional.empty\n", - "Optional[Signal(dash)]\n", - "Optional.empty\n", - "Optional[Signal(dot)]\n", - "Optional[Signal(dash)]\n", - "Optional[Signal(dash)]\n", - "---\n", - "Signal(dot)\n", - "Signal(dot)\n", - "Signal(dash)\n", - "Signal(dash)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里,我们使用 `filter()` 来保留那些非空 **Optional**,然后在 `map()` 中使用 `get()` 获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。\n", - "\n", - "\n", - "\n", - "## 终端操作\n", - "\n", - "\n", - "以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中所做的最后一件事。\n", - "\n", - "\n", - "\n", - "### 数组\n", - "- `toArray()`:将流转换成适当类型的数组。\n", - "- `toArray(generator)`:在特殊情况下,生成自定义类型的数组。\n", - "\n", - "当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假设我们需要复用流产生的随机数时,就可以这么使用。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/RandInts.java\n", - "package streams;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class RandInts {\n", - " private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray();\n", - " public static IntStream rands() {\n", - " return Arrays.stream(rints);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中。这样一来,每次调用 `rands()` 的时候可以重复获取相同的整数流。\n", - "\n", - "\n", - "### 循环\n", - "\n", - "- `forEach(Consumer)`常见如 `System.out::println` 作为 **Consumer** 函数。\n", - "- `forEachOrdered(Consumer)`: 保证 `forEach` 按照原始流顺序操作。\n", - "\n", - "第一种形式:无序操作,仅在引入并行流时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。\n", - "\n", - "`parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。\n", - "\n", - "下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/ForEach.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import static streams.RandInts.*;\n", - "public class ForEach {\n", - " static final int SZ = 14;\n", - " public static void main(String[] args) {\n", - " rands().limit(SZ)\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " rands().limit(SZ)\n", - " .parallel()\n", - " .forEach(n -> System.out.format(\"%d \", n));\n", - " System.out.println();\n", - " rands().limit(SZ)\n", - " .parallel()\n", - " .forEachOrdered(n -> System.out.format(\"%d \", n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "258 555 693 861 961 429 868 200 522 207 288 128 551 589\n", - "551 861 429 589 200 522 555 693 258 128 868 288 961 207\n", - "258 555 693 861 961 429 868 200 522 207 288 128 551 589" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。\n", - "\n", - "在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。\n", - "\n", - "\n", - "\n", - "### 集合\n", - "\n", - "- `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。\n", - "- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。\n", - "\n", - "在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 `java.util.stream.Collectors` 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。\n", - "\n", - "假设我们现在为了保证元素有序,将元素存储在 **TreeSet** 中。**Collectors** 里面没有特定的 `toTreeSet()`,但是我们可以通过将集合的构造函数引用传递给 `Collectors.toCollection()`,从而构建任何类型的集合。下面我们来将一个文件中的单词收集到 **TreeSet** 集合中。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/TreeSetOfWords.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "public class TreeSetOfWords {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " Set words2 =\n", - " Files.lines(Paths.get(\"TreeSetOfWords.java\"))\n", - " .flatMap(s -> Arrays.stream(s.split(\"\\\\W+\")))\n", - " .filter(s -> !s.matches(\"\\\\d+\")) // No numbers\n", - " .map(String::trim)\n", - " .filter(s -> s.length() > 2)\n", - " .limit(100)\n", - " .collect(Collectors.toCollection(TreeSet::new));\n", - " System.out.println(words2);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[Arrays, Collectors, Exception, Files, Output, Paths,\n", - "Set, String, System, TreeSet, TreeSetOfWords, args,\n", - "class, collect, file, filter, flatMap, get, import,\n", - "java, length, limit, lines, main, map, matches, new,\n", - "nio, numbers, out, println, public, split, static,\n", - "stream, streams, throws, toCollection, trim, util,\n", - "void, words2]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果展平映射成为单词流。使用 `matches(\\\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。\n", - "\n", - "\n", - "\n", - "我们也可以在流中生成 **Map**。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/MapCollector.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class Pair {\n", - " public final Character c;\n", - " public final Integer i;\n", - " Pair(Character c, Integer i) {\n", - " this.c = c;\n", - " this.i = i;\n", - " }\n", - " public Character getC() { return c; }\n", - " public Integer getI() { return i; }\n", - " @Override\n", - " public String toString() {\n", - " return \"Pair(\" + c + \", \" + i + \")\";\n", - " }\n", - "}\n", - "class RandomPair {\n", - " Random rand = new Random(47);\n", - " // An infinite iterator of random capital letters:\n", - " Iterator capChars = rand.ints(65,91)\n", - " .mapToObj(i -> (char)i)\n", - " .iterator();\n", - " public Stream stream() {\n", - " return rand.ints(100, 1000).distinct()\n", - " .mapToObj(i -> new Pair(capChars.next(), i));\n", - " }\n", - "}\n", - "public class MapCollector {\n", - " public static void main(String[] args) {\n", - " Map map =\n", - " new RandomPair().stream()\n", - " .limit(8)\n", - " .collect(\n", - " Collectors.toMap(Pair::getI, Pair::getC));\n", - " System.out.println(map);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以这里创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 允许我们在 `stream()` 中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。\n", - "\n", - "在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。\n", - "\n", - "大多数情况下,`java.util.stream.Collectors` 中预设的 **Collector** 就能满足我们的要求。除此之外,你还可以使用第二种形式的 `collect()`。 我把它留作更高级的练习,下例给出基本用法:\n", - "\n", - "" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/SpecialCollector.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class SpecialCollector {\n", - " public static void main(String[] args) throws Exception {\n", - " ArrayList words =\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .collect(ArrayList::new,\n", - " ArrayList::add,\n", - " ArrayList::addAll);\n", - " words.stream()\n", - " .filter(s -> s.equals(\"cheese\"))\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cheese\n", - "cheese" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。\n", - "\n", - "\n", - "\n", - "### 组合\n", - "\n", - "- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**。\n", - "- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。\n", - "- `reduce(identity, BiFunction, BinaryOperator)`:更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。\n", - "\n", - "下面来看下 `reduce` 的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Reduce.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "class Frobnitz {\n", - " int size;\n", - " Frobnitz(int sz) { size = sz; }\n", - " @Override\n", - " public String toString() {\n", - " return \"Frobnitz(\" + size + \")\";\n", - " }\n", - " // Generator:\n", - " static Random rand = new Random(47);\n", - " static final int BOUND = 100;\n", - " static Frobnitz supply() {\n", - " return new Frobnitz(rand.nextInt(BOUND));\n", - " }\n", - "}\n", - "public class Reduce {\n", - " public static void main(String[] args) {\n", - " Stream.generate(Frobnitz::supply)\n", - " .limit(10)\n", - " .peek(System.out::println)\n", - " .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)\n", - " .ifPresent(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Frobnitz(58)\n", - "Frobnitz(55)\n", - "Frobnitz(93)\n", - "Frobnitz(61)\n", - "Frobnitz(61)\n", - "Frobnitz(29)\n", - "Frobnitz(68)\n", - "Frobnitz(0)\n", - "Frobnitz(22)\n", - "Frobnitz(7)\n", - "Frobnitz(29)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。\n", - "\n", - "Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。\n", - "\n", - "`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。当取得第一个长度小于 50 的 `Frobnitz`,只要得到结果就会忽略其他。这是个非常奇怪的约束, 也确实让我们对 `reduce()` 有了更多的了解。\n", - "\n", - "\n", - "### 匹配\n", - "\n", - "- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。\n", - "- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。\n", - "- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。\n", - "\n", - "我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Matching.java\n", - "// Demonstrates short-circuiting of *Match() operations\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "import static streams.RandInts.*;\n", - "\n", - "interface Matcher extends BiPredicate, Predicate> {}\n", - " \n", - "public class Matching {\n", - " static void show(Matcher match, int val) {\n", - " System.out.println(\n", - " match.test(\n", - " IntStream.rangeClosed(1, 9)\n", - " .boxed()\n", - " .peek(n -> System.out.format(\"%d \", n)),\n", - " n -> n < val));\n", - " }\n", - " public static void main(String[] args) {\n", - " show(Stream::allMatch, 10);\n", - " show(Stream::allMatch, 4);\n", - " show(Stream::anyMatch, 2);\n", - " show(Stream::anyMatch, 0);\n", - " show(Stream::noneMatch, 5);\n", - " show(Stream::noneMatch, 0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1 2 3 4 5 6 7 8 9 true\n", - "1 2 3 4 false\n", - "1 true\n", - "1 2 3 4 5 6 7 8 9 false\n", - "1 false\n", - "1 2 3 4 5 6 7 8 9 true" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**BiPredicate** 是一个二元谓词,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。**Matcher** 适用于所有的 **Stream::\\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\\*Match** 函数的调用。\n", - "\n", - "`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。\n", - "\n", - "### 查找\n", - "\n", - "- `findFirst()`:返回第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**。\n", - "- `findAny(`:返回含有任意流元素的 **Optional**,如果流为空返回 **Optional.empty**。\n", - "\n", - "代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/SelectElement.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import static streams.RandInts.*;\n", - "public class SelectElement {\n", - " public static void main(String[] args) {\n", - " System.out.println(rands().findFirst().getAsInt());\n", - " System.out.println(\n", - " rands().parallel().findFirst().getAsInt());\n", - " System.out.println(rands().findAny().getAsInt());\n", - " System.out.println(\n", - " rands().parallel().findAny().getAsInt());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "258\n", - "258\n", - "258\n", - "242" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`findFirst()` 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。\n", - "\n", - "如果必须选择流中最后一个元素,那就使用 `reduce()`。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/LastElement.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class LastElement {\n", - " public static void main(String[] args) {\n", - " OptionalInt last = IntStream.range(10, 20)\n", - " .reduce((n1, n2) -> n2);\n", - " System.out.println(last.orElse(-1));\n", - " // Non-numeric object:\n", - " Optional lastobj =\n", - " Stream.of(\"one\", \"two\", \"three\")\n", - " .reduce((n1, n2) -> n2);\n", - " System.out.println(\n", - " lastobj.orElse(\"Nothing there!\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "19\n", - "three" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`reduce()` 的参数只是用最后一个元素替换了最后两个元素,最终只生成最后一个元素。如果为数字流,你必须使用相近的数字 **Optional** 类型( numeric optional type),否则使用 **Optional** 类型,就像上例中的 `Optional`。\n", - "\n", - "\n", - "\n", - "### 信息\n", - "\n", - "- `count()`:流中的元素个数。\n", - "- `max(Comparator)`:根据所传入的 **Comparator** 所决定的“最大”元素。\n", - "- `min(Comparator)`:根据所传入的 **Comparator** 所决定的“最小”元素。\n", - "\n", - "**String** 类型有预设的 **Comparator** 实现。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/Informational.java\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "public class Informational {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " System.out.println(\n", - " FileToWords.stream(\"Cheese.dat\").count());\n", - " System.out.println(\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .min(String.CASE_INSENSITIVE_ORDER)\n", - " .orElse(\"NONE\"));\n", - " System.out.println(\n", - " FileToWords.stream(\"Cheese.dat\")\n", - " .max(String.CASE_INSENSITIVE_ORDER)\n", - " .orElse(\"NONE\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "32\n", - "a\n", - "you" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`min()` 和 `max()` 的返回类型为 **Optional**,这需要我们使用 `orElse()`来解包。\n", - "\n", - "\n", - "### 数字流信息\n", - "\n", - "- `average()` :求取流元素平均值。\n", - "- `max()` 和 `min()`:数值流操作无需 **Comparator**。\n", - "- `sum()`:对所有流元素进行求和。\n", - "- `summaryStatistics()`:生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// streams/NumericStreamInfo.java\n", - "import java.util.stream.*;\n", - "import static streams.RandInts.*;\n", - "public class NumericStreamInfo {\n", - " public static void main(String[] args) {\n", - " System.out.println(rands().average().getAsDouble());\n", - " System.out.println(rands().max().getAsInt());\n", - " System.out.println(rands().min().getAsInt());\n", - " System.out.println(rands().sum());\n", - " System.out.println(rands().summaryStatistics());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "507.94\n", - "998\n", - "8\n", - "50794\n", - "IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例操作对于 **LongStream** 和 **DoubleStream** 同样适用。\n", - "\n", - "## 本章小结\n", - "\n", - "流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。在本书的剩余部分,我们将尽可能地使用流。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/15-Exceptions.ipynb b/jupyter/15-Exceptions.ipynb deleted file mode 100644 index bd409430..00000000 --- a/jupyter/15-Exceptions.ipynb +++ /dev/null @@ -1,3518 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十五章 异常\n", - "\n", - "> Java 的基本理念是“结构不佳的代码不能运行”。\n", - "\n", - "改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在 Java 中它显得格外重要,因为 Java 的主要目标之一就是创建供他人使用的程序构件。\n", - "\n", - "发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。\n", - "\n", - "> 要想创建健壮的系统,它的每一个构件都必须是健壮的。\n", - "\n", - "Java 使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。\n", - "\n", - "Java 中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更加确信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。\n", - "\n", - "因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理程序,并将展示当方法出问题的时候,如何产生自定义的异常。\n", - "\n", - "\n", - "\n", - "## 异常概念\n", - "\n", - "C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不关我的事”。所以,程序员不去检查错误情形也就不足为奇了(何况对某些错误情形的检查确实很无聊)。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。\n", - "\n", - "解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到 20 世纪 60 年代的操作系统,甚至于 BASIC 语言中的“on error goto”语句。而 C++的异常处理机制基于 Ada,Java 中的异常处理机制则建立在 C++ 的基础之上(尽管看上去更像 Object Pascal)。\n", - "\n", - "“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在那里将作出正确的决定。\n", - "\n", - "异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。\n", - "\n", - "\n", - "\n", - "## 基本异常\n", - "\n", - "异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。\n", - "\n", - "除法就是一个简单的例子。除数有可能为 0,所以先进行检查很有必要。但除数为 0 代表的究竟是什么意思呢?通过当前正在解决的问题环境,或许能知道该如何处理除数为 0 的情况。但如果这是一个意料之外的值,你也不清楚该如何处理,那就要抛出异常,而不是顺着原来的路径继续执行下去。\n", - "\n", - "当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。\n", - "\n", - "举一个抛出异常的简单例子。对于对象引用 t,传给你的时候可能尚未被初始化。所以在使用这个对象引用调用其方法之前,会先对引用进行检查。可以创建一个代表错误信息的对象,并且将它从当前环境中“抛出”,这样就把错误信息传播到了“更大”的环境中。这被称为*抛出一个异常*,看起来像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(t == null)\n", - " throw new NullPointerException();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。具体是哪个“地方”后面很快就会介绍。\n", - "\n", - "异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。\n", - "\n", - "异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++ 这样的语言中,这可真是个问题,尤其是 C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略问题,从而会陷入完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。\n", - "\n", - "\n", - "\n", - "### 异常参数\n", - "\n", - "与使用 Java 中的其他对象一样,我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是无参构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "throw new NullPointerException(\"t = null\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "不久读者将看到,要把这个字符串的内容提取出来可以有多种不同的方法。\n", - "\n", - "关键字 **throw** 将产生许多有趣的结果。在使用 **new** 创建了异常对象之后,此对象的引用将传给 **throw**。尽管异常对象的类型通常与方法设计的返回类型不同,但从效果上看,它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制,当然若过分强调这种类比的话,就会有麻烦了。另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下,将会返回一个异常对象,然后退出方法或作用域。\n", - "\n", - "抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。(异常将在一个恰当的异常处理程序中得到解决,它的位置可能离异常被抛出的地方很远,也可能会跨越方法调用栈的许多层级。)\n", - "\n", - "此外,能够抛出任意类型的 **Throwable** 对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,唯一的信息只有异常的类型名,而在异常对象内部没有任何有意义的信息。)\n", - "\n", - "## 异常捕获\n", - "\n", - "要明白异常是如何被捕获的,必须首先理解监控区域(guarded region)的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。\n", - "\n", - "### try 语句块\n", - "\n", - "如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种(可能产生异常的)方法调用,所以称为 try 块。它是跟在 try 关键字之后的普通程序块:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - " // Code that might generate exceptions\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在 try 块里,然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读,因为代码的意图和错误检查不是混淆在一起的。\n", - "\n", - "### 异常处理程序\n", - "\n", - "当然,抛出的异常必须在某处得到处理。这个“地点”就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在 try 块之后,以关键字 catch 表示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - " // Code that might generate exceptions\n", - "} catch(Type1 id1) {\n", - " // Handle exceptions of Type1\n", - "} catch(Type2 id2) {\n", - " // Handle exceptions of Type2\n", - "} catch(Type3 id3) {\n", - " // Handle exceptions of Type3\n", - "}\n", - "// etc." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个 catch 子句(异常处理程序)看起来就像是接收且仅接收一个特殊类型的参数的方法。可以在处理程序的内部使用标识符(id1,id2 等等),这与方法参数的使用很相似。有时可能用不到标识符,因为异常的类型已经给了你足够的信息来对异常进行处理,但标识符并不可以省略。\n", - "\n", - "异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句结束,则处理程序的查找过程结束。注意,只有匹配的 catch 子句才能得到执行;这与 switch 语句不同,switch 语句需要在每一个 case 后面跟一个 break,以避免执行后续的 case 子句。\n", - "\n", - "注意在 try 块的内部,许多不同的方法调用可能会产生类型相同的异常,而你只需要提供一个针对此类型的异常处理程序。\n", - "\n", - "### 终止与恢复\n", - "\n", - "异常处理理论上有两种基本模型。Java 支持终止模型(它是 Java 和 C++所支持的模型)。在这种模型中,将假设错误非常严重,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。\n", - "\n", - "另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用 Java 实现类似恢复的行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者,把 try 块放在 while 循环里,这样就不断地进入 try 块,直到得到满意的结果。\n", - "\n", - "在过去,使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。\n", - "\n", - "\n", - "\n", - "## 自定义异常\n", - "\n", - "不必拘泥于 Java 中已有的异常类型。Java 提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。\n", - "\n", - "要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生无参构造器,所以这几乎不用写多少代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/InheritingExceptions.java\n", - "// Creating your own exceptions\n", - "class SimpleException extends Exception {}\n", - "\n", - "public class InheritingExceptions {\n", - " public void f() throws SimpleException {\n", - " System.out.println(\n", - " \"Throw SimpleException from f()\");\n", - " throw new SimpleException();\n", - " }\n", - " public static void main(String[] args) {\n", - " InheritingExceptions sed =\n", - " new InheritingExceptions();\n", - " try {\n", - " sed.f();\n", - " } catch(SimpleException e) {\n", - " System.out.println(\"Caught it!\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Throw SimpleException from f()\n", - "Caught it!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器创建了无参构造器,它将自动调用基类的无参构造器。本例中不会得到像 SimpleException(String) 这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。\n", - "\n", - "本例的结果被打印到了控制台上,本书的输出显示系统正是在控制台上自动地捕获和测试这些结果的。但是,你也许想通过写入 System.err 而将错误发送给标准错误流。通常这比把错误信息输出到 System.out 要好,因为 System.out 也许会被重定向。如果把结果送到 System.err,它就不会随 System.out 一起被重定向,这样更容易被用户注意。\n", - "\n", - "你也可以为异常类创建一个接受字符串参数的构造器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/FullConstructors.java\n", - "class MyException extends Exception {\n", - " MyException() {}\n", - " MyException(String msg) { super(msg); }\n", - "}\n", - "public class FullConstructors {\n", - " public static void f() throws MyException {\n", - " System.out.println(\"Throwing MyException from f()\");\n", - " throw new MyException();\n", - " }\n", - " public static void g() throws MyException {\n", - " System.out.println(\"Throwing MyException from g()\");\n", - " throw new MyException(\"Originated in g()\");\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " f();\n", - " } catch(MyException e) {\n", - " e.printStackTrace(System.out);\n", - " }\n", - " try {\n", - " g();\n", - " } catch(MyException e) {\n", - " e.printStackTrace(System.out);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Throwing MyException from f()\n", - "MyException\n", - " at FullConstructors.f(FullConstructors.java:11)\n", - " at\n", - "FullConstructors.main(FullConstructors.java:19)\n", - "Throwing MyException from g()\n", - "MyException: Originated in g()\n", - " at FullConstructors.g(FullConstructors.java:15)\n", - " at\n", - "FullConstructors.main(FullConstructors.java:24)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。\n", - "\n", - "在异常处理程序中,调用了在 Throwable 类声明(Exception 即从此类继承)的 printStackTrace() 方法。就像从输出中看到的,它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里,信息被发送到了 System.out,并自动地被捕获和显示在输出中。但是,如果调用默认版本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "e.printStackTrace();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "信息就会被输出到标准错误流。\n", - "\n", - "### 异常与记录日志\n", - "\n", - "你可能还想使用 java.util.logging 工具将输出记录到日志中。基本的日志记录功能还是相当简单易懂的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/LoggingExceptions.java\n", - "// An exception that reports through a Logger\n", - "// {ErrorOutputExpected}\n", - "import java.util.logging.*;\n", - "import java.io.*;\n", - "class LoggingException extends Exception {\n", - " private static Logger logger =\n", - " Logger.getLogger(\"LoggingException\");\n", - " LoggingException() {\n", - " StringWriter trace = new StringWriter();\n", - " printStackTrace(new PrintWriter(trace));\n", - " logger.severe(trace.toString());\n", - " }\n", - "}\n", - "public class LoggingExceptions {\n", - " public static void main(String[] args) {\n", - " try {\n", - " throw new LoggingException();\n", - " } catch(LoggingException e) {\n", - " System.err.println(\"Caught \" + e);\n", - " }\n", - " try {\n", - " throw new LoggingException();\n", - " } catch(LoggingException e) {\n", - " System.err.println(\"Caught \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "___[ Error Output ]___\n", - "May 09, 2017 6:07:17 AM LoggingException \n", - "SEVERE: LoggingException\n", - "at\n", - "LoggingExceptions.main(LoggingExceptions.java:20)\n", - "Caught LoggingException\n", - "May 09, 2017 6:07:17 AM LoggingException \n", - "SEVERE: LoggingException\n", - "at\n", - "LoggingExceptions.main(LoggingExceptions.java:25)\n", - "Caught LoggingException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "静态的 Logger.getLogger() 方法创建了一个 String 参数相关联的 Logger 对象(通常与错误相关的包名和类名),这个 Logger 对象会将其输出发送到 System.err。向 Logger 写入的最简单方式就是直接调用与日志记录消息的级别相关联的方法,这里使用的是 severe()。为了产生日志记录消息,我们欲获取异常抛出处的栈轨迹,但是 printStackTrace() 不会默认地产生字符串。为了获取字符串,我们需要使用重载的 printStackTrace() 方法,它接受一个 java.io.PrintWriter 对象作为参数(PrintWriter 会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 一章详细介绍)。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器,那么通过调用 toString() 方法,就可以将输出抽取为一个 String。\n", - "\n", - "尽管由于 LoggingException 将所有记录日志的基础设施都构建在异常自身中,使得它所使用的方式非常方便,并因此不需要客户端程序员的干预就可以自动运行,但是更常见的情形是我们需要捕获和记录其他人编写的异常,因此我们必须在异常处理程序中生成日志消息;" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/LoggingExceptions2.java\n", - "// Logging caught exceptions\n", - "// {ErrorOutputExpected}\n", - "import java.util.logging.*;\n", - "import java.io.*;\n", - "public class LoggingExceptions2 {\n", - " private static Logger logger =\n", - " Logger.getLogger(\"LoggingExceptions2\");\n", - " static void logException(Exception e) {\n", - " StringWriter trace = new StringWriter();\n", - " e.printStackTrace(new PrintWriter(trace));\n", - " logger.severe(trace.toString());\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " throw new NullPointerException();\n", - " } catch(NullPointerException e) {\n", - " logException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "___[ Error Output ]___\n", - "May 09, 2017 6:07:17 AM LoggingExceptions2 logException\n", - "SEVERE: java.lang.NullPointerException\n", - "at\n", - "LoggingExceptions2.main(LoggingExceptions2.java:17)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "还可以更进一步自定义异常,比如加入额外的构造器和成员:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/ExtraFeatures.java\n", - "// Further embellishment of exception classes\n", - "class MyException2 extends Exception {\n", - " private int x;\n", - " MyException2() {}\n", - " MyException2(String msg) { super(msg); }\n", - " MyException2(String msg, int x) {\n", - " super(msg);\n", - " this.x = x;\n", - " }\n", - " public int val() { return x; }\n", - " @Override\n", - " public String getMessage() {\n", - " return \"Detail Message: \"+ x\n", - " + \" \"+ super.getMessage();\n", - " }\n", - "}\n", - "public class ExtraFeatures {\n", - " public static void f() throws MyException2 {\n", - " System.out.println(\n", - " \"Throwing MyException2 from f()\");\n", - " throw new MyException2();\n", - " }\n", - " public static void g() throws MyException2 {\n", - " System.out.println(\n", - " \"Throwing MyException2 from g()\");\n", - " throw new MyException2(\"Originated in g()\");\n", - " }\n", - " public static void h() throws MyException2 {\n", - " System.out.println(\n", - " \"Throwing MyException2 from h()\");\n", - " throw new MyException2(\"Originated in h()\", 47);\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " f();\n", - " } catch(MyException2 e) {\n", - " e.printStackTrace(System.out);\n", - " }\n", - " try {\n", - " g();\n", - " } catch(MyException2 e) {\n", - " e.printStackTrace(System.out);\n", - " }\n", - " try {\n", - " h();\n", - " } catch(MyException2 e) {\n", - " e.printStackTrace(System.out);\n", - " System.out.println(\"e.val() = \" + e.val());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Throwing MyException2 from f()\n", - "MyException2: Detail Message: 0 null\n", - "at ExtraFeatures.f(ExtraFeatures.java:24)\n", - "at ExtraFeatures.main(ExtraFeatures.java:38)\n", - "Throwing MyException2 from g()\n", - "MyException2: Detail Message: 0 Originated in g()\n", - "at ExtraFeatures.g(ExtraFeatures.java:29)\n", - "at ExtraFeatures.main(ExtraFeatures.java:43)\n", - "Throwing MyException2 from h()\n", - "MyException2: Detail Message: 47 Originated in h()\n", - "at ExtraFeatures.h(ExtraFeatures.java:34)\n", - "at ExtraFeatures.main(ExtraFeatures.java:48)\n", - "e.val() = 47" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "新的异常添加了字段 x 以及设定 x 值的构造器和读取数据的方法。此外,还覆盖了 Throwable.\n", - "getMessage() 方法,以产生更详细的信息。对于异常类来说,getMessage() 方法有点类似于 toString() 方法。\n", - "\n", - "既然异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数 Java 库里的异常都是这么用的),所以对异常所添加的其他功能也许根本用不上。\n", - "\n", - "## 异常声明\n", - "\n", - "Java 鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。当然,如果提供了源代码,客户端程序员可以在源代码中查找 throw 语句来获知相关信息,然而程序库通常并不与源代码一起发布。为了预防这样的问题,Java 提供了相应的语法(并强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。这就是异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。\n", - "\n", - "异常说明使用了附加的关键字 throws,后面接一个所有潜在异常类型的列表,所以方法定义可能看起来像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void f() throws TooBig, TooSmall, DivZero { // ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是,要是这样写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void f() { // ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "就表示此方法不会抛出任何异常(除了从 RuntimeException 继承的异常,它们可以在没有异常说明的情况下被抛出,这些将在后面进行讨论)。\n", - "\n", - "代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,Java 在编译时就可以保证一定水平的异常正确性。\n", - "\n", - "不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。\n", - "\n", - "这种在编译时被强制检查的异常称为被检查的异常。\n", - "\n", - "## 捕获所有异常\n", - "\n", - "可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类 Exception,就可以做到这一点(事实上还有其他的基类,但 Exception 是所有编程行为相关的基类):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "catch(Exception e) {\n", - " System.out.println(\"Caught an exception\");\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。\n", - "\n", - "因为 Exception 是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息,不过可以调用它从其基类 Throwable 继承的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "String getMessage()\n", - "String getLocalizedMessage()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "用来获取详细信息,或用本地语言表示的详细信息。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "String toString()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "返回对 Throwable 的简单描述,要是有详细信息的话,也会把它包含在内。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void printStackTrace()\n", - "void printStackTrace(PrintStream)\n", - "void printStackTrace(java.io.PrintWriter)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "打印 Throwable 和 Throwable 的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。其中第一个版本输出到标准错误,后两个版本允许选择要输出的流(在[附录 I/O 流 ]() 中,你将会理解为什么有两种不同的流)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Throwable fillInStackTrace()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。\n", - "\n", - "此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass)也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName)方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。\n", - "\n", - "下面的例子演示了如何使用 Exception 类型的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/ExceptionMethods.java\n", - "// Demonstrating the Exception Methods\n", - "public class ExceptionMethods {\n", - " public static void main(String[] args) {\n", - " try {\n", - " throw new Exception(\"My Exception\");\n", - " } catch(Exception e) {\n", - " System.out.println(\"Caught Exception\");\n", - " System.out.println(\n", - " \"getMessage():\" + e.getMessage());\n", - " System.out.println(\"getLocalizedMessage():\" +\n", - " e.getLocalizedMessage());\n", - " System.out.println(\"toString():\" + e);\n", - " System.out.println(\"printStackTrace():\");\n", - " e.printStackTrace(System.out);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Caught Exception\n", - "getMessage():My Exception\n", - "getLocalizedMessage():My Exception\n", - "toString():java.lang.Exception: My Exception\n", - "printStackTrace():\n", - "java.lang.Exception: My Exception\n", - "at\n", - "ExceptionMethods.main(ExceptionMethods.java:7)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。\n", - "\n", - "### 多重捕获\n", - "\n", - "如果有一组具有相同基类的异常,你想使用同一方式进行捕获,那你直接 catch 它们的基类型。但是,如果这些异常没有共同的基类型,在 Java 7 之前,你必须为每一个类型编写一个 catch:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/SameHandler.java\n", - "class EBase1 extends Exception {}\n", - "class Except1 extends EBase1 {}\n", - "class EBase2 extends Exception {}\n", - "class Except2 extends EBase2 {}\n", - "class EBase3 extends Exception {}\n", - "class Except3 extends EBase3 {}\n", - "class EBase4 extends Exception {}\n", - "class Except4 extends EBase4 {}\n", - "\n", - "public class SameHandler {\n", - " void x() throws Except1, Except2, Except3, Except4 {}\n", - " void process() {}\n", - " void f() {\n", - " try {\n", - " x();\n", - " } catch(Except1 e) {\n", - " process();\n", - " } catch(Except2 e) {\n", - " process();\n", - " } catch(Except3 e) {\n", - " process();\n", - " } catch(Except4 e) {\n", - " process();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过 Java 7 的多重捕获机制,你可以使用“或”将不同类型的异常组合起来,只需要一行 catch 语句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/MultiCatch.java\n", - "public class MultiCatch {\n", - " void x() throws Except1, Except2, Except3, Except4 {}\n", - " void process() {}\n", - " void f() {\n", - " try {\n", - " x();\n", - " \n", - " } catch(Except1 | Except2 | Except3 | Except4 e) {\n", - " process();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "或者以其他的组合方式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/MultiCatch2.java\n", - "public class MultiCatch2 {\n", - " void x() throws Except1, Except2, Except3, Except4 {}\n", - " void process1() {}\n", - " void process2() {}\n", - " void f() {\n", - " try {\n", - " x();\n", - " } catch(Except1 | Except2 e) {\n", - " process1();\n", - " } catch(Except3 | Except4 e) {\n", - " process2();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这对书写更整洁的代码很有帮助。\n", - "\n", - "### 栈轨迹\n", - "\n", - "printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每一个元素都表示栈中的一桢。元素 0 是栈顶元素,并且是调用序列中的最后一个方法调用(这个 Throwable 被创建和抛出之处)。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。下面的程序是一个简单的演示示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/WhoCalled.java\n", - "// Programmatic access to stack trace information\n", - "public class WhoCalled {\n", - " static void f() {\n", - "// Generate an exception to fill in the stack trace\n", - " try {\n", - " throw new Exception();\n", - " } catch(Exception e) {\n", - " for(StackTraceElement ste : e.getStackTrace())\n", - " System.out.println(ste.getMethodName());\n", - " }\n", - " }\n", - " static void g() { f(); }\n", - " static void h() { g(); }\n", - " public static void main(String[] args) {\n", - " f();\n", - " System.out.println(\"*******\");\n", - " g();\n", - " System.out.println(\"*******\");\n", - " h();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f\n", - "main\n", - "*******\n", - "f\n", - "g\n", - "main\n", - "*******\n", - "f\n", - "g\n", - "h\n", - "main" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,我们只打印了方法名,但实际上还可以打印整个 StackTraceElement,它包含其他附加的信息。\n", - "\n", - "### 重新抛出异常\n", - "\n", - "有时希望把刚捕获的异常重新抛出,尤其是在使用 Exception 捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "catch(Exception e) {\n", - " System.out.println(\"An exception was thrown\");\n", - " throw e;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "重抛异常会把异常抛给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。\n", - "\n", - "如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 filInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/Rethrowing.java\n", - "// Demonstrating fillInStackTrace()\n", - "public class Rethrowing {\n", - " public static void f() throws Exception {\n", - " System.out.println(\n", - " \"originating the exception in f()\");\n", - " throw new Exception(\"thrown from f()\");\n", - " }\n", - " public static void g() throws Exception {\n", - " try {\n", - " f();\n", - " } catch(Exception e) {\n", - " System.out.println(\n", - " \"Inside g(), e.printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " throw e;\n", - " }\n", - " }\n", - " public static void h() throws Exception {\n", - " try {\n", - " f();\n", - " } catch(Exception e) {\n", - " System.out.println(\n", - " \"Inside h(), e.printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " throw (Exception)e.fillInStackTrace();\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " g();\n", - " } catch(Exception e) {\n", - " System.out.println(\"main: printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " }\n", - " try {\n", - " h();\n", - " } catch(Exception e) {\n", - " System.out.println(\"main: printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "originating the exception in f()\n", - "Inside g(), e.printStackTrace()\n", - "java.lang.Exception: thrown from f()\n", - "at Rethrowing.f(Rethrowing.java:8)\n", - "at Rethrowing.g(Rethrowing.java:12)\n", - "at Rethrowing.main(Rethrowing.java:32)\n", - "main: printStackTrace()\n", - "java.lang.Exception: thrown from f()\n", - "at Rethrowing.f(Rethrowing.java:8)\n", - "at Rethrowing.g(Rethrowing.java:12)\n", - "at Rethrowing.main(Rethrowing.java:32)\n", - "originating the exception in f()\n", - "Inside h(), e.printStackTrace()\n", - "java.lang.Exception: thrown from f()\n", - "at Rethrowing.f(Rethrowing.java:8)\n", - "at Rethrowing.h(Rethrowing.java:22)\n", - "at Rethrowing.main(Rethrowing.java:38)\n", - "main: printStackTrace()\n", - "java.lang.Exception: thrown from f()\n", - "at Rethrowing.h(Rethrowing.java:27)\n", - "at Rethrowing.main(Rethrowing.java:38)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "调用 fillInStackTrace() 的那一行就成了异常的新发生地了。\n", - "\n", - "有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用 filInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/RethrowNew.java\n", - "// Rethrow a different object from the one you caught\n", - "class OneException extends Exception {\n", - " OneException(String s) { super(s); }\n", - "}\n", - "class TwoException extends Exception {\n", - " TwoException(String s) { super(s); }\n", - "}\n", - "public class RethrowNew {\n", - " public static void f() throws OneException {\n", - " System.out.println(\n", - " \"originating the exception in f()\");\n", - " throw new OneException(\"thrown from f()\");\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " try {\n", - " f();\n", - " } catch(OneException e) {\n", - " System.out.println(\n", - " \"Caught in inner try, e.printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " throw new TwoException(\"from inner try\");\n", - " }\n", - " } catch(TwoException e) {\n", - " System.out.println(\n", - " \"Caught in outer try, e.printStackTrace()\");\n", - " e.printStackTrace(System.out);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "originating the exception in f()\n", - "Caught in inner try, e.printStackTrace()\n", - "OneException: thrown from f()\n", - "at RethrowNew.f(RethrowNew.java:16)\n", - "at RethrowNew.main(RethrowNew.java:21)\n", - "Caught in outer try, e.printStackTrace()\n", - "TwoException: from inner try\n", - "at RethrowNew.main(RethrowNew.java:26)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最后那个异常仅知道自己来自 main(),而对 f() 一无所知。\n", - "\n", - "永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。\n", - "\n", - "### 精准的重新抛出异常\n", - "\n", - "在 Java 7 之前,如果遇到异常,则只能重新抛出该类型的异常。这导致在 Java 7 中修复的代码不精确。所以在 Java 7 之前,这无法编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class BaseException extends Exception {}\n", - "class DerivedException extends BaseException {}\n", - "\n", - "public class PreciseRethrow {\n", - " void catcher() throws DerivedException {\n", - " try {\n", - " throw new DerivedException();\n", - " } catch(BaseException e) {\n", - " throw e;\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 catch 捕获了一个 BaseException,编译器强迫你声明 catcher() 抛出 BaseException,即使它实际上抛出了更具体的 DerivedException。从 Java 7 开始,这段代码就可以编译,这是一个很小但很有用的修复。\n", - "\n", - "### 异常链\n", - "\n", - "常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在 JDK1.4 以前,程序员必须自己编写代码来保存原始异常的信息。现在所有 Throwable 的子类在构造器中都可以接受一个 cause(因由)对象作为参数。这个 cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。\n", - "\n", - "有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause0 方法而不是构造器。\n", - "\n", - "下面的例子能让你在运行时动态地向 DymamicFields 对象添加字段:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/DynamicFields.java\n", - "// A Class that dynamically adds fields to itself to\n", - "// demonstrate exception chaining\n", - "class DynamicFieldsException extends Exception {}\n", - "public class DynamicFields {\n", - " private Object[][] fields;\n", - " public DynamicFields(int initialSize) {\n", - " fields = new Object[initialSize][2];\n", - " for(int i = 0; i < initialSize; i++)\n", - " fields[i] = new Object[] { null, null };\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " StringBuilder result = new StringBuilder();\n", - " for(Object[] obj : fields) {\n", - " result.append(obj[0]);\n", - " result.append(\": \");\n", - " result.append(obj[1]);\n", - " result.append(\"\\n\");\n", - " }\n", - " return result.toString();\n", - " }\n", - " private int hasField(String id) {\n", - " for(int i = 0; i < fields.length; i++)\n", - " if(id.equals(fields[i][0]))\n", - " return i;\n", - " return -1;\n", - " }\n", - " private int getFieldNumber(String id)\n", - " throws NoSuchFieldException {\n", - " int fieldNum = hasField(id);\n", - " if(fieldNum == -1)\n", - " throw new NoSuchFieldException();\n", - " return fieldNum;\n", - " }\n", - " private int makeField(String id) {\n", - " for(int i = 0; i < fields.length; i++)\n", - " if(fields[i][0] == null) {\n", - " fields[i][0] = id;\n", - " return i;\n", - " }\n", - "// No empty fields. Add one:\n", - " Object[][] tmp = new Object[fields.length + 1][2];\n", - " for(int i = 0; i < fields.length; i++)\n", - " tmp[i] = fields[i];\n", - " for(int i = fields.length; i < tmp.length; i++)\n", - " tmp[i] = new Object[] { null, null };\n", - " fields = tmp;\n", - "// Recursive call with expanded fields:\n", - " return makeField(id);\n", - " }\n", - " public Object\n", - " getField(String id) throws NoSuchFieldException {\n", - " return fields[getFieldNumber(id)][1];\n", - " }\n", - " public Object setField(String id, Object value)\n", - " throws DynamicFieldsException {\n", - " if(value == null) {\n", - "// Most exceptions don't have a \"cause\"\n", - "// constructor. In these cases you must use\n", - "// initCause(), available in all\n", - "// Throwable subclasses.\n", - " DynamicFieldsException dfe =\n", - " new DynamicFieldsException();\n", - " dfe.initCause(new NullPointerException());\n", - " throw dfe;\n", - " }\n", - " int fieldNumber = hasField(id);\n", - " if(fieldNumber == -1)\n", - " fieldNumber = makeField(id);\n", - " Object result = null;\n", - " try {\n", - " result = getField(id); // Get old value\n", - " } catch(NoSuchFieldException e) {\n", - "// Use constructor that takes \"cause\":\n", - " throw new RuntimeException(e);\n", - " }\n", - " fields[fieldNumber][1] = value;\n", - " return result;\n", - " }\n", - " public static void main(String[] args) {\n", - " DynamicFields df = new DynamicFields(3);\n", - " System.out.println(df);\n", - " try {\n", - " df.setField(\"d\", \"A value for d\");\n", - " df.setField(\"number\", 47);\n", - " df.setField(\"number2\", 48);\n", - " System.out.println(df);\n", - " df.setField(\"d\", \"A new value for d\");\n", - " df.setField(\"number3\", 11);\n", - " System.out.println(\"df: \" + df);\n", - " System.out.println(\"df.getField(\\\"d\\\") : \"\n", - " + df.getField(\"d\"));\n", - " Object field =\n", - " df.setField(\"d\", null); // Exception\n", - " } catch(NoSuchFieldException |\n", - " DynamicFieldsException e) {\n", - " e.printStackTrace(System.out);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "null: null\n", - "null: null\n", - "null: null\n", - "d: A value for d\n", - "number: 47\n", - "number2: 48\n", - "df: d: A new value for d\n", - "number: 47\n", - "number2: 48\n", - "number3: 11\n", - "df.getField(\"d\") : A new value for d\n", - "DynamicFieldsException\n", - "at\n", - "DynamicFields.setField(DynamicFields.java:65)\n", - "at DynamicFields.main(DynamicFields.java:97)\n", - "Caused by: java.lang.NullPointerException\n", - "at\n", - "DynamicFields.setField(DynamicFields.java:67)\n", - "... 1 more" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个 DynamicFields 对象都含有一个数组,其元素是“成对的对象”。第一个对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。当创建对象的时候,要合理估计一下需要多少字段。当调用 setField() 方法的时候,它将试图通过标识修改已有字段值,否则就建一个新的字段,并把值放入。如果空间不够了,将建立一个更长的数组,并把原来数组的元素复制进去。如果你试图为字段设置一个空值,将抛出一个 DynamicFieldsException 异常,它是通过使用 initCause() 方法把 NullPointerException 对象插入而建立的。\n", - "\n", - "至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField0 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。\n", - "\n", - "你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。\n", - "\n", - "主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,并结合“或(|)”符号。此 Java 7 功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。\n", - "\n", - "\n", - "\n", - "## Java 标准异常\n", - "\n", - "Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从 Throwable 继承而得到的类型):Error 用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception。要想对异常有全面的了解,最好去浏览一下 HTML 格式的 Java 文档(可以从 java.sun.com 下载)。为了对不同的异常有个感性的认识,这么做是值得的。但很快你就会发现,这些异常除了名称外其实都差不多。同时,Java 中异常的数目在持续增加,所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说,关键是理解概念以及如何使用。\n", - "\n", - "异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从 java.io.IOException 继承而来的。\n", - "\n", - "### 特例:RuntimeException\n", - "\n", - "在本章的第一个例子中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(t == null)\n", - " throw new NullPointerException();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果必须对传递给方法的每个引用都检查其是否为 null(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。\n", - "\n", - "属于运行时异常的类型有很多,它们会自动被 java 虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。\n", - "\n", - "RuntimeException 代表的是编程错误:\n", - "\n", - "1. 无法预料的错误。比如从你控制范围之外传递进来的 null 引用。\n", - "2. 作为程序员,应该在代码中进行检查的错误。(比如对于 ArrayIndexOutOfBoundsException,就得注意一下数组的大小了。)在一个地方发生的异常,常常会在另一个地方导致错误。\n", - "\n", - "在这些情况下使用异常很有好处,它们能给调试带来便利。\n", - "\n", - "如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,RuntimeException 类型的异常也许会穿越所有的执行路径直达 main() 方法,而不会被捕获。要明白到底发生了什么,可以试试下面的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/NeverCaught.java\n", - "// Ignoring RuntimeExceptions\n", - "// {ThrowsException}\n", - "public class NeverCaught {\n", - " static void f() {\n", - " throw new RuntimeException(\"From f()\");\n", - " }\n", - " static void g() {\n", - " f();\n", - " }\n", - " public static void main(String[] args) {\n", - " g();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "___[ Error Output ]___\n", - "Exception in thread \"main\" java.lang.RuntimeException:\n", - "From f()\n", - "at NeverCaught.f(NeverCaught.java:7)\n", - "at NeverCaught.g(NeverCaught.java:10)\n", - "at NeverCaught.main(NeverCaught.java:13)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果 RuntimeException 没有被捕获而直达 main(),那么在程序退出前将调用异常的 printStackTrace() 方法。\n", - "\n", - "你会发现,RuntimeException(或任何从它继承的异常)是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了 System.err。\n", - "\n", - "请务必记住:只能在代码中忽略 RuntimeException(及其子类)类型的异常,因为所有受检查类型异常的处理都是由编译器强制实施的。\n", - "\n", - "值得注意的是:不应把 Java 的异常处理机制当成是单一用途的工具。是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于发现某些编译器无法检测到的编程错误,也是非常重要的。\n", - "\n", - "\n", - "\n", - "## 使用 finally 进行清理\n", - "\n", - "有一些代码片段,可能会希望无论 try 块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况(因为回收由垃圾回收器完成),为了达到这个效果,可以在异常处理程序后面加上 finally 子句。完整的异常处理程序看起来像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - "// The guarded region: Dangerous activities\n", - "// that might throw A, B, or C\n", - "} catch(A a1) {\n", - "// Handler for situation A\n", - "} catch(B b1) {\n", - "// Handler for situation B\n", - "} catch(C c1) {\n", - "// Handler for situation C\n", - "} finally {\n", - "// Activities that happen every time\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了证明 finally 子句总能运行,可以试试下面这个程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/FinallyWorks.java\n", - "// The finally clause is always executed\n", - "class ThreeException extends Exception {}\n", - "public class FinallyWorks {\n", - " static int count = 0;\n", - " public static void main(String[] args) {\n", - " while(true) {\n", - " try {\n", - "\t\t\t\t// Post-increment is zero first time:\n", - " if(count++ == 0)\n", - " throw new ThreeException();\n", - " System.out.println(\"No exception\");\n", - " } catch(ThreeException e) {\n", - " System.out.println(\"ThreeException\");\n", - " } finally {\n", - " System.out.println(\"In finally clause\");\n", - " if(count == 2) break; // out of \"while\"\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ThreeException\n", - "In finally clause\n", - "No exception\n", - "In finally clause" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以从输出中发现,无论异常是否被抛出,finally 子句总能被执行。这个程序也给了我们一些思路,当 Java 中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把 try 块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个 static 类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。\n", - "\n", - "### finally 用来做什么?\n", - "\n", - "对于没有垃圾回收和析构函数自动调用机制的语言来说,finally 非常重要。它能使程序员保证:无论 try 块里发生了什么,内存总能得到释放。但 Java 有垃圾回收机制,所以内存释放不再是问题。而且,Java 也没有析构函数可供调用。那么,Java 在什么情况下才能用到 finally 呢?\n", - "\n", - "当要把除内存之外的资源恢复到它们的初始状态时,就要用到 finally 子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关,如下面例子所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/Switch.java\n", - "public class Switch {\n", - " private boolean state = false;\n", - " public boolean read() { return state; }\n", - " public void on() {\n", - " state = true;\n", - " System.out.println(this);\n", - " }\n", - " public void off() {\n", - " state = false;\n", - " System.out.println(this);\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return state ? \"on\" : \"off\";\n", - " }\n", - "}\n", - "// exceptions/OnOffException1.java\n", - "public class OnOffException1 extends Exception {}\n", - "// exceptions/OnOffException2.java\n", - "public class OnOffException2 extends Exception {}\n", - "// exceptions/OnOffSwitch.java\n", - "// Why use finally?\n", - "public class OnOffSwitch {\n", - " private static Switch sw = new Switch();\n", - " public static void f()\n", - " throws OnOffException1, OnOffException2 {}\n", - " public static void main(String[] args) {\n", - " try {\n", - " sw.on();\n", - "\t\t\t// Code that can throw exceptions...\n", - " f();\n", - " sw.off();\n", - " } catch(OnOffException1 e) {\n", - " System.out.println(\"OnOffException1\");\n", - " sw.off();\n", - " } catch(OnOffException2 e) {\n", - " System.out.println(\"OnOffException2\");\n", - " sw.off();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "on\n", - "off" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "程序的目的是要确保 main() 结束的时候开关必须是关闭的,所以在每个 try 块和异常处理程序的末尾都加入了对 sw.off() 方法的调用。但也可能有这种情况:异常被抛出,但没被处理程序捕获,这时 sw.off() 就得不到调用。但是有了 finally,只要把 try 块中的清理代码移放在一处即可:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/WithFinally.java\n", - "// Finally Guarantees cleanup\n", - "public class WithFinally {\n", - " static Switch sw = new Switch();\n", - " public static void main(String[] args) {\n", - " try {\n", - " sw.on();\n", - "\t\t\t// Code that can throw exceptions...\n", - " OnOffSwitch.f();\n", - " } catch(OnOffException1 e) {\n", - " System.out.println(\"OnOffException1\");\n", - " } catch(OnOffException2 e) {\n", - " System.out.println(\"OnOffException2\");\n", - " } finally {\n", - " sw.off();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "on\n", - "off" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里 sw.off() 被移到一处,并且保证在任何情况下都能得到执行。\n", - "\n", - "甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行 finally 子句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/AlwaysFinally.java\n", - "// Finally is always executed\n", - "class FourException extends Exception {}\n", - "public class AlwaysFinally {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Entering first try block\");\n", - " try {\n", - " System.out.println(\"Entering second try block\");\n", - " try {\n", - " throw new FourException();\n", - " } finally {\n", - " System.out.println(\"finally in 2nd try block\");\n", - " }\n", - " } catch(FourException e) {\n", - " System.out.println(\n", - " \"Caught FourException in 1st try block\");\n", - " } finally {\n", - " System.out.println(\"finally in 1st try block\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Entering first try block\n", - "Entering second try block\n", - "finally in 2nd try block\n", - "Caught FourException in 1st try block\n", - "finally in 1st try block" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当涉及 break 和 continue 语句的时候,finally 子句也会得到执行。请注意,如果把 finally 子句和带标签的 break 及 continue 配合使用,在 Java 里就没必要使用 goto 语句了。\n", - "\n", - "### 在 return 中使用 finally\n", - "\n", - "因为 finally 子句总是会执行,所以可以从一个方法内的多个点返回,仍然能保证重要的清理工作会执行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/MultipleReturns.java\n", - "public class MultipleReturns {\n", - " public static void f(int i) {\n", - " System.out.println(\n", - " \"Initialization that requires cleanup\");\n", - " try {\n", - " System.out.println(\"Point 1\");\n", - " if(i == 1) return;\n", - " System.out.println(\"Point 2\");\n", - " if(i == 2) return;\n", - " System.out.println(\"Point 3\");\n", - " if(i == 3) return;\n", - " System.out.println(\"End\");\n", - " return;\n", - " } finally {\n", - " System.out.println(\"Performing cleanup\");\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " for(int i = 1; i <= 4; i++)\n", - " f(i);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Initialization that requires cleanup\n", - "Point 1\n", - "Performing cleanup\n", - "Initialization that requires cleanup\n", - "Point 1\n", - "Point 2\n", - "Performing cleanup\n", - "Initialization that requires cleanup\n", - "Point 1\n", - "Point 2\n", - "Point 3\n", - "Performing cleanup\n", - "Initialization that requires cleanup\n", - "Point 1\n", - "Point 2\n", - "Point 3\n", - "End\n", - "Performing cleanup" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从输出中可以看出,从何处返回无关紧要,finally 子句永远会执行。\n", - "\n", - "### 缺憾:异常丢失\n", - "\n", - "遗憾的是,Java 的异常实现也有瑕疵。异常作为程序出错的标志,决不应该被忽略,但它还是有可能被轻易地忽略。用某些特殊的方式使用 finally 子句,就会发生这种情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/LostMessage.java\n", - "// How an exception can be lost\n", - "class VeryImportantException extends Exception {\n", - " @Override\n", - " public String toString() {\n", - " return \"A very important exception!\";\n", - " }\n", - "}\n", - "class HoHumException extends Exception {\n", - " @Override\n", - " public String toString() {\n", - " return \"A trivial exception\";\n", - " }\n", - "}\n", - "public class LostMessage {\n", - " void f() throws VeryImportantException {\n", - " throw new VeryImportantException();\n", - " }\n", - " void dispose() throws HoHumException {\n", - " throw new HoHumException();\n", - " }\n", - " public static void main(String[] args) {\n", - " try {\n", - " LostMessage lm = new LostMessage();\n", - " try {\n", - " lm.f();\n", - " } finally {\n", - " lm.dispose();\n", - " }\n", - " } catch(VeryImportantException | HoHumException e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "A trivial exception" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从输出中可以看到,VeryImportantException 不见了,它被 finally 子句里的 HoHumException 所取代。这是相当严重的缺陷,因为异常可能会以一种比前面例子所示更微妙和难以察觉的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上例中的 dispose() 方法,全部打包放到 try-catch 子句里面)。\n", - "\n", - "一种更加简单的丢失异常的方式是从 finally 子句中返回:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/ExceptionSilencer.java\n", - "public class ExceptionSilencer {\n", - " public static void main(String[] args) {\n", - " try {\n", - " throw new RuntimeException();\n", - " } finally {\n", - " // Using 'return' inside the finally block\n", - " // will silence any thrown exception.\n", - " return;\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果运行这个程序,就会看到即使方法里抛出了异常,它也不会产生任何输出。\n", - "\n", - "\n", - "\n", - "## 异常限制\n", - "\n", - "当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,若当基类使用的代码应用到其派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。\n", - "\n", - "下面例子演示了这种(在编译时)施加在异常上面的限制:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/StormyInning.java\n", - "// Overridden methods can throw only the exceptions\n", - "// specified in their base-class versions, or exceptions\n", - "// derived from the base-class exceptions\n", - "class BaseballException extends Exception {}\n", - "class Foul extends BaseballException {}\n", - "class Strike extends BaseballException {}\n", - "abstract class Inning {\n", - " Inning() throws BaseballException {}\n", - " public void event() throws BaseballException {\n", - "// Doesn't actually have to throw anything\n", - " }\n", - " public abstract void atBat() throws Strike, Foul;\n", - " public void walk() {} // Throws no checked exceptions\n", - "}\n", - "class StormException extends Exception {}\n", - "class RainedOut extends StormException {}\n", - "class PopFoul extends Foul {}\n", - "interface Storm {\n", - " void event() throws RainedOut;\n", - " void rainHard() throws RainedOut;\n", - "}\n", - "public class StormyInning extends Inning implements Storm {\n", - " // OK to add new exceptions for constructors, but you\n", - "// must deal with the base constructor exceptions:\n", - " public StormyInning()\n", - " throws RainedOut, BaseballException {}\n", - " public StormyInning(String s)\n", - " throws BaseballException {}\n", - " // Regular methods must conform to base class:\n", - "//- void walk() throws PopFoul {} //Compile error\n", - "// Interface CANNOT add exceptions to existing\n", - "// methods from the base class:\n", - "//- public void event() throws RainedOut {}\n", - "// If the method doesn't already exist in the\n", - "// base class, the exception is OK:\n", - " @Override\n", - " public void rainHard() throws RainedOut {}\n", - " // You can choose to not throw any exceptions,\n", - "// even if the base version does:\n", - " @Override\n", - " public void event() {}\n", - " // Overridden methods can throw inherited exceptions:\n", - " @Override\n", - " public void atBat() throws PopFoul {}\n", - " public static void main(String[] args) {\n", - " try {\n", - " StormyInning si = new StormyInning();\n", - " si.atBat();\n", - " } catch(PopFoul e) {\n", - " System.out.println(\"Pop foul\");\n", - " } catch(RainedOut e) {\n", - " System.out.println(\"Rained out\");\n", - " } catch(BaseballException e) {\n", - " System.out.println(\"Generic baseball exception\");\n", - " }\n", - "// Strike not thrown in derived version.\n", - " try {\n", - "// What happens if you upcast?\n", - " Inning i = new StormyInning();\n", - " i.atBat();\n", - "// You must catch the exceptions from the\n", - "// base-class version of the method:\n", - " } catch(Strike e) {\n", - " System.out.println(\"Strike\");\n", - " } catch(Foul e) {\n", - " System.out.println(\"Foul\");\n", - " } catch(RainedOut e) {\n", - " System.out.println(\"Rained out\");\n", - " } catch(BaseballException e) {\n", - " System.out.println(\"Generic baseball exception\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。\n", - "\n", - "接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。\n", - "\n", - "异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。\n", - "\n", - "派生类构造器不能捕获基类构造器抛出的异常。\n", - "\n", - "StormyInning.walk() 不能通过编译是因为它抛出了异常,而 Inning.walk() 并没有声明此异常。如果编译器允许这么做的话,就可以在调用 Inning.walk() 的时候不用做异常处理了,而且当把它替换成 Inning 的派生类的对象时,这个方法就有可能会抛出异常,于是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。\n", - "\n", - "覆盖后的 event() 方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在 atBat() 身上,它抛出的是 PopFoul,这个异常是继承自“会被基类的 atBat() 抛出”的 Foul,这样,如果你写的代码是同 Inning 打交道,并且调用了它的 atBat() 的话,那么肯定能捕获 Foul,而 PopFoul 是由 Foul 派生出来的,因此异常处理程序也能捕获 PopFoul。\n", - "\n", - "最后一个值得注意的地方是 main()。这里可以看到,如果处理的刚好是 Stormylnning 对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。\n", - "\n", - "尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。\n", - "\n", - "\n", - "\n", - "## 构造器\n", - "\n", - "有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?\"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。\n", - "\n", - "你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finaly 子句中却是要被清理的。\n", - "\n", - "在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类(将在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中讨论),这些类的基本用法很简单,你应该很容易明白:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/InputFile.java\n", - "// Paying attention to exceptions in constructors\n", - "import java.io.*;\n", - "public class InputFile {\n", - " private BufferedReader in;\n", - " public InputFile(String fname) throws Exception {\n", - " try {\n", - " in = new BufferedReader(new FileReader(fname));\n", - " // Other code that might throw exceptions\n", - " } catch(FileNotFoundException e) {\n", - " System.out.println(\"Could not open \" + fname);\n", - " // Wasn't open, so don't close it\n", - " throw e;\n", - " } catch(Exception e) {\n", - " // All other exceptions must close it\n", - " try {\n", - " in.close();\n", - " } catch(IOException e2) {\n", - " System.out.println(\"in.close() unsuccessful\");\n", - " }\n", - " throw e; // Rethrow\n", - " } finally {\n", - " // Don't close it here!!!\n", - " }\n", - " }\n", - " public String getLine() {\n", - " String s;\n", - " try {\n", - " s = in.readLine();\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(\"readLine() failed\");\n", - " }\n", - " return s;\n", - " }\n", - " public void dispose() {\n", - " try {\n", - " in.close();\n", - " System.out.println(\"dispose() successful\");\n", - " } catch(IOException e2) {\n", - " throw new RuntimeException(\"in.close() failed\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "InputFile 的构造器接受字符串作为参数,该字符串表示所要打开的文件名。在 try 块中,会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大,但可以用它来建立 BufferedReader 对象。注意,使用 InputFile 的好处之一是把两步操作合而为一。\n", - "\n", - "如果 FileReader 的构造器失败了,将抛出 FileNotFoundException 异常。对于这个异常,并不需要关闭文件,因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件,因为在它们捕获到异常之时,文件已经打开了(当然,如果还有其他方法能抛出 FileNotFoundException,这个方法就显得有些投机取巧了。这时,通常必须把这些方法分别放到各自的 try 块里),close() 方法也可能会抛出异常,所以尽管它已经在另一个 catch 子句块里了,还是要再用一层 try-catch,这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后,异常被重新抛出,对于构造器而言这么做是很合适的,因为你总不希望去误导调用方,让他认为“这个对象已经创建完毕,可以使用了”。\n", - "\n", - "在本例中,由于 finally 会在每次完成构造器之后都执行一遍,因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。\n", - "\n", - "getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine(),但是这个异常已经在方法内得到处理,因此 getLine() 不会抛出任何异常。在设计异常时有一个问题:应该把异常全部放在这一层处理;还是先处理一部分,然后再向上层抛出相同的(或新的)异常;又或者是不做任何处理直接向上层抛出。如果用法恰当的话,直接向上层抛出的确能简化编程。在这里,getLine() 方法将异常转换为 RuntimeException,表示一个编程错误。\n", - "\n", - "用户在不再需要 InputFile 对象时,就必须调用 dispose() 方法,这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源(比如文件句柄),在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面,但我在 [封装](./Housekeeping.md) 讲过,你不知道 finalize() 会不会被调用(即使能确定它将被调用,也不知道在什么时候调用),这也是 Java 的缺陷:除了内存的清理之外,所有的清理都不会自动发生。所以必须告诉客户端程序员,这是他们的责任。\n", - "\n", - "对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的 try 子句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/Cleanup.java\n", - "// Guaranteeing proper cleanup of a resource\n", - "public class Cleanup {\n", - " public static void main(String[] args) {\n", - " try {\n", - " InputFile in = new InputFile(\"Cleanup.java\");\n", - " try {\n", - " String s;\n", - " int i = 1;\n", - " while((s = in.getLine()) != null)\n", - " ; // Perform line-by-line processing here...\n", - " } catch(Exception e) {\n", - " System.out.println(\"Caught Exception in main\");\n", - " e.printStackTrace(System.out);\n", - " } finally {\n", - " in.dispose();\n", - " }\n", - " } catch(Exception e) {\n", - " System.out.println(\n", - " \"InputFile construction failed\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dispose() successful" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请仔细观察这里的逻辑:对 InputFile 对象的构造在其自己的 try 语句块中有效,如果构造失败,将进入外部的 catch 子句,而 dispose() 方法不会被调用。但是,如果构造成功,我们肯定想确保对象能够被清理,因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中,finally 子句在构造失败时是不会执行的,而在构造成功时将总是执行。\n", - "\n", - "这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个 try-finally 语句块:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/CleanupIdiom.java\n", - "// Disposable objects must be followed by a try-finally\n", - "class NeedsCleanup { // Construction can't fail\n", - " private static long counter = 1;\n", - " private final long id = counter++;\n", - " public void dispose() {\n", - " System.out.println(\n", - " \"NeedsCleanup \" + id + \" disposed\");\n", - " }\n", - "}\n", - "class ConstructionException extends Exception {}\n", - "class NeedsCleanup2 extends NeedsCleanup {\n", - " // Construction can fail:\n", - " NeedsCleanup2() throws ConstructionException {}\n", - "}\n", - "public class CleanupIdiom {\n", - " public static void main(String[] args) {\n", - " // [1]:\n", - " NeedsCleanup nc1 = new NeedsCleanup();\n", - " try {\n", - " // ...\n", - " } finally {\n", - " nc1.dispose();\n", - " }\n", - " // [2]:\n", - " // If construction cannot fail,\n", - " // you can group objects:\n", - " NeedsCleanup nc2 = new NeedsCleanup();\n", - " NeedsCleanup nc3 = new NeedsCleanup();\n", - " try {\n", - " // ...\n", - " } finally {\n", - " nc3.dispose(); // Reverse order of construction\n", - " nc2.dispose();\n", - " }\n", - " // [3]:\n", - " // If construction can fail you must guard each one:\n", - " try {\n", - " NeedsCleanup2 nc4 = new NeedsCleanup2();\n", - " try {\n", - " NeedsCleanup2 nc5 = new NeedsCleanup2();\n", - " try {\n", - " // ...\n", - " } finally {\n", - " nc5.dispose();\n", - " }\n", - " } catch(ConstructionException e) { // nc5 const.\n", - " System.out.println(e);\n", - " } finally {\n", - " nc4.dispose();\n", - " }\n", - " } catch(ConstructionException e) { // nc4 const.\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NeedsCleanup 1 disposed\n", - "NeedsCleanup 3 disposed\n", - "NeedsCleanup 2 disposed\n", - "NeedsCleanup 5 disposed\n", - "NeedsCleanup 4 disposed" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- [1] 相当简单,遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败,就不需要任何 catch。\n", - "- [2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。\n", - "- [3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。\n", - "\n", - "本例中的异常处理的棘手程度,对于应该创建不能失败的构造器是一个有力的论据,尽管这么做并非总是可行。\n", - "\n", - "注意,如果 dispose() 可以抛出异常,那么你可能需要额外的 try 语句块。基本上,你应该仔细考虑所有的可能性,并确保正确处理每一种情况。\n", - "\n", - "\n", - "\n", - "## Try-With-Resources 用法\n", - "\n", - "上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。\n", - "\n", - "InputFile.java 是一个特别棘手的情况,因为文件被打开(包含所有可能的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常,因此可以调用 dispose() 方法。这是一个很好的例子,因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码(当然,你经常会遇到这种你无法选择的代码设计的情况,因此你必须仍然理解它)。\n", - "\n", - "InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/InputFile2.java\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "public class InputFile2 {\n", - " private String fname;\n", - "\n", - " public InputFile2(String fname) {\n", - " this.fname = fname;\n", - " }\n", - "\n", - " public Stream getLines() throws IOException {\n", - " return Files.lines(Paths.get(fname));\n", - " }\n", - "\n", - " public static void\n", - " main(String[] args) throws IOException {\n", - " new InputFile2(\"InputFile2.java\").getLines()\n", - " .skip(15)\n", - " .limit(1)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "main(String[] args) throws IOException {" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,getLines() 全权负责打开文件并创建 Stream。\n", - "\n", - "你不能总是轻易地回避这个问题。有时会有以下问题:\n", - "\n", - "1. 需要资源清理\n", - "2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。\n", - "\n", - "一个常见的例子是 jav.io.FileInputstream(将会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中提到)。要正确使用它,你必须编写一些棘手的样板代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/MessyExceptions.java\n", - "import java.io.*;\n", - "public class MessyExceptions {\n", - " public static void main(String[] args) {\n", - " InputStream in = null;\n", - " try {\n", - " in = new FileInputStream(\n", - " new File(\"MessyExceptions.java\"));\n", - " int contents = in.read();\n", - " // Process contents\n", - " } catch(IOException e) {\n", - " // Handle the error\n", - " } finally {\n", - " if(in != null) {\n", - " try {\n", - " in.close();\n", - " } catch(IOException e) {\n", - " // Handle the close() error\n", - " }\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当 finally 子句有自己的 try 块时,感觉事情变得过于复杂。\n", - "\n", - "幸运的是,Java 7 引入了 try-with-resources 语法,它可以非常清楚地简化上面的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/TryWithResources.java\n", - "import java.io.*;\n", - "public class TryWithResources {\n", - " public static void main(String[] args) {\n", - " try(\n", - " InputStream in = new FileInputStream(\n", - " new File(\"TryWithResources.java\"))\n", - " ) {\n", - " int contents = in.read();\n", - " // Process contents\n", - " } catch(IOException e) {\n", - " // Handle the error\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Java 7 之前,try 总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(正常或异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。\n", - "\n", - "它是如何工作的?在 try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.AutoCloseable 接口,这个接口有一个方法:close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/StreamsAreAutoCloseable.java\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "public class StreamsAreAutoCloseable {\n", - " public static void\n", - " main(String[] args) throws IOException{\n", - " try(\n", - " Stream in = Files.lines(\n", - " Paths.get(\"StreamsAreAutoCloseable.java\"));\n", - " PrintWriter outfile = new PrintWriter(\n", - " \"Results.txt\"); // [1]\n", - " ) {\n", - " in.skip(5)\n", - " .limit(1)\n", - " .map(String::toLowerCase)\n", - " .forEachOrdered(outfile::println);\n", - " } // [2]\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- [1] 你在这里可以看到其他的特性:资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的)。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。\n", - "- [2] try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里,IOException 被 main() 方法抛出,所以这里并不需要在 try 后面跟着一个 catch 语句块。\n", - "\n", - "Java 5 中的 Closeable 已经被修改,修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象,都支持了 try-with-resources 特性。\n", - "\n", - "### 揭示细节\n", - "\n", - "为了研究 try-with-resources 的基本机制,我们将创建自己的 AutoCloseable 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/AutoCloseableDetails.java\n", - "class Reporter implements AutoCloseable {\n", - " String name = getClass().getSimpleName();\n", - " Reporter() {\n", - " System.out.println(\"Creating \" + name);\n", - " }\n", - " public void close() {\n", - " System.out.println(\"Closing \" + name);\n", - " }\n", - "}\n", - "class First extends Reporter {}\n", - "class Second extends Reporter {}\n", - "public class AutoCloseableDetails {\n", - " public static void main(String[] args) {\n", - " try(\n", - " First f = new First();\n", - " Second s = new Second()\n", - " ) {\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Creating First\n", - "Creating Second\n", - "Closing Second\n", - "Closing First" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在此配置中,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。\n", - "\n", - "假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/TryAnything.java\n", - "// {WillNotCompile}\n", - "class Anything {}\n", - "public class TryAnything {\n", - " public static void main(String[] args) {\n", - " try(\n", - " Anything a = new Anything()\n", - " ) {\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正如我们所希望和期望的那样,Java 不会让我们这样做,并且出现编译时错误。\n", - "\n", - "如果其中一个构造函数抛出异常怎么办?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/ConstructorException.java\n", - "class CE extends Exception {}\n", - "class SecondExcept extends Reporter {\n", - " SecondExcept() throws CE {\n", - " super();\n", - " throw new CE();\n", - " }\n", - "}\n", - "public class ConstructorException {\n", - " public static void main(String[] args) {\n", - " try(\n", - " First f = new First();\n", - " SecondExcept s = new SecondExcept();\n", - " Second s2 = new Second()\n", - " ) {\n", - " System.out.println(\"In body\");\n", - " } catch(CE e) {\n", - " System.out.println(\"Caught: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Creating First\n", - "Creating SecondExcept\n", - "Closing First\n", - "Caught: CE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在资源规范头中定义了 3 个对象,中间的对象抛出异常。因此,编译器强制我们使用 catch 子句来捕获构造函数异常。这意味着资源规范头实际上被 try 块包围。\n", - "\n", - "正如预期的那样,First 创建时没有发生意外,SecondExcept 在创建期间抛出异常。请注意,不会为 SecondExcept 调用 close(),因为如果构造函数失败,则无法假设你可以安全地对该对象执行任何操作,包括关闭它。由于 SecondExcept 的异常,Second 对象实例 s2 不会被创建,因此也不会有清除事件发生。\n", - "\n", - "如果没有构造函数抛出异常,但你可能会在 try 的主体中获取它们,则再次强制你实现 catch 子句:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/BodyException.java\n", - "class Third extends Reporter {}\n", - "public class BodyException {\n", - " public static void main(String[] args) {\n", - " try(\n", - " First f = new First();\n", - " Second s2 = new Second()\n", - " ) {\n", - " System.out.println(\"In body\");\n", - " Third t = new Third();\n", - " new SecondExcept();\n", - " System.out.println(\"End of body\");\n", - " } catch(CE e) {\n", - " System.out.println(\"Caught: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Creating First\n", - "Creating Second\n", - "In body\n", - "Creating Third\n", - "Creating SecondExcept\n", - "Closing Second\n", - "Closing First\n", - "Caught: CE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意,第 3 个对象永远不会被清除。那是因为它不是在资源规范头中创建的,所以它没有被保护。这很重要,因为 Java 在这里没有以警告或错误的形式提供指导,因此像这样的错误很容易漏掉。实际上,如果依赖某些集成开发环境来自动重写代码,以使用 try-with-resources 特性,那么它们(在撰写本文时)通常只会保护它们遇到的第一个对象,而忽略其余的对象。\n", - "\n", - "最后,让我们看一下抛出异常的 close() 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/CloseExceptions.java\n", - "class CloseException extends Exception {}\n", - "class Reporter2 implements AutoCloseable {\n", - " String name = getClass().getSimpleName();\n", - " Reporter2() {\n", - " System.out.println(\"Creating \" + name);\n", - " }\n", - " public void close() throws CloseException {\n", - " System.out.println(\"Closing \" + name);\n", - " }\n", - "}\n", - "class Closer extends Reporter2 {\n", - " @Override\n", - " public void close() throws CloseException {\n", - " super.close();\n", - " throw new CloseException();\n", - " }\n", - "}\n", - "public class CloseExceptions {\n", - " public static void main(String[] args) {\n", - " try(\n", - " First f = new First();\n", - " Closer c = new Closer();\n", - " Second s = new Second()\n", - " ) {\n", - " System.out.println(\"In body\");\n", - " } catch(CloseException e) {\n", - " System.out.println(\"Caught: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Creating First\n", - "Creating Closer\n", - "Creating Second\n", - "In body\n", - "Closing Second\n", - "Closing Closer\n", - "Closing First\n", - "Caught: CloseException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从技术上讲,我们并没有被迫在这里提供一个 catch 子句;你可以通过 **main() throws CloseException** 的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。\n", - "\n", - "请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer 也是如此。 close() 抛出异常。当你想到它时,这就是你想要发生的事情,但是如果你必须自己编写所有这些逻辑,那么你可能会错过一些错误。想象一下所有代码都在那里,程序员没有考虑清理的所有含义,并且做错了。因此,应始终尽可能使用 try-with-resources。它有助于实现该功能,使得生成的代码更清晰,更易于理解。\n", - "\n", - "\n", - "\n", - "## 异常匹配\n", - "\n", - "抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。\n", - "\n", - "查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/Human.java\n", - "// Catching exception hierarchies\n", - "class Annoyance extends Exception {}\n", - "class Sneeze extends Annoyance {}\n", - "public class Human {\n", - " public static void main(String[] args) {\n", - " // Catch the exact type:\n", - " try {\n", - " throw new Sneeze();\n", - " } catch(Sneeze s) {\n", - " System.out.println(\"Caught Sneeze\");\n", - " } catch(Annoyance a) {\n", - " System.out.println(\"Caught Annoyance\");\n", - " }\n", - " // Catch the base type:\n", - " try {\n", - " throw new Sneeze();\n", - " } catch(Annoyance a) {\n", - " System.out.println(\"Caught Annoyance\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Caught Sneeze\n", - "Caught Annoyance" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sneeze 异常会被第一个匹配的 catch 子句捕获,也就是程序里的第一个。然而如果将这个 catch 子句删掉,只留下 Annoyance 的 catch 子句,该程序仍然能运行,因为这次捕获的是 Sneeze 的基类。换句话说,catch(Annoyance a)会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。\n", - "\n", - "如果把捕获基类的 catch 子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - " throw new Sneeze();\n", - "} catch(Annoyance a) {\n", - " // ...\n", - "} catch(Sneeze s) {\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此时,编译器会发现 Sneeze 的 catch 子句永远得不到执行,因此它会向你报告错误。\n", - "\n", - "\n", - "\n", - "## 其他可选方式\n", - "\n", - "异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”\n", - "发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门\"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。\n", - "\n", - "异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常\"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。\n", - "\n", - "“被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句,这就导致了吞食则有害(harmful if swallowed)的问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - " // ... to do something useful\n", - "} catch(ObligatoryException e) {} // Gulp!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "程序员们只做最简单的事情(包括我自己,在本书第 1 版中也有这个问题),常常是无意中\"吞食”了异常,然而一旦这么做,虽然能通过编译,但除非你记得复查并改正代码,否则异常将会丢失。异常确实发生了,但“吞食”后它却完全消失了。因为编译器强迫你立刻写代码来处理异常,所以这种看起来最简单的方法,却可能是最糟糕的做法。\n", - "\n", - "当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第 2 版中,我在处理程序里通过打印栈轨迹的方法“修补”了这个问题(本章中的很多例子还是使用了这种方法,看起来还是比较合适的),虽然这样可以跟踪异常的行为,但是仍旧不知道该如何处理异常。这一节,我们来研究一下“被检查的异常”及其并发症,以及采用什么方法来解决这些问题。\n", - "\n", - "这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。\n", - "\n", - "### 历史\n", - "\n", - "异常处理起源于 PL/1 和 Mesa 之类的系统中,后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 Ruby 和 C# 中。Java 的设计和 C++ 很相似,只是 Java 的设计者去掉了一些他们认为 C++设计得不好的东西。\n", - "\n", - "为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架,异常处理机制很晚才被加入 C++ 标准化过程中,这是由 C++ 的设计者 Bjarne Stroustrup 所倡议。C++ 的异常模型主要借鉴了 CLU 的做法。然而,当时其他语言已经支持异常处理了:包括 Ada、Smalltalk(两者都有异常处理,但是都没有异常说明),以及 Modula-3(它既有异常处理也有异常说明)。\n", - "\n", - "Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文中指出,用瞬时风格(transient fashion)报告错误的语言(如 C 中)有一个主要缺陷,那就是:\n", - "\n", - "> “....每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常。”\n", - "\n", - "因此,异常处理的初衷是要消除这种限制,但是我们又从 Java 的“被检查的异常”中看到了这种代码。他们继续写道:\n", - "\n", - "> “....要求程序员把异常处理程序的代码文本附接到会引发异常的调用上,这会降低程序的可读性,使得程序的正常思路被异常处理给破坏了。”\n", - "\n", - "C++ 中异常的设计参考了 CLU 方式。Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的,因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是,忽略所有的错误,然后使用调试器来跟踪错误。这些程序员知道,使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此,要把他们拉到“使用错误处理”的正轨上,“多出来的”代码决不能太多。我认为,评价 Java 的“被检查的异常”的时候,这一点是很重要的。\n", - "\n", - "C++ 从 CLU 那里还带来另一种思想:异常说明。这样,就可以用编程的方式在方法签名中声明这个方法将会抛出异常。异常说明有两个目的:一个是“我的代码会产生这种异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理的机制和语法的时候,我们一直在关注“你来处理”部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。\n", - "\n", - "C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++ 不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++ 会调用标准类库的 unexpected() 函数。\n", - "\n", - "值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于泛型用于异常说明的方式存在着一些限制。\n", - "\n", - "### 观点\n", - "\n", - "首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。\n", - "\n", - "其次,仅从示意性的例子和小程序来看,“被检查的异常”的好处很明显。但是当程序开始变大的时候,就会带来一些微妙的问题。当然,程序不是一下就变大的,这有个过程。如果把不适用于大项目的语言用于小项目,当这些项目不断膨胀时,突然有一天你会发现,原来可以管理的东西,现在已经变得无法管理了。这就是我所说的过多的类型检查,特别是“被检查的异常\"所造成的问题。\n", - "\n", - "看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C# 的设计人员发现:\n", - "\n", - "> “仅从小程序来看,会认为异常说明能增加开发人员的效率,并提高代码的质量;但考察大项目的时候,结论就不同了-开发效率下降了,而代码质量只有微不足道的提高,甚至毫无提高”。\n", - "\n", - "谈到未被捕获的异常的时候,CLU 的设计师们认为:\n", - "\n", - "> “我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序,是不现实的。”\n", - "\n", - "在解释为什么“函数没有异常说明就表示可以抛出任何异常”的时候,Stroustrup 这样认为:\n", - "\n", - "> “但是,这样一来几乎所有的函数都得提供异常说明了,也就都得重新编译,而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束,他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感。”\n", - ">\n", - "\n", - "我们已经看到这种破坏异常机制的行为了-就在 Java 的“被检查的异常”里。\n", - "\n", - "Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者)给我写了下面这段话:\n", - "\n", - "> “...总体来说,我觉得异常很不错,但是 Java 的”被检查的异常“带来的麻烦比好处要多。”\n", - "\n", - "过去,我曾坚定地认为“被检查的异常”和强静态类型检查对开发健壮的程序是非常必要的。但是,我看到的以及我使用一些动态(类型检查)语言的亲身经历告诉我,这些好处实际上是来自于:\n", - "\n", - "1. 不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型。\n", - "2. 不在于什么时候进行检查,而是一定要有类型检查。也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那倒没关系。\n", - "\n", - "此外,减少编译时施加的约束能显著提高程序员的编程效率。事实上,反射和泛型就是用来补偿静态类型检查所带来的过多限制,在本书很多例子中都会见到这种情形。\n", - "\n", - "我已经听到有人在指责了,他们认为这种言论会令我名誉扫地,会让文明堕落,会导致更高比例的项目失败。他们的信念是应该在编译时指出所有错误,这样才能挽救项目,这种信念可以说是无比坚定的;其实更重要的是要理解编译器的能力限制。在 http://MindView.net/Books/BetterJava 上的补充材料中,我强调了自动构建过程和单元测试的重要性,比起把所有的东西都说成是语法错误,它们的效果可以说是事半功倍。下面这段话是至理名言:\n", - "\n", - "> 好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出坏程序。\n", - "\n", - "不管怎么说,要让 Java 把“被检查的异常”从语言中去除,这种可能性看来非常渺茫。对语言来说,这个变化可能太激进了点,况且 Sun 的支持者们也非常强大。Sun 有完全向后兼容的历史和策略,实际上所有 Sun 的软件都能在 Sun 的硬件上运行,无论它们有多么古老。然而,如果发现有些“被检查的异常”挡住了路,尤其是发现你不得不去对付那些不知道该如何处理的异常,还是有些办法的。\n", - "\n", - "### 把异常传递给控制台\n", - "\n", - "对于简单的程序,比如本书中的许多例子,最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从 main() 传递到控制台。例如,为了读取信息而打开一个文件(在第 12 章将详细介绍),必须对 FilelnputStream 进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做(本书中很多地方采用了这种方法):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/MainException.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "public class MainException {\n", - " // Pass exceptions to the console:\n", - " public static void main(String[] args) throws Exception {\n", - " // Open the file:\n", - " List lines = Files.readAllLines(\n", - " Paths.get(\"MainException.java\"));\n", - " // Use the file ...\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,main() 作为一个方法也可以有异常说明,这里异常的类型是 Exception,它也是所有“被检查的异常”的基类。通过把它传递到控制台,就不必在 main() 里写 try-catch 子句了。(不过,实际的文件输人输出操作比这个例子要复杂得多。你将会在 [文件](./Files.md) 和 [附录:I/O 流](./Appendix-IO-Streams.md) 章节中学到更多)\n", - "\n", - "### 把“被检查的异常”转换为“不检查的异常”\n", - "\n", - "在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的,但这不是通用的方法。\n", - "\n", - "问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或者打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "try {\n", - " // ... to do something useful\n", - "} catch(IDontKnowWhatToDoWithThisCheckedException e) {\n", - " throw new RuntimeException(e);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果想把“被检查的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。\n", - "\n", - "这种技巧给了你一种选择,你可以不写 try-catch 子句和/或异常说明,直接忽略异常,让它自己沿着调用栈往上“冒泡”,同时,还可以用 getCause() 捕获并处理特定的异常,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// exceptions/TurnOffChecking.java\n", - "// \"Turning off\" Checked exceptions\n", - "import java.io.*;\n", - "class WrapCheckedException {\n", - " void throwRuntimeException(int type) {\n", - " try {\n", - " switch(type) {\n", - " case 0: throw new FileNotFoundException();\n", - " case 1: throw new IOException();\n", - " case 2: throw new\n", - " RuntimeException(\"Where am I?\");\n", - " default: return;\n", - " }\n", - " } catch(IOException | RuntimeException e) {\n", - " // Adapt to unchecked:\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}\n", - "class SomeOtherException extends Exception {}\n", - "public class TurnOffChecking {\n", - " public static void main(String[] args) {\n", - " WrapCheckedException wce =\n", - " new WrapCheckedException();\n", - " // You can call throwRuntimeException() without\n", - " // a try block, and let RuntimeExceptions\n", - " // leave the method:\n", - " wce.throwRuntimeException(3);\n", - " // Or you can choose to catch exceptions:\n", - " for(int i = 0; i < 4; i++)\n", - " try {\n", - " if(i < 3)\n", - " wce.throwRuntimeException(i);\n", - " else\n", - " throw new SomeOtherException();\n", - " } catch(SomeOtherException e) {\n", - " System.out.println(\n", - " \"SomeOtherException: \" + e);\n", - " } catch(RuntimeException re) {\n", - " try {\n", - " throw re.getCause();\n", - " } catch(FileNotFoundException e) {\n", - " System.out.println(\n", - " \"FileNotFoundException: \" + e);\n", - " } catch(IOException e) {\n", - " System.out.println(\"IOException: \" + e);\n", - " } catch(Throwable e) {\n", - " System.out.println(\"Throwable: \" + e);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "FileNotFoundException: java.io.FileNotFoundException\n", - "IOException: java.io.IOException\n", - "Throwable: java.lang.RuntimeException: Where am I?\n", - "SomeOtherException: SomeOtherException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型的异常。这些异常被捕获并包装进了 RuntimeException 对象,所以它们成了这些运行时异常的\"cause\"了。\n", - "\n", - "在 TurnOfChecking 里,可以不用 try 块就调用 throwRuntimeException(),因为它没有抛出“被检查的异常”。但是,当你准备好去捕获异常的时候,还是可以用 try 块来捕获任何你想捕获的异常的。应该捕获 try 块肯定会抛出的异常,这里就是 SomeOtherException,RuntimeException 要放到最后去捕获。然后把 getCause() 的结果(也就是被包装的那个原始异常)抛出来。这样就把原先的那个异常给提取出来了,然后就可以用它们自己的 catch 子句进行处理。\n", - "\n", - "本书余下部分将会在合适的时候使用这种“用 RuntimeException 来包装,被检查的异常”的技术。另一种解决方案是创建自己的 RuntimeException 的子类。在这种方式中,不必捕获它,但是希望得到它的其他代码都可以捕获它。\n", - "\n", - "\n", - "\n", - "## 异常指南\n", - "\n", - "应该在下列情况下使用异常:\n", - "\n", - "1. 尽可能使用 try-with-resource。\n", - "2. 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)\n", - "3. 解决问题并且重新调用产生异常的方法。\n", - "4. 进行少许修补,然后绕过异常发生的地方继续执行。\n", - "5. 用别的数据进行计算,以代替方法预计会返回的值。\n", - "6. 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。\n", - "7. 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。\n", - "8. 终止程序。\n", - "9. 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)\n", - "10. 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。\n", - "\n", - "异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。\n", - "\n", - "就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。\n", - "\n", - "## 后记:Exception Bizarro World\n", - "\n", - "(来自于 2011 年的一篇博文)\n", - "\n", - "我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,并且不断被检查的异常所挫败。他向我指出 Howard Lewis Ship 的帖子“[被检查的例外的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”。特别是。James 对他必须跳过去做一些应该简单的事情的所有环感到沮丧。即使在 finally 块中,他也不得不放入更多的 try-catch 子句,因为关闭连接也会导致异常。它在哪里结束?为了简单起见,你必须在环之后跳过环(请注意,try-with-resources语句可以显著改善这种情况)。\n", - "\n", - "我们开始讨论 Go 编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。\n", - "\n", - "我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。\n", - "\n", - "他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。\n", - "\n", - "替代方案非常简单,起初它几乎看起来像 C 一样。因为 Go 从一开始就包含了元组,所以你可以轻松地从函数调用中返回两个对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "go" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "result, err := functionCall()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "( := 告诉 Go 语言这里定义 result 和 err,并且推断他们的数据类型)\n", - "\n", - "就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。\n", - "\n", - "起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?\n", - "\n", - "它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。\n", - "\n", - "James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生(Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二者)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。\n", - "\n", - "关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。\n", - "\n", - "跳入奇异的世界会给 James 带来问题,它为所有程序员增加了更多的工作:因为你无法知道什么时候会发生什么事(你可以随时进入奇怪的世界),你必须添加一些 try 块来确保没有任何东西从裂缝中滑落。您最终必须进行额外的编程以补偿异常机制(它似乎类似于补偿共享内存并发所需的额外工作)。\n", - "\n", - "Go 团队采取了大胆的举动,质疑所有这些,并说,“让我们毫无例外地尝试它,看看会发生什么。”是的,这意味着你通常会在发生错误的地方处理错误,而不是最后将它们聚集在一起 try 块。但这也意味着关于一件事的代码是本地化的,也许这并不是那么糟糕。这也可能意味着您无法轻松组合常见的错误处理代码(除非您确定了常用代码并将其放入函数中,也不是那么糟糕)。但这绝对意味着您不必担心有多个可能的执行路径而且所有这些都需要。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/16-Validating-Your-Code.ipynb b/jupyter/16-Validating-Your-Code.ipynb deleted file mode 100644 index f4cb2c3a..00000000 --- a/jupyter/16-Validating-Your-Code.ipynb +++ /dev/null @@ -1,2480 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第十六章 代码校验\n", - "\n", - "### 你永远不能保证你的代码是正确的,你只能证明它是错的。\n", - "\n", - "让我们先暂停编程语言特性的学习,看看一些代码基础知识。特别是能让你的代码更加健壮的知识。\n", - "\n", - "\n", - "\n", - "## 测试\n", - "\n", - "### 如果没有测试过,它就是不能工作的。\n", - "\n", - "Java是一个静态类型的语言,程序员经常对一种编程语言明显的安全性感到过于舒适,“能通过编译器,那就是没问题的”。但静态类型检查是一种非常局限性的测试,只是说明编译器接受你代码中的语法和基本类型规则,并不意味着你的代码达到程序的目标。随着你代码经验的丰富,你逐渐了解到你的代码从来没有满足过这些目标。迈向代码校验的第一步就是创建测试,针对你的目标检查代码的行为。\n", - "\n", - "#### 单元测试\n", - "\n", - "这个过程是将集成测试构建到你创建的所有代码中,并在每次构建系统时运行这些测试。这样,构建过程不仅能检查语法的错误,同时也能检查语义的错误。\n", - "\n", - "“单元”是指测试一小部分代码 。通常,每个类都有测试来检查它所有方法的行为。“系统”测试则是不同的,它检查的是整个程序是否满足要求。\n", - "\n", - "C 风格的语言,尤其是 C++,通常会认为性能比安全更重要。用 Java 编程比 C++(一般认为大概快两倍)快的原因是 Java 的安全性保障:比如垃圾回收以及改良的类型检测等特性。通过将单元测试集成到构建过程中,你扩大了这个安全保障,因而有了更快的开发效率。当发现设计或实现的缺陷时,可以更容易、更大胆地重构你的代码。\n", - "\n", - "我自己的测试经历开始于我意识到要确保书中代码的正确性,书中的所有程序必须能够通过合适的构建系统自动提取、编译。这本书所使用的构建系统是 Gradle。 你只要在安装 JDK 后输入 **gradlew compileJava**,就能编译本书的所有代码。自动提取和自动编译的效果对本书代码的质量是如此的直接和引人注目,(在我看来)这会很快成为任何编程书籍的必备条件——你怎么能相信没有编译的代码呢? 我还发现我可以使用搜索和替换在整本书进行大范围的修改,如果引入了一个错误,代码提取器和构建系统就会清除它。随着程序越来越复杂,我在系统中发现了一个严重的漏洞。编译程序毫无疑问是重要的第一步, 对于一本要出版的书而言,这看来是相当具有革命意义的发现(由于出版压力, 你经常打开一本程序设计的书会发现书中代码的错误)。但是,我收到了来自读者反馈代码中存在语义问题。当然,这些问题可以通过运行代码发现。我在早期实现一个自动化执行测试系统时尝试了一些不太有效的方式,但迫于出版压力,我明白我的程序绝对有问题,并会以 bug 报告的方式让我自食恶果。我也经常收到读者的抱怨说,我没有显示足够的代码输出。我需要验证程序的输出,并且在书中显示验证的输出。我以前的意见是读者应该一边看书一边运行代码,许多读者就是这么做的并且从中受益。然而,这种态度背后的原因是,我无法保证书中的输出是正确的。从经验来看,我知道随着时间的推移,会发生一些事情,使得输出不再正确(或者一开始就不正确)。为了解决这个问题,我利用 Python 创建了一个工具(你将在下载的示例中找到此工具)。本书中的大多数程序都产生控制台输出,该工具将该输出与源代码清单末尾的注释中显示的预期输出进行比较,所以读者可以看到预期的输出,并且知道这个输出已经被构建程序验证过。\n", - "\n", - "#### JUnit\n", - "\n", - "最初的 JUnit 发布于 2000 年,大概是基于 Java 1.0,因此不能使用 Java 的反射工具。因此,用旧的 JUnit 编写单元测试是一项相当繁忙和冗长的工作。我发现这个设计令人不爽,并编写了自己的单元测试框架作为 [注解](./Annotations.md) 一章的示例。这个框架走向了另一个极端,“尝试最简单可行的方法”(极限编程中的一个关键短语)。从那之后,JUnit 通过反射和注解得到了极大的改进,大大简化了编写单元测试代码的过程。在 Java8 中,他们甚至增加了对 lambdas 表达式的支持。本书使用当时最新的 Junit5 版本\n", - "\n", - "在 JUnit 最简单的使用中,使用 **@Test** 注解标记表示测试的每个方法。JUnit 将这些方法标识为单独的测试,并一次设置和运行一个测试,采取措施避免测试之间的副作用。\n", - "\n", - "让我们尝试一个简单的例子。**CountedList** 继承 **ArrayList** ,添加信息来追踪有多少个 **CountedLists** 被创建:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/CountedList.java\n", - "// Keeps track of how many of itself are created.\n", - "package validating;\n", - "import java.util.*;\t\n", - "public class CountedList extends ArrayList {\n", - " private static int counter = 0;\n", - " private int id = counter++;\n", - " public CountedList() {\n", - " System.out.println(\"CountedList #\" + id);\n", - " }\n", - "\tpublic int getId() { return id; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "标准做法是将测试放在它们自己的子目录中。测试还必须放在包中,以便 JUnit 能发现它们:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/tests/CountedListTest.java\n", - "// Simple use of JUnit to test CountedList.\n", - "package validating;\n", - "import java.util.*;\n", - "import org.junit.jupiter.api.*;\n", - "import static org.junit.jupiter.api.Assertions.*;\n", - "public class CountedListTest {\n", - "private CountedList list;\n", - "\t@BeforeAll\n", - " static void beforeAllMsg() {\n", - " System.out.println(\">>> Starting CountedListTest\");\n", - " }\n", - " \n", - " @AfterAll\n", - " static void afterAllMsg() {\n", - " System.out.println(\">>> Finished CountedListTest\");\n", - " }\n", - " \n", - " @BeforeEach\n", - " public void initialize() {\n", - " \tlist = new CountedList();\n", - " \tSystem.out.println(\"Set up for \" + list.getId());\n", - " for(int i = 0; i < 3; i++)\n", - " list.add(Integer.toString(i));\n", - " }\n", - " \n", - " @AfterEach\n", - " public void cleanup() {\n", - " \tSystem.out.println(\"Cleaning up \" + list.getId());\n", - " }\n", - " \n", - " @Test\n", - " public void insert() {\n", - " System.out.println(\"Running testInsert()\");\n", - " assertEquals(list.size(), 3);\n", - " list.add(1, \"Insert\");\n", - " assertEquals(list.size(), 4);\n", - " assertEquals(list.get(1), \"Insert\");\n", - " }\n", - " \n", - " @Test\n", - " public void replace() {\n", - " \tSystem.out.println(\"Running testReplace()\");\n", - " \tassertEquals(list.size(), 3);\n", - " \tlist.set(1, \"Replace\");\n", - " \t\tassertEquals(list.size(), 3);\n", - " \tassertEquals(list.get(1), \"Replace\");\n", - " }\n", - " \t\n", - " // A helper method to simplify the code. As\n", - " // long as it's not annotated with @Test, it will\n", - " // not be automatically executed by JUnit.\n", - " private void compare(List lst, String[] strs) {\n", - " assertArrayEquals(lst.toArray(new String[0]), strs);\n", - " }\n", - " \n", - " @Test\n", - " public void order() {\n", - " System.out.println(\"Running testOrder()\");\n", - " compare(list, new String[] { \"0\", \"1\", \"2\" });\n", - " }\n", - " \n", - " @Test\n", - " public void remove() {\n", - " \tSystem.out.println(\"Running testRemove()\");\n", - " \tassertEquals(list.size(), 3);\n", - " \tlist.remove(1);\n", - " \tassertEquals(list.size(), 2);\n", - " \tcompare(list, new String[] { \"0\", \"2\" });\n", - " }\n", - " \n", - " @Test\n", - " public void addAll() {\n", - " \tSystem.out.println(\"Running testAddAll()\");\n", - " \tlist.addAll(Arrays.asList(new String[] {\n", - " \t\"An\", \"African\", \"Swallow\"}));\n", - " \tassertEquals(list.size(), 6);\n", - " \tcompare(list, new String[] { \"0\", \"1\", \"2\",\n", - " \t\"An\", \"African\", \"Swallow\" });\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - ">>> Starting CountedListTest\n", - "CountedList #0\n", - "Set up for 0\n", - "Running testRemove()\n", - "Cleaning up 0\n", - "CountedList #1\n", - "Set up for 1\n", - "Running testReplace()\n", - "Cleaning up 1\n", - "CountedList #2\n", - "Set up for 2\n", - "Running testAddAll()\n", - "Cleaning up 2\n", - "CountedList #3\n", - "Set up for 3\n", - "Running testInsert()\n", - "Cleaning up 3\n", - "CountedList #4\n", - "Set up for 4\n", - "Running testOrder()\n", - "Cleaning up 4\n", - ">>> Finished CountedListTest\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@BeforeAll** 注解是在任何其他测试操作之前运行一次的方法。 **@AfterAll** 是所有其他测试操作之后只运行一次的方法。两个方法都必须是静态的。\n", - "\n", - "**@BeforeEach**注解是通常用于创建和初始化公共对象的方法,并在每次测试前运行。可以将所有这样的初始化放在测试类的构造函数中,尽管我认为 **@BeforeEach** 更加清晰。JUnit为每个测试创建一个对象,确保测试运行之间没有副作用。然而,所有测试的所有对象都是同时创建的(而不是在测试之前创建对象),所以使用 **@BeforeEach** 和构造函数之间的唯一区别是 **@BeforeEach** 在测试前直接调用。在大多数情况下,这不是问题,如果你愿意,可以使用构造函数方法。\n", - "\n", - "如果你必须在每次测试后执行清理(如果修改了需要恢复的静态文件,打开文件需要关闭,打开数据库或者网络连接,etc),那就用注解 **@AfterEach**。\n", - "\n", - "每个测试创建一个新的 **CountedListTest** 对象,任何非静态成员变量也会在同一时间创建。然后为每个测试调用 **initialize()** ,于是 list 被赋值为一个新的用字符串“0”、“1” 和 “2” 初始化的 **CountedList** 对象。观察 **@BeforeEach** 和 **@AfterEach** 的行为,这些方法在初始化和清理测试时显示有关测试的信息。\n", - "\n", - "**insert()** 和 **replace()** 演示了典型的测试方法。JUnit 使用 **@Test** 注解发现这些方法,并将每个方法作为测试运行。在方法内部,你可以执行任何所需的操作并使用 JUnit 断言方法(以\"assert\"开头)验证测试的正确性(更全面的\"assert\"说明可以在 Junit 文档里找到)。如果断言失败,将显示导致失败的表达式和值。这通常就足够了,但是你也可以使用每个 JUnit 断言语句的重载版本,它包含一个字符串,以便在断言失败时显示。\n", - "\n", - "断言语句不是必须的;你可以在没有断言的情况下运行测试,如果没有异常,则认为测试是成功的。\n", - "\n", - "**compare()** 是“helper方法”的一个例子,它不是由 JUnit 执行的,而是被类中的其他测试使用。只要没有 **@Test** 注解,JUnit 就不会运行它,也不需要特定的签名。在这里,**compare()** 是私有方法 ,表示仅在测试类中使用,但他同样可以是 **public** 。其余的测试方法通过将其重构为 **compare()** 方法来消除重复的代码。\n", - "\n", - "本书使用 **build.gradle** 控制测试,运行本章节的测试,使用命令:`gradlew validating:test`,Gradle 不会运行已经运行过的测试,所以如果你没有得到测试结果,得先运行:`gradlew validating:clean`。\n", - "\n", - "可以用下面这个命令运行本书的所有测试:\n", - "\n", - "**gradlew test**\n", - "\n", - "尽管可以用最简单的方法,如 **CountedListTest.java** 所示那样,JUnit 还包括大量的测试结构,你可以到[官网](junit.org)上学习它们。\n", - "\n", - "JUnit 是 Java 最流行的单元测试框架,但也有其它可以替代的。你可以通过互联网发现更适合的那一个。\n", - "\n", - "#### 测试覆盖率的幻觉\n", - "\n", - "测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比。百分比越高,测试的覆盖率越大。这里有很多[方法](https://en.wikipedia.org/wiki/Code_coverage)\n", - "\n", - "计算覆盖率,还有有帮助的文章[Java代码覆盖工具](https://en.wikipedia.org/wiki/Java_Code_Coverage_Tools)。\n", - "\n", - "对于没有知识但处于控制地位的人来说,很容易在没有任何了解的情况下也有概念认为 100% 的测试覆盖是唯一可接受的值。这有一个问题,因为 100% 并不意味着是对测试有效性的良好测量。你可以测试所有需要它的东西,但是只需要 65% 的覆盖率。如果需要 100% 的覆盖,你将浪费大量时间来生成剩余的代码,并且在向项目添加代码时浪费的时间更多。\n", - "\n", - "当分析一个未知的代码库时,测试覆盖率作为一个粗略的度量是有用的。如果覆盖率工具报告的值特别低(比如,少于百分之40),则说明覆盖不够充分。然而,一个非常高的值也同样值得怀疑,这表明对编程领域了解不足的人迫使团队做出了武断的决定。覆盖工具的最佳用途是发现代码库中未测试的部分。但是,不要依赖覆盖率来得到测试质量的任何信息。\n", - "\n", - "\n", - "\n", - "## 前置条件\n", - "\n", - "前置条件的概念来自于契约式设计(**Design By Contract, DbC**), 利用断言机制实现。我们从 Java 的断言机制开始来介绍 DBC,最后使用谷歌的 Guava 库作为前置条件。\n", - "\n", - "#### 断言(Assertions)\n", - "\n", - "断言通过验证在程序执行期间满足某些条件,从而增加了程序的健壮性。举例,假设在一个对象中有一个数值字段表示日历上的月份。这个数字总是介于 1-12 之间。断言可以检查这个数字,如果超出了该范围,则报告错误。如果在方法的内部,则可以使用断言检查参数的有效性。这些是确保程序正确的重要测试,但是它们不能在编译时被检查,并且它们不属于单元测试的范围。\n", - "\n", - "#### Java 断言语法\n", - "\n", - "你可以通过其它程序设计架构来模拟断言的效果,因此,在 Java 中包含断言的意义在于它们易于编写。断言语句有两种形式 : \n", - "\n", - "assert boolean-expression;\n", - "\n", - "assert boolean-expression: information-expression;\n", - "\n", - "两者似乎告诉我们 **“我断言这个布尔表达式会产生 true”**, 否则,将抛出 **AssertionError** 异常。\n", - "\n", - "**AssertionError** 是 **Throwable** 的派生类,因此不需要异常说明。\n", - "\n", - "不幸的是,第一种断言形式的异常不会生成包含布尔表达式的任何信息(与大多数其他语言的断言机制相反)。\n", - "\n", - "下面是第一种形式的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Assert1.java\n", - "\n", - "// Non-informative style of assert\n", - "// Must run using -ea flag:\n", - "// {java -ea Assert1}\n", - "// {ThrowsException}\n", - "public class Assert1 {\n", - " public static void main(String[] args) {\n", - " assert false;\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"main\" java.lang.AssertionError\n", - "at Assert1.main(Assert1.java:9)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你正常运行程序,没有任何特殊的断言标志,则不会发生任何事情。你需要在运行程序时显式启用断言。一种简单的方法是使用 **-ea** 标志, 它也可以表示为: **-enableassertion**, 这将运行程序并执行任何断言语句。\n", - "\n", - "输出中并没有包含多少有用的信息。另一方面,如果你使用 **information-expression** , 将生成一条有用的消息作为异常堆栈跟踪的一部分。最有用的 **information-expression** 通常是一串针对程序员的文本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Assert2.java\n", - "// Assert with an information-expression\n", - "// {java Assert2 -ea}\n", - "// {ThrowsException}\n", - "\n", - "public class Assert2 {\n", - " public static void main(String[] args) {\n", - " assert false:\n", - " \"Here's a message saying what happened\";\n", - " }\n", - "}\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"main\" java.lang.AssertionError:\n", - "Here's a message saying what happened\n", - "at Assert2.main(Assert2.java:8)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**information-expression** 可以产生任何类型的对象,因此,通常将构造一个包含对象值的更复杂的字符串,它包含失败的断言。\n", - "\n", - "你还可以基于类名或包名打开或关闭断言;也就是说,你可以对整个包启用或禁用断言。实现这一点的详细信息在 JDK 的断言文档中。此特性对于使用断言的大型项目来说很有用当你想打开或关闭某些断言时。但是,日志记录(*Logging*)或者调试(*Debugging*),可能是捕获这类信息的更好工具。\n", - "\n", - "你还可以通过编程的方式通过链接到类加载器对象(**ClassLoader**)来控制断言。类加载器中有几种方法允许动态启用和禁用断言,其中 **setDefaultAssertionStatus ()** ,它为之后加载的所有类设置断言状态。因此,你可以像下面这样悄悄地开启断言:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/LoaderAssertions.java\n", - "// Using the class loader to enable assertions\n", - "// {ThrowsException}\n", - "public class LoaderAssertions {\n", - "public static void main(String[] args) {\n", - "\n", - "\tClassLoader.getSystemClassLoader().\n", - " setDefaultAssertionStatus(true);\n", - "\t\tnew Loaded().go();\n", - "\t}\n", - "}\n", - "\n", - "class Loaded {\n", - " public void go() {\n", - " assert false: \"Loaded.go()\";\n", - " }\n", - "}\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"main\" java.lang.AssertionError:\n", - "Loaded.go()\n", - "at Loaded.go(LoaderAssertions.java:15)\n", - "at\n", - "LoaderAssertions.main(LoaderAssertions.java:9)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这消除了在运行程序时在命令行上使用 **-ea** 标志的需要,使用 **-ea** 标志启用断言可能同样简单。当交付独立产品时,可能必须设置一个执行脚本让用户能够启动程序,配置其他启动参数,这么做是有意义的。然而,决定在程序运行时启用断言可以使用下面的 **static** 块来实现这一点,该语句位于系统的主类中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "static {\n", - " boolean assertionsEnabled = false;\n", - " // Note intentional side effect of assignment:\n", - " assert assertionsEnabled = true;\n", - " if(!assertionsEnabled)\n", - " throw new RuntimeException(\"Assertions disabled\");\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果启用断言,然后执行 **assert** 语句,**assertionsEnabled** 变为 **true** 。断言不会失败,因为分配的返回值是赋值的值。如果不启用断言,**assert** 语句不执行,**assertionsEnabled** 保持false,将导致异常。\n", - "\n", - "#### Guava断言\n", - "\n", - "因为启用 Java 本地断言很麻烦,Guava 团队添加一个始终启用的用来替换断言的 **Verify** 类。他们建议静态导入 **Verify** 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/GuavaAssertions.java\n", - "// Assertions that are always enabled.\n", - "\n", - "import com.google.common.base.*;\n", - "import static com.google.common.base.Verify.*;\n", - "public class GuavaAssertions {\n", - " public static void main(String[] args) {\n", - " \tverify(2 + 2 == 4);\n", - " \ttry {\n", - " \t\tverify(1 + 2 == 4);\n", - " \t \t} catch(VerifyException e) {\n", - " \t\tSystem.out.println(e);\n", - " \t}\n", - " \n", - "\t\ttry {\n", - "\t\t\tverify(1 + 2 == 4, \"Bad math\");\n", - "\t\t} catch(VerifyException e) {\n", - "\t\t\tSystem.out.println(e.getMessage());\n", - "\t\t}\n", - " \n", - "\t\ttry {\n", - "\t\t\tverify(1 + 2 == 4, \"Bad math: %s\", \"not 4\");\n", - "\t\t} catch(VerifyException e) {\n", - " \tSystem.out.println(e.getMessage());\n", - " }\n", - " \n", - " String s = \"\";\n", - " s = verifyNotNull(s);\n", - " s = null;\n", - " try {\n", - " verifyNotNull(s);\n", - " } catch(VerifyException e) {\n", - " \tSystem.out.println(e.getMessage());\n", - " }\n", - " \n", - " try {\n", - " \tverifyNotNull(\n", - " \t\ts, \"Shouldn't be null: %s\", \"arg s\");\n", - " } catch(VerifyException e) {\n", - " \tSystem.out.println(e.getMessage());\n", - " }\n", - "\t}\n", - "}\n", - "/* Output:\n", - "com.google.common.base.VerifyException\n", - "Bad math\n", - "Bad math: not 4\n", - "expected a non-null reference\n", - "Shouldn't be null: arg s\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里有两个方法,使用变量 **verify()** 和 **verifyNotNull()** 来支持有用的错误消息。注意,**verifyNotNull()** 内置的错误消息通常就足够了,而 **verify()** 太一般,没有有用的默认错误消息。\n", - "\n", - "#### 使用断言进行契约式设计\n", - "\n", - "*契约式设计(DbC)*是 Eiffel 语言的发明者 Bertrand Meyer 提出的一个概念,通过确保对象遵循某些规则来帮助创建健壮的程序。这些规则是由正在解决的问题的性质决定的,这超出了编译器可以验证的范围。虽然断言没有直接实现 **DBC**(Eiffel 语言也是如此),但是它们创建了一种非正式的 DbC 编程风格。DbC 假定服务供应者与该服务的消费者或客户之间存在明确指定的契约。在面向对象编程中,服务通常由对象提供,对象的边界 — 供应者和消费者之间的划分 — 是对象类的接口。当客户端调用特定的公共方法时,它们希望该调用具有特定的行为:对象状态改变,以及一个可预测的返回值。\n", - "\n", - "**Meyer** 认为:\n", - "\n", - "1.应该明确指定行为,就好像它是一个契约一样。\n", - "\n", - "2.通过实现某些运行时检查来保证这种行为,他将这些检查称为前置条件、后置条件和不变项。\n", - "\n", - "不管你是否同意,第一条总是对的,在大多数情况下,DbC 确实是一种有用的方法。(我认为,与任何解决方案一样,它的有用性也有界限。但如果你知道这些界限,你就知道什么时候去尝试。)尤其是,设计过程中一个有价值的部分是特定类 DbC 约束的表达式;如果无法指定约束,则你可能对要构建的内容了解得不够。\n", - "\n", - "#### 检查指令\n", - "\n", - "详细研究 DbC 之前,思考最简单使用断言的办法,**Meyer** 称它为检查指令。检查指令说明你确信代码中的某个特定属性此时已经得到满足。检查指令的思想是在代码中表达非明显性的结论,而不仅仅是为了验证测试,也同样为了将来能够满足阅读者而有一个文档。\n", - "\n", - "在化学领域,你也许会用一种纯液体去滴定测量另一种液体,当达到一个特定的点时,液体变蓝了。从两个液体的颜色上并不能明显看出;这是复杂反应的一部分。滴定完成后一个有用的检查指令是能够断定液体变蓝了。\n", - "\n", - "检查指令是对你的代码进行补充,当你可以测试并阐明对象或程序的状态时,应该使用它。\n", - "\n", - "#### 前置条件\n", - "\n", - "前置条件确保客户端(调用此方法的代码)履行其部分契约。这意味着在方法调用开始时几乎总是会检查参数(在你用那个方法做任何操作之前)以此保证它们的调用在方法中是合适的。因为你永远无法知道客户端会传递给你什么,前置条件是确保检查的一个好做法。\n", - "\n", - "#### 后置条件\n", - "\n", - "后置条件测试你在方法中所做的操作的结果。这段代码放在方法调用的末尾,在 **return** 语句之前(如果有的话)。对于长时间、复杂的方法,在返回计算结果之前需要对计算结果进行验证(也就是说,在某些情况下,由于某种原因,你不能总是相信结果),后置条件很重要,但是任何时候你可以描述方法结果上的约束时,最好将这些约束在代码中表示为后置条件。\n", - "\n", - "#### 不变性\n", - "\n", - "不变性保证了必须在方法调用之间维护的对象的状态。但是,它并不会阻止方法在执行过程中暂时偏离这些保证,它只是在说对象的状态信息应该总是遵守状态规则:\n", - "\n", - "**1**. 在进入该方法时。\n", - "\n", - "**2**. 在离开方法之前。\n", - "\n", - "此外,不变性是构造后对于对象状态的保证。\n", - "\n", - "根据这个描述,一个有效的不变性被定义为一个方法,可能被命名为 **invariant()** ,它在构造之后以及每个方法的开始和结束时调用。方法以如下方式调用:\n", - "\n", - "assert invariant();\n", - "\n", - "这样,如果出于性能原因禁用断言,就不会产生开销。\n", - "\n", - "#### 放松 DbC 检查或非严格的 DbC\n", - "\n", - "尽管 Meyer 强调了前置条件、后置条件和不变性的价值以及在开发过程中使用它们的重要性,他承认在一个产品中包含所有 DbC 代码并不总是实际的。你可以基于对特定位置的代码的信任程度放松 DbC 检查。以下是放松检查的顺序,最安全到最不安全:\n", - "\n", - "**1**. 不变性检查在每个方法一开始的时候是不能进行的,因为在每个方法结束的时候进行不变性检查能保证一开始的时候对象处于有效状态。也就是说,通常情况下,你可以相信对象的状态不会在方法调用之间发生变化。这是一个非常安全的假设,你可以只在代码末尾使用不变性检查来编写代码。\n", - "\n", - "**2**. 接下来禁用后置条件检查,当你进行合理的单元测试以验证方法是否返回了适当的值时。因为不变性检查是观察对象的状态,后置条件检查仅在方法期间验证计算结果,因此可能会被丢弃,以便进行单元测试。单元测试不会像运行时后置条件检查那样安全,但是它可能已经足够了,特别是当对自己的代码有信心时。\n", - "\n", - "**3**. 如果你确信方法主体没有把对象改成无效状态,则可以禁用方法调用末尾的不变性检查。可以通过白盒单元测试(通过访问私有字段的单元测试来验证对象状态)来验证这一点。尽管它可能没有调用 **invariant()** 那么稳妥,可以将不变性检查从运行时测试 “迁移” 到构建时测试(通过单元测试),就像使用后置条件一样。\n", - "\n", - "**4**. 禁用前置条件检查,但除非这是万不得已的情况下。因为这是最不安全、最不明智的选择,因为尽管你知道并且可以控制自己的代码,但是你无法控制客户端可能会传递给方法的参数。然而,某些情况下对性能要求很高,通过分析得到前置条件造成了这个瓶颈,而且你有某种合理的保证客户端不会违反前置条件(比如自己编写客户端的情况下),那么禁用前置条件检查是可接受的。\n", - "\n", - "你不应该直接删除检查的代码,而只需要禁用检查(添加注释)。这样如果发现错误,就可以轻松地恢复检查以快速发现问题。\n", - "\n", - "#### DbC + 单元测试\n", - "\n", - "下面的例子演示了将契约式设计中的概念与单元测试相结合的有效性。它显示了一个简单的先进先出(FIFO)队列,该队列实现为一个“循环”数组,即以循环方式使用的数组。当到达数组的末尾时,将绕回到开头。\n", - "\n", - "我们可以对这个队列做一些契约定义:\n", - "\n", - "**1**. 前置条件(用于put()):不允许将空元素添加到队列中。\n", - "\n", - "**2**. 前置条件(用于put()):将元素放入完整队列是非法的。\n", - "\n", - "**3**. 前置条件(用于get()):试图从空队列中获取元素是非法的。\n", - "\n", - "**4**. 后置条件用于get()):不能从数组中生成空元素。\n", - "\n", - "**5**. 不变性:包含对象的区域不能包含任何空元素。\n", - "\n", - "**6**. 不变性:不包含对象的区域必须只有空值。\n", - "\n", - "下面是实现这些规则的一种方式,为每个 DbC 元素类型使用显式的方法调用。\n", - "\n", - "首先,我们创建一个专用的 **Exception**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " // validating/CircularQueueException.java\n", - " package validating;\n", - " public class CircularQueueException extends RuntimeException {\n", - " public CircularQueueException(String why) {\n", - " super(why);\n", - " }\n", - " }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它用来报告 **CircularQueue** 中出现的错误:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " // validating/CircularQueue.java\n", - " // Demonstration of Design by Contract (DbC)\n", - " package validating;\n", - " import java.util.*;\n", - " public class CircularQueue {\n", - " private Object[] data;\n", - " private int in = 0, // Next available storage space \n", - " out = 0; // Next gettable object\n", - " // Has it wrapped around the circular queue?\n", - " private boolean wrapped = false;\n", - " public CircularQueue(int size) {\n", - " data = new Object[size];\n", - " // Must be true after construction:\n", - " assert invariant();\n", - " }\n", - " \n", - " public boolean empty() {\n", - " return !wrapped && in == out;\n", - " }\n", - " \n", - " public boolean full() {\n", - " \t return wrapped && in == out;\n", - " }\n", - " \n", - " \t public boolean isWrapped() { return wrapped; }\n", - " \n", - " public void put(Object item) {\n", - " \t precondition(item != null, \"put() null item\");\n", - " \t precondition(!full(),\n", - " \t \"put() into full CircularQueue\");\n", - " \t assert invariant();\n", - " \t data[in++] = item;\n", - " \t if(in >= data.length) {\n", - " in = 0;\n", - " wrapped = true;\n", - " \t }\n", - " \t\t assert invariant();\n", - " \t }\n", - " \n", - " public Object get() {\n", - " \t precondition(!empty(),\n", - " \t \"get() from empty CircularQueue\");\n", - " \t assert invariant();\n", - " \t Object returnVal = data[out];\n", - " \t data[out] = null;\n", - " \t out++;\n", - " if(out >= data.length) {\n", - " out = 0;\n", - " wrapped = false;\n", - " }\n", - " assert postcondition(\n", - " returnVal != null,\n", - " \"Null item in CircularQueue\");\n", - " assert invariant();\n", - " return returnVal;\n", - " }\n", - " \n", - " \t // Design-by-contract support methods:\n", - " private static void precondition(boolean cond, String msg) {\n", - " if(!cond) throw new CircularQueueException(msg);\n", - " }\n", - " \n", - " private static boolean postcondition(boolean cond, String msg) {\n", - " if(!cond) throw new CircularQueueException(msg);\n", - " \t return true;\n", - " }\n", - " \n", - " private boolean invariant() {\n", - " // Guarantee that no null values are in the\n", - " // region of 'data' that holds objects:\n", - " for(int i = out; i != in; i = (i + 1) % data.length)\n", - " if(data[i] == null)\n", - " throw new CircularQueueException(\"null in CircularQueue\");\n", - " // Guarantee that only null values are outside the\n", - " // region of 'data' that holds objects:\n", - " if(full()) return true;\n", - " for(int i = in; i != out; i = (i + 1) % data.length)\n", - " if(data[i] != null)\n", - " throw new CircularQueueException(\n", - " \"non-null outside of CircularQueue range: \" + dump());\n", - " return true;\n", - " }\n", - " \n", - " public String dump() {\n", - " return \"in = \" + in +\n", - " \", out = \" + out +\n", - " \", full() = \" + full() +\n", - " \", empty() = \" + empty() +\n", - " \", CircularQueue = \" + Arrays.asList(data);\n", - " }\n", - " }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。\n", - "\n", - "**put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。\n", - "\n", - "注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。\n", - "\n", - "**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。\n", - "\n", - "**dump()** 帮助方法返回一个包含所有数据的字符串,而不是直接打印数据。这允许我们用这部分信息做更多事。 \n", - "\n", - "现在我们可以为类创建 JUnit 测试:\n", - "\n", - " ```java\n", - " // validating/tests/CircularQueueTest.java\n", - " package validating;\n", - " import org.junit.jupiter.api.*;\n", - " import static org.junit.jupiter.api.Assertions.*;\n", - " public class CircularQueueTest {\n", - " private CircularQueue queue = new CircularQueue(10);\n", - " private int i = 0;" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@BeforeEach\n", - "public void initialize() {\n", - " while(i < 5) // Pre-load with some data\n", - " queue.put(Integer.toString(i++));\n", - " \t }\n", - "\n", - "// Support methods:\n", - "private void showFullness() {\n", - " assertTrue(queue.full());\n", - " assertFalse(queue.empty());\n", - " System.out.println(queue.dump());\n", - "}\n", - "\n", - "private void showEmptiness() {\n", - " assertFalse(queue.full());\n", - " assertTrue(queue.empty());\n", - " System.out.println(queue.dump());\n", - "}\n", - "\n", - "@Test\n", - "public void full() {\n", - " System.out.println(\"testFull\");\n", - " System.out.println(queue.dump());\n", - " System.out.println(queue.get());\n", - " System.out.println(queue.get());\n", - " while(!queue.full())\n", - " queue.put(Integer.toString(i++));\n", - " String msg = \"\";\n", - " try {\n", - " \t queue.put(\"\");\n", - " } catch(CircularQueueException e) {\n", - " \t msg = e.getMessage();\n", - " \t ∂System.out.println(msg);\n", - " }\n", - " assertEquals(msg, \"put() into full CircularQueue\");\n", - " showFullness();\n", - "}\n", - "\n", - "@Test\n", - "public void empty() {\n", - " System.out.println(\"testEmpty\");\n", - " while(!queue.empty())\n", - "\t\t System.out.println(queue.get());\n", - "\t\t String msg = \"\";\n", - " try {\n", - " queue.get();\n", - " } catch(CircularQueueException e) {\n", - " msg = e.getMessage();\n", - " System.out.println(msg);\n", - " }\n", - " assertEquals(msg, \"get() from empty CircularQueue\");\n", - " showEmptiness();\n", - "}\n", - "@Test\n", - "public void nullPut() {\n", - " System.out.println(\"testNullPut\");\n", - " String msg = \"\";\n", - " try {\n", - " \t queue.put(null);\n", - " } catch(CircularQueueException e) {\n", - " msg = e.getMessage();\n", - " System.out.println(msg);\n", - " }\n", - " assertEquals(msg, \"put() null item\");\n", - "}\n", - "\n", - "@Test\n", - "public void circularity() {\n", - "\t System.out.println(\"testCircularity\");\n", - "\t while(!queue.full())\n", - "\t\t queue.put(Integer.toString(i++));\n", - "\t\t showFullness();\n", - "\t\t assertTrue(queue.isWrapped());\n", - " \n", - " while(!queue.empty())\n", - " \t System.out.println(queue.get());\n", - " \t showEmptiness();\n", - " \n", - " while(!queue.full())\n", - " \t queue.put(Integer.toString(i++));\n", - " \t showFullness();\n", - " \n", - " while(!queue.empty())\n", - " \t System.out.println(queue.get());\n", - " \t showEmptiness();\n", - " }\n", - " }\n", - " /* Output:\n", - " testNullPut\n", - " put() null item\n", - " testCircularity\n", - " in = 0, out = 0, full() = true, empty() = false,\n", - " CircularQueue =\n", - " [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", - " 0\n", - " 1\n", - " 2\n", - " 3\n", - " 4\n", - " 5\n", - " 6\n", - " 7\n", - " 8\n", - " 9\n", - " in = 0, out = 0, full() = false, empty() = true,\n", - " CircularQueue =\n", - " [null, null, null, null, null, null, null, null, null,\n", - " null]\n", - " in = 0, out = 0, full() = true, empty() = false,\n", - " CircularQueue =\n", - " [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]\n", - " 10\n", - " 11\n", - " 12\n", - " 13\n", - " 14\n", - " 15\n", - " 16\n", - " 17\n", - " 18\n", - " 19\n", - " in = 0, out = 0, full() = false, empty() = true,\n", - " CircularQueue =\n", - " [null, null, null, null, null, null, null, null, null,\n", - " null]\n", - " testFull\n", - " in = 5, out = 0, full() = false, empty() = false,\n", - " CircularQueue =\n", - " [0, 1, 2, 3, 4, null, null, null, null, null]\n", - " 0\n", - " 1\n", - " put() into full CircularQueue\n", - " in = 2, out = 2, full() = true, empty() = false,\n", - " CircularQueue =\n", - " [10, 11, 2, 3, 4, 5, 6, 7, 8, 9]\n", - " testEmpty\n", - " 0\n", - " 1\n", - " 2\n", - " 3\n", - " 4\n", - " get() from empty CircularQueue\n", - " in = 5, out = 5, full() = false, empty() = true,\n", - " CircularQueue =\n", - " [null, null, null, null, null, null, null, null, null,\n", - " null]\n", - " */\n", - " ```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**initialize()** 添加了一些数据,因此每个测试的 **CircularQueue** 都是部分满的。**showFullness()** 和 **showempty()** 表明 **CircularQueue** 是满的还是空的,这四种测试方法中的每一种都确保了 **CircularQueue** 功能在不同的地方正确运行。\n", - "\n", - "通过将 Dbc 和单元测试结合起来,你不仅可以同时使用这两种方法,还可以有一个迁移路径—你可以将一些 Dbc 测试迁移到单元测试中,而不是简单地禁用它们,这样你仍然有一定程度的测试。\n", - "\n", - "#### 使用Guava前置条件\n", - "\n", - "在非严格的 DbC 中,前置条件是 DbC 中你不想删除的那一部分,因为它可以检查方法参数的有效性。那是你没有办法控制的事情,所以你需要对其检查。因为 Java 在默认情况下禁用断言,所以通常最好使用另外一个始终验证方法参数的库。\n", - "\n", - "谷歌的 Guava 库包含了一组很好的前置条件测试,这些测试不仅易于使用,而且命名也足够好。在这里你可以看到它们的简单用法。库的设计人员建议静态导入前置条件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/GuavaPreconditions.java\n", - "// Demonstrating Guava Preconditions\n", - "import java.util.function.*;\n", - "import static com.google.common.base.Preconditions.*;\n", - "public class GuavaPreconditions {\n", - " static void test(Consumer c, String s) {\n", - " try {\n", - " System.out.println(s);\n", - " c.accept(s);\n", - " System.out.println(\"Success\");\n", - " } catch(Exception e) {\n", - " String type = e.getClass().getSimpleName();\n", - " String msg = e.getMessage();\n", - " System.out.println(type +\n", - " (msg == null ? \"\" : \": \" + msg));\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " test(s -> s = checkNotNull(s), \"X\");\n", - " test(s -> s = checkNotNull(s), null);\n", - " test(s -> s = checkNotNull(s, \"s was null\"), null);\n", - " test(s -> s = checkNotNull(\n", - " s, \"s was null, %s %s\", \"arg2\", \"arg3\"), null);\n", - " test(s -> checkArgument(s == \"Fozzie\"), \"Fozzie\");\n", - " test(s -> checkArgument(s == \"Fozzie\"), \"X\");\n", - " test(s -> checkArgument(s == \"Fozzie\"), null);\n", - " test(s -> checkArgument(\n", - " s == \"Fozzie\", \"Bear Left!\"), null);\n", - " test(s -> checkArgument(\n", - " s == \"Fozzie\", \"Bear Left! %s Right!\", \"Frog\"),\n", - " null);\n", - " test(s -> checkState(s.length() > 6), \"Mortimer\");\n", - " test(s -> checkState(s.length() > 6), \"Mort\");\n", - " test(s -> checkState(s.length() > 6), null);\n", - " test(s ->\n", - " checkElementIndex(6, s.length()), \"Robert\");\n", - " test(s ->\n", - " checkElementIndex(6, s.length()), \"Bob\");\n", - " test(s ->\n", - " checkElementIndex(6, s.length()), null);\n", - " test(s ->\n", - " checkPositionIndex(6, s.length()), \"Robert\");\n", - " test(s ->\n", - " checkPositionIndex(6, s.length()), \"Bob\");\n", - " test(s ->\n", - " checkPositionIndex(6, s.length()), null);\n", - " test(s -> checkPositionIndexes(\n", - " 0, 6, s.length()), \"Hieronymus\");\n", - " test(s -> checkPositionIndexes(\n", - " 0, 10, s.length()), \"Hieronymus\");\n", - " test(s -> checkPositionIndexes(\n", - " 0, 11, s.length()), \"Hieronymus\");\n", - " test(s -> checkPositionIndexes(\n", - " -1, 6, s.length()), \"Hieronymus\");\n", - " test(s -> checkPositionIndexes(\n", - " 7, 6, s.length()), \"Hieronymus\");\n", - " test(s -> checkPositionIndexes(\n", - " 0, 6, s.length()), null);\n", - " }\n", - "}\n", - "/* Output:\n", - "X\n", - "Success\n", - "null\n", - "NullPointerException\n", - "null\n", - "NullPointerException: s was null\n", - "null\n", - "NullPointerException: s was null, arg2 arg3\n", - "Fozzie\n", - "Success\n", - "X\n", - "IllegalArgumentException\n", - "null\n", - "IllegalArgumentException\n", - "null\n", - "IllegalArgumentException: Bear Left!\n", - "null\n", - "IllegalArgumentException: Bear Left! Frog Right!\n", - "Mortimer\n", - "Success\n", - "Mort\n", - "IllegalStateException\n", - "null\n", - "NullPointerException\n", - "Robert\n", - "IndexOutOfBoundsException: index (6) must be less than\n", - "size (6)\n", - "Bob\n", - "IndexOutOfBoundsException: index (6) must be less than\n", - "size (3)\n", - "null\n", - "NullPointerException\n", - "Robert\n", - "Success\n", - "Bob\n", - "IndexOutOfBoundsException: index (6) must not be\n", - "greater than size (3)\n", - "null\n", - "NullPointerException\n", - "Hieronymus\n", - "Success\n", - "Hieronymus\n", - "Success\n", - "Hieronymus\n", - "IndexOutOfBoundsException: end index (11) must not be\n", - "greater than size (10)\n", - "Hieronymus\n", - "IndexOutOfBoundsException: start index (-1) must not be\n", - "negative\n", - "Hieronymus\n", - "IndexOutOfBoundsException: end index (6) must not be\t\n", - "less than start index (7)\n", - "null\n", - "NullPointerException\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然 Guava 的前置条件适用于所有类型,但我这里只演示 **字符串(String)** 类型。**test()** 方法需要一个Consumer,因此我们可以传递一个 lambda 表达式作为第一个参数,传递给 lambda 表达式的字符串作为第二个参数。它显示字符串,以便在查看输出时确定方向,然后将字符串传递给 lambda 表达式。try 块中的第二个 **println**() 仅在 lambda 表达式成功时才显示; 否则 catch 块将捕获并显示错误信息。注意 **test()** 方法消除了多少重复的代码。\n", - "\n", - "每个前置条件都有三种不同的重载形式:一个什么都没有,一个带有简单字符串消息,以及带有一个字符串和替换值。为了提高效率,只允许 **%s** (字符串类型)替换标记。在上面的例子中,演示了**checkNotNull()** 和 **checkArgument()** 这两种形式。但是它们对于所有前置条件方法都是相同的。注意 **checkNotNull()** 的返回参数, 所以你可以在表达式中内联使用它。下面是如何在构造函数中使用它来防止包含 **Null** 值的对象构造:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "/ validating/NonNullConstruction.java\n", - "import static com.google.common.base.Preconditions.*;\n", - "public class NonNullConstruction {\n", - " private Integer n;\n", - " private String s;\n", - " NonNullConstruction(Integer n, String s) {\n", - " this.n = checkNotNull(n);\t\n", - " this.s = checkNotNull(s);\n", - " }\n", - " public static void main(String[] args) {\n", - " NonNullConstruction nnc =\n", - " new NonNullConstruction(3, \"Trousers\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**checkArgument()** 接受布尔表达式来对参数进行更具体的测试, 失败时抛出 **IllegalArgumentException**,**checkState()** 用于测试对象的状态(例如,不变性检查),而不是检查参数,并在失败时抛出 **IllegalStateException** 。\n", - "\n", - "最后三个方法在失败时抛出 **IndexOutOfBoundsException**。**checkElementIndex**() 确保其第一个参数是列表、字符串或数组的有效元素索引,其大小由第二个参数指定。**checkPositionIndex()** 确保它的第一个参数在 0 到第二个参数(包括第二个参数)的范围内。 **checkPositionIndexes()** 检查 **[first_arg, second_arg]** 是一个列表的有效子列表,由第三个参数指定大小的字符串或数组。\n", - "\n", - "所有的 Guava 前置条件对于基本类型和对象都有必要的重载。\n", - "\n", - "\n", - "\n", - "## 测试驱动开发\n", - "\n", - "之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码,而且还可以得到更好的代码设计。 一般情况下这个说法都是正确的。 一旦我想到“我将如何测试我的代码?”,这个想法将使我的代码产生变化,并且往往是从“可测试”转变为“可用”。\n", - "\n", - "纯粹的 TDD 主义者会在实现新功能之前就为其编写测试,这称为测试优先的开发。 我们采用一个简易的示例程序来进行说明,它的功能是反转 **String** 中字符的大小写。 让我们随意添加一些约束:**String** 必须小于或等于30个字符,并且必须只包含字母,空格,逗号和句号(英文)。\n", - "\n", - "此示例与标准 TDD 不同,因为它的作用在于接收 **StringInverter** 的不同实现,以便在我们逐步满足测试的过程中来体现类的演变。 为了满足这个要求,将 **StringInverter** 作为接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/StringInverter.java\n", - "package validating;\n", - "\n", - "interface StringInverter {\n", - "\tString invert(String str);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在我们通过可以编写测试来表述我们的要求。 以下所述通常不是你编写测试的方式,但由于我们在此处有一个特殊的约束:我们要对 **StringInverter **多个版本的实现进行测试,为此,我们利用了 JUnit5 中最复杂的新功能之一:动态测试生成。 顾名思义,通过它你可以使你所编写的代码在运行时生成测试,而不需要你对每个测试显式编码。 这带来了许多新的可能性,特别是在明确地需要编写一整套测试而令人望而却步的情况下。\n", - "\n", - "JUnit5 提供了几种动态生成测试的方法,但这里使用的方法可能是最复杂的。 **DynamicTest.stream() **方法采用了:\n", - "\n", - "- 对象集合上的迭代器 (versions) ,这个迭代器在不同组的测试中是不同的。 迭代器生成的对象可以是任何类型,但是只能有一种对象生成,因此对于存在多个不同的对象类型时,必须人为地将它们打包成单个类型。\n", - "- **Function**,它从迭代器获取对象并生成描述测试的 **String** 。\n", - "- **Consumer**,它从迭代器获取对象并包含基于该对象的测试代码。\n", - "\n", - "在此示例中,所有代码将在 **testVersions()** 中进行组合以防止代码重复。 迭代器生成的对象是对 **DynamicTest** 的不同实现,这些对象体现了对接口不同版本的实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/tests/DynamicStringInverterTests.java\n", - "package validating;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import org.junit.jupiter.api.*;\n", - "import static org.junit.jupiter.api.Assertions.*;\n", - "import static org.junit.jupiter.api.DynamicTest.*;\n", - "\n", - "class DynamicStringInverterTests {\n", - "\t// Combine operations to prevent code duplication:\n", - "\tStream testVersions(String id,\n", - "\t\tFunction test) {\n", - "\t\tList versions = Arrays.asList(\n", - "\t\t\tnew Inverter1(), new Inverter2(),\n", - "\t\t\tnew Inverter3(), new Inverter4());\n", - "\t\treturn DynamicTest.stream(\n", - "\t\t\tversions.iterator(),\n", - "\t\t\tinverter -> inverter.getClass().getSimpleName(),\n", - "\t\t\tinverter -> {\n", - "\t\t\t\tSystem.out.println(\n", - "\t\t\t\t\tinverter.getClass().getSimpleName() +\n", - "\t\t\t\t\t\t\": \" + id);\n", - "\t\t\t\ttry {\n", - "\t\t\t\t\tif(test.apply(inverter) != \"fail\")\n", - "\t\t\t\t\t\tSystem.out.println(\"Success\");\n", - "\t\t\t\t} catch(Exception | Error e) {\n", - "\t\t\t\t\tSystem.out.println(\n", - "\t\t\t\t\t\t\"Exception: \" + e.getMessage());\n", - "\t\t\t\t}\n", - "\t\t\t}\n", - "\t\t);\n", - "\t}\n", - " String isEqual(String lval, String rval) {\n", - "\t\tif(lval.equals(rval))\n", - "\t\t\treturn \"success\";\n", - "\t\tSystem.out.println(\"FAIL: \" + lval + \" != \" + rval);\n", - "\t\treturn \"fail\";\n", - "\t}\n", - " @BeforeAll\n", - "\tstatic void startMsg() {\n", - "\t\tSystem.out.println(\n", - "\t\t\t\">>> Starting DynamicStringInverterTests <<<\");\n", - "\t}\n", - " @AfterAll\n", - "\tstatic void endMsg() {\n", - "\t\tSystem.out.println(\n", - "\t\t\t\">>> Finished DynamicStringInverterTests <<<\");\n", - "\t}\n", - "\t@TestFactory\n", - "\tStream basicInversion1() {\n", - "\t\tString in = \"Exit, Pursued by a Bear.\";\n", - "\t\tString out = \"eXIT, pURSUED BY A bEAR.\";\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Basic inversion (should succeed)\",\n", - "\t\t\tinverter -> isEqual(inverter.invert(in), out)\n", - "\t\t);\n", - "\t}\n", - "\t@TestFactory\n", - "\tStream basicInversion2() {\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Basic inversion (should fail)\",\n", - "\t\t\tinverter -> isEqual(inverter.invert(\"X\"), \"X\"));\n", - "\t}\n", - "\t@TestFactory\n", - "\tStream disallowedCharacters() {\n", - "\t\tString disallowed = \";-_()*&^%$#@!~`0123456789\";\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Disallowed characters\",\n", - "\t\t\tinverter -> {\n", - "\t\t\t\tString result = disallowed.chars()\n", - "\t\t\t\t\t.mapToObj(c -> {\n", - "\t\t\t\t\t\tString cc = Character.toString((char)c);\n", - "\t\t\t\t\t\ttry {\n", - "\t\t\t\t\t\t\tinverter.invert(cc);\n", - "\t\t\t\t\t\t\treturn \"\";\n", - "\t\t\t\t\t\t} catch(RuntimeException e) {\n", - "\t\t\t\t\t\t\treturn cc;\n", - "\t\t\t\t\t\t}\n", - "\t\t\t\t\t}).collect(Collectors.joining(\"\"));\n", - "\t\t\t\tif(result.length() == 0)\n", - "\t\t\t\t\treturn \"success\";\n", - "\t\t\t\tSystem.out.println(\"Bad characters: \" + result);\n", - "\t\t\t\treturn \"fail\";\n", - "\t\t\t}\n", - "\t\t);\n", - "\t}\n", - " @TestFactory\n", - "\tStream allowedCharacters() {\n", - "\t\tString lowcase = \"abcdefghijklmnopqrstuvwxyz ,.\";\n", - "\t\tString upcase = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.\";\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Allowed characters (should succeed)\",\n", - " inverter -> {\n", - "\t\t\t\tassertEquals(inverter.invert(lowcase), upcase);\n", - "\t\t\t\tassertEquals(inverter.invert(upcase), lowcase);\n", - "\t\t\t\treturn \"success\";\n", - "\t\t\t}\n", - "\t\t);\n", - "\t}\n", - "\t@TestFactory\n", - "\tStream lengthNoGreaterThan30() {\n", - "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", - "\t\tassertTrue(str.length() > 30);\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Length must be less than 31 (throws exception)\",\n", - "\t\t\tinverter -> inverter.invert(str)\n", - "\t\t);\n", - "\t}\n", - "\t@TestFactory\n", - "\tStream lengthLessThan31() {\n", - "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", - "\t\tassertTrue(str.length() < 31);\n", - "\t\treturn testVersions(\n", - "\t\t\t\"Length must be less than 31 (should succeed)\",\n", - "\t\t\tinverter -> inverter.invert(str)\n", - "\t\t);\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在一般的测试中,你可能认为在进行一个结果为失败的测试时应该停止代码构建。 但是在这里,我们只希望系统报告问题,但仍然继续运行,以便你可以看到不同版本的 **StringInverter** 的效果。\n", - "\n", - "每个使用 **@TestFactory** 注释的方法都会生成一个 **DynamicTest** 对象的 **Stream**(通过 **testVersions()** ),每个 JUnit 都像常规的 **@Test** 方法一样执行。\n", - "\n", - "现在测试都已经准备好了,我们就可以开始实现 **StringInverter **了。 我们从一个仅返回其参数的假的实现类开始:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Inverter1.java\n", - "package validating;\n", - "public class Inverter1 implements StringInverter {\n", - "\tpublic String invert(String str) { return str; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接下来我们实现反转操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Inverter2.java\n", - "package validating;\n", - "import static java.lang.Character.*;\n", - "public class Inverter2 implements StringInverter {\n", - "\tpublic String invert(String str) {\n", - "\t\tString result = \"\";\n", - "\t\tfor(int i = 0; i < str.length(); i++) {\n", - "\t\t\tchar c = str.charAt(i);\n", - "\t\t\tresult += isUpperCase(c) ?\n", - "\t\t\t\t\t toLowerCase(c) :\n", - "\t\t\t\t\t toUpperCase(c);\n", - "\t\t}\n", - "\t\treturn result;\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在添加代码以确保输入不超过30个字符:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Inverter3.java\n", - "package validating;\n", - "import static java.lang.Character.*;\n", - "public class Inverter3 implements StringInverter {\n", - "\tpublic String invert(String str) {\n", - "\t\tif(str.length() > 30)\n", - "\t\t\tthrow new RuntimeException(\"argument too long!\");\n", - "\t\tString result = \"\";\n", - "\t\tfor(int i = 0; i < str.length(); i++) {\n", - "\t\t\tchar c = str.charAt(i);\n", - "\t\t\tresult += isUpperCase(c) ?\n", - "\t\t\t\t\t toLowerCase(c) :\n", - "\t\t\t\t\t toUpperCase(c);\n", - " }\n", - "\t\treturn result;\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最后,我们排除了不允许的字符:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/Inverter4.java\n", - "package validating;\n", - "import static java.lang.Character.*;\n", - "public class Inverter4 implements StringInverter {\n", - "\tstatic final String ALLOWED =\n", - "\t\t\"abcdefghijklmnopqrstuvwxyz ,.\" +\n", - "\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n", - "\tpublic String invert(String str) {\n", - "\t\tif(str.length() > 30)\n", - "\t\t\tthrow new RuntimeException(\"argument too long!\");\n", - "\t\tString result = \"\";\n", - "\t\tfor(int i = 0; i < str.length(); i++) {\n", - "\t\t\tchar c = str.charAt(i);\n", - "\t\t\tif(ALLOWED.indexOf(c) == -1)\n", - "\t\t\t\tthrow new RuntimeException(c + \" Not allowed\");\n", - "\t\t\tresult += isUpperCase(c) ?\n", - "\t\t\t\t\t toLowerCase(c) :\n", - "\t\t\t\t\t toUpperCase(c);\n", - "\t\t}\n", - "\t\treturn result;\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你将从测试输出中看到,每个版本的 **Inverter** 都几乎能通过所有测试。 当你在进行测试优先的开发时会有相同的体验。\n", - "\n", - "**DynamicStringInverterTests.java** 仅是为了显示 TDD 过程中不同 **StringInverter** 实现的开发。 通常,你只需编写一组如下所示的测试,并修改单个 **StringInverter** 类直到它满足所有测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/tests/StringInverterTests.java\n", - "package validating;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import org.junit.jupiter.api.*;\n", - "import static org.junit.jupiter.api.Assertions.*;\n", - "\n", - "public class StringInverterTests {\n", - "\tStringInverter inverter = new Inverter4();\n", - "\t@BeforeAll\n", - "\tstatic void startMsg() {\n", - "\t\tSystem.out.println(\">>> StringInverterTests <<<\");\n", - "\t}\n", - " @Test\n", - "\tvoid basicInversion1() {\n", - "\t\tString in = \"Exit, Pursued by a Bear.\";\n", - "\t\tString out = \"eXIT, pURSUED BY A bEAR.\";\n", - "\t\tassertEquals(inverter.invert(in), out);\n", - "\t}\n", - "\t@Test\n", - "\tvoid basicInversion2() {\n", - "\t\texpectThrows(Error.class, () -> {\n", - "\t\t\tassertEquals(inverter.invert(\"X\"), \"X\");\n", - "\t\t});\n", - "\t}\n", - "\t@Test\n", - "\tvoid disallowedCharacters() {\n", - "\t\tString disallowed = \";-_()*&^%$#@!~`0123456789\";\n", - "\t\tString result = disallowed.chars()\n", - "\t\t\t.mapToObj(c -> {\n", - "\t\t\t\tString cc = Character.toString((char)c);\n", - "\t\t\t\ttry {\n", - "\t\t\t\t\tinverter.invert(cc);\n", - "\t\t\t\t\treturn \"\";\n", - "\t\t\t\t} catch(RuntimeException e) {\n", - "\t\t\t\t\treturn cc;\n", - "\t\t\t\t}\n", - "\t\t\t}).collect(Collectors.joining(\"\"));\n", - "\t\tassertEquals(result, disallowed);\n", - "\t}\n", - "\t@Test\n", - "\tvoid allowedCharacters() {\n", - "\t\tString lowcase = \"abcdefghijklmnopqrstuvwxyz ,.\";\n", - "\t\tString upcase = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.\";\n", - "\t\tassertEquals(inverter.invert(lowcase), upcase);\n", - "\t\tassertEquals(inverter.invert(upcase), lowcase);\n", - "\t}\n", - "\t@Test\n", - "\tvoid lengthNoGreaterThan30() {\n", - "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", - "\t\tassertTrue(str.length() > 30);\n", - "\t\texpectThrows(RuntimeException.class, () -> {\n", - "\t\t\tinverter.invert(str);\n", - "\t\t});\n", - "\t}\n", - " @Test\n", - "\tvoid lengthLessThan31() {\n", - "\t\tString str = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\";\n", - "\t\tassertTrue(str.length() < 31);\n", - "\t\tinverter.invert(str);\n", - "\t}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以通过这种方式进行开发:一开始在测试中建立你期望程序应有的所有特性,然后你就能在实现中一步步添加功能,直到所有测试通过。 完成后,你还可以在将来通过这些测试来得知(或让其他任何人得知)当修复错误或添加功能时,代码是否被破坏了。 TDD的目标是产生更好,更周全的测试,因为在完全实现之后尝试实现完整的测试覆盖通常会产生匆忙或无意义的测试。\n", - "\n", - "### 测试驱动 vs. 测试优先\n", - "\n", - "虽然我自己还没有达到测试优先的意识水平,但我最感兴趣的是来自测试优先中的“测试失败的书签”这一概念。 当你离开你的工作一段时间后,重新回到工作进展中,甚至找到你离开时工作到的地方有时会很有挑战性。 然而,以失败的测试为书签能让你找到之前停止的地方。 这似乎让你能更轻松地暂时离开你的工作,因为不用担心找不到工作进展的位置。\n", - "\n", - "纯粹的测试优先编程的主要问题是它假设你事先了解了你正在解决的问题。 根据我自己的经验,我通常是从实验开始,而只有当我处理问题一段时间后,我对它的理解才会达到能给它编写测试的程度。 当然,偶尔会有一些问题在你开始之前就已经完全定义,但我个人并不常遇到这些问题。 实际上,可能用“*面向测试的开发* ( *Test-Oriented Development* )”这个短语来描述编写测试良好的代码或许更好。\n", - "\n", - "\n", - "\n", - "## 日志\n", - "\n", - "### 日志会给出正在运行的程序的各种信息。\n", - "\n", - "在调试程序中,日志可以是普通状态数据,用于显示程序运行过程(例如,安装程序可能会记录安装过程中采取的步骤,存储文件的目录,程序的启动值等)。\n", - "\n", - "在调试期间,日志也能带来好处。 如果没有日志,你可能会尝试通过插入 **println()** 语句来打印出程序的行为。 本书中的一些例子使用了这种技术,并且在没有调试器的情况下(下文中很快就会介绍这样一个主题),它就是你唯一的工具。 但是,一旦你确定程序正常运行,你可能会将 **println()** 语句注释或者删除。 然而,如果你遇到更多错误,你可能又需要运行它们。因此,如果能够只在需要时轻松启用输出程序状态就好多了。\n", - "\n", - "程序员在日志包可供使用之前,都只能依赖 Java 编译器移除未调用的代码。 如果 **debug** 是一个 **static final boolean **,你就可以这么写:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(debug) {\n", - "\tSystem.out.println(\"Debug info\");\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,当 **debug **为 **false **时,编译器将移除大括号内的代码。 因此,未调用的代码不会对运行时产生影响。 使用这种方法,你可以在整个程序中放置跟踪代码,并轻松启用和关闭它。 但是,该技术的一个缺点是你必须重新编译代码才能启用和关闭跟踪语句。因此,通过更改配置文件来修改日志属性,从而起到启用跟踪语句但不用重新编译程序会更方便。\n", - "\n", - "业内普遍认为标准 Java 发行版本中的日志包 **(java.util.logging)** 的设计相当糟糕。 大多数人会选择其他的替代日志包。如 *Simple Logging Facade for Java(SLF4J)* ,它为多个日志框架提供了一个封装好的调用方式,这些日志框架包括 **java.util.logging** , **logback** 和 **log4j **。 SLF4J 允许用户在部署时插入所需的日志框架。\n", - "\n", - "SLF4J 提供了一个复杂的工具来报告程序的信息,它的效率与前面示例中的技术几乎相同。 对于非常简单的信息日志记录,你可以执行以下操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/SLF4JLogging.java\n", - "import org.slf4j.*;\n", - "public class SLF4JLogging {\n", - "\tprivate static Logger log =\n", - "\t\tLoggerFactory.getLogger(SLF4JLogging.class);\n", - "\tpublic static void main(String[] args) {\n", - "\t\tlog.info(\"hello logging\");\n", - "\t}\n", - "}\n", - "/* Output:\n", - "2017-05-09T06:07:53.418\n", - "[main] INFO SLF4JLogging - hello logging\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "日志输出中的格式和信息,甚至输出是否正常或“错误”都取决于 SLF4J 所连接的后端程序包是怎样实现的。 在上面的示例中,它连接到的是 **logback** 库(通过本书的 **build.gradle** 文件),并显示为标准输出。\n", - "\n", - "如果我们修改 **build.gradle** 从而使用内置在 JDK 中的日志包作为后端,则输出显示为错误输出,如下所示:\n", - "\n", - "**Aug 16, 2016 5:40:31 PM InfoLogging main**\n", - "**INFO: hello logging**\n", - "\n", - "日志系统会检测日志消息处所在的类名和方法名。 但它不能保证这些名称是正确的,所以不要纠结于其准确性。\n", - "\n", - "### 日志等级\n", - "\n", - "SLF4J 提供了多个等级的日志消息。下面这个例子以“严重性”的递增顺序对它们作出演示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/SLF4JLevels.java\n", - "import org.slf4j.*;\n", - "public class SLF4JLevels {\n", - "\tprivate static Logger log =\n", - "\t\tLoggerFactory.getLogger(SLF4JLevels.class);\n", - "\tpublic static void main(String[] args) {\n", - "\t\tlog.trace(\"Hello\");\n", - "\t\tlog.debug(\"Logging\");\n", - "\t\tlog.info(\"Using\");\n", - "\t\tlog.warn(\"the SLF4J\");\n", - "\t\tlog.error(\"Facade\");\n", - "\t}\n", - "}\n", - "/* Output:\n", - "2017-05-09T06:07:52.846\n", - "[main] TRACE SLF4JLevels - Hello\n", - "2017-05-09T06:07:52.849\n", - "[main] DEBUG SLF4JLevels - Logging\n", - "2017-05-09T06:07:52.849\n", - "[main] INFO SLF4JLevels - Using\n", - "2017-05-09T06:07:52.850\n", - "[main] WARN SLF4JLevels - the SLF4J\n", - "2017-05-09T06:07:52.851\n", - "[main] ERROR SLF4JLevels - Facade\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以按等级来查找消息。 级别通常设置在单独的配置文件中,因此你可以重新配置而无需重新编译。 配置文件格式取决于你使用的后端日志包实现。 如 **logback** 使用 XML :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "xml" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\n", - "\n", - "\n", - "\t\n", - "\t\t\n", - "\t\t\t\n", - "%d{yyyy-MM-dd'T'HH:mm:ss.SSS}\n", - "[%thread] %-5level %logger - %msg%n\n", - "\t\t\t\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\t\n", - "\t\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以尝试将 ** **行更改为其他级别,然后重新运行该程序查看日志输出的更改情况。 如果你没有写 **logback.xml** 文件,日志系统将采取默认配置。\n", - "\n", - "这只是 SLF4J 最简单的介绍和一般的日志消息,但也足以作为使用日志的基础 - 你可以沿着这个进行更长久的学习和实践。你可以查阅 [SLF4J 文档](http://www.slf4j.org/manual.html)来获得更深入的信息。\n", - "\n", - "\n", - "\n", - "## 调试\n", - "\n", - "尽管聪明地使用 **System.out** 或日志信息能给我们带来对程序行为的有效见解,但对于困难问题来说,这种方式就显得笨拙且耗时了。\n", - "\n", - "你也可能需要更加深入地理解程序,仅依靠打印日志做不到。此时你需要调试器。除了比打印语句更快更轻易地展示信息以外,调试器还可以设置断点,并在程序运行到这些断点处暂停程序。\n", - "\n", - "使用调试器,可以展示任何时刻的程序状态,查看变量的值,一步一步运行程序,连接远程运行的程序等等。特别是当你构建较大规模的系统(bug 容易被掩埋)时,熟练使用调试器是值得的。\n", - "\n", - "### 使用 JDB 调试\n", - "\n", - "Java 调试器(JDB)是 JDK 内置的命令行工具。从调试的指令和命令行接口两方面看的话,JDB 至少从概念上是 GNU 调试器(GDB,受 Unix DB 的影响)的继承者。JDB 对于学习调试和执行简单的调试任务来说是有用的,而且知道只要安装了 JDK 就可以使用 JDB 是有帮助的。然而,对于大型项目来说,你可能想要一个图形化的调试器,这在后面会描述。\n", - "\n", - "假设你写了如下程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/SimpleDebugging.java\n", - "// {ThrowsException}\n", - "public class SimpleDebugging {\n", - " private static void foo1() {\n", - " System.out.println(\"In foo1\");\n", - " foo2();\n", - " }\n", - " \n", - " private static void foo2() {\n", - " System.out.println(\"In foo2\");\n", - " foo3();\n", - " }\n", - " \n", - " private static void foo3() {\n", - " System.out.println(\"In foo3\");\n", - " int j = 1;\n", - " j--;\n", - " int i = 5 / j;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " foo1();\n", - " }\n", - "}\n", - "/* Output\n", - "In foo1\n", - "In foo2\n", - "In foo3\n", - "__[Error Output]__\n", - "Exception in thread \"main\"\n", - "java.lang.ArithmeticException: /by zero \n", - "at \n", - "SimpleDebugging.foo3(SimpleDebugging.java:17)\n", - "at \n", - "SimpleDebugging.foo2(SimpleDebugging.java:11)\n", - "at\n", - "SimpleDebugging.foo1(SimpleDebugging.java:7)\n", - "at\n", - "SimpleDebugging.main(SimpleDebugging.java:20)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "首先看方法 `foo3()`,问题很明显:除数是 0。但是假如这段代码被埋没在大型程序中(像这里的调用序列暗示的那样)而且你不知道从哪儿开始查找问题。结果呢,异常会给出足够的信息让你定位问题。然而,假设事情更加复杂,你必须更加深入程序中来获得比异常提供的更多的信息。\n", - "\n", - "为了运行 JDB,你需要在编译 **SimpleDebugging.java** 时加上 **-g** 标记,从而告诉编译器生成编译信息。然后使用如下命令开始调试程序:\n", - "\n", - "**jdb SimpleDebugging**\n", - "\n", - "接着 JDB 就会运行,出现命令行提示。你可以输入 **?** 查看可用的 JDB 命令。\n", - "\n", - "这里展示了如何使用交互式追踪一个问题的调试历程:\n", - "\n", - "**Initializing jdb...**\n", - "\n", - "**> catch Exception**\n", - "\n", - "`>` 表明 JDB 在等待输入命令。命令 **catch Exception** 在任何抛出异常的地方设置断点(然而,即使你不显式地设置断点,调试器也会停止— JDB 中好像是默认在异常抛出处设置了异常)。接着命令行会给出如下响应:\n", - "\n", - "**Deferring exception catch Exception.**\n", - "\n", - "**It will be set after the class is loaded.**\n", - "\n", - "继续输入:\n", - "\n", - "**> run**\n", - "\n", - "现在程序将运行到下个断点处,在这个例子中就是异常发生的地方。下面是运行 **run** 命令的结果:\n", - "\n", - "**run SimpleDebugging**\n", - "\n", - "**Set uncaught java.lang.Throwable**\n", - "\n", - "**Set deferred uncaught java.lang.Throwable**\n", - "\n", - "**>**\n", - "\n", - "**VM Started: In foo1**\n", - "\n", - "**In foo2**\n", - "\n", - "**In foo3**\n", - "\n", - "**Exception occurred: java.lang.ArithmeticException**\n", - "\n", - "**(uncaught)\"thread=main\",**\n", - "\n", - "**SimpleDebugging.foo3(),line=16 bci=15**\n", - "\n", - "**16 int i = 5 / j**\n", - "\n", - "程序运行到第16行时发生异常,但是 JDB 在异常发生时就不复存在。调试器还展示了是哪一行导致了异常。你可以使用 **list** 将导致程序终止的执行点列出来:\n", - "\n", - "**main[1] list**\n", - "\n", - "**12 private static void foo3() {**\n", - "\n", - "**13 System.out.println(\"In foo3\");**\n", - "\n", - "**14 int j = 1;**\n", - "\n", - "**15 j--;**\n", - "\n", - "**16 => int i = 5 / j;**\n", - "\n", - "**17 }**\n", - "\n", - "**18 public static void main(String[] args) {**\n", - "\n", - "**19 foo1();**\n", - "\n", - "**20 }**\n", - "\n", - "**21 }**\n", - "\n", - "**/* Output:**\n", - "\n", - "上述 `=>` 展示了程序将继续运行的执行点。你可以使用命令 **cont**(continue) 继续运行,但是会导致 JDB 在异常发生时退出并打印出栈轨迹信息。\n", - "\n", - "命令 **locals** 能转储所有的局部变量值:\n", - "\n", - "**main[1] locals**\n", - "\n", - "**Method arguments:**\n", - "\n", - "**Local variables:**\n", - "\n", - "**j = 0**\n", - "\n", - "命令 **wherei** 打印进入当前线程的方法栈中的栈帧信息:\n", - "\n", - "**main[1] wherei**\n", - "\n", - "**[1] SimpleDebugging.foo3(SimpleDebugging.java:16), pc =15**\n", - "\n", - "**[2] SimpleDebugging.foo2(SimpleDebugging.java:10), pc = 8**\n", - "\n", - "**[3] SimpleDebugging.foo1(SimpleDebugging.java:6), pc = 8**\n", - "\n", - "**[4] SimpleDebugging.main(SimpleDebugging.java:19), pc = 10**\n", - "\n", - "**wherei** 后的每一行代表一个方法调用和调用返回点(由程序计数器显示数值)。这里的调用序列是 **main()**, **foo1()**, **foo2()** 和 **foo3()**。\n", - "\n", - "因为命令 **list** 展示了执行停止的地方,所以你通常有足够的信息得知发生了什么并修复它。命令 **help** 将会告诉你更多关于 **jdb** 的用法,但是在花更多的时间学习它之前必须明白命令行调试器往往需要花费更多的精力得到结果。使用 **jdb** 学习调试的基础部分,然后转而学习图形界面调试器。\n", - "\n", - "### 图形化调试器\n", - "\n", - "使用类似 JDB 的命令行调试器是不方便的。它需要显式的命令去查看变量的状态(**locals**, **dump**),列出源代码中的执行点(**list**),查找系统中的线程(**threads**),设置断点(**stop in**, **stop at**)等等。使用图形化调试器只需要点击几下,不需要使用显式的命令就能使用这些特性,而且能查看被调试程序的最新细节。\n", - "\n", - "因此,尽管你可能一开始用 JDB 尝试调试,但是你将发现使用图形化调试器能更加高效、更快速地追踪 bug。IBM 的 Eclipse,Oracle 的 NetBeans 和 JetBrains 的 IntelliJ 这些集成开发环境都含有面向 Java 语言的好用的图形化调试器。\n", - "\n", - "\n", - "\n", - "## 基准测试\n", - "\n", - "> 我们应该忘掉微小的效率提升,说的就是这些 97% 的时间做的事:过早的优化是万恶之源。\n", - ">\n", - "> ​ —— Donald Knuth\n", - "\n", - "如果你发现自己正在过早优化的滑坡上,你可能浪费了几个月的时间(如果你雄心勃勃的话)。通常,一个简单直接的编码方法就足够好了。如果你进行了不必要的优化,就会使你的代码变得无谓的复杂和难以理解。\n", - "\n", - "基准测试意味着对代码或算法片段进行计时看哪个跑得更快,与下一节的分析和优化截然相反,分析优化是观察整个程序,找到程序中最耗时的部分。\n", - "\n", - "可以简单地对一个代码片段的执行计时吗?在像 C 这样直接的编程语言中,这个方法的确可行。在像 Java 这样拥有复杂的运行时系统的编程语言中,基准测试变得更有挑战性。为了生成可靠的数据,环境设置必须控制诸如 CPU 频率,节能特性,其他运行在相同机器上的进程,优化器选项等等。\n", - "\n", - "### 微基准测试\n", - "\n", - "写一个计时工具类从而比较不同代码块的执行速度是具有吸引力的。看上去这会产生一些有用的数据。比如,这里有一个简单的 **Timer** 类,可以用以下两种方式使用它:\n", - "\n", - "1. 创建一个 **Timer** 对象,执行一些操作然后调用 **Timer** 的 **duration()** 方法产生以毫秒为单位的运行时间。\n", - "2. 向静态的 **duration()** 方法中传入 **Runnable**。任何符合 **Runnable** 接口的类都有一个函数式方法 **run()**,该方法没有入参,且没有返回。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Timer.java\n", - "package onjava;\n", - "import static java.util.concurrent.TimeUnit.*;\n", - "\n", - "public class Timer {\n", - " private long start = System.nanoTime();\n", - " \n", - " public long duration() {\n", - " return NANOSECONDS.toMillis(System.nanoTime() - start);\n", - " }\n", - " \n", - " public static long duration(Runnable test) {\n", - " Timer timer = new Timer();\n", - " test.run();\n", - " return timer.duration();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个很直接的计时方式。难道我们不能只运行一些代码然后看它的运行时长吗?\n", - "\n", - "有许多因素会影响你的结果,即使是生成提示符也会造成计时的混乱。这里举一个看上去天真的例子,它使用了 标准的 Java **Arrays** 库(后面会详细介绍):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/BadMicroBenchmark.java\n", - "// {ExcludeFromTravisCI}\n", - "import java.util.*;\n", - "import onjava.Timer;\n", - "\n", - "public class BadMicroBenchmark {\n", - " static final int SIZE = 250_000_000;\n", - " \n", - " public static void main(String[] args) {\n", - " try { // For machines with insufficient memory\n", - " long[] la = new long[SIZE];\n", - " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> n)));\n", - " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> n)));\n", - " } catch (OutOfMemoryError e) {\n", - " System.out.println(\"Insufficient memory\");\n", - " System.exit(0);\n", - " }\n", - " }\n", - " \n", - "}\n", - "/* Output\n", - "setAll: 272\n", - "parallelSetAll: 301" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**main()** 方法的主体包含在 **try** 语句块中,因为一台机器用光内存后会导致构建停止。\n", - "\n", - "对于一个长度为 250,000,000 的 **long** 型(仅仅差一点就会让大部分机器内存溢出)数组,我们比较了 **Arrays.setAll()** 和 **Arrays.parallelSetAll()** 的性能。这个并行的版本会尝试使用多个处理器加快完成任务(尽管我在这一节谈到了一些并行的概念,但是在 [并发编程](./24-Concurrent-Programming.md) 章节我们才会详细讨论这些 )。然而非并行的版本似乎运行得更快,尽管在不同的机器上结果可能不同。\n", - "\n", - "**BadMicroBenchmark.java** 中的每一步操作都是独立的,但是如果你的操作依赖于同一资源,那么并行版本运行的速度会骤降,因为不同的进程会竞争相同的那个资源。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/BadMicroBenchmark2.java\n", - "// Relying on a common resource\n", - "\n", - "import java.util.*;\n", - "import onjava.Timer;\n", - "\n", - "public class BadMicroBenchmark2 {\n", - " static final int SIZE = 5_000_000;\n", - " \n", - " public static void main(String[] args) {\n", - " long[] la = new long[SIZE];\n", - " Random r = new Random();\n", - " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> r.nextLong())));\n", - " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> r.nextLong())));\n", - " SplittableRandom sr = new SplittableRandom();\n", - " System.out.println(\"parallelSetAll: \" + Timer.duration(() -> Arrays.parallelSetAll(la, n -> sr.nextLong())));\n", - " System.out.println(\"setAll: \" + Timer.duration(() -> Arrays.setAll(la, n -> sr.nextLong())));\n", - " }\n", - "}\n", - "/* Output\n", - "parallelSetAll: 1147\n", - "setAll: 174\n", - "parallelSetAll: 86\n", - "setAll: 39" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**SplittableRandom** 是为并行算法设计的,它当然看起来比普通的 **Random** 在 **parallelSetAll()** 中运行得更快。 但是看上去还是比非并发的 **setAll()** 运行时间更长,有点难以置信(也许是真的,但我们不能通过一个坏的微基准测试得到这个结论)。\n", - "\n", - "这只考虑了微基准测试的问题。Java 虚拟机 Hotspot 也非常影响性能。如果你在测试前没有通过运行代码给 JVM 预热,那么你就会得到“冷”的结果,不能反映出代码在 JVM 预热之后的运行速度(假如你运行的应用没有在预热的 JVM 上运行,你就可能得不到所预期的性能,甚至可能减缓速度)。\n", - "\n", - "优化器有时可以检测出你创建了没有使用的东西,或者是部分代码的运行结果对程序没有影响。如果它优化掉你的测试,那么你可能得到不好的结果。\n", - "\n", - "一个良好的微基准测试系统能自动地弥补像这样的问题(和很多其他的问题)从而产生合理的结果,但是创建这么一套系统是非常棘手,需要深入的知识。\n", - "\n", - "### JMH 的引入\n", - "\n", - "截止目前为止,唯一能产生像样结果的 Java 微基准测试系统就是 Java Microbenchmarking Harness,简称 JMH。本书的 **build.gradle** 自动引入了 JMH 的设置,所以你可以轻松地使用它。\n", - "\n", - "你可以在命令行编写 JMH 代码并运行它,但是推荐的方式是让 JMH 系统为你运行测试;**build.gradle** 文件已经配置成只需要一条命令就能运行 JMH 测试。\n", - "\n", - "JMH 尝试使基准测试变得尽可能简单。例如,我们将使用 JMH 重新编写 **BadMicroBenchmark.java**。这里只有 **@State** 和 **@Benchmark** 这两个注解是必要的。其余的注解要么是为了产生更多易懂的输出,要么是加快基准测试的运行速度(JMH 基准测试通常需要运行很长时间):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/jmh/JMH1.java\n", - "package validating.jmh;\n", - "import java.util.*;\n", - "import org.openjdk.jmh.annotations.*;\n", - "import java.util.concurrent.TimeUnit;\n", - "\n", - "@State(Scope.Thread)\n", - "@BenchmarkMode(Mode.AverageTime)\n", - "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", - "// Increase these three for more accuracy:\n", - "@Warmup(iterations = 5)\n", - "@Measurement(iterations = 5)\n", - "@Fork(1)\n", - "public class JMH1 {\n", - " private long[] la;\n", - " \n", - " @Setup\n", - " public void setup() {\n", - " la = new long[250_000_000];\n", - " }\n", - " \n", - " @Benchmark\n", - " public void setAll() {\n", - " Arrays.setAll(la, n -> n);\n", - " }\n", - " \n", - " public void parallelSetAll() {\n", - " Arrays.parallelSetAll(la, n -> n);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "“forks” 的默认值是 10,意味着每个测试都运行 10 次。为了减少运行时间,这里使用了 **@Fork** 注解来减少这个次数到 1。我还使用了 **@Warmup** 和 **@Measurement** 注解将它们默认的运行次数从 20 减少到 5 次。尽管这降低了整体的准确率,但是结果几乎与使用默认值相同。可以尝试将 **@Warmup**、**@Measurement** 和 **@Fork** 都注释掉然后看使用它们的默认值,结果会有多大显著的差异;一般来说,你应该只能看到长期运行的测试使错误因素减少,而结果没有多大变化。\n", - "\n", - "需要使用显式的 gradle 命令才能运行基准测试(在示例代码的根目录处运行)。这能防止耗时的基准测试运行其他的 **gradlew** 命令:\n", - "\n", - "**gradlew validating:jmh**\n", - "\n", - "这会花费几分钟的时间,取决于你的机器(如果没有注解上的调整,可能需要几个小时)。控制台会显示 **results.txt** 文件的路径,这个文件统计了运行结果。注意,**results.txt** 包含这一章所有 **jmh** 测试的结果:**JMH1.java**,**JMH2.java** 和 **JMH3.java**。\n", - "\n", - "因为输出是绝对时间,所以在不同的机器和操作系统上结果各不相同。重要的因素不是绝对时间,我们真正观察的是一个算法和另一个算法的比较,尤其是哪一个运行得更快,快多少。如果你在自己的机器上运行测试,你将看到不同的结果却有着相同的模式。\n", - "\n", - "我在大量的机器上运行了这些测试,尽管不同的机器上得到的绝对值结果不同,但是相对值保持着合理的稳定性。我只列出了 **results.txt** 中适当的片段并加以编辑使输出更加易懂,而且内容大小适合页面。所有测试中的 **Mode** 都以 **avgt** 展示,代表 “平均时长”。**Cnt**(测试的数目)的值是 200,尽管这里的一个例子中配置的 **Cnt** 值是 5。**Units** 是 **us/op**,是 “Microseconds per operation” 的缩写,因此,这个值越小代表性能越高。\n", - "\n", - "我同样也展示了使用 warmups、measurements 和 forks 默认值的输出。我删除了示例中相应的注解,就是为了获取更加准确的测试结果(这将花费数小时)。结果中数字的模式应该仍然看起来相同,不论你如何运行测试。\n", - "\n", - "下面是 **JMH1.java** 的运行结果:\n", - "\n", - "**Benchmark Score**\n", - "\n", - "**JMH1.setAll 196280.2**\n", - "\n", - "**JMH1.parallelSetAll 195412.9**\n", - "\n", - "即使像 JMH 这么高级的基准测试工具,基准测试的过程也不容易,练习时需要倍加小心。这里测试产生了反直觉的结果:并行的版本 **parallelSetAll()** 花费了与非并行版本的 **setAll()** 相同的时间,两者似乎都运行了相当长的时间。\n", - "\n", - "当创建这个示例时,我假设如果我们要测试数组初始化的话,那么使用非常大的数组是有意义的。所以我选择了尽可能大的数组;如果你实验的话会发现一旦数组的大小超过 2亿5000万,你就开始会得到内存溢出的异常。然而,在这么大的数组上执行大量的操作从而震荡内存系统,产生无法预料的结果是有可能的。不管这个假设是否正确,看上去我们正在测试的并非是我们想测试的内容。\n", - "\n", - "考虑其他的因素:\n", - "\n", - "C:客户端执行操作的线程数量\n", - "\n", - "P:并行算法使用的并行数量\n", - "\n", - "N:数组的大小:**10^(2*k)**,通常来说,**k=1..7** 足够来练习不同的缓存占用。\n", - "\n", - "Q:setter 的操作成本\n", - "\n", - "这个 C/P/N/Q 模型在早期 JDK 8 的 Lambda 开发期间浮出水面,大多数并行的 Stream 操作(**parallelSetAll()** 也基本相似)都满足这些结论:**N*Q**(主要工作量)对于并发性能尤为重要。并行算法在工作量较少时可能实际运行得更慢。\n", - "\n", - "在一些情况下操作竞争如此激烈使得并行毫无帮助,而不管 **N*Q** 有多大。当 **C** 很大时,**P** 就变得不太相关(内部并行在大量的外部并行面前显得多余)。此外,在一些情况下,并行分解会让相同的 **C** 个客户端运行得比它们顺序运行代码更慢。\n", - "\n", - "基于这些信息,我们重新运行测试,并在这些测试中使用不同大小的数组(改变 **N**):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/jmh/JMH2.java\n", - "package validating.jmh;\n", - "import java.util.*;\n", - "import org.openjdk.jmh.annotations.*;\n", - "import java.util.concurrent.TimeUnit;\n", - "\n", - "@State(Scope.Thread)\n", - "@BenchmarkMode(Mode.AverageTime)\n", - "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", - "@Warmup(iterations = 5)\n", - "@Measurement(iterations = 5)\n", - "@Fork(1)\n", - "public class JMH2 {\n", - "\n", - " private long[] la;\n", - "\n", - " @Param({\n", - " \"1\",\n", - " \"10\",\n", - " \"100\",\n", - " \"1000\",\n", - " \"10000\",\n", - " \"100000\",\n", - " \"1000000\",\n", - " \"10000000\",\n", - " \"100000000\",\n", - " \"250000000\"\n", - " })\n", - " int size;\n", - "\n", - " @Setup\n", - " public void setup() {\n", - " la = new long[size];\n", - " }\n", - "\n", - " @Benchmark\n", - " public void setAll() {\n", - " Arrays.setAll(la, n -> n);\n", - " }\n", - "\n", - " @Benchmark\n", - " public void parallelSetAll() {\n", - " Arrays.parallelSetAll(la, n -> n);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@Param** 会自动地将其自身的值注入到变量中。其自身的值必须是字符串类型,并可以转化为适当的类型,在这个例子中是 **int** 类型。\n", - "\n", - "下面是已经编辑过的结果,包含精确计算出的加速数值:\n", - "\n", - "| JMH2 Benchmark | Size | Score % | Speedup |\n", - "| ------------------ | --------- | ---------- | ------- |\n", - "| **setAll** | 1 | 0.001 | |\n", - "| **parallelSetAll** | 1 | 0.036 | 0.028 |\n", - "| **setAll** | 10 | 0.005 | |\n", - "| **parallelSetAll** | 10 | 3.965 | 0.001 |\n", - "| **setAll** | 100 | 0.031 | |\n", - "| **parallelSetAll** | 100 | 3.145 | 0.010 |\n", - "| **setAll** | 1000 | 0.302 | |\n", - "| **parallelSetAll** | 1000 | 3.285 | 0.092 |\n", - "| **setAll** | 10000 | 3.152 | |\n", - "| **parallelSetAll** | 10000 | 9.669 | 0.326 |\n", - "| **setAll** | 100000 | 34.971 | |\n", - "| **parallelSetAll** | 100000 | 20.153 | 1.735 |\n", - "| **setAll** | 1000000 | 420.581 | |\n", - "| **parallelSetAll** | 1000000 | 165.388 | 2.543 |\n", - "| **setAll** | 10000000 | 8160.054 | |\n", - "| **parallelSetAll** | 10000000 | 7610.190 | 1.072 |\n", - "| **setAll** | 100000000 | 79128.752 | |\n", - "| **parallelSetAll** | 100000000 | 76734.671 | 1.031 |\n", - "| **setAll** | 250000000 | 199552.121 | |\n", - "| **parallelSetAll** | 250000000 | 191791.927 | 1.040 |\n", - "可以看到当数组大小达到 10 万左右时,**parallelSetAll()** 开始反超,而后趋于与非并行的运行速度相同。即使它运行速度上胜了,看起来也不足以证明由于并行的存在而使速度变快。\n", - "\n", - "**setAll()/parallelSetAll()** 中工作的计算量起很大影响吗?在前面的例子中,我们所做的只有对数组的赋值操作,这可能是最简单的任务。所以即使 **N** 值变大,**N*Q** 也仍然没有达到巨大,所以看起来像是我们没有为并行提供足够的机会(JMH 提供了一种模拟变量 Q 的途径;如果想了解更多的话,可搜索 **Blackhole.consumeCPU**)。\n", - "\n", - "我们通过使方法 **f()** 中的任务变得更加复杂,从而产生更多的并行机会:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// validating/jmh/JMH3.java\n", - "package validating.jmh;\n", - "import java.util.*;\n", - "import org.openjdk.jmh.annotations.*;\n", - "import java.util.concurrent.TimeUnit;\n", - "\n", - "@State(Scope.Thread)\n", - "@BenchmarkMode(Mode.AverageTime)\n", - "@OutputTimeUnit(TimeUnit.MICROSECONDS)\n", - "@Warmup(iterations = 5)\n", - "@Measurement(iterations = 5)\n", - "@Fork(1)\n", - "public class JMH3 {\n", - " private long[] la;\n", - "\n", - " @Param({\n", - " \"1\",\n", - " \"10\",\n", - " \"100\",\n", - " \"1000\",\n", - " \"10000\",\n", - " \"100000\",\n", - " \"1000000\",\n", - " \"10000000\",\n", - " \"100000000\",\n", - " \"250000000\"\n", - " })\n", - " int size;\n", - "\n", - " @Setup\n", - " public void setup() {\n", - " la = new long[size];\n", - " }\n", - "\n", - " public static long f(long x) {\n", - " long quadratic = 42 * x * x + 19 * x + 47;\n", - " return Long.divideUnsigned(quadratic, x + 1);\n", - " }\n", - "\n", - " @Benchmark\n", - " public void setAll() {\n", - " Arrays.setAll(la, n -> f(n));\n", - " }\n", - "\n", - " @Benchmark\n", - " public void parallelSetAll() {\n", - " Arrays.parallelSetAll(la, n -> f(n));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**f()** 方法提供了更加复杂且耗时的操作。现在除了简单的给数组赋值外,**setAll()** 和 **parallelSetAll()** 都有更多的工作去做,这肯定会影响结果。\n", - "\n", - "| JMH2 Benchmark | Size | Score % | Speedup |\n", - "| ------------------ | --------- | ----------- | ------- |\n", - "| **setAll** | 1 | 0.012 | |\n", - "| **parallelSetAll** | 1 | 0.047 | 0.255 |\n", - "| **setAll** | 10 | 0.107 | |\n", - "| **parallelSetAll** | 10 | 3.894 | 0.027 |\n", - "| **setAll** | 100 | 0.990 | |\n", - "| **parallelSetAll** | 100 | 3.708 | 0.267 |\n", - "| **setAll** | 1000 | 133.814 | |\n", - "| **parallelSetAll** | 1000 | 11.747 | 11.391 |\n", - "| **setAll** | 10000 | 97.954 | |\n", - "| **parallelSetAll** | 10000 | 37.259 | 2.629 |\n", - "| **setAll** | 100000 | 988.475 | |\n", - "| **parallelSetAll** | 100000 | 276.264 | 3.578 |\n", - "| **setAll** | 1000000 | 9203.103 | |\n", - "| **parallelSetAll** | 1000000 | 2826.974 | 3.255 |\n", - "| **setAll** | 10000000 | 92144.951 | |\n", - "| **parallelSetAll** | 10000000 | 28126.202 | 3.276 |\n", - "| **setAll** | 100000000 | 921701.863 | |\n", - "| **parallelSetAll** | 100000000 | 266750.543 | 3.455 |\n", - "| **setAll** | 250000000 | 2299127.273 | |\n", - "| **parallelSetAll** | 250000000 | 538173.425 | 4.272 |\n", - "\n", - "可以看到当数组的大小达到 1000 左右时,**parallelSetAll()** 的运行速度反超了 **setAll()**。看来 **parallelSetAll()** 严重依赖数组中计算的复杂度。这正是基准测试的价值所在,因为我们已经得到了关于 **setAll()** 和 **parallelSetAll()** 间微妙的信息,知道在何时使用它们。\n", - "\n", - "这显然不是从阅读 Javadocs 就能得到的。\n", - "\n", - "大多数时候,JMH 的简单应用会产生好的结果(正如你将在本书后面例子中所见),但是我们从这里知道,你不能一直假定 JMH 会产生好的结果。 JMH 网站上的范例可以帮助你开始。\n", - "\n", - "\n", - "\n", - "## 剖析和优化\n", - "\n", - "有时你必须检测程序运行时间花在哪儿,从而看是否可以优化那一块的性能。剖析器可以找到这些导致程序慢的地方,因而你可以找到最轻松,最明显的方式加快程序运行速度。\n", - "\n", - "剖析器收集的信息能显示程序哪一部分消耗内存,哪个方法最耗时。一些剖析器甚至能关闭垃圾回收,从而帮助限定内存分配的模式。\n", - "\n", - "剖析器还可以帮助检测程序中的线程死锁。注意剖析和基准测试的区别。剖析关注的是已经运行在真实数据上的整个程序,而基准测试关注的是程序中隔离的片段,通常是去优化算法。\n", - "\n", - "安装 Java 开发工具包(JDK)时会顺带安装一个虚拟的剖析器,叫做 **VisualVM**。它会被自动安装在与 **javac** 相同的目录下,你的执行路径应该已经包含该目录。启动 VisualVM 的控制台命令是:\n", - "\n", - "**> jvisualvm**\n", - "\n", - "运行该命令后会弹出一个窗口,其中包括一些指向帮助信息的链接。\n", - "\n", - "### 优化准则\n", - "\n", - "- 避免为了性能牺牲代码的可读性。\n", - "- 不要独立地看待性能。衡量与带来的收益相比所需投入的工作量。\n", - "- 程序的大小很重要。性能优化通常只对运行了长时间的大型项目有价值。性能通常不是小项目的关注点。\n", - "- 运行起来程序比一心钻研它的性能具有更高的优先级。一旦你已经有了可工作的程序,如有必要的话,你可以使用剖析器提高它的效率。只有当性能是关键因素时,才需要在设计/开发阶段考虑性能。\n", - "- 不要猜测瓶颈发生在哪。运行剖析器,让剖析器告诉你。\n", - "- 无论何时有可能的话,显式地设置实例为 null 表明你不再用它。这对垃圾收集器来说是个有用的暗示。\n", - "- **static final** 修饰的变量会被 JVM 优化从而提高程序的运行速度。因而程序中的常量应该声明 **static final**。\n", - "\n", - "\n", - "\n", - "## 风格检测\n", - "\n", - "当你在一个团队中工作时(包括尤其是开源项目),让每个人遵循相同的代码风格是非常有帮助的。这样阅读项目的代码时,不会因为风格的不同产生思维上的中断。然而,如果你习惯了某种不同的代码风格,那么记住项目中所有的风格准则对你来说可能是困难的。幸运的是,存在可以指出你代码中不符合风格准则的工具。\n", - "\n", - "一个流行的风格检测器是 **Checkstyle**。查看本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **gradle.build** 和 **checkstyle.xml** 文件中配置代码风格的方式。checkstyle.xml 是一个常用检测的集合,其中一些检测被注释掉了以允许使用本书中的代码风格。\n", - "\n", - "运行所有风格检测的命令是:\n", - "\n", - "**gradlew checkstyleMain**\n", - "\n", - "一些文件仍然产生了风格检测警告,通常是因为这些例子展示了你在生产代码中不会使用的样例。\n", - "\n", - "你还可以针对一个具体的章节运行代码检测。例如,下面命令会运行 [Annotations](./23-Annotations.md) 章节的风格检测:\n", - "\n", - "**gradlew annotations:checkstyleMain**\n", - "\n", - "\n", - "\n", - "## 静态错误分析\n", - "\n", - "尽管 Java 的静态类型检测可以发现基本的语法错误,其他的分析工具可以发现躲避 **javac** 检测的更加复杂的bug。一个这样的工具叫做 **Findbugs**。本书 [示例代码](https://github.com/BruceEckel/OnJava8-Examples) 中的 **build.gradle** 文件包含了 Findbugs 的配置,所以你可以输入如下命令:\n", - "\n", - "**gradlew findbugsMain**\n", - "\n", - "这会为每一章生成一个名为 **main.html** 的报告,报告中会说明代码中潜在的问题。Gradle 命令的输出会告诉你每个报告在何处。\n", - "\n", - "当你查看报告时,你将会看到很多 false positive 的情况,即代码没问题却报告了问题。我在一些文件中展示了不要做一些事的代码确实是正确的。\n", - "\n", - "当我最初看到本书的 Findbugs 报告时,我发现了一些不是技术错误的地方,但能使我改善代码。如果你正在寻找 bug,那么在调试之前运行 Findbugs 是值得的,因为这将可能节省你数小时的时间找到问题。\n", - "\n", - "\n", - "\n", - "## 代码重审\n", - "\n", - "单元测试能找到明显重要的 bug 类型,风格检测和 Findbugs 能自动执行代码重审,从而发现额外的问题。最终你走到了必须人为参与进来的地步。代码重审是一个或一群人的一段代码被另一个或一群人阅读和评估的众多方式之一。这最初看起来会使人不安,而且需要情感信任,但它的目的肯定不是羞辱任何人。它的目标是找到程序中的错误,代码重审是最成功的能做到这点的途径之一。可惜的是,它们也经常被认为是“过于昂贵的”(有时这会成为程序员避免代码被重审时感到尴尬的借口)。\n", - "\n", - "代码重审可以作为结对编程的一部分,作为代码签入过程的一部分(另一个程序员自动安排上审查新代码的任务)或使用群组预排的方式,即每个人阅读代码并讨论之。后一种方式对于分享知识和营造代码文化是极其有益的。\n", - "\n", - "\n", - "\n", - "## 结对编程\n", - "\n", - "结对编程是指两个程序员一起编程的实践活动。通常来说,一个人“驱动”(敲击键盘,输入代码),另一人(观察者或指引者)重审和分析代码,同时也要思考策略。这产生了一种实时的代码重审。通常程序员会定期地互换角色。\n", - "\n", - "结对编程有很多好处,但最显著的是分享知识和防止阻塞。最佳传递信息的方式之一就是一起解决问题,我已经在很多次研讨会使用了结对编程,都取得了很好的效果(同时,研讨会上的众人可以通过这种方式互相了解对方)。而且两个人一起工作时,可以更容易地推进开发的进展,而只有一个程序员的话,可能被轻易地卡住。结对编程的程序员通常可以从工作中感到更高的满足感。有时很难向管理人员们推行结对编程,因为他们可能觉得两个程序员解决同一个问题的效率比他们分开解决不同问题的效率低。尽管短期内是这样,但是结对编程能带来更高的代码质量;除了结对编程的其他益处,如果你眼光长远的话,这会产生更高的生产力。\n", - "\n", - "维基百科上这篇 [结对编程的文章](https://en.wikipedia.org/wiki/Pair_programming) 可以作为你深入了解结对编程的开始。\n", - "\n", - "\n", - "\n", - "## 重构\n", - "\n", - "技术负债是指迭代发展的软件中为了应急而生的丑陋解决方案从而导致设计难以理解,代码难以阅读的部分。特别是当你必须修改和增加新特性的时候,这会造成麻烦。\n", - "\n", - "重构可以矫正技术负债。重构的关键是它能改善代码设计,结构和可读性(因而减少代码负债),但是它不能改变代码的行为。\n", - "\n", - "很难向管理人员推行重构:“我们将投入很多工作不是增加新的特性,当我们完成时,外界无感知变化。但是相信我们,事情会变得更加美好”。不幸的是,管理人员意识到重构的价值时都为时已晚了:当他们提出增加新的特性时,你不得不告诉他们做不到,因为代码基底已经埋藏了太多的问题,试图增加新特性可能会使软件崩溃,即使你能想出怎么做。\n", - "\n", - "### 重构基石\n", - "\n", - "在开始重构代码之前,你需要有以下三个系统的支撑:\n", - "\n", - "1. 测试(通常,JUnit 测试作为最小的根基),因此你能确保重构不会改变代码的行为。\n", - "2. 自动构建,因而你能轻松地构建代码,运行所有的测试。通过这种方式做些小修改并确保修改不会破坏任何事物是毫不费力的。本书使用的是 Gradle 构建系统,你可以在 [代码示例](https://github.com/BruceEckel/OnJava8-Examples) 的 **build.gradle** 文件中查看示例。\n", - "3. 版本控制,以便你能回退到可工作的代码版本,能够一直记录重构的每一步。\n", - "\n", - "本书的代码托管在 [Github](https://github.com/BruceEckel/OnJava8-Examples) 上,使用的是 **git** 版本控制系统。\n", - "\n", - "没有这三个系统的支持,重构几乎是不可能的。确实,没有这些系统,起初维护和增加代码是一个巨大的挑战。令人意外的是,有很多成功的公司竟然在没有这三个系统的情况下在相当长的时间里勉强过得去。然而,对于这样的公司来说,在他们遇到严重的问题之前,这只是个时间问题。\n", - "\n", - "维基百科上的 [重构文章](https://en.wikipedia.org/wiki/Code_refactoring) 提供了更多的细节。\n", - "\n", - "\n", - "\n", - "## 持续集成\n", - "\n", - "在软件开发的早期,人们只能一次处理一步,所以他们坚信他们总是在经历快乐之旅,每个开发阶段无缝进入下一个。这种错觉经常被称为软件开发中的“瀑布流模型”。很多人告诉我瀑布流是他们的选择方法,好像这是一个选择工具,而不仅是一厢情愿。\n", - "\n", - "在这片童话的土地上,每一步都按照指定的预计时间准时完美结束,然后下一步开始。当最后一步结束时,所有的部件都可以无缝地滑在一起,瞧,一个装载产品诞生了!\n", - "\n", - "当然,现实中没有事能按计划或预计时间运作。相信它应该,然后当它不能时更加相信,只会使整件事变得更糟。否认证据不会产生好的结果。\n", - "\n", - "除此之外,产品本身经常也不是对客户有价值的事物。有时一大堆的特性完全是浪费时间,因为创造出这些特性需求的人不是客户而是其他人。\n", - "\n", - "因为受流水工作线的思路影响,所以每个开发阶段都有自己的团队。上游团队的延期传递到下游团队,当到了需要进行测试和集成的时候,这些团队被指望赶上预期时间,当他们必然做不到时,就认为他们是“差劲的团队成员”。不可能的时间安排和负相关的结合产生了自实现的预期:只有最绝望的开发者才会乐意做这些工作。\n", - "\n", - "另外,商学院培养出的管理人员仍然被训练成只在已有的流程上做一些改动——这些流程都是基于工业时代制造业的想法上。注重培养创造力而不是墨守成规的商学院仍然很稀有。终于一些编程领域的人们再也忍受不了这种情况并开始进行实验。最初一些实验叫做“极限编程”,因为它们与工业时代的思想完全不同。随着实验展示的结果,这些思想开始看起来像是常识。这些实验逐渐形成了如今显而易见的观点——尽管非常小——即把生产可运作的产品交到客户手中,询问他们 (A) 是否想要它 (B) 是否喜欢它工作的方式 (C) 还希望有什么其他有用的功能特性。然后这些信息反馈给开发,从而继续产出一个新版本。版本不断迭代,项目最终演变成为客户带来真正价值的事物。\n", - "\n", - "这完全颠倒了瀑布流开发的方式。你停止假设你要处理产品测试和把部署\"作为最后一步\"这类的事情。相反,每件事从开始到结束必须都在进行——即使一开始产品几乎没有任何特性。这么做对于在开发周期的早期发现更多问题有巨大的益处。此外,不是做大量宏大超前的计划和花费时间金钱在许多无用的特性上,而是一直都能从顾客那得到反馈。当客户不再需要其他特性时,你就完成了。这节省了大量的时间和金钱,并提高了顾客的满意度。\n", - "\n", - "有许多不同的想法导向这种方式,但是目前首要的术语叫持续集成(CI)。CI 与导向 CI 的想法之间的不同之处在于 CI 是一种独特的机械式的过程,过程中涵盖了这些想法;它是一种定义好的做事方式。事实上,它定义得如此明确以至于整个过程是自动化的。\n", - "\n", - "当前 CI 技术的高峰是持续集成服务器。这是一台独立的机器或虚拟机,通常是由第三方公司托管的完全独立的服务。这些公司通常免费提供基本服务,如果你需要额外的特性如更多的处理器或内存或者专门的工具或系统,你需要付费。CI 服务器起初是完全空白状态,即只是可用的操作系统的最小配置。这很重要因为你可能之前在你的开发机器上安装过一些程序,却没有在你的构建和部署系统中包含它。正如重构一样,持续集成需要分布式版本管理,自动构建和自动测试系统作为基础。通常来说,CI 服务器会绑定到你的版本控制仓库上。当 CI 服务器发现仓库中有改变时,就会拉取最新版本的代码,并按照 CI 脚本中的过程处理。这包括安装所有必要的工具和类库(记住,CI 服务器起初只有一个干净、基本的操作系统),所以如果过程中出现任何问题,你都可以发现它们。接着它会执行脚本中定义的构建和测试操作;通常脚本中使用的命令与人们在安装和测试中使用的命令完全相同。如果执行成功或失败,CI 服务器会有许多种方式汇报给你,包括在你的代码仓库上显示一个简单的标记。\n", - "\n", - "使用持续集成,每次你合进仓库时,这些改变都会被从头到尾验证。通过这种方式,一旦出现问题你能立即发现。甚至当你准备交付一个产品的新版本时,都不会有延迟或其他必要的额外步骤(在任何时刻都可以交付叫做持续交付)。\n", - "\n", - "本书的示例代码都是在 Travis-CI(基于 Linux 的系统) 和 AppVeyor(Windows) 上自动测试的。你可以在 [Gihub仓库](https://github.com/BruceEckel/OnJava8-Examples) 上的 Readme 看到通过/失败的标记。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "\"它在我的机器上正常工作了。\" \"我们不会运载你的机器!\"\n", - "\n", - "代码校验不是单一的过程或技术。每种方法只能发现特定类型的 bug,作为程序员的你在开发过程中会明白每个额外的技术都能增加代码的可靠性和鲁棒性。校验不仅能在开发过程中,还能在为应用添加新功能的整个项目期间帮你发现更多的错误。现代化开发意味着比仅仅编写代码更多的内容,每种你在开发过程中融入的测试技术—— 包括而且尤其是你创建的能适应特定应用的自定义工具——都会带来更好、更快和更加愉悦的开发过程,同时也能为客户提供更高的价值和满意度体验。\n", - "\n", - "" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/17-Files.ipynb b/jupyter/17-Files.ipynb deleted file mode 100644 index 37f26133..00000000 --- a/jupyter/17-Files.ipynb +++ /dev/null @@ -1,1123 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第十七章 文件\n", - ">在丑陋的 Java I/O 编程方式诞生多年以后,Java终于简化了文件读写的基本操作。\n", - "\n", - "这种\"困难方式\"的全部细节都在 [Appendix: I/O Streams](./Appendix-IO-Streams.md)。如果你读过这个部分,就会认同 Java 设计者毫不在意他们的使用者的体验这一观念。打开并读取文件对于大多数编程语言来说是非常常用的,由于 I/O 糟糕的设计以至于\n", - "很少有人能够在不依赖其他参考代码的情况下完成打开文件的操作。\n", - "\n", - "好像 Java 设计者终于意识到了 Java 使用者多年来的痛苦,在 Java7 中对此引入了巨大的改进。这些新元素被放在 **java.nio.file** 包下面,过去人们通常把 **nio** 中的 **n** 理解为 **new** 即新的 **io**,现在更应该当成是 **non-blocking** 非阻塞 **io**(**io**就是*input/output输入/输出*)。**java.nio.file** 库终于将 Java 文件操作带到与其他编程语言相同的水平。最重要的是 Java8 新增的 streams 与文件结合使得文件操作编程变得更加优雅。我们将看一下文件操作的两个基本组件:\n", - "\n", - "1. 文件或者目录的路径;\n", - "2. 文件本身。\n", - "\n", - "\n", - "## 文件和目录路径\n", - "\n", - "一个 **Path** 对象表示一个文件或者目录的路径,是一个跨操作系统(OS)和文件系统的抽象,目的是在构造路径时不必关注底层操作系统,代码可以在不进行修改的情况下运行在不同的操作系统上。**java.nio.file.Paths** 类包含一个重载方法 **static get()**,该方法接受一系列 **String** 字符串或一个*统一资源标识符*(URI)作为参数,并且进行转换返回一个 **Path** 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/PathInfo.java\n", - "import java.nio.file.*;\n", - "import java.net.URI;\n", - "import java.io.File;\n", - "import java.io.IOException;\n", - "\n", - "public class PathInfo {\n", - " static void show(String id, Object p) {\n", - " System.out.println(id + \": \" + p);\n", - " }\n", - "\n", - " static void info(Path p) {\n", - " show(\"toString\", p);\n", - " show(\"Exists\", Files.exists(p));\n", - " show(\"RegularFile\", Files.isRegularFile(p));\n", - " show(\"Directory\", Files.isDirectory(p));\n", - " show(\"Absolute\", p.isAbsolute());\n", - " show(\"FileName\", p.getFileName());\n", - " show(\"Parent\", p.getParent());\n", - " show(\"Root\", p.getRoot());\n", - " System.out.println(\"******************\");\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(System.getProperty(\"os.name\"));\n", - " info(Paths.get(\"C:\", \"path\", \"to\", \"nowhere\", \"NoFile.txt\"));\n", - " Path p = Paths.get(\"PathInfo.java\");\n", - " info(p);\n", - " Path ap = p.toAbsolutePath();\n", - " info(ap);\n", - " info(ap.getParent());\n", - " try {\n", - " info(p.toRealPath());\n", - " } catch(IOException e) {\n", - " System.out.println(e);\n", - " }\n", - " URI u = p.toUri();\n", - " System.out.println(\"URI: \" + u);\n", - " Path puri = Paths.get(u);\n", - " System.out.println(Files.exists(puri));\n", - " File f = ap.toFile(); // Don't be fooled\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - "Windows 10\n", - "toString: C:\\path\\to\\nowhere\\NoFile.txt\n", - "Exists: false\n", - "RegularFile: false\n", - "Directory: false\n", - "Absolute: true\n", - "FileName: NoFile.txt\n", - "Parent: C:\\path\\to\\nowhere\n", - "Root: C:\\\n", - "******************\n", - "toString: PathInfo.java\n", - "Exists: true\n", - "RegularFile: true\n", - "Directory: false\n", - "Absolute: false\n", - "FileName: PathInfo.java\n", - "Parent: null\n", - "Root: null\n", - "******************\n", - "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\\PathInfo.java\n", - "Exists: true\n", - "RegularFile: true\n", - "Directory: false\n", - "Absolute: true\n", - "FileName: PathInfo.java\n", - "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "Root: C:\\\n", - "******************\n", - "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "Exists: true\n", - "RegularFile: false\n", - "Directory: true\n", - "Absolute: true\n", - "FileName: files\n", - "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\n", - "Root: C:\\\n", - "******************\n", - "toString: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\\PathInfo.java\n", - "Exists: true\n", - "RegularFile: true\n", - "Directory: false\n", - "Absolute: true\n", - "FileName: PathInfo.java\n", - "Parent: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "Root: C:\\\n", - "******************\n", - "URI: file:///C:/Users/Bruce/Documents/GitHub/onjava/\n", - "ExtractedExamples/files/PathInfo.java\n", - "true\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我已经在这一章第一个程序的 **main()** 方法添加了第一行用于展示操作系统的名称,因此你可以看到不同操作系统之间存在哪些差异。理想情况下,差别会相对较小,并且使用 **/** 或者 **\\\\** 路径分隔符进行分隔。你可以看到我运行在Windows 10 上的程序输出。\n", - "\n", - "当 **toString()** 方法生成完整形式的路径,你可以看到 **getFileName()** 方法总是返回当前文件名。\n", - "通过使用 **Files** 工具类(我们接下来将会更多地使用它),可以测试一个文件是否存在,测试是否是一个\"普通\"文件还是一个目录等等。\"Nofile.txt\"这个示例展示我们描述的文件可能并不在指定的位置;这样可以允许你创建一个新的路径。\"PathInfo.java\"存在于当前目录中,最初它只是没有路径的文件名,但它仍然被检测为\"存在\"。一旦我们将其转换为绝对路径,我们将会得到一个从\"C:\"盘(因为我们是在Windows机器下进行测试)开始的完整路径,现在它也拥有一个父路径。“真实”路径的定义在文档中有点模糊,因为它取决于具体的文件系统。例如,如果文件名不区分大小写,即使路径由于大小写的缘故而不是完全相同,也可能得到肯定的匹配结果。在这样的平台上,**toRealPath()** 将返回实际情况下的 **Path**,并且还会删除任何冗余元素。\n", - "\n", - "这里你会看到 **URI** 看起来只能用于描述文件,实际上 **URI** 可以用于描述更多的东西;通过 [维基百科](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier) 可以了解更多细节。现在我们成功地将 **URI** 转为一个 **Path** 对象。\n", - "\n", - "最后,你会在 **Path** 中看到一些有点欺骗的东西,这就是调用 **toFile()** 方法会生成一个 **File** 对象。听起来似乎可以得到一个类似文件的东西(毕竟被称为 **File** ),但是这个方法的存在仅仅是为了向后兼容。虽然看上去应该被称为\"路径\",实际上却应该表示目录或者文件本身。这是个非常草率并且令人困惑的命名,但是由于 **java.nio.file** 的存在我们可以安全地忽略它的存在。\n", - "\n", - "### 选取路径部分片段\n", - "**Path** 对象可以非常容易地生成路径的某一部分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/PartsOfPaths.java\n", - "import java.nio.file.*;\n", - "\n", - "public class PartsOfPaths {\n", - " public static void main(String[] args) {\n", - " System.out.println(System.getProperty(\"os.name\"));\n", - " Path p = Paths.get(\"PartsOfPaths.java\").toAbsolutePath();\n", - " for(int i = 0; i < p.getNameCount(); i++)\n", - " System.out.println(p.getName(i));\n", - " System.out.println(\"ends with '.java': \" +\n", - " p.endsWith(\".java\"));\n", - " for(Path pp : p) {\n", - " System.out.print(pp + \": \");\n", - " System.out.print(p.startsWith(pp) + \" : \");\n", - " System.out.println(p.endsWith(pp));\n", - " }\n", - " System.out.println(\"Starts with \" + p.getRoot() + \" \" + p.startsWith(p.getRoot()));\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - "Windows 10\n", - "Users\n", - "Bruce\n", - "Documents\n", - "GitHub\n", - "on-java\n", - "ExtractedExamples\n", - "files\n", - "PartsOfPaths.java\n", - "ends with '.java': false\n", - "Users: false : false\n", - "Bruce: false : false\n", - "Documents: false : false\n", - "GitHub: false : false\n", - "on-java: false : false\n", - "ExtractedExamples: false : false\n", - "files: false : false\n", - "PartsOfPaths.java: false : true\n", - "Starts with C:\\ true\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以通过 **getName()** 来索引 **Path** 的各个部分,直到达到上限 **getNameCount()**。**Path** 也实现了 **Iterable** 接口,因此我们也可以通过增强的 for-each 进行遍历。请注意,即使路径以 **.java** 结尾,使用 **endsWith()** 方法也会返回 **false**。这是因为使用 **endsWith()** 比较的是整个路径部分,而不会包含文件路径的后缀。通过使用 **startsWith()** 和 **endsWith()** 也可以完成路径的遍历。但是我们可以看到,遍历 **Path** 对象并不包含根路径,只有使用 **startsWith()** 检测根路径时才会返回 **true**。\n", - "\n", - "### 路径分析\n", - "**Files** 工具类包含一系列完整的方法用于获得 **Path** 相关的信息。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/PathAnalysis.java\n", - "import java.nio.file.*;\n", - "import java.io.IOException;\n", - "\n", - "public class PathAnalysis {\n", - " static void say(String id, Object result) {\n", - " System.out.print(id + \": \");\n", - " System.out.println(result);\n", - " }\n", - " \n", - " public static void main(String[] args) throws IOException {\n", - " System.out.println(System.getProperty(\"os.name\"));\n", - " Path p = Paths.get(\"PathAnalysis.java\").toAbsolutePath();\n", - " say(\"Exists\", Files.exists(p));\n", - " say(\"Directory\", Files.isDirectory(p));\n", - " say(\"Executable\", Files.isExecutable(p));\n", - " say(\"Readable\", Files.isReadable(p));\n", - " say(\"RegularFile\", Files.isRegularFile(p));\n", - " say(\"Writable\", Files.isWritable(p));\n", - " say(\"notExists\", Files.notExists(p));\n", - " say(\"Hidden\", Files.isHidden(p));\n", - " say(\"size\", Files.size(p));\n", - " say(\"FileStore\", Files.getFileStore(p));\n", - " say(\"LastModified: \", Files.getLastModifiedTime(p));\n", - " say(\"Owner\", Files.getOwner(p));\n", - " say(\"ContentType\", Files.probeContentType(p));\n", - " say(\"SymbolicLink\", Files.isSymbolicLink(p));\n", - " if(Files.isSymbolicLink(p))\n", - " say(\"SymbolicLink\", Files.readSymbolicLink(p));\n", - " if(FileSystems.getDefault().supportedFileAttributeViews().contains(\"posix\"))\n", - " say(\"PosixFilePermissions\",\n", - " Files.getPosixFilePermissions(p));\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - "Windows 10\n", - "Exists: true\n", - "Directory: false\n", - "Executable: true\n", - "Readable: true\n", - "RegularFile: true\n", - "Writable: true\n", - "notExists: false\n", - "Hidden: false\n", - "size: 1631\n", - "FileStore: SSD (C:)\n", - "LastModified: : 2017-05-09T12:07:00.428366Z\n", - "Owner: MINDVIEWTOSHIBA\\Bruce (User)\n", - "ContentType: null\n", - "SymbolicLink: false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在调用最后一个测试方法 **getPosixFilePermissions()** 之前我们需要确认一下当前文件系统是否支持 **Posix** 接口,否则会抛出运行时异常。\n", - "\n", - "### **Paths**的增减修改\n", - "我们必须能通过对 **Path** 对象增加或者删除一部分来构造一个新的 **Path** 对象。我们使用 **relativize()** 移除 **Path** 的根路径,使用 **resolve()** 添加 **Path** 的尾路径(不一定是“可发现”的名称)。\n", - "\n", - "对于下面代码中的示例,我使用 **relativize()** 方法从所有的输出中移除根路径,部分原因是为了示范,部分原因是为了简化输出结果,这说明你可以使用该方法将绝对路径转为相对路径。\n", - "这个版本的代码中包含 **id**,以便于跟踪输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/AddAndSubtractPaths.java\n", - "import java.nio.file.*;\n", - "import java.io.IOException;\n", - "\n", - "public class AddAndSubtractPaths {\n", - " static Path base = Paths.get(\"..\", \"..\", \"..\").toAbsolutePath().normalize();\n", - " \n", - " static void show(int id, Path result) {\n", - " if(result.isAbsolute())\n", - " System.out.println(\"(\" + id + \")r \" + base.relativize(result));\n", - " else\n", - " System.out.println(\"(\" + id + \") \" + result);\n", - " try {\n", - " System.out.println(\"RealPath: \" + result.toRealPath());\n", - " } catch(IOException e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " System.out.println(System.getProperty(\"os.name\"));\n", - " System.out.println(base);\n", - " Path p = Paths.get(\"AddAndSubtractPaths.java\").toAbsolutePath();\n", - " show(1, p);\n", - " Path convoluted = p.getParent().getParent()\n", - " .resolve(\"strings\").resolve(\"..\")\n", - " .resolve(p.getParent().getFileName());\n", - " show(2, convoluted);\n", - " show(3, convoluted.normalize());\n", - " Path p2 = Paths.get(\"..\", \"..\");\n", - " show(4, p2);\n", - " show(5, p2.normalize());\n", - " show(6, p2.toAbsolutePath().normalize());\n", - " Path p3 = Paths.get(\".\").toAbsolutePath();\n", - " Path p4 = p3.resolve(p2);\n", - " show(7, p4);\n", - " show(8, p4.normalize());\n", - " Path p5 = Paths.get(\"\").toAbsolutePath();\n", - " show(9, p5);\n", - " show(10, p5.resolveSibling(\"strings\"));\n", - " show(11, Paths.get(\"nonexistent\"));\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - "Windows 10\n", - "C:\\Users\\Bruce\\Documents\\GitHub\n", - "(1)r onjava\\\n", - "ExtractedExamples\\files\\AddAndSubtractPaths.java\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\\AddAndSubtractPaths.java\n", - "(2)r on-java\\ExtractedExamples\\strings\\..\\files\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "(3)r on-java\\ExtractedExamples\\files\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "(4) ..\\..\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", - "(5) ..\\..\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", - "(6)r on-java\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", - "(7)r on-java\\ExtractedExamples\\files\\.\\..\\..\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", - "(8)r on-java\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\on-java\n", - "(9)r on-java\\ExtractedExamples\\files\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\n", - "(10)r on-java\\ExtractedExamples\\strings\n", - "RealPath: C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\strings\n", - "(11) nonexistent\n", - "java.nio.file.NoSuchFileException:\n", - "C:\\Users\\Bruce\\Documents\\GitHub\\onjava\\\n", - "ExtractedExamples\\files\\nonexistent\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我还为 **toRealPath()** 添加了更多的测试,这是为了扩展和规则化,防止路径不存在时抛出运行时异常。\n", - "\n", - "\n", - "\n", - "## 目录\n", - "**Files** 工具类包含大部分我们需要的目录操作和文件操作方法。出于某种原因,它们没有包含删除目录树相关的方法,因此我们将实现并将其添加到 **onjava** 库中。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/RmDir.java\n", - "package onjava;\n", - "\n", - "import java.nio.file.*;\n", - "import java.nio.file.attribute.BasicFileAttributes;\n", - "import java.io.IOException;\n", - "\n", - "public class RmDir {\n", - " public static void rmdir(Path dir) throws IOException {\n", - " Files.walkFileTree(dir, new SimpleFileVisitor() {\n", - " @Override\n", - " public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n", - " Files.delete(file);\n", - " return FileVisitResult.CONTINUE;\n", - " }\n", - " \n", - " @Override\n", - " public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n", - " Files.delete(dir);\n", - " return FileVisitResult.CONTINUE;\n", - " }\n", - " });\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "删除目录树的方法实现依赖于 **Files.walkFileTree()**,\"walking\" 目录树意味着遍历每个子目录和文件。*Visitor* 设计模式提供了一种标准机制来访问集合中的每个对象,然后你需要提供在每个对象上执行的操作。\n", - "此操作的定义取决于实现的 **FileVisitor** 的四个抽象方法,包括:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1. **preVisitDirectory()**:在访问目录中条目之前在目录上运行。 \n", - "2. **visitFile()**:运行目录中的每一个文件。 \n", - "3. **visitFileFailed()**:调用无法访问的文件。 \n", - "4. **postVisitDirectory()**:在访问目录中条目之后在目录上运行,包括所有的子目录。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了简化,**java.nio.file.SimpleFileVisitor** 提供了所有方法的默认实现。这样,在我们的匿名内部类中,我们只需要重写非标准行为的方法:**visitFile()** 和 **postVisitDirectory()** 实现删除文件和删除目录。两者都应该返回标志位决定是否继续访问(这样就可以继续访问,直到找到所需要的)。\n", - "作为探索目录操作的一部分,现在我们可以有条件地删除已存在的目录。在以下例子中,**makeVariant()** 接受基本目录测试,并通过旋转部件列表生成不同的子目录路径。这些旋转与路径分隔符 **sep** 使用 **String.join()** 贴在一起,然后返回一个 **Path** 对象。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/Directories.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "import onjava.RmDir;\n", - "\n", - "public class Directories {\n", - " static Path test = Paths.get(\"test\");\n", - " static String sep = FileSystems.getDefault().getSeparator();\n", - " static List parts = Arrays.asList(\"foo\", \"bar\", \"baz\", \"bag\");\n", - " \n", - " static Path makeVariant() {\n", - " Collections.rotate(parts, 1);\n", - " return Paths.get(\"test\", String.join(sep, parts));\n", - " }\n", - " \n", - " static void refreshTestDir() throws Exception {\n", - " if(Files.exists(test))\n", - " RmDir.rmdir(test);\n", - " if(!Files.exists(test))\n", - " Files.createDirectory(test);\n", - " }\n", - " \n", - " public static void main(String[] args) throws Exception {\n", - " refreshTestDir();\n", - " Files.createFile(test.resolve(\"Hello.txt\"));\n", - " Path variant = makeVariant();\n", - " // Throws exception (too many levels):\n", - " try {\n", - " Files.createDirectory(variant);\n", - " } catch(Exception e) {\n", - " System.out.println(\"Nope, that doesn't work.\");\n", - " }\n", - " populateTestDir();\n", - " Path tempdir = Files.createTempDirectory(test, \"DIR_\");\n", - " Files.createTempFile(tempdir, \"pre\", \".non\");\n", - " Files.newDirectoryStream(test).forEach(System.out::println);\n", - " System.out.println(\"*********\");\n", - " Files.walk(test).forEach(System.out::println);\n", - " }\n", - " \n", - " static void populateTestDir() throws Exception {\n", - " for(int i = 0; i < parts.size(); i++) {\n", - " Path variant = makeVariant();\n", - " if(!Files.exists(variant)) {\n", - " Files.createDirectories(variant);\n", - " Files.copy(Paths.get(\"Directories.java\"),\n", - " variant.resolve(\"File.txt\"));\n", - " Files.createTempFile(variant, null, null);\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - "Nope, that doesn't work.\n", - "test\\bag\n", - "test\\bar\n", - "test\\baz\n", - "test\\DIR_5142667942049986036\n", - "test\\foo\n", - "test\\Hello.txt\n", - "*********\n", - "test\n", - "test\\bag\n", - "test\\bag\\foo\n", - "test\\bag\\foo\\bar\n", - "test\\bag\\foo\\bar\\baz\n", - "test\\bag\\foo\\bar\\baz\\8279660869874696036.tmp\n", - "test\\bag\\foo\\bar\\baz\\File.txt\n", - "test\\bar\n", - "test\\bar\\baz\n", - "test\\bar\\baz\\bag\n", - "test\\bar\\baz\\bag\\foo\n", - "test\\bar\\baz\\bag\\foo\\1274043134240426261.tmp\n", - "test\\bar\\baz\\bag\\foo\\File.txt\n", - "test\\baz\n", - "test\\baz\\bag\n", - "test\\baz\\bag\\foo\n", - "test\\baz\\bag\\foo\\bar\n", - "test\\baz\\bag\\foo\\bar\\6130572530014544105.tmp\n", - "test\\baz\\bag\\foo\\bar\\File.txt\n", - "test\\DIR_5142667942049986036\n", - "test\\DIR_5142667942049986036\\pre7704286843227113253.non\n", - "test\\foo\n", - "test\\foo\\bar\n", - "test\\foo\\bar\\baz\n", - "test\\foo\\bar\\baz\\bag\n", - "test\\foo\\bar\\baz\\bag\\5412864507741775436.tmp\n", - "test\\foo\\bar\\baz\\bag\\File.txt\n", - "test\\Hello.txt\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "首先,**refreshTestDir()** 用于检测 **test** 目录是否已经存在。若存在,则使用我们新工具类 **rmdir()** 删除其整个目录。检查是否 **exists** 是多余的,但我想说明一点,因为如果你对于已经存在的目录调用 **createDirectory()** 将会抛出异常。**createFile()** 使用参数 **Path** 创建一个空文件; **resolve()** 将文件名添加到 **test Path** 的末尾。\n", - "\n", - "我们尝试使用 **createDirectory()** 来创建多级路径,但是这样会抛出异常,因为这个方法只能创建单级路径。我已经将 **populateTestDir()** 作为一个单独的方法,因为它将在后面的例子中被重用。对于每一个变量 **variant**,我们都能使用 **createDirectories()** 创建完整的目录路径,然后使用此文件的副本以不同的目标名称填充该终端目录。然后我们使用 **createTempFile()** 生成一个临时文件。\n", - "\n", - "在调用 **populateTestDir()** 之后,我们在 **test** 目录下面创建一个临时目录。请注意,**createTempDirectory()** 只有名称的前缀选项。与 **createTempFile()** 不同,我们再次使用它将临时文件放入新的临时目录中。你可以从输出中看到,如果未指定后缀,它将默认使用\".tmp\"作为后缀。\n", - "\n", - "为了展示结果,我们首次使用看起来很有希望的 **newDirectoryStream()**,但事实证明这个方法只是返回 **test** 目录内容的 Stream 流,并没有更多的内容。要获取目录树的全部内容的流,请使用 **Files.walk()**。\n", - "\n", - "\n", - "\n", - "## 文件系统\n", - "为了完整起见,我们需要一种方法查找文件系统相关的其他信息。在这里,我们使用静态的 **FileSystems** 工具类获取\"默认\"的文件系统,但你同样也可以在 **Path** 对象上调用 **getFileSystem()** 以获取创建该 **Path** 的文件系统。你可以获得给定 *URI* 的文件系统,还可以构建新的文件系统(对于支持它的操作系统)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/FileSystemDemo.java\n", - "import java.nio.file.*;\n", - "\n", - "public class FileSystemDemo {\n", - " static void show(String id, Object o) {\n", - " System.out.println(id + \": \" + o);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " System.out.println(System.getProperty(\"os.name\"));\n", - " FileSystem fsys = FileSystems.getDefault();\n", - " for(FileStore fs : fsys.getFileStores())\n", - " show(\"File Store\", fs);\n", - " for(Path rd : fsys.getRootDirectories())\n", - " show(\"Root Directory\", rd);\n", - " show(\"Separator\", fsys.getSeparator());\n", - " show(\"UserPrincipalLookupService\",\n", - " fsys.getUserPrincipalLookupService());\n", - " show(\"isOpen\", fsys.isOpen());\n", - " show(\"isReadOnly\", fsys.isReadOnly());\n", - " show(\"FileSystemProvider\", fsys.provider());\n", - " show(\"File Attribute Views\",\n", - " fsys.supportedFileAttributeViews());\n", - " }\n", - "}\n", - "/* 输出:\n", - "Windows 10\n", - "File Store: SSD (C:)\n", - "Root Directory: C:\\\n", - "Root Directory: D:\\\n", - "Separator: \\\n", - "UserPrincipalLookupService:\n", - "sun.nio.fs.WindowsFileSystem$LookupService$1@15db9742\n", - "isOpen: true\n", - "isReadOnly: false\n", - "FileSystemProvider:\n", - "sun.nio.fs.WindowsFileSystemProvider@6d06d69c\n", - "File Attribute Views: [owner, dos, acl, basic, user]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一个 **FileSystem** 对象也能生成 **WatchService** 和 **PathMatcher** 对象,将会在接下来两章中详细讲解。\n", - "\n", - "\n", - "\n", - "## 路径监听\n", - "通过 **WatchService** 可以设置一个进程对目录中的更改做出响应。在这个例子中,**delTxtFiles()** 作为一个单独的任务执行,该任务将遍历整个目录并删除以 **.txt** 结尾的所有文件,**WatchService** 会对文件删除操作做出反应:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/PathWatcher.java\n", - "// {ExcludeFromGradle}\n", - "import java.io.IOException;\n", - "import java.nio.file.*;\n", - "import static java.nio.file.StandardWatchEventKinds.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class PathWatcher {\n", - " static Path test = Paths.get(\"test\");\n", - " \n", - " static void delTxtFiles() {\n", - " try {\n", - " Files.walk(test)\n", - " .filter(f ->\n", - " f.toString()\n", - " .endsWith(\".txt\"))\n", - " .forEach(f -> {\n", - " try {\n", - " System.out.println(\"deleting \" + f);\n", - " Files.delete(f);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " });\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) throws Exception {\n", - " Directories.refreshTestDir();\n", - " Directories.populateTestDir();\n", - " Files.createFile(test.resolve(\"Hello.txt\"));\n", - " WatchService watcher = FileSystems.getDefault().newWatchService();\n", - " test.register(watcher, ENTRY_DELETE);\n", - " Executors.newSingleThreadScheduledExecutor()\n", - " .schedule(PathWatcher::delTxtFiles,\n", - " 250, TimeUnit.MILLISECONDS);\n", - " WatchKey key = watcher.take();\n", - " for(WatchEvent evt : key.pollEvents()) {\n", - " System.out.println(\"evt.context(): \" + evt.context() +\n", - " \"\\nevt.count(): \" + evt.count() +\n", - " \"\\nevt.kind(): \" + evt.kind());\n", - " System.exit(0);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "deleting test\\bag\\foo\\bar\\baz\\File.txt\n", - "deleting test\\bar\\baz\\bag\\foo\\File.txt\n", - "deleting test\\baz\\bag\\foo\\bar\\File.txt\n", - "deleting test\\foo\\bar\\baz\\bag\\File.txt\n", - "deleting test\\Hello.txt\n", - "evt.context(): Hello.txt\n", - "evt.count(): 1\n", - "evt.kind(): ENTRY_DELETE\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**delTxtFiles()** 中的 **try** 代码块看起来有些多余,因为它们捕获的是同一种类型的异常,外部的 **try** 语句似乎已经足够了。然而出于某种原因,Java 要求两者都必须存在(这也可能是一个 bug)。还要注意的是在 **filter()** 中,我们必须显式地使用 **f.toString()** 转为字符串,否则我们调用 **endsWith()** 将会与整个 **Path** 对象进行比较,而不是路径名称字符串的一部分进行比较。\n", - "\n", - "一旦我们从 **FileSystem** 中得到了 **WatchService** 对象,我们将其注册到 **test** 路径以及我们感兴趣的项目的变量参数列表中,可以选择 **ENTRY_CREATE**,**ENTRY_DELETE** 或 **ENTRY_MODIFY**(其中创建和删除不属于修改)。\n", - "\n", - "因为接下来对 **watcher.take()** 的调用会在发生某些事情之前停止所有操作,所以我们希望 **deltxtfiles()** 能够并行运行以便生成我们感兴趣的事件。为了实现这个目的,我通过调用 **Executors.newSingleThreadScheduledExecutor()** 产生一个 **ScheduledExecutorService** 对象,然后调用 **schedule()** 方法传递所需函数的方法引用,并且设置在运行之前应该等待的时间。\n", - "\n", - "此时,**watcher.take()** 将等待并阻塞在这里。当目标事件发生时,会返回一个包含 **WatchEvent** 的 **Watchkey** 对象。展示的这三种方法是能对 **WatchEvent** 执行的全部操作。\n", - "\n", - "查看输出的具体内容。即使我们正在删除以 **.txt** 结尾的文件,在 **Hello.txt** 被删除之前,**WatchService** 也不会被触发。你可能认为,如果说\"监视这个目录\",自然会包含整个目录和下面子目录,但实际上:只会监视给定的目录,而不是下面的所有内容。如果需要监视整个树目录,必须在整个树的每个子目录上放置一个 **Watchservice**。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/TreeWatcher.java\n", - "// {ExcludeFromGradle}\n", - "import java.io.IOException;\n", - "import java.nio.file.*;\n", - "import static java.nio.file.StandardWatchEventKinds.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class TreeWatcher {\n", - "\n", - " static void watchDir(Path dir) {\n", - " try {\n", - " WatchService watcher =\n", - " FileSystems.getDefault().newWatchService();\n", - " dir.register(watcher, ENTRY_DELETE);\n", - " Executors.newSingleThreadExecutor().submit(() -> {\n", - " try {\n", - " WatchKey key = watcher.take();\n", - " for(WatchEvent evt : key.pollEvents()) {\n", - " System.out.println(\n", - " \"evt.context(): \" + evt.context() +\n", - " \"\\nevt.count(): \" + evt.count() +\n", - " \"\\nevt.kind(): \" + evt.kind());\n", - " System.exit(0);\n", - " }\n", - " } catch(InterruptedException e) {\n", - " return;\n", - " }\n", - " });\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) throws Exception {\n", - " Directories.refreshTestDir();\n", - " Directories.populateTestDir();\n", - " Files.walk(Paths.get(\"test\"))\n", - " .filter(Files::isDirectory)\n", - " .forEach(TreeWatcher::watchDir);\n", - " PathWatcher.delTxtFiles();\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - "deleting test\\bag\\foo\\bar\\baz\\File.txt\n", - "deleting test\\bar\\baz\\bag\\foo\\File.txt\n", - "evt.context(): File.txt\n", - "evt.count(): 1\n", - "evt.kind(): ENTRY_DELETE\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **watchDir()** 方法中给 **WatchSevice** 提供参数 **ENTRY_DELETE**,并启动一个独立的线程来监视该**Watchservice**。这里我们没有使用 **schedule()** 进行启动,而是使用 **submit()** 启动线程。我们遍历整个目录树,并将 **watchDir()** 应用于每个子目录。现在,当我们运行 **deltxtfiles()** 时,其中一个 **Watchservice** 会检测到每一次文件删除。\n", - "\n", - "\n", - "\n", - "## 文件查找\n", - "到目前为止,为了找到文件,我们一直使用相当粗糙的方法,在 `path` 上调用 `toString()`,然后使用 `string` 操作查看结果。事实证明,`java.nio.file` 有更好的解决方案:通过在 `FileSystem` 对象上调用 `getPathMatcher()` 获得一个 `PathMatcher`,然后传入您感兴趣的模式。模式有两个选项:`glob` 和 `regex`。`glob` 比较简单,实际上功能非常强大,因此您可以使用 `glob` 解决许多问题。如果您的问题更复杂,可以使用 `regex`,这将在接下来的 `Strings` 一章中解释。\n", - "\n", - "在这里,我们使用 `glob` 查找以 `.tmp` 或 `.txt` 结尾的所有 `Path`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/Find.java\n", - "// {ExcludeFromGradle}\n", - "import java.nio.file.*;\n", - "\n", - "public class Find {\n", - " public static void main(String[] args) throws Exception {\n", - " Path test = Paths.get(\"test\");\n", - " Directories.refreshTestDir();\n", - " Directories.populateTestDir();\n", - " // Creating a *directory*, not a file:\n", - " Files.createDirectory(test.resolve(\"dir.tmp\"));\n", - "\n", - " PathMatcher matcher = FileSystems.getDefault()\n", - " .getPathMatcher(\"glob:**/*.{tmp,txt}\");\n", - " Files.walk(test)\n", - " .filter(matcher::matches)\n", - " .forEach(System.out::println);\n", - " System.out.println(\"***************\");\n", - "\n", - " PathMatcher matcher2 = FileSystems.getDefault()\n", - " .getPathMatcher(\"glob:*.tmp\");\n", - " Files.walk(test)\n", - " .map(Path::getFileName)\n", - " .filter(matcher2::matches)\n", - " .forEach(System.out::println);\n", - " System.out.println(\"***************\");\n", - "\n", - " Files.walk(test) // Only look for files\n", - " .filter(Files::isRegularFile)\n", - " .map(Path::getFileName)\n", - " .filter(matcher2::matches)\n", - " .forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "test\\bag\\foo\\bar\\baz\\5208762845883213974.tmp\n", - "test\\bag\\foo\\bar\\baz\\File.txt\n", - "test\\bar\\baz\\bag\\foo\\7918367201207778677.tmp\n", - "test\\bar\\baz\\bag\\foo\\File.txt\n", - "test\\baz\\bag\\foo\\bar\\8016595521026696632.tmp\n", - "test\\baz\\bag\\foo\\bar\\File.txt\n", - "test\\dir.tmp\n", - "test\\foo\\bar\\baz\\bag\\5832319279813617280.tmp\n", - "test\\foo\\bar\\baz\\bag\\File.txt\n", - "***************\n", - "5208762845883213974.tmp\n", - "7918367201207778677.tmp\n", - "8016595521026696632.tmp\n", - "dir.tmp\n", - "5832319279813617280.tmp\n", - "***************\n", - "5208762845883213974.tmp\n", - "7918367201207778677.tmp\n", - "8016595521026696632.tmp\n", - "5832319279813617280.tmp\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `matcher` 中,`glob` 表达式开头的 `**/` 表示“当前目录及所有子目录”,这在当你不仅仅要匹配当前目录下特定结尾的 `Path` 时非常有用。单 `*` 表示“任何东西”,然后是一个点,然后大括号表示一系列的可能性---我们正在寻找以 `.tmp` 或 `.txt` 结尾的东西。您可以在 `getPathMatcher()` 文档中找到更多详细信息。\n", - "\n", - "`matcher2` 只使用 `*.tmp`,通常不匹配任何内容,但是添加 `map()` 操作会将完整路径减少到末尾的名称。\n", - "\n", - "注意,在这两种情况下,输出中都会出现 `dir.tmp`,即使它是一个目录而不是一个文件。要只查找文件,必须像在最后 `files.walk()` 中那样对其进行筛选。\n", - "\n", - "\n", - "## 文件读写\n", - "此时,我们可以对路径和目录做任何事情。 现在让我们看一下操纵文件本身的内容。\n", - "\n", - "如果一个文件很“小”,也就是说“它运行得足够快且占用内存小”,那么 `java.nio.file.Files` 类中的实用程序将帮助你轻松读写文本和二进制文件。\n", - "\n", - "`Files.readAllLines()` 一次读取整个文件(因此,“小”文件很有必要),产生一个`List`。 对于示例文件,我们将重用`streams/Cheese.dat`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/ListOfLines.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "\n", - "public class ListOfLines {\n", - " public static void main(String[] args) throws Exception {\n", - " Files.readAllLines(\n", - " Paths.get(\"../streams/Cheese.dat\"))\n", - " .stream()\n", - " .filter(line -> !line.startsWith(\"//\"))\n", - " .map(line ->\n", - " line.substring(0, line.length()/2))\n", - " .forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "Not much of a cheese\n", - "Finest in the\n", - "And what leads you\n", - "Well, it's\n", - "It's certainly uncon\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "跳过注释行,其余的内容每行只打印一半。 这实现起来很简单:你只需将 `Path` 传递给 `readAllLines()` (以前的 java 实现这个功能很复杂)。`readAllLines()` 有一个重载版本,包含一个 `Charset` 参数来存储文件的 Unicode 编码。\n", - "\n", - "`Files.write()` 被重载以写入 `byte` 数组或任何 `Iterable` 对象(它也有 `Charset` 选项):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/Writing.java\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "\n", - "public class Writing {\n", - " static Random rand = new Random(47);\n", - " static final int SIZE = 1000;\n", - " \n", - " public static void main(String[] args) throws Exception {\n", - " // Write bytes to a file:\n", - " byte[] bytes = new byte[SIZE];\n", - " rand.nextBytes(bytes);\n", - " Files.write(Paths.get(\"bytes.dat\"), bytes);\n", - " System.out.println(\"bytes.dat: \" + Files.size(Paths.get(\"bytes.dat\")));\n", - "\n", - " // Write an iterable to a file:\n", - " List lines = Files.readAllLines(\n", - " Paths.get(\"../streams/Cheese.dat\"));\n", - " Files.write(Paths.get(\"Cheese.txt\"), lines);\n", - " System.out.println(\"Cheese.txt: \" + Files.size(Paths.get(\"Cheese.txt\")));\n", - " }\n", - "}\n", - "/* Output:\n", - "bytes.dat: 1000\n", - "Cheese.txt: 199\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们使用 `Random` 来创建一个随机的 `byte` 数组; 你可以看到生成的文件大小是 1000。\n", - "\n", - "一个 `List` 被写入文件,任何 `Iterable` 对象也可以这么做。\n", - "\n", - "如果文件大小有问题怎么办? 比如说:\n", - "\n", - "1. 文件太大,如果你一次性读完整个文件,你可能会耗尽内存。\n", - "\n", - "2. 您只需要在文件的中途工作以获得所需的结果,因此读取整个文件会浪费时间。\n", - "\n", - "`Files.lines()` 方便地将文件转换为行的 `Stream`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/ReadLineStream.java\n", - "import java.nio.file.*;\n", - "\n", - "public class ReadLineStream {\n", - " public static void main(String[] args) throws Exception {\n", - " Files.lines(Paths.get(\"PathInfo.java\"))\n", - " .skip(13)\n", - " .findFirst()\n", - " .ifPresent(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - " show(\"RegularFile\", Files.isRegularFile(p));\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这对本章中第一个示例代码做了流式处理,跳过 13 行,然后选择下一行并将其打印出来。\n", - "\n", - "`Files.lines()` 对于把文件处理行的传入流时非常有用,但是如果你想在 `Stream` 中读取,处理或写入怎么办?这就需要稍微复杂的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// files/StreamInAndOut.java\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class StreamInAndOut {\n", - " public static void main(String[] args) {\n", - " try(\n", - " Stream input =\n", - " Files.lines(Paths.get(\"StreamInAndOut.java\"));\n", - " PrintWriter output =\n", - " new PrintWriter(\"StreamInAndOut.txt\")\n", - " ) {\n", - " input.map(String::toUpperCase)\n", - " .forEachOrdered(output::println);\n", - " } catch(Exception e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为我们在同一个块中执行所有操作,所以这两个文件都可以在相同的 try-with-resources 语句中打开。`PrintWriter` 是一个旧式的 `java.io` 类,允许你“打印”到一个文件,所以它是这个应用的理想选择。如果你看一下 `StreamInAndOut.txt`,你会发现它里面的内容确实是大写的。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "虽然本章对文件和目录操作做了相当全面的介绍,但是仍然有没被介绍的类库中的功能——一定要研究 `java.nio.file` 的 Javadocs,尤其是 `java.nio.file.Files` 这个类。\n", - "\n", - "Java 7 和 8 对于处理文件和目录的类库做了大量改进。如果您刚刚开始使用 Java,那么您很幸运。在过去,它令人非常不愉快,我确信 Java 设计者以前对于文件操作不够重视才没做简化。对于初学者来说这是一件很棒的事,对于教学者来说也一样。我不明白为什么花了这么长时间来解决这个明显的问题,但不管怎么说它被解决了,我很高兴。使用文件现在很简单,甚至很有趣,这是你以前永远想不到的。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/18-Strings.ipynb b/jupyter/18-Strings.ipynb deleted file mode 100644 index 3a6e124f..00000000 --- a/jupyter/18-Strings.ipynb +++ /dev/null @@ -1,2481 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第十八章 字符串\n", - ">字符串操作毫无疑问是计算机程序设计中最常见的行为之一。\n", - "\n", - "在 Java 大展拳脚的 Web 系统中更是如此。在本章中,我们将深入学习在 Java 语言中应用最广泛的 `String` 类,并研究与之相关的类及工具。\n", - "\n", - "\n", - "\n", - "## 字符串的不可变\n", - "`String` 对象是不可变的。查看 JDK 文档你就会发现,`String` 类中每一个看起来会修改 `String` 值的方法,实际上都是创建了一个全新的 `String` 对象,以包含修改后的字符串内容。而最初的 `String` 对象则丝毫未动。\n", - "\n", - "看看下面的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Immutable.java\n", - "public class Immutable { \n", - " public static String upcase(String s) { \n", - " return s.toUpperCase(); \n", - " } \n", - " public static void main(String[] args) { \n", - " String q = \"howdy\";\n", - " System.out.println(q); // howdy \n", - " String qq = upcase(q); \n", - " System.out.println(qq); // HOWDY \n", - " System.out.println(q); // howdy \n", - " } \n", - "} \n", - "/* Output: \n", - "howdy\n", - "HOWDY \n", - "howdy\n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当把 `q` 传递给 `upcase()` 方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。\n", - "\n", - "回到 `upcase()` 的定义,传入其中的引用有了名字 `s`,只有 `upcase()` 运行的时候,局部引用 `s` 才存在。一旦 `upcase()` 运行结束,`s` 就消失了。当然了,`upcase()` 的返回值,其实是最终结果的引用。这足以说明,`upcase()` 返回的引用已经指向了一个新的对象,而 `q` 仍然在原来的位置。\n", - "\n", - "`String` 的这种行为正是我们想要的。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "String s = \"asdf\";\n", - "String x = Immutable.upcase(s);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "难道你真的希望 `upcase()` 方法改变其参数吗?对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。\n", - "\n", - "\n", - "\n", - "## `+` 的重载与 `StringBuilder`\n", - "`String` 对象是不可变的,你可以给一个 `String` 对象添加任意多的别名。因为 `String` 是只读的,所以指向它的任何引用都不可能修改它的值,因此,也就不会影响到其他引用。\n", - "\n", - "不可变性会带来一定的效率问题。为 `String` 对象重载的 `+` 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于 `String` 的 `+` 与 `+=` 是 Java 中仅有的两个重载过的操作符,Java 不允许程序员重载任何其他的操作符 [^1])。\n", - "\n", - "操作符 `+` 可以用来连接 `String`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Concatenation.java\n", - "\n", - "public class Concatenation {\n", - " public static void main(String[] args) { \n", - " String mango = \"mango\"; \n", - " String s = \"abc\" + mango + \"def\" + 47; \n", - " System.out.println(s);\n", - " } \n", - "}\n", - "/* Output:\n", - "abcmangodef47 \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以想象一下,这段代码是这样工作的:`String` 可能有一个 `append()` 方法,它会生成一个新的 `String` 对象,以包含“abc”与 `mango` 连接后的字符串。该对象会再创建另一个新的 `String` 对象,然后与“def”相连,生成另一个新的对象,依此类推。\n", - "\n", - "这种方式当然是可行的,但是为了生成最终的 `String` 对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。\n", - "\n", - "想看看以上代码到底是如何工作的吗?可以用 JDK 自带的 `javap` 工具来反编译以上代码。命令如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "javap -c Concatenation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里的 `-c` 标志表示将生成 JVM 字节码。我们剔除不感兴趣的部分,然后做细微的修改,于是有了以下的字节码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "x86asm" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public static void main(java.lang.String[]); \n", - " Code:\n", - " Stack=2, Locals=3, Args_size=1\n", - " 0: ldc #2; //String mango \n", - " 2: astore_1 \n", - " 3: new #3; //class StringBuilder \n", - " 6: dup \n", - " 7: invokespecial #4; //StringBuilder.\"\":() \n", - " 10: ldc #5; //String abc \n", - " 12: invokevirtual #6; //StringBuilder.append:(String) \n", - " 15: aload_1 \n", - " 16: invokevirtual #6; //StringBuilder.append:(String) \n", - " 19: ldc #7; //String def \n", - " 21: invokevirtual #6; //StringBuilder.append:(String) \n", - " 24: bipush 47 \n", - " 26: invokevirtual #8; //StringBuilder.append:(I) \n", - " 29: invokevirtual #9; //StringBuilder.toString:() \n", - " 32: astore_2 \n", - " 33: getstatic #10; //Field System.out:PrintStream;\n", - " 36: aload_2 \n", - " 37: invokevirtual #11; //PrintStream.println:(String) \n", - " 40: return" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你有汇编语言的经验,以上代码应该很眼熟(其中的 `dup` 和 `invokevirtual` 语句相当于Java虚拟机上的汇编语句。即使你完全不了解汇编语言也无需担心)。需要重点注意的是:编译器自动引入了 `java.lang.StringBuilder` 类。虽然源代码中并没有使用 `StringBuilder` 类,但是编译器却自作主张地使用了它,就因为它更高效。\n", - "\n", - "在这里,编译器创建了一个 `StringBuilder` 对象,用于构建最终的 `String`,并对每个字符串调用了一次 `append()` 方法,共计 4 次。最后调用 `toString()` 生成结果,并存为 `s` (使用的命令为 `astore_2`)。\n", - "\n", - "现在,也许你会觉得可以随意使用 `String` 对象,反正编译器会自动为你做性能优化。可是在这之前,让我们更深入地看看编译器能为我们优化到什么程度。下面的例子采用两种方式生成一个 `String`:方法一使用了多个 `String` 对象;方法二在代码中使用了 `StringBuilder`。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/WhitherStringBuilder.java\n", - "\n", - "public class WhitherStringBuilder { \n", - " public String implicit(String[] fields) { \n", - " String result = \"\"; \n", - " for(String field : fields) { \n", - " result += field;\n", - " }\n", - " return result; \n", - " }\n", - " public String explicit(String[] fields) { \n", - " StringBuilder result = new StringBuilder(); \n", - " for(String field : fields) { \n", - " result.append(field); \n", - " } \n", - " return result.toString(); \n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在运行 `javap -c WitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是 `implicit()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "x86asm" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public java.lang.String implicit(java.lang.String[]); \n", - "0: ldc #2 // String \n", - "2: astore_2\n", - "3: aload_1 \n", - "4: astore_3 \n", - "5: aload_3 \n", - "6: arraylength \n", - "7: istore 4 \n", - "9: iconst_0 \n", - "10: istore 5 \n", - "12: iload 5 \n", - "14: iload 4 \n", - "16: if_icmpge 51 \n", - "19: aload_3 \n", - "20: iload 5 \n", - "22: aaload \n", - "23: astore 6 \n", - "25: new #3 // StringBuilder \n", - "28: dup \n", - "29: invokespecial #4 // StringBuilder.\"\"\n", - "32: aload_2 \n", - "33: invokevirtual #5 // StringBuilder.append:(String) \n", - "36: aload 6 \n", - "38: invokevirtual #5 // StringBuilder.append:(String;) \n", - "41: invokevirtual #6 // StringBuilder.toString:() \n", - "44: astore_2 \n", - "45: iinc 5, 1 \n", - "48: goto 12 \n", - "51: aload_2 \n", - "52: areturn" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意从第 16 行到第 48 行构成了一个循环体。第 16 行:对堆栈中的操作数进行“大于或等于的整数比较运算”,循环结束时跳转到第 51 行。第 48 行:重新回到循环体的起始位置(第 12 行)。注意:`StringBuilder` 是在循环内构造的,这意味着每进行一次循环,会创建一个新的 `StringBuilder` 对象。\n", - "\n", - "下面是 `explicit()` 方法对应的字节码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "x86asm" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public java.lang.String explicit(java.lang.String[]); \n", - "0: new #3 // StringBuilder \n", - "3: dup\n", - "4: invokespecial #4 // StringBuilder.\"\" \n", - "7: astore_2 \n", - "8: aload_1 \n", - "9: astore_3 \n", - "10: aload_3 \n", - "11: arraylength \n", - "12: istore 4 \n", - "14: iconst_0 \n", - "15: istore 5 \n", - "17: iload 5 \n", - "19: iload 4 \n", - "21: if_icmpge 43 \n", - "24: aload_3 \n", - "25: iload 5 \n", - "27: aaload \n", - "28: astore 6 \n", - "30: aload_2 \n", - "31: aload 6 \n", - "33: invokevirtual #5 // StringBuilder.append:(String) \n", - "36: pop\n", - "37: iinc 5, 1 \n", - "40: goto 17 \n", - "43: aload_2 \n", - "44: invokevirtual #6 // StringBuilder.toString:() \n", - "47: areturn" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,不仅循环部分的代码更简短、更简单,而且它只生成了一个 `StringBuilder` 对象。显式地创建 `StringBuilder` 还允许你预先为其指定大小。如果你已经知道最终字符串的大概长度,那预先指定 `StringBuilder` 的大小可以避免频繁地重新分配缓冲。\n", - "\n", - "因此,当你为一个类编写 `toString()` 方法时,如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但是,如果你要在 `toString()` 方法中使用循环,且可能有性能问题,那么最好自己创建一个 `StringBuilder` 对象,用它来构建最终结果。请参考以下示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/UsingStringBuilder.java \n", - "\n", - "import java.util.*; \n", - "import java.util.stream.*; \n", - "public class UsingStringBuilder { \n", - " public static String string1() { \n", - " Random rand = new Random(47);\n", - " StringBuilder result = new StringBuilder(\"[\"); \n", - " for(int i = 0; i < 25; i++) { \n", - " result.append(rand.nextInt(100)); \n", - " result.append(\", \"); \n", - " } \n", - " result.delete(result.length()-2, result.length()); \n", - " result.append(\"]\");\n", - " return result.toString(); \n", - " } \n", - " public static String string2() { \n", - " String result = new Random(47)\n", - " .ints(25, 0, 100)\n", - " .mapToObj(Integer::toString)\n", - " .collect(Collectors.joining(\", \"));\n", - " return \"[\" + result + \"]\"; \n", - " } \n", - " public static void main(String[] args) { \n", - " System.out.println(string1()); \n", - " System.out.println(string2()); \n", - " }\n", - "} \n", - "/* Output: \n", - "[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, \n", - "9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] \n", - "[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89,\n", - "9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4] \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在方法 `string1()` 中,最终结果是用 `append()` 语句拼接起来的。如果你想走捷径,例如:`append(a + \": \" + c)`,编译器就会掉入陷阱,从而为你另外创建一个 `StringBuilder` 对象处理括号内的字符串操作。如果拿不准该用哪种方式,随时可以用 `javap` 来分析你的程序。\n", - "\n", - "`StringBuilder` 提供了丰富而全面的方法,包括 `insert()`、`replace()`、`substring()`,甚至还有`reverse()`,但是最常用的还是 `append()` 和 `toString()`。还有 `delete()`,上面的例子中我们用它删除最后一个逗号和空格,以便添加右括号。\n", - "\n", - "`string2()` 使用了 `Stream`,这样代码更加简洁美观。可以证明,`Collectors.joining()` 内部也是使用的 `StringBuilder`,这种写法不会影响性能!\n", - "\n", - "`StringBuilder `是 Java SE5 引入的,在这之前用的是 `StringBuffer`。后者是线程安全的(参见[并发编程](./24-Concurrent-Programming.md)),因此开销也会大些。使用 `StringBuilder` 进行字符串操作更快一点。\n", - "\n", - "\n", - "\n", - "## 意外递归\n", - "Java 中的每个类从根本上都是继承自 `Object`,标准集合类也是如此,它们都有 `toString()` 方法,并且覆盖了该方法,使得它生成的 `String` 结果能够表达集合自身,以及集合包含的对象。例如 `ArrayList.toString()`,它会遍历 `ArrayList` 中包含的所有对象,调用每个元素上的 `toString()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ArrayListDisplay.java \n", - "import java.util.*;\n", - "import java.util.stream.*; \n", - "import generics.coffee.*;\n", - "public class ArrayListDisplay { \n", - " public static void main(String[] args) {\n", - " List coffees = \n", - " Stream.generate(new CoffeeSupplier())\n", - " .limit(10)\n", - " .collect(Collectors.toList()); \n", - " System.out.println(coffees); \n", - " } \n", - "}\n", - "/* Output: \n", - "[Americano 0, Latte 1, Americano 2, Mocha 3, Mocha 4, \n", - "Breve 5, Americano 6, Latte 7, Cappuccino 8, Cappuccino 9] \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你希望 `toString()` 打印出类的内存地址,也许你会考虑使用 `this` 关键字:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/InfiniteRecursion.java \n", - "// Accidental recursion \n", - "// {ThrowsException} \n", - "// {VisuallyInspectOutput} Throws very long exception\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class InfiniteRecursion { \n", - " @Override \n", - " public String toString() { \n", - " return \" InfiniteRecursion address: \" + this + \"\\n\"\n", - " } \n", - " public static void main(String[] args) { \n", - " Stream.generate(InfiniteRecursion::new) \n", - " .limit(10) \n", - " .forEach(System.out::println); \n", - " } \n", - "} " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当你创建了 `InfiniteRecursion` 对象,并将其打印出来的时候,你会得到一串很长的异常信息。如果你将该 `InfiniteRecursion` 对象存入一个 `ArrayList` 中,然后打印该 `ArrayList`,同样也会抛出异常。其实,当运行到如下代码时:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\"InfiniteRecursion address: \" + this " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里发生了自动类型转换,由 `InfiniteRecursion` 类型转换为 `String` 类型。因为编译器发现一个 `String` 对象后面跟着一个 “+”,而 “+” 后面的对象不是 `String`,于是编译器试着将 `this` 转换成一个 `String`。它怎么转换呢?正是通过调用 `this` 上的 `toString()` 方法,于是就发生了递归调用。\n", - "\n", - "如果你真的想要打印对象的内存地址,应该调用 `Object.toString()` 方法,这才是负责此任务的方法。所以,不要使用 `this`,而是应该调用 `super.toString()` 方法。\n", - "\n", - "\n", - "\n", - "## 字符串操作\n", - "以下是 `String` 对象具备的一些基本方法。重载的方法归纳在同一行中:\n", - "\n", - "| 方法 | 参数,重载版本 | 作用 |\n", - "| ---- | ---- | ---- |\n", - "| 构造方法 | 默认版本,`String`,`StringBuilder`,`StringBuffer`,`char`数组,`byte`数组 | 创建`String`对象 |\n", - "| `length()` | | `String`中字符的个数 |\n", - "| `charAt()` | `int`索引|获取`String`中索引位置上的`char` |\n", - "| `getChars()`,`getBytes()` | 待复制部分的开始和结束索引,复制的目标数组,目标数组的开始索引 | 复制`char`或`byte`到一个目标数组中 |\n", - "| `toCharArray()` | | 生成一个`char[]`,包含`String`中的所有字符 |\n", - "| `equals()`,`equalsIgnoreCase()` | 与之进行比较的`String` | 比较两个`String`的内容是否相同。如果相同,结果为`true` |\n", - "| `compareTo()`,`compareToIgnoreCase()` | 与之进行比较的`String` | 按词典顺序比较`String`的内容,比较结果为负数、零或正数。注意,大小写不等价 |\n", - "| `contains()` | 要搜索的`CharSequence` | 如果该`String`对象包含参数的内容,则返回`true` |\n", - "| `contentEquals()` | 与之进行比较的`CharSequence`或`StringBuffer` | 如果该`String`对象与参数的内容完全一致,则返回`true` |\n", - "| `isEmpty()` | | 返回`boolean`结果,以表明`String`对象的长度是否为0 |\n", - "| `regionMatches()` | 该`String`的索引偏移量,另一个`String`及其索引偏移量,要比较的长度。重载版本增加了“忽略大小写”功能|返回`boolean`结果,以表明所比较区域是否相等 |\n", - "| `startsWith()` | 可能的起始`String`。重载版本在参数中增加了偏移量 | 返回`boolean`结果,以表明该`String`是否以传入参数开始 |\n", - "| `endsWith()` | 该`String`可能的后缀`String` | 返回`boolean`结果,以表明此参数是否是该字符串的后缀 |\n", - "| `indexOf()`,`lastIndexOf()` | 重载版本包括:`char`,`char`与起始索引,`String`,`String`与起始索引 | 如果该`String`并不包含此参数,就返回-1;否则返回此参数在`String`中的起始索引。`lastIndexOf`()是从后往前搜索 |\n", - "| `matches()` | 一个正则表达式 | 返回`boolean`结果,以表明该`String`和给出的正则表达式是否匹配 |\n", - "| `split()` | 一个正则表达式。可选参数为需要拆分的最大数量 | 按照正则表达式拆分`String`,返回一个结果数组 |\n", - "| `join()`(Java8引入的)|分隔符,待拼字符序列。用分隔符将字符序列拼接成一个新的`String` |用分隔符拼接字符片段,产生一个新的`String` |\n", - "| `substring()`(即`subSequence()`)| 重载版本:起始索引;起始索引+终止索引 | 返回一个新的`String`对象,以包含参数指定的子串 |\n", - "| `concat()` | 要连接的`String` |返回一个新的`String`对象,内容为原始`String`连接上参数`String` |\n", - "| `replace()` | 要替换的字符,用来进行替换的新字符。也可以用一个`CharSequence`替换另一个`CharSequence` |返回替换字符后的新`String`对象。如果没有替换发生,则返回原始的`String`对象 |\n", - "| `replaceFirst()` |要替换的正则表达式,用来进行替换的`String` | 返回替换首个目标字符串后的`String`对象 |\n", - "| `replaceAll()` | 要替换的正则表达式,用来进行替换的`String` | 返回替换所有目标字符串后的`String`对象 |\n", - "| `toLowerCase()`,`toUpperCase()` | | 将字符的大小写改变后,返回一个新的`String`对象。如果没有任何改变,则返回原始的`String`对象|\n", - "| `trim()` | |将`String`两端的空白符删除后,返回一个新的`String`对象。如果没有任何改变,则返回原始的`String`对象|\n", - "| `valueOf()`(`static`)|重载版本:`Object`;`char[]`;`char[]`,偏移量,与字符个数;`boolean`;`char`;`int`;`long`;`float`;`double` | 返回一个表示参数内容的`String` |\n", - "| `intern()` | | 为每个唯一的字符序列生成一个且仅生成一个`String`引用 |\n", - "| `format()` | 要格式化的字符串,要替换到格式化字符串的参数 | 返回格式化结果`String` |\n", - "\n", - "从这个表可以看出,当需要改变字符串的内容时,`String` 类的方法都会返回一个新的 `String` 对象。同时,如果内容不改变,`String` 方法只是返回原始对象的一个引用而已。这可以节约存储空间以及避免额外的开销。\n", - "\n", - "本章稍后还将介绍正则表达式在 `String` 方法中的应用。\n", - "\n", - "\n", - "\n", - "## 格式化输出\n", - "在长久的等待之后,Java SE5 终于推出了 C 语言中 `printf()` 风格的格式化输出这一功能。这不仅使得控制输出的代码更加简单,同时也给与Java开发者对于输出格式与排列更强大的控制能力。\n", - "### `printf()`\n", - "C 语言的 `printf()` 并不像 Java 那样连接字符串,它使用一个简单的格式化字符串,加上要插入其中的值,然后将其格式化输出。 `printf()` 并不使用重载的 `+` 操作符(C语言没有重载)来连接引号内的字符串或字符串变量,而是使用特殊的占位符来表示数据将来的位置。而且它还将插入格式化字符串的参数,以逗号分隔,排成一行。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "System.out.printf(\"Row 1: [%d %f]%n\", x, y);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这一行代码在运行的时候,首先将 `x` 的值插入到 `%d_` 的位置,然后将 `y` 的值插入到 `%f` 的位置。这些占位符叫做*格式修饰符*,它们不仅指明了插入数据的位置,同时还指明了将会插入什么类型的变量,以及如何格式化。在这个例子中 `%d` 表示 `x` 是一个整数,`%f` 表示 `y` 是一个浮点数(`float` 或者 `double`)。\n", - "### `System.out.format()`\n", - "Java SE5 引入了 `format()` 方法,可用于 `PrintStream` 或者 `PrintWriter` 对象(你可以在 [附录:流式 I/O](./Appendix-IO-Streams.md) 了解更多内容),其中也包括 `System.out` 对象。`format()` 方法模仿了 C 语言的 `printf()`。如果你比较怀旧的话,也可以使用 `printf()`。以下是一个简单的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/SimpleFormat.java \n", - "\n", - "public class SimpleFormat { \n", - " public static void main(String[] args) { \n", - " int x = 5; \n", - " double y = 5.332542; \n", - " // The old way: \n", - " System.out.println(\"Row 1: [\" + x + \" \" + y + \"]\"); \n", - " // The new way: \n", - " System.out.format(\"Row 1: [%d %f]%n\", x, y); \n", - " // or \n", - " System.out.printf(\"Row 1: [%d %f]%n\", x, y); \n", - " } \n", - "} \n", - "/* Output: \n", - "Row 1: [5 5.332542] \n", - "Row 1: [5 5.332542] \n", - "Row 1: [5 5.332542] \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,`format()` 和 `printf()` 是等价的,它们只需要一个简单的格式化字符串,加上一串参数即可,每个参数对应一个格式修饰符。\n", - "\n", - "`String` 类也有一个 `static format()` 方法,可以格式化字符串。\n", - "\n", - "### `Formatter` 类\n", - "在 Java 中,所有的格式化功能都是由 `java.util.Formatter` 类处理的。可以将 `Formatter` 看做一个翻译器,它将你的格式化字符串与数据翻译成需要的结果。当你创建一个 `Formatter` 对象时,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Turtle.java \n", - "import java.io.*;\n", - "import java.util.*;\n", - "\n", - "public class Turtle { \n", - " private String name; \n", - " private Formatter f; \n", - " public Turtle(String name, Formatter f) {\n", - " this.name = name; \n", - " this.f = f; \n", - " } \n", - " public void move(int x, int y) { \n", - " f.format(\"%s The Turtle is at (%d,%d)%n\", \n", - " name, x, y); \n", - " }\n", - " public static void main(String[] args) { \n", - " PrintStream outAlias = System.out; \n", - " Turtle tommy = new Turtle(\"Tommy\",\n", - " new Formatter(System.out)); \n", - " Turtle terry = new Turtle(\"Terry\", \n", - " new Formatter(outAlias)); \n", - " tommy.move(0,0); \n", - " terry.move(4,8); \n", - " tommy.move(3,4); \n", - " terry.move(2,5); \n", - " tommy.move(3,3); \n", - " terry.move(3,3); \n", - " } \n", - "} \n", - "/* Output: \n", - "Tommy The Turtle is at (0,0) \n", - "Terry The Turtle is at (4,8) \n", - "Tommy The Turtle is at (3,4) \n", - "Terry The Turtle is at (2,5) \n", - "Tommy The Turtle is at (3,3) \n", - "Terry The Turtle is at (3,3) \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "格式化修饰符 `%s` 表明这里需要 `String` 参数。\n", - "\n", - "所有的 `tommy` 都将输出到 `System.out`,而所有的 `terry` 则都输出到 `System.out` 的一个别名中。`Formatter` 的重载构造器支持输出到多个路径,不过最常用的还是 `PrintStream()`(如上例)、`OutputStream` 和 `File`。你可以在 [附录:流式 I/O](././Appendix-IO-Streams.md) 中了解更多信息。\n", - "### 格式化修饰符\n", - "在插入数据时,如果想要优化空格与对齐,你需要更精细复杂的格式修饰符。以下是其通用语法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "%[argument_index$][flags][width][.precision]conversion " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最常见的应用是控制一个字段的最小长度,这可以通过指定 *width* 来实现。`Formatter `对象通过在必要时添加空格,来确保一个字段至少达到设定长度。默认情况下,数据是右对齐的,不过可以通过使用 `-` 标志来改变对齐方向。\n", - "\n", - "与 *width* 相对的是 *precision*,用于指定最大长度。*width* 可以应用于各种类型的数据转换,并且其行为方式都一样。*precision* 则不然,当应用于不同类型的数据转换时,*precision* 的意义也不同。在将 *precision* 应用于 `String` 时,它表示打印 `string` 时输出字符的最大数量。而在将 *precision* 应用于浮点数时,它表示小数部分要显示出来的位数(默认是 6 位小数),如果小数位数过多则舍入,太少则在尾部补零。由于整数没有小数部分,所以 *precision* 无法应用于整数,如果你对整数应用 *precision*,则会触发异常。\n", - "\n", - "下面的程序应用格式修饰符来打印一个购物收据。这是 *Builder* 设计模式的一个简单实现,即先创建一个初始对象,然后逐渐添加新东西,最后调用 `build()` 方法完成构建:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ReceiptBuilder.java \n", - "import java.util.*; \n", - "\n", - "public class ReceiptBuilder { \n", - " private double total = 0; \n", - " private Formatter f = \n", - " new Formatter(new StringBuilder()); \n", - " public ReceiptBuilder() { \n", - " f.format( \n", - " \"%-15s %5s %10s%n\", \"Item\", \"Qty\", \"Price\"); \n", - " f.format( \n", - " \"%-15s %5s %10s%n\", \"----\", \"---\", \"-----\"); \n", - " } \n", - " public void add(String name, int qty, double price) { \n", - " f.format(\"%-15.15s %5d %10.2f%n\", name, qty, price); \n", - " total += price * qty; \n", - " } \n", - " public String build() { \n", - " f.format(\"%-15s %5s %10.2f%n\", \"Tax\", \"\", \n", - " total * 0.06); \n", - " f.format(\"%-15s %5s %10s%n\", \"\", \"\", \"-----\"); \n", - " f.format(\"%-15s %5s %10.2f%n\", \"Total\", \"\", \n", - " total * 1.06); \n", - " return f.toString(); \n", - " } \n", - " public static void main(String[] args) { \n", - " ReceiptBuilder receiptBuilder = \n", - " new ReceiptBuilder(); \n", - " receiptBuilder.add(\"Jack's Magic Beans\", 4, 4.25); \n", - " receiptBuilder.add(\"Princess Peas\", 3, 5.1); \n", - " receiptBuilder.add( \n", - " \"Three Bears Porridge\", 1, 14.29); \n", - " System.out.println(receiptBuilder.build()); \n", - " } \n", - "} \n", - "/* Output: \n", - "Item Qty Price \n", - "---- --- ----- \n", - "Jack's Magic Be 4 4.25 \n", - "Princess Peas 3 5.10 \n", - "Three Bears Por 1 14.29 \n", - "Tax 2.80 \n", - " ----- \n", - "Total 49.39 \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过传入一个 `StringBuilder` 对象到 `Formatter` 的构造器,我们指定了一个容器来构建目标 `String`。你也可以通过不同的构造器参数,把结果输出到标准输出,甚至是一个文件里。\n", - "\n", - "正如你所见,通过相当简洁的语法,`Formatter ` 提供了对空格与对齐的强大控制能力。在该程序中,为了恰当地控制间隔,格式化字符串被重复利用了多遍。\n", - "### `Formatter` 转换\n", - "下面的表格展示了最常用的类型转换:\n", - "\n", - "| 类型 | 含义 |\n", - "| :----: | :---- |\n", - "| `d` | 整型(十进制) |\n", - "| `c` | Unicode字符 |\n", - "| `b` | Boolean值 |\n", - "| `s` | String |\n", - "| `f` | 浮点数(十进制) |\n", - "| `e` | 浮点数(科学计数) |\n", - "| `x` | 整型(十六进制) |\n", - "| `h` | 散列码(十六进制) |\n", - "| `%` | 字面值“%” |\n", - "\n", - "下面的程序演示了这些转换是如何工作的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Conversion.java \n", - "import java.math.*;\n", - "import java.util.*; \n", - "\n", - "public class Conversion { \n", - " public static void main(String[] args) { \n", - " Formatter f = new Formatter(System.out); \n", - "\n", - " char u = 'a'; \n", - " System.out.println(\"u = 'a'\"); \n", - " f.format(\"s: %s%n\", u); \n", - " // f.format(\"d: %d%n\", u); \n", - " f.format(\"c: %c%n\", u); \n", - " f.format(\"b: %b%n\", u); \n", - " // f.format(\"f: %f%n\", u); \n", - " // f.format(\"e: %e%n\", u); \n", - " // f.format(\"x: %x%n\", u); \n", - " f.format(\"h: %h%n\", u); \n", - "\n", - " int v = 121; \n", - " System.out.println(\"v = 121\"); \n", - " f.format(\"d: %d%n\", v); \n", - " f.format(\"c: %c%n\", v); \n", - " f.format(\"b: %b%n\", v); \n", - " f.format(\"s: %s%n\", v); \n", - " // f.format(\"f: %f%n\", v); \n", - " // f.format(\"e: %e%n\", v); \n", - " f.format(\"x: %x%n\", v); \n", - " f.format(\"h: %h%n\", v); \n", - "\n", - " BigInteger w = new BigInteger(\"50000000000000\"); \n", - " System.out.println( \n", - " \"w = new BigInteger(\\\"50000000000000\\\")\"); \n", - " f.format(\"d: %d%n\", w); \n", - " // f.format(\"c: %c%n\", w); \n", - " f.format(\"b: %b%n\", w); \n", - " f.format(\"s: %s%n\", w); \n", - " // f.format(\"f: %f%n\", w); \n", - " // f.format(\"e: %e%n\", w); \n", - " f.format(\"x: %x%n\", w); \n", - " f.format(\"h: %h%n\", w); \n", - "\n", - " double x = 179.543; \n", - " System.out.println(\"x = 179.543\"); \n", - " // f.format(\"d: %d%n\", x); \n", - " // f.format(\"c: %c%n\", x); \n", - " f.format(\"b: %b%n\", x); \n", - " f.format(\"s: %s%n\", x); \n", - " f.format(\"f: %f%n\", x); \n", - " f.format(\"e: %e%n\", x); \n", - " // f.format(\"x: %x%n\", x); \n", - " f.format(\"h: %h%n\", x); \n", - "\n", - " Conversion y = new Conversion(); \n", - " System.out.println(\"y = new Conversion()\"); \n", - "\n", - " // f.format(\"d: %d%n\", y); \n", - " // f.format(\"c: %c%n\", y); \n", - " f.format(\"b: %b%n\", y); \n", - " f.format(\"s: %s%n\", y); \n", - " // f.format(\"f: %f%n\", y); \n", - " // f.format(\"e: %e%n\", y); \n", - " // f.format(\"x: %x%n\", y); \n", - " f.format(\"h: %h%n\", y); \n", - "\n", - " boolean z = false; \n", - " System.out.println(\"z = false\"); \n", - " // f.format(\"d: %d%n\", z); \n", - " // f.format(\"c: %c%n\", z); \n", - " f.format(\"b: %b%n\", z); \n", - " f.format(\"s: %s%n\", z); \n", - " // f.format(\"f: %f%n\", z); \n", - " // f.format(\"e: %e%n\", z); \n", - " // f.format(\"x: %x%n\", z); \n", - " f.format(\"h: %h%n\", z); \n", - " } \n", - "} \n", - "/* Output: \n", - "u = 'a' \n", - "s: a \n", - "c: a \n", - "b: true \n", - "h: 61 \n", - "v = 121 \n", - "d: 121 \n", - "c: y \n", - "b: true \n", - "s: 121 \n", - "x: 79 \n", - "h: 79 \n", - "w = new BigInteger(\"50000000000000\") \n", - "d: 50000000000000 \n", - "b: true \n", - "s: 50000000000000 \n", - "x: 2d79883d2000 \n", - "h: 8842a1a7 \n", - "x = 179.543 \n", - "b: true \n", - "s: 179.543 \n", - "f: 179.543000 \n", - "e: 1.795430e+02 \n", - "h: 1ef462c \n", - "y = new Conversion() \n", - "b: true \n", - "s: Conversion@15db9742 \n", - "h: 15db9742 \n", - "z = false \n", - "b: false \n", - "s: false\n", - "h: 4d5 \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "被注释的代码表示,针对相应类型的变量,这些转换是无效的。如果执行这些转换,则会触发异常。\n", - "\n", - "注意,程序中的每个变量都用到了 `b` 转换。虽然它对各种类型都是合法的,但其行为却不一定与你想象的一致。对于 `boolean` 基本类型或 `Boolean` 对象,其转换结果是对应的 `true` 或 `false`。但是,对其他类型的参数,只要该参数不为 `null`,其转换结果永远都是 `true`。即使是数字 0,转换结果依然为 `true`,而这在其他语言中(包括C),往往转换为 `false`。所以,将 `b` 应用于非布尔类型的对象时请格外小心。\n", - "\n", - "还有许多不常用的类型转换与格式修饰符选项,你可以在 JDK 文档中的 `Formatter` 类部分找到它们。\n", - "### `String.format()`\n", - "Java SE5 也参考了 C 中的 `sprintf()` 方法,以生成格式化的 `String` 对象。`String.format()` 是一个 `static` 方法,它接受与 `Formatter.format()` 方法一样的参数,但返回一个 `String` 对象。当你只需使用一次 `format()` 方法的时候,`String.format()` 用起来很方便。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/DatabaseException.java \n", - "\n", - "public class DatabaseException extends Exception { \n", - " public DatabaseException(int transactionID, \n", - " int queryID, String message) { \n", - " super(String.format(\"(t%d, q%d) %s\", transactionID, \n", - " queryID, message)); \n", - " } \n", - " public static void main(String[] args) { \n", - " try { \n", - " throw new DatabaseException(3, 7, \"Write failed\"); \n", - " } catch(Exception e) { \n", - " System.out.println(e); \n", - " } \n", - " } \n", - "} \n", - "/* \n", - "Output: \n", - "DatabaseException: (t3, q7) Write failed \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其实在 `String.format()` 内部,它也是创建了一个 `Formatter` 对象,然后将你传入的参数转给 `Formatter`。不过,与其自己做这些事情,不如使用便捷的 `String.format()` 方法,何况这样的代码更清晰易读。\n", - "#### 一个十六进制转储(dump)工具\n", - "在第二个例子中,我们把二进制文件转换为十六进制格式。下面的小工具使用了 `String.format()` 方法,以可读的十六进制格式将字节数组打印出来:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Hex.java \n", - "// {java onjava.Hex} \n", - "package onjava;\n", - "import java.io.*; \n", - "import java.nio.file.*; \n", - "\n", - "public class Hex { \n", - " public static String format(byte[] data) { \n", - " StringBuilder result = new StringBuilder(); \n", - " int n = 0; \n", - " for(byte b : data) { \n", - " if(n % 16 == 0) \n", - " result.append(String.format(\"%05X: \", n)); \n", - " result.append(String.format(\"%02X \", b)); \n", - " n++; \n", - " if(n % 16 == 0) result.append(\"\\n\"); \n", - " } \n", - " result.append(\"\\n\"); \n", - " return result.toString(); \n", - " } \n", - " public static void main(String[] args) throws Exception { \n", - " if(args.length == 0) \n", - " // Test by displaying this class file: \n", - " System.out.println(format( \n", - " Files.readAllBytes(Paths.get( \n", - " \"build/classes/main/onjava/Hex.class\")))); \n", - " else \n", - " System.out.println(format( \n", - " Files.readAllBytes(Paths.get(args[0])))); \n", - " } \n", - "} \n", - "/* Output: (First 6 Lines) \n", - "00000: CA FE BA BE 00 00 00 34 00 61 0A 00 05 00 31 07 \n", - "00010: 00 32 0A 00 02 00 31 08 00 33 07 00 34 0A 00 35 \n", - "00020: 00 36 0A 00 0F 00 37 0A 00 02 00 38 08 00 39 0A \n", - "00030: 00 3A 00 3B 08 00 3C 0A 00 02 00 3D 09 00 3E 00 \n", - "00040: 3F 08 00 40 07 00 41 0A 00 42 00 43 0A 00 44 00 \n", - "00050: 45 0A 00 14 00 46 0A 00 47 00 48 07 00 49 01 00\n", - " ... \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了打开及读入二进制文件,我们用到了另一个工具 `Files.readAllBytes()`,这已经在 [Files章节](./17-Files.md) 介绍过了。这里的 `readAllBytes()` 方法将整个文件以 `byte` 数组的形式返回。\n", - "\n", - "\n", - "\n", - "## 正则表达式\n", - "很久之前,*正则表达式*就已经整合到标准 Unix 工具集之中,例如 sed、awk 和程序语言之中了,如 Python 和Perl(有些人认为正是正则表达式促成了 Perl 的成功)。而在 Java 中,字符串操作还主要集中于`String`、`StringBuffer` 和 `StringTokenizer` 类。与正则表达式相比较,它们只能提供相当简单的功能。\n", - "\n", - "正则表达式是一种强大而灵活的文本处理工具。使用正则表达式,我们能够以编程的方式,构造复杂的文本模式,并对输入 `String` 进行搜索。一旦找到了匹配这些模式的部分,你就能随心所欲地对它们进行处理。初学正则表达式时,其语法是一个难点,但它确实是一种简洁、动态的语言。正则表达式提供了一种完全通用的方式,能够解决各种 `String` 处理相关的问题:匹配、选择、编辑以及验证。\n", - "### 基础\n", - "一般来说,正则表达式就是以某种方式来描述字符串,因此你可以说:“如果一个字符串含有这些东西,那么它就是我正在找的东西。”例如,要找一个数字,它可能有一个负号在最前面,那你就写一个负号加上一个问号,就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "-?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要描述一个整数,你可以说它有一位或多位阿拉伯数字。在正则表达式中,用 `\\d` 表示一位数字。如果在其他语言中使用过正则表达式,那你可能就能发现 Java 对反斜线 \\ 的不同处理方式。在其他语言中,`\\\\` 表示“我想要在正则表达式中插入一个普通的(字面上的)反斜线,请不要给它任何特殊的意义。”而在Java中,`\\\\` 的意思是“我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。”例如,如果你想表示一位数字,那么正则表达式应该是 `\\\\d`。如果你想插入一个普通的反斜线,应该这样写 `\\\\\\`。不过换行符和制表符之类的东西只需要使用单反斜线:`\\n\\t`。 [^2]\n", - "\n", - "要表示“一个或多个之前的表达式”,应该使用 `+`。所以,如果要表示“可能有一个负号,后面跟着一位或多位数字”,可以这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "-?\\\\d+ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "应用正则表达式最简单的途径,就是利用 `String` 类内建的功能。例如,你可以检查一个 `String` 是否匹配如上所述的正则表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/IntegerMatch.java \n", - "\n", - "public class IntegerMatch { \n", - " public static void main(String[] args) { \n", - " System.out.println(\"-1234\".matches(\"-?\\\\d+\")); \n", - " System.out.println(\"5678\".matches(\"-?\\\\d+\")); \n", - " System.out.println(\"+911\".matches(\"-?\\\\d+\")); \n", - " System.out.println(\"+911\".matches(\"(-|\\\\+)?\\\\d+\")); \n", - " }\n", - "}\n", - "/* Output: \n", - "true \n", - "true \n", - "false \n", - "true \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "前两个字符串都满足对应的正则表达式,匹配成功。第三个字符串以 `+` 开头,这也是一个合法的符号,但与对应的正则表达式却不匹配。因此,我们的正则表达式应该描述为:“可能以一个加号或减号开头”。在正则表达式中,用括号将表达式进行分组,用竖线 ` | ` 表示或操作。也就是:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "(-|\\\\+)? " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个正则表达式表示字符串的起始字符可能是一个 `-` 或 `+`,或者二者都没有(因为后面跟着 `?` 修饰符)。因为字符 `+` 在正则表达式中有特殊的意义,所以必须使用 `\\\\` 将其转义,使之成为表达式中的一个普通字符。\n", - "\n", - "`String`类还自带了一个非常有用的正则表达式工具——`split()` 方法,其功能是“将字符串从正则表达式匹配的地方切开。”" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Splitting.java import java.util.*; \n", - "\n", - "public class Splitting {\n", - " public static String knights = \n", - " \"Then, when you have found the shrubbery, \" +\n", - " \"you must cut down the mightiest tree in the \" +\n", - " \"forest...with... a herring!\";\n", - " public static void split(String regex) {\n", - " System.out.println(\n", - " Arrays.toString(knights.split(regex)));\n", - " }\n", - " public static void main(String[] args) {\n", - " split(\" \"); // Doesn't have to contain regex chars\n", - " split(\"\\\\W+\"); // Non-word characters\n", - " split(\"n\\\\W+\"); // 'n' followed by non-words\n", - " }\n", - "}\n", - "/* Output:\n", - "[Then,, when, you, have, found, the, shrubbery,, you,\n", - "must, cut, down, the, mightiest, tree, in, the,\n", - "forest...with..., a, herring!]\n", - "[Then, when, you, have, found, the, shrubbery, you,\n", - "must, cut, down, the, mightiest, tree, in, the, forest,\n", - "with, a, herring]\n", - "[The, whe, you have found the shrubbery, you must cut\n", - "dow, the mightiest tree i, the forest...with... a\n", - "herring!]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "首先看第一个语句,注意这里用的是普通的字符作为正则表达式,其中并不包含任何特殊字符。因此第一个 `split()` 只是按空格来划分字符串。\n", - "\n", - "第二个和第三个 `split()` 都用到了 `\\\\W`,它的意思是一个非单词字符(如果 W 小写,`\\\\w`,则表示一个单词字符)。通过第二个例子可以看到,它将标点字符删除了。第三个 `split()` 表示“字母 `n` 后面跟着一个或多个非单词字符。”可以看到,在原始字符串中,与正则表达式匹配的部分,在最终结果中都不存在了。\n", - "\n", - "`String.split()` 还有一个重载的版本,它允许你限制字符串分割的次数。\n", - "\n", - "用正则表达式进行替换操作时,你可以只替换第一处匹配,也可以替换所有的匹配:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Replacing.java \n", - "\n", - "public class Replacing {\n", - " static String s = Splitting.knights; \n", - " public static void main(String[] args) {\n", - " System.out.println(\n", - " s.replaceFirst(\"f\\\\w+\", \"located\"));\n", - " System.out.println( \n", - " s.replaceAll(\"shrubbery|tree|herring\",\"banana\")); \n", - " } \n", - "}\n", - "/* Output: \n", - "Then, when you have located the shrubbery, you must cut \n", - "down the mightiest tree in the forest...with... a \n", - "herring! \n", - "Then, when you have found the banana, you must cut down\n", - "the mightiest banana in the forest...with... a banana! \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第一个表达式要匹配的是,以字母 `f` 开头,后面跟一个或多个字母(注意这里的 `w` 是小写的)。并且只替换掉第一个匹配的部分,所以 “found” 被替换成 “located”。\n", - "\n", - "第二个表达式要匹配的是三个单词中的任意一个,因为它们以竖线分割表示“或”,并且替换所有匹配的部分。\n", - "\n", - "稍后你会看到,`String` 之外的正则表达式还有更强大的替换工具,例如,可以通过方法调用执行替换。而且,如果正则表达式不是只使用一次的话,非 `String` 对象的正则表达式明显具备更佳的性能。\n", - "### 创建正则表达式\n", - "我们首先从正则表达式可能存在的构造集中选取一个很有用的子集,以此开始学习正则表达式。正则表达式的完整构造子列表,请参考JDK文档 `java.util.regex` 包中的 `Pattern`类。\n", - "\n", - "| 表达式 | 含义 |\n", - "| :---- | :---- |\n", - "| `B` | 指定字符`B` |\n", - "| `\\xhh` | 十六进制值为`0xhh`的字符 |\n", - "| `\\uhhhh` | 十六进制表现为`0xhhhh`的Unicode字符 |\n", - "| `\\t` | 制表符Tab |\n", - "| `\\n` | 换行符 |\n", - "| `\\r` | 回车 |\n", - "| `\\f` | 换页 |\n", - "| `\\e` | 转义(Escape) |\n", - "\n", - "当你学会了使用字符类(character classes)之后,正则表达式的威力才能真正显现出来。以下是一些创建字符类的典型方式,以及一些预定义的类:\n", - "\n", - "| 表达式 | 含义 |\n", - "| :---- | :---- |\n", - "| `.` | 任意字符 |\n", - "| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a|b|c`作用相同)|\n", - "| `[^abc]` | 除`a`、`b`和`c`之外的任何字符(否定) |\n", - "| `[a-zA-Z]` | 从`a`到`z`或从`A`到`Z`的任何字符(范围) |\n", - "| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a|b|c|h|i|j`作用相同)(合并) |\n", - "| `[a-z&&[hij]]` | 任意`h`、`i`或`j`(交) |\n", - "| `\\s` | 空白符(空格、tab、换行、换页、回车) |\n", - "| `\\S` | 非空白符(`[^\\s]`) |\n", - "| `\\d` | 数字(`[0-9]`) |\n", - "| `\\D` | 非数字(`[^0-9]`) |\n", - "| `\\w` | 词字符(`[a-zA-Z_0-9]`) |\n", - "| `\\W` | 非词字符(`[^\\w]`) |\n", - "\n", - "这里只列出了部分常用的表达式,你应该将JDK文档中 `java.util.regex.Pattern` 那一页加入浏览器书签中,以便在需要的时候方便查询。\n", - "\n", - "| 逻辑操作符 | 含义 |\n", - "| :----: | :---- |\n", - "| `XY` | `Y`跟在`X`后面 |\n", - "| `X|Y` | `X`或`Y` |\n", - "| `(X)` | 捕获组(capturing group)。可以在表达式中用`\\i`引用第i个捕获组 |\n", - "\n", - "下面是不同的边界匹配符:\n", - "\n", - "| 边界匹配符 | 含义 |\n", - "| :----: | :---- |\n", - "| `^` | 一行的开始 |\n", - "| `$` | 一行的结束 |\n", - "| `\\b` | 词的边界 |\n", - "| `\\B` | 非词的边界 |\n", - "| `\\G` | 前一个匹配的结束 |\n", - "\n", - "作为演示,下面的每一个正则表达式都能成功匹配字符序列“Rudolph”:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Rudolph.java \n", - "\n", - "public class Rudolph { \n", - " public static void main(String[] args) { \n", - " for(String pattern : new String[]{ \n", - " \"Rudolph\", \n", - " \"[rR]udolph\", \n", - " \"[rR][aeiou][a-z]ol.*\", \n", - " \"R.*\" }) \n", - " System.out.println(\"Rudolph\".matches(pattern)); \n", - " } \n", - "} \n", - "/* Output: \n", - "true \n", - "true \n", - "true \n", - "true \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们的目的并不是编写最难理解的正则表达式,而是尽量编写能够完成任务的、最简单以及最必要的正则表达式。一旦真正开始使用正则表达式了,你就会发现,在编写新的表达式之前,你通常会参考代码中已经用到的正则表达式。\n", - "### 量词\n", - "量词描述了一个模式捕获输入文本的方式:\n", - "\n", - "+ **贪婪型**:\n", - "量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。导致此问题的一个典型理由就是假定我们的模式仅能匹配第一个可能的字符组,如果它是贪婪的,那么它就会继续往下匹配。\n", - "\n", - "+ **勉强型**:\n", - "用问号来指定,这个量词匹配满足模式所需的最少字符数。因此也被称作懒惰的、最少匹配的、非贪婪的或不贪婪的。\n", - "\n", - "+ **占有型**:\n", - "目前,这种类型的量词只有在 Java 语言中才可用(在其他语言中不可用),并且也更高级,因此我们大概不会立刻用到它。当正则表达式被应用于 `String` 时,它会产生相当多的状态,以便在匹配失败时可以回溯。而“占有的”量词并不保存这些中间状态,因此它们可以防止回溯。它们常常用于防止正则表达式失控,因此可以使正则表达式执行起来更高效。\n", - "\n", - "| 贪婪型 | 勉强型 | 占有型 | 如何匹配 |\n", - "| ---- | ---- | ---- | ---- |\n", - "| `X?` | `X??` | `X?+` | 一个或零个`X` |\n", - "| `X*` | `X*?` | `X*+` | 零个或多个`X` |\n", - "| `X+` | `X+?` | `X++` | 一个或多个`X` |\n", - "| `X{n}` | `X{n}?` | `X{n}+` | 恰好`n`次`X` |\n", - "| `X{n,}` | `X{n,}?` | `X{n,}+` | 至少`n`次`X` |\n", - "| `X{n,m}` | `X{n,m}?` | `X{n,m}+` | `X`至少`n`次,但不超过`m`次 |\n", - "\n", - "应该非常清楚地意识到,表达式 `X` 通常必须要用圆括号括起来,以便它能够按照我们期望的效果去执行。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "abc+" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "看起来它似乎应该匹配1个或多个`abc`序列,如果我们把它应用于输入字符串`abcabcabc`,则实际上会获得3个匹配。然而,这个表达式实际上表示的是:匹配`ab`,后面跟随1个或多个`c`。要表明匹配1个或多个完整的字符串`abc`,我们必须这样表示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "(abc)+" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你会发现,在使用正则表达式时很容易混淆,因为它是一种在 Java 之上的新语言。\n", - "### `CharSequence`\n", - "接口 `CharSequence` 从 `CharBuffer`、`String`、`StringBuffer`、`StringBuilder` 类中抽象出了字符序列的一般化定义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "interface CharSequence { \n", - " char charAt(int i); \n", - " int length();\n", - " CharSequence subSequence(int start, int end);\n", - " String toString(); \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,这些类都实现了该接口。多数正则表达式操作都接受 `CharSequence` 类型参数。\n", - "### `Pattern` 和 `Matcher`\n", - "通常,比起功能有限的 `String` 类,我们更愿意构造功能强大的正则表达式对象。只需导入 `java.util.regex`包,然后用 `static Pattern.compile()` 方法来编译你的正则表达式即可。它会根据你的 `String` 类型的正则表达式生成一个 `Pattern` 对象。接下来,把你想要检索的字符串传入 `Pattern` 对象的 `matcher()` 方法。`matcher()` 方法会生成一个 `Matcher` 对象,它有很多功能可用(可以参考 `java.util.regext.Matcher` 的 JDK 文档)。例如,它的 `replaceAll()` 方法能将所有匹配的部分都替换成你传入的参数。\n", - "\n", - "作为第一个示例,下面的类可以用来测试正则表达式,看看它们能否匹配一个输入字符串。第一个控制台参数是将要用来搜索匹配的输入字符串,后面的一个或多个参数都是正则表达式,它们将被用来在输入的第一个字符串中查找匹配。在Unix/Linux上,命令行中的正则表达式必须用引号括起来。这个程序在测试正则表达式时很有用,特别是当你想验证它们是否具备你所期待的匹配功能的时候。[^3]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/TestRegularExpression.java \n", - "// Simple regular expression demonstration \n", - "// {java TestRegularExpression \n", - "// abcabcabcdefabc \"abc+\" \"(abc)+\" } \n", - "import java.util.regex.*; \n", - "\n", - "public class TestRegularExpression {\n", - " public static void main(String[] args) { \n", - " if(args.length < 2) { \n", - " System.out.println( \n", - " \"Usage:\\njava TestRegularExpression \" + \n", - " \"characterSequence regularExpression+\"); \n", - " System.exit(0); \n", - " }\n", - " System.out.println(\"Input: \\\"\" + args[0] + \"\\\"\"); \n", - " for(String arg : args) { \n", - " System.out.println( \n", - " \"Regular expression: \\\"\" + arg + \"\\\"\"); \n", - " Pattern p = Pattern.compile(arg); \n", - " Matcher m = p.matcher(args[0]); \n", - " while(m.find()) { \n", - " System.out.println( \n", - " \"Match \\\"\" + m.group() + \"\\\" at positions \" + \n", - " m.start() + \"-\" + (m.end() - 1)); \n", - " } \n", - " } \n", - " }\n", - "}\n", - "/* Output: \n", - "Input: \"abcabcabcdefabc\" \n", - "Regular expression: \"abcabcabcdefabc\" \n", - "Match \"abcabcabcdefabc\" at positions 0-14 \n", - "Regular expression: \"abc+\" \n", - "Match \"abc\" at positions 0-2 \n", - "Match \"abc\" at positions 3-5 \n", - "Match \"abc\" at positions 6-8 \n", - "Match \"abc\" at positions 12-14 \n", - "Regular expression: \"(abc)+\"\n", - "Match \"abcabcabc\" at positions 0-8 \n", - "Match \"abc\" at positions 12-14 \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "还可以在控制台参数中加入`“(abc){2,}”`,看看执行结果。\n", - "\n", - "`Pattern` 对象表示编译后的正则表达式。从这个例子可以看到,我们使用已编译的 `Pattern` 对象上的 `matcher()` 方法,加上一个输入字符串,从而共同构造了一个 `Matcher` 对象。同时,`Pattern` 类还提供了一个`static`方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "static boolean matches(String regex, CharSequence input)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "该方法用以检查 `regex` 是否匹配整个 `CharSequence` 类型的 `input` 参数。编译后的 `Pattern` 对象还提供了 `split()` 方法,它从匹配了 `regex` 的地方分割输入字符串,返回分割后的子字符串 `String` 数组。\n", - "\n", - "通过调用 `Pattern.matcher()` 方法,并传入一个字符串参数,我们得到了一个 `Matcher` 对象。使用 `Matcher` 上的方法,我们将能够判断各种不同类型的匹配是否成功:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "boolean matches() \n", - "boolean lookingAt() \n", - "boolean find() \n", - "boolean find(int start)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中的 `matches()` 方法用来判断整个输入字符串是否匹配正则表达式模式,而 `lookingAt()` 则用来判断该字符串(不必是整个字符串)的起始部分是否能够匹配模式。\n", - "### `find()`\n", - "`Matcher.find()` 方法可用来在 `CharSequence` 中查找多个匹配。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Finding.java \n", - "import java.util.regex.*; \n", - "\n", - "public class Finding { \n", - " public static void main(String[] args) { \n", - " Matcher m = Pattern.compile(\"\\\\w+\") \n", - " .matcher( \n", - " \"Evening is full of the linnet's wings\"); \n", - " while(m.find()) \n", - " System.out.print(m.group() + \" \"); \n", - " System.out.println(); \n", - " int i = 0; \n", - " while(m.find(i)) { \n", - " System.out.print(m.group() + \" \"); \n", - " i++; \n", - " } \n", - " }\n", - "}\n", - "/* Output: \n", - "Evening is full of the linnet s wings\n", - "Evening vening ening ning ing ng g is is s full full \n", - "ull ll l of of f the the he e linnet linnet innet nnet \n", - "net et t s s wings wings ings ngs gs s \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "模式 `\\\\w+` 将字符串划分为词。`find()` 方法像迭代器那样向前遍历输入字符串。而第二个重载的 `find()` 接收一个整型参数,该整数表示字符串中字符的位置,并以其作为搜索的起点。从结果可以看出,后一个版本的 `find()` 方法能够根据其参数的值,不断重新设定搜索的起始位置。\n", - "### 组(Groups)\n", - "组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为 0 表示整个表达式,组号 1 表示被第一对括号括起来的组,以此类推。因此,下面这个表达式," - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "A(B(C))D" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "中有三个组:组 0 是 `ABCD`,组 1 是 `BC`,组 2 是 `C`。\n", - "\n", - "`Matcher` 对象提供了一系列方法,用以获取与组相关的信息:\n", - "\n", - "+ `public int groupCount()` 返回该匹配器的模式中的分组数目,组 0 不包括在内。\n", - "+ `public String group()` 返回前一次匹配操作(例如 `find()`)的第 0 组(整个匹配)。\n", - "+ `public String group(int i)` 返回前一次匹配操作期间指定的组号,如果匹配成功,但是指定的组没有匹配输入字符串的任何部分,则将返回 `null`。\n", - "+ `public int start(int group)` 返回在前一次匹配操作中寻找到的组的起始索引。\n", - "+ `public int end(int group)` 返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。\n", - "\n", - "下面是正则表达式组的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Groups.java\n", - "import java.util.regex.*; \n", - "\n", - "public class Groups { \n", - " public static final String POEM = \n", - " \"Twas brillig, and the slithy toves\\n\" + \n", - " \"Did gyre and gimble in the wabe.\\n\" + \n", - " \"All mimsy were the borogoves,\\n\" + \n", - " \"And the mome raths outgrabe.\\n\\n\" + \n", - " \"Beware the Jabberwock, my son,\\n\" + \n", - " \"The jaws that bite, the claws that catch.\\n\" + \n", - " \"Beware the Jubjub bird, and shun\\n\" + \n", - " \"The frumious Bandersnatch.\"; \n", - " public static void main(String[] args) { \n", - " Matcher m = Pattern.compile(\n", - " \"(?m)(\\\\S+)\\\\s+((\\\\S+)\\\\s+(\\\\S+))$\") \n", - " .matcher(POEM); \n", - " while(m.find()) { \n", - " for(int j = 0; j <= m.groupCount(); j++) \n", - " System.out.print(\"[\" + m.group(j) + \"]\"); \n", - " System.out.println(); \n", - " } \n", - " } \n", - "}\n", - "/* Output: \n", - "[the slithy toves][the][slithy toves][slithy][toves] \n", - "[in the wabe.][in][the wabe.][the][wabe.] \n", - "[were the borogoves,][were][the \n", - "borogoves,][the][borogoves,] \n", - "[mome raths outgrabe.][mome][raths \n", - "outgrabe.][raths][outgrabe.] \n", - "[Jabberwock, my son,][Jabberwock,][my son,][my][son,] \n", - "[claws that catch.][claws][that catch.][that][catch.] \n", - "[bird, and shun][bird,][and shun][and][shun] \n", - "[The frumious Bandersnatch.][The][frumious \n", - "Bandersnatch.][frumious][Bandersnatch.] \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这首诗来自于 Lewis Carroll 所写的 *Through the Looking Glass* 中的 “Jabberwocky”。可以看到这个正则表达式模式有许多圆括号分组,由任意数目的非空白符(`\\\\S+`)及随后的任意数目的空白符(`\\\\s+`)所组成。目的是捕获每行的最后3个词,每行最后以 `\\$` 结束。不过,在正常情况下是将 `\\$` 与整个输入序列的末端相匹配。所以我们一定要显式地告知正则表达式注意输入序列中的换行符。这可以由序列开头的模式标记 `(?m)` 来完成(模式标记马上就会介绍)。\n", - "### `start()` 和 `end()`\n", - "在匹配操作成功之后,`start()` 返回先前匹配的起始位置的索引,而 `end()` 返回所匹配的最后字符的索引加一的值。匹配操作失败之后(或先于一个正在进行的匹配操作去尝试)调用 `start()` 或 `end()` 将会产生 `IllegalStateException`。下面的示例还同时展示了 `matches()` 和 `lookingAt()` 的用法 [^4]:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/StartEnd.java \n", - "import java.util.regex.*; \n", - "\n", - "public class StartEnd {\n", - " public static String input =\n", - " \"As long as there is injustice, whenever a\\n\" + \n", - " \"Targathian baby cries out, wherever a distress\\n\" + \n", - " \"signal sounds among the stars \" + \n", - " \"... We'll be there.\\n\"+ \n", - " \"This fine ship, and this fine crew ...\\n\" + \n", - " \"Never give up! Never surrender!\"; \n", - " private static class Display {\n", - " private boolean regexPrinted = false; \n", - " private String regex;\n", - " Display(String regex) { this.regex = regex; } \n", - " \n", - " void display(String message) { \n", - " if(!regexPrinted) { \n", - " System.out.println(regex); \n", - " regexPrinted = true; \n", - " } \n", - " System.out.println(message); \n", - " } \n", - " } \n", - " \n", - " static void examine(String s, String regex) { \n", - " Display d = new Display(regex); \n", - " Pattern p = Pattern.compile(regex); \n", - " Matcher m = p.matcher(s); \n", - " while(m.find()) \n", - " d.display(\"find() '\" + m.group() + \n", - " \"' start = \"+ m.start() + \" end = \" + m.end()); \n", - " if(m.lookingAt()) // No reset() necessary \n", - " d.display(\"lookingAt() start = \" \n", - " + m.start() + \" end = \" + m.end()); \n", - " if(m.matches()) // No reset() necessary \n", - " d.display(\"matches() start = \" \n", - " + m.start() + \" end = \" + m.end()); \n", - " }\n", - " \n", - " public static void main(String[] args) { \n", - " for(String in : input.split(\"\\n\")) { \n", - " System.out.println(\"input : \" + in); \n", - " for(String regex : new String[]{\"\\\\w*ere\\\\w*\", \n", - " \"\\\\w*ever\", \"T\\\\w+\", \"Never.*?!\"}) \n", - " examine(in, regex); \n", - " } \n", - " } \n", - "} \n", - "/* Output: \n", - "input : As long as there is injustice, whenever a \n", - "\\w*ere\\w* \n", - "find() 'there' start = 11 end = 16 \n", - "\\w*ever \n", - "find() 'whenever' start = 31 end = 39 \n", - "input : Targathian baby cries out, wherever a distress \n", - "\\w*ere\\w* \n", - "find() 'wherever' start = 27 end = 35 \n", - "\\w*ever \n", - "find() 'wherever' start = 27 end = 35 \n", - "T\\w+ find() 'Targathian' start = 0 end = 10 \n", - "lookingAt() start = 0 end = 10 \n", - "input : signal sounds among the stars ... We'll be \n", - "there. \n", - "\\w*ere\\w* \n", - "find() 'there' start = 43 end = 48 \n", - "input : This fine ship, and this fine crew ... \n", - "T\\w+ find() 'This' start = 0 end = 4\n", - "lookingAt() start = 0 end = 4 \n", - "input : Never give up! Never surrender! \n", - "\\w*ever \n", - "find() 'Never' start = 0 end = 5 \n", - "find() 'Never' start = 15 end = 20 \n", - "lookingAt() start = 0 end = 5 \n", - "Never.*?! \n", - "find() 'Never give up!' start = 0 end = 14 \n", - "find() 'Never surrender!' start = 15 end = 31 \n", - "lookingAt() start = 0 end = 14 \n", - "matches() start = 0 end = 31 \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,`find()` 可以在输入的任意位置定位正则表达式,而 `lookingAt()` 和 `matches()` 只有在正则表达式与输入的最开始处就开始匹配时才会成功。`matches()` 只有在整个输入都匹配正则表达式时才会成功,而 `lookingAt()` [^5] 只要输入的第一部分匹配就会成功。\n", - "### `Pattern` 标记\n", - "`Pattern` 类的 `compile()` 方法还有另一个版本,它接受一个标记参数,以调整匹配行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Pattern Pattern.compile(String regex, int flag)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中的 `flag` 来自以下 `Pattern` 类中的常量\n", - "\n", - "| 编译标记 | 效果 |\n", - "| ---- |---- |\n", - "| `Pattern.CANON_EQ` | 当且仅当两个字符的完全规范分解相匹配时,才认为它们是匹配的。例如,如果我们指定这个标记,表达式`\\u003F`就会匹配字符串`?`。默认情况下,匹配不考虑规范的等价性 |\n", - "| `Pattern.CASE_INSENSITIVE(?i)` | 默认情况下,大小写不敏感的匹配假定只有US-ASCII字符集中的字符才能进行。这个标记允许模式匹配不考虑大小写(大写或小写)。通过指定`UNICODE_CASE`标记及结合此标记。基于Unicode的大小写不敏感的匹配就可以开启了 |\n", - "| `Pattern.COMMENTS(?x)` | 在这种模式下,空格符将被忽略掉,并且以`#`开始直到行末的注释也会被忽略掉。通过嵌入的标记表达式也可以开启Unix的行模式 |\n", - "| `Pattern.DOTALL(?s)` | 在dotall模式下,表达式`.`匹配所有字符,包括行终止符。默认情况下,`.`不会匹配行终止符 |\n", - "| `Pattern.MULTILINE(?m)` | 在多行模式下,表达式`^`和`$`分别匹配一行的开始和结束。`^`还匹配输入字符串的开始,而`$`还匹配输入字符串的结尾。默认情况下,这些表达式仅匹配输入的完整字符串的开始和结束 |\n", - "| `Pattern.UNICODE_CASE(?u)` | 当指定这个标记,并且开启`CASE_INSENSITIVE`时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行 |\n", - "| `Pattern.UNIX_LINES(?d)` | 在这种模式下,在`.`、`^`和`$`的行为中,只识别行终止符`\\n` |\n", - "\n", - "在这些标记中,`Pattern.CASE_INSENSITIVE`、`Pattern.MULTILINE` 以及 `Pattern.COMMENTS`(对声明或文档有用)特别有用。请注意,你可以直接在正则表达式中使用其中的大多数标记,只需要将上表中括号括起来的字符插入到正则表达式中,你希望它起作用的位置即可。\n", - "\n", - "你还可以通过“或”(`|`)操作符组合多个标记的功能:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ReFlags.java \n", - "import java.util.regex.*; \n", - "\n", - "public class ReFlags { \n", - " public static void main(String[] args) { \n", - " Pattern p = Pattern.compile(\"^java\", \n", - " Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); \n", - " Matcher m = p.matcher( \n", - " \"java has regex\\nJava has regex\\n\" + \n", - " \"JAVA has pretty good regular expressions\\n\" + \n", - " \"Regular expressions are in Java\"); \n", - " while(m.find()) \n", - " System.out.println(m.group()); \n", - " } \n", - "}\n", - "/* Output: \n", - "java \n", - "Java \n", - "JAVA \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个例子中,我们创建了一个模式,它将匹配所有以“java”、“Java”和“JAVA”等开头的行,并且是在设置了多行标记的状态下,对每一行(从字符序列的第一个字符开始,至每一个行终止符)都进行匹配。注意,`group()` 方法只返回已匹配的部分。\n", - "### `split()`\n", - "`split()`方法将输入 `String` 断开成 `String` 对象数组,断开边界由正则表达式确定:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "String[] split(CharSequence input) \n", - "String[] split(CharSequence input, int limit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个快速而方便的方法,可以按照通用边界断开输入文本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/SplitDemo.java \n", - "import java.util.regex.*; \n", - "import java.util.*; \n", - "\n", - "public class SplitDemo { \n", - " public static void main(String[] args) { \n", - " String input = \n", - " \"This!!unusual use!!of exclamation!!points\"; \n", - " System.out.println(Arrays.toString( \n", - " Pattern.compile(\"!!\").split(input))); \n", - " // Only do the first three: \n", - " System.out.println(Arrays.toString( \n", - " Pattern.compile(\"!!\").split(input, 3))); \n", - " }\n", - "}\n", - "/* Output: \n", - "[This, unusual use, of exclamation, points] \n", - "[This, unusual use, of exclamation!!points]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第二种形式的 `split()` 方法可以限制将输入分割成字符串的数量。\n", - "### 替换操作\n", - "正则表达式在进行文本替换时特别方便,它提供了许多方法:\n", - "+ `replaceFirst(String replacement)` 以参数字符串 `replacement` 替换掉第一个匹配成功的部分。\n", - "+ `replaceAll(String replacement)` 以参数字符串 `replacement` 替换所有匹配成功的部分。\n", - "+ `appendReplacement(StringBuffer sbuf, String replacement)` 执行渐进式的替换,而不是像 `replaceFirst()` 和 `replaceAll()` 那样只替换第一个匹配或全部匹配。这是一个非常重要的方法。它允许你调用其他方法来生成或处理 `replacement`(`replaceFirst()` 和 `replaceAll()` 则只能使用一个固定的字符串),使你能够以编程的方式将目标分割成组,从而具备更强大的替换功能。\n", - "+ `appendTail(StringBuffer sbuf)` 在执行了一次或多次 `appendReplacement()` 之后,调用此方法可以将输入字符串余下的部分复制到 `sbuf` 中。\n", - "\n", - "下面的程序演示了如何使用这些替换方法。开头部分注释掉的文本,就是正则表达式要处理的输入字符串:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/TheReplacements.java \n", - "import java.util.regex.*; \n", - "import java.nio.file.*; \n", - "import java.util.stream.*;\n", - "\n", - "/*! Here's a block of text to use as input to \n", - " the regular expression matcher. Note that we \n", - " first extract the block of text by looking for \n", - " the special delimiters, then process the \n", - " extracted block. !*/\n", - "\n", - "public class TheReplacements { \n", - " public static void main(String[] args) throws Exception { \n", - " String s = Files.lines( \n", - " Paths.get(\"TheReplacements.java\")) \n", - " .collect(Collectors.joining(\"\\n\")); \n", - " // Match specially commented block of text above: \n", - " Matcher mInput = Pattern.compile( \n", - " \"/\\\\*!(.*)!\\\\*/\", Pattern.DOTALL).matcher(s); \n", - " if(mInput.find()) \n", - " s = mInput.group(1); // Captured by parentheses \n", - " // Replace two or more spaces with a single space: \n", - " s = s.replaceAll(\" {2,}\", \" \"); \n", - " // Replace 1+ spaces at the beginning of each \n", - " // line with no spaces. Must enable MULTILINE mode: \n", - " s = s.replaceAll(\"(?m)^ +\", \"\"); \n", - " System.out.println(s); \n", - " s = s.replaceFirst(\"[aeiou]\", \"(VOWEL1)\"); \n", - " StringBuffer sbuf = new StringBuffer(); \n", - " Pattern p = Pattern.compile(\"[aeiou]\"); \n", - " Matcher m = p.matcher(s); \n", - " // Process the find information as you \n", - " // perform the replacements: \n", - " while(m.find()) \n", - " m.appendReplacement(sbuf, m.group().toUpperCase()); \n", - " // Put in the remainder of the text: \n", - " m.appendTail(sbuf); \n", - " System.out.println(sbuf);\n", - " } \n", - "}\n", - "/* Output: \n", - "Here's a block of text to use as input to \n", - "the regular expression matcher. Note that we \n", - "first extract the block of text by looking for \n", - "the special delimiters, then process the \n", - "extracted block. \n", - "H(VOWEL1)rE's A blOck Of tExt tO UsE As InpUt tO \n", - "thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE \n", - "fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr \n", - "thE spEcIAl dElImItErs, thEn prOcEss thE \n", - "ExtrActEd blOck. \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此处使用上一章介绍过的 [`Files`](./17-Files.md) 类打开并读入文件。`Files.lines()` 返回一个 `Stream` 对象,包含读入的所有行,`Collectors.joining()` 在每一行的结尾追加参数字符序列,最终拼接成一个 `String` 对象。\n", - "\n", - "`mInput` 匹配 `/*!` 和 `!*/` 之间的所有文字(注意分组的括号)。接下来,将存在两个或两个以上空格的地方,缩减为一个空格,并且删除每行开头部分的所有空格(为了使每一行都达到这个效果,而不仅仅是删除文本开头部分的空格,这里特意开启了多行模式)。这两个替换操作所使用的的 `replaceAll()` 是 `String` 对象自带的方法,在这里,使用此方法更方便。注意,因为这两个替换操作都只使用了一次 `replaceAll()`,所以,与其编译为 `Pattern`,不如直接使用 `String` 的 `replaceAll()` 方法,而且开销也更小些。\n", - "\n", - "`replaceFirst()` 只对找到的第一个匹配进行替换。此外,`replaceFirst()` 和 `replaceAll()` 方法用来替换的只是普通字符串,所以,如果想对这些替换字符串进行某些特殊处理,这两个方法时无法胜任的。如果你想要那么做,就应该使用 `appendReplacement()` 方法。该方法允许你在执行替换的过程中,操作用来替换的字符串。在这个例子中,先构造了 `sbuf` 用来保存最终结果,然后用 `group()` 选择一个组,并对其进行处理,将正则表达式找到的元音字母替换成大些字母。一般情况下,你应该遍历执行所有的替换操作,然后再调用 `appendTail()` 方法,但是,如果你想模拟 `replaceFirst()`(或替换n次)的行为,那就只需要执行一次替换,然后调用 `appendTail()` 方法,将剩余未处理的部分存入 `sbuf` 即可。\n", - "\n", - "同时,`appendReplacement()` 方法还允许你通过 `\\$g` 直接找到匹配的某个组,这里的 `g` 就是组号。然而,它只能应付一些简单的处理,无法实现类似前面这个例子中的功能。\n", - "### `reset()`\n", - "通过 `reset()` 方法,可以将现有的 `Matcher` 对象应用于一个新的字符序列:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/Resetting.java \n", - "import java.util.regex.*; \n", - "\n", - "public class Resetting { \n", - " public static void main(String[] args) throws Exception { \n", - " Matcher m = Pattern.compile(\"[frb][aiu][gx]\") \n", - " .matcher(\"fix the rug with bags\"); \n", - " while(m.find()) \n", - " System.out.print(m.group() + \" \"); \n", - " System.out.println(); \n", - " m.reset(\"fix the rig with rags\"); \n", - " while(m.find()) \n", - " System.out.print(m.group() + \" \"); \n", - " } \n", - "} \n", - "/* Output: \n", - "fix rug bag \n", - "fix rig rag \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用不带参数的 `reset()` 方法,可以将 `Matcher` 对象重新设置到当前字符序列的起始位置。\n", - "### 正则表达式与 Java I/O\n", - "到目前为止,我们看到的例子都是将正则表达式用于静态的字符串。下面的例子将向你演示,如何应用正则表达式在一个文件中进行搜索匹配操作。`JGrep.java` 的灵感源自于 Unix 上的 *grep*。它有两个参数:文件名以及要匹配的正则表达式。输出的是每行有匹配的部分以及匹配部分在行中的位置。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/JGrep.java \n", - "// A very simple version of the \"grep\" program \n", - "// {java JGrep \n", - "// WhitherStringBuilder.java 'return|for|String'} \n", - "import java.util.regex.*; \n", - "import java.nio.file.*; \n", - "import java.util.stream.*;\n", - "\n", - "public class JGrep {  \n", - " public static void main(String[] args) throws Exception {    \n", - " if(args.length < 2) {      \n", - " System.out.println(        \n", - " \"Usage: java JGrep file regex\");      \n", - " System.exit(0);   \n", - " }    \n", - " Pattern p = Pattern.compile(args[1]);    \n", - " // Iterate through the lines of the input file:    \n", - " int index = 0;    \n", - " Matcher m = p.matcher(\"\");    \n", - " for(String line: Files.readAllLines(Paths.get(args[0]))) {      \n", - " m.reset(line);      \n", - " while(m.find())        \n", - " System.out.println(index++ + \": \" +          \n", - " m.group() + \": \" + m.start());   \n", - " } \n", - " } \n", - "} \n", - "/* Output: \n", - "0: for: 4 \n", - "1: for: 4 \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Files.readAllLines()` 返回一个 `List` 对象,这意味着可以用 *for-in* 进行遍历。虽然可以在 `for` 循环内部创建一个新的 `Matcher` 对象,但是,在循环体外创建一个空的 `Matcher` 对象,然后用 `reset()` 方法每次为 `Matcher` 加载一行输入,这种处理会有一定的性能优化。最后用 `find()` 搜索结果。\n", - "\n", - "这里读入的测试参数是 `JGrep.java` 文件,然后搜索以 `[Ssct]` 开头的单词。\n", - "\n", - "如果想要更深入地学习正则表达式,你可以阅读 Jeffrey E. F. Friedl 的《精通正则表达式(第2版)》。网络上也有很多正则表达式的介绍,你还可以从 Perl 和 Python 等其他语言的文档中找到有用的信息。\n", - "\n", - "\n", - "\n", - "## 扫描输入\n", - "到目前为止,从文件或标准输入读取数据还是一件相当痛苦的事情。一般的解决办法就是读入一行文本,对其进行分词,然后使用 `Integer`、`Double` 等类的各种解析方法来解析数据:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/SimpleRead.java \n", - "import java.io.*;\n", - "\n", - "public class SimpleRead {  \n", - " public static BufferedReader input =    \n", - " new BufferedReader(new StringReader(    \n", - " \"Sir Robin of Camelot\\n22 1.61803\"));  \n", - " public static void main(String[] args) {    \n", - " try {      \n", - " System.out.println(\"What is your name?\");      \n", - " String name = input.readLine();      \n", - " System.out.println(name);      \n", - " System.out.println(\"How old are you? \" +        \n", - " \"What is your favorite double?\");      \n", - " System.out.println(\"(input: )\");      \n", - " String numbers = input.readLine();      \n", - " System.out.println(numbers);      \n", - " String[] numArray = numbers.split(\" \");      \n", - " int age = Integer.parseInt(numArray[0]);      \n", - " double favorite = Double.parseDouble(numArray[1]);      \n", - " System.out.format(\"Hi %s.%n\", name);      \n", - " System.out.format(\"In 5 years you will be %d.%n\", age + 5);      \n", - " System.out.format(\"My favorite double is %f.\", favorite / 2);   \n", - " } catch(IOException e) {      \n", - " System.err.println(\"I/O exception\");   \n", - " } \n", - " } \n", - "}\n", - "/* Output: \n", - "What is your name? \n", - "Sir Robin of Camelot \n", - "How old are you? What is your favorite double? \n", - "(input: ) \n", - "22 1.61803\n", - "Hi Sir Robin of Camelot. \n", - "In 5 years you will be 27. \n", - "My favorite double is 0.809015. \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`input` 字段使用的类来自 `java.io`,[附录:流式 I/O](./Appendix-IO-Streams.md) 详细介绍了相关内容。`StringReader` 将 `String` 转化为可读的流对象,然后用这个对象来构造 `BufferedReader` 对象,因为我们要使用 `BufferedReader` 的 `readLine()` 方法。最终,我们可以使用 `input` 对象一次读取一行文本,就像从控制台读入标准输入一样。\n", - "\n", - "`readLine()` 方法将一行输入转为 `String` 对象。如果每一行数据正好对应一个输入值,那这个方法也还可行。但是,如果两个输入值在同一行中,事情就不好办了,我们必须分解这个行,才能分别解析所需的输入值。在这个例子中,分解的操作发生在创建 `numArray`时。\n", - "\n", - "终于,Java SE5 新增了 `Scanner` 类,它可以大大减轻扫描输入的工作负担:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/BetterRead.java \n", - "import java.util.*; \n", - "\n", - "public class BetterRead {\n", - " public static void main(String[] args) {\n", - " Scanner stdin = new Scanner(SimpleRead.input);\n", - " System.out.println(\"What is your name?\");\n", - " String name = stdin.nextLine();\n", - " System.out.println(name);\n", - " System.out.println(\n", - " \"How old are you? What is your favorite double?\");\n", - " System.out.println(\"(input: )\");\n", - " int age = stdin.nextInt();\n", - " double favorite = stdin.nextDouble();\n", - " System.out.println(age);\n", - " System.out.println(favorite);\n", - " System.out.format(\"Hi %s.%n\", name);\n", - " System.out.format(\"In 5 years you will be %d.%n\",\n", - " age + 5);\n", - " System.out.format(\"My favorite double is %f.\",\n", - " favorite / 2);\n", - " }\n", - "}\n", - "/* Output: \n", - "What is your name? \n", - "Sir Robin of Camelot \n", - "How old are you? What is your favorite double? \n", - "(input: ) \n", - "22 \n", - "1.61803 \n", - "Hi Sir Robin of Camelot. \n", - "In 5 years you will be 27. \n", - "My favorite double is 0.809015. \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Scanner` 的构造器可以接收任意类型的输入对象,包括 `File`、`InputStream`、`String` 或者像此例中的`Readable` 实现类。`Readable` 是 Java SE5 中新加入的一个接口,表示“具有 `read()` 方法的某种东西”。上一个例子中的 `BufferedReader` 也归于这一类。\n", - "\n", - "有了 `Scanner`,所有的输入、分词、以及解析的操作都隐藏在不同类型的 `next` 方法中。普通的 `next()` 方法返回下一个 `String`。所有的基本类型(除 `char` 之外)都有对应的 `next` 方法,包括 `BigDecimal` 和 `BigInteger`。所有的 next 方法,只有在找到一个完整的分词之后才会返回。`Scanner` 还有相应的 `hasNext` 方法,用以判断下一个输入分词是否是所需的类型,如果是则返回 `true`。\n", - "\n", - "在 `BetterRead.java` 中没有用 `try` 区块捕获`IOException`。因为,`Scanner` 有一个假设,在输入结束时会抛出 `IOException`,所以 `Scanner` 会把 `IOException` 吞掉。不过,通过 `ioException()` 方法,你可以找到最近发生的异常,因此,你可以在必要时检查它。\n", - "### `Scanner` 分隔符\n", - "默认情况下,`Scanner` 根据空白字符对输入进行分词,但是你可以用正则表达式指定自己所需的分隔符:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ScannerDelimiter.java \n", - "import java.util.*;\n", - "public class ScannerDelimiter {  \n", - " public static void main(String[] args) {    \n", - " Scanner scanner = new Scanner(\"12, 42, 78, 99, 42\");    \n", - " scanner.useDelimiter(\"\\\\s*,\\\\s*\");    \n", - " while(scanner.hasNextInt())    \n", - " System.out.println(scanner.nextInt()); \n", - " } \n", - "}\n", - "/* Output: \n", - "12 \n", - "42 \n", - "78 \n", - "99 \n", - "42 \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个例子使用逗号(包括逗号前后任意的空白字符)作为分隔符,同样的技术也可以用来读取逗号分隔的文件。我们可以用 `useDelimiter()` 来设置分隔符,同时,还有一个 `delimiter()` 方法,用来返回当前正在作为分隔符使用的 `Pattern` 对象。\n", - "### 用正则表达式扫描\n", - "除了能够扫描基本类型之外,你还可以使用自定义的正则表达式进行扫描,这在扫描复杂数据时非常有用。下面的例子将扫描一个防火墙日志文件中的威胁数据:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ThreatAnalyzer.java \n", - "import java.util.regex.*; \n", - "import java.util.*;\n", - "public class ThreatAnalyzer { \n", - " static String threatData =    \n", - " \"58.27.82.161@08/10/2015\\n\" +   \n", - " \"204.45.234.40@08/11/2015\\n\" +    \n", - " \"58.27.82.161@08/11/2015\\n\" +    \n", - " \"58.27.82.161@08/12/2015\\n\" +    \n", - " \"58.27.82.161@08/12/2015\\n\" +\n", - "     \"[Next log section with different data format]\";  \n", - " public static void main(String[] args) { \n", - " Scanner scanner = new Scanner(threatData);    \n", - " String pattern = \"(\\\\d+[.]\\\\d+[.]\\\\d+[.]\\\\d+)@\" +      \n", - " \"(\\\\d{2}/\\\\d{2}/\\\\d{4})\";    \n", - " while(scanner.hasNext(pattern)) {      \n", - " scanner.next(pattern);      \n", - " MatchResult match = scanner.match();      \n", - " String ip = match.group(1);      \n", - " String date = match.group(2);      \n", - " System.out.format(        \n", - " \"Threat on %s from %s%n\", date,ip);   \n", - " } \n", - " } \n", - "} \n", - "/* Output: \n", - "Threat on 08/10/2015 from 58.27.82.161 \n", - "Threat on 08/11/2015 from 204.45.234.40 \n", - "Threat on 08/11/2015 from 58.27.82.161 \n", - "Threat on 08/12/2015 from 58.27.82.161 \n", - "Threat on 08/12/2015 from 58.27.82.161 \n", - "*/ " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当 `next()` 方法配合指定的正则表达式使用时,将找到下一个匹配该模式的输入部分,调用 `match()` 方法就可以获得匹配的结果。如上所示,它的工作方式与之前看到的正则表达式匹配相似。\n", - "\n", - "在配合正则表达式使用扫描时,有一点需要注意:它仅仅针对下一个输入分词进行匹配,如果你的正则表达式中含有分隔符,那永远不可能匹配成功。\n", - "\n", - "\n", - "\n", - "## StringTokenizer类\n", - "在 Java 引入正则表达式(J2SE1.4)和 `Scanner` 类(Java SE5)之前,分割字符串的唯一方法是使用 `StringTokenizer` 来分词。不过,现在有了正则表达式和 `Scanner`,我们可以使用更加简单、更加简洁的方式来完成同样的工作了。下面的例子中,我们将 `StringTokenizer` 与另外两种技术简单地做了一个比较:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// strings/ReplacingStringTokenizer.java \n", - "import java.util.*; \n", - "\n", - "public class ReplacingStringTokenizer { \n", - " public static void main(String[] args) { \n", - " String input = \n", - " \"But I'm not dead yet! I feel happy!\"; \n", - " StringTokenizer stoke = new StringTokenizer(input); \n", - " while(stoke.hasMoreElements()) \n", - " System.out.print(stoke.nextToken() + \" \"); \n", - " System.out.println(); \n", - " System.out.println(Arrays.toString(input.split(\" \"))); \n", - " Scanner scanner = new Scanner(input); \n", - " while(scanner.hasNext()) \n", - " System.out.print(scanner.next() + \" \"); \n", - " }\n", - "} \n", - "/* Output: \n", - "But I'm not dead yet! I feel happy! \n", - "[But, I'm, not, dead, yet!, I, feel, happy!] \n", - "But I'm not dead yet! I feel happy! \n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用正则表达式或 `Scanner` 对象,我们能够以更加复杂的模式来分割一个字符串,而这对于 `StringTokenizer` 来说就很困难了。基本上,我们可以放心地说,`StringTokenizer` 已经可以废弃不用了。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "过去,Java 对于字符串操作的技术相当不完善。不过随着近几个版本的升级,我们可以看到,Java 已经从其他语言中吸取了许多成熟的经验。到目前为止,它对字符串操作的支持已经很完善了。不过,有时你还要在细节上注意效率问题,例如恰当地使用 `StringBuilder` 等。\n", - "\n", - "\n", - "[^1]: C++允许编程人员任意重载操作符。这通常是很复杂的过程(参见Prentice Hall于2000年编写的《Thinking in C++(第2版)》第10章),因此Java设计者认为这是很糟糕的功能,不应该纳入到Java中。起始重载操作符并没有糟糕到只能自己去重载的地步,但具有讽刺意味的是,与C++相比,在Java中使用操作符重载要容易得多。这一点可以在Python(参见[www.Python.org](http://www.python.org))和C#中看到,它们都有垃圾回收机制,操作符重载也简单易懂。\n", - "\n", - "\n", - "[^2]: Java并非在一开始就支持正则表达式,因此这个令人费解的语法是硬塞进来的。\n", - "\n", - "\n", - "[^3]: 网上还有很多实用并且成熟的正则表达式工具。\n", - "\n", - "\n", - "[^4]: input来自于[Galaxy Quest](https://en.wikipedia.org/wiki/Galaxy_Quest)中Taggart司令的一篇演讲。\n", - "\n", - "\n", - "[^5]: 我不知道他们是如何想出这个方法名的,或者它到底指的什么。这只是代码审查很重要的原因之一。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/19-Type-Information.ipynb b/jupyter/19-Type-Information.ipynb deleted file mode 100644 index 5c13de9a..00000000 --- a/jupyter/19-Type-Information.ipynb +++ /dev/null @@ -1,3701 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第十九章 类型信息\n", - "\n", - "> RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息\n", - "\n", - "RTTI 把我们从只能在编译期进行面向类型操作的禁锢中解脱了出来,并且让我们可以使用某些非常强大的程序。对 RTTI 的需要,揭示了面向对象设计中许多有趣(并且复杂)的特性,同时也带来了关于如何组织程序的基本问题。\n", - "\n", - "本章将讨论 Java 是如何在运行时识别对象和类信息的。主要有两种方式:\n", - "\n", - "1. “传统的” RTTI:假定我们在编译时已经知道了所有的类型;\n", - "2. “反射”机制:允许我们在运行时发现和使用类的信息。\n", - "\n", - "\n", - "\n", - "## 为什么需要 RTTI\n", - "\n", - "下面看一下我们已经很熟悉的一个例子,它使用了多态的类层次结构。基类 `Shape` 是泛化的类型,从它派生出了三个具体类: `Circle` 、`Square` 和 `Triangle`(见下图所示)。\n", - "\n", - "![多态例子Shape的类层次结构图](../images/image-20190409114913825-4781754.png)\n", - "\n", - "这是一个典型的类层次结构图,基类位于顶部,派生类向下扩展。面向对象编程的一个基本目的是:让代码只操纵对基类(这里即 `Shape` )的引用。这样,如果你想添加一个新类(比如从 `Shape` 派生出 `Rhomboid`)来扩展程序,就不会影响原来的代码。在这个例子中,`Shape` 接口中动态绑定了 `draw()` 方法,这样做的目的就是让客户端程序员可以使用泛化的 `Shape` 引用来调用 `draw()`。`draw()` 方法在所有派生类里都会被覆盖,而且由于它是动态绑定的,所以即使通过 `Shape` 引用来调用它,也能产生恰当的行为,这就是多态。\n", - "\n", - "因此,我们通常会创建一个具体的对象(`Circle`、`Square` 或者 `Triangle`),把它向上转型成 `Shape` (忽略对象的具体类型),并且在后面的程序中使用 `Shape` 引用来调用在具体对象中被重载的方法(如 `draw()`)。\n", - "\n", - "代码如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Shapes.java\n", - "import java.util.stream.*;\n", - "\n", - "abstract class Shape {\n", - " void draw() { System.out.println(this + \".draw()\"); }\n", - " @Override\n", - " public abstract String toString();\n", - "}\n", - "\n", - "class Circle extends Shape {\n", - " @Override\n", - " public String toString() { return \"Circle\"; }\n", - "}\n", - "\n", - "class Square extends Shape {\n", - " @Override\n", - " public String toString() { return \"Square\"; }\n", - "}\n", - "\n", - "class Triangle extends Shape {\n", - " @Override\n", - " public String toString() { return \"Triangle\"; }\n", - "}\n", - "\n", - "public class Shapes {\n", - " public static void main(String[] args) {\n", - " Stream.of(\n", - " new Circle(), new Square(), new Triangle())\n", - " .forEach(Shape::draw);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Circle.draw()\n", - "Square.draw()\n", - "Triangle.draw()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "基类中包含 `draw()` 方法,它通过传递 `this` 参数传递给 `System.out.println()`,间接地使用 `toString()` 打印类标识符(注意:这里将 `toString()` 声明为 `abstract`,以此强制继承者覆盖该方法,并防止对 `Shape` 的实例化)。如果某个对象出现在字符串表达式中(涉及\"+\"和字符串对象的表达式),`toString()` 方法就会被自动调用,以生成表示该对象的 `String`。每个派生类都要覆盖(从 `Object` 继承来的)`toString()` 方法,这样 `draw()` 在不同情况下就打印出不同的消息(多态)。\n", - "\n", - "这个例子中,在把 `Shape` 对象放入 `Stream` 中时就会进行向上转型(隐式),但在向上转型的时候也丢失了这些对象的具体类型。对 `stream` 而言,它们只是 `Shape` 对象。\n", - "\n", - "严格来说,`Stream` 实际上是把放入其中的所有对象都当做 `Object` 对象来持有,只是取元素时会自动将其类型转为 `Shape`。这也是 RTTI 最基本的使用形式,因为在 Java 中,所有类型转换的正确性检查都是在运行时进行的。这也正是 RTTI 的含义所在:在运行时,识别一个对象的类型。\n", - "\n", - "另外在这个例子中,类型转换并不彻底:`Object` 被转型为 `Shape` ,而不是 `Circle`、`Square` 或者 `Triangle`。这是因为目前我们只能确保这个 `Stream` 保存的都是 `Shape`:\n", - "\n", - "- 编译期,`stream` 和 Java 泛型系统确保放入 `stream` 的都是 `Shape` 对象(`Shape` 子类的对象也可视为 `Shape` 的对象),否则编译器会报错;\n", - "- 运行时,自动类型转换确保了从 `stream` 中取出的对象都是 `Shape` 类型。\n", - "\n", - "接下来就是多态机制的事了,`Shape` 对象实际执行什么样的代码,是由引用所指向的具体对象(`Circle`、`Square` 或者 `Triangle`)决定的。这也符合我们编写代码的一般需求,通常,我们希望大部分代码尽可能少了解对象的具体类型,而是只与对象家族中的一个通用表示打交道(本例中即为 `Shape`)。这样,代码会更容易写,更易读和维护;设计也更容易实现,更易于理解和修改。所以多态是面向对象的基本目标。\n", - "\n", - "但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而 Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。\n", - "\n", - "\n", - "## `Class` 对象\n", - "\n", - "要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有\"常规\"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用 RTTI 的其它方式。\n", - "\n", - "类是程序的一部分,每个类都有一个 `Class` 对象。换言之,每当我们编写并且编译了一个新类,就会产生一个 `Class` 对象(更恰当的说,是被保存在一个同名的 `.class` 文件中)。为了生成这个类的对象,Java 虚拟机 (JVM) 先会调用\"类加载器\"子系统把这个类加载到内存中。\n", - "\n", - "类加载器子系统可能包含一条类加载器链,但有且只有一个**原生类加载器**,它是 JVM 实现的一部分。原生类加载器加载的是”可信类”(包括 Java API 类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果你有特殊需求(例如以某种特殊的方式加载类,以支持 Web 服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。\n", - "\n", - "所有的类都是第一次使用时动态加载到 JVM 中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。\n", - "\n", - "> 其实构造器也是类的静态方法,虽然构造器前面并没有 `static` 关键字。所以,使用 `new` 操作符创建类的新对象,这个操作也算作对类的静态成员引用。\n", - "\n", - "因此,Java 程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。这一点与许多传统编程语言不同,动态加载使得 Java 具有一些静态加载语言(如 C++)很难或者根本不可能实现的特性。\n", - "\n", - "类加载器首先会检查这个类的 `Class` 对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找 `.class` 文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM 会对其进行验证,确保它没有损坏,并且不包含不良的 Java 代码(这是 Java 安全防范的一种措施)。\n", - "\n", - "一旦某个类的 `Class` 对象被载入内存,它就可以用来创建这个类的所有对象。下面的示范程序可以证明这点:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/SweetShop.java\n", - "// 检查类加载器工作方式\n", - "class Cookie {\n", - " static { System.out.println(\"Loading Cookie\"); }\n", - "}\n", - "\n", - "class Gum {\n", - " static { System.out.println(\"Loading Gum\"); }\n", - "}\n", - "\n", - "class Candy {\n", - " static { System.out.println(\"Loading Candy\"); }\n", - "}\n", - "\n", - "public class SweetShop {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"inside main\");\n", - " new Candy();\n", - " System.out.println(\"After creating Candy\");\n", - " try {\n", - " Class.forName(\"Gum\");\n", - " } catch(ClassNotFoundException e) {\n", - " System.out.println(\"Couldn't find Gum\");\n", - " }\n", - " System.out.println(\"After Class.forName(\\\"Gum\\\")\");\n", - " new Cookie();\n", - " System.out.println(\"After creating Cookie\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "inside main\n", - "Loading Candy\n", - "After creating Candy\n", - "Loading Gum\n", - "After Class.forName(\"Gum\")\n", - "Loading Cookie\n", - "After creating Cookie" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上面的代码中,`Candy`、`Gum` 和 `Cookie` 这几个类都有一个 `static{...}` 静态初始化块,这些静态初始化块在类第一次被加载的时候就会执行。也就是说,静态初始化块会打印出相应的信息,告诉我们这些类分别是什么时候被加载了。而在主方法里边,创建对象的代码都放在了 `print()` 语句之间,以帮助我们判断类加载的时间点。\n", - "\n", - "从输出中可以看到,`Class` 对象仅在需要的时候才会被加载,`static` 初始化是在类加载时进行的。\n", - "\n", - "代码里面还有特别有趣的一行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Class.forName(\"Gum\");" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "所有 `Class` 对象都属于 `Class` 类,而且它跟其他普通对象一样,我们可以获取和操控它的引用(这也是类加载器的工作)。`forName()` 是 `Class` 类的一个静态方法,我们可以使用 `forName()` 根据目标类的类名(`String`)得到该类的 `Class` 对象。上面的代码忽略了 `forName()` 的返回值,因为那个调用是为了得到它产生的“副作用”。从结果可以看出,`forName()` 执行的副作用是如果 `Gum` 类没有被加载就加载它,而在加载的过程中,`Gum` 的 `static` 初始化块被执行了。\n", - "\n", - "还需要注意的是,如果 `Class.forName()` 找不到要加载的类,它就会抛出异常 `ClassNotFoundException`。上面的例子中我们只是简单地报告了问题,但在更严密的程序里,就要考虑在异常处理程序中把问题解决掉(具体例子详见[设计模式](./25-Patterns)章节)。\n", - "\n", - "无论何时,只要你想在运行时使用类型信息,就必须先得到那个 `Class` 对象的引用。`Class.forName()` 就是实现这个功能的一个便捷途径,因为使用该方法你不需要先持有这个类型 的对象。但是,如果你已经拥有了目标类的对象,那就可以通过调用 `getClass()` 方法来获取 `Class` 引用了,这个方法来自根类 `Object`,它将返回表示该对象实际类型的 `Class` 对象的引用。`Class` 包含很多有用的方法,下面代码展示了其中的一部分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/toys/ToyTest.java\n", - "// 测试 Class 类\n", - "// {java typeinfo.toys.ToyTest}\n", - "package typeinfo.toys;\n", - "\n", - "interface HasBatteries {}\n", - "interface Waterproof {}\n", - "interface Shoots {}\n", - "\n", - "class Toy {\n", - " // 注释下面的无参数构造器会引起 NoSuchMethodError 错误\n", - " Toy() {}\n", - " Toy(int i) {}\n", - "}\n", - "\n", - "class FancyToy extends Toy\n", - "implements HasBatteries, Waterproof, Shoots {\n", - " FancyToy() { super(1); }\n", - "}\n", - "\n", - "public class ToyTest {\n", - " static void printInfo(Class cc) {\n", - " System.out.println(\"Class name: \" + cc.getName() +\n", - " \" is interface? [\" + cc.isInterface() + \"]\");\n", - " System.out.println(\n", - " \"Simple name: \" + cc.getSimpleName());\n", - " System.out.println(\n", - " \"Canonical name : \" + cc.getCanonicalName());\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Class c = null;\n", - " try {\n", - " c = Class.forName(\"typeinfo.toys.FancyToy\");\n", - " } catch(ClassNotFoundException e) {\n", - " System.out.println(\"Can't find FancyToy\");\n", - " System.exit(1);\n", - " }\n", - "\n", - " printInfo(c);\n", - " for(Class face : c.getInterfaces())\n", - " printInfo(face);\n", - "\n", - " Class up = c.getSuperclass();\n", - " Object obj = null;\n", - "\n", - " try {\n", - " // Requires no-arg constructor:\n", - " obj = up.newInstance();\n", - " } catch(InstantiationException e) {\n", - " System.out.println(\"Cannot instantiate\");\n", - " System.exit(1);\n", - " } catch(IllegalAccessException e) {\n", - " System.out.println(\"Cannot access\");\n", - " System.exit(1);\n", - " }\n", - "\n", - " printInfo(obj.getClass());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Class name: typeinfo.toys.FancyToy is interface?\n", - "[false]\n", - "Simple name: FancyToy\n", - "Canonical name : typeinfo.toys.FancyToy\n", - "Class name: typeinfo.toys.HasBatteries is interface?\n", - "[true]\n", - "Simple name: HasBatteries\n", - "Canonical name : typeinfo.toys.HasBatteries\n", - "Class name: typeinfo.toys.Waterproof is interface?\n", - "[true]\n", - "Simple name: Waterproof\n", - "Canonical name : typeinfo.toys.Waterproof\n", - "Class name: typeinfo.toys.Shoots is interface? [true]\n", - "Simple name: Shoots\n", - "Canonical name : typeinfo.toys.Shoots\n", - "Class name: typeinfo.toys.Toy is interface? [false]\n", - "Simple name: Toy\n", - "Canonical name : typeinfo.toys.Toy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`FancyToy` 继承自 `Toy` 并实现了 `HasBatteries`、`Waterproof` 和 `Shoots` 接口。在 `main` 方法中,我们创建了一个 `Class` 引用,然后在 `try` 语句里边用 `forName()` 方法创建了一个 `FancyToy` 的类对象并赋值给该引用。需要注意的是,传递给 `forName()` 的字符串必须使用类的全限定名(包含包名)。\n", - "\n", - "`printInfo()` 函数使用 `getName()` 来产生完整类名,使用 `getSimpleName()` 产生不带包名的类名,`getCanonicalName()` 也是产生完整类名(除内部类和数组外,对大部分类产生的结果与 `getName()` 相同)。`isInterface()` 用于判断某个 `Class` 对象代表的是否为一个接口。因此,通过 `Class` 对象,你可以得到关于该类型的所有信息。\n", - "\n", - "在主方法中调用的 `Class.getInterfaces()` 方法返回的是存放 `Class` 对象的数组,里面的 `Class` 对象表示的是那个类实现的接口。\n", - "\n", - "另外,你还可以调用 `getSuperclass()` 方法来得到父类的 `Class` 对象,再用父类的 `Class` 对象调用该方法,重复多次,你就可以得到一个对象完整的类继承结构。\n", - "\n", - "`Class` 对象的 `newInstance()` 方法是实现“虚拟构造器”的一种途径,虚拟构造器可以让你在不知道一个类的确切类型的时候,创建这个类的对象。在前面的例子中,`up` 只是一个 `Class` 对象的引用,在编译期并不知道这个引用会指向哪个类的 `Class` 对象。当你创建新实例时,会得到一个 `Object` 引用,但是这个引用指向的是 `Toy` 对象。当然,由于得到的是 `Object` 引用,目前你只能给它发送 `Object` 对象能够接受的调用。而如果你想请求具体对象才有的调用,你就得先获取该对象更多的类型信息,并执行某种转型。另外,使用 `newInstance()` 来创建的类,必须带有无参数的构造器。在本章稍后部分,你将会看到如何通过 Java 的反射 API,用任意的构造器来动态地创建类的对象。\n", - "\n", - "### 类字面常量\n", - "\n", - "Java 还提供了另一种方法来生成类对象的引用:**类字面常量**。对上述程序来说,就像这样:`FancyToy.class;`。这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不必放在 `try` 语句块中)。并且它根除了对 `forName()` 方法的调用,所以效率更高。\n", - "\n", - "类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段 `TYPE`。`TYPE` 字段是一个引用,指向对应的基本数据类型的 `Class` 对象,如下所示:\n", - "\n", - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
...等价于...
boolean.classBoolean.TYPE
char.classCharacter.TYPE
byte.classByte.TYPE
short.classShort.TYPE
int.classInteger.TYPE
long.classLong.TYPE
float.classFloat.TYPE
double.classDouble.TYPE
void.classVoid.TYPE
\n", - "
\n", - "\n", - "我的建议是使用 `.class` 的形式,以保持与普通类的一致性。\n", - "\n", - "注意,有一点很有趣:当使用 `.class` 来创建对 `Class` 对象的引用时,不会自动地初始化该 `Class` 对象。为了使用类而做的准备工作实际包含三个步骤:\n", - "\n", - "1. **加载**,这是由类加载器执行的。该步骤将查找字节码(通常在 classpath 所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个 `Class` 对象。\n", - "\n", - "2. **链接**。在链接阶段将验证类中的字节码,为 `static` 字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。\n", - "\n", - "3. **初始化**。如果该类具有超类,则先初始化超类,执行 `static` 初始化器和 `static` 初始化块。\n", - "\n", - "直到第一次引用一个 `static` 方法(构造器隐式地是 `static`)或者非常量的 `static` 字段,才会进行类初始化。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/ClassInitialization.java\n", - "import java.util.*;\n", - "\n", - "class Initable {\n", - " static final int STATIC_FINAL = 47;\n", - " static final int STATIC_FINAL2 =\n", - " ClassInitialization.rand.nextInt(1000);\n", - " static {\n", - " System.out.println(\"Initializing Initable\");\n", - " }\n", - "}\n", - "\n", - "class Initable2 {\n", - " static int staticNonFinal = 147;\n", - " static {\n", - " System.out.println(\"Initializing Initable2\");\n", - " }\n", - "}\n", - "\n", - "class Initable3 {\n", - " static int staticNonFinal = 74;\n", - " static {\n", - " System.out.println(\"Initializing Initable3\");\n", - " }\n", - "}\n", - "\n", - "public class ClassInitialization {\n", - " public static Random rand = new Random(47);\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " Class initable = Initable.class;\n", - " System.out.println(\"After creating Initable ref\");\n", - " // Does not trigger initialization:\n", - " System.out.println(Initable.STATIC_FINAL);\n", - " // Does trigger initialization:\n", - " System.out.println(Initable.STATIC_FINAL2);\n", - " // Does trigger initialization:\n", - " System.out.println(Initable2.staticNonFinal);\n", - " Class initable3 = Class.forName(\"Initable3\");\n", - " System.out.println(\"After creating Initable3 ref\");\n", - " System.out.println(Initable3.staticNonFinal);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "After creating Initable ref\n", - "47\n", - "Initializing Initable\n", - "258\n", - "Initializing Initable2\n", - "147\n", - "Initializing Initable3\n", - "After creating Initable3 ref\n", - "74" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "初始化有效地实现了尽可能的“惰性”,从对 `initable` 引用的创建中可以看到,仅使用 `.class` 语法来获得对类对象的引用不会引发初始化。但与此相反,使用 `Class.forName()` 来产生 `Class` 引用会立即就进行初始化,如 `initable3`。\n", - "\n", - "如果一个 `static final` 值是“编译期常量”(如 `Initable.staticFinal`),那么这个值不需要对 `Initable` 类进行初始化就可以被读取。但是,如果只是将一个字段设置成为 `static` 和 `final`,还不足以确保这种行为。例如,对 `Initable.staticFinal2` 的访问将强制进行类的初始化,因为它不是一个编译期常量。\n", - "\n", - "如果一个 `static` 字段不是 `final` 的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间),就像在对 `Initable2.staticNonFinal` 的访问中所看到的那样。\n", - "\n", - "### 泛化的 `Class` 引用\n", - "\n", - "`Class` 引用总是指向某个 `Class` 对象,而 `Class` 对象可以用于产生类的实例,并且包含可作用于这些实例的所有方法代码。它还包含该类的 `static` 成员,因此 `Class` 引用表明了它所指向对象的确切类型,而该对象便是 `Class` 类的一个对象。\n", - "\n", - "\n", - "\n", - "但是,Java 设计者看准机会,将它的类型变得更具体了一些。Java 引入泛型语法之后,我们可以使用泛型对 `Class` 引用所指向的 `Class` 对象的类型进行限定。在下面的实例中,两种语法都是正确的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/GenericClassReferences.java\n", - "\n", - "public class GenericClassReferences {\n", - " public static void main(String[] args) {\n", - " Class intClass = int.class;\n", - " Class genericIntClass = int.class;\n", - " genericIntClass = Integer.class; // 同一个东西\n", - " intClass = double.class;\n", - " // genericIntClass = double.class; // 非法\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "普通的类引用不会产生警告信息。你可以看到,普通的类引用可以重新赋值指向任何其他的 `Class` 对象,但是使用泛型限定的类引用只能指向其声明的类型。通过使用泛型语法,我们可以让编译器强制执行额外的类型检查。\n", - "\n", - "那如果我们希望稍微放松一些限制,应该怎么办呢?乍一看,下面的操作好像是可以的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Class geenericNumberClass = int.class;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这看起来似乎是起作用的,因为 `Integer` 继承自 `Number`。但事实却是不行,因为 `Integer` 的 `Class` 对象并不是 `Number`的 `Class` 对象的子类(这看起来可能有点诡异,我们将在[泛型](./20-Generics)这一章详细讨论)。\n", - "\n", - "为了在使用 `Class` 引用时放松限制,我们使用了通配符,它是 Java 泛型中的一部分。通配符就是 `?`,表示“任何事物”。因此,我们可以在上例的普通 `Class` 引用中添加通配符,并产生相同的结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/WildcardClassReferences.java\n", - "\n", - "public class WildcardClassReferences {\n", - " public static void main(String[] args) {\n", - " Class intClass = int.class;\n", - " intClass = double.class;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 `Class` 比单纯使用 `Class` 要好,虽然它们是等价的,并且单纯使用 `Class` 不会产生编译器警告信息。使用 `Class` 的好处是它表示你并非是碰巧或者由于疏忽才使用了一个非具体的类引用,而是特意为之。\n", - "\n", - "为了创建一个限定指向某种类型或其子类的 `Class` 引用,我们需要将通配符与 `extends` 关键字配合使用,创建一个范围限定。这与仅仅声明 `Class` 不同,现在做如下声明:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/BoundedClassReferences.java\n", - "\n", - "public class BoundedClassReferences {\n", - " public static void main(String[] args) {\n", - " Class bounded = int.class;\n", - " bounded = double.class;\n", - " bounded = Number.class;\n", - " // Or anything else derived from Number.\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "向 `Class` 引用添加泛型语法的原因只是为了提供编译期类型检查,因此如果你操作有误,稍后就会发现这点。使用普通的 `Class` 引用你要确保自己不会犯错,因为一旦你犯了错误,就要等到运行时才能发现它,很不方便。\n", - "\n", - "下面的示例使用了泛型语法,它保存了一个类引用,稍后又用 `newInstance()` 方法产生类的对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/DynamicSupplier.java\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "class CountedInteger {\n", - " private static long counter;\n", - " private final long id = counter++;\n", - " @Override\n", - " public String toString() { return Long.toString(id); }\n", - "}\n", - "\n", - "public class DynamicSupplier implements Supplier {\n", - " private Class type;\n", - " public DynamicSupplier(Class type) {\n", - " this.type = type;\n", - " }\n", - " public T get() {\n", - " try {\n", - " return type.newInstance();\n", - " } catch(InstantiationException |\n", - " IllegalAccessException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " Stream.generate(\n", - " new DynamicSupplier<>(CountedInteger.class))\n", - " .skip(10)\n", - " .limit(5)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "10\n", - "11\n", - "12\n", - "13\n", - "14" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,这个类必须假设与它一起工作的任何类型都有一个无参构造器,否则运行时会抛出异常。编译期对该程序不会产生任何警告信息。\n", - "\n", - "当你将泛型语法用于 `Class` 对象时,`newInstance()` 将返回该对象的确切类型,而不仅仅只是在 `ToyTest.java` 中看到的基类 `Object`。然而,这在某种程度上有些受限:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/toys/GenericToyTest.java\n", - "// 测试 Class 类\n", - "// {java typeinfo.toys.GenericToyTest}\n", - "package typeinfo.toys;\n", - "\n", - "public class GenericToyTest {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " Class ftClass = FancyToy.class;\n", - " // Produces exact type:\n", - " FancyToy fancyToy = ftClass.newInstance();\n", - " Class up =\n", - " ftClass.getSuperclass();\n", - " // This won't compile:\n", - " // Class up2 = ftClass.getSuperclass();\n", - " // Only produces Object:\n", - " Object obj = up.newInstance();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是 `FancyToy` 的超类”,就像在表达式 `Class` 中所看到的那样。而不会接收 `Class` 这样的声明。这看上去显得有些怪,因为 `getSuperClass()` 方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是 `Toy.class`),而不仅仅只是\"某个类\"。不管怎样,正是由于这种含糊性,`up.newInstance` 的返回值不是精确类型,而只是 `Object`。\n", - "\n", - "### `cast()` 方法\n", - "\n", - "Java 中还有用于 `Class` 引用的转型语法,即 `cast()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/ClassCasts.java\n", - "\n", - "class Building {}\n", - "class House extends Building {}\n", - "\n", - "public class ClassCasts {\n", - " public static void main(String[] args) {\n", - " Building b = new House();\n", - " Class houseType = House.class;\n", - " House h = houseType.cast(b);\n", - " h = (House)b; // ... 或者这样做.\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`cast()` 方法接受参数对象,并将其类型转换为 `Class` 引用的类型。但是,如果观察上面的代码,你就会发现,与实现了相同功能的 `main` 方法中最后一行相比,这种转型好像做了很多额外的工作。\n", - "\n", - "`cast()` 在无法使用普通类型转换的情况下会显得非常有用,在你编写泛型代码(你将在[泛型](./20-Generics)这一章学习到)时,如果你保存了 `Class` 引用,并希望以后通过这个引用来执行转型,你就需要用到 `cast()`。但事实却是这种情况非常少见,我发现整个 Java 类库中,只有一处使用了 `cast()`(在 `com.sun.mirror.util.DeclarationFilter` 中)。\n", - "\n", - "Java 类库中另一个没有任何用处的特性就是 `Class.asSubclass()`,该方法允许你将一个 `Class` 对象转型为更加具体的类型。\n", - "\n", - "## 类型转换检测\n", - "\n", - "直到现在,我们已知的 RTTI 类型包括:\n", - "\n", - "1. 传统的类型转换,如 “`(Shape)`”,由 RTTI 确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 `ClassCastException` 异常。\n", - "\n", - "2. 代表对象类型的 `Class` 对象. 通过查询 `Class` 对象可以获取运行时所需的信息.\n", - "\n", - "在 C++ 中,经典的类型转换 “`(Shape)`” 并不使用 RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待. 而 Java 会进行类型检查,这种类型转换一般被称作“类型安全的向下转型”。之所以称作“向下转型”,是因为传统上类继承图是这么画的。将 `Circle` 转换为 `Shape` 是一次向上转型, 将 `Shape` 转换为 `Circle` 是一次向下转型。但是, 因为我们知道 `Circle` 肯定是一个 `Shape`,所以编译器允许我们自由地做向上转型的赋值操作,且不需要任何显式的转型操作。当你给编译器一个 `Shape` 的时候,编译器并不知道它到底是什么类型的 `Shape`——它可能是 `Shape`,也可能是 `Shape` 的子类型,例如 `Circle`、`Square`、`Triangle` 或某种其他的类型。在编译期,编译器只能知道它是 `Shape`。因此,你需要使用显式地进行类型转换,以告知编译器你想转换的特定类型,否则编译器就不允许你执行向下转型赋值。 (编译器将会检查向下转型是否合理,因此它不允许向下转型到实际不是待转型类型的子类类型上)。\n", - "\n", - "RTTI 在 Java 中还有第三种形式,那就是关键字 `instanceof`。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它,就像这个样子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "if(x instanceof Dog)\n", - " ((Dog)x).bark();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在将 `x` 的类型转换为 `Dog` 之前,`if` 语句会先检查 `x` 是否是 `Dog` 类型的对象。进行向下转型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 `instanceof` 是非常重要的,否则会得到一个 `ClassCastException` 异常。\n", - "\n", - "一般,可能想要查找某种类型(比如要找三角形,并填充为紫色),这时可以轻松地使用 `instanceof` 来度量所有对象。举个例子,假如你有一个类的继承体系,描述了 `Pet`(以及它们的主人,在后面一个例子中会用到这个特性)。在这个继承体系中的每个 `Individual` 都有一个 `id` 和一个可选的名字。尽管下面的类都继承自 `Individual`,但是 `Individual` 类复杂性较高,因此其代码将放在[附录:容器](./Appendix-Collection-Topics)中进行解释说明。正如你所看到的,此处并不需要去了解 `Individual` 的代码——你只需了解你可以创建其具名或不具名的对象,并且每个 `Individual` 都有一个 `id()` 方法,如果你没有为 `Individual` 提供名字,`toString()` 方法只产生类型名。\n", - "\n", - "下面是继承自 `Individual` 的类的继承体系:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Person.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Person extends Individual {\n", - " public Person(String name) { super(name); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Pet.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Pet extends Individual {\n", - " public Pet(String name) { super(name); }\n", - " public Pet() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Dog.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Dog extends Pet {\n", - " public Dog(String name) { super(name); }\n", - " public Dog() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Mutt.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Mutt extends Dog {\n", - " public Mutt(String name) { super(name); }\n", - " public Mutt() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Pug.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Pug extends Dog {\n", - " public Pug(String name) { super(name); }\n", - " public Pug() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Cat.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Cat extends Pet {\n", - " public Cat(String name) { super(name); }\n", - " public Cat() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/EgyptianMau.java\n", - "package typeinfo.pets;\n", - "\n", - "public class EgyptianMau extends Cat {\n", - " public EgyptianMau(String name) { super(name); }\n", - " public EgyptianMau() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Manx.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Manx extends Cat {\n", - " public Manx(String name) { super(name); }\n", - " public Manx() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Cymric.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Cymric extends Manx {\n", - " public Cymric(String name) { super(name); }\n", - " public Cymric() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Rodent.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Rodent extends Pet {\n", - " public Rodent(String name) { super(name); }\n", - " public Rodent() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Rat.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Rat extends Rodent {\n", - " public Rat(String name) { super(name); }\n", - " public Rat() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Mouse.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Mouse extends Rodent {\n", - " public Mouse(String name) { super(name); }\n", - " public Mouse() { super(); }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Hamster.java\n", - "package typeinfo.pets;\n", - "\n", - "public class Hamster extends Rodent {\n", - " public Hamster(String name) { super(name); }\n", - " public Hamster() { super(); }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们必须显式地为每一个子类编写无参构造器。因为我们有一个带一个参数的构造器,所以编译器不会自动地为我们加上无参构造器。\n", - "\n", - "接下来,我们需要一个类,它可以随机地创建不同类型的宠物,同时,它还可以创建宠物数组和持有宠物的 `List`。为了使这个类更加普遍适用,我们将其定义为抽象类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/PetCreator.java\n", - "// Creates random sequences of Pets\n", - "package typeinfo.pets;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "public abstract class PetCreator implements Supplier {\n", - " private Random rand = new Random(47);\n", - "\n", - " // The List of the different types of Pet to create:\n", - " public abstract List> types();\n", - "\n", - " public Pet get() { // Create one random Pet\n", - " int n = rand.nextInt(types().size());\n", - " try {\n", - " return types().get(n).newInstance();\n", - " } catch (InstantiationException |\n", - " IllegalAccessException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "抽象的 `types()` 方法需要子类来实现,以此来获取 `Class` 对象构成的 `List`(这是模板方法设计模式的一种变体)。注意,其中类的类型被定义为“任何从 `Pet` 导出的类型”,因此 `newInstance()` 不需要转型就可以产生 `Pet`。`get()` 随机的选取出一个 `Class` 对象,然后可以通过 `Class.newInstance()` 来生成该类的新实例。\n", - "\n", - "在调用 `newInstance()` 时,可能会出现两种异常。在紧跟 `try` 语句块后面的 `catch` 子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(`IllegalAccessException` 违反了 Java 安全机制,在本例中,表示默认构造器为 `private` 的情况)。\n", - "\n", - "当你创建 `PetCreator` 的子类时,你需要为 `get()` 方法提供 `Pet` 类型的 `List`。`types()` 方法会简单地返回一个静态 `List` 的引用。下面是使用 `forName()` 的一个具体实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/ForNameCreator.java\n", - "package typeinfo.pets;\n", - "import java.util.*;\n", - "\n", - "public class ForNameCreator extends PetCreator {\n", - " private static List> types =\n", - " new ArrayList<>();\n", - " // 需要随机生成的类型名:\n", - " private static String[] typeNames = {\n", - " \"typeinfo.pets.Mutt\",\n", - " \"typeinfo.pets.Pug\",\n", - " \"typeinfo.pets.EgyptianMau\",\n", - " \"typeinfo.pets.Manx\",\n", - " \"typeinfo.pets.Cymric\",\n", - " \"typeinfo.pets.Rat\",\n", - " \"typeinfo.pets.Mouse\",\n", - " \"typeinfo.pets.Hamster\"\n", - " };\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " private static void loader() {\n", - " try {\n", - " for (String name : typeNames)\n", - " types.add(\n", - " (Class) Class.forName(name));\n", - " } catch (ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " static {\n", - " loader();\n", - " }\n", - "\n", - " @Override\n", - " public List> types() {\n", - " return types;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`loader()` 方法使用 `Class.forName()` 创建了 `Class` 对象的 `List`。这可能会导致 `ClassNotFoundException` 异常,因为你传入的是一个 `String` 类型的参数,它不能在编译期间被确认是否合理。由于 `Pet` 相关的文件在 `typeinfo` 包里面,所以使用它们的时候需要填写完整的包名。\n", - "\n", - "为了使得 `List` 装入的是具体的 `Class` 对象,类型转换是必须的,它会产生一个编译时警告。`loader()` 方法是分开编写的,然后它被放入到一个静态代码块里,因为 `@SuppressWarning` 注解不能够直接放置在静态代码块之上。\n", - "\n", - "为了对 `Pet` 进行计数,我们需要一个能跟踪不同类型的 `Pet` 的工具。`Map` 是这个需求的首选,我们将 `Pet` 类型名作为键,将保存 `Pet` 数量的 `Integer` 作为值。通过这种方式,你就可以询问:“有多少个 `Hamster` 对象?”我们可以使用 `instanceof` 来对 `Pet` 进行计数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/PetCount.java\n", - "// 使用 instanceof\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class PetCount {\n", - " static class Counter extends HashMap {\n", - " public void count(String type) {\n", - " Integer quantity = get(type);\n", - " if (quantity == null)\n", - " put(type, 1);\n", - " else\n", - " put(type, quantity + 1);\n", - " }\n", - " }\n", - "\n", - " public static void\n", - " countPets(PetCreator creator) {\n", - " Counter counter = new Counter();\n", - " for (Pet pet : Pets.array(20)) {\n", - " // List each individual pet:\n", - " System.out.print(\n", - " pet.getClass().getSimpleName() + \" \");\n", - " if (pet instanceof Pet)\n", - " counter.count(\"Pet\");\n", - " if (pet instanceof Dog)\n", - " counter.count(\"Dog\");\n", - " if (pet instanceof Mutt)\n", - " counter.count(\"Mutt\");\n", - " if (pet instanceof Pug)\n", - " counter.count(\"Pug\");\n", - " if (pet instanceof Cat)\n", - " counter.count(\"Cat\");\n", - " if (pet instanceof EgyptianMau)\n", - " counter.count(\"EgyptianMau\");\n", - " if (pet instanceof Manx)\n", - " counter.count(\"Manx\");\n", - " if (pet instanceof Cymric)\n", - " counter.count(\"Cymric\");\n", - " if (pet instanceof Rodent)\n", - " counter.count(\"Rodent\");\n", - " if (pet instanceof Rat)\n", - " counter.count(\"Rat\");\n", - " if (pet instanceof Mouse)\n", - " counter.count(\"Mouse\");\n", - " if (pet instanceof Hamster)\n", - " counter.count(\"Hamster\");\n", - " }\n", - " // Show the counts:\n", - " System.out.println();\n", - " System.out.println(counter);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " countPets(new ForNameCreator());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", - "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", - "Pug Mouse Cymric\n", - "{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,\n", - "Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `countPets()` 中,一个简短的静态方法 `Pets.array()` 生产出了一个随机动物的集合。每个 `Pet` 都被 `instanceof` 检测到并计算了一遍。\n", - "\n", - "`instanceof` 有一个严格的限制:只可以将它与命名类型进行比较,而不能与 `Class` 对象作比较。在前面的例子中,你可能会觉得写出一大堆 `instanceof` 表达式很乏味,事实也是如此。但是,也没有办法让 `instanceof` 聪明起来,让它能够自动地创建一个 `Class` 对象的数组,然后将目标与这个数组中的对象逐一进行比较(稍后会看到一种替代方案)。其实这并不是那么大的限制,如果你在程序中写了大量的 `instanceof`,那就说明你的设计可能存在瑕疵。\n", - "\n", - "### 使用类字面量\n", - "\n", - "如果我们使用类字面量重新实现 `PetCreator` 类的话,其结果在很多方面都会更清晰:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/LiteralPetCreator.java\n", - "// 使用类字面量\n", - "// {java typeinfo.pets.LiteralPetCreator}\n", - "package typeinfo.pets;\n", - "import java.util.*;\n", - "\n", - "public class LiteralPetCreator extends PetCreator {\n", - " // try 代码块不再需要\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static final List> ALL_TYPES =\n", - " Collections.unmodifiableList(Arrays.asList(\n", - " Pet.class, Dog.class, Cat.class, Rodent.class,\n", - " Mutt.class, Pug.class, EgyptianMau.class,\n", - " Manx.class, Cymric.class, Rat.class,\n", - " Mouse.class, Hamster.class));\n", - " // 用于随机创建的类型:\n", - " private static final List> TYPES =\n", - " ALL_TYPES.subList(ALL_TYPES.indexOf(Mutt.class),\n", - " ALL_TYPES.size());\n", - "\n", - " @Override\n", - " public List> types() {\n", - " return TYPES;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(TYPES);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[class typeinfo.pets.Mutt, class typeinfo.pets.Pug,\n", - "class typeinfo.pets.EgyptianMau, class\n", - "typeinfo.pets.Manx, class typeinfo.pets.Cymric, class\n", - "typeinfo.pets.Rat, class typeinfo.pets.Mouse, class\n", - "typeinfo.pets.Hamster]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在即将到来的 `PetCount3.java` 示例中,我们用所有 `Pet` 类型预先加载一个 `Map`(不仅仅是随机生成的),因此 `ALL_TYPES` 类型的列表是必要的。`types` 列表是 `ALL_TYPES` 类型(使用 `List.subList()` 创建)的一部分,它包含精确的宠物类型,因此用于随机生成 `Pet`。\n", - "\n", - "这次,`types` 的创建没有被 `try` 块包围,因为它是在编译时计算的,因此不会引发任何异常,不像 `Class.forName()`。\n", - "\n", - "我们现在在 `typeinfo.pets` 库中有两个 `PetCreator` 的实现。为了提供第二个作为默认实现,我们可以创建一个使用 `LiteralPetCreator` 的 *外观模式*:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Pets.java\n", - "// Facade to produce a default PetCreator\n", - "package typeinfo.pets;\n", - "\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class Pets {\n", - " public static final PetCreator CREATOR = new LiteralPetCreator();\n", - "\n", - " public static Pet get() {\n", - " return CREATOR.get();\n", - " }\n", - "\n", - " public static Pet[] array(int size) {\n", - " Pet[] result = new Pet[size];\n", - " for (int i = 0; i < size; i++)\n", - " result[i] = CREATOR.get();\n", - " return result;\n", - " }\n", - "\n", - " public static List list(int size) {\n", - " List result = new ArrayList<>();\n", - " Collections.addAll(result, array(size));\n", - " return result;\n", - " }\n", - "\n", - " public static Stream stream() {\n", - " return Stream.generate(CREATOR);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这还提供了对 `get()`、`array()` 和 `list()` 的间接调用,以及生成 `Stream` 的新方法。\n", - "\n", - "因为 `PetCount.countPets()` 采用了 `PetCreator` 参数,所以我们可以很容易地测试 `LiteralPetCreator`(通过上面的外观模式):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/PetCount2.java\n", - "import typeinfo.pets.*;\n", - "\n", - "public class PetCount2 {\n", - " public static void main(String[] args) {\n", - " PetCount.countPets(Pets.CREATOR);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", - "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", - "Pug Mouse Cymric\n", - "{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9,\n", - "Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出与 `PetCount.java` 的输出相同。\n", - "\n", - "### 一个动态 `instanceof` 函数\n", - "\n", - "`Class.isInstance()` 方法提供了一种动态测试对象类型的方法。因此,所有这些繁琐的 `instanceof` 语句都可以从 `PetCount.java` 中删除:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/PetCount3.java\n", - "// 使用 isInstance() 方法\n", - "\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "import onjava.*;\n", - "import typeinfo.pets.*;\n", - "\n", - "public class PetCount3 {\n", - " static class Counter extends\n", - " LinkedHashMap, Integer> {\n", - " Counter() {\n", - " super(LiteralPetCreator.ALL_TYPES.stream()\n", - " .map(lpc -> Pair.make(lpc, 0))\n", - " .collect(\n", - " Collectors.toMap(Pair::key, Pair::value)));\n", - " }\n", - "\n", - " public void count(Pet pet) {\n", - " // Class.isInstance() 替换 instanceof:\n", - " entrySet().stream()\n", - " .filter(pair -> pair.getKey().isInstance(pet))\n", - " .forEach(pair ->\n", - " put(pair.getKey(), pair.getValue() + 1));\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " String result = entrySet().stream()\n", - " .map(pair -> String.format(\"%s=%s\",\n", - " pair.getKey().getSimpleName(),\n", - " pair.getValue()))\n", - " .collect(Collectors.joining(\", \"));\n", - " return \"{\" + result + \"}\";\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Counter petCount = new Counter();\n", - " Pets.stream()\n", - " .limit(20)\n", - " .peek(petCount::count)\n", - " .forEach(p -> System.out.print(\n", - " p.getClass().getSimpleName() + \" \"));\n", - " System.out.println(\"n\" + petCount);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", - "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", - "Pug Mouse Cymric\n", - "{Rat=2, Pug=3, Mutt=3, Mouse=2, Cat=9, Dog=6, Cymric=5,\n", - "EgyptianMau=2, Rodent=5, Hamster=1, Manx=7, Pet=20}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了计算所有不同类型的 `Pet`,`Counter Map` 预先加载了来自 `LiteralPetCreator.ALL_TYPES` 的类型。如果不预先加载 `Map`,将只计数随机生成的类型,而不是像 `Pet` 和 `Cat` 这样的基本类型。\n", - "\n", - "`isInstance()` 方法消除了对 `instanceof` 表达式的需要。此外,这意味着你可以通过更改 `LiteralPetCreator.types` 数组来添加新类型的 `Pet`;程序的其余部分不需要修改(就像使用 `instanceof` 表达式时那样)。\n", - "\n", - "`toString()` 方法被重载,以便更容易读取输出,该输出仍与打印 `Map` 时看到的典型输出匹配。\n", - "\n", - "### 递归计数\n", - "\n", - "`PetCount3.Counter` 中的 `Map` 预先加载了所有不同的 `Pet` 类。我们可以使用 `Class.isAssignableFrom()` 而不是预加载 `Map` ,并创建一个不限于计数 `Pet` 的通用工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/TypeCounter.java\n", - "// 计算类型家族的实例数\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class TypeCounter extends HashMap, Integer> {\n", - " private Class baseType;\n", - "\n", - " public TypeCounter(Class baseType) {\n", - " this.baseType = baseType;\n", - " }\n", - "\n", - " public void count(Object obj) {\n", - " Class type = obj.getClass();\n", - " if(!baseType.isAssignableFrom(type))\n", - " throw new RuntimeException(\n", - " obj + \" incorrect type: \" + type +\n", - " \", should be type or subtype of \" + baseType);\n", - " countClass(type);\n", - " }\n", - "\n", - " private void countClass(Class type) {\n", - " Integer quantity = get(type);\n", - " put(type, quantity == null ? 1 : quantity + 1);\n", - " Class superClass = type.getSuperclass();\n", - " if(superClass != null &&\n", - " baseType.isAssignableFrom(superClass))\n", - " countClass(superClass);\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " String result = entrySet().stream()\n", - " .map(pair -> String.format(\"%s=%s\",\n", - " pair.getKey().getSimpleName(),\n", - " pair.getValue()))\n", - " .collect(Collectors.joining(\", \"));\n", - " return \"{\" + result + \"}\";\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`count()` 方法获取其参数的 `Class`,并使用 `isAssignableFrom()` 进行运行时检查,以验证传递的对象实际上属于感兴趣的层次结构。`countClass()` 首先计算类的确切类型。然后,如果 `baseType` 可以从超类赋值,则在超类上递归调用 `countClass()`。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/PetCount4.java\n", - "import typeinfo.pets.*;\n", - "import onjava.*;\n", - "\n", - "public class PetCount4 {\n", - " public static void main(String[] args) {\n", - " TypeCounter counter = new TypeCounter(Pet.class);\n", - " Pets.stream()\n", - " .limit(20)\n", - " .peek(counter::count)\n", - " .forEach(p -> System.out.print(\n", - " p.getClass().getSimpleName() + \" \"));\n", - " System.out.println(\"n\" + counter);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat\n", - "EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse\n", - "Pug Mouse Cymric\n", - "{Dog=6, Manx=7, Cat=9, Rodent=5, Hamster=1, Rat=2,\n", - "Pug=3, Mutt=3, Cymric=5, EgyptianMau=2, Pet=20,\n", - "Mouse=2}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出表明两个基类型以及精确类型都被计数了。\n", - "\n", - "\n", - "\n", - "## 注册工厂\n", - "\n", - "从 `Pet` 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 `Pet` 时,必须记住将其添加到 `LiteralPetCreator.java` 的条目中。在一个定期添加更多类的系统中,这可能会成为问题。\n", - "\n", - "你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。\n", - "\n", - "基本上,你必须自己手工创建列表(除非你编写了一个工具来搜索和分析源代码,然后创建和编译列表)。所以你能做的最好的事情就是把列表集中放在一个明显的地方。层次结构的基类可能是最好的地方。\n", - "\n", - "我们在这里所做的另一个更改是使用*工厂方法*设计模式将对象的创建推迟到类本身。工厂方法可以以多态方式调用,并为你创建适当类型的对象。事实证明,`java.util.function.Supplier` 用 `T get()` 描述了原型工厂方法。协变返回类型允许 `get()` 为 `Supplier` 的每个子类实现返回不同的类型。\n", - "\n", - "在本例中,基类 `Part` 包含一个工厂对象的静态列表,列表成员类型为 `Supplier`。对于应该由 `get()` 方法生成的类型的工厂,通过将它们添加到 `prototypes` 列表向基类“注册”。奇怪的是,这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的*原型*:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/RegisteredFactories.java\n", - "// 注册工厂到基础类\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "class Part implements Supplier {\n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName();\n", - " }\n", - "\n", - " static List> prototypes =\n", - " Arrays.asList(\n", - " new FuelFilter(),\n", - " new AirFilter(),\n", - " new CabinAirFilter(),\n", - " new OilFilter(),\n", - " new FanBelt(),\n", - " new PowerSteeringBelt(),\n", - " new GeneratorBelt()\n", - " );\n", - "\n", - " private static Random rand = new Random(47);\n", - " public Part get() {\n", - " int n = rand.nextInt(prototypes.size());\n", - " return prototypes.get(n).get();\n", - " }\n", - "}\n", - "\n", - "class Filter extends Part {}\n", - "\n", - "class FuelFilter extends Filter {\n", - " @Override\n", - " public FuelFilter get() {\n", - " return new FuelFilter();\n", - " }\n", - "}\n", - "\n", - "class AirFilter extends Filter {\n", - " @Override\n", - " public AirFilter get() {\n", - " return new AirFilter();\n", - " }\n", - "}\n", - "\n", - "class CabinAirFilter extends Filter {\n", - " @Override\n", - " public CabinAirFilter get() {\n", - " return new CabinAirFilter();\n", - " }\n", - "}\n", - "\n", - "class OilFilter extends Filter {\n", - " @Override\n", - " public OilFilter get() {\n", - " return new OilFilter();\n", - " }\n", - "}\n", - "\n", - "class Belt extends Part {}\n", - "\n", - "class FanBelt extends Belt {\n", - " @Override\n", - " public FanBelt get() {\n", - " return new FanBelt();\n", - " }\n", - "}\n", - "\n", - "class GeneratorBelt extends Belt {\n", - " @Override\n", - " public GeneratorBelt get() {\n", - " return new GeneratorBelt();\n", - " }\n", - "}\n", - "\n", - "class PowerSteeringBelt extends Belt {\n", - " @Override\n", - " public PowerSteeringBelt get() {\n", - " return new PowerSteeringBelt();\n", - " }\n", - "}\n", - "\n", - "public class RegisteredFactories {\n", - " public static void main(String[] args) {\n", - " Stream.generate(new Part())\n", - " .limit(10)\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "GeneratorBelt\n", - "CabinAirFilter\n", - "GeneratorBelt\n", - "AirFilter\n", - "PowerSteeringBelt\n", - "CabinAirFilter\n", - "FuelFilter\n", - "PowerSteeringBelt\n", - "PowerSteeringBelt\n", - "FuelFilter" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "并非层次结构中的所有类都应实例化;这里的 `Filter` 和 `Belt` 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 `Part` 基类的行为)。\n", - "\n", - "因为 `Part implements Supplier`,`Part` 通过其 `get()` 方法供应其他 `Part`。如果为基类 `Part` 调用 `get()`(或者如果 `generate()` 调用 `get()`),它将创建随机特定的 `Part` 子类型,每个子类型最终都从 `Part` 继承,并重写相应的 `get()` 以生成它们中的一个。\n", - "\n", - "\n", - "\n", - "## 类的等价比较\n", - "\n", - "当你查询类型信息时,需要注意:instanceof 的形式(即 `instanceof` 或 `isInstance()` ,这两者产生的结果相同) 和 与 Class 对象直接比较 这两者间存在重要区别。下面的例子展示了这种区别:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/FamilyVsExactType.java\n", - "// instanceof 与 class 的差别\n", - "// {java typeinfo.FamilyVsExactType}\n", - "package typeinfo;\n", - "\n", - "class Base {}\n", - "class Derived extends Base {}\n", - "\n", - "public class FamilyVsExactType {\n", - " static void test(Object x) {\n", - " System.out.println(\n", - " \"Testing x of type \" + x.getClass());\n", - " System.out.println(\n", - " \"x instanceof Base \" + (x instanceof Base));\n", - " System.out.println(\n", - " \"x instanceof Derived \" + (x instanceof Derived));\n", - " System.out.println(\n", - " \"Base.isInstance(x) \" + Base.class.isInstance(x));\n", - " System.out.println(\n", - " \"Derived.isInstance(x) \" +\n", - " Derived.class.isInstance(x));\n", - " System.out.println(\n", - " \"x.getClass() == Base.class \" +\n", - " (x.getClass() == Base.class));\n", - " System.out.println(\n", - " \"x.getClass() == Derived.class \" +\n", - " (x.getClass() == Derived.class));\n", - " System.out.println(\n", - " \"x.getClass().equals(Base.class)) \"+\n", - " (x.getClass().equals(Base.class)));\n", - " System.out.println(\n", - " \"x.getClass().equals(Derived.class)) \" +\n", - " (x.getClass().equals(Derived.class)));\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " test(new Base());\n", - " test(new Derived());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Testing x of type class typeinfo.Base\n", - "x instanceof Base true\n", - "x instanceof Derived false\n", - "Base.isInstance(x) true\n", - "Derived.isInstance(x) false\n", - "x.getClass() == Base.class true\n", - "x.getClass() == Derived.class false\n", - "x.getClass().equals(Base.class)) true\n", - "x.getClass().equals(Derived.class)) false\n", - "Testing x of type class typeinfo.Derived\n", - "x instanceof Base true\n", - "x instanceof Derived true\n", - "Base.isInstance(x) true\n", - "Derived.isInstance(x) true\n", - "x.getClass() == Base.class false\n", - "x.getClass() == Derived.class true\n", - "x.getClass().equals(Base.class)) false\n", - "x.getClass().equals(Derived.class)) true" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`test()` 方法使用两种形式的 `instanceof` 对其参数执行类型检查。然后,它获取 `Class` 引用,并使用 `==` 和 `equals()` 测试 `Class` 对象的相等性。令人放心的是,`instanceof` 和 `isInstance()` 产生的结果相同, `equals()` 和 `==` 产生的结果也相同。但测试本身得出了不同的结论。与类型的概念一致,`instanceof` 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 `==` 比较实际的`Class` 对象,则与继承无关 —— 它要么是确切的类型,要么不是。\n", - "\n", - "\n", - "## 反射:运行时类信息\n", - "\n", - "如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。\n", - "\n", - "起初,这看起来并没有那么大的限制,但是假设你引用了一个不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类?\n", - "\n", - "在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器*集成开发环境*中使用*快速应用程序开发*(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在编程时设置这些组件的一些值来配置这些组件。这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理*图形用户界面*(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆写这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。\n", - "\n", - "在运行时发现类信息的另一个令人信服的动机是提供跨网络在远程平台上创建和执行对象的能力。这称为*远程方法调用*(RMI),它使 Java 程序的对象分布在许多机器上。这种分布有多种原因。如果你想加速一个计算密集型的任务,你可以把它分解成小块放到空闲的机器上。或者你可以将处理特定类型任务的代码(例如,多层次客户机/服务器体系结构中的“业务规则”)放在特定的机器上,这样机器就成为描述这些操作的公共存储库,并且可以很容易地更改它以影响系统中的每个人。分布式计算还支持专门的硬件,这些硬件可能擅长于某个特定的任务——例如矩阵转换——但对于通用编程来说不合适或过于昂贵。\n", - "\n", - "类 `Class` 支持*反射*的概念, `java.lang.reflect` 库中包含类 `Field`、`Method` 和 `Constructor`(每一个都实现了 `Member` 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。然后,可以使用 `Constructor` 创建新对象,`get()` 和 `set()` 方法读取和修改与 `Field` 对象关联的字段,`invoke()` 方法调用与 `Method` 对象关联的方法。此外,还可以调用便利方法 `getFields()`、`getMethods()`、`getConstructors()` 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 `Class` 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。\n", - "\n", - "重要的是要意识到反射没有什么魔力。当你使用反射与未知类型的对象交互时,JVM 将查看该对象,并看到它属于特定的类(就像普通的 RTTI)。在对其执行任何操作之前,必须加载 `Class` 对象。因此,该特定类型的 `.class` 文件必须在本地计算机上或通过网络对 JVM 仍然可用。因此,RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 `.class` 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,`.class` 文件在编译时不可用;它由运行时环境打开并检查。\n", - "\n", - "### 类方法提取器\n", - "\n", - "通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化(参见[附录:对象序列化](https://lingcoder.github.io/OnJava8/#/book/Appendix-Object-Serialization))。但是,有时动态提取有关类的信息很有用。\n", - "\n", - "考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示*在该类定义中*定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时[^1]。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/ShowMethods.java\n", - "// 使用反射展示一个类的所有方法,甚至包括定义在基类中方法\n", - "// {java ShowMethods ShowMethods}\n", - "import java.lang.reflect.*;\n", - "import java.util.regex.*;\n", - "\n", - "public class ShowMethods {\n", - " private static String usage =\n", - " \"usage:\\n\" +\n", - " \"ShowMethods qualified.class.name\\n\" +\n", - " \"To show all methods in class or:\\n\" +\n", - " \"ShowMethods qualified.class.name word\\n\" +\n", - " \"To search for methods involving 'word'\";\n", - " private static Pattern p = Pattern.compile(\"\\\\w+\\\\.\");\n", - "\n", - " public static void main(String[] args) {\n", - " if (args.length < 1) {\n", - " System.out.println(usage);\n", - " System.exit(0);\n", - " }\n", - " int lines = 0;\n", - " try {\n", - " Class c = Class.forName(args[0]);\n", - " Method[] methods = c.getMethods();\n", - " Constructor[] ctors = c.getConstructors();\n", - " if (args.length == 1) {\n", - " for (Method method : methods)\n", - " System.out.println(\n", - " p.matcher(\n", - " method.toString()).replaceAll(\"\"));\n", - " for (Constructor ctor : ctors)\n", - " System.out.println(\n", - " p.matcher(ctor.toString()).replaceAll(\"\"));\n", - " lines = methods.length + ctors.length;\n", - " } else {\n", - " for (Method method : methods)\n", - " if (method.toString().contains(args[1])) {\n", - " System.out.println(p.matcher(\n", - " method.toString()).replaceAll(\"\"));\n", - " lines++;\n", - " }\n", - " for (Constructor ctor : ctors)\n", - " if (ctor.toString().contains(args[1])) {\n", - " System.out.println(p.matcher(\n", - " ctor.toString()).replaceAll(\"\"));\n", - " lines++;\n", - " }\n", - " }\n", - " } catch (ClassNotFoundException e) {\n", - " System.out.println(\"No such class: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "public static void main(String[])\n", - "public final void wait() throws InterruptedException\n", - "public final void wait(long,int) throws\n", - "InterruptedException\n", - "public final native void wait(long) throws\n", - "InterruptedException\n", - "public boolean equals(Object)\n", - "public String toString()\n", - "public native int hashCode()\n", - "public final native Class getClass()\n", - "public final native void notify()\n", - "public final native void notifyAll()\n", - "public ShowMethods()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Class` 方法 `getmethods()` 和 `getconstructors()` 分别返回 `Method` 数组和 `Constructor` 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 `toString()`,生成带有整个方法签名的 `String`。代码的其余部分提取命令行信息,确定特定签名是否与目标 `String`(使用 `indexOf()`)匹配,并使用正则表达式(在 [Strings](#ch021.xhtml#strings) 一章中介绍)删除名称限定符。\n", - "\n", - "编译时无法知道 `Class.forName()` 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用(本书后面有这样的例子)。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。\n", - "\n", - "上面的输出来自命令行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "java ShowMethods ShowMethods" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出包含一个 `public` 无参数构造函数,即使未定义构造函数。你看到的构造函数是由编译器自动合成的。如果将 `ShowMethods` 设置为非 `public` 类(即只有包级访问权),则合成的无参数构造函数将不再显示在输出中。自动为合成的无参数构造函数授予与类相同的访问权。\n", - "\n", - "尝试运行 `java ShowMethods java.lang.String`,并附加一个 `char`、`int`、`String` 等参数。\n", - "\n", - "编程时,当你不记得某个类是否有特定的方法,并且不想在 JDK 文档中搜索索引或类层次结构时,或者如果你不知道该类是否可以对 `Color` 对象执行任何操作时,该工具能节省不少时间。\n", - "\n", - "\n", - "\n", - "## 动态代理\n", - "\n", - "*代理*是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作---这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/SimpleProxyDemo.java\n", - "\n", - "interface Interface {\n", - " void doSomething();\n", - "\n", - " void somethingElse(String arg);\n", - "}\n", - "\n", - "class RealObject implements Interface {\n", - " @Override\n", - " public void doSomething() {\n", - " System.out.println(\"doSomething\");\n", - " }\n", - "\n", - " @Override\n", - " public void somethingElse(String arg) {\n", - " System.out.println(\"somethingElse \" + arg);\n", - " }\n", - "}\n", - "\n", - "class SimpleProxy implements Interface {\n", - " private Interface proxied;\n", - "\n", - " SimpleProxy(Interface proxied) {\n", - " this.proxied = proxied;\n", - " }\n", - "\n", - " @Override\n", - " public void doSomething() {\n", - " System.out.println(\"SimpleProxy doSomething\");\n", - " proxied.doSomething();\n", - " }\n", - "\n", - " @Override\n", - " public void somethingElse(String arg) {\n", - " System.out.println(\n", - " \"SimpleProxy somethingElse \" + arg);\n", - " proxied.somethingElse(arg);\n", - " }\n", - "}\n", - "\n", - "class SimpleProxyDemo {\n", - " public static void consumer(Interface iface) {\n", - " iface.doSomething();\n", - " iface.somethingElse(\"bonobo\");\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " consumer(new RealObject());\n", - " consumer(new SimpleProxy(new RealObject()));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "doSomething\n", - "somethingElse bonobo\n", - "SimpleProxy doSomething\n", - "doSomething\n", - "SimpleProxy somethingElse bonobo\n", - "somethingElse bonobo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 `consumer()` 接受 `Interface`,所以它不知道获得的是 `RealObject` 还是 `SimpleProxy`,因为两者都实现了 `Interface`。\n", - "但是,在客户端和 `RealObject` 之间插入的 `SimpleProxy` 执行操作,然后在 `RealObject` 上调用相同的方法。\n", - "\n", - "当你希望将额外的操作与“真实对象”做分离时,代理可能会有所帮助,尤其是当你想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更---所以你必须改变一些东西以证明模式的合理性)。例如,如果你想跟踪对 `RealObject` 中方法的调用,或衡量此类调用的开销,该怎么办?你不想这部分代码耦合到你的程序中,而代理能使你可以很轻松地添加或删除它。\n", - "\n", - "Java 的*动态代理*更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个*调用处理程序*,该处理程序负责发现调用的内容并决定如何处理。这是 `SimpleProxyDemo.java` 使用动态代理重写的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/SimpleDynamicProxy.java\n", - "\n", - "import java.lang.reflect.*;\n", - "\n", - "class DynamicProxyHandler implements InvocationHandler {\n", - " private Object proxied;\n", - "\n", - " DynamicProxyHandler(Object proxied) {\n", - " this.proxied = proxied;\n", - " }\n", - "\n", - " @Override\n", - " public Object\n", - " invoke(Object proxy, Method method, Object[] args)\n", - " throws Throwable {\n", - " System.out.println(\n", - " \"**** proxy: \" + proxy.getClass() +\n", - " \", method: \" + method + \", args: \" + args);\n", - " if (args != null)\n", - " for (Object arg : args)\n", - " System.out.println(\" \" + arg);\n", - " return method.invoke(proxied, args);\n", - " }\n", - "}\n", - "\n", - "class SimpleDynamicProxy {\n", - " public static void consumer(Interface iface) {\n", - " iface.doSomething();\n", - " iface.somethingElse(\"bonobo\");\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " RealObject real = new RealObject();\n", - " consumer(real);\n", - " // Insert a proxy and call again:\n", - " Interface proxy = (Interface) Proxy.newProxyInstance(\n", - " Interface.class.getClassLoader(),\n", - " new Class[]{Interface.class},\n", - " new DynamicProxyHandler(real));\n", - " consumer(proxy);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "doSomething\n", - "somethingElse bonobo\n", - "**** proxy: class $Proxy0, method: public abstract void\n", - "Interface.doSomething(), args: null\n", - "doSomething\n", - "**** proxy: class $Proxy0, method: public abstract void\n", - "Interface.somethingElse(java.lang.String), args:\n", - "[Ljava.lang.Object;@6bc7c054\n", - " bonobo\n", - "somethingElse bonobo" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以通过调用静态方法 `Proxy.newProxyInstance()` 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 `InvocationHandler` 的一个实现。动态代理会将所有调用重定向到调用处理程序,因此通常为调用处理程序的构造函数提供对“真实”对象的引用,以便一旦执行中介任务便可以转发请求。\n", - "\n", - "`invoke()` 方法被传递给代理对象,以防万一你必须区分请求的来源---但是在很多情况下都无需关心。但是,在 `invoke()` 内的代理上调用方法时要小心,因为接口的调用是通过代理重定向的。\n", - "\n", - "通常执行代理操作,然后使用 `Method.invoke()` 将请求转发给被代理对象,并携带必要的参数。这在一开始看起来是有限制的,好像你只能执行一般的操作。但是,可以过滤某些方法调用,同时传递其他方法调用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/SelectingMethods.java\n", - "// Looking for particular methods in a dynamic proxy\n", - "\n", - "import java.lang.reflect.*;\n", - "\n", - "class MethodSelector implements InvocationHandler {\n", - " private Object proxied;\n", - "\n", - " MethodSelector(Object proxied) {\n", - " this.proxied = proxied;\n", - " }\n", - "\n", - " @Override\n", - " public Object\n", - " invoke(Object proxy, Method method, Object[] args)\n", - " throws Throwable {\n", - " if (method.getName().equals(\"interesting\"))\n", - " System.out.println(\n", - " \"Proxy detected the interesting method\");\n", - " return method.invoke(proxied, args);\n", - " }\n", - "}\n", - "\n", - "interface SomeMethods {\n", - " void boring1();\n", - "\n", - " void boring2();\n", - "\n", - " void interesting(String arg);\n", - "\n", - " void boring3();\n", - "}\n", - "\n", - "class Implementation implements SomeMethods {\n", - " @Override\n", - " public void boring1() {\n", - " System.out.println(\"boring1\");\n", - " }\n", - "\n", - " @Override\n", - " public void boring2() {\n", - " System.out.println(\"boring2\");\n", - " }\n", - "\n", - " @Override\n", - " public void interesting(String arg) {\n", - " System.out.println(\"interesting \" + arg);\n", - " }\n", - "\n", - " @Override\n", - " public void boring3() {\n", - " System.out.println(\"boring3\");\n", - " }\n", - "}\n", - "\n", - "class SelectingMethods {\n", - " public static void main(String[] args) {\n", - " SomeMethods proxy =\n", - " (SomeMethods) Proxy.newProxyInstance(\n", - " SomeMethods.class.getClassLoader(),\n", - " new Class[]{ SomeMethods.class },\n", - " new MethodSelector(new Implementation()));\n", - " proxy.boring1();\n", - " proxy.boring2();\n", - " proxy.interesting(\"bonobo\");\n", - " proxy.boring3();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "boring1\n", - "boring2\n", - "Proxy detected the interesting method\n", - "interesting bonobo\n", - "boring3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个示例里,我们只是在寻找方法名,但是你也可以寻找方法签名的其他方面,甚至可以搜索特定的参数值。\n", - "\n", - "动态代理不是你每天都会使用的工具,但是它可以很好地解决某些类型的问题。你可以在 Erich Gamma 等人的*设计模式*中了解有关*代理*和其他设计模式的更多信息。 (Addison-Wesley,1995年),以及[设计模式](./25-Patterns.md)一章。\n", - "\n", - "\n", - "\n", - "## Optional类\n", - "\n", - "如果你使用内置的 `null` 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 `null`,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 `null` 没什么自己的行为,只会在你想用它执行任何操作的时候产生 `NullPointException`。`java.util.Optional`(首次出现是在[函数式编程](docs/book/13-Functional-Programming.md)这章)为 `null` 值提供了一个轻量级代理,`Optional` 对象可以防止你的代码直接抛出 `NullPointException`。\n", - "\n", - "虽然 `Optional` 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。\n", - "\n", - "实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理地假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Person.java\n", - "// Using Optional with regular classes\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.*;\n", - "\n", - "class Person {\n", - " public final Optional first;\n", - " public final Optional last;\n", - " public final Optional address;\n", - " // etc.\n", - " public final Boolean empty;\n", - "\n", - " Person(String first, String last, String address) {\n", - " this.first = Optional.ofNullable(first);\n", - " this.last = Optional.ofNullable(last);\n", - " this.address = Optional.ofNullable(address);\n", - " empty = !this.first.isPresent()\n", - " && !this.last.isPresent()\n", - " && !this.address.isPresent();\n", - " }\n", - "\n", - " Person(String first, String last) {\n", - " this(first, last, null);\n", - " }\n", - "\n", - " Person(String last) {\n", - " this(null, last, null);\n", - " }\n", - "\n", - " Person() {\n", - " this(null, null, null);\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " if (empty)\n", - " return \"\";\n", - " return (first.orElse(\"\") +\n", - " \" \" + last.orElse(\"\") +\n", - " \" \" + address.orElse(\"\")).trim();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(new Person());\n", - " System.out.println(new Person(\"Smith\"));\n", - " System.out.println(new Person(\"Bob\", \"Smith\"));\n", - " System.out.println(new Person(\"Bob\", \"Smith\",\n", - " \"11 Degree Lane, Frostbite Falls, MN\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "Smith\n", - "Bob Smith\n", - "Bob Smith 11 Degree Lane, Frostbite Falls, MN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有字段都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的字段重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 字段在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。\n", - "\n", - "如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 字段,这样就不会意外触发 `NullPointException` 了。\n", - "\n", - "现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 `Person Optional` 对象放在每个 `Position` 上:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Position.java\n", - "\n", - "import java.util.*;\n", - "\n", - "class EmptyTitleException extends RuntimeException {\n", - "}\n", - "\n", - "class Position {\n", - " private String title;\n", - " private Person person;\n", - "\n", - " Position(String jobTitle, Person employee) {\n", - " setTitle(jobTitle);\n", - " setPerson(employee);\n", - " }\n", - "\n", - " Position(String jobTitle) {\n", - " this(jobTitle, null);\n", - " }\n", - "\n", - " public String getTitle() {\n", - " return title;\n", - " }\n", - "\n", - " public void setTitle(String newTitle) {\n", - " // Throws EmptyTitleException if newTitle is null:\n", - " title = Optional.ofNullable(newTitle)\n", - " .orElseThrow(EmptyTitleException::new);\n", - " }\n", - "\n", - " public Person getPerson() {\n", - " return person;\n", - " }\n", - "\n", - " public void setPerson(Person newPerson) {\n", - " // Uses empty Person if newPerson is null:\n", - " person = Optional.ofNullable(newPerson)\n", - " .orElse(new Person());\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"Position: \" + title +\n", - " \", Employee: \" + person;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(new Position(\"CEO\"));\n", - " System.out.println(new Position(\"Programmer\",\n", - " new Person(\"Arthur\", \"Fonzarelli\")));\n", - " try {\n", - " new Position(null);\n", - " } catch (Exception e) {\n", - " System.out.println(\"caught \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Position: CEO, Employee: \n", - "Position: Programmer, Employee: Arthur Fonzarelli\n", - "caught EmptyTitleException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通字段,不受 `Optional` 的保护。但是,修改这些字段的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对字段进行了严格的限制。\n", - "\n", - "同时,我们想保证 `title` 字段永远不会变成 `null` 值。为此,我们可以自己在 `setTitle()` 方法里边检查参数 `newTitle` 的值。但其实还有更好的做法,函数式编程一大优势就是可以让我们重用经过验证的功能(即便是个很小的功能),以减少自己手动编写代码可能产生的一些小错误。所以在这里,我们用 `ofNullable()` 把 `newTitle` 转换一个 `Optional`(如果传入的值为 `null`,`ofNullable()` 返回的将是 `Optional.empty()`)。紧接着我们调用了 `orElseThrow()` 方法,所以如果 `newTitle` 的值是 `null`,你将会得到一个异常。这里我们并没有把 `title` 保存成 `Optional`,但通过应用 `Optional` 的功能,我们仍然如愿以偿地对这个字段施加了约束。\n", - "\n", - "`EmptyTitleException` 是一个 `RuntimeException`,因为它意味着程序存在错误。在这个方案里边,你仍然可能会得到一个异常。但不同的是,在错误产生的那一刻(向 `setTitle()` 传 `null` 值时)就会抛出异常,而不是发生在其它时刻,需要你通过调试才能发现问题所在。另外,使用 `EmptyTitleException` 还有助于定位 BUG。\n", - "\n", - "`Person` 字段的限制又不太一样:如果你把它的值设为 `null`,程序会自动把将它赋值成一个空的 `Person` 对象。先前我们也用过类似的方法把字段转换成 `Optional`,但这里我们是在返回结果的时候使用 `orElse(new Person())` 插入一个空的 `Person` 对象替代了 `null`。\n", - "\n", - "在 `Position` 里边,我们没有创建一个表示“空”的标志位或者方法,因为 `person` 字段的 `Person` 对象为空,就表示这个 `Position` 是个空缺位置。之后,你可能会发现你必须添加一个显式的表示“空位”的方法,但是正如 YAGNI[^2] (You Aren't Going to Need It,你永远不需要它)所言,在初稿时“实现尽最大可能的简单”,直到程序在某些方面要求你为其添加一些额外的特性,而不是假设这是必要的。\n", - "\n", - "请注意,虽然你清楚你使用了 `Optional`,可以免受 `NullPointerExceptions` 的困扰,但是 `Staff` 类却对此毫不知情。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Staff.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class Staff extends ArrayList {\n", - " public void add(String title, Person person) {\n", - " add(new Position(title, person));\n", - " }\n", - "\n", - " public void add(String... titles) {\n", - " for (String title : titles)\n", - " add(new Position(title));\n", - " }\n", - "\n", - " public Staff(String... titles) {\n", - " add(titles);\n", - " }\n", - "\n", - " public Boolean positionAvailable(String title) {\n", - " for (Position position : this)\n", - " if (position.getTitle().equals(title) &&\n", - " position.getPerson().empty)\n", - " return true;\n", - " return false;\n", - " }\n", - "\n", - " public void fillPosition(String title, Person hire) {\n", - " for (Position position : this)\n", - " if (position.getTitle().equals(title) &&\n", - " position.getPerson().empty) {\n", - " position.setPerson(hire);\n", - " return;\n", - " }\n", - " throw new RuntimeException(\n", - " \"Position \" + title + \" not available\");\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Staff staff = new Staff(\"President\", \"CTO\",\n", - " \"Marketing Manager\", \"Product Manager\",\n", - " \"Project Lead\", \"Software Engineer\",\n", - " \"Software Engineer\", \"Software Engineer\",\n", - " \"Software Engineer\", \"Test Engineer\",\n", - " \"Technical Writer\");\n", - " staff.fillPosition(\"President\",\n", - " new Person(\"Me\", \"Last\", \"The Top, Lonely At\"));\n", - " staff.fillPosition(\"Project Lead\",\n", - " new Person(\"Janet\", \"Planner\", \"The Burbs\"));\n", - " if (staff.positionAvailable(\"Software Engineer\"))\n", - " staff.fillPosition(\"Software Engineer\",\n", - " new Person(\n", - " \"Bob\", \"Coder\", \"Bright Light City\"));\n", - " System.out.println(staff);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[Position: President, Employee: Me Last The Top, Lonely\n", - "At, Position: CTO, Employee: , Position:\n", - "Marketing Manager, Employee: , Position: Product\n", - "Manager, Employee: , Position: Project Lead,\n", - "Employee: Janet Planner The Burbs, Position: Software\n", - "Engineer, Employee: Bob Coder Bright Light City,\n", - "Position: Software Engineer, Employee: ,\n", - "Position: Software Engineer, Employee: ,\n", - "Position: Software Engineer, Employee: ,\n", - "Position: Test Engineer, Employee: , Position:\n", - "Technical Writer, Employee: ]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,在有些地方你可能还是要测试引用是不是 `Optional`,这跟检查是否为 `null` 没什么不同。但是在其它地方(例如本例中的 `toString()` 转换),你就不必执行额外的测试了,而可以直接假设所有对象都是有效的。\n", - "\n", - "### 标记接口\n", - "\n", - "有时候使用一个**标记接口**来表示空值会更方便。标记接口里边什么都没有,你只要把它的名字当做标签来用就可以。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Null.java\n", - "package onjava;\n", - "public interface Null {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你用接口取代具体类,那么就可以使用 `DynamicProxy` 来自动地创建 `Null` 对象。假设我们有一个 `Robot` 接口,它定义了一个名字、一个模型和一个描述 `Robot` 行为能力的 `List`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Robot.java\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.*;\n", - "\n", - "public interface Robot {\n", - " String name();\n", - "\n", - " String model();\n", - "\n", - " List operations();\n", - "\n", - " static void test(Robot r) {\n", - " if (r instanceof Null)\n", - " System.out.println(\"[Null Robot]\");\n", - " System.out.println(\"Robot name: \" + r.name());\n", - " System.out.println(\"Robot model: \" + r.model());\n", - " for (Operation operation : r.operations()) {\n", - " System.out.println(operation.description.get());\n", - " operation.command.run();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以通过调用 `operations()` 来访问 `Robot` 的服务。`Robot` 里边还有一个 `static` 方法来执行测试。\n", - "\n", - "`Operation` 包含一个描述和一个命令(这用到了**命令模式**)。它们被定义成函数式接口的引用,所以可以把 lambda 表达式或者方法的引用传给 `Operation` 的构造器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/Operation.java\n", - "\n", - "import java.util.function.*;\n", - "\n", - "public class Operation {\n", - " public final Supplier description;\n", - " public final Runnable command;\n", - "\n", - " public Operation(Supplier descr, Runnable cmd) {\n", - " description = descr;\n", - " command = cmd;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在我们可以创建一个扫雪 `Robot`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/SnowRemovalRobot.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class SnowRemovalRobot implements Robot {\n", - " private String name;\n", - "\n", - " public SnowRemovalRobot(String name) {\n", - " this.name = name;\n", - " }\n", - "\n", - " @Override\n", - " public String name() {\n", - " return name;\n", - " }\n", - "\n", - " @Override\n", - " public String model() {\n", - " return \"SnowBot Series 11\";\n", - " }\n", - "\n", - " private List ops = Arrays.asList(\n", - " new Operation(\n", - " () -> name + \" can shovel snow\",\n", - " () -> System.out.println(\n", - " name + \" shoveling snow\")),\n", - " new Operation(\n", - " () -> name + \" can chip ice\",\n", - " () -> System.out.println(name + \" chipping ice\")),\n", - " new Operation(\n", - " () -> name + \" can clear the roof\",\n", - " () -> System.out.println(\n", - " name + \" clearing roof\")));\n", - "\n", - " public List operations() {\n", - " return ops;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Robot.test(new SnowRemovalRobot(\"Slusher\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Robot name: Slusher\n", - "Robot model: SnowBot Series 11\n", - "Slusher can shovel snow\n", - "Slusher shoveling snow\n", - "Slusher can chip ice\n", - "Slusher chipping ice\n", - "Slusher can clear the roof\n", - "Slusher clearing roof" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "假设存在许多不同类型的 `Robot`,我们想让每种 `Robot` 都创建一个 `Null` 对象来执行一些特殊的操作——在本例中,即提供 `Null` 对象所代表 `Robot` 的确切类型信息。这些信息是通过动态代理捕获的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/NullRobot.java\n", - "// Using a dynamic proxy to create an Optional\n", - "\n", - "import java.lang.reflect.*;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "import onjava.*;\n", - "\n", - "class NullRobotProxyHandler\n", - " implements InvocationHandler {\n", - " private String nullName;\n", - " private Robot proxied = new NRobot();\n", - "\n", - " NullRobotProxyHandler(Class type) {\n", - " nullName = type.getSimpleName() + \" NullRobot\";\n", - " }\n", - "\n", - " private class NRobot implements Null, Robot {\n", - " @Override\n", - " public String name() {\n", - " return nullName;\n", - " }\n", - "\n", - " @Override\n", - " public String model() {\n", - " return nullName;\n", - " }\n", - "\n", - " @Override\n", - " public List operations() {\n", - " return Collections.emptyList();\n", - " }\n", - " }\n", - "\n", - " @Override\n", - " public Object\n", - " invoke(Object proxy, Method method, Object[] args)\n", - " throws Throwable {\n", - " return method.invoke(proxied, args);\n", - " }\n", - "}\n", - "\n", - "public class NullRobot {\n", - " public static Robot\n", - " newNullRobot(Class type) {\n", - " return (Robot) Proxy.newProxyInstance(\n", - " NullRobot.class.getClassLoader(),\n", - " new Class[] { Null.class, Robot.class },\n", - " new NullRobotProxyHandler(type));\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Stream.of(\n", - " new SnowRemovalRobot(\"SnowBee\"),\n", - " newNullRobot(SnowRemovalRobot.class)\n", - " ).forEach(Robot::test);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Robot name: SnowBee\n", - "Robot model: SnowBot Series 11\n", - "SnowBee can shovel snow\n", - "SnowBee shoveling snow\n", - "SnowBee can chip ice\n", - "SnowBee chipping ice\n", - "SnowBee can clear the roof\n", - "SnowBee clearing roof\n", - "[Null Robot]\n", - "Robot name: SnowRemovalRobot NullRobot\n", - "Robot model: SnowRemovalRobot NullRobot" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "无论何时,如果你需要一个空 `Robot` 对象,只需要调用 `newNullRobot()`,并传递需要代理的 `Robot` 的类型。这个代理满足了 `Robot` 和 `Null` 接口的需要,并提供了它所代理的类型的确切名字。\n", - "\n", - "### Mock 对象和桩\n", - "\n", - "**Mock 对象**和 **桩(Stub)**在逻辑上都是 `Optional` 的变体。他们都是最终程序中所使用的“实际”对象的代理。不过,Mock 对象和桩都是假扮成那些可以传递实际信息的实际对象,而不是像 `Optional` 那样把包含潜在 `null` 值的对象隐藏。\n", - "\n", - "Mock 对象和桩之间的的差别在于程度不同。Mock 对象往往是轻量级的,且用于自测试。通常,为了处理各种不同的测试场景,我们会创建出很多 Mock 对象。而桩只是返回桩数据,它通常是重量级的,并且经常在多个测试中被复用。桩可以根据它们被调用的方式,通过配置进行修改。因此,桩是一种复杂对象,它可以做很多事情。至于 Mock 对象,如果你要做很多事,通常会创建大量又小又简单的 Mock 对象。\n", - "\n", - "\n", - "## 接口和类型\n", - "\n", - "`interface` 关键字的一个重要目标就是允许程序员隔离组件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/interfacea/A.java\n", - "package typeinfo.interfacea;\n", - "\n", - "public interface A {\n", - " void f();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后实现这个接口,你可以看到其代码是怎么从实际类型开始顺藤摸瓜的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/InterfaceViolation.java\n", - "// Sneaking around an interface\n", - "\n", - "import typeinfo.interfacea.*;\n", - "\n", - "class B implements A {\n", - " public void f() {\n", - " }\n", - "\n", - " public void g() {\n", - " }\n", - "}\n", - "\n", - "public class InterfaceViolation {\n", - " public static void main(String[] args) {\n", - " A a = new B();\n", - " a.f();\n", - " // a.g(); // Compile error\n", - " System.out.println(a.getClass().getName());\n", - " if (a instanceof B) {\n", - " B b = (B) a;\n", - " b.g();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "B" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过使用 RTTI,我们发现 `a` 是用 `B` 实现的。通过将其转型为 `B`,我们可以调用不在 `A` 中的方法。\n", - "\n", - "这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 `interface` 关键字正在保护你,但其实并没有。另外,在本例中使用 `B` 来实现 `A` 这种情况是有公开案例可查的[^3]。\n", - "\n", - "一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。这在很多情况下都是可行的,但“可能”还不够,你或许希望能有一些更严格的控制方式。\n", - "\n", - "最简单的方式是让实现类只具有包访问权限,这样在包外部的客户端就看不到它了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/packageaccess/HiddenC.java\n", - "package typeinfo.packageaccess;\n", - "\n", - "import typeinfo.interfacea.*;\n", - "\n", - "class C implements A {\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"public C.f()\");\n", - " }\n", - "\n", - " public void g() {\n", - " System.out.println(\"public C.g()\");\n", - " }\n", - "\n", - " void u() {\n", - " System.out.println(\"package C.u()\");\n", - " }\n", - "\n", - " protected void v() {\n", - " System.out.println(\"protected C.v()\");\n", - " }\n", - "\n", - " private void w() {\n", - " System.out.println(\"private C.w()\");\n", - " }\n", - "}\n", - "\n", - "public class HiddenC {\n", - " public static A makeA() {\n", - " return new C();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个包中唯一 `public` 的部分就是 `HiddenC`,在被调用时将产生 `A`接口类型的对象。这里有趣之处在于:即使你从 `makeA()` 返回的是 `C` 类型,你在包的外部仍旧不能使用 `A` 之外的任何方法,因为你不能在包的外部命名 `C`。\n", - "\n", - "现在如果你试着将其向下转型为 `C`,则将被禁止,因为在包的外部没有任何 `C` 类型可用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/HiddenImplementation.java\n", - "// Sneaking around package hiding\n", - "\n", - "import typeinfo.interfacea.*;\n", - "import typeinfo.packageaccess.*;\n", - "\n", - "import java.lang.reflect.*;\n", - "\n", - "public class HiddenImplementation {\n", - " public static void main(String[] args) throws Exception {\n", - " A a = HiddenC.makeA();\n", - " a.f();\n", - " System.out.println(a.getClass().getName());\n", - " // Compile error: cannot find symbol 'C':\n", - " /* if(a instanceof C) {\n", - " C c = (C)a;\n", - " c.g();\n", - " } */\n", - " // Oops! Reflection still allows us to call g():\n", - " callHiddenMethod(a, \"g\");\n", - " // And even less accessible methods!\n", - " callHiddenMethod(a, \"u\");\n", - " callHiddenMethod(a, \"v\");\n", - " callHiddenMethod(a, \"w\");\n", - " }\n", - "\n", - " static void callHiddenMethod(Object a, String methodName) throws Exception {\n", - " Method g = a.getClass().getDeclaredMethod(methodName);\n", - " g.setAccessible(true);\n", - " g.invoke(a);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "public C.f()\n", - "typeinfo.packageaccess.C\n", - "public C.g()\n", - "package C.u()\n", - "protected C.v()\n", - "private C.w()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正如你所看到的,通过使用反射,仍然可以调用所有方法,甚至是 `private` 方法!如果知道方法名,你就可以在其 `Method` 对象上调用 `setAccessible(true)`,就像在 `callHiddenMethod()` 中看到的那样。\n", - "\n", - "你可能觉得,可以通过只发布编译后的代码来阻止这种情况,但其实这并不能解决问题。因为只需要运行 `javap`(一个随 JDK 发布的反编译器)即可突破这一限制。下面是一个使用 `javap` 的命令行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "javap -private C" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`-private` 标志表示所有的成员都应该显示,甚至包括私有成员。下面是输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class typeinfo.packageaccess.C extends\n", - "java.lang.Object implements typeinfo.interfacea.A {\n", - " typeinfo.packageaccess.C();\n", - " public void f();\n", - " public void g();\n", - " void u();\n", - " protected void v();\n", - " private void w();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此,任何人都可以获取你最私有的方法的名字和签名,然后调用它们。\n", - "\n", - "那如果把接口实现为一个私有内部类,又会怎么样呢?下面展示了这种情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/InnerImplementation.java\n", - "// Private inner classes can't hide from reflection\n", - "\n", - "import typeinfo.interfacea.*;\n", - "\n", - "class InnerA {\n", - " private static class C implements A {\n", - " public void f() {\n", - " System.out.println(\"public C.f()\");\n", - " }\n", - "\n", - " public void g() {\n", - " System.out.println(\"public C.g()\");\n", - " }\n", - "\n", - " void u() {\n", - " System.out.println(\"package C.u()\");\n", - " }\n", - "\n", - " protected void v() {\n", - " System.out.println(\"protected C.v()\");\n", - " }\n", - "\n", - " private void w() {\n", - " System.out.println(\"private C.w()\");\n", - " }\n", - " }\n", - "\n", - " public static A makeA() {\n", - " return new C();\n", - " }\n", - "}\n", - "\n", - "public class InnerImplementation {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " A a = InnerA.makeA();\n", - " a.f();\n", - " System.out.println(a.getClass().getName());\n", - " // Reflection still gets into the private class:\n", - " HiddenImplementation.callHiddenMethod(a, \"g\");\n", - " HiddenImplementation.callHiddenMethod(a, \"u\");\n", - " HiddenImplementation.callHiddenMethod(a, \"v\");\n", - " HiddenImplementation.callHiddenMethod(a, \"w\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "public C.f()\n", - "InnerA$C\n", - "public C.g()\n", - "package C.u()\n", - "protected C.v()\n", - "private C.w()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里对反射仍然没有任何东西可以隐藏。那么如果是匿名类呢?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/AnonymousImplementation.java\n", - "// Anonymous inner classes can't hide from reflection\n", - "\n", - "import typeinfo.interfacea.*;\n", - "\n", - "class AnonymousA {\n", - " public static A makeA() {\n", - " return new A() {\n", - " public void f() {\n", - " System.out.println(\"public C.f()\");\n", - " }\n", - "\n", - " public void g() {\n", - " System.out.println(\"public C.g()\");\n", - " }\n", - "\n", - " void u() {\n", - " System.out.println(\"package C.u()\");\n", - " }\n", - "\n", - " protected void v() {\n", - " System.out.println(\"protected C.v()\");\n", - " }\n", - "\n", - " private void w() {\n", - " System.out.println(\"private C.w()\");\n", - " }\n", - " };\n", - " }\n", - "}\n", - "\n", - "public class AnonymousImplementation {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " A a = AnonymousA.makeA();\n", - " a.f();\n", - " System.out.println(a.getClass().getName());\n", - " // Reflection still gets into the anonymous class:\n", - " HiddenImplementation.callHiddenMethod(a, \"g\");\n", - " HiddenImplementation.callHiddenMethod(a, \"u\");\n", - " HiddenImplementation.callHiddenMethod(a, \"v\");\n", - " HiddenImplementation.callHiddenMethod(a, \"w\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "public C.f()\n", - "AnonymousA$1\n", - "public C.g()\n", - "package C.u()\n", - "protected C.v()\n", - "private C.w()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于字段来说也是这样,即便是 `private` 字段:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/ModifyingPrivateFields.java\n", - "\n", - "import java.lang.reflect.*;\n", - "\n", - "class WithPrivateFinalField {\n", - " private int i = 1;\n", - " private final String s = \"I'm totally safe\";\n", - " private String s2 = \"Am I safe?\";\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"i = \" + i + \", \" + s + \", \" + s2;\n", - " }\n", - "}\n", - "\n", - "public class ModifyingPrivateFields {\n", - " public static void main(String[] args) throws Exception {\n", - " WithPrivateFinalField pf =\n", - " new WithPrivateFinalField();\n", - " System.out.println(pf);\n", - " Field f = pf.getClass().getDeclaredField(\"i\");\n", - " f.setAccessible(true);\n", - " System.out.println(\n", - " \"f.getInt(pf): \" + f.getInt(pf));\n", - " f.setInt(pf, 47);\n", - " System.out.println(pf);\n", - " f = pf.getClass().getDeclaredField(\"s\");\n", - " f.setAccessible(true);\n", - " System.out.println(\"f.get(pf): \" + f.get(pf));\n", - " f.set(pf, \"No, you're not!\");\n", - " System.out.println(pf);\n", - " f = pf.getClass().getDeclaredField(\"s2\");\n", - " f.setAccessible(true);\n", - " System.out.println(\"f.get(pf): \" + f.get(pf));\n", - " f.set(pf, \"No, you're not!\");\n", - " System.out.println(pf);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 1, I'm totally safe, Am I safe?\n", - "f.getInt(pf): 1\n", - "i = 47, I'm totally safe, Am I safe?\n", - "f.get(pf): I'm totally safe\n", - "i = 47, I'm totally safe, Am I safe?\n", - "f.get(pf): Am I safe?\n", - "i = 47, I'm totally safe, No, you're not!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但实际上 `final` 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。\n", - "\n", - "通常,所有这些违反访问权限的操作并不是什么十恶不赦的。如果有人使用这样的技术去调用标志为 `private` 或包访问权限的方法(很明显这些访问权限表示这些人不应该调用它们),那么对他们来说,如果你修改了这些方法的某些地方,他们不应该抱怨。另一方面,总是在类中留下后门,也许会帮助你解决某些特定类型的问题(这些问题往往除此之外,别无它法)。总之,不可否认,反射给我们带来了很多好处。\n", - "\n", - "程序员往往对编程语言提供的访问控制过于自信,甚至认为 Java 在安全性上比其它提供了(明显)更宽松的访问控制的语言要优越[^4]。然而,正如你所看到的,事实并不是这样。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 `switch` 语句,你可以用 RTTI 和 `switch` 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能地使用多态机制,只在非用不可的时候才使用 RTTI。\n", - "\n", - "然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 `switch` 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。\n", - "\n", - "如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 `Instrument` 的类层次结构。假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 `Instrument` 中放入 `clearSpitValve()` 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 `Percussion`、弦乐器 `Stringed` 和电子乐器 `Electronic` 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 `clearSpitValve()` 放在某个合适的类中,在这个例子中是管乐器 `Wind`。不过,在这里你可能会发现还有更好的解决方法,就是将 `prepareInstrument()` 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。\n", - "\n", - "最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早地关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)[^5]。\n", - "\n", - "我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。有些人想得更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 `try` 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只要你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。\n", - "\n", - "[^1]: 特别是在过去。但现在 Java 的 HTML 文档有了很大的提升,要查看基类的方法已经变得很容易了。\n", - "\n", - "[^2]: 这是极限编程(XP,Extreme Programming)的原则之一:“Try the simplest thing that could possibly work,实现尽最大可能的简单。”\n", - "\n", - "[^3]: 最著名的例子是 Windows 操作系统,Windows 为开发者提供了公开的 API,但是开发者还可以找到一些非公开但是可以调用的函数。为了解决问题,很多程序员使用了隐藏的 API 函数。这就迫使微软公司要像维护公开 API 一样维护这些隐藏的 API,消耗了巨大的成本和精力。\n", - "\n", - "[^4]: 比如,Python 中在元素前面添加双下划线 `__`,就表示你想隐藏这个元素。如果你在类或者包外面调用了这个元素,运行环境就会报错。\n", - "\n", - "[^5]: 译者注:Java Profiler 是一种 Java 性能分析工具,用于在 JVM 级别监视 Java 字节码的构造和执行。主流的 Profiler 有 JProfiler、YourKit 和 Java VisualVM 等。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/20-Generics.ipynb b/jupyter/20-Generics.ipynb deleted file mode 100644 index 17efcaf7..00000000 --- a/jupyter/20-Generics.ipynb +++ /dev/null @@ -1,7433 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第二十章 泛型\n", - "\n", - "> 普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。\n", - "\n", - "多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了 `final` 类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。\n", - "\n", - "拘泥于单一的继承体系太过局限,因为只有继承体系中的对象才能适用基类作为参数的方法中。如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。这给予调用方一种选项,通过调整现有的类来实现接口,满足方法参数要求。接口可以突破继承体系的限制。\n", - "\n", - "即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。\n", - "\n", - "这就是泛型的概念,是 Java 5 的重大变化之一。泛型实现了*参数化类型*,这样你编写的组件(通常是集合)可以适用于多种类型。“泛型”这个术语的含义是“适用于很多类型”。编程语言中泛型出现的初衷是通过解耦类或方法与所使用的类型之间的约束,使得类或方法具备最宽泛的表达力。随后你会发现 Java 中泛型的实现并没有那么“泛”,你可能会质疑“泛型”这个词是否合适用来描述这一功能。\n", - "\n", - "如果你从未接触过参数化类型机制,你会发现泛型对 Java 语言确实是个很有益的补充。在你实例化一个类型参数时,编译器会负责转型并确保类型的正确性。这是一大进步。\n", - "\n", - "然而,如果你了解其他语言(例如 C++ )的参数化机制,你会发现,Java 泛型并不能满足所有的预期。使用别人创建好的泛型相对容易,但是创建自己的泛型时,就会遇到很多意料之外的麻烦。\n", - "\n", - "这并不是说 Java 泛型毫无用处。在很多情况下,它可以使代码更直接更优雅。不过,如果你见识过那种实现了更纯粹的泛型的编程语言,那么,Java 可能会令你失望。本章会介绍 Java 泛型的优点与局限。我会解释 Java 的泛型是如何发展成现在这样的,希望能够帮助你更有效地使用这个特性。[^1]\n", - "\n", - "### 与 C++ 的比较\n", - "\n", - "Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。\n", - "\n", - "但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ *模板*(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里)。\n", - "\n", - "第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。\n", - "\n", - "因此,本章中会介绍少量 C++ 模板的例子,仅当它们确实可以加深理解时才会引入。\n", - "\n", - "\n", - "\n", - "## 简单泛型\n", - "\n", - "促成泛型出现的最主要的动机之一是为了创建*集合类*,参见 [集合](book/12-Collections.md) 章节。集合用于存放要使用到的对象。数组也是如此,不过集合比数组更加灵活,功能更丰富。几乎所有程序在运行过程中都会涉及到一组对象,因此集合是可复用性最高的类库之一。\n", - "\n", - "我们先看一个只能持有单个对象的类。这个类可以明确指定其持有的对象的类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Holder1.java\n", - "\n", - "class Automobile {}\n", - "\n", - "public class Holder1 {\n", - " private Automobile a;\n", - " public Holder1(Automobile a) { this.a = a; }\n", - " Automobile get() { return a; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个类的可复用性不高,它无法持有其他类型的对象。我们可不希望为碰到的每个类型都编写一个新的类。\n", - "\n", - "在 Java 5 之前,我们可以让这个类直接持有 `Object` 类型的对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ObjectHolder.java\n", - "\n", - "public class ObjectHolder {\n", - " private Object a;\n", - " public ObjectHolder(Object a) { this.a = a; }\n", - " public void set(Object a) { this.a = a; }\n", - " public Object get() { return a; }\n", - " \n", - " public static void main(String[] args) {\n", - " ObjectHolder h2 = new ObjectHolder(new Automobile());\n", - " Automobile a = (Automobile)h2.get();\n", - " h2.set(\"Not an Automobile\");\n", - " String s = (String)h2.get();\n", - " h2.set(1); // 自动装箱为 Integer\n", - " Integer x = (Integer)h2.get();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,`ObjectHolder` 可以持有任何类型的对象,在上面的示例中,一个 `ObjectHolder` 先后持有了三种不同类型的对象。\n", - "\n", - "一个集合中存储多种不同类型的对象的情况很少见,通常而言,我们只会用集合存储同一种类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。\n", - "\n", - "因此,与其使用 `Object` ,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。要达到这个目的,需要使用*类型参数*,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数。在下面的例子中,`T` 就是类型参数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericHolder.java\n", - "\n", - "public class GenericHolder {\n", - " private T a;\n", - " public GenericHolder() {}\n", - " public void set(T a) { this.a = a; }\n", - " public T get() { return a; }\n", - " \n", - " public static void main(String[] args) {\n", - " GenericHolder h3 = new GenericHolder();\n", - " h3.set(new Automobile()); // 此处有类型校验\n", - " Automobile a = h3.get(); // 无需类型转换\n", - " //- h3.set(\"Not an Automobile\"); // 报错\n", - " //- h3.set(1); // 报错\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "创建 `GenericHolder` 对象时,必须指明要持有的对象的类型,将其置于尖括号内,就像 `main()` 中那样使用。然后,你就只能在 `GenericHolder` 中存储该类型(或其子类,因为多态与泛型不冲突)的对象了。当你调用 `get()` 取值时,直接就是正确的类型。\n", - "\n", - "这就是 Java 泛型的核心概念:你只需告诉编译器要使用什么类型,剩下的细节交给它来处理。\n", - "\n", - "你可能注意到 `h3` 的定义非常繁复。在 `=` 左边有 `GenericHolder`, 右边又重复了一次。在 Java 5 中,这种写法被解释成“必要的”,但在 Java 7 中设计者修正了这个问题(新的简写语法随后成为备受欢迎的特性)。以下是简写的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Diamond.java\n", - "\n", - "class Bob {}\n", - "\n", - "public class Diamond {\n", - " public static void main(String[] args) {\n", - " GenericHolder h3 = new GenericHolder<>();\n", - " h3.set(new Bob());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,在 `h3` 的定义处,`=` 右边的尖括号是空的(称为“钻石语法”),而不是重复左边的类型信息。在本书剩余部分都会使用这种语法。\n", - "\n", - "一般来说,你可以认为泛型和其他类型差不多,只不过它们碰巧有类型参数罢了。在使用泛型时,你只需要指定它们的名称和类型参数列表即可。\n", - "\n", - "### 一个元组类库\n", - "\n", - "有时一个方法需要能返回多个对象。而 **return** 语句只能返回单个对象,解决方法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,我们就可以一劳永逸。同时,还获得了编译时的类型安全。\n", - "\n", - "这个概念称为*元组*,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象(这个概念也称为 *数据传输对象* 或 *信使* )。\n", - "\n", - "通常,元组可以具有任意长度,元组中的对象可以是不同类型的。不过,我们希望能够为每个对象指明类型,并且从元组中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。下面是一个可以存储两个对象的元组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Tuple2.java\n", - "package onjava;\n", - "\n", - "public class Tuple2 {\n", - " public final A a1;\n", - " public final B a2;\n", - " public Tuple2(A a, B b) { a1 = a; a2 = b; }\n", - " public String rep() { return a1 + \", \" + a2; }\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return \"(\" + rep() + \")\";\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "构造函数传入要存储的对象。这个元组隐式地保持了其中元素的次序。\n", - "\n", - "初次阅读上面的代码时,你可能认为这违反了 Java 编程的封装原则。`a1` 和 `a2` 应该声明为 **private**,然后提供 `getFirst()` 和 `getSecond()` 取值方法才对呀?考虑下这样做能提供的“安全性”是什么:元组的使用程序可以读取 `a1` 和 `a2` 然后对它们执行任何操作,但无法对 `a1` 和 `a2` 重新赋值。例子中的 `final` 可以实现同样的效果,并且更为简洁明了。\n", - "\n", - "另一种设计思路是允许元组的用户给 `a1` 和 `a2` 重新赋值。然而,采用上例中的形式无疑更加安全,如果用户想存储不同的元素,就会强制他们创建新的 `Tuple2` 对象。\n", - "\n", - "我们可以利用继承机制实现长度更长的元组。添加更多的类型参数就行了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Tuple3.java\n", - "package onjava;\n", - "\n", - "public class Tuple3 extends Tuple2 {\n", - " public final C a3;\n", - " public Tuple3(A a, B b, C c) {\n", - " super(a, b);\n", - " a3 = c;\n", - " }\n", - " \n", - " @Override\n", - " public String rep() {\n", - " return super.rep() + \", \" + a3;\n", - " }\n", - "}\n", - "\n", - "// onjava/Tuple4.java\n", - "package onjava;\n", - "\n", - "public class Tuple4\n", - " extends Tuple3 {\n", - " public final D a4;\n", - " public Tuple4(A a, B b, C c, D d) {\n", - " super(a, b, c);\n", - " a4 = d;\n", - " }\n", - " \n", - " @Override\n", - " public String rep() {\n", - " return super.rep() + \", \" + a4;\n", - " }\n", - "}\n", - "\n", - "// onjava/Tuple5.java\n", - "package onjava;\n", - "\n", - "public class Tuple5\n", - " extends Tuple4 {\n", - " public final E a5;\n", - " public Tuple5(A a, B b, C c, D d, E e) {\n", - " super(a, b, c, d);\n", - " a5 = e;\n", - " }\n", - " \n", - " @Override\n", - " public String rep() {\n", - " return super.rep() + \", \" + a5;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "演示需要,再定义两个类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Amphibian.java\n", - "public class Amphibian {}\n", - "\n", - "// generics/Vehicle.java\n", - "public class Vehicle {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用元组时,你只需要定义一个长度适合的元组,将其作为返回值即可。注意下面例子中方法的返回类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/TupleTest.java\n", - "import onjava.*;\n", - "\n", - "public class TupleTest {\n", - " static Tuple2 f() {\n", - " // 47 自动装箱为 Integer\n", - " return new Tuple2<>(\"hi\", 47);\n", - " }\n", - " \n", - " static Tuple3 g() {\n", - " return new Tuple3<>(new Amphibian(), \"hi\", 47);\n", - " }\n", - " \n", - " static Tuple4 h() {\n", - " return new Tuple4<>(new Vehicle(), new Amphibian(), \"hi\", 47);\n", - " }\n", - " \n", - " static Tuple5 k() {\n", - " return new Tuple5<>(new Vehicle(), new Amphibian(), \"hi\", 47, 11.1);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Tuple2 ttsi = f();\n", - " System.out.println(ttsi);\n", - " // ttsi.a1 = \"there\"; // 编译错误,因为 final 不能重新赋值\n", - " System.out.println(g());\n", - " System.out.println(h());\n", - " System.out.println(k());\n", - " }\n", - "}\n", - "\n", - "/* 输出:\n", - " (hi, 47)\n", - " (Amphibian@1540e19d, hi, 47)\n", - " (Vehicle@7f31245a, Amphibian@6d6f6e28, hi, 47)\n", - " (Vehicle@330bedb4, Amphibian@2503dbd3, hi, 47, 11.1)\n", - " */" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有了泛型,你可以很容易地创建元组,令其返回一组任意类型的对象。\n", - "\n", - "通过 `ttsi.a1 = \"there\"` 语句的报错,我们可以看出,**final** 声明确实可以确保 **public** 字段在对象被构造出来之后就不能重新赋值了。\n", - "\n", - "在上面的程序中,`new` 表达式有些啰嗦。本章稍后会介绍,如何利用 *泛型方法* 简化它们。\n", - "\n", - "### 一个堆栈类\n", - "\n", - "接下来我们看一个稍微复杂一点的例子:堆栈。在 [集合](book/12-Collections.md) 一章中,我们用 `LinkedList` 实现了 `onjava.Stack` 类。在那个例子中,`LinkedList` 本身已经具备了创建堆栈所需的方法。`Stack` 是通过两个泛型类 `Stack` 和 `LinkedList` 的组合来创建。我们可以看出,泛型只不过是一种类型罢了(稍后我们会看到一些例外的情况)。\n", - "\n", - "这次我们不用 `LinkedList` 来实现自己的内部链式存储机制。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/LinkedStack.java\n", - "// 用链式结构实现的堆栈\n", - "\n", - "public class LinkedStack {\n", - " private static class Node {\n", - " U item;\n", - " Node next;\n", - " \n", - " Node() { item = null; next = null; }\n", - " \n", - " Node(U item, Node next) {\n", - " this.item = item;\n", - " this.next = next;\n", - " }\n", - " \n", - " boolean end() {\n", - " return item == null && next == null;\n", - " }\n", - " }\n", - " \n", - " private Node top = new Node<>(); // 栈顶\n", - " \n", - " public void push(T item) {\n", - " top = new Node<>(item, top);\n", - " }\n", - " \n", - " public T pop() {\n", - " T result = top.item;\n", - " if (!top.end()) {\n", - " top = top.next;\n", - " }\n", - " return result;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " LinkedStack lss = new LinkedStack<>();\n", - " for (String s : \"Phasers on stun!\".split(\" \")) {\n", - " lss.push(s);\n", - " }\n", - " String s;\n", - " while ((s = lss.pop()) != null) {\n", - " System.out.println(s);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "stun!\n", - "on\n", - "Phasers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "内部类 `Node` 也是一个泛型,它拥有自己的类型参数。\n", - "\n", - "这个例子使用了一个 *末端标识* (end sentinel) 来判断栈何时为空。这个末端标识是在构造 `LinkedStack` 时创建的。然后,每次调用 `push()` 就会创建一个 `Node` 对象,并将其链接到前一个 `Node` 对象。当你调用 `pop()` 方法时,总是返回 `top.item`,然后丢弃当前 `top` 所指向的 `Node`,并将 `top` 指向下一个 `Node`,除非到达末端标识,这时就不能再移动 `top` 了。如果已经到达末端,程序还继续调用 `pop()` 方法,它只能得到 `null`,说明栈已经空了。\n", - "\n", - "### RandomList\n", - "\n", - "作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用它的 `select()` 方法时都随机返回一个元素。如果希望这种列表可以适用于各种类型,就需要使用泛型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/RandomList.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class RandomList extends ArrayList {\n", - " private Random rand = new Random(47);\n", - " \n", - " public T select() {\n", - " return get(rand.nextInt(size()));\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " RandomList rs = new RandomList<>();\n", - " Arrays.stream(\"The quick brown fox jumped over the lazy brown dog\".split(\" \")).forEach(rs::add);\n", - " IntStream.range(0, 11).forEach(i -> \n", - " System.out.print(rs.select() + \" \"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "brown over fox quick quick dog brown The brown lazy brown" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`RandomList` 继承了 `ArrayList` 的所有方法。本例中只添加了 `select()` 这个方法。\n", - "\n", - "\n", - "\n", - "## 泛型接口\n", - "\n", - "泛型也可以应用于接口。例如 *生成器*,这是一种专门负责创建对象的类。实际上,这是 *工厂方法* 设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。\n", - "\n", - "一般而言,一个生成器只定义一个方法,用于创建对象。例如 `java.util.function` 类库中的 `Supplier` 就是一个生成器,调用其 `get()` 获取对象。`get()` 是泛型方法,返回值为类型参数 `T`。\n", - "\n", - "为了演示 `Supplier`,我们需要定义几个类。下面是个咖啡相关的继承体系:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/coffee/Coffee.java\n", - "package generics.coffee;\n", - "\n", - "public class Coffee {\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - " \n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName() + \" \" + id;\n", - " }\n", - "}\n", - "\n", - "\n", - "// generics/coffee/Latte.java\n", - "package generics.coffee;\n", - "public class Latte extends Coffee {}\n", - "\n", - "\n", - "// generics/coffee/Mocha.java\n", - "package generics.coffee;\n", - "public class Mocha extends Coffee {}\n", - "\n", - "\n", - "// generics/coffee/Cappuccino.java\n", - "package generics.coffee;\n", - "public class Cappuccino extends Coffee {}\n", - "\n", - "\n", - "// generics/coffee/Americano.java\n", - "package generics.coffee;\n", - "public class Americano extends Coffee {}\n", - "\n", - "\n", - "// generics/coffee/Breve.java\n", - "package generics.coffee;\n", - "public class Breve extends Coffee {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,我们可以编写一个类,实现 `Supplier` 接口,它能够随机生成不同类型的 `Coffee` 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/coffee/CoffeeSupplier.java\n", - "// {java generics.coffee.CoffeeSupplier}\n", - "package generics.coffee;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class CoffeeSupplier\n", - "implements Supplier, Iterable {\n", - " private Class[] types = { Latte.class, Mocha.class, \n", - " Cappuccino.class, Americano.class, Breve.class };\n", - " private static Random rand = new Random(47);\n", - " \n", - " public CoffeeSupplier() {}\n", - " // For iteration:\n", - " private int size = 0;\n", - " public CoffeeSupplier(int sz) { size = sz; }\n", - " \n", - " @Override\n", - " public Coffee get() {\n", - " try {\n", - " return (Coffee) types[rand.nextInt(types.length)].newInstance();\n", - " } catch (InstantiationException | IllegalAccessException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " \n", - " class CoffeeIterator implements Iterator {\n", - " int count = size;\n", - " @Override\n", - " public boolean hasNext() { return count > 0; }\n", - " @Override\n", - " public Coffee next() {\n", - " count--;\n", - " return CoffeeSupplier.this.get();\n", - " }\n", - " @Override\n", - " public void remove() {\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " }\n", - " \n", - " @Override\n", - " public Iterator iterator() {\n", - " return new CoffeeIterator();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Stream.generate(new CoffeeSupplier())\n", - " .limit(5)\n", - " .forEach(System.out::println);\n", - " for (Coffee c : new CoffeeSupplier(5)) {\n", - " System.out.println(c);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Americano 0\n", - "Latte 1\n", - "Americano 2\n", - "Mocha 3\n", - "Mocha 4\n", - "Breve 5\n", - "Americano 6\n", - "Latte 7\n", - "Cappuccino 8\n", - "Cappuccino 9" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "参数化的 `Supplier` 接口确保 `get()` 返回值是参数的类型。`CoffeeSupplier` 同时还实现了 `Iterable` 接口,所以能用于 *for-in* 语句。不过,它还需要知道何时终止循环,这正是第二个构造函数的作用。\n", - "\n", - "下面是另一个实现 `Supplier` 接口的例子,它负责生成 Fibonacci 数列:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Fibonacci.java\n", - "// Generate a Fibonacci sequence\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class Fibonacci implements Supplier {\n", - " private int count = 0;\n", - " @Override\n", - " public Integer get() { return fib(count++); }\n", - " \n", - " private int fib(int n) {\n", - " if(n < 2) return 1;\n", - " return fib(n-2) + fib(n-1);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Stream.generate(new Fibonacci())\n", - " .limit(18)\n", - " .map(n -> n + \" \")\n", - " .forEach(System.out::print);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然我们在 `Fibonacci` 类的里里外外使用的都是 `int` 类型,但是其参数类型却是 `Integer`。这个例子引出了 Java 泛型的一个局限性:基本类型无法作为类型参数。不过 Java 5 具备自动装箱和拆箱的功能,可以很方便地在基本类型和相应的包装类之间进行转换。通过这个例子中 `Fibonacci` 类对 `int` 的使用,我们已经看到了这种效果。\n", - "\n", - "如果还想更进一步,编写一个实现了 `Iterable` 的 `Fibnoacci` 生成器。我们的一个选择是重写这个类,令其实现 `Iterable` 接口。不过,你并不是总能拥有源代码的控制权,并且,除非必须这么做,否则,我们也不愿意重写一个类。而且我们还有另一种选择,就是创建一个 *适配器* (Adapter) 来实现所需的接口,我们在前面介绍过这个设计模式。\n", - "\n", - "有多种方法可以实现适配器。例如,可以通过继承来创建适配器类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/IterableFibonacci.java\n", - "// Adapt the Fibonacci class to make it Iterable\n", - "import java.util.*;\n", - "\n", - "public class IterableFibonacci\n", - "extends Fibonacci implements Iterable {\n", - " private int n;\n", - " public IterableFibonacci(int count) { n = count; }\n", - " \n", - " @Override\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " @Override\n", - " public boolean hasNext() { return n > 0; }\n", - " @Override\n", - " public Integer next() {\n", - " n--;\n", - " return IterableFibonacci.this.get();\n", - " }\n", - " @Override\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " for(int i : new IterableFibonacci(18))\n", - " System.out.print(i + \" \");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 *for-in* 语句中使用 `IterableFibonacci`,必须在构造函数中提供一个边界值,这样 `hasNext()` 才知道何时返回 **false**,结束循环。\n", - "\n", - "\n", - "\n", - "## 泛型方法\n", - "\n", - "到目前为止,我们已经研究了参数化整个类。其实还可以参数化类中的方法。类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型的并没有什么关系。\n", - "\n", - "泛型方法独立于类而改变方法。作为准则,请“尽可能”使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。\n", - "\n", - "如果方法是 **static** 的,则无法访问该类的泛型类型参数,因此,如果使用了泛型类型参数,则它必须是泛型方法。\n", - "\n", - "要定义泛型方法,请将泛型参数列表放置在返回值之前,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericMethods.java\n", - "\n", - "public class GenericMethods {\n", - " public void f(T x) {\n", - " System.out.println(x.getClass().getName());\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " GenericMethods gm = new GenericMethods();\n", - " gm.f(\"\");\n", - " gm.f(1);\n", - " gm.f(1.0);\n", - " gm.f(1.0F);\n", - " gm.f('c');\n", - " gm.f(gm);\n", - " }\n", - "}\n", - "/* Output:\n", - "java.lang.String\n", - "java.lang.Integer\n", - "java.lang.Double\n", - "java.lang.Float\n", - "java.lang.Character\n", - "GenericMethods\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管可以同时对类及其方法进行参数化,但这里未将 **GenericMethods** 类参数化。只有方法 `f()` 具有类型参数,该参数由方法返回类型之前的参数列表指示。\n", - "\n", - "对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。 这称为 *类型参数推断*。因此,对 `f()` 的调用看起来像普通的方法调用,并且 `f()` 看起来像被重载了无数次一样。它甚至会接受 **GenericMethods** 类型的参数。\n", - "\n", - "如果使用基本类型调用 `f()` ,自动装箱就开始起作用,自动将基本类型包装在它们对应的包装类型中。\n", - "\n", - "\n", - "### 变长参数和泛型方法\n", - "\n", - "泛型方法和变长参数列表可以很好地共存:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericVarargs.java\n", - "\n", - "import java.util.ArrayList;\n", - "import java.util.List;\n", - "\n", - "public class GenericVarargs {\n", - " @SafeVarargs\n", - " public static List makeList(T... args) {\n", - " List result = new ArrayList<>();\n", - " for (T item : args)\n", - " result.add(item);\n", - " return result;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " List ls = makeList(\"A\");\n", - " System.out.println(ls);\n", - " ls = makeList(\"A\", \"B\", \"C\");\n", - " System.out.println(ls);\n", - " ls = makeList(\n", - " \"ABCDEFFHIJKLMNOPQRSTUVWXYZ\".split(\"\"));\n", - " System.out.println(ls);\n", - " }\n", - "}\n", - "/* Output:\n", - "[A]\n", - "[A, B, C]\n", - "[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R,\n", - "S, T, U, V, W, X, Y, Z]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此处显示的 `makeList()` 方法产生的功能与标准库的 `java.util.Arrays.asList()` 方法相同。\n", - "\n", - "`@SafeVarargs` 注解保证我们不会对变长参数列表进行任何修改,这是正确的,因为我们只从中读取。如果没有此注解,编译器将无法知道这些并会发出警告。\n", - "\n", - "\n", - "### 一个泛型的 Supplier\n", - "\n", - "这是一个为任意具有无参构造方法的类生成 **Supplier** 的类。为了减少键入,它还包括一个用于生成 **BasicSupplier** 的泛型方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/BasicSupplier.java\n", - "// Supplier from a class with a no-arg constructor\n", - "package onjava;\n", - "\n", - "import java.util.function.Supplier;\n", - "\n", - "public class BasicSupplier implements Supplier {\n", - " private Class type;\n", - "\n", - " public BasicSupplier(Class type) {\n", - " this.type = type;\n", - " }\n", - "\n", - " @Override\n", - " public T get() {\n", - " try {\n", - " // Assumes type is a public class:\n", - " return type.newInstance();\n", - " } catch (InstantiationException |\n", - " IllegalAccessException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " // Produce a default Supplier from a type token:\n", - " public static Supplier create(Class type) {\n", - " return new BasicSupplier<>(type);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此类提供了产生以下对象的基本实现:\n", - "\n", - "1. 是 **public** 的。 因为 **BasicSupplier** 在单独的包中,所以相关的类必须具有 **public** 权限,而不仅仅是包级访问权限。\n", - "\n", - "2. 具有无参构造方法。要创建一个这样的 **BasicSupplier** 对象,请调用 `create()` 方法,并将要生成类型的类型令牌传递给它。通用的 `create()` 方法提供了 `BasicSupplier.create(MyType.class)` 这种较简洁的语法来代替较笨拙的 `new BasicSupplier (MyType.class)`。\n", - "\n", - "例如,这是一个具有无参构造方法的简单类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CountedObject.java\n", - "\n", - "public class CountedObject {\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - "\n", - " public long id() {\n", - " return id;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"CountedObject \" + id;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**CountedObject** 类可以跟踪自身创建了多少个实例,并通过 `toString()` 报告这些实例的数量。 **BasicSupplier** 可以轻松地为 **CountedObject** 创建 **Supplier**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " // generics/BasicSupplierDemo.java\n", - "\n", - "import onjava.BasicSupplier;\n", - "\n", - "import java.util.stream.Stream;\n", - "\n", - "public class BasicSupplierDemo {\n", - " public static void main(String[] args) {\n", - " Stream.generate(\n", - " BasicSupplier.create(CountedObject.class))\n", - " .limit(5)\n", - " .forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "CountedObject 0\n", - "CountedObject 1\n", - "CountedObject 2\n", - "CountedObject 3\n", - "CountedObject 4\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "泛型方法减少了产生 **Supplier** 对象所需的代码量。 Java 泛型强制传递 **Class** 对象,以便在 `create()` 方法中将其用于类型推断。\n", - "\n", - "\n", - "### 简化元组的使用\n", - "\n", - "使用类型参数推断和静态导入,我们将把早期的元组重写为更通用的库。在这里,我们使用重载的静态方法创建元组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Tuple.java\n", - "// Tuple library using type argument inference\n", - "package onjava;\n", - "\n", - "public class Tuple {\n", - " public static Tuple2 tuple(A a, B b) {\n", - " return new Tuple2<>(a, b);\n", - " }\n", - "\n", - " public static Tuple3\n", - " tuple(A a, B b, C c) {\n", - " return new Tuple3<>(a, b, c);\n", - " }\n", - "\n", - " public static Tuple4\n", - " tuple(A a, B b, C c, D d) {\n", - " return new Tuple4<>(a, b, c, d);\n", - " }\n", - "\n", - " public static \n", - " Tuple5 tuple(A a, B b, C c, D d, E e) {\n", - " return new Tuple5<>(a, b, c, d, e);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们修改 **TupleTest.java** 来测试 **Tuple.java** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/TupleTest2.java\n", - "\n", - "import onjava.Tuple2;\n", - "import onjava.Tuple3;\n", - "import onjava.Tuple4;\n", - "import onjava.Tuple5;\n", - "\n", - "import static onjava.Tuple.tuple;\n", - "\n", - "public class TupleTest2 {\n", - " static Tuple2 f() {\n", - " return tuple(\"hi\", 47);\n", - " }\n", - "\n", - " static Tuple2 f2() {\n", - " return tuple(\"hi\", 47);\n", - " }\n", - "\n", - " static Tuple3 g() {\n", - " return tuple(new Amphibian(), \"hi\", 47);\n", - " }\n", - "\n", - " static Tuple4 h() {\n", - " return tuple(\n", - " new Vehicle(), new Amphibian(), \"hi\", 47);\n", - " }\n", - "\n", - " static Tuple5 k() {\n", - " return tuple(new Vehicle(), new Amphibian(),\n", - " \"hi\", 47, 11.1);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Tuple2 ttsi = f();\n", - " System.out.println(ttsi);\n", - " System.out.println(f2());\n", - " System.out.println(g());\n", - " System.out.println(h());\n", - " System.out.println(k());\n", - " }\n", - "}\n", - "/* Output:\n", - "(hi, 47)\n", - "(hi, 47)\n", - "(Amphibian@14ae5a5, hi, 47)\n", - "(Vehicle@135fbaa4, Amphibian@45ee12a7, hi, 47)\n", - "(Vehicle@4b67cf4d, Amphibian@7ea987ac, hi, 47, 11.1)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意,`f()` 返回一个参数化的 **Tuple2** 对象,而 `f2()` 返回一个未参数化的 **Tuple2** 对象。编译器不会在这里警告 `f2()` ,因为返回值未以参数化方式使用。从某种意义上说,它被“向上转型”为一个未参数化的 **Tuple2** 。 但是,如果尝试将 `f2()` 的结果放入到参数化的 **Tuple2** 中,则编译器将发出警告。\n", - "\n", - "\n", - "### 一个 Set 工具\n", - "\n", - "对于泛型方法的另一个示例,请考虑由 **Set** 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Sets.java\n", - "\n", - "package onjava;\n", - "\n", - "import java.util.HashSet;\n", - "import java.util.Set;\n", - "\n", - "public class Sets {\n", - " public static Set union(Set a, Set b) {\n", - " Set result = new HashSet<>(a);\n", - " result.addAll(b);\n", - " return result;\n", - " }\n", - "\n", - " public static \n", - " Set intersection(Set a, Set b) {\n", - " Set result = new HashSet<>(a);\n", - " result.retainAll(b);\n", - " return result;\n", - " }\n", - "\n", - " // Subtract subset from superset:\n", - " public static Set\n", - " difference(Set superset, Set subset) {\n", - " Set result = new HashSet<>(superset);\n", - " result.removeAll(subset);\n", - " return result;\n", - " }\n", - "\n", - " // Reflexive--everything not in the intersection:\n", - " public static Set complement(Set a, Set b) {\n", - " return difference(union(a, b), intersection(a, b));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "前三个方法通过将第一个参数的引用复制到新的 **HashSet** 对象中来复制第一个参数,因此不会直接修改参数集合。因此,返回值是一个新的 **Set** 对象。\n", - "\n", - "这四种方法代表数学集合操作: `union()` 返回一个包含两个参数并集的 **Set** , `intersection()` 返回一个包含两个参数集合交集的 **Set** , `difference()` 从 **superset** 中减去 **subset** 的元素 ,而 `complement()` 返回所有不在交集中的元素的 **Set**。作为显示这些方法效果的简单示例的一部分,下面是一个包含不同水彩名称的 **enum** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/watercolors/Watercolors.java\n", - "\n", - "package watercolors;\n", - "\n", - "public enum Watercolors {\n", - " ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW,\n", - " ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA,\n", - " ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE,\n", - " PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE,\n", - " PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN,\n", - " YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,\n", - " BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了方便起见(不必全限定所有名称),将其静态导入到以下示例中。本示例使用 **EnumSet** 轻松从 **enum** 中创建 **Set** 。(可以在[第二十二章 枚举](book/22-Enumerations.md)一章中了解有关 **EnumSet** 的更多信息。)在这里,静态方法 `EnumSet.range()` 要求提供所要在结果 **Set** 中创建的元素范围的第一个和最后一个元素:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/WatercolorSets.java\n", - "\n", - "import watercolors.*;\n", - "\n", - "import java.util.EnumSet;\n", - "import java.util.Set;\n", - "\n", - "import static watercolors.Watercolors.*;\n", - "import static onjava.Sets.*;\n", - "\n", - "public class WatercolorSets {\n", - " public static void main(String[] args) {\n", - " Set set1 =\n", - " EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE);\n", - " Set set2 =\n", - " EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER);\n", - " System.out.println(\"set1: \" + set1);\n", - " System.out.println(\"set2: \" + set2);\n", - " System.out.println(\n", - " \"union(set1, set2): \" + union(set1, set2));\n", - " Set subset = intersection(set1, set2);\n", - " System.out.println(\n", - " \"intersection(set1, set2): \" + subset);\n", - " System.out.println(\"difference(set1, subset): \" +\n", - " difference(set1, subset));\n", - " System.out.println(\"difference(set2, subset): \" +\n", - " difference(set2, subset));\n", - " System.out.println(\"complement(set1, set2): \" +\n", - " complement(set1, set2));\n", - " }\n", - "}\n", - "/* Output:\n", - "set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER,\n", - "VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,\n", - "COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE]\n", - "set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,\n", - "COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE,\n", - "SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,\n", - "BURNT_UMBER]\n", - "union(set1, set2): [BURNT_SIENNA, BRILLIANT_RED,\n", - "YELLOW_OCHRE, MAGENTA, SAP_GREEN, CERULEAN_BLUE_HUE,\n", - "ULTRAMARINE, VIRIDIAN_HUE, VIOLET, RAW_UMBER,\n", - "ROSE_MADDER, PERMANENT_GREEN, BURNT_UMBER,\n", - "PHTHALO_BLUE, CRIMSON, COBALT_BLUE_HUE]\n", - "intersection(set1, set2): [PERMANENT_GREEN,\n", - "CERULEAN_BLUE_HUE, ULTRAMARINE, VIRIDIAN_HUE,\n", - "PHTHALO_BLUE, COBALT_BLUE_HUE]\n", - "difference(set1, subset): [BRILLIANT_RED, MAGENTA,\n", - "VIOLET, CRIMSON, ROSE_MADDER]\n", - "difference(set2, subset): [BURNT_SIENNA, YELLOW_OCHRE,\n", - "BURNT_UMBER, SAP_GREEN, RAW_UMBER]\n", - "complement(set1, set2): [BURNT_SIENNA, BRILLIANT_RED,\n", - "YELLOW_OCHRE, MAGENTA, SAP_GREEN, VIOLET, RAW_UMBER,\n", - "ROSE_MADDER, BURNT_UMBER, CRIMSON]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接下来的例子使用 `Sets.difference()` 方法来展示 **java.util** 包中各种 **Collection** 和 **Map** 类之间的方法差异:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/CollectionMethodDifferences.java\n", - "// {java onjava.CollectionMethodDifferences}\n", - "\n", - "package onjava;\n", - "\n", - "import java.lang.reflect.Method;\n", - "import java.util.*;\n", - "import java.util.stream.Collectors;\n", - "\n", - "public class CollectionMethodDifferences {\n", - " static Set methodSet(Class type) {\n", - " return Arrays.stream(type.getMethods())\n", - " .map(Method::getName)\n", - " .collect(Collectors.toCollection(TreeSet::new));\n", - " }\n", - "\n", - " static void interfaces(Class type) {\n", - " System.out.print(\"Interfaces in \" +\n", - " type.getSimpleName() + \": \");\n", - " System.out.println(\n", - " Arrays.stream(type.getInterfaces())\n", - " .map(Class::getSimpleName)\n", - " .collect(Collectors.toList()));\n", - " }\n", - "\n", - " static Set object = methodSet(Object.class);\n", - "\n", - " static {\n", - " object.add(\"clone\");\n", - " }\n", - "\n", - " static void\n", - " difference(Class superset, Class subset) {\n", - " System.out.print(superset.getSimpleName() +\n", - " \" extends \" + subset.getSimpleName() +\n", - " \", adds: \");\n", - " Set comp = Sets.difference(\n", - " methodSet(superset), methodSet(subset));\n", - " comp.removeAll(object); // Ignore 'Object' methods\n", - " System.out.println(comp);\n", - " interfaces(superset);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Collection: \" +\n", - " methodSet(Collection.class));\n", - " interfaces(Collection.class);\n", - " difference(Set.class, Collection.class);\n", - " difference(HashSet.class, Set.class);\n", - " difference(LinkedHashSet.class, HashSet.class);\n", - " difference(TreeSet.class, Set.class);\n", - " difference(List.class, Collection.class);\n", - " difference(ArrayList.class, List.class);\n", - " difference(LinkedList.class, List.class);\n", - " difference(Queue.class, Collection.class);\n", - " difference(PriorityQueue.class, Queue.class);\n", - " System.out.println(\"Map: \" + methodSet(Map.class));\n", - " difference(HashMap.class, Map.class);\n", - " difference(LinkedHashMap.class, HashMap.class);\n", - " difference(SortedMap.class, Map.class);\n", - " difference(TreeMap.class, Map.class);\n", - " }\n", - "}\n", - "/* Output:\n", - "Collection: [add, addAll, clear, contains, containsAll,\n", - "equals, forEach, hashCode, isEmpty, iterator,\n", - "parallelStream, remove, removeAll, removeIf, retainAll,\n", - "size, spliterator, stream, toArray]\n", - "Interfaces in Collection: [Iterable]\n", - "Set extends Collection, adds: []\n", - "Interfaces in Set: [Collection]\n", - "HashSet extends Set, adds: []\n", - "Interfaces in HashSet: [Set, Cloneable, Serializable]\n", - "LinkedHashSet extends HashSet, adds: []\n", - "Interfaces in LinkedHashSet: [Set, Cloneable,\n", - "Serializable]\n", - "TreeSet extends Set, adds: [headSet,\n", - "descendingIterator, descendingSet, pollLast, subSet,\n", - "floor, tailSet, ceiling, last, lower, comparator,\n", - "pollFirst, first, higher]\n", - "Interfaces in TreeSet: [NavigableSet, Cloneable,\n", - "Serializable]\n", - "List extends Collection, adds: [replaceAll, get,\n", - "indexOf, subList, set, sort, lastIndexOf, listIterator]\n", - "Interfaces in List: [Collection]\n", - "ArrayList extends List, adds: [trimToSize,\n", - "ensureCapacity]\n", - "Interfaces in ArrayList: [List, RandomAccess,\n", - "Cloneable, Serializable]\n", - "LinkedList extends List, adds: [offerFirst, poll,\n", - "getLast, offer, getFirst, removeFirst, element,\n", - "removeLastOccurrence, peekFirst, peekLast, push,\n", - "pollFirst, removeFirstOccurrence, descendingIterator,\n", - "pollLast, removeLast, pop, addLast, peek, offerLast,\n", - "addFirst]\n", - "Interfaces in LinkedList: [List, Deque, Cloneable,\n", - "Serializable]\n", - "Queue extends Collection, adds: [poll, peek, offer,\n", - "element]\n", - "Interfaces in Queue: [Collection]\n", - "PriorityQueue extends Queue, adds: [comparator]\n", - "Interfaces in PriorityQueue: [Serializable]\n", - "Map: [clear, compute, computeIfAbsent,\n", - "computeIfPresent, containsKey, containsValue, entrySet,\n", - "equals, forEach, get, getOrDefault, hashCode, isEmpty,\n", - "keySet, merge, put, putAll, putIfAbsent, remove,\n", - "replace, replaceAll, size, values]\n", - "HashMap extends Map, adds: []\n", - "Interfaces in HashMap: [Map, Cloneable, Serializable]\n", - "LinkedHashMap extends HashMap, adds: []\n", - "Interfaces in LinkedHashMap: [Map]\n", - "SortedMap extends Map, adds: [lastKey, subMap,\n", - "comparator, firstKey, headMap, tailMap]\n", - "Interfaces in SortedMap: [Map]\n", - "TreeMap extends Map, adds: [descendingKeySet,\n", - "navigableKeySet, higherEntry, higherKey, floorKey,\n", - "subMap, ceilingKey, pollLastEntry, firstKey, lowerKey,\n", - "headMap, tailMap, lowerEntry, ceilingEntry,\n", - "descendingMap, pollFirstEntry, lastKey, firstEntry,\n", - "floorEntry, comparator, lastEntry]\n", - "Interfaces in TreeMap: [NavigableMap, Cloneable,\n", - "Serializable]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在第十二章 [集合的本章小结](book/12-Collections.md#本章小结) 部分将会用到这里的输出结果。\n", - "\n", - "\n", - "\n", - "## 构建复杂模型\n", - "\n", - "泛型的一个重要好处是能够简单安全地创建复杂模型。例如,我们可以轻松地创建一个元组列表:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/TupleList.java\n", - "// Combining generic types to make complex generic types\n", - "\n", - "import onjava.Tuple4;\n", - "\n", - "import java.util.ArrayList;\n", - "\n", - "public class TupleList\n", - " extends ArrayList> {\n", - " public static void main(String[] args) {\n", - " TupleList tl =\n", - " new TupleList<>();\n", - " tl.add(TupleTest2.h());\n", - " tl.add(TupleTest2.h());\n", - " tl.forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "(Vehicle@7cca494b, Amphibian@7ba4f24f, hi, 47)\n", - "(Vehicle@3b9a45b3, Amphibian@7699a589, hi, 47)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这将产生一个功能强大的数据结构,而无需太多代码。\n", - "\n", - "下面是第二个例子。每个类都是组成块,总体包含很多个块。在这里,该模型是一个具有过道,货架和产品的零售商店:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Store.java\n", - "// Building a complex model using generic collections\n", - "\n", - "import onjava.Suppliers;\n", - "\n", - "import java.util.ArrayList;\n", - "import java.util.Random;\n", - "import java.util.function.Supplier;\n", - "\n", - "class Product {\n", - " private final int id;\n", - " private String description;\n", - " private double price;\n", - "\n", - " Product(int idNumber, String descr, double price) {\n", - " id = idNumber;\n", - " description = descr;\n", - " this.price = price;\n", - " System.out.println(toString());\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return id + \": \" + description +\n", - " \", price: $\" + price;\n", - " }\n", - "\n", - " public void priceChange(double change) {\n", - " price += change;\n", - " }\n", - "\n", - " public static Supplier generator =\n", - " new Supplier() {\n", - " private Random rand = new Random(47);\n", - "\n", - " @Override\n", - " public Product get() {\n", - " return new Product(rand.nextInt(1000), \"Test\",\n", - " Math.round(\n", - " rand.nextDouble() * 1000.0) + 0.99);\n", - " }\n", - " };\n", - "}\n", - "\n", - "class Shelf extends ArrayList {\n", - " Shelf(int nProducts) {\n", - " Suppliers.fill(this, Product.generator, nProducts);\n", - " }\n", - "}\n", - "\n", - "class Aisle extends ArrayList {\n", - " Aisle(int nShelves, int nProducts) {\n", - " for (int i = 0; i < nShelves; i++)\n", - " add(new Shelf(nProducts));\n", - " }\n", - "}\n", - "\n", - "class CheckoutStand {\n", - "}\n", - "\n", - "class Office {\n", - "}\n", - "\n", - "public class Store extends ArrayList {\n", - " private ArrayList checkouts =\n", - " new ArrayList<>();\n", - " private Office office = new Office();\n", - "\n", - " public Store(\n", - " int nAisles, int nShelves, int nProducts) {\n", - " for (int i = 0; i < nAisles; i++)\n", - " add(new Aisle(nShelves, nProducts));\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " StringBuilder result = new StringBuilder();\n", - " for (Aisle a : this)\n", - " for (Shelf s : a)\n", - " for (Product p : s) {\n", - " result.append(p);\n", - " result.append(\"\\n\");\n", - " }\n", - " return result.toString();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(new Store(5, 4, 3));\n", - " }\n", - "}\n", - "/* Output: (First 8 Lines)\n", - "258: Test, price: $400.99\n", - "861: Test, price: $160.99\n", - "868: Test, price: $417.99\n", - "207: Test, price: $268.99\n", - "551: Test, price: $114.99\n", - "278: Test, price: $804.99\n", - "520: Test, price: $554.99\n", - "140: Test, price: $530.99\n", - " ...\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Store.toString()` 显示了结果:尽管有复杂的层次结构,但多层的集合仍然是类型安全的和可管理的。令人印象深刻的是,组装这样的模型并不需要耗费过多精力。\n", - "\n", - "**Shelf** 使用 `Suppliers.fill()` 这个实用程序,该实用程序接受 **Collection** (第一个参数),并使用 **Supplier** (第二个参数),以元素的数量为 **n** (第三个参数)来填充它。 **Suppliers** 类将会在本章末尾定义,其中的方法都是在执行某种填充操作,并在本章的其他示例中使用。\n", - "\n", - "\n", - "## 泛型擦除\n", - "\n", - "当你开始更深入地钻研泛型时,会发现有大量的东西初看起来是没有意义的。例如,尽管可以说 `ArrayList.class`,但不能说成 `ArrayList.class`。考虑下面的情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ErasedTypeEquivalence.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class ErasedTypeEquivalence {\n", - " \n", - " public static void main(String[] args) {\n", - " Class c1 = new ArrayList().getClass();\n", - " Class c2 = new ArrayList().getClass();\n", - " System.out.println(c1 == c2);\n", - " }\n", - " \n", - "}\n", - "/* Output:\n", - "true\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`ArrayList` 和 `ArrayList` 应该是不同的类型。不同的类型会有不同的行为。例如,如果尝试向 `ArrayList` 中放入一个 `Integer`,所得到的行为(失败)和向 `ArrayList` 中放入一个 `Integer` 所得到的行为(成功)完全不同。然而上面的程序认为它们是相同的类型。\n", - "\n", - "下面的例子是对该谜题的补充:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/LostInformation.java\n", - "\n", - "import java.util.*;\n", - "\n", - "class Frob {}\n", - "class Fnorkle {}\n", - "class Quark {}\n", - "\n", - "class Particle {}\n", - "\n", - "public class LostInformation {\n", - "\n", - " public static void main(String[] args) {\n", - " List list = new ArrayList<>();\n", - " Map map = new HashMap<>();\n", - " Quark quark = new Quark<>();\n", - " Particle p = new Particle<>();\n", - " System.out.println(Arrays.toString(list.getClass().getTypeParameters()));\n", - " System.out.println(Arrays.toString(map.getClass().getTypeParameters()));\n", - " System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));\n", - " System.out.println(Arrays.toString(p.getClass().getTypeParameters()));\n", - " }\n", - "\n", - "}\n", - "/* Output:\n", - "[E]\n", - "[K,V]\n", - "[Q]\n", - "[POSITION,MOMENTUM]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "根据 JDK 文档,**Class.getTypeParameters()** “返回一个 **TypeVariable** 对象数组,表示泛型声明中声明的类型参数...” 这暗示你可以发现这些参数类型。但是正如上例中输出所示,你只能看到用作参数占位符的标识符,这并非有用的信息。\n", - "\n", - "残酷的现实是:\n", - "\n", - "在泛型代码内部,无法获取任何有关泛型参数类型的信息。\n", - "\n", - "因此,你可以知道如类型参数标识符和泛型边界这些信息,但无法得知实际的类型参数从而用来创建特定的实例。如果你曾是 C++ 程序员,那么这个事实会让你很沮丧,在使用 Java 泛型工作时,它是必须处理的最基本的问题。\n", - "\n", - "Java 泛型是使用擦除实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此,`List` 和 `List` 在运行时实际上是相同的类型。它们都被擦除成原生类型 `List`。\n", - "\n", - "理解擦除并知道如何处理它,是你在学习 Java 泛型时面临的最大障碍之一。这也是本节将要探讨的内容。\n", - "\n", - "### C++ 的方式\n", - "\n", - "下面是使用模版的 C++ 示例。你会看到类型参数的语法十分相似,因为 Java 是受 C++ 启发的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Templates.cpp\n", - "\n", - "#include \n", - "using namespace std;\n", - "\n", - "template class Manipulator {\n", - " T obj;\n", - "public:\n", - " Manipulator(T x) { obj = x; }\n", - " void manipulate() { obj.f(); }\n", - "};\n", - "\n", - "class HasF {\n", - "public:\n", - " void f() { cout << \"HasF::f()\" << endl; }\n", - "};\n", - "\n", - "int main() {\n", - " HasF hf;\n", - " Manipulator manipulator(hf);\n", - " manipulator.manipulate();\n", - "}\n", - "/* Output:\n", - "HasF::f()\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Manipulator** 类存储了一个 **T** 类型的对象。`manipulate()` 方法会调用 **obj** 上的 `f()` 方法。它是如何知道类型参数 **T** 中存在 `f()` 方法的呢?C++ 编译器会在你实例化模版时进行检查,所以在 `Manipulator` 实例化的那一刻,它看到 **HasF** 中含有一个方法 `f()`。如果情况并非如此,你就会得到一个编译期错误,保持类型安全。\n", - "\n", - "用 C++ 编写这种代码很简单,因为当模版被实例化时,模版代码就知道模版参数的类型。Java 泛型就不同了。下面是 **HasF** 的 Java 版本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/HasF.java\n", - "\n", - "public class HasF {\n", - " public void f() {\n", - " System.out.println(\"HasF.f()\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们将示例的其余代码用 Java 实现,就不会通过编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Manipulation.java\n", - "// {WillNotCompile}\n", - "\n", - "class Manipulator {\n", - " private T obj;\n", - " \n", - " Manipulator(T x) {\n", - " obj = x;\n", - " }\n", - " \n", - " // Error: cannot find symbol: method f():\n", - " public void manipulate() {\n", - " obj.f();\n", - " }\n", - "}\n", - "\n", - "public class Manipulation {\n", - "\tpublic static void main(String[] args) {\n", - " HasF hf = new HasF();\n", - " Manipulator manipulator = new Manipulator<>(hf);\n", - " manipulator.manipulate();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为擦除,Java 编译器无法将 `manipulate()` 方法必须能调用 **obj** 的 `f()` 方法这一需求映射到 HasF 具有 `f()` 方法这个事实上。为了调用 `f()`,我们必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了 **extends** 关键字。由于有了边界,下面的代码就能通过编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public class Manipulator2 {\n", - " private T obj;\n", - "\n", - " Manipulator2(T x) {\n", - " obj = x;\n", - " }\n", - "\n", - " public void manipulate() {\n", - " obj.f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "边界 `` 声明 T 必须是 HasF 类型或其子类。如果情况确实如此,就可以安全地在 **obj** 上调用 `f()` 方法。\n", - "\n", - "我们说泛型类型参数会擦除到它的第一个边界(可能有多个边界,稍后你将看到)。我们还提到了类型参数的擦除。编译器实际上会把类型参数替换为它的擦除,就像上面的示例,**T** 擦除到了 **HasF**,就像在类的声明中用 **HasF** 替换了 **T** 一样。\n", - "\n", - "你可能正确地观察到了泛型在 **Manipulator2.java** 中没有贡献任何事。你可以很轻松地自己去执行擦除,生成没有泛型的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Manipulator3.java\n", - "\n", - "class Manipulator3 {\n", - " private HasF obj;\n", - " \n", - " Manipulator3(HasF x) {\n", - " obj = x;\n", - " }\n", - " \n", - " public void manipulate() {\n", - " obj.f();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这提出了很重要的一点:泛型只有在类型参数比某个具体类型(以及其子类)更加“泛化”——代码能跨多个类工作时才有用。因此,类型参数和它们在有用的泛型代码中的应用,通常比简单的类替换更加复杂。但是,不能因此认为使用 `` 形式就是有缺陷的。例如,如果某个类有一个返回 **T** 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ReturnGenericType.java\n", - "\n", - "public class ReturnGenericType {\n", - " private T obj;\n", - " \n", - " ReturnGenericType(T x) {\n", - " obj = x;\n", - " }\n", - " \n", - " public T get() {\n", - " return obj;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你必须查看所有的代码,从而确定代码是否复杂到必须使用泛型的程度。\n", - "\n", - "我们将在本章稍后看到有关边界的更多细节。\n", - "\n", - "### 迁移兼容性\n", - "\n", - "为了减少潜在的关于擦除的困惑,你必须清楚地认识到这不是一个语言特性。它是 Java 实现泛型的一种妥协,因为泛型不是 Java 语言出现时就有的,所以就有了这种妥协。它会使你痛苦,因此你需要尽早习惯它并了解为什么它会这样。\n", - "\n", - "如果 Java 1.0 就含有泛型的话,那么这个特性就不会使用擦除来实现——它会使用具体化,保持参数类型为第一类实体,因此你就能在类型参数上执行基于类型的语言操作和反射操作。本章稍后你会看到,擦除减少了泛型的泛化性。泛型在 Java 中仍然是有用的,只是不如它们本来设想的那么有用,而原因就是擦除。\n", - "\n", - "在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文使用泛型类型。泛型类型只有在静态类型检测期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如, `List` 这样的类型注解会被擦除为 **List**,普通的类型变量在未指定边界的情况下会被擦除为 **Object**。\n", - "\n", - "擦除的核心动机是你可以在泛化的客户端上使用非泛型的类库,反之亦然。这经常被称为“迁移兼容性”。在理想情况下,所有事物将在指定的某天被泛化。在现实中,即使程序员只编写泛型代码,他们也必须处理 Java 5 之前编写的非泛型类库。这些类库的作者可能从没想过要泛化他们的代码,或许他们可能刚刚开始接触泛型。\n", - "\n", - "因此 Java 泛型不仅必须支持向后兼容性——现有的代码和类文件仍然合法,继续保持之前的含义——而且还必须支持迁移兼容性,使得类库能按照它们自己的步调变为泛型,当某个类库变为泛型时,不会破坏依赖于它的代码和应用。在确定了这个目标后,Java 设计者们和从事此问题相关工作的各个团队决策认为擦除是唯一可行的解决方案。擦除使得这种向泛型的迁移成为可能,允许非泛型的代码和泛型代码共存。\n", - "\n", - "例如,假设一个应用使用了两个类库 **X** 和 **Y**,**Y** 使用了类库 **Z**。随着 Java 5 的出现,这个应用和这些类库的创建者最终可能希望迁移到泛型上。但是当进行迁移时,它们有着不同的动机和限制。为了实现迁移兼容性,每个类库与应用必须与其他所有的部分是否使用泛型无关。因此,它们不能探测其他类库是否使用了泛型。因此,某个特定的类库使用了泛型这样的证据必须被”擦除“。\n", - "\n", - "如果没有某种类型的迁移途径,所有已经构建了很长时间的类库就需要与希望迁移到 Java 泛型上的开发者们说再见了。类库毫无争议是编程语言的一部分,对生产效率有着极大的影响,所以这种代码无法接受。擦除是否是最佳的或唯一的迁移途径,还待时间来证明。\n", - "\n", - "### 擦除的问题\n", - "\n", - "因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下将泛型融入到语言中。擦除允许你继续使用现有的非泛型客户端代码,直至客户端准备好用泛型重写这些代码。这是一个崇高的动机,因为它不会骤然破坏所有现有的代码。\n", - "\n", - "擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、**instanceof** 操作和 **new** 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。\n", - "\n", - "考虑如下的代码段:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class Foo {\n", - " T var;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "看上去当你创建一个 **Foo** 实例时:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Foo f = new Foo<>();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**class** **Foo** 中的代码应该知道现在工作于 **Cat** 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 **Object**“。\n", - "\n", - "另外,擦除和迁移兼容性意味着,使用泛型并不是强制的,尽管你可能希望这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ErasureAndInheritance.java\n", - "\n", - "class GenericBase {\n", - " private T element;\n", - " \n", - " public void set(T arg) {\n", - " element = arg;\n", - " }\n", - " \n", - " public T get() {\n", - " return element;\n", - " }\n", - "}\n", - "\n", - "class Derived1 extends GenericBase {}\n", - "\n", - "class Derived2 extends GenericBase {} // No warning\n", - "\n", - "// class Derived3 extends GenericBase {}\n", - "// Strange error:\n", - "// unexpected type\n", - "// required: class or interface without bounds\n", - "public class ErasureAndInteritance {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " Derived2 d2 = new Derived2();\n", - " Object obj = d2.get();\n", - " d2.set(obj); // Warning here!\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Derived2** 继承自 **GenericBase**,但是没有任何类型参数,编译器没有发出任何警告。直到调用 `set()` 方法时才出现警告。\n", - "\n", - "为了关闭警告,Java 提供了一个注解,我们可以在列表中看到它:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@SuppressWarnings(\"unchecked\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个注解放置在产生警告的方法上,而不是整个类上。当你要关闭警告时,最好尽可能地“聚焦”,这样就不会因为过于宽泛地关闭警告,而导致意外地遮蔽掉真正的问题。\n", - "\n", - "可以推断,**Derived3** 产生的错误意味着编译器期望得到一个原生基类。\n", - "\n", - "当你希望将类型参数不仅仅当作 Object 处理时,就需要付出额外努力来管理边界,并且与在 C++、Ada 和 Eiffel 这样的语言中获得参数化类型相比,你需要付出多得多的努力来获得少得多的回报。这并不是说,对于大多数的编程问题而言,这些语言通常都会比 Java 更得心应手,只是说它们的参数化类型机制相比 Java 更灵活、更强大。\n", - "\n", - "### 边界处的动作\n", - "\n", - "因为擦除,我发现了泛型最令人困惑的方面是可以表示没有任何意义的事物。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ArrayMaker.java\n", - "\n", - "import java.lang.reflect.*;\n", - "import java.util.*;\n", - "\n", - "public class ArrayMaker {\n", - " private Class kind;\n", - "\n", - " public ArrayMaker(Class kind) {\n", - " this.kind = kind;\n", - " }\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " T[] create(int size) {\n", - " return (T[]) Array.newInstance(kind, size);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " ArrayMaker stringMaker = new ArrayMaker<>(String.class);\n", - " String[] stringArray = stringMaker.create(9);\n", - " System.out.println(Arrays.toString(stringArray));\n", - " }\n", - "}\n", - "/* Output\n", - "[null,null,null,null,null,null,null,null,null]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "即使 **kind** 被存储为 `Class`,擦除也意味着它实际被存储为没有任何参数的 **Class**。因此,当你在使用它时,例如创建数组,`Array.newInstance()` 实际上并未拥有 **kind** 所蕴含的类型信息。所以它不会产生具体的结果,因而必须转型,这会产生一条令你无法满意的警告。\n", - "\n", - "注意,对于在泛型中创建数组,使用 `Array.newInstance()` 是推荐的方式。\n", - "\n", - "如果我们创建一个集合而不是数组,情况就不同了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ListMaker.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class ListMaker {\n", - " List create() {\n", - " return new ArrayList<>();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " ListMaker stringMaker = new ListMaker<>();\n", - " List stringList = stringMaker.create();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器不会给出任何警告,尽管我们知道(从擦除中)在 `create()` 内部的 `new ArrayList<>()` 中的 `` 被移除了——在运行时,类内部没有任何 ``,因此这看起来毫无意义。但是如果你遵从这种思路,并将这个表达式改为 `new ArrayList()`,编译器就会发出警告。\n", - "\n", - "本例中这么做真的毫无意义吗?如果在创建 **List** 的同时向其中放入一些对象呢,像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/FilledList.java\n", - "\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import onjava.*;\n", - "\n", - "public class FilledList extends ArrayList {\n", - " FilledList(Supplier gen, int size) {\n", - " Suppliers.fill(this, gen, size);\n", - " }\n", - " \n", - " public FilledList(T t, int size) {\n", - " for (int i = 0; i < size; i++) {\n", - " this.add(t);\n", - " }\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " List list = new FilledList<>(\"Hello\", 4);\n", - " System.out.println(list);\n", - " // Supplier version:\n", - " List ilist = new FilledList<>(() -> 47, 4);\n", - " System.out.println(ilist);\n", - " }\n", - "}\n", - "/* Output:\n", - "[Hello,Hello,Hello,Hello]\n", - "[47,47,47,47]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "即使编译器无法得知 `add()` 中的 **T** 的任何信息,但它仍可以在编译期确保你放入 **FilledList** 中的对象是 **T** 类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以确保方法或类中使用的类型的内部一致性。\n", - "\n", - "因为擦除移除了方法体中的类型信息,所以在运行时的问题就是*边界*:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。\n", - "\n", - "考虑如下这段非泛型示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SimpleHolder.java\n", - "\n", - "public class SimpleHolder {\n", - " private Object obj;\n", - " \n", - " public void set(Object obj) {\n", - " this.obj = obj;\n", - " }\n", - " \n", - " public Object get() {\n", - " return obj;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " SimpleHolder holder = new SimpleHolder();\n", - " holder.set(\"Item\");\n", - " String s = (String) holder.get();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果用 **javap -c SimpleHolder** 反编译这个类,会得到如下内容(经过编辑):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public void set(java.lang.Object);\n", - " 0: aload_0\n", - " 1: aload_1\n", - " 2: putfield #2; // Field obj:Object;\n", - " 5: return\n", - " \n", - "public java.lang.Object get();\n", - " 0: aload_0\n", - " 1: getfield #2; // Field obj:Object;\n", - " 4: areturn\n", - " \n", - "public static void main(java.lang.String[]);\n", - " 0: new #3; // class SimpleHolder\n", - " 3: dup\n", - " 4: invokespecial #4; // Method \"\":()V\n", - " 7: astore_1\n", - " 8: aload_1\n", - " 9: ldc #5; // String Item\n", - " 11: invokevirtual #6; // Method set:(Object;)V\n", - " 14: aload_1\n", - " 15: invokevirtual #7; // Method get:()Object;\n", - " 18: checkcast #8; // class java/lang/String\n", - " 21: astore_2\n", - " 22: return" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`set()` 和 `get()` 方法存储和产生值,转型在调用 `get()` 时接受检查。\n", - "\n", - "现在将泛型融入上例代码中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericHolder2.java\n", - "\n", - "public class GenericHolder2 {\n", - " private T obj;\n", - "\n", - " public void set(T obj) {\n", - " this.obj = obj;\n", - " }\n", - "\n", - " public T get() {\n", - " return obj;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " GenericHolder2 holder = new GenericHolder2<>();\n", - " holder.set(\"Item\");\n", - " String s = holder.get();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从 `get()` 返回后的转型消失了,但是我们还知道传递给 `set()` 的值在编译期会被检查。下面是相关的字节码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public void set(java.lang.Object);\n", - " 0: aload_0\n", - " 1: aload_1\n", - " 2: putfield #2; // Field obj:Object;\n", - " 5: return\n", - " \n", - "public java.lang.Object get();\n", - " 0: aload_0\n", - " 1: getfield #2; // Field obj:Object;\n", - " 4: areturn\n", - " \n", - "public static void main(java.lang.String[]);\n", - " 0: new #3; // class GenericHolder2\n", - " 3: dup\n", - " 4: invokespecial #4; // Method \"\":()V\n", - " 7: astore_1\n", - " 8: aload_1\n", - " 9: ldc #5; // String Item\n", - " 11: invokevirtual #6; // Method set:(Object;)V\n", - " 14: aload_1\n", - " 15: invokevirtual #7; // Method get:()Object;\n", - " 18: checkcast #8; // class java/lang/String\n", - " 21: astore_2\n", - " 22: return" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "所产生的字节码是相同的。对进入 `set()` 的类型进行检查是不需要的,因为这将由编译器执行。而对 `get()` 返回的值进行转型仍然是需要的,只不过不需要你来操作,它由编译器自动插入,这样你就不用编写(阅读)杂乱的代码。\n", - "\n", - "`get()` 和 `set()` 产生了相同的字节码,这就告诉我们泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。\n", - "\n", - "\n", - "\n", - "## 补偿擦除\n", - "\n", - "因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Erased.java\n", - "// {WillNotCompile}\n", - "\n", - "public class Erased {\n", - " private final int SIZE = 100;\n", - "\n", - " public void f(Object arg) {\n", - " // error: illegal generic type for instanceof\n", - " if (arg instanceof T) {\n", - " }\n", - " // error: unexpected type\n", - " T var = new T();\n", - " // error: generic array creation\n", - " T[] array = new T[SIZE];\n", - " // warning: [unchecked] unchecked cast\n", - " T[] array = (T[]) new Object[SIZE];\n", - "\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 **Class** 对象,以在类型表达式中使用它。\n", - "\n", - "例如,由于擦除了类型信息,因此在上一个程序中尝试使用 **instanceof** 将会失败。类型标签可以使用动态 `isInstance()` :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ClassTypeCapture.java\n", - "\n", - "class Building {\n", - "}\n", - "\n", - "class House extends Building {\n", - "}\n", - "\n", - "public class ClassTypeCapture {\n", - " Class kind;\n", - "\n", - " public ClassTypeCapture(Class kind) {\n", - " this.kind = kind;\n", - " }\n", - "\n", - " public boolean f(Object arg) {\n", - " return kind.isInstance(arg);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " ClassTypeCapture ctt1 =\n", - " new ClassTypeCapture<>(Building.class);\n", - " System.out.println(ctt1.f(new Building()));\n", - " System.out.println(ctt1.f(new House()));\n", - " ClassTypeCapture ctt2 =\n", - " new ClassTypeCapture<>(House.class);\n", - " System.out.println(ctt2.f(new Building()));\n", - " System.out.println(ctt2.f(new House()));\n", - " }\n", - "}\n", - "/* Output:\n", - "true\n", - "true\n", - "false\n", - "true\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器来保证类型标签与泛型参数相匹配。\n", - "\n", - "\n", - "### 创建类型的实例\n", - "\n", - "试图在 **Erased.java** 中 `new T()` 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 **T** 是否具有默认(无参)构造函数。但是在 C++ 中,此操作自然,直接且安全(在编译时检查):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "C++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/InstantiateGenericType.cpp\n", - "// C++, not Java!\n", - "\n", - "template class Foo {\n", - " T x; // Create a field of type T\n", - " T* y; // Pointer to T\n", - "public:\n", - " // Initialize the pointer:\n", - " Foo() { y = new T(); }\n", - "};\n", - "\n", - "class Bar {};\n", - "\n", - "int main() {\n", - " Foo fb;\n", - " Foo fi; // ... and it works with primitives\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Java 中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是 **Class** 对象,因此,如果使用类型标记,则可以使用 `newInstance()` 创建该类型的新对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/InstantiateGenericType.java\n", - "\n", - "import java.util.function.Supplier;\n", - "\n", - "class ClassAsFactory implements Supplier {\n", - " Class kind;\n", - "\n", - " ClassAsFactory(Class kind) {\n", - " this.kind = kind;\n", - " }\n", - "\n", - " @Override\n", - " public T get() {\n", - " try {\n", - " return kind.newInstance();\n", - " } catch (InstantiationException |\n", - " IllegalAccessException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}\n", - "\n", - "class Employee {\n", - " @Override\n", - " public String toString() {\n", - " return \"Employee\";\n", - " }\n", - "}\n", - "\n", - "public class InstantiateGenericType {\n", - " public static void main(String[] args) {\n", - " ClassAsFactory fe =\n", - " new ClassAsFactory<>(Employee.class);\n", - " System.out.println(fe.get());\n", - " ClassAsFactory fi =\n", - " new ClassAsFactory<>(Integer.class);\n", - " try {\n", - " System.out.println(fi.get());\n", - " } catch (Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "Employee\n", - "java.lang.InstantiationException: java.lang.Integer\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这样可以编译,但对于 `ClassAsFactory` 会失败,这是因为 **Integer** 没有无参构造函数。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法。他们建议使用显式工厂(**Supplier**)并约束类型,以便只有实现该工厂的类可以这样创建对象。这是创建工厂的两种不同方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/FactoryConstraint.java\n", - "\n", - "import onjava.Suppliers;\n", - "\n", - "import java.util.ArrayList;\n", - "import java.util.List;\n", - "import java.util.function.Supplier;\n", - "\n", - "class IntegerFactory implements Supplier {\n", - " private int i = 0;\n", - "\n", - " @Override\n", - " public Integer get() {\n", - " return ++i;\n", - " }\n", - "}\n", - "\n", - "class Widget {\n", - " private int id;\n", - "\n", - " Widget(int n) {\n", - " id = n;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"Widget \" + id;\n", - " }\n", - "\n", - " public static\n", - " class Factory implements Supplier {\n", - " private int i = 0;\n", - "\n", - " @Override\n", - " public Widget get() {\n", - " return new Widget(++i);\n", - " }\n", - " }\n", - "}\n", - "\n", - "class Fudge {\n", - " private static int count = 1;\n", - " private int n = count++;\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"Fudge \" + n;\n", - " }\n", - "}\n", - "\n", - "class Foo2 {\n", - " private List x = new ArrayList<>();\n", - "\n", - " Foo2(Supplier factory) {\n", - " Suppliers.fill(x, factory, 5);\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return x.toString();\n", - " }\n", - "}\n", - "\n", - "public class FactoryConstraint {\n", - " public static void main(String[] args) {\n", - " System.out.println(\n", - " new Foo2<>(new IntegerFactory()));\n", - " System.out.println(\n", - " new Foo2<>(new Widget.Factory()));\n", - " System.out.println(\n", - " new Foo2<>(Fudge::new));\n", - " }\n", - "}\n", - "/* Output:\n", - "[1, 2, 3, 4, 5]\n", - "[Widget 1, Widget 2, Widget 3, Widget 4, Widget 5]\n", - "[Fudge 1, Fudge 2, Fudge 3, Fudge 4, Fudge 5]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**IntegerFactory** 本身就是通过实现 `Supplier` 的工厂。 **Widget** 包含一个内部类,它是一个工厂。还要注意,**Fudge** 并没有做任何类似于工厂的操作,并且传递 `Fudge::new` 仍然会产生工厂行为,因为编译器将对函数方法 `::new` 的调用转换为对 `get()` 的调用。\n", - "\n", - "另一种方法是模板方法设计模式。在以下示例中,`create()` 是模板方法,在子类中被重写以生成该类型的对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CreatorGeneric.java\n", - "\n", - "abstract class GenericWithCreate {\n", - " final T element;\n", - "\n", - " GenericWithCreate() {\n", - " element = create();\n", - " }\n", - "\n", - " abstract T create();\n", - "}\n", - "\n", - "class X {\n", - "}\n", - "\n", - "class XCreator extends GenericWithCreate {\n", - " @Override\n", - " X create() {\n", - " return new X();\n", - " }\n", - "\n", - " void f() {\n", - " System.out.println(\n", - " element.getClass().getSimpleName());\n", - " }\n", - "}\n", - "\n", - "public class CreatorGeneric {\n", - " public static void main(String[] args) {\n", - " XCreator xc = new XCreator();\n", - " xc.f();\n", - " }\n", - "}\n", - "/* Output:\n", - "X\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**GenericWithCreate** 包含 `element` 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 `create()` 方法。这种创建方式可以在子类中定义,同时建立 **T** 的类型。\n", - "\n", - "\n", - "\n", - "### 泛型数组\n", - "\n", - "正如在 **Erased.java** 中所看到的,我们无法创建泛型数组。通用解决方案是在试图创建泛型数组的时候使用 **ArrayList** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ListOfGenerics.java\n", - "\n", - "import java.util.ArrayList;\n", - "import java.util.List;\n", - "\n", - "public class ListOfGenerics {\n", - " private List array = new ArrayList<>();\n", - "\n", - " public void add(T item) {\n", - " array.add(item);\n", - " }\n", - "\n", - " public T get(int index) {\n", - " return array.get(index);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这样做可以获得数组的行为,并且还具有泛型提供的编译时类型安全性。\n", - "\n", - "有时,仍然会创建泛型类型的数组(例如, **ArrayList** 在内部使用数组)。可以通过使编译器满意的方式定义对数组的通用引用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ArrayOfGenericReference.java\n", - "\n", - "class Generic {\n", - "}\n", - "\n", - "public class ArrayOfGenericReference {\n", - " static Generic[] gia;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器接受此操作而不产生警告。但是我们永远无法创建具有该确切类型(包括类型参数)的数组,因此有点令人困惑。由于所有数组,无论它们持有什么类型,都具有相同的结构(每个数组插槽的大小和数组布局),因此似乎可以创建一个 **Object** 数组并将其转换为所需的数组类型。实际上,这确实可以编译,但是会产生 **ClassCastException** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ArrayOfGeneric.java\n", - "\n", - "public class ArrayOfGeneric {\n", - " static final int SIZE = 100;\n", - " static Generic[] gia;\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " try {\n", - " gia = (Generic[]) new Object[SIZE];\n", - " } catch (ClassCastException e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " // Runtime type is the raw (erased) type:\n", - " gia = (Generic[]) new Generic[SIZE];\n", - " System.out.println(gia.getClass().getSimpleName());\n", - " gia[0] = new Generic<>();\n", - " //- gia[1] = new Object(); // Compile-time error\n", - " // Discovers type mismatch at compile time:\n", - " //- gia[2] = new Generic();\n", - " }\n", - "}\n", - "/* Output:\n", - "[Ljava.lang.Object; cannot be cast to [LGeneric;\n", - "Generic[]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 `gia` 被强制转换为 `Generic[]` ,该信息也仅在编译时存在(并且没有 **@SuppressWarnings** 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个 **Object** 数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。\n", - "\n", - "让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericArray.java\n", - "\n", - "public class GenericArray {\n", - " private T[] array;\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " public GenericArray(int sz) {\n", - " array = (T[]) new Object[sz];\n", - " }\n", - "\n", - " public void put(int index, T item) {\n", - " array[index] = item;\n", - " }\n", - "\n", - " public T get(int index) {\n", - " return array[index];\n", - " }\n", - "\n", - " // Method that exposes the underlying representation:\n", - " public T[] rep() {\n", - " return array;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " GenericArray gai = new GenericArray<>(10);\n", - " try {\n", - " Integer[] ia = gai.rep();\n", - " } catch (ClassCastException e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " // This is OK:\n", - " Object[] oa = gai.rep();\n", - " }\n", - "}\n", - "/* Output:\n", - "[Ljava.lang.Object; cannot be cast to\n", - "[Ljava.lang.Integer;\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "和以前一样,我们不能说 `T[] array = new T[sz]` ,所以我们创建了一个 **Object** 数组并将其强制转换。\n", - "\n", - "`rep()` 方法返回一个 `T[]` ,在主方法中它应该是 `gai` 的 `Integer[]`,但是如果调用它并尝试将结果转换为 `Integer[]` 引用,则会得到 **ClassCastException** ,这再次是因为实际的运行时类型为 `Object[]` 。\n", - "\n", - "如果再注释掉 **@SuppressWarnings** 注解后编译 **GenericArray.java** ,则编译器会产生警告:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "GenericArray.java uses unchecked or unsafe operations.\n", - "Recompile with -Xlint:unchecked for details." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里,我们收到了一个警告,我们认为这是有关强制转换的。\n", - "\n", - "但是要真正确定,请使用 `-Xlint:unchecked` 进行编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "确实是在抱怨那个强制转换。由于警告会变成噪音,因此,一旦我们确认预期会出现特定警告,我们可以做的最好的办法就是使用 **@SuppressWarnings** 将其关闭。这样,当警告确实出现时,我们将进行实际调查。\n", - "\n", - "由于擦除,数组的运行时类型只能是 `Object[]` 。 如果我们立即将其转换为 `T[]` ,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用 `Object[]` ,并在使用数组元素时向 **T** 添加强制类型转换。让我们来看看在 **GenericArray.java** 示例中会是怎么样的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericArray2.java\n", - "\n", - "public class GenericArray2 {\n", - " private Object[] array;\n", - "\n", - " public GenericArray2(int sz) {\n", - " array = new Object[sz];\n", - " }\n", - "\n", - " public void put(int index, T item) {\n", - " array[index] = item;\n", - " }\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " public T get(int index) {\n", - " return (T) array[index];\n", - " }\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " public T[] rep() {\n", - " return (T[]) array; // Unchecked cast\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " GenericArray2 gai =\n", - " new GenericArray2<>(10);\n", - " for (int i = 0; i < 10; i++)\n", - " gai.put(i, i);\n", - " for (int i = 0; i < 10; i++)\n", - " System.out.print(gai.get(i) + \" \");\n", - " System.out.println();\n", - " try {\n", - " Integer[] ia = gai.rep();\n", - " } catch (Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "0 1 2 3 4 5 6 7 8 9\n", - "java.lang.ClassCastException: [Ljava.lang.Object;\n", - "cannot be cast to [Ljava.lang.Integer;\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最初,看起来并没有太大不同,只是转换的位置移动了。没有 **@SuppressWarnings** 注解,仍然会收到“unchecked”警告。但是,内部表示现在是 `Object[]` 而不是 `T[]` 。 调用 `get()` 时,它将对象强制转换为 **T** ,实际上这是正确的类型,因此很安全。但是,如果调用 `rep()` ,它将再次尝试将 `Object[]` 强制转换为 `T[]` ,但仍然不正确,并在编译时生成警告,并在运行时生成异常。因此,无法破坏基础数组的类型,该基础数组只能是 `Object[]` 。在内部将数组视为 `Object[]` 而不是 `T[]` 的优点是,我们不太可能会忘记数组的运行时类型并意外地引入了bug,尽管大多数(也许是全部)此类错误会在运行时被迅速检测到。\n", - "\n", - "对于新代码,请传入类型标记。在这种情况下,**GenericArray** 如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericArrayWithTypeToken.java\n", - "\n", - "import java.lang.reflect.Array;\n", - "\n", - "public class GenericArrayWithTypeToken {\n", - " private T[] array;\n", - "\n", - " @SuppressWarnings(\"unchecked\")\n", - " public GenericArrayWithTypeToken(Class type, int sz) {\n", - " array = (T[]) Array.newInstance(type, sz);\n", - " }\n", - "\n", - " public void put(int index, T item) {\n", - " array[index] = item;\n", - " }\n", - "\n", - " public T get(int index) {\n", - " return array[index];\n", - " }\n", - "\n", - " // Expose the underlying representation:\n", - " public T[] rep() {\n", - " return array;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " GenericArrayWithTypeToken gai =\n", - " new GenericArrayWithTypeToken<>(\n", - " Integer.class, 10);\n", - " // This now works:\n", - " Integer[] ia = gai.rep();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "类型标记 **Class\\** 被传递到构造函数中以从擦除中恢复,因此尽管必须使用 **@SuppressWarnings** 关闭来自强制类型转换的警告,但我们仍可以创建所需的实际数组类型。一旦获得了实际的类型,就可以返回它并产生所需的结果,如在主方法中看到的那样。数组的运行时类型是确切的类型 `T[]` 。\n", - "\n", - "不幸的是,如果查看 Java 标准库中的源代码,你会发现到处都有从 **Object** 数组到参数化类型的转换。例如,这是**ArrayList** 中,复制一个 **Collection** 的构造函数,这里为了简化,去除了源码中对此不重要的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "public ArrayList(Collection c) {\n", - " size = c.size();\n", - " elementData = (E[])new Object[size];\n", - " c.toArray(elementData);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你浏览 **ArrayList.java** 的代码,将会发现很多此类强制转换。当我们编译它时会发生什么?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Note: ArrayList.java uses unchecked or unsafe operations\n", - "Note: Recompile with -Xlint:unchecked for details." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "果然,标准库会产生很多警告。如果你使用过 C 语言,尤其是使用 ANSI C 之前的语言,你会记住警告的特殊效果:发现警告后,可以忽略它们。因此,除非程序员必须对其进行处理,否则最好不要从编译器发出任何类型的消息。\n", - "\n", - "Neal Gafter(Java 5 的主要开发人员之一)在他的博客中[^2]指出,他在重写 Java 库时是很随意、马虎的,我们不应该像他那样做。Neal 还指出,他在不破坏现有接口的情况下无法修复某些 Java 库代码。因此,即使在 Java 库源代码中出现了一些习惯用法,它们也不一定是正确的做法。当查看库代码时,我们不能认为这就是要在自己代码中必须遵循的示例。\n", - "\n", - "请注意,在 Java 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》[^3],他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。\n", - "\n", - "\n", - "\n", - "## 边界\n", - "\n", - "*边界*(bounds)在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在绑定的类型中调用方法。\n", - "\n", - "由于擦除会删除类型信息,因此唯一可用于无限制泛型参数的方法是那些 **Object** 可用的方法。但是,如果将该参数限制为某类型的子集,则可以调用该子集中的方法。为了应用约束,Java 泛型使用了 `extends` 关键字。\n", - "\n", - "重要的是要理解,当用于限定泛型类型时,`extends` 的含义与通常的意义截然不同。此示例展示边界的基础应用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/BasicBounds.java\n", - "\n", - "interface HasColor {\n", - " java.awt.Color getColor();\n", - "}\n", - "\n", - "class WithColor {\n", - " T item;\n", - "\n", - " WithColor(T item) {\n", - " this.item = item;\n", - " }\n", - "\n", - " T getItem() {\n", - " return item;\n", - " }\n", - "\n", - " // The bound allows you to call a method:\n", - " java.awt.Color color() {\n", - " return item.getColor();\n", - " }\n", - "}\n", - "\n", - "class Coord {\n", - " public int x, y, z;\n", - "}\n", - "\n", - "// This fails. Class must be first, then interfaces:\n", - "// class WithColorCoord {\n", - "\n", - "// Multiple bounds:\n", - "class WithColorCoord {\n", - " T item;\n", - "\n", - " WithColorCoord(T item) {\n", - " this.item = item;\n", - " }\n", - "\n", - " T getItem() {\n", - " return item;\n", - " }\n", - "\n", - " java.awt.Color color() {\n", - " return item.getColor();\n", - " }\n", - "\n", - " int getX() {\n", - " return item.x;\n", - " }\n", - "\n", - " int getY() {\n", - " return item.y;\n", - " }\n", - "\n", - " int getZ() {\n", - " return item.z;\n", - " }\n", - "}\n", - "\n", - "interface Weight {\n", - " int weight();\n", - "}\n", - "\n", - "// As with inheritance, you can have only one\n", - "// concrete class but multiple interfaces:\n", - "class Solid {\n", - " T item;\n", - "\n", - " Solid(T item) {\n", - " this.item = item;\n", - " }\n", - "\n", - " T getItem() {\n", - " return item;\n", - " }\n", - "\n", - " java.awt.Color color() {\n", - " return item.getColor();\n", - " }\n", - "\n", - " int getX() {\n", - " return item.x;\n", - " }\n", - "\n", - " int getY() {\n", - " return item.y;\n", - " }\n", - "\n", - " int getZ() {\n", - " return item.z;\n", - " }\n", - "\n", - " int weight() {\n", - " return item.weight();\n", - " }\n", - "}\n", - "\n", - "class Bounded\n", - " extends Coord implements HasColor, Weight {\n", - " @Override\n", - " public java.awt.Color getColor() {\n", - " return null;\n", - " }\n", - "\n", - " @Override\n", - " public int weight() {\n", - " return 0;\n", - " }\n", - "}\n", - "\n", - "public class BasicBounds {\n", - " public static void main(String[] args) {\n", - " Solid solid =\n", - " new Solid<>(new Bounded());\n", - " solid.color();\n", - " solid.getY();\n", - " solid.weight();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可能会观察到 **BasicBounds.java** 中似乎包含一些冗余,它们可以通过继承来消除。在这里,每个继承级别还添加了边界约束:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/InheritBounds.java\n", - "\n", - "class HoldItem {\n", - " T item;\n", - "\n", - " HoldItem(T item) {\n", - " this.item = item;\n", - " }\n", - "\n", - " T getItem() {\n", - " return item;\n", - " }\n", - "}\n", - "\n", - "class WithColor2\n", - " extends HoldItem {\n", - " WithColor2(T item) {\n", - " super(item);\n", - " }\n", - "\n", - " java.awt.Color color() {\n", - " return item.getColor();\n", - " }\n", - "}\n", - "\n", - "class WithColorCoord2\n", - " extends WithColor2 {\n", - " WithColorCoord2(T item) {\n", - " super(item);\n", - " }\n", - "\n", - " int getX() {\n", - " return item.x;\n", - " }\n", - "\n", - " int getY() {\n", - " return item.y;\n", - " }\n", - "\n", - " int getZ() {\n", - " return item.z;\n", - " }\n", - "}\n", - "\n", - "class Solid2\n", - " extends WithColorCoord2 {\n", - " Solid2(T item) {\n", - " super(item);\n", - " }\n", - "\n", - " int weight() {\n", - " return item.weight();\n", - " }\n", - "}\n", - "\n", - "public class InheritBounds {\n", - " public static void main(String[] args) {\n", - " Solid2 solid2 =\n", - " new Solid2<>(new Bounded());\n", - " solid2.color();\n", - " solid2.getY();\n", - " solid2.weight();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**HoldItem** 拥有一个对象,因此此行为将继承到 **WithColor2** 中,这也需要其参数符合 **HasColor**。 **WithColorCoord2** 和 **Solid2** 进一步扩展了层次结构,并在每个级别添加了边界。现在,这些方法已被继承,并且在每个类中不再重复。\n", - "\n", - "这是一个具有更多层次的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/EpicBattle.java\n", - "// Bounds in Java generics\n", - "\n", - "import java.util.List;\n", - "\n", - "interface SuperPower {\n", - "}\n", - "\n", - "interface XRayVision extends SuperPower {\n", - " void seeThroughWalls();\n", - "}\n", - "\n", - "interface SuperHearing extends SuperPower {\n", - " void hearSubtleNoises();\n", - "}\n", - "\n", - "interface SuperSmell extends SuperPower {\n", - " void trackBySmell();\n", - "}\n", - "\n", - "class SuperHero {\n", - " POWER power;\n", - "\n", - " SuperHero(POWER power) {\n", - " this.power = power;\n", - " }\n", - "\n", - " POWER getPower() {\n", - " return power;\n", - " }\n", - "}\n", - "\n", - "class SuperSleuth\n", - " extends SuperHero {\n", - " SuperSleuth(POWER power) {\n", - " super(power);\n", - " }\n", - "\n", - " void see() {\n", - " power.seeThroughWalls();\n", - " }\n", - "}\n", - "\n", - "class\n", - "CanineHero\n", - " extends SuperHero {\n", - " CanineHero(POWER power) {\n", - " super(power);\n", - " }\n", - "\n", - " void hear() {\n", - " power.hearSubtleNoises();\n", - " }\n", - "\n", - " void smell() {\n", - " power.trackBySmell();\n", - " }\n", - "}\n", - "\n", - "class SuperHearSmell\n", - " implements SuperHearing, SuperSmell {\n", - " @Override\n", - " public void hearSubtleNoises() {\n", - " }\n", - "\n", - " @Override\n", - " public void trackBySmell() {\n", - " }\n", - "}\n", - "\n", - "class DogPerson extends CanineHero {\n", - " DogPerson() {\n", - " super(new SuperHearSmell());\n", - " }\n", - "}\n", - "\n", - "public class EpicBattle {\n", - " // Bounds in generic methods:\n", - " static \n", - " void useSuperHearing(SuperHero hero) {\n", - " hero.getPower().hearSubtleNoises();\n", - " }\n", - "\n", - " static \n", - " void superFind(SuperHero hero) {\n", - " hero.getPower().hearSubtleNoises();\n", - " hero.getPower().trackBySmell();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " DogPerson dogPerson = new DogPerson();\n", - " useSuperHearing(dogPerson);\n", - " superFind(dogPerson);\n", - " // You can do this:\n", - " List audioPeople;\n", - " // But you can't do this:\n", - " // List dogPs;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接下来将要研究的通配符将会把范围限制在单个类型。\n", - "\n", - "\n", - "\n", - "## 通配符\n", - "\n", - "你已经在 [集合](book/12-Collections.md) 章节中看到了一些简单示例使用了通配符——在泛型参数表达式中的问号,在 [类型信息](book/19-Type-Information.md) 一章中这种示例更多。本节将更深入地探讨这个特性。\n", - "\n", - "我们的起始示例要展示数组的一种特殊行为:你可以将派生类的数组赋值给基类的引用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CovariantArrays.java\n", - "\n", - "class Fruit {}\n", - "\n", - "class Apple extends Fruit {}\n", - "\n", - "class Jonathan extends Apple {}\n", - "\n", - "class Orange extends Fruit {}\n", - "\n", - "public class CovariantArrays {\n", - " \n", - " public static void main(String[] args) {\n", - " Fruit[] fruit = new Apple[10];\n", - " fruit[0] = new Apple(); // OK\n", - " fruit[1] = new Jonathan(); // OK\n", - " // Runtime type is Apple[], not Fruit[] or Orange[]:\n", - " try {\n", - " // Compiler allows you to add Fruit:\n", - " fruit[0] = new Fruit(); // ArrayStoreException\n", - " } catch (Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " try {\n", - " // Compiler allows you to add Oranges:\n", - " fruit[0] = new Orange(); // ArrayStoreException\n", - " } catch (Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "java.lang.ArrayStoreException: Fruit\n", - "java.lang.ArrayStoreException: Orange" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`main()` 中的第一行创建了 **Apple** 数组,并赋值给一个 **Fruit** 数组引用。这是有意义的,因为 **Apple** 也是一种 **Fruit**,因此 **Apple** 数组应该也是一个 **Fruit** 数组。\n", - "\n", - "但是,如果实际的数组类型是 **Apple[]**,你可以在其中放置 **Apple** 或 **Apple** 的子类型,这在编译期和运行时都可以工作。但是你也可以在数组中放置 **Fruit** 对象。这对编译器来说是有意义的,因为它有一个 **Fruit[]** 引用——它有什么理由不允许将 **Fruit** 对象或任何从 **Fruit** 继承出来的对象(比如 **Orange**),放置到这个数组中呢?因此在编译期,这是允许的。然而,运行时的数组机制知道它处理的是 **Apple[]**,因此会在向数组中放置异构类型时抛出异常。\n", - "\n", - "向上转型用在这里不合适。你真正在做的是将一个数组赋值给另一个数组。数组的行为是持有其他对象,这里只是因为我们能够向上转型而已,所以很明显,数组对象可以保留有关它们包含的对象类型的规则。看起来就像数组对它们持有的对象是有意识的,因此在编译期检查和运行时检查之间,你不能滥用它们。\n", - "\n", - "数组的这种赋值并不是那么可怕,因为在运行时你可以发现插入了错误的类型。但是泛型的主要目标之一是将这种错误检测移到编译期。所以当我们试图使用泛型集合代替数组时,会发生什么呢?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/NonCovariantGenerics.java\n", - "// {WillNotCompile}\n", - "\n", - "import java.util.*;\n", - "\n", - "public class NonCovariantGenerics {\n", - " // Compile Error: incompatible types:\n", - " List flist = new ArrayList();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尽管你在首次阅读这段代码时会认为“不能将一个 **Apple** 集合赋值给一个 **Fruit** 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 **Apple** 的泛型赋值给一个涉及 **Fruit** 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 将持有 **Apple** 和 **Apple** 的子类型,**Fruit** 的 **List** 将持有任何类型的 **Fruit**。是的,这包括 **Apple**,但是它不是一个 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在类型上不等价于 **Fruit** 的 **List**,即使 **Apple** 是一种 **Fruit** 类型。\n", - "\n", - "真正的问题是我们在讨论的集合类型,而不是集合持有对象的类型。与数组不同,泛型没有内建的协变类型。这是因为数组是完全在语言中定义的,因此可以具有编译期和运行时的内建检查,但是在使用泛型时,编译器和运行时系统不知道你想用类型做什么,以及应该采用什么规则。\n", - "\n", - "但是,有时你想在两个类型间建立某种向上转型关系。通配符可以产生这种关系。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericsAndCovariance.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class GenericsAndCovariance {\n", - " \n", - " public static void main(String[] args) {\n", - " // Wildcards allow covariance:\n", - " List flist = new ArrayList<>();\n", - " // Compile Error: can't add any type of object:\n", - " // flist.add(new Apple());\n", - " // flist.add(new Fruit());\n", - " // flist.add(new Object());\n", - " flist.add(null); // Legal but uninteresting\n", - " // We know it returns at least Fruit:\n", - " Fruit f = flist.get(0);\n", - " }\n", - " \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**flist** 的类型现在是 `List`,你可以读作“一个具有任何从 **Fruit** 继承的类型的列表”。然而,这实际上并不意味着这个 **List** 将持有任何类型的 **Fruit**。通配符引用的是明确的类型,因此它意味着“某种 **flist** 引用没有指定的具体类型”。因此这个被赋值的 **List** 必须持有诸如 **Fruit** 或 **Apple** 这样的指定类型,但是为了向上转型为 **Fruit**,这个类型是什么没人在意。\n", - "\n", - "**List** 必须持有一种具体的 **Fruit** 或 **Fruit** 的子类型,但是如果你不关心具体的类型是什么,那么你能对这样的 **List** 做什么呢?如果不知道 **List** 中持有的对象是什么类型,你怎能保证安全地向其中添加对象呢?就像在 **CovariantArrays.java** 中向上转型一样,你不能,除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。\n", - "\n", - "你可能认为事情开始变得有点走极端了,因为现在你甚至不能向刚刚声明过将持有 **Apple** 对象的 **List** 中放入一个 **Apple** 对象。是的,但编译器并不知道这一点。`List` 可能合法地指向一个 `List`。一旦执行这种类型的向上转型,你就丢失了向其中传递任何对象的能力,甚至传递 **Object** 也不行。\n", - "\n", - "另一方面,如果你调用了一个返回 **Fruit** 的方法,则是安全的,因为你知道这个 **List** 中的任何对象至少具有 **Fruit** 类型,因此编译器允许这么做。\n", - "\n", - "### 编译器有多聪明\n", - "\n", - "现在你可能会猜想自己不能去调用任何接受参数的方法,但是考虑下面的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CompilerIntelligence.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class CompilerIntelligence {\n", - " \n", - " public static void main(String[] args) {\n", - " List flist = Arrays.asList(new Apple());\n", - " Apple a = (Apple) flist.get(0); // No warning\n", - " flist.contains(new Apple()); // Argument is 'Object'\n", - " flist.indexOf(new Apple()); // Argument is 'Object'\n", - " }\n", - " \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里对 `contains()` 和 `indexOf()` 的调用接受 **Apple** 对象作为参数,执行没问题。这是否意味着编译器实际上会检查代码,以查看是否有某个特定的方法修改了它的对象?\n", - "\n", - "通过查看 **ArrayList** 的文档,我们发现编译器没有那么聪明。尽管 `add()` 接受一个泛型参数类型的参数,但 `contains()` 和 `indexOf()` 接受的参数类型是 **Object**。因此当你指定一个 `ArrayList` 时,`add()` 的参数就变成了\"**? extends Fruit**\"。从这个描述中,编译器无法得知这里需要 **Fruit** 的哪个具体子类型,因此它不会接受任何类型的 **Fruit**。如果你先把 **Apple** 向上转型为 **Fruit**,也没有关系——编译器仅仅会拒绝调用像 `add()` 这样参数列表中涉及通配符的方法。\n", - "\n", - "`contains()` 和 `indexOf()` 的参数类型是 **Object**,不涉及通配符,所以编译器允许调用它们。这意味着将由泛型类的设计者来决定哪些调用是“安全的”,并使用 **Object** 类作为它们的参数类型。为了禁止对类型中使用了通配符的方法调用,需要在参数列表中使用类型参数。\n", - "\n", - "下面展示一个简单的 **Holder** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Holder.java\n", - "\n", - "public class Holder {\n", - "\n", - " private T value;\n", - "\n", - " public Holder() {}\n", - "\n", - " public Holder(T val) {\n", - " value = val;\n", - " }\n", - "\n", - " public void set(T val) {\n", - " value = val;\n", - " }\n", - "\n", - " public T get() {\n", - " return value;\n", - " }\n", - "\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof Holder && Objects.equals(value, ((Holder) o).value);\n", - " }\n", - "\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hashCode(value);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Holder apple = new Holder<>(new Apple());\n", - " Apple d = apple.get();\n", - " apple.set(d);\n", - " // Holder fruit = apple; // Cannot upcast\n", - " Holder fruit = apple; // OK\n", - " Fruit p = fruit.get();\n", - " d = (Apple) fruit.get();\n", - " try {\n", - " Orange c = (Orange) fruit.get(); // No warning\n", - " } catch (Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " // fruit.set(new Apple()); // Cannot call set()\n", - " // fruit.set(new Fruit()); // Cannot call set()\n", - " System.out.println(fruit.equals(d)); // OK\n", - " }\n", - "}\n", - "/* Output\n", - "java.lang.ClassCastException: Apple cannot be cast to Orange\n", - "false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Holder** 有一个接受 **T** 类型对象的 `set()` 方法,一个返回 T 对象的 `get()` 方法和一个接受 Object 对象的 `equals()` 方法。正如你所见,如果创建了一个 `Holder`,就不能将其向上转型为 `Holder`,但是可以向上转型为 `Holder`。如果调用 `get()`,只能返回一个 **Fruit**——这就是在给定“任何扩展自 **Fruit** 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 **Fruit** 而不会导致任何警告,但是存在得到 **ClassCastException** 的风险。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因为 `set()` 的参数也是\"**? extends Fruit**\",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。\n", - "\n", - "但是,`equals()` 方法可以正常工作,因为它接受的参数是 **Object** 而不是 **T** 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。\n", - "\n", - "Java 7 引入了 **java.util.Objects** 库,使创建 `equals()` 和 `hashCode()` 方法变得更加容易,当然还有很多其他功能。`equals()` 方法的标准形式参考 [附录:理解 equals 和 hashCode 方法](book/Appendix-Understanding-equals-and-hashCode) 一章。\n", - "\n", - "### 逆变\n", - "\n", - "还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 `<?super MyClass>` ,或者甚至使用类型参数: `<?super T>`(尽管你不能对泛型参数给出一个超类型边界;即不能声明 `` )。这使得你可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向 **Collection** 写入了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SuperTypeWildcards.java\n", - "import java.util.*;\n", - "public class SuperTypeWildcards {\n", - " static void writeTo(List apples) {\n", - " apples.add(new Apple());\n", - " apples.add(new Jonathan());\n", - " // apples.add(new Fruit()); // Error\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "参数 **apples** 是 **Apple** 的某种基类型的 **List**,这样你就知道向其中添加 **Apple** 或 **Apple** 的子类型是安全的。但是因为 **Apple** 是下界,所以你知道向这样的 **List** 中添加 **Fruit** 是不安全的,因为这将使这个 **List** 敞开口子,从而可以向其中添加非 **Apple** 类型的对象,而这是违反静态类型安全的。\n", - "下面的示例复习了一下逆变和通配符的的使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericReading.java\n", - "import java.util.*;\n", - "\n", - "public class GenericReading {\n", - " static List apples = Arrays.asList(new Apple());\n", - " static List fruit = Arrays.asList(new Fruit());\n", - " \n", - " static T readExact(List list) {\n", - " return list.get(0);\n", - " }\n", - " \n", - " // A static method adapts to each call:\n", - " static void f1() {\n", - " Apple a = readExact(apples);\n", - " Fruit f = readExact(fruit);\n", - " f = readExact(apples);\n", - " }\n", - " \n", - " // A class type is established\n", - " // when the class is instantiated:\n", - " static class Reader {\n", - " T readExact(List list) { \n", - " return list.get(0); \n", - " }\n", - " }\n", - " \n", - " static void f2() {\n", - " Reader fruitReader = new Reader<>();\n", - " Fruit f = fruitReader.readExact(fruit);\n", - " //- Fruit a = fruitReader.readExact(apples);\n", - " // error: incompatible types: List\n", - " // cannot be converted to List\n", - " }\n", - " \n", - " static class CovariantReader {\n", - " T readCovariant(List list) {\n", - " return list.get(0);\n", - " }\n", - " }\n", - " \n", - " static void f3() {\n", - " CovariantReader fruitReader = new CovariantReader<>();\n", - " Fruit f = fruitReader.readCovariant(fruit);\n", - " Fruit a = fruitReader.readCovariant(apples);\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " f1(); \n", - " f2(); \n", - " f3();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`readExact()` 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型,就可以向 **List** 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 `readExact()` 可以有效地“适应”每个方法调用,并能够从 `List` 中返回一个 **Apple** ,从 `List` 中返回一个 **Fruit** ,就像在 `f1()` 中看到的那样。因此,如果可以摆脱静态泛型方法,那么在读取时就不需要协变类型了。\n", - "然而对于泛型类来说,当你创建这个类的实例时,就要为这个类确定参数。就像在 `f2()` 中看到的,**fruitReader** 实例可以从 `List` 中读取一个 **Fruit** ,因为这就是它的确切类型。但是 `List` 也应该产生 **Fruit** 对象,而 **fruitReader** 不允许这么做。\n", - "为了修正这个问题,`CovariantReader.readCovariant()` 方法将接受 `List<?extends T>` ,因此,从这个列表中读取一个 **T** 是安全的(你知道在这个列表中的所有对象至少是一个 **T** ,并且可能是从 T 导出的某种对象)。在 `f3()` 中,你可以看到现在可以从 `List` 中读取 **Fruit** 了。\n", - "\n", - "### 无界通配符\n", - "\n", - "无界通配符 `` 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/UnboundedWildcards1.java\n", - "import java.util.*;\n", - "\n", - "public class UnboundedWildcards1 {\n", - " static List list1;\n", - " static List list2;\n", - " static List list3;\n", - " \n", - " static void assign1(List list) {\n", - " list1 = list;\n", - " list2 = list;\n", - " //- list3 = list;\n", - " // warning: [unchecked] unchecked conversion\n", - " // list3 = list;\n", - " // ^\n", - " // required: List\n", - " // found: List\n", - " }\n", - " \n", - " static void assign2(List list) {\n", - " list1 = list;\n", - " list2 = list;\n", - " list3 = list;\n", - " }\n", - " \n", - " static void assign3(List list) {\n", - " list1 = list;\n", - " list2 = list;\n", - " list3 = list;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " assign1(new ArrayList());\n", - " assign2(new ArrayList());\n", - " //- assign3(new ArrayList());\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method assign3 in class UnboundedWildcards1\n", - " // is applied to given types\n", - " // assign3(new ArrayList());\n", - " // ^\n", - " // required: List\n", - " // found: ArrayList\n", - " // warning: [unchecked] unchecked conversion\n", - " // assign3(new ArrayList());\n", - " // ^\n", - " // required: List\n", - " // found: ArrayList\n", - " // 2 warnings\n", - " assign1(new ArrayList<>());\n", - " assign2(new ArrayList<>());\n", - " assign3(new ArrayList<>());\n", - " // Both forms are acceptable as List:\n", - " List wildList = new ArrayList();\n", - " wildList = new ArrayList<>();\n", - " assign1(wildList);\n", - " assign2(wildList);\n", - " assign3(wildList);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "有很多情况都和你在这里看到的情况类似,即编译器很少关心使用的是原生类型还是 `` 。在这些情况中,`` 可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上它是在声明:“我是想用 Java 的泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。”\n", - "第二个示例展示了无界通配符的一个重要应用。当你在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/UnboundedWildcards2.java\n", - "import java.util.*;\n", - "\n", - "public class UnboundedWildcards2 {\n", - " static Map map1;\n", - " static Map map2;\n", - " static Map map3;\n", - " \n", - " static void assign1(Map map) { \n", - " map1 = map; \n", - " }\n", - " \n", - " static void assign2(Map map) { \n", - " map2 = map; \n", - " }\n", - " \n", - " static void assign3(Map map) { \n", - " map3 = map; \n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " assign1(new HashMap());\n", - " assign2(new HashMap());\n", - " //- assign3(new HashMap());\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method assign3 in class UnboundedWildcards2\n", - " // is applied to given types\n", - " // assign3(new HashMap());\n", - " // ^\n", - " // required: Map\n", - " // found: HashMap\n", - " // warning: [unchecked] unchecked conversion\n", - " // assign3(new HashMap());\n", - " // ^\n", - " // required: Map\n", - " // found: HashMap\n", - " // 2 warnings\n", - " assign1(new HashMap<>());\n", - " assign2(new HashMap<>());\n", - " assign3(new HashMap<>());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards1.java** 展示了编译器处理 `List` 和 `List` 是不同的。\n", - "令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。”\n", - "编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Wildcards.java\n", - "// Exploring the meaning of wildcards\n", - "\n", - "public class Wildcards {\n", - " // Raw argument:\n", - " static void rawArgs(Holder holder, Object arg) {\n", - " //- holder.set(arg);\n", - " // warning: [unchecked] unchecked call to set(T)\n", - " // as a member of the raw type Holder\n", - " // holder.set(arg);\n", - " // ^\n", - " // where T is a type-variable:\n", - " // T extends Object declared in class Holder\n", - " // 1 warning\n", - "\n", - " // Can't do this; don't have any 'T':\n", - " // T t = holder.get();\n", - "\n", - " // OK, but type information is lost:\n", - " Object obj = holder.get();\n", - " }\n", - " \n", - " // Like rawArgs(), but errors instead of warnings:\n", - " static void unboundedArg(Holder holder, Object arg) {\n", - " //- holder.set(arg);\n", - " // error: method set in class Holder\n", - " // cannot be applied to given types;\n", - " // holder.set(arg);\n", - " // ^\n", - " // required: CAP#1\n", - " // found: Object\n", - " // reason: argument mismatch;\n", - " // Object cannot be converted to CAP#1\n", - " // where T is a type-variable:\n", - " // T extends Object declared in class Holder\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Object from capture of ?\n", - " // 1 error\n", - "\n", - " // Can't do this; don't have any 'T':\n", - " // T t = holder.get();\n", - "\n", - " // OK, but type information is lost:\n", - " Object obj = holder.get();\n", - " }\n", - " \n", - " static T exact1(Holder holder) {\n", - " return holder.get();\n", - " }\n", - " \n", - " static T exact2(Holder holder, T arg) {\n", - " holder.set(arg);\n", - " return holder.get();\n", - " }\n", - " \n", - " static T wildSubtype(Holder holder, T arg) {\n", - " //- holder.set(arg);\n", - " // error: method set in class Holder\n", - " // cannot be applied to given types;\n", - " // holder.set(arg);\n", - " // ^\n", - " // required: CAP#1\n", - " // found: T#1\n", - " // reason: argument mismatch;\n", - " // T#1 cannot be converted to CAP#1\n", - " // where T#1,T#2 are type-variables:\n", - " // T#1 extends Object declared in method\n", - " // wildSubtype(Holder,T#1)\n", - " // T#2 extends Object declared in class Holder\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends T#1 from\n", - " // capture of ? extends T#1\n", - " // 1 error\n", - " return holder.get();\n", - " }\n", - " \n", - " static void wildSupertype(Holder holder, T arg) {\n", - " holder.set(arg);\n", - " //- T t = holder.get();\n", - " // error: incompatible types:\n", - " // CAP#1 cannot be converted to T\n", - " // T t = holder.get();\n", - " // ^\n", - " // where T is a type-variable:\n", - " // T extends Object declared in method\n", - " // wildSupertype(Holder,T)\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Object super:\n", - " // T from capture of ? super T\n", - " // 1 error\n", - "\n", - " // OK, but type information is lost:\n", - " Object obj = holder.get();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " Holder raw = new Holder<>();\n", - " // Or:\n", - " raw = new Holder();\n", - " Holder qualified = new Holder<>();\n", - " Holder unbounded = new Holder<>();\n", - " Holder bounded = new Holder<>();\n", - " Long lng = 1L;\n", - "\n", - " rawArgs(raw, lng);\n", - " rawArgs(qualified, lng);\n", - " rawArgs(unbounded, lng);\n", - " rawArgs(bounded, lng);\n", - "\n", - " unboundedArg(raw, lng);\n", - " unboundedArg(qualified, lng);\n", - " unboundedArg(unbounded, lng);\n", - " unboundedArg(bounded, lng);\n", - "\n", - " //- Object r1 = exact1(raw);\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method exact1 in class Wildcards is applied\n", - " // to given types\n", - " // Object r1 = exact1(raw);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact1(Holder)\n", - " // warning: [unchecked] unchecked conversion\n", - " // Object r1 = exact1(raw);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact1(Holder)\n", - " // 2 warnings\n", - "\n", - " Long r2 = exact1(qualified);\n", - " Object r3 = exact1(unbounded); // Must return Object\n", - " Long r4 = exact1(bounded);\n", - "\n", - " //- Long r5 = exact2(raw, lng);\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method exact2 in class Wildcards is\n", - " // applied to given types\n", - " // Long r5 = exact2(raw, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact2(Holder,T)\n", - " // warning: [unchecked] unchecked conversion\n", - " // Long r5 = exact2(raw, lng);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact2(Holder,T)\n", - " // 2 warnings\n", - "\n", - " Long r6 = exact2(qualified, lng);\n", - "\n", - " //- Long r7 = exact2(unbounded, lng);\n", - " // error: method exact2 in class Wildcards\n", - " // cannot be applied to given types;\n", - " // Long r7 = exact2(unbounded, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // reason: inference variable T has\n", - " // incompatible bounds\n", - " // equality constraints: CAP#1\n", - " // lower bounds: Long\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact2(Holder,T)\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Object from capture of ?\n", - " // 1 error\n", - "\n", - " //- Long r8 = exact2(bounded, lng);\n", - " // error: method exact2 in class Wildcards\n", - " // cannot be applied to given types;\n", - " // Long r8 = exact2(bounded, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // reason: inference variable T\n", - " // has incompatible bounds\n", - " // equality constraints: CAP#1\n", - " // lower bounds: Long\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method exact2(Holder,T)\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Long from\n", - " // capture of ? extends Long\n", - " // 1 error\n", - "\n", - " //- Long r9 = wildSubtype(raw, lng);\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method wildSubtype in class Wildcards\n", - " // is applied to given types\n", - " // Long r9 = wildSubtype(raw, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSubtype(Holder,T)\n", - " // warning: [unchecked] unchecked conversion\n", - " // Long r9 = wildSubtype(raw, lng);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSubtype(Holder,T)\n", - " // 2 warnings\n", - "\n", - " Long r10 = wildSubtype(qualified, lng);\n", - " // OK, but can only return Object:\n", - " Object r11 = wildSubtype(unbounded, lng);\n", - " Long r12 = wildSubtype(bounded, lng);\n", - "\n", - " //- wildSupertype(raw, lng);\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method wildSupertype in class Wildcards\n", - " // is applied to given types\n", - " // wildSupertype(raw, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSupertype(Holder,T)\n", - " // warning: [unchecked] unchecked conversion\n", - " // wildSupertype(raw, lng);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSupertype(Holder,T)\n", - " // 2 warnings\n", - "\n", - " wildSupertype(qualified, lng);\n", - "\n", - " //- wildSupertype(unbounded, lng);\n", - " // error: method wildSupertype in class Wildcards\n", - " // cannot be applied to given types;\n", - " // wildSupertype(unbounded, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // reason: cannot infer type-variable(s) T\n", - " // (argument mismatch; Holder\n", - " // cannot be converted to Holder)\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSupertype(Holder,T)\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Object from capture of ?\n", - " // 1 error\n", - "\n", - " //- wildSupertype(bounded, lng);\n", - " // error: method wildSupertype in class Wildcards\n", - " // cannot be applied to given types;\n", - " // wildSupertype(bounded, lng);\n", - " // ^\n", - " // required: Holder,T\n", - " // found: Holder,Long\n", - " // reason: cannot infer type-variable(s) T\n", - " // (argument mismatch; Holder\n", - " // cannot be converted to Holder)\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method wildSupertype(Holder,T)\n", - " // where CAP#1 is a fresh type-variable:\n", - " // CAP#1 extends Long from capture of\n", - " // ? extends Long\n", - " // 1 error\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `rawArgs()` 中,编译器知道 `Holder` 是一个泛型类型,因此即使它在这里被表示成一个原生类型,编译器仍旧知道向 `set()` 传递一个 **Object** 是不安全的。由于它是原生类型,你可以将任何类型的对象传递给 `set()` ,而这个对象将被向上转型为 **Object** 。因此无论何时,只要使用了原生类型,都会放弃编译期检查。对 `get()` 的调用说明了相同的问题:没有任何 **T** 类型的对象,因此结果只能是一个 **Object**。\n", - "人们很自然地会开始考虑原生 `Holder` 与 `Holder` 是大致相同的事物。但是 `unboundedArg()` 强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生 **Holder** 将持有任何类型的组合,而 `Holder` 将持有具有某种具体类型的同构集合,因此不能只是向其中传递 **Object** 。\n", - "在 `exact1()` 和 `exact2()` 中,你可以看到使用了确切的泛型参数——没有任何通配符。你将看到,`exact2()`与 `exact1()` 具有不同的限制,因为它有额外的参数。\n", - "在 `wildSubtype()` 中,在 **Holder** 类型上的限制被放松为包括持有任何扩展自 **T** 的对象的 **Holder** 。这还是意味着如果 T 是 **Fruit** ,那么 `holder` 可以是 `Holder` ,这是合法的。为了防止将 **Orange** 放置到 `Holder` 中,对 `set()` 的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍旧知道任何来自 `Holder<?extends Fruit>` 的对象至少是 **Fruit** ,因此 `get()` (或者任何将产生具有这个类型参数的返回值的方法)都是允许的。\n", - "`wildSupertype()` 展示了超类型通配符,这个方法展示了与 `wildSubtype()` 相反的行为:`holder` 可以是持有任何 T 的基类型的容器。因此, `set()` 可以接受 **T** ,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是 **T** )。但是,尝试着调用 `get()` 是没有用的,因为由 `holder` 持有的类型可以是任何超类型,因此唯一安全的类型就是 **Object** 。\n", - "这个示例还展示了对于在 `unbounded()` 中使用无界通配符能够做什么不能做什么所做出的限制:因为你没有 **T**,所以你不能将 `set()` 或 `get()` 作用于 **T** 上。\n", - "\n", - "在 `main()` 方法中你看到了某些方法在接受某些类型的参数时没有报错和警告。为了迁移兼容性,`rawArgs()` 将接受所有 **Holder** 的不同变体,而不会产生警告。`unboundedArg()` 方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的方式并不相同。\n", - "\n", - "如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生 **Holder** 引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向 `exact1()` 传递一个无界引用,就不会有任何可以确定返回类型的类型信息。\n", - "可以看到,`exact2()` 具有最多的限制,因为它希望精确地得到一个 `Holder` ,以及一个具有类型 **T** 的参数,正由于此,它将产生错误或警告,除非提供确切的参数。有时,这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(就像在 `wildSubtype()` 中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在 `wildSupertype()` 中看到的那样)。\n", - "因此,使用确切类型来替代通配符类型的好处是,可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。\n", - "\n", - "### 捕获转换\n", - "\n", - "有一种特殊情况需要使用 `` 而不是原生类型。如果向一个使用 `` 的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。下面的示例演示了这种技术,它被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。这里,有关警告的注释只有在 `@SuppressWarnings` 注解被移除之后才能起作用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CaptureConversion.java\n", - "\n", - "public class CaptureConversion {\n", - " static void f1(Holder holder) {\n", - " T t = holder.get();\n", - " System.out.println(t.getClass().getSimpleName());\n", - " }\n", - " \n", - " static void f2(Holder holder) {\n", - " f1(holder); // Call with captured type\n", - " }\n", - " \n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " Holder raw = new Holder<>(1);\n", - " f1(raw);\n", - " // warning: [unchecked] unchecked method invocation:\n", - " // method f1 in class CaptureConversion\n", - " // is applied to given types\n", - " // f1(raw);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method f1(Holder)\n", - " // warning: [unchecked] unchecked conversion\n", - " // f1(raw);\n", - " // ^\n", - " // required: Holder\n", - " // found: Holder\n", - " // where T is a type-variable:\n", - " // T extends Object declared in\n", - " // method f1(Holder)\n", - " // 2 warnings\n", - " f2(raw); // No warnings\n", - " \n", - " Holder rawBasic = new Holder();\n", - " rawBasic.set(new Object());\n", - " // warning: [unchecked] unchecked call to set(T)\n", - " // as a member of the raw type Holder\n", - " // rawBasic.set(new Object());\n", - " // ^\n", - " // where T is a type-variable:\n", - " // T extends Object declared in class Holder\n", - " // 1 warning\n", - " f2(rawBasic); // No warnings\n", - " \n", - " // Upcast to Holder, still figures it out:\n", - " Holder wildcarded = new Holder<>(1.0);\n", - " f2(wildcarded);\n", - " }\n", - "}\n", - "/* Output:\n", - "Integer\n", - "Integer\n", - "Object\n", - "Double\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`f1()` 中的类型参数都是确切的,没有通配符或边界。在 `f2()` 中,**Holder** 参数是一个无界通配符,因此它看起来是未知的。但是,在 `f2()` 中调用了 `f1()`,而 `f1()` 需要一个已知参数。这里所发生的是:在调用 `f2()` 的过程中捕获了参数类型,并在调用 `f1()` 时使用了这种类型。\n", - "你可能想知道这项技术是否可以用于写入,但是这要求在传递 `Holder` 时同时传递一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,你需要使用确切的类型。注意,不能从 `f2()` 中返回 **T**,因为 **T** 对于 `f2()` 来说是未知的。捕获转换十分有趣,但是非常受限。\n", - "\n", - "\n", - "\n", - "## 问题\n", - "\n", - "本节将阐述在使用 Java 泛型时会出现的各类问题。\n", - "\n", - "### 任何基本类型都不能作为类型参数\n", - "\n", - "正如本章早先提到的,Java 泛型的限制之一是不能将基本类型用作类型参数。因此,不能创建 `ArrayList` 之类的东西。\n", - "解决方法是使用基本类型的包装器类以及自动装箱机制。如果创建一个 `ArrayList`,并将基本类型 **int** 应用于这个集合,那么你将发现自动装箱机制将自动地实现 **int** 到 **Integer** 的双向转换——因此,这几乎就像是有一个 `ArrayList` 一样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ListOfInt.java\n", - "// Autoboxing compensates for the inability\n", - "// to use primitives in generics\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class ListOfInt {\n", - " public static void main(String[] args) {\n", - " List li = IntStream.range(38, 48)\n", - " .boxed() // Converts ints to Integers\n", - " .collect(Collectors.toList());\n", - " System.out.println(li);\n", - " }\n", - "}\n", - "/* Output:\n", - "[38, 39, 40, 41, 42, 43, 44, 45, 46, 47]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通常,这种解决方案工作得很好——能够成功地存储和读取 **int**,自动装箱隐藏了转换的过程。但是如果性能成为问题的话,就需要使用专门为基本类型适配的特殊版本的集合;一个开源版本的实现是 **org.apache.commons.collections.primitives**。\n", - "下面是另外一种方式,它可以创建持有 **Byte** 的 **Set**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ByteSet.java\n", - "import java.util.*;\n", - "\n", - "public class ByteSet {\n", - " Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };\n", - " Set mySet = new HashSet<>(Arrays.asList(possibles));\n", - " // But you can't do this:\n", - " // Set mySet2 = new HashSet<>(\n", - " // Arrays.asList(1,2,3,4,5,6,7,8,9));\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "自动装箱机制解决了一些问题,但并没有解决所有问题。\n", - "\n", - "在下面的示例中,**FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的)。**Supplier** 实现来自 [数组](book/21-Arrays.md) 一章,并且在 `main()` 中,可以看到 `FillArray.fill()` 使用对象填充了数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/PrimitiveGenericTest.java\n", - "import onjava.*;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "// Fill an array using a generator:\n", - "interface FillArray {\n", - " static T[] fill(T[] a, Supplier gen) {\n", - " Arrays.setAll(a, n -> gen.get());\n", - " return a;\n", - " }\n", - " \n", - " static int[] fill(int[] a, IntSupplier gen) {\n", - " Arrays.setAll(a, n -> gen.getAsInt());\n", - " return a;\n", - " }\n", - " \n", - " static long[] fill(long[] a, LongSupplier gen) {\n", - " Arrays.setAll(a, n -> gen.getAsLong());\n", - " return a;\n", - " }\n", - " \n", - " static double[] fill(double[] a, DoubleSupplier gen) {\n", - " Arrays.setAll(a, n -> gen.getAsDouble());\n", - " return a;\n", - " }\n", - "}\n", - "\n", - "public class PrimitiveGenericTest {\n", - " public static void main(String[] args) {\n", - " String[] strings = FillArray.fill(\n", - " new String[5], new Rand.String(9));\n", - " System.out.println(Arrays.toString(strings));\n", - " int[] integers = FillArray.fill(\n", - " new int[9], new Rand.Pint());\n", - " System.out.println(Arrays.toString(integers));\n", - " }\n", - "}\n", - "/* Output:\n", - "[btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl]\n", - "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768, 4948]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "自动装箱不适用于数组,因此我们必须创建 `FillArray.fill()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll()` 有用一点,因为它返回填充的数组。\n", - "\n", - "### 实现参数化接口\n", - "\n", - "一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/MultipleInterfaceVariants.java\n", - "// {WillNotCompile}\n", - "package generics;\n", - "\n", - "interface Payable {}\n", - "\n", - "class Employee implements Payable {}\n", - "\n", - "class Hourly extends Employee implements Payable {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Hourly** 不能编译,因为擦除会将 `Payable` 和 `Payable` 简化为相同的类 **Payable**,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 **Payable** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。\n", - "\n", - "在使用某些更基本的 Java 接口,例如 `Comparable` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后看到的那样。\n", - "\n", - "### 转型和警告\n", - "\n", - "使用带有泛型类型参数的转型或 **instanceof** 不会有任何效果。下面的集合在内部将各个值存储为 **Object**,并在获取这些值时,再将它们转型回 **T**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericCast.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "class FixedSizeStack {\n", - " private final int size;\n", - " private Object[] storage;\n", - " private int index = 0;\n", - " \n", - " FixedSizeStack(int size) {\n", - " this.size = size;\n", - " storage = new Object[size];\n", - " }\n", - " \n", - " public void push(T item) {\n", - " if(index < size)\n", - " storage[index++] = item;\n", - " }\n", - " \n", - " @SuppressWarnings(\"unchecked\")\n", - " public T pop() {\n", - " return index == 0 ? null : (T)storage[--index];\n", - " }\n", - " \n", - " @SuppressWarnings(\"unchecked\")\n", - " Stream stream() {\n", - " return (Stream)Arrays.stream(storage);\n", - " }\n", - "}\n", - "\n", - "public class GenericCast {\n", - " static String[] letters = \"ABCDEFGHIJKLMNOPQRS\".split(\"\");\n", - " \n", - " public static void main(String[] args) {\n", - " FixedSizeStack strings =\n", - " new FixedSizeStack<>(letters.length);\n", - " Arrays.stream(\"ABCDEFGHIJKLMNOPQRS\".split(\"\"))\n", - " .forEach(strings::push);\n", - " System.out.println(strings.pop());\n", - " strings.stream()\n", - " .map(s -> s + \" \")\n", - " .forEach(System.out::print);\n", - " }\n", - "}\n", - "/* Output:\n", - "S\n", - "A B C D E F G H I J K L M N O P Q R S\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有 **@SuppressWarnings** 注解,编译器将对 `pop()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop()` 方法实际上并没有执行任何转型。\n", - "这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop()` 实际上只是将 **Object** 转型为 **Object**。\n", - "有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/NeedCasting.java\n", - "import java.io.*;\n", - "import java.util.*;\n", - "\n", - "public class NeedCasting {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public void f(String[] args) throws Exception {\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(args[0]));\n", - " List shapes = (List)in.readObject();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正如你将在 [附录:对象序列化](book/Appendix-Object-Serialization.md) 中学到的那样,`readObject()` 无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解并编译这个程序时,就会得到下面的警告。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NeedCasting.java uses unchecked or unsafe operations.\n", - "Recompile with -Xlint:unchecked for details.\n", - "\n", - "And if you follow the instructions and recompile with -\n", - "Xlint:unchecked :(如果遵循这条指示,使用-Xlint:unchecked来重新编译:)\n", - "\n", - "NeedCasting.java:10: warning: [unchecked] unchecked cast\n", - " List shapes = (List)in.readObject();\n", - " required: List\n", - " found: Object\n", - "1 warning" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,既通过泛型类来转型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ClassCasting.java\n", - "import java.io.*;\n", - "import java.util.*;\n", - "\n", - "public class ClassCasting {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public void f(String[] args) throws Exception {\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(args[0]));\n", - " // Won't Compile:\n", - " // List lw1 =\n", - " // List<>.class.cast(in.readObject());\n", - " List lw2 = List.class.cast(in.readObject());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是,不能转型到实际类型( `List` )。也就是说,不能声明:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "List.class.cast(in.readobject())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "甚至当你添加一个像下面这样的另一个转型时:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "(List)List.class.cast(in.readobject())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "仍旧会得到一个警告。\n", - "\n", - "### 重载\n", - "\n", - "下面的程序是不能编译的,即使它看起来是合理的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/UseList.java\n", - "// {WillNotCompile}\n", - "import java.util.*;\n", - "\n", - "public class UseList {\n", - " void f(List v) {}\n", - " void f(List v) {}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为擦除,所以重载方法产生了相同的类型签名。\n", - "\n", - "因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/UseList2.java\n", - "\n", - "import java.util.*;\n", - "\n", - "public class UseList2 {\n", - " void f1(List v) {}\n", - " void f2(List v) {}\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "幸运的是,编译器可以检测到这类问题。\n", - "\n", - "### 基类劫持接口\n", - "\n", - "假设你有一个实现了 **Comparable** 接口的 **Pet** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ComparablePet.java\n", - "\n", - "public class ComparablePet implements Comparable {\n", - " @Override\n", - " public int compareTo(ComparablePet o) {\n", - " return 0;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "尝试缩小 **ComparablePet** 子类的比较类型是有意义的。例如,**Cat** 类可以与其他的 **Cat** 比较:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/HijackedInterface.java\n", - "// {WillNotCompile}\n", - "\n", - "class Cat extends ComparablePet implements Comparable {\n", - " // error: Comparable cannot be inherited with\n", - " // different arguments: and \n", - " // class Cat\n", - " // ^\n", - " // 1 error\n", - " public int compareTo(Cat arg) {\n", - " return 0;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "不幸的是,这不能工作。一旦 **Comparable** 的类型参数设置为 **ComparablePet**,其他的实现类只能比较 **ComparablePet**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/RestrictedComparablePets.java\n", - "\n", - "public class Hamster extends ComparablePet implements Comparable {\n", - "\n", - " @Override\n", - " public int compareTo(ComparablePet arg) {\n", - " return 0;\n", - " }\n", - "}\n", - "// Or just:\n", - "class Gecko extends ComparablePet {\n", - " public int compareTo(ComparablePet arg) {\n", - " return 0;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Hamster** 显示了重新实现 **ComparableSet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。\n", - "\n", - "\n", - "\n", - "## 自限定的类型\n", - "\n", - "在 Java 泛型中,有一个似乎经常性出现的惯用法,它相当令人费解:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class SelfBounded> { // ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。**SelfBounded** 类接受泛型参数 **T**,而 **T** 由一个边界类限定,这个边界就是拥有 **T** 作为其参数的 **SelfBounded**。\n", - "\n", - "当你首次看到它时,很难去解析它,它强调的是当 **extends** 关键字用于边界与用来创建子类明显是不同的。\n", - "\n", - "### 古怪的循环泛型\n", - "\n", - "为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。\n", - "\n", - "不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CuriouslyRecurringGeneric.java\n", - "\n", - "class GenericType {}\n", - "\n", - "public class CuriouslyRecurringGeneric\n", - " extends GenericType {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这可以按照 Jim Coplien 在 C++ 中的*古怪的循环模版模式*的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。\n", - "为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java 中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,尽管这些将被擦除为 **Object** 的类型。下面是表示了这种情况的一个泛型类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/BasicHolder.java\n", - "\n", - "public class BasicHolder {\n", - " T element;\n", - " void set(T arg) { element = arg; }\n", - " T get() { return element; }\n", - " void f() {\n", - " System.out.println(element.getClass().getSimpleName());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个普通的泛型类型,它的一些方法将接受和产生具有其参数类型的对象,还有一个方法在其存储的域上执行操作(尽管只是在这个域上执行 **Object** 操作)。\n", - "我们可以在一个古怪的循环泛型中使用 **BasicHolder**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CRGWithBasicHolder.java\n", - "\n", - "class Subtype extends BasicHolder {}\n", - "\n", - "public class CRGWithBasicHolder {\n", - " public static void main(String[] args) {\n", - " Subtype st1 = new Subtype(), st2 = new Subtype();\n", - " st1.set(st2);\n", - " Subtype st3 = st1.get();\n", - " st1.f();\n", - " }\n", - "}\n", - "/* Output:\n", - "Subtype\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,这里有些东西很重要:新类 **Subtype** 接受的参数和返回的值具有 **Subtype** 类型而不仅仅是基类 **BasicHolder** 类型。这就是 CRG 的本质:基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模版,但是这些功能对于其所有参数和返回值,将使用导出类型。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在**Subtype** 中,传递给 `set()` 的参数和从 `get()` 返回的类型都是确切的 **Subtype**。\n", - "\n", - "### 自限定\n", - "\n", - "**BasicHolder** 可以使用任何类型作为其泛型参数,就像下面看到的那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Unconstrained.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "\n", - "class Other {}\n", - "class BasicOther extends BasicHolder {}\n", - "\n", - "public class Unconstrained {\n", - " public static void main(String[] args) {\n", - " BasicOther b = new BasicOther();\n", - " BasicOther b2 = new BasicOther();\n", - " b.set(new Other());\n", - " Other other = b.get();\n", - " b.f();\n", - " }\n", - "}\n", - "/* Output:\n", - "Other\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "限定将采取额外的步骤,强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SelfBounding.java\n", - "\n", - "class SelfBounded> {\n", - " T element;\n", - " SelfBounded set(T arg) {\n", - " element = arg;\n", - " return this;\n", - " }\n", - " T get() { return element; }\n", - "}\n", - "\n", - "class A extends SelfBounded {}\n", - "class B extends SelfBounded {} // Also OK\n", - "\n", - "class C extends SelfBounded {\n", - " C setAndGet(C arg) { \n", - " set(arg); \n", - " return get();\n", - " }\n", - "}\n", - "\n", - "class D {}\n", - "// Can't do this:\n", - "// class E extends SelfBounded {}\n", - "// Compile error:\n", - "// Type parameter D is not within its bound\n", - "\n", - "// Alas, you can do this, so you cannot force the idiom:\n", - "class F extends SelfBounded {}\n", - "\n", - "public class SelfBounding {\n", - " public static void main(String[] args) {\n", - " A a = new A();\n", - " a.set(new A());\n", - " a = a.set(new A()).get();\n", - " a = a.get();\n", - " C c = new C();\n", - " c = c.setAndGet(new C());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "自限定所做的,就是要求在继承关系中,像下面这样使用这个类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class A extends SelfBounded{}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这会强制要求将正在定义的类当作参数传递给基类。\n", - "\n", - "自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的,还可以从使用了另一个 **SelfBounded** 参数的 **SelfBounded** 中导出,尽管在 **A** 类看到的用法看起来是主要的用法。对定义 **E** 的尝试说明不能使用不是 **SelfBounded** 的类型参数。\n", - "遗憾的是, **F** 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。\n", - "注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 **E** 也会因此而变得可编译:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/NotSelfBounded.java\n", - "\n", - "public class NotSelfBounded {\n", - " T element;\n", - " NotSelfBounded set(T arg) {\n", - " element = arg;\n", - " return this;\n", - " }\n", - " T get() { return element; }\n", - "} \n", - "\n", - "class A2 extends NotSelfBounded {}\n", - "class B2 extends NotSelfBounded {}\n", - "\n", - "class C2 extends NotSelfBounded {\n", - " C2 setAndGet(C2 arg) { \n", - " set(arg); \n", - " return get(); \n", - " }\n", - "}\n", - "\n", - "class D2 {}\n", - "// Now this is OK:\n", - "class E2 extends NotSelfBounded {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因此很明显,自限定限制只能强制作用于继承关系。如果使用自限定,就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基类型。这会强制要求使用这个类的每个人都要遵循这种形式。\n", - "还可以将自限定用于泛型方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SelfBoundingMethods.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "\n", - "public class SelfBoundingMethods {\n", - " static > T f(T arg) {\n", - " return arg.set(arg).get();\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " A a = f(new A());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这可以防止这个方法被应用于除上述形式的自限定参数之外的任何事物上。\n", - "\n", - "### 参数协变\n", - "\n", - "自限定类型的价值在于它们可以产生*协变参数类型*——方法参数类型会随子类而变化。\n", - "\n", - "尽管自限定类型还可以产生与子类类型相同的返回类型,但是这并不十分重要,因为*协变返回类型*是在 Java 5 引入:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CovariantReturnTypes.java\n", - "\n", - "class Base {}\n", - "class Derived extends Base {}\n", - "\n", - "interface OrdinaryGetter {\n", - " Base get();\n", - "}\n", - "\n", - "interface DerivedGetter extends OrdinaryGetter {\n", - " // Overridden method return type can vary:\n", - " @Override\n", - " Derived get();\n", - "}\n", - "\n", - "public class CovariantReturnTypes {\n", - " void test(DerivedGetter d) {\n", - " Derived d2 = d.get();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**DerivedGetter** 中的 `get()` 方法覆盖了 **OrdinaryGetter** 中的 `get()` ,并返回了一个从 `OrdinaryGetter.get()` 的返回类型中导出的类型。尽管这是完全合乎逻辑的事情(导出类方法应该能够返回比它覆盖的基类方法更具体的类型)但是这在早先的 Java 版本中是不合法的。\n", - "\n", - "自限定泛型事实上将产生确切的导出类型作为其返回值,就像在 `get()` 中所看到的一样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/GenericsAndReturnTypes.java\n", - "\n", - "interface GenericGetter> {\n", - " T get();\n", - "}\n", - "\n", - "interface Getter extends GenericGetter {}\n", - "\n", - "public class GenericsAndReturnTypes {\n", - " void test(Getter g) {\n", - " Getter result = g.get();\n", - " GenericGetter gg = g.get(); // Also the base type\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java 5。\n", - "\n", - "然而,在非泛型代码中,参数类型不能随子类型发生变化:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/OrdinaryArguments.java\n", - "\n", - "class OrdinarySetter {\n", - " void set(Base base) {\n", - " System.out.println(\"OrdinarySetter.set(Base)\");\n", - " }\n", - "}\n", - "\n", - "class DerivedSetter extends OrdinarySetter {\n", - " void set(Derived derived) {\n", - " System.out.println(\"DerivedSetter.set(Derived)\");\n", - " }\n", - "}\n", - "\n", - "public class OrdinaryArguments {\n", - " public static void main(String[] args) {\n", - " Base base = new Base();\n", - " Derived derived = new Derived();\n", - " DerivedSetter ds = new DerivedSetter();\n", - " ds.set(derived);\n", - " // Compiles--overloaded, not overridden!:\n", - " ds.set(base);\n", - " }\n", - "}\n", - "/* Output:\n", - "DerivedSetter.set(Derived)\n", - "OrdinarySetter.set(Base)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`set(derived)` 和 `set(base)` 都是合法的,因此 `DerivedSetter.set()` 没有覆盖 `OrdinarySetter.set()` ,而是重载了这个方法。从输出中可以看到,在 **DerivedSetter** 中有两个方法,因此基类版本仍旧是可用的,因此可以证明它被重载过。\n", - "但是,在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型为参数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SelfBoundingAndCovariantArguments.java\n", - "\n", - "interface SelfBoundSetter> {\n", - " void set(T arg);\n", - "}\n", - "\n", - "interface Setter extends SelfBoundSetter {}\n", - "\n", - "public class SelfBoundingAndCovariantArguments {\n", - " void\n", - " testA(Setter s1, Setter s2, SelfBoundSetter sbs) {\n", - " s1.set(s2);\n", - " //- s1.set(sbs);\n", - " // error: method set in interface SelfBoundSetter\n", - " // cannot be applied to given types;\n", - " // s1.set(sbs);\n", - " // ^\n", - " // required: Setter\n", - " // found: SelfBoundSetter\n", - " // reason: argument mismatch;\n", - " // SelfBoundSetter cannot be converted to Setter\n", - " // where T is a type-variable:\n", - " // T extends SelfBoundSetter declared in\n", - " // interface SelfBoundSetter\n", - " // 1 error\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器不能识别将基类型当作参数传递给 `set()` 的尝试,因为没有任何方法具有这样的签名。实际上,这个参数已经被覆盖。\n", - "如果不使用自限定类型,普通的继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/PlainGenericInheritance.java\n", - "\n", - "class GenericSetter { // Not self-bounded\n", - " void set(T arg) {\n", - " System.out.println(\"GenericSetter.set(Base)\");\n", - " }\n", - "}\n", - "\n", - "class DerivedGS extends GenericSetter {\n", - " void set(Derived derived) {\n", - " System.out.println(\"DerivedGS.set(Derived)\");\n", - " }\n", - "}\n", - "\n", - "public class PlainGenericInheritance {\n", - " public static void main(String[] args) {\n", - " Base base = new Base();\n", - " Derived derived = new Derived();\n", - " DerivedGS dgs = new DerivedGS();\n", - " dgs.set(derived);\n", - " dgs.set(base); // Overloaded, not overridden!\n", - " }\n", - "}\n", - "/* Output:\n", - "DerivedGS.set(Derived)\n", - "GenericSetter.set(Base)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这段代码在模仿 **OrdinaryArguments.java**;在那个示例中,**DerivedSetter** 继承自包含一个 `set(Base)` 的**OrdinarySetter** 。而这里,**DerivedGS** 继承自泛型创建的也包含有一个 `set(Base)`的 `GenericSetter`。就像 **OrdinaryArguments.java** 一样,你可以从输出中看到, **DerivedGS** 包含两个 `set()` 的重载版本。如果不使用自限定,将重载参数类型。如果使用了自限定,只能获得方法的一个版本,它将接受确切的参数类型。\n", - "\n", - "\n", - "\n", - "## 动态类型安全\n", - "\n", - "因为可以向 Java 5 之前的代码传递泛型集合,所以旧式代码仍旧有可能会破坏你的集合。Java 5 的 **java.util.Collections** 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法 `checkedCollection()` 、`checkedList()`、 `checkedMap()` 、 `checkedSet()` 、`checkedSortedMap()`和 `checkedSortedSet()`。这些方法每一个都会将你希望动态检查的集合当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。\n", - "\n", - "受检查的集合在你试图插入类型不正确的对象时抛出 **ClassCastException** ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的集合,就可以发现谁在试图插入不良对象。\n", - "让我们用受检查的集合来看看“将猫插入到狗列表中”这个问题。这里,`oldStyleMethod()` 表示遗留代码,因为它接受的是原生的 **List** ,而 **@SuppressWarnings(“unchecked”)** 注解对于压制所产生的警告是必需的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/CheckedList.java\n", - "// Using Collection.checkedList()\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "\n", - "public class CheckedList {\n", - " @SuppressWarnings(\"unchecked\")\n", - " static void oldStyleMethod(List probablyDogs) {\n", - " probablyDogs.add(new Cat());\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " List dogs1 = new ArrayList<>();\n", - " oldStyleMethod(dogs1); // Quietly accepts a Cat\n", - " List dogs2 = Collections.checkedList(\n", - " new ArrayList<>(), Dog.class);\n", - " try {\n", - " oldStyleMethod(dogs2); // Throws an exception\n", - " } catch(Exception e) {\n", - " System.out.println(\"Expected: \" + e);\n", - " }\n", - " // Derived types work fine:\n", - " List pets = Collections.checkedList(\n", - " new ArrayList<>(), Pet.class);\n", - " pets.add(new Dog());\n", - " pets.add(new Cat());\n", - " }\n", - "}\n", - "/* Output:\n", - "Expected: java.lang.ClassCastException: Attempt to\n", - "insert class typeinfo.pets.Cat element into collection\n", - "with element type class typeinfo.pets.Dog\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "运行这个程序时,你会发现插入一个 **Cat** 对于 **dogs1** 来说没有任何问题,而 **dogs2** 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。\n", - "\n", - "\n", - "\n", - "## 泛型异常\n", - "\n", - "由于擦除的原因,**catch** 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 **Throwable**(这将进一步阻止你去定义不能捕获的泛型异常)。\n", - "但是,类型参数可能会在一个方法的 **throws** 子句中用到。这使得你可以编写随检查型异常类型变化的泛型代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ThrowGenericException.java\n", - "\n", - "import java.util.*;\n", - "\n", - "interface Processor {\n", - " void process(List resultCollector) throws E;\n", - "}\n", - "\n", - "class ProcessRunner\n", - "extends ArrayList> {\n", - " List processAll() throws E {\n", - " List resultCollector = new ArrayList<>();\n", - " for(Processor processor : this)\n", - " processor.process(resultCollector);\n", - " return resultCollector;\n", - " }\n", - "}\n", - "\n", - "class Failure1 extends Exception {}\n", - "\n", - "class Processor1\n", - "implements Processor {\n", - " static int count = 3;\n", - " @Override\n", - " public void process(List resultCollector)\n", - " throws Failure1 {\n", - " if(count-- > 1)\n", - " resultCollector.add(\"Hep!\");\n", - " else\n", - " resultCollector.add(\"Ho!\");\n", - " if(count < 0)\n", - " throw new Failure1();\n", - " }\n", - "}\n", - "\n", - "class Failure2 extends Exception {}\n", - "\n", - "class Processor2\n", - "implements Processor {\n", - " static int count = 2;\n", - " @Override\n", - " public void process(List resultCollector)\n", - " throws Failure2 {\n", - " if(count-- == 0)\n", - " resultCollector.add(47);\n", - " else {\n", - " resultCollector.add(11);\n", - " }\n", - " if(count < 0)\n", - " throw new Failure2();\n", - " }\n", - "}\n", - "\n", - "public class ThrowGenericException {\n", - " public static void main(String[] args) {\n", - " ProcessRunner runner =\n", - " new ProcessRunner<>();\n", - " for(int i = 0; i < 3; i++)\n", - " runner.add(new Processor1());\n", - " try {\n", - " System.out.println(runner.processAll());\n", - " } catch(Failure1 e) {\n", - " System.out.println(e);\n", - " }\n", - "\n", - " ProcessRunner runner2 =\n", - " new ProcessRunner<>();\n", - " for(int i = 0; i < 3; i++)\n", - " runner2.add(new Processor2());\n", - " try {\n", - " System.out.println(runner2.processAll());\n", - " } catch(Failure2 e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "[Hep!, Hep!, Ho!]\n", - "Failure2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Processor** 执行 `process()` 方法,并且可能会抛出具有类型 **E** 的异常。`process()` 的结果存储在 `ListresultCollector` 中(这被称为*收集参数*)。**ProcessRunner** 有一个 `processAll()` 方法,它会在所持有的每个 **Process** 对象执行,并返回 **resultCollector** 。\n", - "如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。\n", - "\n", - "\n", - "\n", - "## 混型\n", - "\n", - "术语*混型*随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。\n", - "混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点*面向切面编程* (AOP) 的味道,而切面经常被建议用来解决混型问题。\n", - "\n", - "### C++ 中的混型\n", - "\n", - "在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。\n", - "下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Mixins.cpp\n", - "\n", - "#include \n", - "#include \n", - "#include \n", - "using namespace std;\n", - "\n", - "template class TimeStamped : public T {\n", - " long timeStamp;\n", - "public:\n", - " TimeStamped() { timeStamp = time(0); }\n", - " long getStamp() { return timeStamp; }\n", - "};\n", - "\n", - "template class SerialNumbered : public T {\n", - " long serialNumber;\n", - " static long counter;\n", - "public:\n", - " SerialNumbered() { serialNumber = counter++; }\n", - " long getSerialNumber() { return serialNumber; }\n", - "};\n", - "\n", - "// Define and initialize the static storage:\n", - "template long SerialNumbered::counter = 1;\n", - "\n", - "class Basic {\n", - " string value;\n", - "public:\n", - " void set(string val) { value = val; }\n", - " string get() { return value; }\n", - "};\n", - "\n", - "int main() {\n", - " TimeStamped> mixin1, mixin2;\n", - " mixin1.set(\"test string 1\");\n", - " mixin2.set(\"test string 2\");\n", - " cout << mixin1.get() << \" \" << mixin1.getStamp() <<\n", - " \" \" << mixin1.getSerialNumber() << endl;\n", - " cout << mixin2.get() << \" \" << mixin2.getStamp() <<\n", - " \" \" << mixin2.getSerialNumber() << endl;\n", - "}\n", - "/* Output:\n", - "test string 1 1452987605 1\n", - "test string 2 1452987605 2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `main()` 中, **mixin1** 和 **mixin2** 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么的轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "TimeStamped> mixin1,mixin2;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "遗憾的是,Java 泛型不允许这样。擦除会忘记基类类型,因此\n", - "\n", - "> 泛型类不能直接继承自一个泛型参数\n", - "\n", - "这突显了许多我在 Java 语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您会发现自己做不到。\n", - "\n", - "### 与接口混合\n", - "\n", - "一种更常见的推荐解决方案是使用接口来产生混型效果,就像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Mixins.java\n", - "\n", - "import java.util.*;\n", - "\n", - "interface TimeStamped { long getStamp(); }\n", - "\n", - "class TimeStampedImp implements TimeStamped {\n", - " private final long timeStamp;\n", - " TimeStampedImp() {\n", - " timeStamp = new Date().getTime();\n", - " }\n", - " @Override\n", - " public long getStamp() { return timeStamp; }\n", - "}\n", - "\n", - "interface SerialNumbered { long getSerialNumber(); }\n", - "\n", - "class SerialNumberedImp implements SerialNumbered {\n", - " private static long counter = 1;\n", - " private final long serialNumber = counter++;\n", - " @Override\n", - " public long getSerialNumber() { return serialNumber; }\n", - "}\n", - "\n", - "interface Basic {\n", - " void set(String val);\n", - " String get();\n", - "}\n", - "\n", - "class BasicImp implements Basic {\n", - " private String value;\n", - " @Override\n", - " public void set(String val) { value = val; }\n", - " @Override\n", - " public String get() { return value; }\n", - "}\n", - "\n", - "class Mixin extends BasicImp\n", - "implements TimeStamped, SerialNumbered {\n", - " private TimeStamped timeStamp = new TimeStampedImp();\n", - " private SerialNumbered serialNumber =\n", - " new SerialNumberedImp();\n", - " @Override\n", - " public long getStamp() {\n", - " return timeStamp.getStamp();\n", - " }\n", - " @Override\n", - " public long getSerialNumber() {\n", - " return serialNumber.getSerialNumber();\n", - " }\n", - "}\n", - "\n", - "public class Mixins {\n", - " public static void main(String[] args) {\n", - " Mixin mixin1 = new Mixin(), mixin2 = new Mixin();\n", - " mixin1.set(\"test string 1\");\n", - " mixin2.set(\"test string 2\");\n", - " System.out.println(mixin1.get() + \" \" +\n", - " mixin1.getStamp() + \" \" + mixin1.getSerialNumber());\n", - " System.out.println(mixin2.get() + \" \" +\n", - " mixin2.getStamp() + \" \" + mixin2.getSerialNumber());\n", - " }\n", - "}\n", - "/* Output:\n", - "test string 1 1494331663026 1\n", - "test string 2 1494331663027 2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。\n", - "\n", - "### 使用装饰器模式\n", - "\n", - "当你观察混型的使用方式时,就会发现混型概念好像与*装饰器*设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。\n", - "装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。\n", - "装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。\n", - "前面的示例可以被改写为使用装饰器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/decorator/Decoration.java\n", - "\n", - "// {java generics.decorator.Decoration}\n", - "package generics.decorator;\n", - "import java.util.*;\n", - "\n", - "class Basic {\n", - " private String value;\n", - " public void set(String val) { value = val; }\n", - " public String get() { return value; }\n", - "}\n", - "\n", - "class Decorator extends Basic {\n", - " protected Basic basic;\n", - " Decorator(Basic basic) { this.basic = basic; }\n", - " @Override\n", - " public void set(String val) { basic.set(val); }\n", - " @Override\n", - " public String get() { return basic.get(); }\n", - "}\n", - "\n", - "class TimeStamped extends Decorator {\n", - " private final long timeStamp;\n", - " TimeStamped(Basic basic) {\n", - " super(basic);\n", - " timeStamp = new Date().getTime();\n", - " }\n", - " public long getStamp() { return timeStamp; }\n", - "}\n", - "\n", - "class SerialNumbered extends Decorator {\n", - " private static long counter = 1;\n", - " private final long serialNumber = counter++;\n", - " SerialNumbered(Basic basic) { super(basic); }\n", - " public long getSerialNumber() { return serialNumber; }\n", - "}\n", - "\n", - "public class Decoration {\n", - " public static void main(String[] args) {\n", - " TimeStamped t = new TimeStamped(new Basic());\n", - " TimeStamped t2 = new TimeStamped(\n", - " new SerialNumbered(new Basic()));\n", - " //- t2.getSerialNumber(); // Not available\n", - " SerialNumbered s = new SerialNumbered(new Basic());\n", - " SerialNumbered s2 = new SerialNumbered(\n", - " new TimeStamped(new Basic()));\n", - " //- s2.getStamp(); // Not available\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "产生自泛型的类包含所有感兴趣的方法,但是由使用装饰器所产生的对象类型是最后被装饰的类型。也就是说,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,而混型的类型是所有被混合到一起的类型。因此对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。\n", - "\n", - "### 与动态代理混合\n", - "\n", - "可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看 [类型信息](book/19-Type-Information.md) 一章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。\n", - "由于动态代理的限制,每个被混入的类都必须是某个接口的实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/DynamicProxyMixin.java\n", - "\n", - "import java.lang.reflect.*;\n", - "import java.util.*;\n", - "import onjava.*;\n", - "import static onjava.Tuple.*;\n", - "\n", - "class MixinProxy implements InvocationHandler {\n", - " Map delegatesByMethod;\n", - " @SuppressWarnings(\"unchecked\")\n", - " MixinProxy(Tuple2>... pairs) {\n", - " delegatesByMethod = new HashMap<>();\n", - " for(Tuple2> pair : pairs) {\n", - " for(Method method : pair.a2.getMethods()) {\n", - " String methodName = method.getName();\n", - " // The first interface in the map\n", - " // implements the method.\n", - " if(!delegatesByMethod.containsKey(methodName))\n", - " delegatesByMethod.put(methodName, pair.a1);\n", - " }\n", - " }\n", - " }\n", - " @Override\n", - " public Object invoke(Object proxy, Method method,\n", - " Object[] args) throws Throwable {\n", - " String methodName = method.getName();\n", - " Object delegate = delegatesByMethod.get(methodName);\n", - " return method.invoke(delegate, args);\n", - " }\n", - " \n", - " @SuppressWarnings(\"unchecked\")\n", - " public static Object newInstance(Tuple2... pairs) {\n", - " Class[] interfaces = new Class[pairs.length];\n", - " for(int i = 0; i < pairs.length; i++) {\n", - " interfaces[i] = (Class)pairs[i].a2;\n", - " }\n", - " ClassLoader cl = pairs[0].a1.getClass().getClassLoader();\n", - " return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));\n", - " }\n", - "}\n", - "\n", - "public class DynamicProxyMixin {\n", - " public static void main(String[] args) {\n", - " Object mixin = MixinProxy.newInstance(\n", - " tuple(new BasicImp(), Basic.class),\n", - " tuple(new TimeStampedImp(), TimeStamped.class),\n", - " tuple(new SerialNumberedImp(), SerialNumbered.class));\n", - " Basic b = (Basic)mixin;\n", - " TimeStamped t = (TimeStamped)mixin;\n", - " SerialNumbered s = (SerialNumbered)mixin;\n", - " b.set(\"Hello\");\n", - " System.out.println(b.get());\n", - " System.out.println(t.getStamp());\n", - " System.out.println(s.getSerialNumber());\n", - " }\n", - "}\n", - "/* Output:\n", - "Hello\n", - "1494331653339\n", - "1\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。\n", - "为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。\n", - "\n", - "\n", - "\n", - "## 潜在类型机制\n", - "\n", - "在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。\n", - "\n", - "Java 泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动装箱机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。\n", - "\n", - "还是正如你所见到的,当要在泛型类型上执行操作(即调用 **Object** 方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。\n", - "\n", - "某些编程语言提供的一种解决方案称为*潜在类型机制*或*结构化类型机制*,而更古怪的术语称为*鸭子类型机制*,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。\n", - "\n", - "泛型代码典型地只能在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 `speak()` 和 `sit()` 即可。”由于不要求具体类型,因此代码就可以更加泛化。\n", - "\n", - "潜在类型机制是一种代码组织和复用机制。有了它,编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。\n", - "\n", - "支持潜在类型机制的语言包括 Python(可以从 www.Python.org 免费下载)、C++、Ruby、SmallTalk 和 Go。Python 是动态类型语言(几乎所有的类型检查都发生在运行时),而 C++ 和 Go 是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。\n", - "\n", - "### pyhton 中的潜在类型\n", - "\n", - "如果我们将上面的描述用 Python 来表示,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# generics/DogsAndRobots.py\n", - "\n", - "class Dog:\n", - " def speak(self):\n", - " print(\"Arf!\")\n", - " def sit(self):\n", - " print(\"Sitting\")\n", - " def reproduce(self):\n", - " pass\n", - "\n", - "class Robot:\n", - " def speak(self):\n", - " print(\"Click!\")\n", - " def sit(self):\n", - " print(\"Clank!\")\n", - " def oilChange(self):\n", - " pass\n", - "\n", - "def perform(anything):\n", - " anything.speak()\n", - " anything.sit()\n", - "\n", - "a = Dog()\n", - "b = Robot()\n", - "perform(a)\n", - "perform(b)\n", - "\n", - "output = \"\"\"\n", - "Arf!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "\"\"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“**#**” 表示注释到行尾,就像Java中的 “ **//** ”。类的方法需要显式地指定 **this** 引用的等价物作为第一个参数,按惯例成为 **self** 。构造器调用不要求任何类型的“ **new** ”关键字,并且 Python 允许普通(非成员)函数,就像 `perform()` 所表明的那样。注意,在 `perform(anything)` 中,没有任何针对 **anything** 的类型,**anything** 只是一个标识符,它必须能够执行 `perform()` 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。`perform()` 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 `speak()` 和 `sit()` 方法。如果传递给 `perform()` 的对象不支持这些操作,那么将会得到运行时异常。\n", - "\n", - "输出规定使用三重引号创建带有内嵌换行符的字符串。\n", - "\n", - "### C++ 中的潜在类型\n", - "\n", - "我们可以用 C++ 产生相同的效果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/DogsAndRobots.cpp\n", - "\n", - "#include \n", - "using namespace std;\n", - "\n", - "class Dog {\n", - "public:\n", - " void speak() { cout << \"Arf!\" << endl; }\n", - " void sit() { cout << \"Sitting\" << endl; }\n", - " void reproduce() {}\n", - "};\n", - "\n", - "class Robot {\n", - "public:\n", - " void speak() { cout << \"Click!\" << endl; }\n", - " void sit() { cout << \"Clank!\" << endl; }\n", - " void oilChange() {}\n", - "};\n", - "\n", - "template void perform(T anything) {\n", - " anything.speak();\n", - " anything.sit();\n", - "}\n", - "\n", - "int main() {\n", - " Dog d;\n", - " Robot r;\n", - " perform(d);\n", - " perform(r);\n", - "}\n", - "/* Output:\n", - "Arf!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。\n", - "C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。\n", - "\n", - "### Go 中的潜在类型\n", - "\n", - "这里用 Go 语言编写相同的程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "go" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/dogsandrobots.go\n", - "\n", - "package main\n", - "import \"fmt\"\n", - "\n", - "type Dog struct {}\n", - "func (this Dog) speak() { fmt.Printf(\"Arf!\\n\")}\n", - "func (this Dog) sit() { fmt.Printf(\"Sitting\\n\")}\n", - "func (this Dog) reproduce() {}\n", - "\n", - "type Robot struct {}\n", - "func (this Robot) speak() { fmt.Printf(\"Click!\\n\") }\n", - "func (this Robot) sit() { fmt.Printf(\"Clank!\\n\") }\n", - "func (this Robot) oilChange() {}\n", - "\n", - "func perform(speaker interface { speak(); sit() }) {\n", - " speaker.speak();\n", - " speaker.sit();\n", - "}\n", - "\n", - "func main() {\n", - " perform(Dog{})\n", - " perform(Robot{})\n", - "}\n", - "/* Output:\n", - "Arf!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Go 没有 **class** 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 **struct** ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 **func** 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 **this** 来提醒您,就像在 C ++ 或 Java 中的 **this** 一样。 然后,在Go中像这样定义其余的函数。\n", - "\n", - "Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要原因。 但是,Go 的组成很简单。\n", - "\n", - "`perform()` 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 `speak()` 和 `sit()` 方法即可。 该接口在此处匿名定义,内联,如 `perform()` 的参数列表所示。\n", - "\n", - "`main()` 证明 `perform()` 确实对其参数的确切类型不在乎,只要可以在该参数上调用 `talk()` 和 `sit()` 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。\n", - "\n", - "语法 **Dog {}** 和 **Robot {}** 创建匿名的 **Dog** 和 **Robot** 结构。\n", - "\n", - "### java中的直接潜在类型\n", - "\n", - "因为泛型是在这场竞赛的后期才添加到 Java 中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用擦除来实现 Java 泛型的实现有时称为第二类泛型类型)例如,在 Java 8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Performs.java\n", - "\n", - "public interface Performs {\n", - " void speak();\n", - " void sit();\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/DogsAndRobots.java\n", - "// No (direct) latent typing in Java\n", - "import typeinfo.pets.*;\n", - "\n", - "class PerformingDog extends Dog implements Performs {\n", - " @Override\n", - " public void speak() { System.out.println(\"Woof!\"); }\n", - " @Override\n", - " public void sit() { System.out.println(\"Sitting\"); }\n", - " public void reproduce() {}\n", - "}\n", - "\n", - "class Robot implements Performs {\n", - " public void speak() { System.out.println(\"Click!\"); }\n", - " public void sit() { System.out.println(\"Clank!\"); }\n", - " public void oilChange() {}\n", - "}\n", - "\n", - "class Communicate {\n", - " public static \n", - " void perform(T performer) {\n", - " performer.speak();\n", - " performer.sit();\n", - " }\n", - "}\n", - "\n", - "public class DogsAndRobots {\n", - " public static void main(String[] args) {\n", - " Communicate.perform(new PerformingDog());\n", - " Communicate.perform(new Robot());\n", - " }\n", - "}\n", - "/* Output:\n", - "Woof!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是要注意,`perform()` 不需要使用泛型来工作,它可以被简单地指定为接受一个 **Performs** 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SimpleDogsAndRobots.java\n", - "// Removing the generic; code still works\n", - "\n", - "class CommunicateSimply {\n", - " static void perform(Performs performer) {\n", - " performer.speak();\n", - " performer.sit();\n", - " }\n", - "}\n", - "\n", - "public class SimpleDogsAndRobots {\n", - " public static void main(String[] args) {\n", - " CommunicateSimply.perform(new PerformingDog());\n", - " CommunicateSimply.perform(new Robot());\n", - " }\n", - "}\n", - "/* Output:\n", - "Woof!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在本例中,泛型不是必需的,因为这些类已经被强制要求实现 **Performs** 接口。\n", - "\n", - "\n", - "\n", - "## 对缺乏潜在类型机制的补偿\n", - "\n", - "尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。\n", - "\n", - "### 反射\n", - "\n", - "可以使用的一种方式是反射,下面的 `perform()` 方法就是用了潜在类型机制:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/LatentReflection.java\n", - "// Using reflection for latent typing\n", - "import java.lang.reflect.*;\n", - "\n", - "// Does not implement Performs:\n", - "class Mime {\n", - " public void walkAgainstTheWind() {}\n", - " public void sit() {\n", - " System.out.println(\"Pretending to sit\");\n", - " }\n", - " public void pushInvisibleWalls() {}\n", - " @Override\n", - " public String toString() { return \"Mime\"; }\n", - "}\n", - "\n", - "// Does not implement Performs:\n", - "class SmartDog {\n", - " public void speak() { System.out.println(\"Woof!\"); }\n", - " public void sit() { System.out.println(\"Sitting\"); }\n", - " public void reproduce() {}\n", - "}\n", - "\n", - "class CommunicateReflectively {\n", - " public static void perform(Object speaker) {\n", - " Class spkr = speaker.getClass();\n", - " try {\n", - " try {\n", - " Method speak = spkr.getMethod(\"speak\");\n", - " speak.invoke(speaker);\n", - " } catch(NoSuchMethodException e) {\n", - " System.out.println(speaker + \" cannot speak\");\n", - " }\n", - " try {\n", - " Method sit = spkr.getMethod(\"sit\");\n", - " sit.invoke(speaker);\n", - " } catch(NoSuchMethodException e) {\n", - " System.out.println(speaker + \" cannot sit\");\n", - " }\n", - " } catch(SecurityException |\n", - " IllegalAccessException |\n", - " IllegalArgumentException |\n", - " InvocationTargetException e) {\n", - " throw new RuntimeException(speaker.toString(), e);\n", - " }\n", - " }\n", - "}\n", - "\n", - "public class LatentReflection {\n", - " public static void main(String[] args) {\n", - " CommunicateReflectively.perform(new SmartDog());\n", - " CommunicateReflectively.perform(new Robot());\n", - " CommunicateReflectively.perform(new Mime());\n", - " }\n", - "}\n", - "/* Output:\n", - "Woof!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "Mime cannot speak\n", - "Pretending to sit\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上例中,这些类完全是彼此分离的,没有任何公共基类(除了 **Object** )或接口。通过反射, `CommunicateReflectively.perform()` 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 **Mime** 只具有一个必需的方法这一事实,并能够部分实现其目标。\n", - "\n", - "### 将一个方法应用于序列\n", - "\n", - "反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗?\n", - "\n", - "让我们看一个说明这个问题的示例。假设想要创建一个 `apply()` 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢?\n", - "\n", - "最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Apply.java\n", - "\n", - "import java.lang.reflect.*;\n", - "import java.util.*;\n", - "\n", - "public class Apply {\n", - " public static >\n", - " void apply(S seq, Method f, Object... args) {\n", - " try {\n", - " for(T t: seq)\n", - " f.invoke(t, args);\n", - " } catch(IllegalAccessException |\n", - " IllegalArgumentException |\n", - " InvocationTargetException e) {\n", - " // Failures are programmer errors\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **Apply.java** 中,异常被转换为 **RuntimeException** ,因为没有多少办法可以从这种异常中恢复——在这种情况下,它们实际上代表着程序员的错误。\n", - "\n", - "为什么我们不只使用 Java 8 方法参考(稍后显示)而不是反射方法 **f** ? 注意,`invoke()` 和 `apply()` 的优点是它们可以接受任意数量的参数。 在某些情况下,灵活性可能至关重要。\n", - "\n", - "为了测试 **Apply** ,我们首先创建一个 **Shape** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Shape.java\n", - "\n", - "public class Shape {\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName() + \" \" + id;\n", - " }\n", - " public void rotate() {\n", - " System.out.println(this + \" rotate\");\n", - " }\n", - " public void resize(int newSize) {\n", - " System.out.println(this + \" resize \" + newSize);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "被一个子类 **Square** 继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/Square.java\n", - "\n", - "public class Square extends Shape {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过这些,我们可以测试 **Apply**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ApplyTest.java\n", - "\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import onjava.*;\n", - "\n", - "public class ApplyTest {\n", - " public static\n", - " void main(String[] args) throws Exception {\n", - " List shapes =\n", - " Suppliers.create(ArrayList::new, Shape::new, 3);\n", - " Apply.apply(shapes, Shape.class.getMethod(\"rotate\"));\n", - " Apply.apply(shapes, Shape.class.getMethod(\"resize\", int.class), 7);\n", - "\n", - " List squares =\n", - " Suppliers.create(ArrayList::new, Square::new, 3);\n", - " Apply.apply(squares, Shape.class.getMethod(\"rotate\"));\n", - " Apply.apply(squares, Shape.class.getMethod(\"resize\", int.class), 7);\n", - "\n", - " Apply.apply(new FilledList<>(Shape::new, 3),\n", - " Shape.class.getMethod(\"rotate\"));\n", - " Apply.apply(new FilledList<>(Square::new, 3),\n", - " Shape.class.getMethod(\"rotate\"));\n", - "\n", - " SimpleQueue shapeQ = Suppliers.fill(\n", - " new SimpleQueue<>(), SimpleQueue::add,\n", - " Shape::new, 3);\n", - " Suppliers.fill(shapeQ, SimpleQueue::add,\n", - " Square::new, 3);\n", - " Apply.apply(shapeQ, Shape.class.getMethod(\"rotate\"));\n", - " }\n", - "}\n", - "/* Output:\n", - "Shape 0 rotate\n", - "Shape 1 rotate\n", - "Shape 2 rotate\n", - "Shape 0 resize 7\n", - "Shape 1 resize 7\n", - "Shape 2 resize 7\n", - "Square 3 rotate\n", - "Square 4 rotate\n", - "Square 5 rotate\n", - "Square 3 resize 7\n", - "Square 4 resize 7\n", - "Square 5 resize 7\n", - "Shape 6 rotate\n", - "Shape 7 rotate\n", - "Shape 8 rotate\n", - "Square 9 rotate\n", - "Square 10 rotate\n", - "Square 11 rotate\n", - "Shape 12 rotate\n", - "Shape 13 rotate\n", - "Shape 14 rotate\n", - "Square 15 rotate\n", - "Square 16 rotate\n", - "Square 17 rotate\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 **Apply** 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 **Iterable** 接口。正由于此, `apply()` 方法可以接受任何实现了 **Iterable** 接口的事物,包括诸如 **List** 这样的所有 **Collection** 类。但是它还可以接受其他任何事物,只要能够使这些事物是 **Iterable** 的——例如,在 `main()` 中使用下面定义的 **SimpleQueue** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/SimpleQueue.java\n", - "\n", - "// A different kind of Iterable collection\n", - "import java.util.*;\n", - "\n", - "public class SimpleQueue implements Iterable {\n", - " private LinkedList storage = new LinkedList<>();\n", - " public void add(T t) { storage.offer(t); }\n", - " public T get() { return storage.poll(); }\n", - " @Override\n", - " public Iterator iterator() {\n", - " return storage.iterator();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。\n", - "\n", - "几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 **ApplyTest.java** 进行了重写,以利用 Java 8 的流和函数工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/ApplyFunctional.java\n", - "\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "import onjava.*;\n", - "\n", - "public class ApplyFunctional {\n", - " public static void main(String[] args) {\n", - " Stream.of(\n", - " Stream.generate(Shape::new).limit(2),\n", - " Stream.generate(Square::new).limit(2))\n", - " .flatMap(c -> c) // flatten into one stream\n", - " .peek(Shape::rotate)\n", - " .forEach(s -> s.resize(7));\n", - "\n", - " new FilledList<>(Shape::new, 2)\n", - " .forEach(Shape::rotate);\n", - " new FilledList<>(Square::new, 2)\n", - " .forEach(Shape::rotate);\n", - "\n", - " SimpleQueue shapeQ = Suppliers.fill(\n", - " new SimpleQueue<>(), SimpleQueue::add,\n", - " Shape::new, 2);\n", - " Suppliers.fill(shapeQ, SimpleQueue::add,\n", - " Square::new, 2);\n", - " shapeQ.forEach(Shape::rotate);\n", - " }\n", - "}\n", - "/* Output:\n", - "Shape 0 rotate\n", - "Shape 0 resize 7\n", - "Shape 1 rotate\n", - "Shape 1 resize 7\n", - "Square 2 rotate\n", - "Square 2 resize 7\n", - "Square 3 rotate\n", - "Square 3 resize 7\n", - "Shape 4 rotate\n", - "Shape 5 rotate\n", - "Square 6 rotate\n", - "Square 7 rotate\n", - "Shape 8 rotate\n", - "Shape 9 rotate\n", - "Square 10 rotate\n", - "Square 11 rotate\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于使用 Java 8,因此不需要 `Apply.apply()` 。\n", - "\n", - "我们首先生成两个 **Stream** : 一个是 **Shape** ,一个是 **Square** ,并将它们展平为单个流。 尽管 Java 缺少功能语言中经常出现的 `flatten()` ,但是我们可以使用 `flatMap(c-> c)` 产生相同的结果,后者使用身份映射将操作简化为“ **flatten** ”。\n", - "\n", - "我们使用 `peek()` 当做对 `rotate()` 的调用,因为 `peek()` 执行一个操作(此处是出于副作用),并在未更改的情况下传递对象。\n", - "\n", - "注意,使用 **FilledList** 和 **shapeQ** 调用 `forEach()` 比 `Apply.apply()` 代码整洁得多。 在代码简单性和可读性方面,结果比以前的方法好得多。 并且,现在也不可能从 `main()` 引发异常。\n", - "\n", - "\n", - "\n", - "## Java8 中的辅助潜在类型\n", - "\n", - "先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为 Java 最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。\n", - "\n", - "我在其他地方从没遇过这种技术,因此我将其称为辅助潜在类型。\n", - "\n", - "我们将重写 **DogsAndRobots.java** 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 **A**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/DogsAndRobotMethodReferences.java\n", - "\n", - "// \"Assisted Latent Typing\"\n", - "import typeinfo.pets.*;\n", - "import java.util.function.*;\n", - "\n", - "class PerformingDogA extends Dog {\n", - " public void speak() { System.out.println(\"Woof!\"); }\n", - " public void sit() { System.out.println(\"Sitting\"); }\n", - " public void reproduce() {}\n", - "}\n", - "\n", - "class RobotA {\n", - " public void speak() { System.out.println(\"Click!\"); }\n", - " public void sit() { System.out.println(\"Clank!\"); }\n", - " public void oilChange() {}\n", - "}\n", - "\n", - "class CommunicateA {\n", - " public static

void perform(P performer,\n", - " Consumer

action1, Consumer

action2) {\n", - " action1.accept(performer);\n", - " action2.accept(performer);\n", - " }\n", - "}\n", - "\n", - "public class DogsAndRobotMethodReferences {\n", - " public static void main(String[] args) {\n", - " CommunicateA.perform(new PerformingDogA(),\n", - " PerformingDogA::speak, PerformingDogA::sit);\n", - " CommunicateA.perform(new RobotA(),\n", - " RobotA::speak, RobotA::sit);\n", - " CommunicateA.perform(new Mime(),\n", - " Mime::walkAgainstTheWind,\n", - " Mime::pushInvisibleWalls);\n", - " }\n", - "}\n", - "/* Output:\n", - "Woof!\n", - "Sitting\n", - "Click!\n", - "Clank!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**PerformingDogA** 和 **RobotA** 与 **DogsAndRobots.java** 中的相同,不同之处在于它们不继承通用接口 **Performs** ,因此它们没有通用性。\n", - "\n", - "`CommunicateA.perform()` 在没有约束的 **P** 上生成。 只要可以使用 `Consumer

`,它在这里就可以是任何东西,这些 `Consumer

` 代表不带参数的 **P** 方法的未绑定方法引用。当您调用 **Consumer** 的 `accept()` 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于 [函数式编程](book/13-Functional-Programming.md) 一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 `CommunicateA.perform()` 。\n", - "\n", - "之所以称其为“辅助”,是因为您必须显式地为 `perform()` 提供要使用的方法引用。 它不能只按名称调用方法。\n", - "\n", - "尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 `CommunicateA.perform()` ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。\n", - "\n", - "为了证明这一点,我还从 **LatentReflection.java** 中引入了 **Mime**。\n", - "\n", - "### 使用**Suppliers**类的通用方法\n", - "\n", - "通过辅助潜在类型,我们可以定义本章其他部分中使用的 **Suppliers** 类。 此类包含使用生成器填充 **Collection** 的工具方法。 泛化这些操作很有意义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Suppliers.java\n", - "\n", - "// A utility to use with Suppliers\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class Suppliers {\n", - " // Create a collection and fill it:\n", - " public static > C\n", - " create(Supplier factory, Supplier gen, int n) {\n", - " return Stream.generate(gen)\n", - " .limit(n)\n", - " .collect(factory, C::add, C::addAll);\n", - " }\n", - " \n", - " // Fill an existing collection:\n", - " public static >\n", - " C fill(C coll, Supplier gen, int n) {\n", - " Stream.generate(gen)\n", - " .limit(n)\n", - " .forEach(coll::add);\n", - " return coll;\n", - " }\n", - " \n", - " // Use an unbound method reference to\n", - " // produce a more general method:\n", - " public static H fill(H holder,\n", - " BiConsumer adder, Supplier gen, int n) {\n", - " Stream.generate(gen)\n", - " .limit(n)\n", - " .forEach(a -> adder.accept(holder, a));\n", - " return holder;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。\n", - "\n", - "前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。\n", - "\n", - "在一个稍作模拟的测试中对 **Suppliers** 工具程序进行了测试,该仿真还使用了本章前面定义的 **RandomList** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// generics/BankTeller.java\n", - "\n", - "// A very simple bank teller simulation\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "class Customer {\n", - " private static long counter = 1;\n", - " private final long id = counter++;\n", - " @Override\n", - " public String toString() {\n", - " return \"Customer \" + id;\n", - " }\n", - "}\n", - "\n", - "class Teller {\n", - " private static long counter = 1;\n", - " private final long id = counter++;\n", - " @Override\n", - " public String toString() {\n", - " return \"Teller \" + id;\n", - " }\n", - "}\n", - "\n", - "class Bank {\n", - " private List tellers =\n", - " new ArrayList<>();\n", - " public void put(BankTeller bt) {\n", - " tellers.add(bt);\n", - " }\n", - "}\n", - "\n", - "public class BankTeller {\n", - " public static void serve(Teller t, Customer c) {\n", - " System.out.println(t + \" serves \" + c);\n", - " }\n", - " public static void main(String[] args) {\n", - " // Demonstrate create():\n", - " RandomList tellers =\n", - " Suppliers.create(\n", - " RandomList::new, Teller::new, 4);\n", - " // Demonstrate fill():\n", - " List customers = Suppliers.fill(\n", - " new ArrayList<>(), Customer::new, 12);\n", - " customers.forEach(c ->\n", - " serve(tellers.select(), c));\n", - " // Demonstrate assisted latent typing:\n", - " Bank bank = Suppliers.fill(\n", - " new Bank(), Bank::put, BankTeller::new, 3);\n", - " // Can also use second version of fill():\n", - " List customers2 = Suppliers.fill(\n", - " new ArrayList<>(),\n", - " List::add, Customer::new, 12);\n", - " }\n", - "}\n", - "/* Output:\n", - "Teller 3 serves Customer 1\n", - "Teller 2 serves Customer 2\n", - "Teller 3 serves Customer 3\n", - "Teller 1 serves Customer 4\n", - "Teller 1 serves Customer 5\n", - "Teller 3 serves Customer 6\n", - "Teller 1 serves Customer 7\n", - "Teller 2 serves Customer 8\n", - "Teller 3 serves Customer 9\n", - "Teller 3 serves Customer 10\n", - "Teller 2 serves Customer 11\n", - "Teller 4 serves Customer 12\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到 `create()` 生成一个新的 **Collection** 对象,而 `fill()` 添加到现有 **Collection** 中。第二个版本`fill()` 显示,它不仅与无关的新类型 **Bank** 一起使用,还能与 **List** 一起使用。因此,从技术上讲,`fill()` 的第一个版本在技术上不是必需的,但在使用 **Collection** 时提供了较短的语法。\n", - "\n", - "\n", - "\n", - "## 总结:类型转换真的如此之糟吗?\n", - "\n", - "自从 C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。\n", - "\n", - "这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用集合类的地方,这些类包括诸如各种 **List** 、各种 **Set** 、各种 **Map** 等你在 [集合](book/12-Collections.md) 和 [附录:集合主题](book/Appendix-Collection-Topics.md) 这两章所见。在 Java 5 之前,当你将一个对象放置到集合中时,这个对象就会被向上转型为 **Object** ,因此你会丢失类型信息。当你想要将这个对象从集合中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。我用的示例是持有 **Cat** 的 **List** (这个示例的一种使用苹果和桔子的变体在 [集合](book/12-Collections.md) 章节的开头展示过)。如果没有 Java 5 泛型版本的集合,你放到容集里和从集合中取回的都是 **Object** 。因此,我们很可能会将一个 **Dog** 放置到 **Cat** 的 **List** 中。\n", - "\n", - "但是,泛型出现之前的 Java 并不会让你误用放入到集合中的对象。如果将一个 **Dog** 扔到 **Cat** 的集合中,并且试图将这个集合中的所有东西都当作 **Cat** 处理,那么当你从这个 **Cat** 集合中取回那个 **Dog** 引用,并试图将其转型为 **Cat** 时,就会得到一个 **RuntimeException** 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。\n", - "\n", - "在本书以前的版本中,我曾经说过:\n", - "\n", - "> 这不止令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向集合中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了集合中,那么必须发现这个不良插入到底是在何处发生的。\n", - ">\n", - "\n", - "但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个称为 **files** 的 list 示例,它包含 **String** 对象。在这个示例中,向 **files** 中添加一个 **File** 对象看起来相当自然,因此这个对象的名字可能叫 **fileNames** 更好。无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的集合,例如 **cats** ,因为它们可以向试图添加非 **Cat** 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。\n", - "\n", - "有一位作者甚至断言,这样的缺陷将“*潜伏数年*”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在 [多线程编程](book/24-Concurrent-Programming.md) 章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗?\n", - "我相信被称为*泛型*的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的集合。类型安全的集合是能够创建更通用代码这一能力所带来的副作用。\n", - "因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解这些代码的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。\n", - "\n", - "还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 **Map** ,在特定的方法 `containsKey(Object key) `和 `get(Object key)` 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 **Object** ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 **map** 中,键的类型总是在编译期检查的。\n", - "\n", - "有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。\n", - "某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语言将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。\n", - "\n", - "## 进阶阅读\n", - "\n", - "泛型的入门文档是 《Generics in the Java Programming Language》,作者是 Gilad Bracha,可以从 http://java.oracle.com 获取。\n", - "\n", - "Angelika Langer 的《Java Generics FAQs》是一份非常有帮助的资料,可以从 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html 获取。\n", - "\n", - "你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。\n", - "\n", - "Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到:http://www.infoq.com/articles/neal-gafter-on-java。\n", - "\n", - "[^1]: 在编写本章期间,Angelika Langer的 Java 泛型常见问题解答以及她的其他著作(与Klaus Kreft一起)是非常宝贵的。\n", - "[^2]: [http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html](http://gafter.blogspot.com/2004/09/puzzling-through-erasureanswer.html)\n", - "[^3]: 参见本章章末引文。\n", - "[^4]: 注意,一些编程环境,如 Eclipse 和 IntelliJ IDEA,将会自动生成委托代码。\n", - "[^5]: 因为可以使用转型,有效地禁止了类型系统,一些人就认为 C++ 是弱类型,但这太极端了。一种可能更好的说法是 C++ 是有一道暗门的强类型语言。\n", - "[^6]: 我再次从 Brian Goetz 那获得帮助。\n", - "\n", - "\n", - "\n", - "

" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/21-Arrays.ipynb b/jupyter/21-Arrays.ipynb deleted file mode 100644 index 1ac36a3a..00000000 --- a/jupyter/21-Arrays.ipynb +++ /dev/null @@ -1,3498 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第二十一章 数组\n", - "\n", - "\n", - "> 在 [初始化和清理](book/06-Housekeeping.md) 一章的最后,你已经学过如何定义和初始化一个数组。\n", - "\n", - "简单来看,数组需要你去创建和初始化,你可以通过下标对数组元素进行访问,数组的大小不会改变。大多数时候你只需要知道这些,但有时候你必须在数组上进行更复杂的操作,你也可能需要在数组和更加灵活的 **集合** (Collection)之间做出评估。因此本章我们将对数组进行更加深入的分析。\n", - "\n", - "**注意:** 随着 Java Collection 和 Stream 类中高级功能的不断增加,日常编程中使用数组的需求也在变少,所以你暂且可以放心地略读甚至跳过这一章。但是,即使你自己避免使用数组,也总会有需要阅读别人数组代码的那一天。那时候,本章依然在这里等着你来翻阅。\n", - "\n", - "\n", - "\n", - "## 数组特性\n", - "\n", - "明明还有很多其他的办法来保存对象,那么是什么令数组如此特别?\n", - "\n", - "将数组和其他类型的集合区分开来的原因有三:效率,类型,保存基本数据类型的能力。在 Java 中,使用数组存储和随机访问对象引用序列是非常高效的。数组是简单的线性序列,这使得对元素的访问变得非常快。然而这种高速也是有代价的,代价就是数组对象的大小是固定的,且在该数组的生存期内不能更改。\n", - "\n", - "速度通常并不是问题,如果有问题,你保存和检索对象的方式也很少是罪魁祸首。你应该总是从 **ArrayList** (来自 [集合](book/12-Collections.md ))开始,它将数组封装起来。必要时,它会自动分配更多的数组空间,创建新数组,并将旧数组中的引用移动到新数组。这种灵活性需要开销,所以一个 **ArrayList** 的效率不如数组。在极少的情况下效率会成为问题,所以这种时候你可以直接使用数组。\n", - "\n", - "\n", - "数组和集合(Collections)都不能滥用。不管你使用数组还是集合,如果你越界,你都会得到一个 **RuntimeException** 的异常提醒,这表明你的程序中存在错误。\n", - "\n", - "\n", - "在泛型前,其他的集合类以一种宽泛的方式处理对象(就好像它们没有特定类型一样)。事实上,这些集合类把保存对象的类型默认为 **Object**,也就是 Java 中所有类的基类。而数组是优于 **预泛型** (pre-generic)集合类的,因为你创建一个数组就可以保存特定类型的数据。这意味着你获得了一个编译时的类型检查,而这可以防止你插入错误的数据类型,或者搞错你正在提取的数据类型。\n", - "\n", - "\n", - "当然,不管在编译时还是运行时,Java都会阻止你犯向对象发送不正确消息的错误。然而不管怎样,使用数组都不会有更大的风险。比较好的地方在于,如果编译器报错,最终的用户更容易理解抛出异常的含义。\n", - "\n", - "\n", - "一个数组可以保存基本数据类型,而一个预泛型的集合不可以。然而对于泛型而言,集合可以指定和检查他们保存对象的类型,而通过 **自动装箱** (autoboxing)机制,集合表现地就像它们可以保存基本数据类型一样,因为这种转换是自动的。\n", - "\n", - "下面给出一例用于比较数组和泛型集合:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/CollectionComparison.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "import java.util.*;\n", - "import onjava.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "class BerylliumSphere {\n", - " private static long counter;\n", - " private final long id = counter++;\n", - " @Override\n", - " public String toString() {\n", - " return \"Sphere \" + id;\n", - " }\n", - "}\n", - "\n", - "public class CollectionComparison {\n", - " public static void main(String[] args) {\n", - " BerylliumSphere[] spheres =\n", - " new BerylliumSphere[10];\n", - " for(int i = 0; i < 5; i++)\n", - " spheres[i] = new BerylliumSphere();\n", - " show(spheres);\n", - " System.out.println(spheres[4]);\n", - "\n", - " List sphereList = Suppliers.create(\n", - " ArrayList::new, BerylliumSphere::new, 5);\n", - " System.out.println(sphereList);\n", - " System.out.println(sphereList.get(4));\n", - "\n", - " int[] integers = { 0, 1, 2, 3, 4, 5 };\n", - " show(integers);\n", - " System.out.println(integers[4]);\n", - "\n", - " List intList = new ArrayList<>(\n", - " Arrays.asList(0, 1, 2, 3, 4, 5));\n", - " intList.add(97);\n", - " System.out.println(intList);\n", - " System.out.println(intList.get(4));\n", - " }\n", - "}\n", - "/* Output:\n", - "[Sphere 0, Sphere 1, Sphere 2, Sphere 3, Sphere 4,\n", - "null, null, null, null, null]\n", - "Sphere 4\n", - "[Sphere 5, Sphere 6, Sphere 7, Sphere 8, Sphere 9]\n", - "Sphere 9\n", - "[0, 1, 2, 3, 4, 5]\n", - "4\n", - "[0, 1, 2, 3, 4, 5, 97]\n", - "4\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Suppliers.create()** 方法在[泛型](book/20-Generics.md)一章中被定义。上面两种保存对象的方式都是有类型检查的,唯一比较明显的区别就是数组使用 **[ ]** 来随机存取元素,而一个 **List** 使用诸如 `add()` 和 `get()` 等方法。数组和 **ArrayList** 之间的相似是设计者有意为之,所以在概念上,两者很容易切换。但是就像你在[集合](book/12-Collections.md)中看到的,集合的功能明显多于数组。随着 Java 自动装箱技术的出现,通过集合使用基本数据类型几乎和通过数组一样简单。数组唯一剩下的优势就是效率。然而,当你解决一个更加普遍的问题时,数组可能限制太多,这种情形下,您可以使用集合类。\n", - "\n", - "\n", - "### 用于显示数组的实用程序\n", - "\n", - "在本章中,我们处处都要显示数组。Java 提供了 **Arrays.toString()** 来将数组转换为可读字符串,然后可以在控制台上显示。然而这种方式视觉上噪音太大,所以我们创建一个小的库来完成这项工作。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/ArrayShow.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "package onjava;\n", - "import java.util.*;\n", - "\n", - "public interface ArrayShow {\n", - " static void show(Object[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(boolean[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(byte[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(char[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(short[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(int[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(long[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(float[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " static void show(double[] a) {\n", - " System.out.println(Arrays.toString(a));\n", - " }\n", - " // Start with a description:\n", - " static void show(String info, Object[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, boolean[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, byte[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, char[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, short[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, int[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, long[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, float[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - " static void show(String info, double[] a) {\n", - " System.out.print(info + \": \");\n", - " show(a);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第一个方法适用于对象数组,包括那些包装基本数据类型的数组。所有的方法重载对于不同的数据类型是必要的。\n", - "\n", - "第二组重载方法可以让你显示带有信息 **字符串** 前缀的数组。\n", - "\n", - "为了简单起见,你通常可以静态地导入它们。\n", - "\n", - "\n", - "\n", - "## 一等对象\n", - "\n", - "不管你使用的什么类型的数组,数组中的数据集实际上都是对堆中真正对象的引用。数组是保存指向其他对象的引用的对象,数组可以隐式地创建,作为数组初始化语法的一部分,也可以显式地创建,比如使用一个 **new** 表达式。数组对象的一部分(事实上,你唯一可以使用的方法)就是只读的 **length** 成员函数,它能告诉你数组对象中可以存储多少元素。**[ ]** 语法是你访问数组对象的唯一方式。\n", - "\n", - "下面的例子总结了初始化数组的多种方式,并且展示了如何给不同的数组对象分配数组引用。同时也可以看出对象数组和基元数组在使用上是完全相同的。唯一的不同之处就是对象数组存储的是对象的引用,而基元数组则直接存储基本数据类型的值。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ArrayOptions.java\n", - "// Initialization & re-assignment of arrays\n", - "import java.util.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class ArrayOptions {\n", - " public static void main(String[] args) {\n", - " // Arrays of objects:\n", - " BerylliumSphere[] a; // Uninitialized local\n", - " BerylliumSphere[] b = new BerylliumSphere[5];\n", - "\n", - " // The references inside the array are\n", - " // automatically initialized to null:\n", - " show(\"b\", b);\n", - " BerylliumSphere[] c = new BerylliumSphere[4];\n", - " for(int i = 0; i < c.length; i++)\n", - " if(c[i] == null) // Can test for null reference\n", - " c[i] = new BerylliumSphere();\n", - "\n", - " // Aggregate initialization:\n", - " BerylliumSphere[] d = {\n", - " new BerylliumSphere(),\n", - " new BerylliumSphere(),\n", - " new BerylliumSphere()\n", - " };\n", - "\n", - " // Dynamic aggregate initialization:\n", - " a = new BerylliumSphere[]{\n", - " new BerylliumSphere(), new BerylliumSphere(),\n", - " };\n", - " // (Trailing comma is optional)\n", - "\n", - " System.out.println(\"a.length = \" + a.length);\n", - " System.out.println(\"b.length = \" + b.length);\n", - " System.out.println(\"c.length = \" + c.length);\n", - " System.out.println(\"d.length = \" + d.length);\n", - " a = d;\n", - " System.out.println(\"a.length = \" + a.length);\n", - "\n", - " // Arrays of primitives:\n", - " int[] e; // Null reference\n", - " int[] f = new int[5];\n", - "\n", - " // The primitives inside the array are\n", - " // automatically initialized to zero:\n", - " show(\"f\", f);\n", - " int[] g = new int[4];\n", - " for(int i = 0; i < g.length; i++)\n", - " g[i] = i*i;\n", - " int[] h = { 11, 47, 93 };\n", - "\n", - " // Compile error: variable e not initialized:\n", - " //- System.out.println(\"e.length = \" + e.length);\n", - " System.out.println(\"f.length = \" + f.length);\n", - " System.out.println(\"g.length = \" + g.length);\n", - " System.out.println(\"h.length = \" + h.length);\n", - " e = h;\n", - " System.out.println(\"e.length = \" + e.length);\n", - " e = new int[]{ 1, 2 };\n", - " System.out.println(\"e.length = \" + e.length);\n", - " }\n", - "}\n", - "/* Output:\n", - "b: [null, null, null, null, null]\n", - "a.length = 2\n", - "b.length = 5\n", - "c.length = 4\n", - "d.length = 3\n", - "a.length = 3\n", - "f: [0, 0, 0, 0, 0]\n", - "f.length = 5\n", - "g.length = 4\n", - "h.length = 3\n", - "e.length = 3\n", - "e.length = 2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "数组 **a** 是一个未初始化的本地变量,编译器不会允许你使用这个引用直到你正确地对其进行初始化。数组 **b** 被初始化成一系列指向 **BerylliumSphere** 对象的引用,但是并没有真正的 **BerylliumSphere** 对象被存储在数组中。尽管你仍然可以获得这个数组的大小,因为 **b** 指向合法对象。这带来了一个小问题:你无法找出到底有多少元素存储在数组中,因为 **length** 只能告诉你数组可以存储多少元素;这就是说,数组对象的大小并不是真正存储在数组中对象的个数。然而,当你创建一个数组对象,其引用将自动初始化为 **null**,因此你可以通过检查特定数组元素中的引用是否为 **null** 来判断其中是否有对象。基元数组也有类似的机制,比如自动将数值类型初始化为 **0**,char 型初始化为 **(char)0**,布尔类型初始化为 **false**。\n", - "\n", - "数组 **c** 展示了创建数组对象后给数组中各元素分配 **BerylliumSphere** 对象。数组 **d** 展示了创建数组对象的聚合初始化语法(隐式地使用 **new** 在堆中创建对象,就像 **c** 一样)并且初始化成 **BeryliumSphere** 对象,这一切都在一条语句中完成。\n", - "\n", - "下一个数组初始化可以被看做是一个“动态聚合初始化”。 **d** 使用的聚合初始化必须在 **d** 定义处使用,但是使用第二种语法,你可以在任何地方创建和初始化数组对象。例如,假设 **hide()** 是一个需要使用一系列的 **BeryliumSphere**对象。你可以这样调用它:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "hide(d);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你也可以动态地创建你用作参数传递的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "hide(new BerylliumSphere[]{\n", - " new BerlliumSphere(),\n", - " new BerlliumSphere()\n", - "});" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "很多情况下这种语法写代码更加方便。\n", - "\n", - "表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "a = d;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "显示了你如何获取指向一个数组对象的引用并将其分配给另一个数组对象。就像你可以处理其他类型的对象引用。现在 **a** 和 **d** 都指向了堆中的同一个数组对象。\n", - "\n", - "**ArrayOptions.java** 的第二部分展示了基元数组的语法就像对象数组一样,除了基元数组直接保存基本数据类型的值。\n", - "\n", - "\n", - "\n", - "## 返回数组\n", - "\n", - "假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对 C++/C 这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。\n", - "\n", - "而在 Java 中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。\n", - "\n", - "下面,我们返回一个 **字符串** 数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/IceCreamFlavors.java\n", - "// Returning arrays from methods\n", - "import java.util.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class IceCreamFlavors {\n", - " private static SplittableRandom rand =\n", - " new SplittableRandom(47);\n", - " static final String[] FLAVORS = {\n", - " \"Chocolate\", \"Strawberry\", \"Vanilla Fudge Swirl\",\n", - " \"Mint Chip\", \"Mocha Almond Fudge\", \"Rum Raisin\",\n", - " \"Praline Cream\", \"Mud Pie\"\n", - " };\n", - " public static String[] flavorSet(int n) {\n", - " if(n > FLAVORS.length)\n", - " throw new IllegalArgumentException(\"Set too big\");\n", - " String[] results = new String[n];\n", - " boolean[] picked = new boolean[FLAVORS.length];\n", - " for(int i = 0; i < n; i++) {\n", - " int t;\n", - " do\n", - " t = rand.nextInt(FLAVORS.length);\n", - " while(picked[t]);\n", - " results[i] = FLAVORS[t];\n", - " picked[t] = true;\n", - " }\n", - " return results;\n", - " }\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 7; i++)\n", - " show(flavorSet(3));\n", - " }\n", - "}\n", - "/* Output:\n", - "[Praline Cream, Mint Chip, Vanilla Fudge Swirl]\n", - "[Strawberry, Vanilla Fudge Swirl, Mud Pie]\n", - "[Chocolate, Strawberry, Vanilla Fudge Swirl]\n", - "[Rum Raisin, Praline Cream, Chocolate]\n", - "[Mint Chip, Rum Raisin, Mocha Almond Fudge]\n", - "[Mocha Almond Fudge, Mud Pie, Vanilla Fudge Swirl]\n", - "[Mocha Almond Fudge, Mud Pie, Mint Chip]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**flavorset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后从数组 **FLAVORS** 中随机选择 flavors 并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者是在其他什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。\n", - "\n", - "如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/20-Generics.md) 中介绍的 **元组** 。\n", - "\n", - "注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项没被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串\n", - "\n", - "比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flavorSet()** 每一次都是按照随机顺序选择 flavors。\n", - "\n", - "一直到现在,随机数都是通过 **java.util.Random** 类生成的,这个类从 Java 1.0 就有,甚至更新过以提供 Java 8 流。现在我们可以介绍 Java 8 中的 **SplittableRandom** ,它不仅能在并行操作使用(你最终会学到),而且提供了一个高质量的随机数。这本书的剩余部分都使用 **SplittableRandom** 。\n", - "\n", - "\n", - "\n", - "## 多维数组\n", - "\n", - "要创建多维的基元数组,你要用大括号来界定数组中的向量:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/MultidimensionalPrimitiveArray.java\n", - "import java.util.*;\n", - "\n", - "public class MultidimensionalPrimitiveArray {\n", - " public static void main(String[] args) {\n", - " int[][] a = {\n", - " { 1, 2, 3, },\n", - " { 4, 5, 6, },\n", - " };\n", - " System.out.println(Arrays.deepToString(a));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[1, 2, 3], [4, 5, 6]]\n", - "*/。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个嵌套的大括号都代表了数组的一个维度。\n", - "\n", - "这个例子使用 **Arrays.deepToString()** 方法,将多维数组转换成 **String** 类型,就像输出中显示的那样。\n", - "\n", - "你也可以使用 **new** 分配数组。这是一个使用 **new** 表达式分配的三维数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ThreeDWithNew.java\n", - "import java.util.*;\n", - "\n", - "public class ThreeDWithNew {\n", - " public static void main(String[] args) {\n", - " // 3-D array with fixed length:\n", - " int[][][] a = new int[2][2][4];\n", - " System.out.println(Arrays.deepToString(a));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0,\n", - "0]]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "倘若你不对基元数组进行显式的初始化,它的值会自动初始化。而对象数组将被初始化为 **null** 。\n", - "\n", - "组成矩阵的数组中每一个向量都可以是任意长度的(这叫做不规则数组):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/RaggedArray.java\n", - "import java.util.*;\n", - "\n", - "public class RaggedArray {\n", - " static int val = 1;\n", - " public static void main(String[] args) {\n", - " SplittableRandom rand = new SplittableRandom(47);\n", - " // 3-D array with varied-length vectors:\n", - " int[][][] a = new int[rand.nextInt(7)][][];\n", - " for(int i = 0; i < a.length; i++) {\n", - " a[i] = new int[rand.nextInt(5)][];\n", - " for(int j = 0; j < a[i].length; j++) {\n", - " a[i][j] = new int[rand.nextInt(5)];\n", - " Arrays.setAll(a[i][j], n -> val++); // [1]\n", - " }\n", - " }\n", - " System.out.println(Arrays.deepToString(a));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[[1], []], [[2, 3, 4, 5], [6]], [[7, 8, 9], [10, 11,\n", - "12], []]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个 **new** 在 for 循环中给数组填充了第二个元素,第三个 **new** 为数组的最后一个索引填充元素。\n", - "\n", - "* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数式接口 **IntUnaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的 lambda 表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。\n", - "\n", - "非基元的对象数组也可以定义为不规则数组。这里,我们收集了许多使用大括号的 **new** 表达式:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/MultidimensionalObjectArrays.java\n", - "import java.util.*;\n", - "\n", - "public class MultidimensionalObjectArrays {\n", - " public static void main(String[] args) {\n", - " BerylliumSphere[][] spheres = {\n", - " { new BerylliumSphere(), new BerylliumSphere() },\n", - " { new BerylliumSphere(), new BerylliumSphere(),\n", - " new BerylliumSphere(), new BerylliumSphere() },\n", - " { new BerylliumSphere(), new BerylliumSphere(),\n", - " new BerylliumSphere(), new BerylliumSphere(),\n", - " new BerylliumSphere(), new BerylliumSphere(),\n", - " new BerylliumSphere(), new BerylliumSphere() },\n", - " };\n", - " System.out.println(Arrays.deepToString(spheres));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[Sphere 0, Sphere 1], [Sphere 2, Sphere 3, Sphere 4,\n", - "Sphere 5], [Sphere 6, Sphere 7, Sphere 8, Sphere 9,\n", - "Sphere 10, Sphere 11, Sphere 12, Sphere 13]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "数组初始化时使用自动装箱技术:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/AutoboxingArrays.java\n", - "import java.util.*;\n", - "\n", - "public class AutoboxingArrays {\n", - " public static void main(String[] args) {\n", - " Integer[][] a = { // Autoboxing:\n", - " { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },\n", - " { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 },\n", - " { 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },\n", - " { 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 },\n", - " };\n", - " System.out.println(Arrays.deepToString(a));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [21, 22, 23, 24, 25,\n", - "26, 27, 28, 29, 30], [51, 52, 53, 54, 55, 56, 57, 58,\n", - "59, 60], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "以下是如何逐个构建非基元的对象数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/AssemblingMultidimensionalArrays.java\n", - "// Creating multidimensional arrays\n", - "import java.util.*;\n", - "\n", - "public class AssemblingMultidimensionalArrays {\n", - " public static void main(String[] args) {\n", - " Integer[][] a;\n", - " a = new Integer[3][];\n", - " for(int i = 0; i < a.length; i++) {\n", - " a[i] = new Integer[3];\n", - " for(int j = 0; j < a[i].length; j++)\n", - " a[i][j] = i * j; // Autoboxing\n", - " }\n", - " System.out.println(Arrays.deepToString(a));\n", - " }\n", - "}\n", - "/* Output:\n", - "[[0, 0, 0], [0, 1, 2], [0, 2, 4]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。\n", - "\n", - "**Arrays.deepToString()** 方法同时适用于基元数组和对象数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/MultiDimWrapperArray.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Multidimensional arrays of \"wrapper\" objects\n", - "import java.util.*;\n", - "\n", - "public class MultiDimWrapperArray {\n", - " public static void main(String[] args) {\n", - " Integer[][] a1 = { // Autoboxing\n", - " { 1, 2, 3, },\n", - " { 4, 5, 6, },\n", - " };\n", - " Double[][][] a2 = { // Autoboxing\n", - " { { 1.1, 2.2 }, { 3.3, 4.4 } },\n", - " { { 5.5, 6.6 }, { 7.7, 8.8 } },\n", - " { { 9.9, 1.2 }, { 2.3, 3.4 } },\n", - " };\n", - " String[][] a3 = {\n", - " { \"The\", \"Quick\", \"Sly\", \"Fox\" },\n", - " { \"Jumped\", \"Over\" },\n", - " { \"The\", \"Lazy\", \"Brown\", \"Dog\", \"&\", \"friend\" },\n", - " };\n", - " System.out.println(\n", - " \"a1: \" + Arrays.deepToString(a1));\n", - " System.out.println(\n", - " \"a2: \" + Arrays.deepToString(a2));\n", - " System.out.println(\n", - " \"a3: \" + Arrays.deepToString(a3));\n", - " }\n", - "}\n", - "/* Output:\n", - "a1: [[1, 2, 3], [4, 5, 6]]\n", - "a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7,\n", - "8.8]], [[9.9, 1.2], [2.3, 3.4]]]\n", - "a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The,\n", - "Lazy, Brown, Dog, &, friend]]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "同样的,在 **Integer** 和 **Double** 数组中,自动装箱可为你创建包装器对象。\n", - "\n", - "\n", - "## 泛型数组\n", - "\n", - "一般来说,数组和泛型并不能很好的结合。你不能实例化参数化类型的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Peel[] peels = new Peel[10]; // Illegal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "类型擦除需要删除参数类型信息,而且数组必须知道它们所保存的确切类型,以强制保证类型安全。\n", - "\n", - "但是,可以参数化数组本身的类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ParameterizedArrayType.java\n", - "\n", - "class ClassParameter {\n", - " public T[] f(T[] arg) { return arg; }\n", - "}\n", - "\n", - "class MethodParameter {\n", - " public static T[] f(T[] arg) { return arg; }\n", - "}\n", - "\n", - "public class ParameterizedArrayType {\n", - " public static void main(String[] args) {\n", - " Integer[] ints = { 1, 2, 3, 4, 5 };\n", - " Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 };\n", - " Integer[] ints2 =\n", - " new ClassParameter().f(ints);\n", - " Double[] doubles2 =\n", - " new ClassParameter().f(doubles);\n", - " ints2 = MethodParameter.f(ints);\n", - " doubles2 = MethodParameter.f(doubles);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "比起使用参数化类,使用参数化方法很方便。您不必为应用它的每个不同类型都实例化一个带有参数的类,但是可以使它成为 **静态** 的。你不能总是选择使用参数化方法而不用参数化的类,但通常参数化方法是更好的选择。\n", - "\n", - "你不能创建泛型类型的数组,这种说法并不完全正确。是的,编译器不会让你 *实例化* 一个泛型的数组。但是,它将允许您创建对此类数组的引用。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "List[] ls;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "无可争议的,这可以通过编译。尽管不能创建包含泛型的实际数组对象,但是你可以创建一个非泛型的数组并对其进行强制类型转换:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ArrayOfGenerics.java\n", - "import java.util.*;\n", - "\n", - "public class ArrayOfGenerics {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " List[] ls;\n", - " List[] la = new List[10];\n", - " ls = (List[])la; // Unchecked cast\n", - " ls[0] = new ArrayList<>();\n", - "\n", - " //- ls[1] = new ArrayList();\n", - " // error: incompatible types: ArrayList\n", - " // cannot be converted to List\n", - " // ls[1] = new ArrayList();\n", - " // ^\n", - "\n", - " // The problem: List is a subtype of Object\n", - " Object[] objects = ls; // So assignment is OK\n", - " // Compiles and runs without complaint:\n", - " objects[1] = new ArrayList<>();\n", - "\n", - " // However, if your needs are straightforward it is\n", - " // possible to create an array of generics, albeit\n", - " // with an \"unchecked cast\" warning:\n", - " List[] spheres =\n", - " (List[])new List[10];\n", - " Arrays.setAll(spheres, n -> new ArrayList<>());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦你有了对 **List[]** 的引用 , 你会发现多了一些编译时检查。问题是数组是协变的,所以 **List[]** 也是一个 **Object[]** ,你可以用这来将 **ArrayList ** 分配进你的数组,在编译或者运行时都不会出错。\n", - "\n", - "如果你知道你不会进行向上类型转换,你的需求相对简单,那么可以创建一个泛型数组,它将提供基本的编译时类型检查。然而,一个泛型 **Collection** 实际上是一个比泛型数组更好的选择。\n", - "\n", - "一般来说,您会发现泛型在类或方法的边界上是有效的。在内部,擦除常常会使泛型不可使用。所以,就像下面的例子,不能创建泛型类型的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ArrayOfGenericType.java\n", - "\n", - "public class ArrayOfGenericType {\n", - " T[] array; // OK\n", - " @SuppressWarnings(\"unchecked\")\n", - " public ArrayOfGenericType(int size) {\n", - " // error: generic array creation:\n", - " //- array = new T[size];\n", - " array = (T[])new Object[size]; // unchecked cast\n", - " }\n", - " // error: generic array creation:\n", - " //- public U[] makeArray() { return new U[10]; }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "擦除再次从中作梗,这个例子试图创建已经擦除的类型数组,因此它们是未知的类型。你可以创建一个 **对象** 数组,然后对其进行强制类型转换,但如果没有 **@SuppressWarnings** 注释,你将会得到一个 \"unchecked\" 警告,因为数组实际上不真正支持而且将对类型 **T** 动态检查 。这就是说,如果我创建了一个 **String[]** , Java将在编译时和运行时强制执行,我只能在数组中放置字符串对象。然而,如果我创建一个 **Object[]** ,我可以把除了基元类型外的任何东西放入数组。\n", - "\n", - "\n", - "\n", - "\n", - "## Arrays的fill方法\n", - "\n", - "通常情况下,当对数组和程序进行实验时,能够很轻易地生成充满测试数据的数组是很有帮助的。 Java 标准库 **Arrays** 类包括一个普通的 **fill()** 方法,该方法将单个值复制到整个数组,或者在对象数组的情况下,将相同的引用复制到整个数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/FillingArrays.java\n", - "// Using Arrays.fill()\n", - "import java.util.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class FillingArrays {\n", - " public static void main(String[] args) {\n", - " int size = 6;\n", - " boolean[] a1 = new boolean[size];\n", - " byte[] a2 = new byte[size];\n", - " char[] a3 = new char[size];\n", - " short[] a4 = new short[size];\n", - " int[] a5 = new int[size];\n", - " long[] a6 = new long[size];\n", - " float[] a7 = new float[size];\n", - " double[] a8 = new double[size];\n", - " String[] a9 = new String[size];\n", - " Arrays.fill(a1, true);\n", - " show(\"a1\", a1);\n", - " Arrays.fill(a2, (byte)11);\n", - " show(\"a2\", a2);\n", - " Arrays.fill(a3, 'x');\n", - " show(\"a3\", a3);\n", - " Arrays.fill(a4, (short)17);\n", - " show(\"a4\", a4);\n", - " Arrays.fill(a5, 19);\n", - " show(\"a5\", a5);\n", - " Arrays.fill(a6, 23);\n", - " show(\"a6\", a6);\n", - " Arrays.fill(a7, 29);\n", - " show(\"a7\", a7);\n", - " Arrays.fill(a8, 47);\n", - " show(\"a8\", a8);\n", - " Arrays.fill(a9, \"Hello\");\n", - " show(\"a9\", a9);\n", - " // Manipulating ranges:\n", - " Arrays.fill(a9, 3, 5, \"World\");\n", - " show(\"a9\", a9);\n", - " }\n", - "}gedan\n", - "/* Output:\n", - "a1: [true, true, true, true, true, true]\n", - "a2: [11, 11, 11, 11, 11, 11]\n", - "a3: [x, x, x, x, x, x]\n", - "a4: [17, 17, 17, 17, 17, 17]\n", - "a5: [19, 19, 19, 19, 19, 19]\n", - "a6: [23, 23, 23, 23, 23, 23]\n", - "a7: [29.0, 29.0, 29.0, 29.0, 29.0, 29.0]\n", - "a8: [47.0, 47.0, 47.0, 47.0, 47.0, 47.0]\n", - "a9: [Hello, Hello, Hello, Hello, Hello, Hello]\n", - "a9: [Hello, Hello, Hello, World, World, Hello]\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你既可以填充整个数组,也可以像最后两个语句所示,填充一系列的元素。但是由于你只能使用单个值调用 **Arrays.fill()** ,因此结果并非特别有用。\n", - "\n", - "\n", - "\n", - "## Arrays的setAll方法\n", - "\n", - "在Java 8中, 在**RaggedArray.java** 中引入并在 **ArrayOfGenerics.java.Array.setAll()** 中重用。它使用一个生成器并生成不同的值,可以选择基于数组的索引元素(通过访问当前索引,生成器可以读取数组值并对其进行修改)。 **static Arrays.setAll()** 的重载签名为:\n", - "\n", - "* **void setAll(int[] a, IntUnaryOperator gen)**\n", - "* **void setAll(long[] a, IntToLongFunction gen)**\n", - "* **void setAll(double[] a, IntToDoubleFunctiongen)**\n", - "* ** void setAll(T[] a, IntFunction gen)**\n", - "\n", - "除了 **int** , **long** , **double** 有特殊的版本,其他的一切都由泛型版本处理。生成器不是 **Supplier** 因为它们不带参数,并且必须将 **int** 数组索引作为参数。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/SimpleSetAll.java\n", - "\n", - "import java.util.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "class Bob {\n", - " final int id;\n", - " Bob(int n) { id = n; }\n", - " @Override\n", - " public String toString() { return \"Bob\" + id; }\n", - "}\n", - "\n", - "public class SimpleSetAll {\n", - " public static final int SZ = 8;\n", - " static int val = 1;\n", - " static char[] chars = \"abcdefghijklmnopqrstuvwxyz\"\n", - " .toCharArray();\n", - " static char getChar(int n) { return chars[n]; }\n", - " public static void main(String[] args) {\n", - " int[] ia = new int[SZ];\n", - " long[] la = new long[SZ];\n", - " double[] da = new double[SZ];\n", - " Arrays.setAll(ia, n -> n); // [1]\n", - " Arrays.setAll(la, n -> n);\n", - " Arrays.setAll(da, n -> n);\n", - " show(ia);\n", - " show(la);\n", - " show(da);\n", - " Arrays.setAll(ia, n -> val++); // [2]\n", - " Arrays.setAll(la, n -> val++);\n", - " Arrays.setAll(da, n -> val++);\n", - " show(ia);\n", - " show(la);\n", - " show(da);\n", - "\n", - " Bob[] ba = new Bob[SZ];\n", - " Arrays.setAll(ba, Bob::new); // [3]\n", - " show(ba);\n", - "\n", - " Character[] ca = new Character[SZ];\n", - " Arrays.setAll(ca, SimpleSetAll::getChar); // [4]\n", - " show(ca);\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", - "[1, 2, 3, 4, 5, 6, 7, 8]\n", - "[9, 10, 11, 12, 13, 14, 15, 16]\n", - "[17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0]\n", - "[Bob0, Bob1, Bob2, Bob3, Bob4, Bob5, Bob6, Bob7]\n", - "[a, b, c, d, e, f, g, h]\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* **[1]** 这里,我们只是将数组索引作为值插入数组。这将自动转化为 **long** 和 **double** 版本。\n", - "* **[2]** 这个函数只需要接受索引就能产生正确结果。这个,我们忽略索引值并且使用 **val** 生成结果。\n", - "* **[3]** 方法引用有效,因为 **Bob** 的构造器接收一个 **int** 参数。只要我们传递的函数接收一个 **int** 参数且能产生正确的结果,就认为它完成了工作。\n", - "* **[4]** 为了处理除了 **int** ,**long** ,**double** 之外的基元类型,请为基元创建包装类的数组。然后使用 **setAll()** 的泛型版本。请注意,**getChar()** 生成基元类型,因此这是自动装箱到 **Character** 。\n", - "\n", - "\n", - "\n", - "## 增量生成\n", - "\n", - "这是一个方法库,用于为不同类型生成增量值。\n", - "\n", - "这些被作为内部类来生成容易记住的名字;比如,为了使用 **Integer** 工具你可以用 **new Conut.Interger()** , 如果你想要使用基本数据类型 **int** 工具,你可以用 **new Count.Pint()** (基本类型的名字不能被直接使用,所以它们都在前面添加一个 **P** 来表示基本数据类型'primitive', 我们的第一选择是使用基本类型名字后面跟着下划线,比如 **int_** 和 **double_** ,但是这种方式违背Java的命名习惯)。每个包装类的生成器都使用 **get()** 方法实现了它的 **Supplier** 。要使用**Array.setAll()** ,一个重载的 **get(int n)** 方法要接受(并忽略)其参数,以便接受 **setAll()** 传递的索引值。\n", - "\n", - "注意,通过使用包装类的名称作为内部类名,我们必须调用 **java.lang** 包来保证我们可以使用实际包装类的名字:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Count.java\n", - "// Generate incremental values of different types\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import static onjava.ConvertTo.*;\n", - "\n", - "public interface Count {\n", - " class Boolean\n", - " implements Supplier {\n", - " private boolean b = true;\n", - " @Override\n", - " public java.lang.Boolean get() {\n", - " b = !b;\n", - " return java.lang.Boolean.valueOf(b);\n", - " }\n", - " public java.lang.Boolean get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Boolean[] array(int sz) {\n", - " java.lang.Boolean[] result =\n", - " new java.lang.Boolean[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pboolean {\n", - " private boolean b = true;\n", - " public boolean get() {\n", - " b = !b;\n", - " return b;\n", - " }\n", - " public boolean get(int n) { return get(); }\n", - " public boolean[] array(int sz) {\n", - " return primitive(new Boolean().array(sz));\n", - " }\n", - " }\n", - " class Byte\n", - " implements Supplier {\n", - " private byte b;\n", - " @Override\n", - " public java.lang.Byte get() { return b++; }\n", - " public java.lang.Byte get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Byte[] array(int sz) {\n", - " java.lang.Byte[] result =\n", - " new java.lang.Byte[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pbyte {\n", - " private byte b;\n", - " public byte get() { return b++; }\n", - " public byte get(int n) { return get(); }\n", - " public byte[] array(int sz) {\n", - " return primitive(new Byte().array(sz));\n", - " }\n", - " }\n", - " char[] CHARS =\n", - " \"abcdefghijklmnopqrstuvwxyz\".toCharArray();\n", - " class Character\n", - " implements Supplier {\n", - " private int i;\n", - " @Override\n", - " public java.lang.Character get() {\n", - " i = (i + 1) % CHARS.length;\n", - " return CHARS[i];\n", - " }\n", - " public java.lang.Character get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Character[] array(int sz) {\n", - " java.lang.Character[] result =\n", - " new java.lang.Character[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pchar {\n", - " private int i;\n", - " public char get() {\n", - " i = (i + 1) % CHARS.length;\n", - " return CHARS[i];\n", - " }\n", - " public char get(int n) { return get(); }\n", - " public char[] array(int sz) {\n", - " return primitive(new Character().array(sz));\n", - " }\n", - " }\n", - " class Short\n", - " implements Supplier {\n", - " short s;\n", - " @Override\n", - " public java.lang.Short get() { return s++; }\n", - " public java.lang.Short get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Short[] array(int sz) {\n", - " java.lang.Short[] result =\n", - " new java.lang.Short[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pshort {\n", - " short s;\n", - " public short get() { return s++; }\n", - " public short get(int n) { return get(); }\n", - " public short[] array(int sz) {\n", - " return primitive(new Short().array(sz));\n", - " }\n", - " }\n", - " class Integer\n", - " implements Supplier {\n", - " int i;\n", - " @Override\n", - " public java.lang.Integer get() { return i++; }\n", - " public java.lang.Integer get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Integer[] array(int sz) {\n", - " java.lang.Integer[] result =\n", - " new java.lang.Integer[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pint implements IntSupplier {\n", - " int i;\n", - " public int get() { return i++; }\n", - " public int get(int n) { return get(); }\n", - " @Override\n", - " public int getAsInt() { return get(); }\n", - " public int[] array(int sz) {\n", - " return primitive(new Integer().array(sz));\n", - " }\n", - " }\n", - " class Long\n", - " implements Supplier {\n", - " private long l;\n", - " @Override\n", - " public java.lang.Long get() { return l++; }\n", - " public java.lang.Long get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Long[] array(int sz) {\n", - " java.lang.Long[] result =\n", - " new java.lang.Long[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Plong implements LongSupplier {\n", - " private long l;\n", - " public long get() { return l++; }\n", - " public long get(int n) { return get(); }\n", - " @Override\n", - " public long getAsLong() { return get(); }\n", - " public long[] array(int sz) {\n", - " return primitive(new Long().array(sz));\n", - " }\n", - " }\n", - " class Float\n", - " implements Supplier {\n", - " private int i;\n", - " @Override\n", - " public java.lang.Float get() {\n", - " return java.lang.Float.valueOf(i++);\n", - " }\n", - " public java.lang.Float get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Float[] array(int sz) {\n", - " java.lang.Float[] result =\n", - " new java.lang.Float[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pfloat {\n", - " private int i;\n", - " public float get() { return i++; }\n", - " public float get(int n) { return get(); }\n", - " public float[] array(int sz) {\n", - " return primitive(new Float().array(sz));\n", - " }\n", - " }\n", - " class Double\n", - " implements Supplier {\n", - " private int i;\n", - " @Override\n", - " public java.lang.Double get() {\n", - " return java.lang.Double.valueOf(i++);\n", - " }\n", - " public java.lang.Double get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Double[] array(int sz) {\n", - " java.lang.Double[] result =\n", - " new java.lang.Double[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pdouble implements DoubleSupplier {\n", - " private int i;\n", - " public double get() { return i++; }\n", - " public double get(int n) { return get(); }\n", - " @Override\n", - " public double getAsDouble() { return get(0); }\n", - " public double[] array(int sz) {\n", - " return primitive(new Double().array(sz));\n", - " }\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于 **int** ,**long** ,**double** 这三个有特殊 **Supplier** 接口的原始数据类型来说,**Pint** , **Plong** 和 **Pdouble** 实现了这些接口。\n", - "\n", - "这里是对 **Count** 的测试,这同样给我们提供了如何使用它的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/TestCount.java\n", - "// Test counting generators\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class TestCount {\n", - " static final int SZ = 5;\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Boolean\");\n", - " Boolean[] a1 = new Boolean[SZ];\n", - " Arrays.setAll(a1, new Count.Boolean()::get);\n", - " show(a1);\n", - " a1 = Stream.generate(new Count.Boolean())\n", - " .limit(SZ + 1).toArray(Boolean[]::new);\n", - " show(a1);\n", - " a1 = new Count.Boolean().array(SZ + 2);\n", - " show(a1);\n", - " boolean[] a1b =\n", - " new Count.Pboolean().array(SZ + 3);\n", - " show(a1b);\n", - "\n", - " System.out.println(\"Byte\");\n", - " Byte[] a2 = new Byte[SZ];\n", - " Arrays.setAll(a2, new Count.Byte()::get);\n", - " show(a2);\n", - " a2 = Stream.generate(new Count.Byte())\n", - " .limit(SZ + 1).toArray(Byte[]::new);\n", - " show(a2);\n", - " a2 = new Count.Byte().array(SZ + 2);\n", - " show(a2);\n", - " byte[] a2b = new Count.Pbyte().array(SZ + 3);\n", - " show(a2b);\n", - "\n", - " System.out.println(\"Character\");\n", - " Character[] a3 = new Character[SZ];\n", - " Arrays.setAll(a3, new Count.Character()::get);\n", - " show(a3);\n", - " a3 = Stream.generate(new Count.Character())\n", - " .limit(SZ + 1).toArray(Character[]::new);\n", - " show(a3);\n", - " a3 = new Count.Character().array(SZ + 2);\n", - " show(a3);\n", - " char[] a3b = new Count.Pchar().array(SZ + 3);\n", - " show(a3b);\n", - "\n", - " System.out.println(\"Short\");\n", - " Short[] a4 = new Short[SZ];\n", - " Arrays.setAll(a4, new Count.Short()::get);\n", - " show(a4);\n", - " a4 = Stream.generate(new Count.Short())\n", - " .limit(SZ + 1).toArray(Short[]::new);\n", - " show(a4);\n", - " a4 = new Count.Short().array(SZ + 2);\n", - " show(a4);\n", - " short[] a4b = new Count.Pshort().array(SZ + 3);\n", - " show(a4b);\n", - "\n", - " System.out.println(\"Integer\");\n", - " int[] a5 = new int[SZ];\n", - " Arrays.setAll(a5, new Count.Integer()::get);\n", - " show(a5);\n", - " Integer[] a5b =\n", - " Stream.generate(new Count.Integer())\n", - " .limit(SZ + 1).toArray(Integer[]::new);\n", - " show(a5b);\n", - " a5b = new Count.Integer().array(SZ + 2);\n", - " show(a5b);\n", - " a5 = IntStream.generate(new Count.Pint())\n", - " .limit(SZ + 1).toArray();\n", - " show(a5);\n", - " a5 = new Count.Pint().array(SZ + 3);\n", - " show(a5);\n", - "\n", - " System.out.println(\"Long\");\n", - " long[] a6 = new long[SZ];\n", - " Arrays.setAll(a6, new Count.Long()::get);\n", - " show(a6);\n", - " Long[] a6b = Stream.generate(new Count.Long())\n", - " .limit(SZ + 1).toArray(Long[]::new);\n", - " show(a6b);\n", - " a6b = new Count.Long().array(SZ + 2);\n", - " show(a6b);\n", - " a6 = LongStream.generate(new Count.Plong())\n", - " .limit(SZ + 1).toArray();\n", - " show(a6);\n", - " a6 = new Count.Plong().array(SZ + 3);\n", - " show(a6);\n", - "\n", - " System.out.println(\"Float\");\n", - " Float[] a7 = new Float[SZ];\n", - " Arrays.setAll(a7, new Count.Float()::get);\n", - " show(a7);\n", - " a7 = Stream.generate(new Count.Float())\n", - " .limit(SZ + 1).toArray(Float[]::new);\n", - " show(a7);\n", - " a7 = new Count.Float().array(SZ + 2);\n", - " show(a7);\n", - " float[] a7b = new Count.Pfloat().array(SZ + 3);\n", - " show(a7b);\n", - "\n", - " System.out.println(\"Double\");\n", - " double[] a8 = new double[SZ];\n", - " Arrays.setAll(a8, new Count.Double()::get);\n", - " show(a8);\n", - " Double[] a8b =\n", - " Stream.generate(new Count.Double())\n", - " .limit(SZ + 1).toArray(Double[]::new);\n", - " show(a8b);\n", - " a8b = new Count.Double().array(SZ + 2);\n", - " show(a8b);\n", - " a8 = DoubleStream.generate(new Count.Pdouble())\n", - " .limit(SZ + 1).toArray();\n", - " show(a8);\n", - " a8 = new Count.Pdouble().array(SZ + 3);\n", - " show(a8);\n", - " }\n", - "}\n", - "/* Output:\n", - "Boolean\n", - "[false, true, false, true, false]\n", - "[false, true, false, true, false, true]\n", - "[false, true, false, true, false, true, false]\n", - "[false, true, false, true, false, true, false, true]\n", - "Byte\n", - "[0, 1, 2, 3, 4]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6]\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "Character\n", - "[b, c, d, e, f]\n", - "[b, c, d, e, f, g]\n", - "[b, c, d, e, f, g, h]\n", - "[b, c, d, e, f, g, h, i]\n", - "Short\n", - "[0, 1, 2, 3, 4]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6]\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "Integer\n", - "[0, 1, 2, 3, 4]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "Long\n", - "[0, 1, 2, 3, 4]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6]\n", - "[0, 1, 2, 3, 4, 5]\n", - "[0, 1, 2, 3, 4, 5, 6, 7]\n", - "Float\n", - "[0.0, 1.0, 2.0, 3.0, 4.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", - "Double\n", - "[0.0, 1.0, 2.0, 3.0, 4.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0]\n", - "[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意到原始数组类型 **int[]** ,**long[]** ,**double[]** 可以直接被 **Arrays.setAll()** 填充,但是其他的原始类型都要求用包装器类型的数组。\n", - "\n", - "通过 **Stream.generate()** 创建的包装数组显示了 **toArray()** 的重载用法,在这里你应该提供给它要创建的数组类型的构造器。\n", - "\n", - "\n", - "## 随机生成\n", - "\n", - "我们可以按照 **Count.java** 的结构创建一个生成随机值的工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Rand.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Generate random values of different types\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import static onjava.ConvertTo.*;\n", - "\n", - "public interface Rand {\n", - " int MOD = 10_000;\n", - " class Boolean\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Boolean get() {\n", - " return r.nextBoolean();\n", - " }\n", - " public java.lang.Boolean get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Boolean[] array(int sz) {\n", - " java.lang.Boolean[] result =\n", - " new java.lang.Boolean[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pboolean {\n", - " public boolean[] array(int sz) {\n", - " return primitive(new Boolean().array(sz));\n", - " }\n", - " }\n", - " class Byte\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Byte get() {\n", - " return (byte)r.nextInt(MOD);\n", - " }\n", - " public java.lang.Byte get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Byte[] array(int sz) {\n", - " java.lang.Byte[] result =\n", - " new java.lang.Byte[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pbyte {\n", - " public byte[] array(int sz) {\n", - " return primitive(new Byte().array(sz));\n", - " }\n", - " }\n", - " class Character\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Character get() {\n", - " return (char)r.nextInt('a', 'z' + 1);\n", - " }\n", - " public java.lang.Character get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Character[] array(int sz) {\n", - " java.lang.Character[] result =\n", - " new java.lang.Character[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pchar {\n", - " public char[] array(int sz) {\n", - " return primitive(new Character().array(sz));\n", - " }\n", - " }\n", - " class Short\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Short get() {\n", - " return (short)r.nextInt(MOD);\n", - " }\n", - " public java.lang.Short get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Short[] array(int sz) {\n", - " java.lang.Short[] result =\n", - " new java.lang.Short[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pshort {\n", - " public short[] array(int sz) {\n", - " return primitive(new Short().array(sz));\n", - " }\n", - " }\n", - " class Integer\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Integer get() {\n", - " return r.nextInt(MOD);\n", - " }\n", - " public java.lang.Integer get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Integer[] array(int sz) {\n", - " int[] primitive = new Pint().array(sz);\n", - " java.lang.Integer[] result =\n", - " new java.lang.Integer[sz];\n", - " for(int i = 0; i < sz; i++)\n", - " result[i] = primitive[i];\n", - " return result;\n", - " }\n", - " }\n", - " class Pint implements IntSupplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public int getAsInt() {\n", - " return r.nextInt(MOD);\n", - " }\n", - " public int get(int n) { return getAsInt(); }\n", - " public int[] array(int sz) {\n", - " return r.ints(sz, 0, MOD).toArray();\n", - " }\n", - " }\n", - " class Long\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Long get() {\n", - " return r.nextLong(MOD);\n", - " }\n", - " public java.lang.Long get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Long[] array(int sz) {\n", - " long[] primitive = new Plong().array(sz);\n", - " java.lang.Long[] result =\n", - " new java.lang.Long[sz];\n", - " for(int i = 0; i < sz; i++)\n", - " result[i] = primitive[i];\n", - " return result;\n", - " }\n", - " }\n", - " class Plong implements LongSupplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public long getAsLong() {\n", - " return r.nextLong(MOD);\n", - " }\n", - " public long get(int n) { return getAsLong(); }\n", - " public long[] array(int sz) {\n", - " return r.longs(sz, 0, MOD).toArray();\n", - " }\n", - " }\n", - " class Float\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Float get() {\n", - " return (float)trim(r.nextDouble());\n", - " }\n", - " public java.lang.Float get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Float[] array(int sz) {\n", - " java.lang.Float[] result =\n", - " new java.lang.Float[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - " class Pfloat {\n", - " public float[] array(int sz) {\n", - " return primitive(new Float().array(sz));\n", - " }\n", - " }\n", - " static double trim(double d) {\n", - " return\n", - " ((double)Math.round(d * 1000.0)) / 100.0;\n", - " }\n", - " class Double\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public java.lang.Double get() {\n", - " return trim(r.nextDouble());\n", - " }\n", - " public java.lang.Double get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.Double[] array(int sz) {\n", - " double[] primitive =\n", - " new Rand.Pdouble().array(sz);\n", - " java.lang.Double[] result =\n", - " new java.lang.Double[sz];\n", - " for(int i = 0; i < sz; i++)\n", - " result[i] = primitive[i];\n", - " return result;\n", - " }\n", - " }\n", - " class Pdouble implements DoubleSupplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " @Override\n", - " public double getAsDouble() {\n", - " return trim(r.nextDouble());\n", - " }\n", - " public double get(int n) {\n", - " return getAsDouble();\n", - " }\n", - " public double[] array(int sz) {\n", - " double[] result = r.doubles(sz).toArray();\n", - " Arrays.setAll(result,\n", - " n -> result[n] = trim(result[n]));\n", - " return result;\n", - " }\n", - " }\n", - " class String\n", - " implements Supplier {\n", - " SplittableRandom r = new SplittableRandom(47);\n", - " private int strlen = 7; // Default length\n", - " public String() {}\n", - " public String(int strLength) {\n", - " strlen = strLength;\n", - " }\n", - " @Override\n", - " public java.lang.String get() {\n", - " return r.ints(strlen, 'a', 'z' + 1)\n", - " .collect(StringBuilder::new,\n", - " StringBuilder::appendCodePoint,\n", - " StringBuilder::append).toString();\n", - " }\n", - " public java.lang.String get(int n) {\n", - " return get();\n", - " }\n", - " public java.lang.String[] array(int sz) {\n", - " java.lang.String[] result =\n", - " new java.lang.String[sz];\n", - " Arrays.setAll(result, n -> get());\n", - " return result;\n", - " }\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于除了 **int** 、 **long** 和 **double** 之外的所有基本类型元素生成器,只生成数组,而不是 Count 中看到的完整操作集。这只是一个设计选择,因为本书不需要额外的功能。\n", - "\n", - "下面是对所有 **Rand** 工具的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/TestRand.java\n", - "// Test random generators\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class TestRand {\n", - " static final int SZ = 5;\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Boolean\");\n", - " Boolean[] a1 = new Boolean[SZ];\n", - " Arrays.setAll(a1, new Rand.Boolean()::get);\n", - " show(a1);\n", - " a1 = Stream.generate(new Rand.Boolean())\n", - " .limit(SZ + 1).toArray(Boolean[]::new);\n", - " show(a1);\n", - " a1 = new Rand.Boolean().array(SZ + 2);\n", - " show(a1);\n", - " boolean[] a1b =\n", - " new Rand.Pboolean().array(SZ + 3);\n", - " show(a1b);\n", - "\n", - " System.out.println(\"Byte\");\n", - " Byte[] a2 = new Byte[SZ];\n", - " Arrays.setAll(a2, new Rand.Byte()::get);\n", - " show(a2);\n", - " a2 = Stream.generate(new Rand.Byte())\n", - " .limit(SZ + 1).toArray(Byte[]::new);\n", - " show(a2);\n", - " a2 = new Rand.Byte().array(SZ + 2);\n", - " show(a2);\n", - " byte[] a2b = new Rand.Pbyte().array(SZ + 3);\n", - " show(a2b);\n", - "\n", - " System.out.println(\"Character\");\n", - " Character[] a3 = new Character[SZ];\n", - " Arrays.setAll(a3, new Rand.Character()::get);\n", - " show(a3);\n", - " a3 = Stream.generate(new Rand.Character())\n", - " .limit(SZ + 1).toArray(Character[]::new);\n", - " show(a3);\n", - " a3 = new Rand.Character().array(SZ + 2);\n", - " show(a3);\n", - " char[] a3b = new Rand.Pchar().array(SZ + 3);\n", - " show(a3b);\n", - "\n", - " System.out.println(\"Short\");\n", - " Short[] a4 = new Short[SZ];\n", - " Arrays.setAll(a4, new Rand.Short()::get);\n", - " show(a4);\n", - " a4 = Stream.generate(new Rand.Short())\n", - " .limit(SZ + 1).toArray(Short[]::new);\n", - " show(a4);\n", - " a4 = new Rand.Short().array(SZ + 2);\n", - " show(a4);\n", - " short[] a4b = new Rand.Pshort().array(SZ + 3);\n", - " show(a4b);\n", - "\n", - " System.out.println(\"Integer\");\n", - " int[] a5 = new int[SZ];\n", - " Arrays.setAll(a5, new Rand.Integer()::get);\n", - " show(a5);\n", - " Integer[] a5b =\n", - " Stream.generate(new Rand.Integer())\n", - " .limit(SZ + 1).toArray(Integer[]::new);\n", - " show(a5b);\n", - " a5b = new Rand.Integer().array(SZ + 2);\n", - " show(a5b);\n", - " a5 = IntStream.generate(new Rand.Pint())\n", - " .limit(SZ + 1).toArray();\n", - " show(a5);\n", - " a5 = new Rand.Pint().array(SZ + 3);\n", - " show(a5);\n", - "\n", - " System.out.println(\"Long\");\n", - " long[] a6 = new long[SZ];\n", - " Arrays.setAll(a6, new Rand.Long()::get);\n", - " show(a6);\n", - " Long[] a6b = Stream.generate(new Rand.Long())\n", - " .limit(SZ + 1).toArray(Long[]::new);\n", - " show(a6b);\n", - " a6b = new Rand.Long().array(SZ + 2);\n", - " show(a6b);\n", - " a6 = LongStream.generate(new Rand.Plong())\n", - " .limit(SZ + 1).toArray();\n", - " show(a6);\n", - " a6 = new Rand.Plong().array(SZ + 3);\n", - " show(a6);\n", - "\n", - " System.out.println(\"Float\");\n", - " Float[] a7 = new Float[SZ];\n", - " Arrays.setAll(a7, new Rand.Float()::get);\n", - " show(a7);\n", - " a7 = Stream.generate(new Rand.Float())\n", - " .limit(SZ + 1).toArray(Float[]::new);\n", - " show(a7);\n", - " a7 = new Rand.Float().array(SZ + 2);\n", - " show(a7);\n", - " float[] a7b = new Rand.Pfloat().array(SZ + 3);\n", - " show(a7b);\n", - "\n", - " System.out.println(\"Double\");\n", - " double[] a8 = new double[SZ];\n", - " Arrays.setAll(a8, new Rand.Double()::get);\n", - " show(a8);\n", - " Double[] a8b =\n", - " Stream.generate(new Rand.Double())\n", - " .limit(SZ + 1).toArray(Double[]::new);\n", - " show(a8b);\n", - " a8b = new Rand.Double().array(SZ + 2);\n", - " show(a8b);\n", - " a8 = DoubleStream.generate(new Rand.Pdouble())\n", - " .limit(SZ + 1).toArray();\n", - " show(a8);\n", - " a8 = new Rand.Pdouble().array(SZ + 3);\n", - " show(a8);\n", - "\n", - " System.out.println(\"String\");\n", - " String[] s = new String[SZ - 1];\n", - " Arrays.setAll(s, new Rand.String()::get);\n", - " show(s);\n", - " s = Stream.generate(new Rand.String())\n", - " .limit(SZ).toArray(String[]::new);\n", - " show(s);\n", - " s = new Rand.String().array(SZ + 1);\n", - " show(s);\n", - "\n", - " Arrays.setAll(s, new Rand.String(4)::get);\n", - " show(s);\n", - " s = Stream.generate(new Rand.String(4))\n", - " .limit(SZ).toArray(String[]::new);\n", - " show(s);\n", - " s = new Rand.String(4).array(SZ + 1);\n", - " show(s);\n", - " }\n", - "}\n", - "/* Output:\n", - "Boolean\n", - "[true, false, true, true, true]\n", - "[true, false, true, true, true, false]\n", - "[true, false, true, true, true, false, false]\n", - "[true, false, true, true, true, false, false, true]\n", - "Byte\n", - "[123, 33, 101, 112, 33]\n", - "[123, 33, 101, 112, 33, 31]\n", - "[123, 33, 101, 112, 33, 31, 0]\n", - "[123, 33, 101, 112, 33, 31, 0, -72]\n", - "Character\n", - "[b, t, p, e, n]\n", - "[b, t, p, e, n, p]\n", - "[b, t, p, e, n, p, c]\n", - "[b, t, p, e, n, p, c, c]\n", - "Short\n", - "[635, 8737, 3941, 4720, 6177]\n", - "[635, 8737, 3941, 4720, 6177, 8479]\n", - "[635, 8737, 3941, 4720, 6177, 8479, 6656]\n", - "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768]\n", - "Integer\n", - "[635, 8737, 3941, 4720, 6177]\n", - "[635, 8737, 3941, 4720, 6177, 8479]\n", - "[635, 8737, 3941, 4720, 6177, 8479, 6656]\n", - "[635, 8737, 3941, 4720, 6177, 8479]\n", - "[635, 8737, 3941, 4720, 6177, 8479, 6656, 3768]\n", - "Long\n", - "[6882, 3765, 692, 9575, 4439]\n", - "[6882, 3765, 692, 9575, 4439, 2638]\n", - "[6882, 3765, 692, 9575, 4439, 2638, 4011]\n", - "[6882, 3765, 692, 9575, 4439, 2638]\n", - "[6882, 3765, 692, 9575, 4439, 2638, 4011, 9610]\n", - "Float\n", - "[4.83, 2.89, 2.9, 1.97, 3.01]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28]\n", - "Double\n", - "[4.83, 2.89, 2.9, 1.97, 3.01]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99, 8.28]\n", - "String\n", - "[btpenpc, cuxszgv, gmeinne, eloztdv]\n", - "[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc]\n", - "[btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc, ygpoalk]\n", - "[btpe, npcc, uxsz, gvgm, einn, eelo]\n", - "[btpe, npcc, uxsz, gvgm, einn]\n", - "[btpe, npcc, uxsz, gvgm, einn, eelo]\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意(除了 **String** 部分之外),这段代码与 **TestCount.java** 中的代码相同,**Count** 被 **Rand** 替换。\n", - "\n", - "\n", - "## 泛型和基本数组\n", - "在本章的前面,我们被提醒,泛型不能和基元一起工作。在这种情况下,我们必须从基元数组转换为包装类型的数组,并且还必须从另一个方向转换。下面是一个转换器可以同时对所有类型的数据执行操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/ConvertTo.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "package onjava;\n", - "\n", - "public interface ConvertTo {\n", - " static boolean[] primitive(Boolean[] in) {\n", - " boolean[] result = new boolean[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i]; // Autounboxing\n", - " return result;\n", - " }\n", - " static char[] primitive(Character[] in) {\n", - " char[] result = new char[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static byte[] primitive(Byte[] in) {\n", - " byte[] result = new byte[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static short[] primitive(Short[] in) {\n", - " short[] result = new short[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static int[] primitive(Integer[] in) {\n", - " int[] result = new int[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static long[] primitive(Long[] in) {\n", - " long[] result = new long[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static float[] primitive(Float[] in) {\n", - " float[] result = new float[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static double[] primitive(Double[] in) {\n", - " double[] result = new double[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " // Convert from primitive array to wrapped array:\n", - " static Boolean[] boxed(boolean[] in) {\n", - " Boolean[] result = new Boolean[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i]; // Autoboxing\n", - " return result;\n", - " }\n", - " static Character[] boxed(char[] in) {\n", - " Character[] result = new Character[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Byte[] boxed(byte[] in) {\n", - " Byte[] result = new Byte[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Short[] boxed(short[] in) {\n", - " Short[] result = new Short[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Integer[] boxed(int[] in) {\n", - " Integer[] result = new Integer[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Long[] boxed(long[] in) {\n", - " Long[] result = new Long[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Float[] boxed(float[] in) {\n", - " Float[] result = new Float[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - " static Double[] boxed(double[] in) {\n", - " Double[] result = new Double[in.length];\n", - " for(int i = 0; i < in.length; i++)\n", - " result[i] = in[i];\n", - " return result;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**primitive()** 的每个版本都创建一个准确长度的适当基元数组,然后从包装类的 **in** 数组中复制元素。如果任何包装的数组元素是 **null** ,你将得到一个异常(这是合理的—否则无法选择有意义的值进行替换)。注意在这个任务中自动装箱如何发生。\n", - "\n", - "下面是对 **ConvertTo** 中所有方法的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/TestConvertTo.java\n", - "import java.util.*;\n", - "import onjava.*;\n", - "import static onjava.ArrayShow.*;\n", - "import static onjava.ConvertTo.*;\n", - "\n", - "public class TestConvertTo {\n", - " static final int SIZE = 6;\n", - " public static void main(String[] args) {\n", - " Boolean[] a1 = new Boolean[SIZE];\n", - " Arrays.setAll(a1, new Rand.Boolean()::get);\n", - " boolean[] a1p = primitive(a1);\n", - " show(\"a1p\", a1p);\n", - " Boolean[] a1b = boxed(a1p);\n", - " show(\"a1b\", a1b);\n", - "\n", - " Byte[] a2 = new Byte[SIZE];\n", - " Arrays.setAll(a2, new Rand.Byte()::get);\n", - " byte[] a2p = primitive(a2);\n", - " show(\"a2p\", a2p);\n", - " Byte[] a2b = boxed(a2p);\n", - " show(\"a2b\", a2b);\n", - "\n", - " Character[] a3 = new Character[SIZE];\n", - " Arrays.setAll(a3, new Rand.Character()::get);\n", - " char[] a3p = primitive(a3);\n", - " show(\"a3p\", a3p);\n", - " Character[] a3b = boxed(a3p);\n", - " show(\"a3b\", a3b);\n", - "\n", - " Short[] a4 = new Short[SIZE];\n", - " Arrays.setAll(a4, new Rand.Short()::get);\n", - " short[] a4p = primitive(a4);\n", - " show(\"a4p\", a4p);\n", - " Short[] a4b = boxed(a4p);\n", - " show(\"a4b\", a4b);\n", - "\n", - " Integer[] a5 = new Integer[SIZE];\n", - " Arrays.setAll(a5, new Rand.Integer()::get);\n", - " int[] a5p = primitive(a5);\n", - " show(\"a5p\", a5p);\n", - " Integer[] a5b = boxed(a5p);\n", - " show(\"a5b\", a5b);\n", - "\n", - " Long[] a6 = new Long[SIZE];\n", - " Arrays.setAll(a6, new Rand.Long()::get);\n", - " long[] a6p = primitive(a6);\n", - " show(\"a6p\", a6p);\n", - " Long[] a6b = boxed(a6p);\n", - " show(\"a6b\", a6b);\n", - "\n", - " Float[] a7 = new Float[SIZE];\n", - " Arrays.setAll(a7, new Rand.Float()::get);\n", - " float[] a7p = primitive(a7);\n", - " show(\"a7p\", a7p);\n", - " Float[] a7b = boxed(a7p);\n", - " show(\"a7b\", a7b);\n", - "\n", - " Double[] a8 = new Double[SIZE];\n", - " Arrays.setAll(a8, new Rand.Double()::get);\n", - " double[] a8p = primitive(a8);\n", - " show(\"a8p\", a8p);\n", - " Double[] a8b = boxed(a8p);\n", - " show(\"a8b\", a8b);\n", - " }\n", - "}\n", - "/* Output:\n", - "a1p: [true, false, true, true, true, false]\n", - "a1b: [true, false, true, true, true, false]\n", - "a2p: [123, 33, 101, 112, 33, 31]\n", - "a2b: [123, 33, 101, 112, 33, 31]\n", - "a3p: [b, t, p, e, n, p]\n", - "a3b: [b, t, p, e, n, p]\n", - "a4p: [635, 8737, 3941, 4720, 6177, 8479]\n", - "a4b: [635, 8737, 3941, 4720, 6177, 8479]\n", - "a5p: [635, 8737, 3941, 4720, 6177, 8479]\n", - "a5b: [635, 8737, 3941, 4720, 6177, 8479]\n", - "a6p: [6882, 3765, 692, 9575, 4439, 2638]\n", - "a6b: [6882, 3765, 692, 9575, 4439, 2638]\n", - "a7p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "a7b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "a8p: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "a8b: [4.83, 2.89, 2.9, 1.97, 3.01, 0.18]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在每种情况下,原始数组都是为包装类型创建的,并使用 **Arrays.setAll()** 填充,正如我们在 **TestCouner.java** 中所做的那样(这也验证了 **Arrays.setAll()** 是否能同 **Integer** ,**Long** ,和 **Double** )。然后 **ConvertTo.primitive()** 将包装器数组转换为对应的基元数组,**ConverTo.boxed()** 将其转换回来。\n", - "\n", - "\n", - "## 数组元素修改\n", - "\n", - "传递给 **Arrays.setAll()** 的生成器函数可以使用它接收到的数组索引修改现有的数组元素:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ModifyExisting.java\n", - "\n", - "import java.util.*;\n", - "import onjava.*;\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class ModifyExisting {\n", - " public static void main(String[] args) {\n", - " double[] da = new double[7];\n", - " Arrays.setAll(da, new Rand.Double()::get);\n", - " show(da);\n", - " Arrays.setAll(da, n -> da[n] / 100); // [1]\n", - " show(da);\n", - "\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - "[4.83, 2.89, 2.9, 1.97, 3.01, 0.18, 0.99]\n", - "[0.0483, 0.028900000000000002, 0.028999999999999998,\n", - "0.0197, 0.0301, 0.0018, 0.009899999999999999]\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[1] Lambdas在这里特别有用,因为数组总是在lambda表达式的范围内。\n", - "\n", - "\n", - "\n", - "## 数组并行\n", - "\n", - "我们很快就不得不面对并行的主题。例如,“并行”一词在许多Java库方法中使用。您可能听说过类似“并行程序运行得更快”这样的说法,这是有道理的—当您可以有多个处理器时,为什么只有一个处理器在您的程序上工作呢? 如果您认为您应该利用其中的“并行”,这是很容易被原谅的。\n", - "要是这么简单就好了。不幸的是,通过采用这种方法,您可以很容易地编写比非并行版本运行速度更慢的代码。在你深刻理解所有的问题之前,并行编程看起来更像是一门艺术而非科学。\n", - "以下是简短的版本:用简单的方法编写代码。不要开始处理并行性,除非它成为一个问题。您仍然会遇到并行性。在本章中,我们将介绍一些为并行执行而编写的Java库方法。因此,您必须对它有足够的了解,以便进行基本的讨论,并避免出现错误。\n", - "\n", - "在阅读并发编程这一章之后,您将更深入地理解它(但是,唉,这还远远不够。只是这些的话,充分理解这个主题是不可能的)。\n", - "在某些情况下,即使您只有一个处理器,无论您是否显式地尝试并行,并行实现是惟一的、最佳的或最符合逻辑的选择。它是一个可以一直使用的工具,所以您必须了解它的相关问题。\n", - "\n", - "最好从数据的角度来考虑并行性。对于大量数据(以及可用的额外处理器),并行可能会有所帮助。但您也可能使事情变得更糟。\n", - "\n", - "在本书的其余部分,我们将遇到不同的情况:\n", - "\n", - "- 1、所提供的惟一选项是并行的。这很简单,因为我们别无选择,只能使用它。这种情况是比较罕见的。\n", - "\n", - "- 2、有多个选项,但是并行版本(通常是最新的版本)被设计成在任何地方都可以使用(甚至在那些不关心并行性的代码中),如案例#1。我们将按预期使用并行版本。\n", - "\n", - "- 3、案例1和案例2并不经常发生。相反,您将遇到某些算法的两个版本,一个用于并行使用,另一个用于正常使用。我将描述并行的一个,但不会在普通代码中使用它,因为它也许会产生所有可能的问题。\n", - "\n", - "我建议您在自己的代码中采用这种方法。\n", - "\n", - "[http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](要进一步了解为什么这是一个难题,请参阅Doug Lea的文章。)\n", - "\n", - "**parallelSetAll()**\n", - "\n", - "流式编程产生优雅的代码。例如,假设我们想要创建一个数值由从零开始填充的长数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/CountUpward.java\n", - "\n", - "import java.util.stream.LongStream;\n", - "\n", - "public class CountUpward {\n", - " static long[] fillCounted(int size) {\n", - " return LongStream.iterate(0, i -> i + 1).limit(size).toArray();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " long[] l1 = fillCounted(20); // No problem\n", - " show(l1);\n", - " // On my machine, this runs out of heap space:\n", - " // - long[] l2 = fillCounted(10_000_000);\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", - "16, 17, 18, 19]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**流** 实际上可以存储到将近1000万,但是之后就会耗尽堆空间。常规的 **setAll()** 是有效的,但是如果我们能更快地处理如此大量的数字,那就更好了。\n", - "我们可以使用 **setAll()** 初始化更大的数组。如果速度成为一个问题,**Arrays.parallelSetAll()** 将(可能)更快地执行初始化(请记住并行性中描述的问题)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "\n", - "// arrays/ParallelSetAll.java\n", - "\n", - "import onjava.*;\n", - "import java.util.Arrays;\n", - "\n", - "public class ParallelSetAll {\n", - " static final int SIZE = 10_000_000;\n", - "\n", - " static void intArray() {\n", - " int[] ia = new int[SIZE];\n", - " Arrays.setAll(ia, new Rand.Pint()::get);\n", - " Arrays.parallelSetAll(ia, new Rand.Pint()::get);\n", - " }\n", - "\n", - " static void longArray() {\n", - " long[] la = new long[SIZE];\n", - " Arrays.setAll(la, new Rand.Plong()::get);\n", - " Arrays.parallelSetAll(la, new Rand.Plong()::get);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " intArray();\n", - " longArray();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "数组分配和初始化是在单独的方法中执行的,因为如果两个数组都在 **main()** 中分配,它会耗尽内存(至少在我的机器上是这样。还有一些方法可以告诉Java在启动时分配更多的内存)。\n", - "\n", - "\n", - "\n", - "\n", - "## Arrays工具类\n", - "\n", - "您已经看到了 **java.util.Arrays** 中的 **fill()** 和 **setAll()/parallelSetAll()** 。该类包含许多其他有用的 **静态** 程序方法,我们将对此进行研究。\n", - "\n", - "概述:\n", - "\n", - "- **asList()**: 获取任何序列或数组,并将其转换为一个 **列表集合** (集合章节介绍了此方法)。\n", - "\n", - "- **copyOf()**:以新的长度创建现有数组的新副本。\n", - "\n", - "- **copyOfRange()**:创建现有数组的一部分的新副本。\n", - "\n", - "- **equals()**:比较两个数组是否相等。\n", - "\n", - "- **deepEquals()**:多维数组的相等性比较。\n", - "\n", - "- **stream()**:生成数组元素的流。\n", - "\n", - "- **hashCode()**:生成数组的哈希值(您将在附录中了解这意味着什么:理解equals()和hashCode())。\n", - "\n", - "- **deepHashCode()**: 多维数组的哈希值。\n", - "\n", - "- **sort()**:排序数组\n", - "\n", - "- **parallelSort()**:对数组进行并行排序,以提高速度。\n", - "\n", - "- **binarySearch()**:在已排序的数组中查找元素。\n", - "\n", - "- **parallelPrefix()**:使用提供的函数并行累积(以获得速度)。基本上,就是数组的reduce()。\n", - "\n", - "- **spliterator()**:从数组中产生一个Spliterator;这是本书没有涉及到的流的高级部分。\n", - "\n", - "- **toString()**:为数组生成一个字符串表示。你在整个章节中经常看到这种用法。\n", - "\n", - "- **deepToString()**:为多维数组生成一个字符串。你在整个章节中经常看到这种用法。对于所有基本类型和对象,所有这些方法都是重载的。\n", - "\n", - "\n", - "## 数组拷贝\n", - "\n", - "与使用for循环手工执行复制相比,**copyOf()** 和 **copyOfRange()** 复制数组要快得多。这些方法被重载以处理所有类型。\n", - "\n", - "我们从复制 **int** 和 **Integer** 数组开始:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ArrayCopying.java\n", - "// Demonstrate Arrays.copy() and Arrays.copyOf()\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "class Sup {\n", - " // Superclass\n", - " private int id;\n", - "\n", - " Sup(int n) {\n", - " id = n;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName() + id;\n", - " }\n", - "}\n", - "\n", - "class Sub extends Sup { // Subclass\n", - "\n", - " Sub(int n) {\n", - " super(n);\n", - " }\n", - "}\n", - "\n", - "public class ArrayCopying {\n", - " public static final int SZ = 15;\n", - "\n", - " public static void main(String[] args) {\n", - " int[] a1 = new int[SZ];\n", - " Arrays.setAll(a1, new Count.Integer()::get);\n", - " show(\"a1\", a1);\n", - " int[] a2 = Arrays.copyOf(a1, a1.length); // [1]\n", - " // Prove they are distinct arrays:\n", - " Arrays.fill(a1, 1);\n", - " show(\"a1\", a1);\n", - " show(\"a2\", a2);\n", - " // Create a shorter result:\n", - " a2 = Arrays.copyOf(a2, a2.length / 2); // [2]\n", - " show(\"a2\", a2);\n", - " // Allocate more space:\n", - " a2 = Arrays.copyOf(a2, a2.length + 5);\n", - " show(\"a2\", a2);\n", - " // Also copies wrapped arrays:\n", - " Integer[] a3 = new Integer[SZ]; // [3]\n", - " Arrays.setAll(a3, new Count.Integer()::get);\n", - " Integer[] a4 = Arrays.copyOfRange(a3, 4, 12);\n", - " show(\"a4\", a4);\n", - " Sub[] d = new Sub[SZ / 2];\n", - " Arrays.setAll(d, Sub::new); // Produce Sup[] from Sub[]:\n", - " Sup[] b = Arrays.copyOf(d, d.length, Sup[].class); // [4]\n", - " show(b); // This \"downcast\" works fine:\n", - " Sub[] d2 = Arrays.copyOf(b, b.length, Sub[].class); // [5]\n", - " show(d2); // Bad \"downcast\" compiles but throws exception:\n", - " Sup[] b2 = new Sup[SZ / 2];\n", - " Arrays.setAll(b2, Sup::new);\n", - " try {\n", - " Sub[] d3 = Arrays.copyOf(b2, b2.length, Sub[].class); // [6]\n", - " } catch (Exception e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}\n", - "/* Output: a1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] a1: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\n", - " a2:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]a2:[0, 1, 2, 3, 4, 5, 6]a2:[\n", - " 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0]a4:[4, 5, 6, 7, 8, 9, 10, 11][Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6][\n", - " Sub0, Sub1, Sub2, Sub3, Sub4, Sub5, Sub6]java.lang.ArrayStoreException */\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[1] 这是复制的基本方法;只需给出返回的复制数组的大小。这对于编写需要调整存储大小的算法很有帮助。复制之后,我们把a1的所有元素都设为1,以证明a1的变化不会影响a2中的任何东西。\n", - "\n", - "[2] 通过更改最后一个参数,我们可以缩短或延长返回的复制数组。\n", - "\n", - "[3] **copyOf()** 和 **copyOfRange()** 也可以使用包装类型。**copyOfRange()** 需要一个开始和结束索引。\n", - "\n", - "[4] **copyOf()** 和 **copyOfRange()** 都有一个版本,该版本通过在方法调用的末尾添加目标类型来创建不同类型的数组。我首先想到的是,这可能是一种从原生数组生成包装数组的方法,反之亦然。\n", - "但这没用。它的实际用途是“向上转换”和“向下转换”数组。也就是说,如果您有一个子类型(派生类型)的数组,而您想要一个基类型的数组,那么这些方法将生成所需的数组。\n", - "\n", - "[5] 您甚至可以成功地“向下强制转换”,并从超类型的数组生成子类型的数组。这个版本运行良好,因为我们只是“upcast”。\n", - "\n", - "[6] 这个“数组转换”将编译,但是如果类型不兼容,您将得到一个运行时异常。在这里,强制将基类型转换为派生类型是非法的,因为派生对象中可能有基对象中没有的属性和方法。\n", - "\n", - "实例表明,原生数组和对象数组都可以被复制。但是,如果复制对象的数组,那么只复制引用—不复制对象本身。这称为浅拷贝(有关更多细节,请参阅附录:传递和返回对象)。\n", - "\n", - "还有一个方法 **System.arraycopy()** ,它将一个数组复制到另一个已经分配的数组中。这将不会执行自动装箱或自动卸载—两个数组必须是完全相同的类型。\n", - "\n", - "\n", - "## 数组比较\n", - "\n", - "**数组** 提供了 **equals()** 来比较一维数组,以及 **deepEquals()** 来比较多维数组。对于所有原生类型和对象,这些方法都是重载的。\n", - "\n", - "数组相等的含义:数组必须有相同数量的元素,并且每个元素必须与另一个数组中的对应元素相等,对每个元素使用 **equals()**(对于原生类型,使用原生类型的包装类的 **equals()** 方法;例如,int的Integer.equals()。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ComparingArrays.java\n", - "// Using Arrays.equals()\n", - "\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class ComparingArrays {\n", - " public static final int SZ = 15;\n", - "\n", - " static String[][] twoDArray() {\n", - " String[][] md = new String[5][];\n", - " Arrays.setAll(md, n -> new String[n]);\n", - " for (int i = 0; i < md.length; i++) Arrays.setAll(md[i], new Rand.String()::get);\n", - " return md;\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " int[] a1 = new int[SZ], a2 = new int[SZ];\n", - " Arrays.setAll(a1, new Count.Integer()::get);\n", - " Arrays.setAll(a2, new Count.Integer()::get);\n", - " System.out.println(\"a1 == a2: \" + Arrays.equals(a1, a2));\n", - " a2[3] = 11;\n", - " System.out.println(\"a1 == a2: \" + Arrays.equals(a1, a2));\n", - " Integer[] a1w = new Integer[SZ], a2w = new Integer[SZ];\n", - " Arrays.setAll(a1w, new Count.Integer()::get);\n", - " Arrays.setAll(a2w, new Count.Integer()::get);\n", - " System.out.println(\"a1w == a2w: \" + Arrays.equals(a1w, a2w));\n", - " a2w[3] = 11;\n", - " System.out.println(\"a1w == a2w: \" + Arrays.equals(a1w, a2w));\n", - " String[][] md1 = twoDArray(), md2 = twoDArray();\n", - " System.out.println(Arrays.deepToString(md1));\n", - " System.out.println(\"deepEquals(md1, md2): \" + Arrays.deepEquals(md1, md2));\n", - " System.out.println(\"md1 == md2: \" + Arrays.equals(md1, md2));\n", - " md1[4][1] = \"#$#$#$#\";\n", - " System.out.println(Arrays.deepToString(md1));\n", - " System.out.println(\"deepEquals(md1, md2): \" + Arrays.deepEquals(md1, md2));\n", - " }\n", - "}\n", - "\n", - "/* Output:\n", - "a1 == a2: true\n", - "a1 == a2: false\n", - "a1w == a2w: true\n", - "a1w == a2w: false\n", - "[[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv,\n", - " gmeinne], [btpenpc, cuxszgv, gmeinne, eloztdv]]\n", - " deepEquals(md1, md2): true\n", - " md1 == md2: false\n", - " [[], [btpenpc], [btpenpc, cuxszgv], [btpenpc, cuxszgv,\n", - " gmeinne], [btpenpc, #$#$#$#, gmeinne, eloztdv]]\n", - " deepEquals(md1, md2): false\n", - " */" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最初,a1和a2是完全相等的,所以输出是true,但是之后其中一个元素改变了,这使得结果为false。a1w和a2w是对一个封装类型数组重复该练习。\n", - "\n", - "**md1** 和 **md2** 是通过 **twoDArray()** 以相同方式初始化的多维字符串数组。注意,**deepEquals()** 返回 **true**,因为它执行了适当的比较,而普通的 **equals()** 错误地返回 **false**。如果我们更改数组中的一个元素,**deepEquals()** 将检测它。\n", - "\n", - "\n", - "## 流和数组\n", - "\n", - "**stream()** 方法很容易从某些类型的数组中生成元素流。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/StreamFromArray.java\n", - "\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class StreamFromArray {\n", - " public static void main(String[] args) {\n", - " String[] s = new Rand.String().array(10);\n", - " Arrays.stream(s).skip(3).limit(5).map(ss -> ss + \"!\").forEach(System.out::println);\n", - " int[] ia = new Rand.Pint().array(10);\n", - " Arrays.stream(ia).skip(3).limit(5)\n", - " .map(i -> i * 10).forEach(System.out::println);\n", - " Arrays.stream(new long[10]);\n", - " Arrays.stream(new double[10]);\n", - " // Only int, long and double work:\n", - " // - Arrays.stream(new boolean[10]);\n", - " // - Arrays.stream(new byte[10]);\n", - " // - Arrays.stream(new char[10]);\n", - " // - Arrays.stream(new short[10]);\n", - " // - Arrays.stream(new float[10]);\n", - " // For the other types you must use wrapped arrays:\n", - " float[] fa = new Rand.Pfloat().array(10);\n", - " Arrays.stream(ConvertTo.boxed(fa));\n", - " Arrays.stream(new Rand.Float().array(10));\n", - " }\n", - "}\n", - "/* Output:\n", - " eloztdv!\n", - " ewcippc!\n", - " ygpoalk!\n", - " ljlbynx!\n", - " taprwxz!\n", - " 47200\n", - " 61770\n", - " 84790\n", - " 66560\n", - " 37680\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只有“原生类型” **int**、**long** 和 **double** 可以与 **Arrays.stream()** 一起使用;对于其他的,您必须以某种方式获得一个包装类型的数组。\n", - "\n", - "通常,将数组转换为流来生成所需的结果要比直接操作数组容易得多。请注意,即使流已经“用完”(您不能重复使用它),您仍然拥有该数组,因此您可以以其他方式使用它----包括生成另一个流。\n", - "\n", - "\n", - "## 数组排序\n", - "\n", - "根据对象的实际类型执行比较排序。一种方法是为不同的类型编写对应的排序方法,但是这样的代码不能复用。\n", - "\n", - "编程设计的一个主要目标是“将易变的元素与稳定的元素分开”,在这里,保持不变的代码是一般的排序算法,但是变化的是对象的比较方式。因此,使用策略设计模式而不是将比较代码放入许多不同的排序源码中。使用策略模式时,变化的代码部分被封装在一个单独的类(策略对象)中。\n", - "\n", - "您将一个策略对象交给相同的代码,该代码使用策略模式来实现其算法。通过这种方式,您将使用相同的排序代码,使不同的对象表达不同的比较方式。\n", - "\n", - "Java有两种方式提供比较功能。第一种方法是通过实现 **java.lang.Comparable** 接口的原生方法。这是一个简单的接口,只含有一个方法 **compareTo()**。该方法接受另一个与参数类型相同的对象作为参数,如果当前对象小于参数,则产生一个负值;如果参数相等,则产生零值;如果当前对象大于参数,则产生一个正值。\n", - "\n", - "这里有一个类,它实现了 **Comparable** 接口并演示了可比性,而且使用Java标准库方法 **Arrays.sort()**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/CompType.java\n", - "// Implementing Comparable in a class\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "import java.util.SplittableRandom;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class CompType implements Comparable {\n", - " private static int count = 1;\n", - " private static SplittableRandom r = new SplittableRandom(47);\n", - " int i;\n", - " int j;\n", - "\n", - " public CompType(int n1, int n2) {\n", - " i = n1;\n", - " j = n2;\n", - " }\n", - "\n", - " public static CompType get() {\n", - " return new CompType(r.nextInt(100), r.nextInt(100));\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " CompType[] a = new CompType[12];\n", - " Arrays.setAll(a, n -> get());\n", - " show(\"Before sorting\", a);\n", - " Arrays.sort(a);\n", - " show(\"After sorting\", a);\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " String result = \"[i = \" + i + \", j = \" + j + \"]\";\n", - " if (count++ % 3 == 0) result += \"\\n\";\n", - " return result;\n", - " }\n", - "\n", - " @Override\n", - " public int compareTo(CompType rv) {\n", - " return (i < rv.i ? -1 : (i == rv.i ? 0 : 1));\n", - " }\n", - "}\n", - "/* Output:\n", - "Before sorting: [[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] ,\n", - " [i = 56, j = 68], [i = 48, j = 93],\n", - " [i = 70, j = 7] , [i = 0, j = 25],\n", - " [i = 62, j = 34], [i = 50, j = 82] ,\n", - " [i = 31, j = 67], [i = 66, j = 54],\n", - " [i = 21, j = 6] ]\n", - "After sorting: [[i = 0, j = 25], [i = 21, j = 6], [i = 31, j = 67] ,\n", - " [i = 35, j = 37], [i = 41, j = 20], [i = 48, j = 93] ,\n", - " [i = 50, j = 82], [i = 56, j = 68], [i = 62, j = 34] ,\n", - " [i = 66, j = 54], [i = 70, j = 7], [i = 77, j = 79] ]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当您定义比较方法时,您有责任决定将一个对象与另一个对象进行比较意味着什么。这里,在比较中只使用i值和j值\n", - "将被忽略。\n", - "\n", - "**get()** 方法通过使用随机值初始化CompType对象来构建它们。在 **main()** 中,**get()** 与 **Arrays.setAll()** 一起使用,以填充一个 **CompType类型** 数组,然后对其排序。如果没有实现 **Comparable接口**,那么当您试图调用 **sort()** 时,您将在运行时获得一个 **ClassCastException** 。这是因为 **sort()** 将其参数转换为 **Comparable类型**。\n", - "\n", - "现在假设有人给了你一个没有实现 **Comparable接口** 的类,或者给了你一个实现 **Comparable接口** 的类,但是你不喜欢它的工作方式而愿意有一个不同的对于此类型的比较方法。为了解决这个问题,创建一个实现 **Comparator** 接口的单独的类(在集合一章中简要介绍)。它有两个方法,**compare()** 和 **equals()**。但是,除了特殊的性能需求外,您不需要实现 **equals()**,因为无论何时创建一个类,它都是隐式地继承自 **Object**,**Object** 有一个equals()。您可以只使用默认的 **Object equals()** 来满足接口的规范。\n", - "\n", - "集合类(注意复数;我们将在下一章节讨论它) 包含一个方法 **reverseOrder()**,它生成一个来 **Comparator**(比较器)反转自然排序顺序。这可以应用到比较对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/Reverse.java\n", - "// The Collections.reverseOrder() Comparator\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "import java.util.Collections;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class Reverse {\n", - " public static void main(String[] args) {\n", - " CompType[] a = new CompType[12];\n", - " Arrays.setAll(a, n -> CompType.get());\n", - " show(\"Before sorting\", a);\n", - " Arrays.sort(a, Collections.reverseOrder());\n", - " show(\"After sorting\", a);\n", - " }\n", - "}\n", - "/* Output:\n", - "Before sorting: [[i = 35, j = 37], [i = 41, j = 20],\n", - " [i = 77, j = 79] , [i = 56, j = 68],\n", - " [i = 48, j = 93], [i = 70, j = 7],\n", - " [i = 0, j = 25], [i = 62, j = 34],\n", - " [i = 50, j = 82] , [i = 31, j = 67],\n", - " [i = 66, j = 54], [i = 21, j = 6] ]\n", - "After sorting: [[i = 77, j = 79], [i = 70, j = 7],\n", - " [i = 66, j = 54] , [i = 62, j = 34],\n", - " [i = 56, j = 68], [i = 50, j = 82] ,\n", - " [i = 48, j = 93], [i = 41, j = 20],\n", - " [i = 35, j = 37] , [i = 31, j = 67],\n", - " [i = 21, j = 6], [i = 0, j = 25] ]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "您还可以编写自己的比较器。这个比较CompType对象基于它们的j值而不是它们的i值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ComparatorTest.java\n", - "// Implementing a Comparator for a class\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "import java.util.Comparator;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "class CompTypeComparator implements Comparator {\n", - " public int compare(CompType o1, CompType o2) {\n", - " return (o1.j < o2.j ? -1 : (o1.j == o2.j ? 0 : 1));\n", - " }\n", - "}\n", - "\n", - "public class ComparatorTest {\n", - " public static void main(String[] args) {\n", - " CompType[] a = new CompType[12];\n", - " Arrays.setAll(a, n -> CompType.get());\n", - " show(\"Before sorting\", a);\n", - " Arrays.sort(a, new CompTypeComparator());\n", - " show(\"After sorting\", a);\n", - " }\n", - "}\n", - "/* Output:\n", - "Before sorting:[[i = 35, j = 37], [i = 41, j = 20], [i = 77, j = 79] ,\n", - " [i = 56, j = 68], [i = 48, j = 93], [i = 70, j = 7] ,\n", - " [i = 0, j = 25], [i = 62, j = 34], [i = 50, j = 82],\n", - " [i = 31, j = 67], [i = 66, j = 54], [i = 21, j = 6] ]\n", - "After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] ,\n", - " [i = 0, j = 25], [i = 62, j = 34], [i = 35, j = 37] ,\n", - " [i = 66, j = 54], [i = 31, j = 67], [i = 56, j = 68] ,\n", - " [i = 77, j = 79], [i = 50, j = 82], [i = 48, j = 93] ]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Arrays.sort()的使用\n", - "\n", - "使用内置的排序方法,您可以对实现了 **Comparable** 接口或具有 **Comparator** 的任何对象数组 或 任何原生数组进行排序。这里我们生成一个随机字符串对象数组并对其排序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/StringSorting.java\n", - "// Sorting an array of Strings\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "import java.util.Collections;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class StringSorting {\n", - " public static void main(String[] args) {\n", - " String[] sa = new Rand.String().array(20);\n", - " show(\"Before sort\", sa);\n", - " Arrays.sort(sa);\n", - " show(\"After sort\", sa);\n", - " Arrays.sort(sa, Collections.reverseOrder());\n", - " show(\"Reverse sort\", sa);\n", - " Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);\n", - " show(\"Case-insensitive sort\", sa);\n", - " }\n", - "}\n", - "/* Output:\n", - "Before sort: [btpenpc, cuxszgv, gmeinne, eloztdv, ewcippc,\n", - " ygpoalk, ljlbynx, taprwxz, bhmupju, cjwzmmr,\n", - " anmkkyh, fcjpthl, skddcat, jbvlgwc, mvducuj,\n", - " ydpulcq, zehpfmm, zrxmclh, qgekgly, hyoubzl]\n", - "\n", - "After sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv,\n", - " eloztdv, ewcippc, fcjpthl, gmeinne, hyoubzl,\n", - " jbvlgwc, ljlbynx, mvducuj, qgekgly, skddcat,\n", - " taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh]\n", - "\n", - "Reverse sort: [zrxmclh, zehpfmm, ygpoalk, ydpulcq,taprwxz,\n", - " skddcat, qgekgly, mvducuj, ljlbynx, jbvlgwc,\n", - " hyoubzl, gmeinne, fcjpthl, ewcippc, eloztdv,\n", - " cuxszgv, cjwzmmr, btpenpc, bhmupju, anmkkyh]\n", - "\n", - "Case-insensitive sort: [anmkkyh, bhmupju, btpenpc, cjwzmmr,\n", - " cuxszgv, eloztdv, ewcippc, fcjpthl, gmeinne,\n", - " hyoubzl, jbvlgwc, ljlbynx, mvducuj, qgekgly,\n", - " skddcat, taprwxz, ydpulcq, ygpoalk, zehpfmm, zrxmclh]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意字符串排序算法中的输出。它是字典式的,所以它把所有以大写字母开头的单词放在前面,然后是所有以小写字母开头的单词。(电话簿通常是这样分类的。)无论大小写,要将单词组合在一起,请使用 **String.CASE_INSENSITIVE_ORDER** ,如对sort()的最后一次调用所示。\n", - "\n", - "Java标准库中使用的排序算法被设计为最适合您正在排序的类型----原生类型的快速排序和对象的归并排序。\n", - "\n", - "\n", - "\n", - "## 并行排序\n", - "\n", - "如果排序性能是一个问题,那么可以使用 **Java 8 parallelSort()**,它为所有不可预见的情况(包括数组的排序区域或使用了比较器)提供了重载版本。为了查看相比于普通的sort(), **parallelSort()** 的优点,我们使用了用来验证代码时的 **JMH**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/jmh/ParallelSort.java\n", - "package arrays.jmh;\n", - "\n", - "import onjava.*;\n", - "import org.openjdk.jmh.annotations.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "@State(Scope.Thread)\n", - "public class ParallelSort {\n", - " private long[] la;\n", - "\n", - " @Setup\n", - " public void setup() {\n", - " la = new Rand.Plong().array(100_000);\n", - " }\n", - "\n", - " @Benchmark\n", - " public void sort() {\n", - " Arrays.sort(la);\n", - " }\n", - "\n", - " @Benchmark\n", - " public void parallelSort() {\n", - " Arrays.parallelSort(la);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**parallelSort()** 算法将大数组拆分成更小的数组,直到数组大小达到极限,然后使用普通的 **Arrays .sort()** 方法。然后合并结果。该算法需要不大于原始数组的额外工作空间。\n", - "\n", - "您可能会看到不同的结果,但是在我的机器上,并行排序将速度提高了大约3倍。由于并行版本使用起来很简单,所以很容易考虑在任何地方使用它,而不是\n", - "**Arrays.sort ()**。当然,它可能不是那么简单—看看微基准测试。\n", - "\n", - "\n", - "\n", - "## binarySearch二分查找\n", - "\n", - "一旦数组被排序,您就可以通过使用 **Arrays.binarySearch()** 来执行对特定项的快速搜索。但是,如果尝试在未排序的数组上使用 **binarySearch()**,结果是不可预测的。下面的示例使用 **Rand.Pint** 类来创建一个填充随机整形值的数组,然后调用 **getAsInt()** (因为 **Rand.Pint** 是一个 **IntSupplier**)来产生搜索值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ArraySearching.java\n", - "// Using Arrays.binarySearch()\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class ArraySearching {\n", - " public static void main(String[] args) {\n", - " Rand.Pint rand = new Rand.Pint();\n", - " int[] a = new Rand.Pint().array(25);\n", - " Arrays.sort(a);\n", - " show(\"Sorted array\", a);\n", - " while (true) {\n", - " int r = rand.getAsInt();\n", - " int location = Arrays.binarySearch(a, r);\n", - " if (location >= 0) {\n", - " System.out.println(\"Location of \" + r + \" is \" + location + \", a[\" + location + \"] is \" + a[location]);\n", - " break; // Out of while loop\n", - " }\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "Sorted array: [125, 267, 635, 650, 1131, 1506, 1634, 2400, 2766,\n", - " 3063, 3768, 3941, 4720, 4762, 4948, 5070, 5682,\n", - " 5807, 6177, 6193, 6656, 7021, 8479, 8737, 9954]\n", - "Location of 635 is 2, a[2] is 635\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在while循环中,随机值作为搜索项生成,直到在数组中找到其中一个为止。\n", - "\n", - "如果找到了搜索项,**Arrays.binarySearch()** 将生成一个大于或等于零的值。否则,它将产生一个负值,表示如果手动维护已排序的数组,则应该插入元素的位置。产生的值是 -(插入点) - 1 。插入点是大于键的第一个元素的索引,如果数组中的所有元素都小于指定的键,则是 **a.size()** 。\n", - "\n", - "如果数组包含重复的元素,则无法保证找到其中的那些重复项。搜索算法不是为了支持重复的元素,而是为了容忍它们。如果需要没有重复元素的排序列表,可以使用 **TreeSet** (用于维持排序顺序)或 **LinkedHashSet** (用于维持插入顺序)。这些类自动为您处理所有的细节。只有在出现性能瓶颈的情况下,才应该使用手工维护的数组替换这些类中的一个。\n", - "\n", - "如果使用比较器(原语数组不允许使用比较器进行排序)对对象数组进行排序,那么在执行 **binarySearch()** (使用重载版本的binarySearch())时必须包含相同的比较器。例如,可以修改 **StringSorting.java** 来执行搜索:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/AlphabeticSearch.java\n", - "// Searching with a Comparator\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class AlphabeticSearch {\n", - " public static void main(String[] args) {\n", - " String[] sa = new Rand.String().array(30);\n", - " Arrays.sort(sa, String.CASE_INSENSITIVE_ORDER);\n", - " show(sa);\n", - " int index = Arrays.binarySearch(sa, sa[10], String.CASE_INSENSITIVE_ORDER);\n", - " System.out.println(\"Index: \" + index + \"\\n\" + sa[index]);\n", - " }\n", - "}\n", - "/* Output:\n", - "[anmkkyh, bhmupju, btpenpc, cjwzmmr, cuxszgv, eloztdv, ewcippc,\n", - "ezdeklu, fcjpthl, fqmlgsh, gmeinne, hyoubzl, jbvlgwc, jlxpqds,\n", - "ljlbynx, mvducuj, qgekgly, skddcat, taprwxz, uybypgp, vjsszkn,\n", - "vniyapk, vqqakbm, vwodhcf, ydpulcq, ygpoalk, yskvett, zehpfmm,\n", - "zofmmvm, zrxmclh]\n", - "Index: 10 gmeinne\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "比较器必须作为第三个参数传递给重载的 **binarySearch()** 。在本例中,成功是有保证的,因为搜索项是从数组本身中选择的。\n", - "\n", - "\n", - "## parallelPrefix并行前缀\n", - "\n", - "没有“prefix()”方法,只有 **parallelPrefix()**。这类似于 **Stream** 类中的 **reduce()** 方法:它对前一个元素和当前元素执行一个操作,并将结果放入当前元素位置:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ParallelPrefix1.java\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class ParallelPrefix1 {\n", - " public static void main(String[] args) {\n", - " int[] nums = new Count.Pint().array(10);\n", - " show(nums);\n", - " System.out.println(Arrays.stream(nums).reduce(Integer::sum).getAsInt());\n", - " Arrays.parallelPrefix(nums, Integer::sum);\n", - " show(nums);\n", - " System.out.println(Arrays.stream(new Count.Pint().array(6)).reduce(Integer::sum).getAsInt());\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", - "45\n", - "[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]\n", - "15\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里我们对数组应用Integer::sum。在位置0中,它将先前计算的值(因为没有先前的值)与原始数组位置0中的值组合在一起。在位置1中,它获取之前计算的值(它只是存储在位置0中),并将其与位置1中先前计算的值相结合。依次往复。\n", - "\n", - "使用 **Stream.reduce()**,您只能得到最终结果,而使用 **Arrays.parallelPrefix()**,您还可以得到所有中间计算,以确保它们是有用的。注意,第二个 **Stream.reduce()** 计算的结果已经在 **parallelPrefix()** 计算的数组中。\n", - "\n", - "使用字符串可能更清楚:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ParallelPrefix2.java\n", - "\n", - "import onjava.*;\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "import static onjava.ArrayShow.*;\n", - "\n", - "public class ParallelPrefix2 {\n", - " public static void main(String[] args) {\n", - " String[] strings = new Rand.String(1).array(8);\n", - " show(strings);\n", - " Arrays.parallelPrefix(strings, (a, b) -> a + b);\n", - " show(strings);\n", - " }\n", - "}\n", - "/* Output:\n", - "[b, t, p, e, n, p, c, c]\n", - "[b, bt, btp, btpe, btpen, btpenp, btpenpc, btpenpcc]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如前所述,使用流进行初始化非常优雅,但是对于大型数组,这种方法可能会耗尽堆空间。使用 **setAll()** 执行初始化更节省内存:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "JAVA" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// arrays/ParallelPrefix3.java\n", - "// {ExcludeFromTravisCI}\n", - "\n", - "import java.util.Arrays;\n", - "\n", - "public class ParallelPrefix3 {\n", - " static final int SIZE = 10_000_000;\n", - "\n", - " public static void main(String[] args) {\n", - " long[] nums = new long[SIZE];\n", - " Arrays.setAll(nums, n -> n);\n", - " Arrays.parallelPrefix(nums, Long::sum);\n", - " System.out.println(\"First 20: \" + nums[19]);\n", - " System.out.println(\"First 200: \" + nums[199]);\n", - " System.out.println(\"All: \" + nums[nums.length - 1]);\n", - " }\n", - "}\n", - "/* Output:\n", - "First 20: 190\n", - "First 200: 19900\n", - "All: 49999995000000\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为正确使用 **parallelPrefix()** 可能相当复杂,所以通常应该只在存在内存或速度问题(或两者都有)时使用。否则,**Stream.reduce()** 应该是您的首选。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "Java为固定大小的低级数组提供了合理的支持。这种数组强调的是性能而不是灵活性,就像C和c++数组模型一样。在Java的最初版本中,固定大小的低级数组是绝对必要的,这不仅是因为Java设计人员选择包含原生类型(也考虑到性能),还因为那个版本对集合的支持非常少。因此,在早期的Java版本中,选择数组总是合理的。\n", - "\n", - "在Java的后续版本中,集合支持得到了显著的改进,现在集合在除性能外的所有方面都优于数组,即使这样,集合的性能也得到了显著的改进。正如本书其他部分所述,无论如何,性能问题通常不会出现在您设想的地方。\n", - "\n", - "使用自动装箱和泛型,在集合中保存原生类型是毫不费力的,这进一步鼓励您用集合替换低级数组。由于泛型产生类型安全的集合,数组在这方面也不再有优势。\n", - "\n", - "如本章所述,当您尝试使用泛型时,您将看到泛型对数组是相当不友好的。通常,即使可以让泛型和数组以某种形式一起工作(在下一章中您将看到),在编译期间仍然会出现“unchecked”警告。\n", - "\n", - "有几次,当我们讨论特定的例子时,我直接从Java语言设计人员那里听到我应该使用集合而不是数组(我使用数组来演示特定的技术,所以我没有这个选项)。\n", - "\n", - "所有这些问题都表明,在使用Java的最新版本进行编程时,应该“优先选择集合而不是数组”。只有当您证明性能是一个问题(并且切换到一个数组实际上会有很大的不同)时,才应该重构到数组。这是一个相当大胆的声明,但是有些语言根本没有固定大小的低级数组。它们只有可调整大小的集合,而且比C/C++/java风格的数组功能多得多。例如,Python有一个使用基本数组语法的列表类型,但是具有更大的功能—您甚至可以从它继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Python" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "# arrays/PythonLists.py\n", - "\n", - "aList=[1,2,3,4,5]print(type(aList)) #\n", - "print(aList) # [1,2,3,4,5]\n", - " print(aList[4]) # 5Basic list indexing\n", - " aList.append(6) # lists can be resized\n", - " aList+=[7,8] # Add a list to a list\n", - " print(aList) # [1,2,3,4,5,6,7,8]\n", - " aSlice=aList[2:4]\n", - " print(aSlice) # [3,4]\n", - "\n", - "class MyList(list): # Inherit from list\n", - " # Define a method;'this'pointer is explicit:\n", - " def getReversed(self):\n", - " reversed=self[:] # Copy list using slices\n", - " reversed.reverse() # Built-in list method\n", - " return reversed\n", - " # No'new'necessary for object creation:\n", - " list2=MyList(aList)\n", - " print(type(list2)) #\n", - " print(list2.getReversed()) # [8,7,6,5,4,3,2,1]\n", - " output=\"\"\"\n", - " \n", - " [1, 2, 3, 4, 5]\n", - " 5\n", - " [1, 2, 3, 4, 5, 6, 7, 8]\n", - " [3, 4]\n", - " \n", - " [8, 7, 6, 5, 4, 3, 2, 1]\n", - " \"\"\"\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "前一章介绍了基本的Python语法。在这里,通过用方括号包围以逗号分隔的对象序列来创建列表。结果是一个运行时类型为list的对象(print语句的输出显示为同一行中的注释)。打印列表的结果与在Java中使用Arrays.toString()的结果相同。\n", - "通过将 : 操作符放在索引操作中,通过切片来创建列表的子序列。list类型有更多的内置操作,通常只需要序列类型。\n", - "MyList是一个类定义;基类放在括号内。在类内部,def语句生成方法,该方法的第一个参数在Java中自动与之等价,除了在Python中它是显式的,而且标识符self是按约定使用的(它不是关键字)。注意构造函数是如何自动继承的。\n", - "\n", - "虽然一切在Python中真的是一个对象(包括整数和浮点类型),你仍然有一个安全门,因为你可以优化性能关键型的部分代码编写扩展的C, c++或使用特殊的工具设计容易加速您的Python代码(有很多)。通过这种方式,可以在不影响性能改进的情况下保持对象的纯度。\n", - "\n", - "PHP甚至更进一步,它只有一个数组类型,既充当int索引数组,又充当关联数组(Map)。\n", - "\n", - "在经历了这么多年的Java发展之后,我们可以很有趣地推测,如果重新开始,设计人员是否会将原生类型和低级数组放在该语言中(同样在JVM上运行的Scala语言不包括这些)。如果不考虑这些,就有可能开发出一种真正纯粹的面向对象语言(尽管有这样的说法,Java并不是一种纯粹的面向对象语言,这正是因为它的底层缺陷)。关于效率的最初争论总是令人信服的,但是随着时间的推移,我们已经看到了从这个想法向更高层次的组件(如集合)的演进。此外,如果集合可以像在某些语言中一样构建到核心语言中,那么编译器就有更好的机会进行优化。\n", - "\n", - "撇开““Green-fields”的推测不谈,我们肯定会被数组所困扰,当你阅读代码时就会看到它们。然而,集合几乎总是更好的选择。\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/22-Enumerations.ipynb b/jupyter/22-Enumerations.ipynb deleted file mode 100644 index feba10e5..00000000 --- a/jupyter/22-Enumerations.ipynb +++ /dev/null @@ -1,3076 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第二十二章 枚举\n", - "\n", - "> 关键字 enum 可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。这是一种非常有用的功能\n", - "\n", - "在[初始化和清理 ]() 这章结束的时候,我们已经简单地介绍了枚举的概念。现在,你对 Java 已经有了更深刻的理解,因此可以更深入地学习 Java 中的枚举了。你将在本章中看到,使用 enum 可以做很多有趣的事情,同时,我们也会深入其他的 Java 特性,例如泛型和反射。在这个过程中,我们还将学习一些设计模式。\n", - "\n", - "\n", - "\n", - "## 基本 enum 特性\n", - "\n", - "我们已经在[初始化和清理 ]() 这章章看到,调用 enum 的 values() 方法,可以遍历 enum 实例 .values() 方法返回 enum 实例的数组,而且该数组中的元素严格保持其在 enum 中声明时的顺序,因此你可以在循环中使用 values() 返回的数组。\n", - "\n", - "创建 enum 时,编译器会为你生成一个相关的类,这个类继承自 Java.lang.Enum。下面的例子演示了 Enum 提供的一些功能:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/EnumClass.java\n", - "// Capabilities of the Enum class\n", - "enum Shrubbery { GROUND, CRAWLING, HANGING }\n", - "public class EnumClass {\n", - " public static void main(String[] args) {\n", - " for(Shrubbery s : Shrubbery.values()) {\n", - " System.out.println(\n", - " s + \" ordinal: \" + s.ordinal());\n", - " System.out.print(\n", - " s.compareTo(Shrubbery.CRAWLING) + \" \");\n", - " System.out.print(\n", - " s.equals(Shrubbery.CRAWLING) + \" \");\n", - " System.out.println(s == Shrubbery.CRAWLING);\n", - " System.out.println(s.getDeclaringClass());\n", - " System.out.println(s.name());\n", - " System.out.println(\"********************\");\n", - " }\n", - "// Produce an enum value from a String name:\n", - " for(String s :\n", - " \"HANGING CRAWLING GROUND\".split(\" \")) {\n", - " Shrubbery shrub =\n", - " Enum.valueOf(Shrubbery.class, s);\n", - " System.out.println(shrub);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "GROUND ordinal: 0\n", - "-1 false false\n", - "class Shrubbery\n", - "GROUND\n", - "********************\n", - "CRAWLING ordinal: 1\n", - "0 true true\n", - "class Shrubbery\n", - "CRAWLING\n", - "********************\n", - "HANGING ordinal: 2\n", - "1 false false\n", - "class Shrubbery\n", - "HANGING\n", - "********************\n", - "HANGING\n", - "CRAWLING\n", - "GROUND" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "ordinal() 方法返回一个 int 值,这是每个 enum 实例在声明时的次序,从 0 开始。可以使用==来比较 enum 实例,编译器会自动为你提供 equals() 和 hashCode() 方法。Enum 类实现了 Comparable 接口,所以它具有 compareTo() 方法。同时,它还实现了 Serializable 接口。\n", - "\n", - "如果在 enum 实例上调用 getDeclaringClass() 方法,我们就能知道其所属的 enum 类。\n", - "\n", - "name() 方法返回 enum 实例声明时的名字,这与使用 toString() 方法效果相同。valueOf() 是在 Enum 中定义的 static 方法,它根据给定的名字返回相应的 enum 实例,如果不存在给定名字的实例,将会抛出异常。\n", - "\n", - "### 将静态类型导入用于 enum\n", - "\n", - "先看一看 [初始化和清理 ]() 这章中 Burrito.java 的另一个版本:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/SpicinessEnum.java\n", - "package enums;\n", - "public enum SpicinessEnum {\n", - " NOT, MILD, MEDIUM, HOT, FLAMING\n", - "}\n", - "// enums/Burrito2.java\n", - "// {java enums.Burrito2}\n", - "package enums;\n", - "import static enums.SpicinessEnum.*;\n", - "public class Burrito2 {\n", - " SpicinessEnum degree;\n", - " public Burrito2(SpicinessEnum degree) {\n", - " this.degree = degree;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"Burrito is \"+ degree;\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(new Burrito2(NOT));\n", - " System.out.println(new Burrito2(MEDIUM));\n", - " System.out.println(new Burrito2(HOT));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Burrito is NOT\n", - "Burrito is MEDIUM\n", - "Burrito is HOT" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 static import 能够将 enum 实例的标识符带入当前的命名空间,所以无需再用 enum 类型来修饰 enum 实例。这是一个好的想法吗?或者还是显式地修饰 enum 实例更好?这要看代码的复杂程度了。编译器可以确保你使用的是正确的类型,所以唯一需要担心的是,使用静态导入会不会导致你的代码令人难以理解。多数情况下,使用 static import 还是有好处的,不过,程序员还是应该对具体情况进行具体分析。\n", - "\n", - "注意,在定义 enum 的同一个文件中,这种技巧无法使用,如果是在默认包中定义 enum,这种技巧也无法使用(在 Sun 内部对这一点显然也有不同意见)。\n", - "\n", - "\n", - "\n", - "## 方法添加\n", - "\n", - "除了不能继承自一个 enum 之外,我们基本上可以将 enum 看作一个常规的类。也就是说我们可以向 enum 中添加方法。enum 甚至可以有 main() 方法。\n", - "\n", - "一般来说,我们希望每个枚举实例能够返回对自身的描述,而不仅仅只是默认的 toString() 实现,这只能返回枚举实例的名字。为此,你可以提供一个构造器,专门负责处理这个额外的信息,然后添加一个方法,返回这个描述信息。看一看下面的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/OzWitch.java\n", - "// The witches in the land of Oz\n", - "public enum OzWitch {\n", - " // Instances must be defined first, before methods:\n", - " WEST(\"Miss Gulch, aka the Wicked Witch of the West\"),\n", - " NORTH(\"Glinda, the Good Witch of the North\"),\n", - " EAST(\"Wicked Witch of the East, wearer of the Ruby \" +\n", - " \"Slippers, crushed by Dorothy's house\"),\n", - " SOUTH(\"Good by inference, but missing\");\n", - " private String description;\n", - " // Constructor must be package or private access:\n", - " private OzWitch(String description) {\n", - " this.description = description;\n", - " }\n", - " public String getDescription() { return description; }\n", - " public static void main(String[] args) {\n", - " for(OzWitch witch : OzWitch.values())\n", - " System.out.println(\n", - " witch + \": \" + witch.getDescription());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "WEST: Miss Gulch, aka the Wicked Witch of the West\n", - "NORTH: Glinda, the Good Witch of the North\n", - "EAST: Wicked Witch of the East, wearer of the Ruby\n", - "Slippers, crushed by Dorothy's house\n", - "SOUTH: Good by inference, but missing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,如果你打算定义自己的方法,那么必须在 enum 实例序列的最后添加一个分号。同时,Java 要求你必须先定义 enum 实例。如果在定义 enum 实例之前定义了任何方法或属性,那么在编译时就会得到错误信息。\n", - "\n", - "enum 中的构造器与方法和普通的类没有区别,因为除了有少许限制之外,enum 就是一个普通的类。所以,我们可以使用 enum 做许多事情(虽然,我们一般只使用普通的枚举类型)\n", - "\n", - "在这个例子中,虽然我们有意识地将 enum 的构造器声明为 private,但对于它的可访问性而言,其实并没有什么变化,因为(即使不声明为 private)我们只能在 enum 定义的内部使用其构造器创建 enum 实例。一旦 enum 的定义结束,编译器就不允许我们再使用其构造器来创建任何实例了。\n", - "\n", - "### 覆盖 enum 的方法\n", - "\n", - "覆盖 toSring() 方法,给我们提供了另一种方式来为枚举实例生成不同的字符串描述信息。\n", - "在下面的示例中,我们使用的就是实例的名字,不过我们希望改变其格式。覆盖 enum 的 toSring() 方法与覆盖一般类的方法没有区别:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/SpaceShip.java\n", - "import java.util.stream.*;\n", - "public enum SpaceShip {\n", - " SCOUT, CARGO, TRANSPORT,\n", - " CRUISER, BATTLESHIP, MOTHERSHIP;\n", - " @Override\n", - " public String toString() {\n", - " String id = name();\n", - " String lower = id.substring(1).toLowerCase();\n", - " return id.charAt(0) + lower;\n", - " }\n", - " public static void main(String[] args) {\n", - " Stream.of(values())\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Scout\n", - "Cargo\n", - "Transport\n", - "Cruiser\n", - "Battleship\n", - "Mothership" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "toString() 方法通过调用 name() 方法取得 SpaceShip 的名字,然后将其修改为只有首字母大写的格式。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "## switch 语句中的 enum\n", - "\n", - "在 switch 中使用 enum,是 enum 提供的一项非常便利的功能。一般来说,在 switch 中只能使用整数值,而枚举实例天生就具备整数值的次序,并且可以通过 ordinal() 方法取得其次序(显然编译器帮我们做了类似的工作),因此我们可以在 switch 语句中使用 enum。\n", - "\n", - "虽然一般情况下我们必须使用 enum 类型来修饰一个 enum 实例,但是在 case 语句中却不必如此。下面的例子使用 enum 构造了一个小型状态机:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/TrafficLight.java\n", - "// Enums in switch statements\n", - "// Define an enum type:\n", - "enum Signal { GREEN, YELLOW, RED, }\n", - "\n", - "public class TrafficLight {\n", - " Signal color = Signal.RED;\n", - " public void change() {\n", - " switch(color) {\n", - " // Note you don't have to say Signal.RED\n", - " // in the case statement:\n", - " case RED: color = Signal.GREEN;\n", - " break;\n", - " case GREEN: color = Signal.YELLOW;\n", - " break;\n", - " case YELLOW: color = Signal.RED;\n", - " break;\n", - " }\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"The traffic light is \" + color;\n", - " }\n", - " public static void main(String[] args) {\n", - " TrafficLight t = new TrafficLight();\n", - " for(int i = 0; i < 7; i++) {\n", - " System.out.println(t);\n", - " t.change();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "The traffic light is RED\n", - "The traffic light is GREEN\n", - "The traffic light is YELLOW\n", - "The traffic light is RED\n", - "The traffic light is GREEN\n", - "The traffic light is YELLOW\n", - "The traffic light is RED" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "编译器并没有抱怨 switch 中没有 default 语句,但这并不是因为每一个 Signal 都有对应的 case 语句。如果你注释掉其中的某个 case 语句,编译器同样不会抱怨什么。这意味着,你必须确保自己覆盖了所有的分支。但是,如果在 case 语句中调用 return,那么编译器就会抱怨缺少 default 语句了。这与是否覆盖了 enum 的所有实例无关。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "## values 方法的神秘之处\n", - "\n", - "前面已经提到,编译器为你创建的 enum 类都继承自 Enum 类。然而,如果你研究一下 Enum 类就会发现,它并没有 values() 方法。可我们明明已经用过该方法了,难道存在某种“隐藏的”方法吗?我们可以利用反射机制编写一个简单的程序,来查看其中的究竟:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/Reflection.java\n", - "// Analyzing enums using reflection\n", - "import java.lang.reflect.*;\n", - "import java.util.*;\n", - "import onjava.*;\n", - "enum Explore { HERE, THERE }\n", - "public class Reflection {\n", - " public static\n", - " Set analyze(Class enumClass) {\n", - " System.out.println(\n", - " \"_____ Analyzing \" + enumClass + \" _____\");\n", - " System.out.println(\"Interfaces:\");\n", - " for(Type t : enumClass.getGenericInterfaces())\n", - " System.out.println(t);\n", - " System.out.println(\n", - " \"Base: \" + enumClass.getSuperclass());\n", - " System.out.println(\"Methods: \");\n", - " Set methods = new TreeSet<>();\n", - " for(Method m : enumClass.getMethods())\n", - " methods.add(m.getName());\n", - " System.out.println(methods);\n", - " return methods;\n", - " }\n", - " public static void main(String[] args) {\n", - " Set exploreMethods =\n", - " analyze(Explore.class);\n", - " Set enumMethods = analyze(Enum.class);\n", - " System.out.println(\n", - " \"Explore.containsAll(Enum)? \" +\n", - " exploreMethods.containsAll(enumMethods));\n", - " System.out.print(\"Explore.removeAll(Enum): \");\n", - " exploreMethods.removeAll(enumMethods);\n", - " System.out.println(exploreMethods);\n", - "// Decompile the code for the enum:\n", - " OSExecute.command(\n", - " \"javap -cp build/classes/main Explore\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "_____ Analyzing class Explore _____\n", - "Interfaces:\n", - "Base: class java.lang.Enum\n", - "Methods:\n", - "[compareTo, equals, getClass, getDeclaringClass,\n", - "hashCode, name, notify, notifyAll, ordinal, toString,\n", - "valueOf, values, wait]\n", - "_____ Analyzing class java.lang.Enum _____\n", - "Interfaces:\n", - "java.lang.Comparable\n", - "interface java.io.Serializable\n", - "Base: class java.lang.Object\n", - "Methods:\n", - "[compareTo, equals, getClass, getDeclaringClass,\n", - "hashCode, name, notify, notifyAll, ordinal, toString,\n", - "valueOf, wait]\n", - "Explore.containsAll(Enum)? true\n", - "Explore.removeAll(Enum): [values]\n", - "Compiled from \"Reflection.java\"\n", - "final class Explore extends java.lang.Enum {\n", - " public static final Explore HERE;\n", - " public static final Explore THERE;\n", - " public static Explore[] values();\n", - " public static Explore valueOf(java.lang.String);\n", - " static {};\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "答案是,values() 是由编译器添加的 static 方法。可以看出,在创建 Explore 的过程中,编译器还为其添加了 valueOf() 方法。这可能有点令人迷惑,Enum 类不是已经有 valueOf() 方法了吗。\n", - "\n", - "不过 Enum 中的 valueOf() 方法需要两个参数,而这个新增的方法只需一个参数。由于这里使用的 Set 只存储方法的名字,而不考虑方法的签名,所以在调用 Explore.removeAll(Enum) 之后,就只剩下[values] 了。\n", - "\n", - "从最后的输出中可以看到,编译器将 Explore 标记为 final 类,所以无法继承自 enum,其中还有一个 static 的初始化子句,稍后我们将学习如何重定义该句。\n", - "\n", - "由于擦除效应(在[泛型 ]() 章节中介绍过),反编译无法得到 Enum 的完整信息,所以它展示的 Explore 的父类只是一个原始的 Enum,而非事实上的 Enum\\。\n", - "\n", - "由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants0 方法,所以即便 Enum 接口中没有 values0 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/UpcastEnum.java\n", - "// No values() method if you upcast an enum\n", - "enum Search { HITHER, YON }\n", - "public class UpcastEnum {\n", - " public static void main(String[] args) {\n", - " Search[] vals = Search.values();\n", - " Enum e = Search.HITHER; // Upcast\n", - "// e.values(); // No values() in Enum\n", - " for(Enum en : e.getClass().getEnumConstants())\n", - " System.out.println(en);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HITHER\n", - "YON" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 getEnumConstants() 是 Class 上的方法,所以你甚至可以对不是枚举的类调用此方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/NonEnum.java\n", - "public class NonEnum {\n", - " public static void main(String[] args) {\n", - " Class intClass = Integer.class;\n", - " try {\n", - " for(Object en : intClass.getEnumConstants())\n", - " System.out.println(en);\n", - " } catch(Exception e) {\n", - " System.out.println(\"Expected: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Expected: java.lang.NullPointerException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只不过,此时该方法返回 null,所以当你试图使用其返回的结果时会发生异常。\n", - "\n", - "\n", - "\n", - "## 实现而非继承\n", - "\n", - "我们已经知道,所有的 enum 都继承自 Java.lang.Enum 类。由于 Java 不支持多重继承,所以你的 enum 不能再继承其他类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "enum NotPossible extends Pet { ... // Won't work" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然而,在我们创建一个新的 enum 时,可以同时实现一个或多个接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/cartoons/EnumImplementation.java\n", - "// An enum can implement an interface\n", - "// {java enums.cartoons.EnumImplementation}\n", - "package enums.cartoons;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "enum CartoonCharacter\n", - " implements Supplier {\n", - " SLAPPY, SPANKY, PUNCHY,\n", - " SILLY, BOUNCY, NUTTY, BOB;\n", - " private Random rand =\n", - " new Random(47);\n", - " @Override\n", - " public CartoonCharacter get() {\n", - " return values()[rand.nextInt(values().length)];\n", - " }\n", - "}\n", - "public class EnumImplementation {\n", - " public static void printNext(Supplier rg) {\n", - " System.out.print(rg.get() + \", \");\n", - " }\n", - " public static void main(String[] args) {\n", - "// Choose any instance:\n", - " CartoonCharacter cc = CartoonCharacter.BOB;\n", - " for(int i = 0; i < 10; i++)\n", - " printNext(cc);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "BOB, PUNCHY, BOB, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY,\n", - "NUTTY, SLAPPY," - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个结果有点奇怪,不过你必须要有一个 enum 实例才能调用其上的方法。现在,在任何接受 Supplier 参数的方法中,例如 printNext(),都可以使用 CartoonCharacter。\n", - "\n", - "\n", - "\n", - "## 随机选择\n", - "\n", - "就像你在 CartoonCharacter.get() 中看到的那样,本章中的很多示例都需要从 enum 实例中进行随机选择。我们可以利用泛型,从而使得这个工作更一般化,并将其加入到我们的工具库中。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Enums.java\n", - "package onjava;\n", - "import java.util.*;\n", - "public class Enums {\n", - " private static Random rand = new Random(47);\n", - " \n", - " public static > T random(Class ec) {\n", - " return random(ec.getEnumConstants());\n", - " }\n", - " \n", - " public static T random(T[] values) {\n", - " return values[rand.nextInt(values.length)];\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "古怪的语法\\\\> 表示 T 是一个 enum 实例。而将 Class\\ 作为参数的话,我们就可以利用 Class 对象得到 enum 实例的数组了。重载后的 random() 方法只需使用 T[] 作为参数,因为它并不会调用 Enum 上的任何操作,它只需从数组中随机选择一个元素即可。这样,最终的返回类型正是 enum 的类型。\n", - "\n", - "下面是 random() 方法的一个简单示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RandomTest.java\n", - "import onjava.*;\n", - "enum Activity { SITTING, LYING, STANDING, HOPPING,\n", - " RUNNING, DODGING, JUMPING, FALLING, FLYING }\n", - " \n", - "public class RandomTest {\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 20; i++)\n", - " System.out.print(\n", - " Enums.random(Activity.class) + \" \");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "STANDING FLYING RUNNING STANDING RUNNING STANDING LYING\n", - "DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING\n", - "STANDING LYING FALLING RUNNING FLYING LYING" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "## 使用接口组织枚举\n", - "\n", - "无法从 enum 继承子类有时很令人沮丧。这种需求有时源自我们希望扩展原 enum 中的元素,有时是因为我们希望使用子类将一个 enum 中的元素进行分组。\n", - "\n", - "在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组,可以达到将枚举元素分类组织的目的。举例来说,假设你想用 enum 来表示不同类别的食物,同时还希望每个 enum 元素仍然保持 Food 类型。那可以这样实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/menu/Food.java\n", - "// Subcategorization of enums within interfaces\n", - "package enums.menu;\n", - "public interface Food {\n", - " enum Appetizer implements Food {\n", - " SALAD, SOUP, SPRING_ROLLS;\n", - " }\n", - " enum MainCourse implements Food {\n", - " LASAGNE, BURRITO, PAD_THAI,\n", - " LENTILS, HUMMOUS, VINDALOO;\n", - " }\n", - " enum Dessert implements Food {\n", - " TIRAMISU, GELATO, BLACK_FOREST_CAKE,\n", - " FRUIT, CREME_CARAMEL;\n", - " }\n", - " enum Coffee implements Food {\n", - " BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,\n", - " LATTE, CAPPUCCINO, TEA, HERB_TEA;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于 enum 而言,实现接口是使其子类化的唯一办法,所以嵌入在 Food 中的每个 enum 都实现了 Food 接口。现在,在下面的程序中,我们可以说“所有东西都是某种类型的 Food\"。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/menu/TypeOfFood.java\n", - "// {java enums.menu.TypeOfFood}\n", - "package enums.menu;\n", - "import static enums.menu.Food.*;\n", - "public class TypeOfFood {\n", - " public static void main(String[] args) {\n", - " Food food = Appetizer.SALAD;\n", - " food = MainCourse.LASAGNE;\n", - " food = Dessert.GELATO;\n", - " food = Coffee.CAPPUCCINO;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果 enum 类型实现了 Food 接口,那么我们就可以将其实例向上转型为 Food,所以上例中的所有东西都是 Food。\n", - "\n", - "然而,当你需要与一大堆类型打交道时,接口就不如 enum 好用了。例如,如果你想创建一个“校举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/menu/Course.java\n", - "package enums.menu;\n", - "import onjava.*;\n", - "public enum Course {\n", - " APPETIZER(Food.Appetizer.class),\n", - " MAINCOURSE(Food.MainCourse.class),\n", - " DESSERT(Food.Dessert.class),\n", - " COFFEE(Food.Coffee.class);\n", - " private Food[] values;\n", - " private Course(Class kind) {\n", - " values = kind.getEnumConstants();\n", - " }\n", - " public Food randomSelection() {\n", - " return Enums.random(values);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每一个 Course 的实例都将其对应的 Class 对象作为构造器的参数。通过 getEnumConstants0 方法,可以从该 Class 对象中取得某个 Food 子类的所有 enum 实例。这些实例在 randomSelection() 中被用到。因此,通过从每一个 Course 实例中随机地选择一个 Food,我们便能够生成一份菜单:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/menu/Meal.java\n", - "// {java enums.menu.Meal}\n", - "package enums.menu;\n", - "public class Meal {\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 5; i++) {\n", - " for(Course course : Course.values()) {\n", - " Food food = course.randomSelection();\n", - " System.out.println(food);\n", - " }\n", - " System.out.println(\"***\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "SPRING_ROLLS\n", - "VINDALOO\n", - "FRUIT\n", - "DECAF_COFFEE\n", - "***\n", - "SOUP\n", - "VINDALOO\n", - "FRUIT\n", - "TEA\n", - "***\n", - "SALAD\n", - "BURRITO\n", - "FRUIT\n", - "TEA\n", - "***\n", - "SALAD\n", - "BURRITO\n", - "CREME_CARAMEL\n", - "LATTE\n", - "***\n", - "SOUP\n", - "BURRITO\n", - "TIRAMISU\n", - "ESPRESSO\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个例子中,我们通过遍历每一个 Course 实例来获得“枚举的枚举”的值。稍后,在 VendingMachine.java 中,我们会看到另一种组织枚举实例的方式,但其也有一些其他的限制。\n", - "\n", - "此外,还有一种更简洁的管理枚举的办法,就是将一个 enum 嵌套在另一个 enum 内。就像这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/SecurityCategory.java\n", - "// More succinct subcategorization of enums\n", - "import onjava.*;\n", - "enum SecurityCategory {\n", - " STOCK(Security.Stock.class),\n", - " BOND(Security.Bond.class);\n", - " Security[] values;\n", - " SecurityCategory(Class kind) {\n", - " values = kind.getEnumConstants();\n", - " }\n", - " interface Security {\n", - " enum Stock implements Security {\n", - " SHORT, LONG, MARGIN\n", - " }\n", - " enum Bond implements Security {\n", - " MUNICIPAL, JUNK\n", - " }\n", - " }\n", - " public Security randomSelection() {\n", - " return Enums.random(values);\n", - " }\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 10; i++) {\n", - " SecurityCategory category =\n", - " Enums.random(SecurityCategory.class);\n", - " System.out.println(category + \": \" +\n", - " category.randomSelection());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "BOND: MUNICIPAL\n", - "BOND: MUNICIPAL\n", - "STOCK: MARGIN\n", - "STOCK: MARGIN\n", - "BOND: JUNK\n", - "STOCK: SHORT\n", - "STOCK: LONG\n", - "STOCK: LONG\n", - "BOND: MUNICIPAL\n", - "BOND: JUNK" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Security 接口的作用是将其所包含的 enum 组合成一个公共类型,这一点是有必要的。然后,SecurityCategory 才能将 Security 中的 enum 作为其构造器的参数使用,以起到组织的效果。\n", - "\n", - "如果我们将这种方式应用于 Food 的例子,结果应该这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/menu/Meal2.java\n", - "// {java enums.menu.Meal2}\n", - "package enums.menu;\n", - "import onjava.*;\n", - "public enum Meal2 {\n", - " APPETIZER(Food.Appetizer.class),\n", - " MAINCOURSE(Food.MainCourse.class),\n", - " DESSERT(Food.Dessert.class),\n", - " COFFEE(Food.Coffee.class);\n", - " private Food[] values;\n", - " private Meal2(Class kind) {\n", - " values = kind.getEnumConstants();\n", - " }\n", - " public interface Food {\n", - " enum Appetizer implements Food {\n", - " SALAD, SOUP, SPRING_ROLLS;\n", - " }\n", - " enum MainCourse implements Food {\n", - " LASAGNE, BURRITO, PAD_THAI,\n", - " LENTILS, HUMMOUS, VINDALOO;\n", - " }\n", - " enum Dessert implements Food {\n", - " TIRAMISU, GELATO, BLACK_FOREST_CAKE,\n", - " FRUIT, CREME_CARAMEL;\n", - " }\n", - " enum Coffee implements Food {\n", - " BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,\n", - " LATTE, CAPPUCCINO, TEA, HERB_TEA;\n", - " }\n", - " }\n", - " public Food randomSelection() {\n", - " return Enums.random(values);\n", - " }\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < 5; i++) {\n", - " for(Meal2 meal : Meal2.values()) {\n", - " Food food = meal.randomSelection();\n", - " System.out.println(food);\n", - " }\n", - " System.out.println(\"***\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "SPRING_ROLLS\n", - "VINDALOO\n", - "FRUIT\n", - "DECAF_COFFEE\n", - "***\n", - "SOUP\n", - "VINDALOO\n", - "FRUIT\n", - "TEA\n", - "***\n", - "SALAD\n", - "BURRITO\n", - "FRUIT\n", - "TEA\n", - "***\n", - "SALAD\n", - "BURRITO\n", - "CREME_CARAMEL\n", - "LATTE\n", - "***\n", - "SOUP\n", - "BURRITO\n", - "TIRAMISU\n", - "ESPRESSO\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其实,这仅仅是重新组织了一下代码,不过多数情况下,这种方式使你的代码具有更清晰的结构。\n", - "\n", - "\n", - "\n", - "## 使用 EnumSet 替代 Flags\n", - "\n", - "Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enumi 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。\n", - "\n", - "EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。\n", - "\n", - "EnumSet 中的元素必须来自一个 enum。下面的 enum 表示在一座大楼中,警报传感器的安放位置:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/AlarmPoints.java\n", - "package enums;\n", - "public enum AlarmPoints {\n", - " STAIR1, STAIR2, LOBBY, OFFICE1, OFFICE2, OFFICE3,\n", - " OFFICE4, BATHROOM, UTILITY, KITCHEN\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,我们用 EnumSet 来跟踪报警器的状态:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/EnumSets.java\n", - "// Operations on EnumSets\n", - "// {java enums.EnumSets}\n", - "package enums;\n", - "import java.util.*;\n", - "import static enums.AlarmPoints.*;\n", - "public class EnumSets {\n", - " public static void main(String[] args) {\n", - " EnumSet points =\n", - " EnumSet.noneOf(AlarmPoints.class); // Empty\n", - " points.add(BATHROOM);\n", - " System.out.println(points);\n", - " points.addAll(\n", - " EnumSet.of(STAIR1, STAIR2, KITCHEN));\n", - " System.out.println(points);\n", - " points = EnumSet.allOf(AlarmPoints.class);\n", - " points.removeAll(\n", - " EnumSet.of(STAIR1, STAIR2, KITCHEN));\n", - " System.out.println(points);\n", - " points.removeAll(\n", - " EnumSet.range(OFFICE1, OFFICE4));\n", - " System.out.println(points);\n", - " points = EnumSet.complementOf(points);\n", - " System.out.println(points);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[BATHROOM]\n", - "[STAIR1, STAIR2, BATHROOM, KITCHEN]\n", - "[LOBBY, OFFICE1, OFFICE2, OFFICE3, OFFICE4, BATHROOM,\n", - "UTILITY]\n", - "[LOBBY, BATHROOM, UTILITY]\n", - "[STAIR1, STAIR2, OFFICE1, OFFICE2, OFFICE3, OFFICE4,\n", - "KITCHEN]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 static import 可以简化 enum 常量的使用。EnumSet 的方法的名字都相当直观,你可以查阅 JDK 文档找到其完整详细的描述。如果仔细研究了 EnumSet 的文档,你还会发现 of() 方法被重载了很多次,不但为可变数量参数进行了重载,而且为接收 2 至 5 个显式的参数的情况都进行了重载。这也从侧面表现了 EnumSet 对性能的关注。因为,其实只使用单独的 of() 方法解决可变参数已经可以解决整个问题了,但是对比显式的参数,会有一点性能损失。采用现在这种设计,当你只使用 2 到 5 个参数调用 of() 方法时,你可以调用对应的重载过的方法(速度稍快一点),而当你使用一个参数或多过 5 个参数时,你调用的将是使用可变参数的 of() 方法。注意,如果你只使用一个参数,编译器并不会构造可变参数的数组,所以与调用只有一个参数的方法相比,也就不会有额外的性能损耗。\n", - "\n", - "EnumSet 的基础是 long,一个 long 值有 64 位,而一个 enum 实例只需一位 bit 表示其是否存在。\n", - "也就是说,在不超过一个 long 的表达能力的情况下,你的 EnumSet 可以应用于最多不超过 64 个元素的 enum。如果 enum 超过了 64 个元素会发生什么呢?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/BigEnumSet.java\n", - "import java.util.*;\n", - "public class BigEnumSet {\n", - " enum Big { A0, A1, A2, A3, A4, A5, A6, A7, A8, A9,\n", - " A10, A11, A12, A13, A14, A15, A16, A17, A18, A19,\n", - " A20, A21, A22, A23, A24, A25, A26, A27, A28, A29,\n", - " A30, A31, A32, A33, A34, A35, A36, A37, A38, A39,\n", - " A40, A41, A42, A43, A44, A45, A46, A47, A48, A49,\n", - " A50, A51, A52, A53, A54, A55, A56, A57, A58, A59,\n", - " A60, A61, A62, A63, A64, A65, A66, A67, A68, A69,\n", - " A70, A71, A72, A73, A74, A75 }\n", - " public static void main(String[] args) {\n", - " EnumSet bigEnumSet = EnumSet.allOf(Big.class);\n", - " System.out.println(bigEnumSet);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12,\n", - "A13, A14, A15, A16, A17, A18, A19, A20, A21, A22, A23,\n", - "A24, A25, A26, A27, A28, A29, A30, A31, A32, A33, A34,\n", - "A35, A36, A37, A38, A39, A40, A41, A42, A43, A44, A45,\n", - "A46, A47, A48, A49, A50, A51, A52, A53, A54, A55, A56,\n", - "A57, A58, A59, A60, A61, A62, A63, A64, A65, A66, A67,\n", - "A68, A69, A70, A71, A72, A73, A74, A75]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "显然,EnumSet 可以应用于多过 64 个元素的 enum,所以我猜测,Enum 会在必要的时候增加一个 long。\n", - "\n", - "\n", - "\n", - "## 使用 EnumMap\n", - "\n", - "EnumMap 是一种特殊的 Map,它要求其中的键(key)必须来自一个 enum,由于 enum 本身的限制,所以 EnumMap 在内部可由数组实现。因此 EnumMap 的速度很快,我们可以放心地使用 enum 实例在 EnumMap 中进行查找操作。不过,我们只能将 enum 的实例作为键来调用 put() 可方法,其他操作与使用一般的 Map 差不多。\n", - "\n", - "下面的例子演示了*命令设计模式*的用法。一般来说,命令模式首先需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。接下来,程序员就可以构造命令对象,并在需要的时候使用它们了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/EnumMaps.java\n", - "// Basics of EnumMaps\n", - "// {java enums.EnumMaps}\n", - "package enums;\n", - "import java.util.*;\n", - "import static enums.AlarmPoints.*;\n", - "interface Command { void action(); }\n", - "public class EnumMaps {\n", - " public static void main(String[] args) {\n", - " EnumMap em =\n", - " new EnumMap<>(AlarmPoints.class);\n", - " em.put(KITCHEN,\n", - " () -> System.out.println(\"Kitchen fire!\"));\n", - " em.put(BATHROOM,\n", - " () -> System.out.println(\"Bathroom alert!\"));\n", - " for(Map.Entry e:\n", - " em.entrySet()) {\n", - " System.out.print(e.getKey() + \": \");\n", - " e.getValue().action();\n", - " }\n", - " try { // If there's no value for a particular key:\n", - " em.get(UTILITY).action();\n", - " } catch(Exception e) {\n", - " System.out.println(\"Expected: \" + e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "BATHROOM: Bathroom alert!\n", - "KITCHEN: Kitchen fire!\n", - "Expected: java.lang.NullPointerException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "与 EnumSet 一样,enum 实例定义时的次序决定了其在 EnumMap 中的顺序。\n", - "\n", - "main() 方法的最后部分说明,enum 的每个实例作为一个键,总是存在的。但是,如果你没有为这个键调用 put() 方法来存入相应的值的话,其对应的值就是 null。\n", - "\n", - "与常量相关的方法(constant-specific methods 将在下一节中介绍)相比,EnumMap 有一个优点,那 EnumMap 允许程序员改变值对象,而常量相关的方法在编译期就被固定了。稍后你会看到,在你有多种类型的 enum,而且它们之间存在互操作的情况下,我们可以用 EnumMap 实现多路分发(multiple dispatching)。\n", - "\n", - "\n", - "\n", - "## 常量特定方法\n", - "\n", - "Java 的 enum 有一个非常有趣的特性,即它允许程序员为 enum 实例编写方法,从而为每个 enum 实例赋予各自不同的行为。要实现常量相关的方法,你需要为 enum 定义一个或多个 abstract 方法,然后为每个 enum 实例实现该抽象方法。参考下面的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/ConstantSpecificMethod.java\n", - "import java.util.*;\n", - "import java.text.*;\n", - "public enum ConstantSpecificMethod {\n", - " DATE_TIME {\n", - " @Override\n", - " String getInfo() {\n", - " return\n", - " DateFormat.getDateInstance()\n", - " .format(new Date());\n", - " }\n", - " },\n", - " CLASSPATH {\n", - " @Override\n", - " String getInfo() {\n", - " return System.getenv(\"CLASSPATH\");\n", - " }\n", - " },\n", - " VERSION {\n", - " @Override\n", - " String getInfo() {\n", - " return System.getProperty(\"java.version\");\n", - " }\n", - " };\n", - " abstract String getInfo();\n", - " public static void main(String[] args) {\n", - " for(ConstantSpecificMethod csm : values())\n", - " System.out.println(csm.getInfo());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "May 9, 2017\n", - "C:\\Users\\Bruce\\Documents\\GitHub\\on-\n", - "java\\ExtractedExamples\\\\gradle\\wrapper\\gradle-\n", - "wrapper.jar\n", - "1.8.0_112" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过相应的 enum 实例,我们可以调用其上的方法。这通常也称为表驱动的代码(table-driven code,请注意它与前面提到的命令模式的相似之处)。\n", - "\n", - "在面向对象的程序设计中,不同的行为与不同的类关联。而通过常量相关的方法,每个 enum 实例可以具备自己独特的行为,这似乎说明每个 enum 实例就像一个独特的类。在上面的例子中,enum 实例似乎被当作其“超类”ConstantSpecificMethod 来使用,在调用 getInfo() 方法时,体现出多态的行为。\n", - "\n", - "然而,enum 实例与类的相似之处也仅限于此了。我们并不能真的将 enum 实例作为一个类型来使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/NotClasses.java\n", - "// {javap -c LikeClasses}\n", - "enum LikeClasses {\n", - " WINKEN {\n", - " @Override\n", - " void behavior() {\n", - " System.out.println(\"Behavior1\");\n", - " }\n", - " },\n", - " BLINKEN {\n", - " @Override\n", - " void behavior() {\n", - " System.out.println(\"Behavior2\");\n", - " }\n", - " },\n", - " NOD {\n", - " @Override\n", - " void behavior() {\n", - " System.out.println(\"Behavior3\");\n", - " }\n", - " };\n", - " abstract void behavior();\n", - "}\n", - "public class NotClasses {\n", - " // void f1(LikeClasses.WINKEN instance) {} // Nope\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为(前 12 行):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Compiled from \"NotClasses.java\"\n", - "abstract class LikeClasses extends\n", - "java.lang.Enum {\n", - "public static final LikeClasses WINKEN;\n", - "public static final LikeClasses BLINKEN;\n", - "public static final LikeClasses NOD;\n", - "public static LikeClasses[] values();\n", - "Code:\n", - "0: getstatic #2 // Field\n", - "$VALUES:[LLikeClasses;\n", - "3: invokevirtual #3 // Method\n", - "\"[LLikeClasses;\".clone:()Ljava/lang/Object;\n", - "..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在方法 f1() 中,编译器不允许我们将一个 enum 实例当作 class 类型。如果我们分析一下编译器生成的代码,就知道这种行为也是很正常的。因为每个 enum 元素都是一个 LikeClasses 类型的 static final 实例。\n", - "\n", - "同时,由于它们是 static 实例,无法访问外部类的非 static 元素或方法,所以对于内部的 enum 的实例而言,其行为与一般的内部类并不相同。\n", - "\n", - "再看一个更有趣的关于洗车的例子。每个顾客在洗车时,都有一个选择菜单,每个选择对应一个不同的动作。可以将一个常量相关的方法关联到一个选择上,再使用一个 EnumSet 来保存客户的选择:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/CarWash.java\n", - "import java.util.*;\n", - "public class CarWash {\n", - " public enum Cycle {\n", - " UNDERBODY {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Spraying the underbody\");\n", - " }\n", - " },\n", - " WHEELWASH {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Washing the wheels\");\n", - " }\n", - " },\n", - " PREWASH {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Loosening the dirt\");\n", - " }\n", - " },\n", - " BASIC {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"The basic wash\");\n", - " }\n", - " },\n", - " HOTWAX {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Applying hot wax\");\n", - " }\n", - " },\n", - " RINSE {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Rinsing\");\n", - " }\n", - " },\n", - " BLOWDRY {\n", - " @Override\n", - " void action() {\n", - " System.out.println(\"Blowing dry\");\n", - " }\n", - " };\n", - " abstract void action();\n", - " }\n", - " EnumSet cycles =\n", - " EnumSet.of(Cycle.BASIC, Cycle.RINSE);\n", - " public void add(Cycle cycle) {\n", - " cycles.add(cycle);\n", - " }\n", - " public void washCar() {\n", - " for(Cycle c : cycles)\n", - " c.action();\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return cycles.toString();\n", - " }\n", - " public static void main(String[] args) {\n", - " CarWash wash = new CarWash();\n", - " System.out.println(wash);\n", - " wash.washCar();\n", - "// Order of addition is unimportant:\n", - " wash.add(Cycle.BLOWDRY);\n", - " wash.add(Cycle.BLOWDRY); // Duplicates ignored\n", - " wash.add(Cycle.RINSE);\n", - " wash.add(Cycle.HOTWAX);\n", - " System.out.println(wash);\n", - " wash.washCar();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[BASIC, RINSE]\n", - "The basic wash\n", - "Rinsing\n", - "[BASIC, HOTWAX, RINSE, BLOWDRY]\n", - "The basic wash\n", - "Applying hot wax\n", - "Rinsing\n", - "Blowing dry" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "与使用匿名内部类相比较,定义常量相关方法的语法更高效、简洁。\n", - "\n", - "这个例子也展示了 EnumSet 了一些特性。因为它是一个集合,所以对于同一个元素而言,只能出现一次,因此对同一个参数重复地调用 add0 方法会被忽略掉(这是正确的行为,因为一个 bit 位开关只能“打开”一次),同样地,向 EnumSet 添加 enum 实例的顺序并不重要,因为其输出的次序决定于 enum 实例定义时的次序。\n", - "\n", - "除了实现 abstract 方法以外,程序员是否可以覆盖常量相关的方法呢?答案是肯定的,参考下面的程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/OverrideConstantSpecific.java\n", - "public enum OverrideConstantSpecific {\n", - " NUT, BOLT,\n", - " WASHER {\n", - " @Override\n", - " void f() {\n", - " System.out.println(\"Overridden method\");\n", - " }\n", - " };\n", - " void f() {\n", - " System.out.println(\"default behavior\");\n", - " }\n", - " public static void main(String[] args) {\n", - " for(OverrideConstantSpecific ocs : values()) {\n", - " System.out.print(ocs + \": \");\n", - " ocs.f();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NUT: default behavior\n", - "BOLT: default behavior\n", - "WASHER: Overridden method" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然 enum 有某些限制,但是一般而言,我们还是可以将其看作是类。\n", - "\n", - "### 使用 enum 的职责链\n", - "\n", - "在职责链(Chain of Responsibility)设计模式中,程序员以多种不同的方式来解决一个问题,然后将它们链接在一起。当一个请求到来时,它遍历这个链,直到链中的某个解决方案能够处理该请求。\n", - "\n", - "通过常量相关的方法,我们可以很容易地实现一个简单的职责链。我们以一个邮局的模型为例。邮局需要以尽可能通用的方式来处理每一封邮件,并且要不断尝试处理邮件,直到该邮件最终被确定为一封死信。其中的每一次尝试可以看作为一个策略(也是一个设计模式),而完整的处理方式列表就是一个职责链。\n", - "\n", - "我们先来描述一下邮件。邮件的每个关键特征都可以用 enum 来表示。程序将随机地生成 Mail 对象,如果要减小一封邮件的 GeneralDelivery 为 YES 的概率,那最简单的方法就是多创建几个不是 YES 的 enum 实例,所以 enum 的定义看起来有点古怪。\n", - "\n", - "我们看到 Mail 中有一个 randomMail() 方法,它负责随机地创建用于测试的邮件。而 generator() 方法生成一个 Iterable 对象,该对象在你调用 next() 方法时,在其内部使用 randomMail() 来创建 Mail 对象。这样的结构使程序员可以通过调用 Mail.generator() 方法,很容易地构造出一个 foreach 循环:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/PostOffice.java\n", - "// Modeling a post office\n", - "import java.util.*;\n", - "import onjava.*;\n", - "class Mail {\n", - " // The NO's reduce probability of random selection:\n", - " enum GeneralDelivery {YES,NO1,NO2,NO3,NO4,NO5}\n", - " enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4}\n", - " enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}\n", - " enum Address {INCORRECT,OK1,OK2,OK3,OK4,OK5,OK6}\n", - " enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OK5}\n", - " GeneralDelivery generalDelivery;\n", - " Scannability scannability;\n", - " Readability readability;\n", - " Address address;\n", - " ReturnAddress returnAddress;\n", - " static long counter = 0;\n", - " long id = counter++;\n", - " @Override\n", - " public String toString() { return \"Mail \" + id; }\n", - " public String details() {\n", - " return toString() +\n", - " \", General Delivery: \" + generalDelivery +\n", - " \", Address Scanability: \" + scannability +\n", - " \", Address Readability: \" + readability +\n", - " \", Address Address: \" + address +\n", - " \", Return address: \" + returnAddress;\n", - " }\n", - " // Generate test Mail:\n", - " public static Mail randomMail() {\n", - " Mail m = new Mail();\n", - " m.generalDelivery =\n", - " Enums.random(GeneralDelivery.class);\n", - " m.scannability =\n", - " Enums.random(Scannability.class);\n", - " m.readability =\n", - " Enums.random(Readability.class);\n", - " m.address = Enums.random(Address.class);\n", - " m.returnAddress =\n", - " Enums.random(ReturnAddress.class);\n", - " return m;\n", - " }\n", - " public static\n", - " Iterable generator(final int count) {\n", - " return new Iterable() {\n", - " int n = count;\n", - " @Override\n", - " public Iterator iterator() {\n", - " return new Iterator() {\n", - " @Override\n", - " public boolean hasNext() {\n", - " return n-- > 0;\n", - " }\n", - " @Override\n", - " public Mail next() {\n", - " return randomMail();\n", - " }\n", - " @Override\n", - " public void remove() { // Not implemented\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " };\n", - " }\n", - " };\n", - " }\n", - "}\n", - "public class PostOffice {\n", - " enum MailHandler {\n", - " GENERAL_DELIVERY {\n", - " @Override\n", - " boolean handle(Mail m) {\n", - " switch(m.generalDelivery) {\n", - " case YES:\n", - " System.out.println(\n", - " \"Using general delivery for \" + m);\n", - " return true;\n", - " default: return false;\n", - " }\n", - " }\n", - " },\n", - " MACHINE_SCAN {\n", - " @Override\n", - " boolean handle(Mail m) {\n", - " switch(m.scannability) {\n", - " case UNSCANNABLE: return false;\n", - " default:\n", - " switch(m.address) {\n", - " case INCORRECT: return false;\n", - " default:\n", - " System.out.println(\n", - " \"Delivering \"+ m + \" automatically\");\n", - " return true;\n", - " }\n", - " }\n", - " }\n", - " },\n", - " VISUAL_INSPECTION {\n", - " @Override\n", - " boolean handle(Mail m) {\n", - " switch(m.readability) {\n", - " case ILLEGIBLE: return false;\n", - " default:\n", - " switch(m.address) {\n", - " case INCORRECT: return false;\n", - " default:\n", - " System.out.println(\n", - " \"Delivering \" + m + \" normally\");\n", - " return true;\n", - " }\n", - " }\n", - " }\n", - " },\n", - " RETURN_TO_SENDER {\n", - " @Override\n", - " boolean handle(Mail m) {\n", - " switch(m.returnAddress) {\n", - " case MISSING: return false;\n", - " default:\n", - " System.out.println(\n", - " \"Returning \" + m + \" to sender\");\n", - " return true;\n", - " }\n", - " }\n", - " };\n", - " abstract boolean handle(Mail m);\n", - " }\n", - " static void handle(Mail m) {\n", - " for(MailHandler handler : MailHandler.values())\n", - " if(handler.handle(m))\n", - " return;\n", - " System.out.println(m + \" is a dead letter\");\n", - " }\n", - " public static void main(String[] args) {\n", - " for(Mail mail : Mail.generator(10)) {\n", - " System.out.println(mail.details());\n", - " handle(mail);\n", - " System.out.println(\"*****\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Mail 0, General Delivery: NO2, Address Scanability:\n", - "UNSCANNABLE, Address Readability: YES3, Address\n", - "Address: OK1, Return address: OK1\n", - "Delivering Mail 0 normally\n", - "*****\n", - "Mail 1, General Delivery: NO5, Address Scanability:\n", - "YES3, Address Readability: ILLEGIBLE, Address Address:\n", - "OK5, Return address: OK1\n", - "Delivering Mail 1 automatically\n", - "*****\n", - "Mail 2, General Delivery: YES, Address Scanability:\n", - "YES3, Address Readability: YES1, Address Address: OK1,\n", - "Return address: OK5\n", - "Using general delivery for Mail 2\n", - "*****\n", - "Mail 3, General Delivery: NO4, Address Scanability:\n", - "YES3, Address Readability: YES1, Address Address:\n", - "INCORRECT, Return address: OK4\n", - "Returning Mail 3 to sender\n", - "*****\n", - "Mail 4, General Delivery: NO4, Address Scanability:\n", - "UNSCANNABLE, Address Readability: YES1, Address\n", - "Address: INCORRECT, Return address: OK2\n", - "Returning Mail 4 to sender\n", - "*****\n", - "Mail 5, General Delivery: NO3, Address Scanability:\n", - "YES1, Address Readability: ILLEGIBLE, Address Address:\n", - "OK4, Return address: OK2\n", - "Delivering Mail 5 automatically\n", - "*****\n", - "Mail 6, General Delivery: YES, Address Scanability:\n", - "YES4, Address Readability: ILLEGIBLE, Address Address:\n", - "OK4, Return address: OK4\n", - "Using general delivery for Mail 6\n", - "*****\n", - "Mail 7, General Delivery: YES, Address Scanability:\n", - "YES3, Address Readability: YES4, Address Address: OK2,\n", - "Return address: MISSING\n", - "Using general delivery for Mail 7\n", - "*****\n", - "Mail 8, General Delivery: NO3, Address Scanability:\n", - "YES1, Address Readability: YES3, Address Address:\n", - "INCORRECT, Return address: MISSING\n", - "Mail 8 is a dead letter\n", - "*****\n", - "Mail 9, General Delivery: NO1, Address Scanability:\n", - "UNSCANNABLE, Address Readability: YES2, Address\n", - "Address: OK1, Return address: OK4\n", - "Delivering Mail 9 normally\n", - "*****" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "职责链由 enum MailHandler 实现,而 enum 定义的次序决定了各个解决策略在应用时的次序。对每一封邮件,都要按此顺序尝试每个解决策略,直到其中一个能够成功地处理该邮件,如果所有的策略都失败了,那么该邮件将被判定为一封死信。\n", - "\n", - "### 使用 enum 的状态机\n", - "\n", - "枚举类型非常适合用来创建状态机。一个状态机可以具有有限个特定的状态,它通常根据输入,从一个状态转移到下一个状态,不过也可能存在瞬时状态(transient states),而一旦任务执行结束,状态机就会立刻离开瞬时状态。\n", - "\n", - "每个状态都具有某些可接受的输入,不同的输入会使状态机从当前状态转移到不同的新状态。由于 enum 对其实例有严格限制,非常适合用来表现不同的状态和输入。一般而言,每个状态都具有一些相关的输出。\n", - "\n", - "自动售贷机是一个很好的状态机的例子。首先,我们用一个 enum 定义各种输入:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/Input.java\n", - "import java.util.*;\n", - "public enum Input {\n", - " NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100),\n", - " TOOTHPASTE(200), CHIPS(75), SODA(100), SOAP(50),\n", - " ABORT_TRANSACTION {\n", - " @Override\n", - " public int amount() { // Disallow\n", - " throw new RuntimeException(\"ABORT.amount()\");\n", - " }\n", - " },\n", - " STOP { // This must be the last instance.\n", - " @Override\n", - " public int amount() { // Disallow\n", - " throw new RuntimeException(\"SHUT_DOWN.amount()\");\n", - " }\n", - " };\n", - " int value; // In cents\n", - " Input(int value) { this.value = value; }\n", - " Input() {}\n", - " int amount() { return value; }; // In cents\n", - " static Random rand = new Random(47);\n", - " public static Input randomSelection() {\n", - " // Don't include STOP:\n", - " return values()[rand.nextInt(values().length - 1)];\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,除了两个特殊的 Input 实例之外,其他的 Input 都有相应的价格,因此在接口中定义了 amount(方法。然而,对那两个特殊 Input 实例而言,调用 amount(方法并不合适,所以如果程序员调用它们的 amount)方法就会有异常抛出(在接口内定义了一个方法,然后在你调用该方法的某个实现时就会抛出异常),这似乎有点奇怪,但由于 enum 的限制,我们不得不采用这种方式。\n", - "\n", - "VendingMachine 对输入的第一个反应是将其归类为 Category enum 中的某一个 enum 实例,这可以通过 switch 实现。下面的例子演示了 enum 是如何使代码变得更加清晰且易于管理的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/VendingMachine.java\n", - "// {java VendingMachine VendingMachineInput.txt}\n", - "import java.util.*;\n", - "import java.io.IOException;\n", - "import java.util.function.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "enum Category {\n", - " MONEY(Input.NICKEL, Input.DIME,\n", - " Input.QUARTER, Input.DOLLAR),\n", - " ITEM_SELECTION(Input.TOOTHPASTE, Input.CHIPS,\n", - " Input.SODA, Input.SOAP),\n", - " QUIT_TRANSACTION(Input.ABORT_TRANSACTION),\n", - " SHUT_DOWN(Input.STOP);\n", - " private Input[] values;\n", - " Category(Input... types) { values = types; }\n", - " private static EnumMap categories =\n", - " new EnumMap<>(Input.class);\n", - " static {\n", - " for(Category c : Category.class.getEnumConstants())\n", - " for(Input type : c.values)\n", - " categories.put(type, c);\n", - " }\n", - " public static Category categorize(Input input) {\n", - " return categories.get(input);\n", - " }\n", - "}\n", - "\n", - "public class VendingMachine {\n", - " private static State state = State.RESTING;\n", - " private static int amount = 0;\n", - " private static Input selection = null;\n", - " enum StateDuration { TRANSIENT } // Tagging enum\n", - " enum State {\n", - " RESTING {\n", - " @Override\n", - " void next(Input input) {\n", - " switch(Category.categorize(input)) {\n", - " case MONEY:\n", - " amount += input.amount();\n", - " state = ADDING_MONEY;\n", - " break;\n", - " case SHUT_DOWN:\n", - " state = TERMINAL;\n", - " default:\n", - " }\n", - " }\n", - " },\n", - " ADDING_MONEY {\n", - " @Override\n", - " void next(Input input) {\n", - " switch(Category.categorize(input)) {\n", - " case MONEY:\n", - " amount += input.amount();\n", - " break;\n", - " case ITEM_SELECTION:\n", - " selection = input;\n", - " if(amount < selection.amount())\n", - " System.out.println(\n", - " \"Insufficient money for \" + selection);\n", - " else state = DISPENSING;\n", - " break;\n", - " case QUIT_TRANSACTION:\n", - " state = GIVING_CHANGE;\n", - " break;\n", - " case SHUT_DOWN:\n", - " state = TERMINAL;\n", - " default:\n", - " }\n", - " }\n", - " },\n", - " DISPENSING(StateDuration.TRANSIENT) {\n", - " @Override\n", - " void next() {\n", - " System.out.println(\"here is your \" + selection);\n", - " amount -= selection.amount();\n", - " state = GIVING_CHANGE;\n", - " }\n", - " },\n", - " GIVING_CHANGE(StateDuration.TRANSIENT) {\n", - " @Override\n", - " void next() {\n", - " if(amount > 0) {\n", - " System.out.println(\"Your change: \" + amount);\n", - " amount = 0;\n", - " }\n", - " state = RESTING;\n", - " }\n", - " },\n", - " TERMINAL {@Override\n", - " void output() { System.out.println(\"Halted\"); } };\n", - " private boolean isTransient = false;\n", - " State() {}\n", - " State(StateDuration trans) { isTransient = true; }\n", - " void next(Input input) {\n", - " throw new RuntimeException(\"Only call \" +\n", - " \"next(Input input) for non-transient states\");\n", - " }\n", - " void next() {\n", - " throw new RuntimeException(\n", - " \"Only call next() for \" +\n", - " \"StateDuration.TRANSIENT states\");\n", - " }\n", - " void output() { System.out.println(amount); }\n", - " }\n", - " static void run(Supplier gen) {\n", - " while(state != State.TERMINAL) {\n", - " state.next(gen.get());\n", - " while(state.isTransient)\n", - " state.next();\n", - " state.output();\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " Supplier gen = new RandomInputSupplier();\n", - " if(args.length == 1)\n", - " gen = new FileInputSupplier(args[0]);\n", - " run(gen);\n", - " }\n", - "}\n", - "\n", - "// For a basic sanity check:\n", - "class RandomInputSupplier implements Supplier {\n", - " @Override\n", - " public Input get() {\n", - " return Input.randomSelection();\n", - " }\n", - "}\n", - "\n", - "// Create Inputs from a file of ';'-separated strings:\n", - "class FileInputSupplier implements Supplier {\n", - " private Iterator input;\n", - " FileInputSupplier(String fileName) {\n", - " try {\n", - " input = Files.lines(Paths.get(fileName))\n", - " .skip(1) // Skip the comment line\n", - " .flatMap(s -> Arrays.stream(s.split(\";\")))\n", - " .map(String::trim)\n", - " .collect(Collectors.toList())\n", - " .iterator();\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " @Override\n", - " public Input get() {\n", - " if(!input.hasNext())\n", - " return null;\n", - " return Enum.valueOf(Input.class, input.next().trim());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "25\n", - "50\n", - "75\n", - "here is your CHIPS\n", - "0\n", - "100\n", - "200\n", - "here is your TOOTHPASTE\n", - "0\n", - "25\n", - "35\n", - "Your change: 35\n", - "0\n", - "25\n", - "35\n", - "Insufficient money for SODA\n", - "35\n", - "60\n", - "70\n", - "75\n", - "Insufficient money for SODA\n", - "75\n", - "Your change: 75\n", - "0\n", - "Halted" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于用 switch 语句从 enum 实例中进行选择是最常见的一种方式(请注意,为了使 enum 在 switch 语句中的使用变得简单,我们是需要付出其他代价的),所以,我们经常遇到这样的问题:将多个 enum 进行分类时,“我们希望在什么 enum 中使用 switch 语句?”我们通过 VendingMachine 的例子来研究一下这个问题。对于每一个 State,我们都需要在输入动作的基本分类中进行查找:用户塞入钞票,选择了某个货物,操作被取消,以及机器停止。然而,在这些基本分类之下,我们又可以塞人不同类型的钞票,可以选择不同的货物。Category enum 将不同类型的 Input 进行分组,因而,可以使用 categorize0 方法为 switch 语句生成恰当的 Cateroy 实例。并且,该方法使用的 EnumMap 确保了在其中进行查询时的效率与安全。\n", - "\n", - "如果读者仔细研究 VendingMachine 类,就会发现每种状态的不同之处,以及对于输入的不同响应,其中还有两个瞬时状态。在 run() 方法中,状态机等待着下一个 Input,并一直在各个状态中移动,直到它不再处于瞬时状态。\n", - "\n", - "通过两种不同的 Generator 对象,我们可以使用不同的 Supplier 对象来测试 VendingMachine,首先是 RandomInputSupplier,它会不停地生成除了 SHUT-DOWN 之外的各种输入。通过长时间地运行 RandomInputSupplier,可以起到健全测试(sanity test)的作用,能够确保该状态机不会进入一个错误状态。另一个是 FileInputSupplier,使用文件以文本的方式来描述输入,然后将它们转换成 enum 实例,并创建对应的 Input 对象。上面的程序使用的正是如下的文本文件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "// enums/VendingMachineInput.txt\n", - "QUARTER; QUARTER; QUARTER; CHIPS;\n", - "DOLLAR; DOLLAR; TOOTHPASTE;\n", - "QUARTER; DIME; ABORT_TRANSACTION;\n", - "QUARTER; DIME; SODA;\n", - "QUARTER; DIME; NICKEL; SODA;\n", - "ABORT_TRANSACTION;\n", - "STOP;" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "FileInputSupplier 构造函数将此文件转换为流,并跳过注释行。然后它使用 String.split() 以分号进行分割。这会生成一个 String 数组,并可以通过将其转换为 Stream,然后应用 flatMap() 来将其输入到流中。其输出结果将去除所有空格空格,并转换为 List\\,且从中获取 Iterator\\。\n", - "\n", - "这种设计有一个缺陷,它要求 enum State 实例访问的 VendingMachine 属性必须声明为 static,这意味着,你只能有一个 VendingMachine 实例。不过如果我们思考一下实际的(嵌入式 Java)应用,这也许并不是一个大问题,因为在一台机器上,我们可能只有一个应用程序。\n", - "\n", - "\n", - "\n", - "## 多路分发\n", - "\n", - "当你要处理多种交互类型时,程序可能会变得相当杂乱。举例来说,如果一个系统要分析和执行数学表达式。我们可能会声明 Number.plus(Number),Number.multiple(Number) 等等,其中 Number 是各种数字对象的超类。然而,当你声明 a.plus(b) 时,你并不知道 a 或 b 的确切类型,那你如何能让它们正确地交互呢?\n", - "\n", - "你可能从未思考过这个问题的答案.Java 只支持单路分发。也就是说,如果要执行的操作包含了不止一个类型未知的对象时,那么 Java 的动态绑定机制只能处理其中一个的类型。这就无法解决我们上面提到的问题。所以,你必须自己来判定其他的类型,从而实现自己的动态线定行为。\n", - "\n", - "解决上面问题的办法就是多路分发(在那个例子中,只有两个分发,一般称之为两路分发).多态只能发生在方法调用时,所以,如果你想使用两路分发,那么就必须有两个方法调用:第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知的类型。要利用多路分发,程序员必须为每一个类型提供一个实际的方法调用,如果你要处理两个不同的类型体系,就需要为每个类型体系执行一个方法调用。一般而言,程序员需要有设定好的某种配置,以便一个方法调用能够引出更多的方法调用,从而能够在这个过程中处理多种类型。为了达到这种效果,我们需要与多个方法一同工作:因为每个分发都需要一个方法调用。在下面的例子中(实现了 “石头、剪刀、布”游戏,也称为 RoShamBo)对应的方法是 compete() 和 eval(),二者都是同一个类型的成员,它们可以产生三种 Outcome 实例中的一个作为结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/Outcome.java\n", - "package enums;\n", - "public enum Outcome { WIN, LOSE, DRAW }\n", - "// enums/RoShamBo1.java\n", - "// Demonstration of multiple dispatching\n", - "// {java enums.RoShamBo1}\n", - "package enums;\n", - " import java.util.*;\n", - " import static enums.Outcome.*;\n", - "interface Item {\n", - " Outcome compete(Item it);\n", - " Outcome eval(Paper p);\n", - " Outcome eval(Scissors s);\n", - " Outcome eval(Rock r);\n", - "}\n", - "class Paper implements Item {\n", - " @Override\n", - " public Outcome compete(Item it) {\n", - " return it.eval(this);\n", - " }\n", - " @Override\n", - " public Outcome eval(Paper p) { return DRAW; }\n", - " @Override\n", - " public Outcome eval(Scissors s) { return WIN; }\n", - " @Override\n", - " public Outcome eval(Rock r) { return LOSE; }\n", - " @Override\n", - " public String toString() { return \"Paper\"; }\n", - "}\n", - "class Scissors implements Item {\n", - " @Override\n", - " public Outcome compete(Item it) {\n", - " return it.eval(this);\n", - " }\n", - " @Override\n", - " public Outcome eval(Paper p) { return LOSE; }\n", - " @Override\n", - " public Outcome eval(Scissors s) { return DRAW; }\n", - " @Override\n", - " public Outcome eval(Rock r) { return WIN; }\n", - " @Override\n", - " public String toString() { return \"Scissors\"; }\n", - "}\n", - "class Rock implements Item {\n", - " @Override\n", - " public Outcome compete(Item it) {\n", - " return it.eval(this);\n", - " }\n", - " @Override\n", - " public Outcome eval(Paper p) { return WIN; }\n", - " @Override\n", - " public Outcome eval(Scissors s) { return LOSE; }\n", - " @Override\n", - " public Outcome eval(Rock r) { return DRAW; }\n", - " @Override\n", - " public String toString() { return \"Rock\"; }\n", - "}\n", - "public class RoShamBo1 {\n", - " static final int SIZE = 20;\n", - " private static Random rand = new Random(47);\n", - " public static Item newItem() {\n", - " switch(rand.nextInt(3)) {\n", - " default:\n", - " case 0: return new Scissors();\n", - " case 1: return new Paper();\n", - " case 2: return new Rock();\n", - " }\n", - " }\n", - " public static void match(Item a, Item b) {\n", - " System.out.println(\n", - " a + \" vs. \" + b + \": \" + a.compete(b));\n", - " }\n", - " public static void main(String[] args) {\n", - " for(int i = 0; i < SIZE; i++)\n", - " match(newItem(), newItem());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Rock vs. Rock: DRAW\n", - "Paper vs. Rock: WIN\n", - "Paper vs. Rock: WIN\n", - "Paper vs. Rock: WIN\n", - "Scissors vs. Paper: WIN\n", - "Scissors vs. Scissors: DRAW\n", - "Scissors vs. Paper: WIN\n", - "Rock vs. Paper: LOSE\n", - "Paper vs. Paper: DRAW\n", - "Rock vs. Paper: LOSE\n", - "Paper vs. Scissors: LOSE\n", - "Paper vs. Scissors: LOSE\n", - "Rock vs. Scissors: WIN\n", - "Rock vs. Paper: LOSE\n", - "Paper vs. Rock: WIN\n", - "Scissors vs. Paper: WIN\n", - "Paper vs. Scissors: LOSE\n", - "Paper vs. Scissors: LOSE\n", - "Paper vs. Scissors: LOSE\n", - "Paper vs. Scissors: LOSE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Item 是这几种类型的接口,将会被用作多路分发。RoShamBo1.match() 有两个 Item 参数,通过调用 Item.compete90) 方法开始两路分发。要判定 a 的类型,分发机制会在 a 的实际类型的 compete(内部起到分发的作用。compete() 方法通过调用 eval() 来为另一个类型实现第二次分法。\n", - "\n", - "将自身(this)作为参数调用 evalo,能够调用重载过的 eval() 方法,这能够保留第一次分发的类型信息。当第二次分发完成时,你就能够知道两个 Item 对象的具体类型了。\n", - "\n", - "要配置好多路分发需要很多的工序,不过要记住,它的好处在于方法调用时的优雅的话法,这避免了在一个方法中判定多个对象的类型的丑陋代码,你只需说,“嘿,你们两个,我不在乎你们是什么类型,请你们自己交流!”不过,在使用多路分发前,请先明确,这种优雅的代码对你确实有重要的意义。\n", - "\n", - "### 使用 enum 分发\n", - "\n", - "直接将 RoShamBol.java 翻译为基于 enum 的版本是有问题的,因为 enum 实例不是类型,不能将 enum 实例作为参数的类型,所以无法重载 eval() 方法。不过,还有很多方式可以实现多路分发,并从 enum 中获益。\n", - "\n", - "一种方式是使用构造器来初始化每个 enum 实例,并以“一组”结果作为参数。这二者放在一块,形成了类似查询表的结构:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RoShamBo2.java\n", - "// Switching one enum on another\n", - "// {java enums.RoShamBo2}\n", - "package enums;\n", - "import static enums.Outcome.*;\n", - "public enum RoShamBo2 implements Competitor {\n", - " PAPER(DRAW, LOSE, WIN),\n", - " SCISSORS(WIN, DRAW, LOSE),\n", - " ROCK(LOSE, WIN, DRAW);\n", - " private Outcome vPAPER, vSCISSORS, vROCK;\n", - " RoShamBo2(Outcome paper,\n", - " Outcome scissors, Outcome rock) {\n", - " this.vPAPER = paper;\n", - " this.vSCISSORS = scissors;\n", - " this.vROCK = rock;\n", - " }\n", - " @Override\n", - " public Outcome compete(RoShamBo2 it) {\n", - " switch(it) {\n", - " default:\n", - " case PAPER: return vPAPER;\n", - " case SCISSORS: return vSCISSORS;\n", - " case ROCK: return vROCK;\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " RoShamBo.play(RoShamBo2.class, 20);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ROCK vs. ROCK: DRAW\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "PAPER vs. PAPER: DRAW\n", - "PAPER vs. SCISSORS: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. SCISSORS: DRAW\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. PAPER: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 compete() 方法中,一旦两种类型都被确定了,那么唯一的操作就是返回结果 Outcome 然而,你可能还需要调用其他的方法,(例如)甚至是调用在构造器中指定的某个命令对象上的方法。\n", - "\n", - "RoShamBo2.javal 之前的例子短小得多,而且更直接,更易于理解。注意,我们仍然是使用两路分发来判定两个对象的类型。在 RoShamBol.java 中,两次分发都是通过实际的方法调用实现,而在这个例子中,只有第一次分发是实际的方法调用。第二个分发使用的是 switch,不过这样做是安全的,因为 enum 限制了 switch 语句的选择分支。\n", - "\n", - "在代码中,enum 被单独抽取出来,因此它可以应用在其他例子中。首先,Competitor 接口定义了一种类型,该类型的对象可以与另一个 Competitor 相竞争:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/Competitor.java\n", - "// Switching one enum on another\n", - "package enums;\n", - "public interface Competitor> {\n", - " Outcome compete(T competitor);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后,我们定义两个 static 方法(static 可以避免显式地指明参数类型),第一个是 match() 方法,它会为一个 Competitor 对象调用 compete() 方法,并与另一个 Competitor 对象作比较。在这个例子中,我们看到,match())方法的参数需要是 Competitor\\ 类型。但是在 play() 方法中,类型参数必须同时是 Enum\\ 类型(因为它将在 Enums.random() 中使用)和 Competitor\\ 类型因为它将被传递给 match() 方法):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RoShamBo.java\n", - "// Common tools for RoShamBo examples\n", - "package enums;\n", - "import onjava.*;\n", - "public class RoShamBo {\n", - " public static >\n", - " void match(T a, T b) {\n", - " System.out.println(\n", - " a + \" vs. \" + b + \": \" + a.compete(b));\n", - " }\n", - " public static & Competitor>\n", - " void play(Class rsbClass, int size) {\n", - " for(int i = 0; i < size; i++)\n", - " match(Enums.random(rsbClass),Enums.random(rsbClass));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "play() 方法没有将类型参数 T 作为返回值类型,因此,似乎我们应该在 Class\\ 中使用通配符来代替上面的参数声明。然而,通配符不能扩展多个基类,所以我们必须采用以上的表达式。\n", - "\n", - "### 使用常量相关的方法\n", - "\n", - "常量相关的方法允许我们为每个 enum 实例提供方法的不同实现,这使得常量相关的方法似乎是实现多路分发的完美解决方案。不过,通过这种方式,enum 实例虽然可以具有不同的行为,但它们仍然不是类型,不能将其作为方法签名中的参数类型来使用。最好的办法是将 enum 用在 switch 语句中,见下例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RoShamBo3.java\n", - "// Using constant-specific methods\n", - "// {java enums.RoShamBo3}\n", - "package enums;\n", - "import static enums.Outcome.*;\n", - "public enum RoShamBo3 implements Competitor {\n", - " PAPER {\n", - " @Override\n", - " public Outcome compete(RoShamBo3 it) {\n", - " switch(it) {\n", - " default: // To placate the compiler\n", - " case PAPER: return DRAW;\n", - " case SCISSORS: return LOSE;\n", - " case ROCK: return WIN;\n", - " }\n", - " }\n", - " },\n", - " SCISSORS {\n", - " @Override\n", - " public Outcome compete(RoShamBo3 it) {\n", - " switch(it) {\n", - " default:\n", - " case PAPER: return WIN;\n", - " case SCISSORS: return DRAW;\n", - " case ROCK: return LOSE;\n", - " }\n", - " }\n", - " },\n", - " ROCK {\n", - " @Override\n", - " public Outcome compete(RoShamBo3 it) {\n", - " switch(it) {\n", - " default:\n", - " case PAPER: return LOSE;\n", - " case SCISSORS: return WIN;\n", - " case ROCK: return DRAW;\n", - " }\n", - " }\n", - " };\n", - " @Override\n", - " public abstract Outcome compete(RoShamBo3 it);\n", - " public static void main(String[] args) {\n", - " RoShamBo.play(RoShamBo3.class, 20);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ROCK vs. ROCK: DRAW\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "PAPER vs. PAPER: DRAW\n", - "PAPER vs. SCISSORS: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. SCISSORS: DRAW\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. PAPER: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然这种方式可以工作,但是却不甚合理,如果采用 RoShamB02.java 的解决方案,那么在添加一个新的类型时,只需更少的代码,而且也更直接。\n", - "\n", - ":然而,RoShamBo3.java 还可以压缩简化一下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RoShamBo4.java\n", - "// {java enums.RoShamBo4}\n", - "package enums;\n", - "public enum RoShamBo4 implements Competitor {\n", - " ROCK {\n", - " @Override\n", - " public Outcome compete(RoShamBo4 opponent) {\n", - " return compete(SCISSORS, opponent);\n", - " }\n", - " },\n", - " SCISSORS {\n", - " @Override\n", - " public Outcome compete(RoShamBo4 opponent) {\n", - " return compete(PAPER, opponent);\n", - " }\n", - " },\n", - " PAPER {\n", - " @Override\n", - " public Outcome compete(RoShamBo4 opponent) {\n", - " return compete(ROCK, opponent);\n", - " }\n", - " };\n", - " Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) {\n", - " return ((opponent == this) ? Outcome.DRAW\n", - " : ((opponent == loser) ? Outcome.WIN\n", - " : Outcome.LOSE));\n", - " }\n", - " public static void main(String[] args) {\n", - " RoShamBo.play(RoShamBo4.class, 20);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PAPER vs. PAPER: DRAW\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. SCISSORS: WIN\n", - "ROCK vs. ROCK: DRAW\n", - "ROCK vs. SCISSORS: WIN\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. SCISSORS: DRAW\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. ROCK: WIN\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中,具有两个参数的 compete() 方法执行第二个分发,该方法执行一系列的比较,其行为类似 switch 语句。这个版本的程序更简短,不过却比较难理解,对于一个大型系统而言,难以理解的代码将导致整个系统不够健壮。\n", - "\n", - "### 使用 EnumMap 进行分发\n", - "\n", - "使用 EnumMap 能够实现“真正的”两路分发。EnumMap 是为 enum 专门设计的一种性能非常好的特殊 Map。由于我们的目的是摸索出两种未知的类型,所以可以用一个 EnumMap 的 EnumMap 来实现两路分发:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// enums/RoShamBo5.java\n", - "// Multiple dispatching using an EnumMap of EnumMaps\n", - "// {java enums.RoShamBo5}\n", - "package enums;\n", - "import java.util.*;\n", - "import static enums.Outcome.*;\n", - "enum RoShamBo5 implements Competitor {\n", - " PAPER, SCISSORS, ROCK;\n", - " static EnumMap>\n", - " table = new EnumMap<>(RoShamBo5.class);\n", - " static {\n", - " for(RoShamBo5 it : RoShamBo5.values())\n", - " table.put(it, new EnumMap<>(RoShamBo5.class));\n", - " initRow(PAPER, DRAW, LOSE, WIN);\n", - " initRow(SCISSORS, WIN, DRAW, LOSE);\n", - " initRow(ROCK, LOSE, WIN, DRAW);\n", - " }\n", - " static void initRow(RoShamBo5 it,\n", - " Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {\n", - " EnumMap row =\n", - " RoShamBo5.table.get(it);\n", - " row.put(RoShamBo5.PAPER, vPAPER);\n", - " row.put(RoShamBo5.SCISSORS, vSCISSORS);\n", - " row.put(RoShamBo5.ROCK, vROCK);\n", - " }\n", - " @Override\n", - " public Outcome compete(RoShamBo5 it) {\n", - " return table.get(this).get(it);\n", - " }\n", - " public static void main(String[] args) {\n", - " RoShamBo.play(RoShamBo5.class, 20);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ROCK vs. ROCK: DRAW\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "PAPER vs. PAPER: DRAW\n", - "PAPER vs. SCISSORS: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. SCISSORS: DRAW\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. PAPER: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "该程序在一个 static 子句中初始化 EnumMap 对象,具体见表格似的 initRow() 方法调用。请注意 compete() 方法,您可以看到,在一行语句中发生了两次分发。\n", - "\n", - "### 使用二维数组\n", - "\n", - "我们还可以进一步简化实现两路分发的解决方案。我们注意到,每个 enum 实例都有一个固定的值(基于其声明的次序),并且可以通过 ordinal() 方法取得该值。因此我们可以使用二维数组,将竞争者映射到竞争结果。采用这种方式能够获得最简洁、最直接的解决方案(很可能也是最快速的,虽然我们知道 EnumMap 内部其实也是使用数组实现的)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "We can simplify the solution even more by noting that each enum instance has a fixed\n", - " value (based on its declaration order) and that ordinal() produces this value. A two-\n", - " dimensional array mapping the competitors onto the outcomes produces the smallest\n", - " and most straightforward solution (and possibly the fastest, although remember that\n", - " EnumMap uses an internal array):\n", - "// enums/RoShamBo6.java\n", - "// Enums using \"tables\" instead of multiple dispatch\n", - "// {java enums.RoShamBo6}\n", - " package enums;\n", - " import static enums.Outcome.*;\n", - "enum RoShamBo6 implements Competitor {\n", - " PAPER, SCISSORS, ROCK;\n", - " private static Outcome[][] table = {\n", - " { DRAW, LOSE, WIN }, // PAPER\n", - " { WIN, DRAW, LOSE }, // SCISSORS\n", - " { LOSE, WIN, DRAW }, // ROCK\n", - " };\n", - " @Override\n", - " public Outcome compete(RoShamBo6 other) {\n", - " return table[this.ordinal()][other.ordinal()];\n", - " }\n", - " public static void main(String[] args) {\n", - " RoShamBo.play(RoShamBo6.class, 20);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ROCK vs. ROCK: DRAW\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "PAPER vs. PAPER: DRAW\n", - "PAPER vs. SCISSORS: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. SCISSORS: DRAW\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "ROCK vs. PAPER: LOSE\n", - "ROCK vs. SCISSORS: WIN\n", - "SCISSORS vs. ROCK: LOSE\n", - "PAPER vs. SCISSORS: LOSE\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN\n", - "SCISSORS vs. PAPER: WIN" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "table 与前一个例子中 initRow() 方法的调用次序完全相同。\n", - "\n", - "与前面一个例子相比,这个程序代码虽然简短,但表达能力却更强,部分原因是其代码更易于理解与修改,而且也更直接。不过,由于它使用的是数组,所以这种方式不太“安全”。如果使用一个大型数组,可能会不小心使用了错误的尺寸,而且,如果你的测试不能覆盖所有的可能性,有些错误可能会从你眼前溜过。\n", - "\n", - "事实上,以上所有的解决方案只是各种不同类型的表罢了。不过,分析各种表的表现形式,找出最适合的那一种,还是很有价值的。注意,虽然上例是最简洁的一种解决方案,但它也是相当僵硬的方案,因为它只能针对给定的常量输入产生常量输出。然而,也没有什么特别的理由阻止你用 table 来生成功能对象。对于某类问题而言,“表驱动式编码”的概念具有非常强大的功能。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "虽然枚举类型本身并不是特别复杂,但我还是将本章安排在全书比较靠后的位置,这是因为,程序员可以将 enum 与 Java 语言的其他功能结合使用,例如多态、泛型和反射。\n", - "\n", - "虽然 Java 中的枚举比 C 或 C++中的 enum 更成熟,但它仍然是一个“小”功能,Java 没有它也已经(虽然有点笨拙)存在很多年了。而本章正好说明了一个“小”功能所能带来的价值。有时恰恰因为它,你才能够优雅而干净地解决问题。正如我在本书中一再强调的那样,优雅与清晰很重要,正是它们区别了成功的解决方案与失败的解决方案。而失败的解决方案就是因为其他人无法理解它。\n", - "\n", - "关于清晰的话题,Java 1.0 对术语 enumeration 的选择正是一个不幸的反例。对于一个专门用于从序列中选择每一个元素的对象而言,Java 竟然没有使用更通用、更普遍接受的术语 iterator 来表示它(参见[集合 ]() 章节),有些语言甚至将枚举的数据类型称为 “enumerators”!Java 修正了这个错误,但是 Enumeration 接口已经无法轻易地抹去了,因此它将一直存在于旧的(甚至有些新的)代码、类库以及文档中。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/23-Annotations.ipynb b/jupyter/23-Annotations.ipynb deleted file mode 100644 index e8e766ce..00000000 --- a/jupyter/23-Annotations.ipynb +++ /dev/null @@ -1,2737 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "# 第二十三章 注解\n", - "\n", - "注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方式,使我们可以在稍后的某个时刻更容易的使用这些数据。\n", - "\n", - "注解在一定程度上是把元数据和源代码文件结合在一起的趋势所激发的,而不是保存在外部文档。这同样是对像 C# 语言对于 Java 语言特性压力的一种回应。\n", - "\n", - "注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此,注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件,甚至是新的类定义,并且有助于减轻编写“样板”代码的负担。通过使用注解,你可以将元数据保存在 Java 源代码中。并拥有如下优势:简单易读的代码,编译器类型检查,使用 annotation API 为自己的注解构造处理工具。即使 Java 定义了一些类型的元数据,但是一般来说注解类型的添加和如何使用完全取决于你。\n", - "\n", - "注解的语法十分简单,主要是在现有语法中添加 @ 符号。Java 5 引入了前三种定义在 **java.lang** 包中的注解:\n", - "\n", - "- **@Override**:表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误,或者方法签名被错误拼写的时候,编译器就会发出错误提示。\n", - "- **@Deprecated**:如果使用该注解的元素被调用,编译器就会发出警告信息。\n", - "- **@SuppressWarnings**:关闭不当的编译器警告信息。\n", - "- **@SafeVarargs**:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。\n", - "- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口\n", - "\n", - "还有 5 种额外的注解类型用于创造新的注解。你将会在这一章学习它们。\n", - "\n", - "每当创建涉及重复工作的类或接口时,你通常可以使用注解来自动化和简化流程。例如在 Enterprise JavaBean(EJB)中的许多额外工作就是通过注解来消除的。\n", - "\n", - "注解的出现可以替代一些现有的系统,例如 XDoclet,它是一种独立的文档化工具,专门设计用来生成注解风格的文档。与之相比,注解是真正语言层级的概念,以前构造出来就享有编译器的类型检查保护。注解在源代码级别保存所有信息而不是通过注释文字,这使得代码更加整洁和便于维护。通过使用拓展的 annotation API 或稍后在本章节可以看到的外部的字节码工具类库,你会拥有对源代码及字节码强大的检查与操作能力。\n", - "\n", - "\n", - "\n", - "## 基本语法\n", - "\n", - "\n", - "\n", - "在下面的例子中,使用 `@Test` 对 `testExecute()` 进行注解。该注解本身不做任何事情,但是编译器要保证其类路径上有 `@Test` 注解的定义。你将在本章看到,我们通过注解创建了一个工具用于运行这个方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/Testable.java\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "public class Testable {\n", - " public void execute() {\n", - " System.out.println(\"Executing..\");\n", - " }\n", - " @Test\n", - " void testExecute() { execute(); }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。\n", - "\n", - "### 定义注解\n", - "\n", - "如下是一个注解的定义。注解的定义看起来很像接口的定义。事实上,它们和其他 Java 接口一样,也会被编译成 class 文件。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/Test.java\n", - "// The @Test tag\n", - "package onjava.atunit;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.METHOD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface Test {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annoation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。\n", - "\n", - "注解通常会包含一些表示特定值的元素。当分析处理注解的时候,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,但是可以为其指定默认值。\n", - "\n", - "不包含任何元素的注解称为标记注解(marker annotation),例如上例中的 `@Test` 就是标记注解。\n", - "\n", - "下面是一个简单的注解,我们可以用它来追踪项目中的用例。程序员可以使用该注解来标注满足特定用例的一个方法或者一组方法。于是,项目经理可以通过统计已经实现的用例来掌控项目的进展,而开发者在维护项目时可以轻松的找到用例用于更新,或者他们可以调试系统中业务逻辑。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/UseCase.java\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.METHOD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface UseCase {\n", - " int id();\n", - " String description() default \"no description\";\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意 **id** 和 **description** 与方法定义类似。由于编译器会对 **id** 进行类型检查,因此将跟踪数据库与用例文档和源代码相关联是可靠的方式。**description** 元素拥有一个 **default** 值,如果在注解某个方法时没有给出 **description** 的值。则该注解的处理器会使用此元素的默认值。\n", - "\n", - "在下面的类中,有三个方法被注解为用例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/PasswordUtils.java\n", - "import java.util.*;\n", - "public class PasswordUtils {\n", - " @UseCase(id = 47, description =\n", - " \"Passwords must contain at least one numeric\")\n", - " public boolean validatePassword(String passwd) {\n", - " return (passwd.matches(\"\\\\w*\\\\d\\\\w*\"));\n", - " }\n", - " @UseCase(id = 48)\n", - " public String encryptPassword(String passwd) {\n", - " return new StringBuilder(passwd)\n", - " .reverse().toString();\n", - " }\n", - " @UseCase(id = 49, description =\n", - " \"New passwords can't equal previously used ones\")\n", - " public boolean checkForNewPassword(\n", - " List prevPasswords, String passwd) {\n", - " return !prevPasswords.contains(passwd);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注解的元素在使用时表现为 名-值 对的形式,并且需要放置在 `@UseCase` 声明之后的括号内。在 `encryptPassword()` 方法的注解中,并没有给出 **description** 的值,所以在 **@interface UseCase** 的注解处理器分析处理这个类的时候会使用该元素的默认值。\n", - "\n", - "你应该能够想象到如何使用这套工具来“勾勒”出将要建造的系统,然后在建造的过程中逐渐实现系统的各项功能。\n", - "\n", - "### 元注解\n", - "\n", - "Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元注解。元注解用于注解其他的注解\n", - "\n", - "| 注解 | 解释 |\n", - "| ----------- | ------------------------------------------------------------ |\n", - "| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明
**FIELD**:字段声明(包括 enum 实例)
**LOCAL_VARIABLE**:局部变量声明
**METHOD**:方法声明
**PACKAGE**:包声明
**PARAMETER**:参数声明
**TYPE**:类、接口(包括注解类型)或者 enum 声明 |\n", - "| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 |\n", - "| @Documented | 将此注解保存在 Javadoc 中 |\n", - "| @Inherited | 允许子类继承父类的注解 |\n", - "| @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 |\n", - "\n", - "大多数时候,程序员定义自己的注解,并编写自己的处理器来处理他们。\n", - "\n", - "## 编写注解处理器\n", - "\n", - "如果没有用于读取注解的工具,那么注解不会比注释更有用。使用注解中一个很重要的部分就是,创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解。\n", - "\n", - "下面是一个非常简单的注解处理器,我们用它来读取被注解的 **PasswordUtils** 类,并且使用反射机制来寻找 **@UseCase** 标记。给定一组 **id** 值,然后列出在 **PasswordUtils** 中找到的用例,以及缺失的用例。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/UseCaseTracker.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.lang.reflect.*;\n", - "public class UseCaseTracker {\n", - " public static void\n", - " trackUseCases(List useCases, Class cl) {\n", - " for(Method m : cl.getDeclaredMethods()) {\n", - " UseCase uc = m.getAnnotation(UseCase.class);\n", - " if(uc != null) {\n", - " System.out.println(\"Found Use Case \" +\n", - " uc.id() + \"\\n \" + uc.description());\n", - " useCases.remove(Integer.valueOf(uc.id()));\n", - " }\n", - " }\n", - " useCases.forEach(i ->\n", - " System.out.println(\"Missing use case \" + i));\n", - " }\n", - " public static void main(String[] args) {\n", - " List useCases = IntStream.range(47, 51)\n", - " .boxed().collect(Collectors.toList());\n", - " trackUseCases(useCases, PasswordUtils.class);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Found Use Case 48\n", - "no description\n", - "Found Use Case 47\n", - "Passwords must contain at least one numeric\n", - "Found Use Case 49\n", - "New passwords can't equal previously used ones\n", - "Missing use case 50" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个程序用了两个反射的方法:`getDeclaredMethods()` 和 `getAnnotation()`,它们都属于 **AnnotatedElement** 接口(**Class**,**Method** 与 **Field** 类都实现了该接口)。`getAnnotation()` 方法返回指定类型的注解对象,在本例中就是 “**UseCase**”。如果被注解的方法上没有该类型的注解,返回值就为 **null**。我们通过调用 `id()` 和 `description()` 方法来提取元素值。注意 `encryptPassword()` 方法在注解的时候没有指定 **description** 的值,因此处理器在处理它对应的注解时,通过 `description()` 取得的是默认值 “no description”。\n", - "\n", - "### 注解元素\n", - "\n", - "在 **UseCase.java** 中定义的 **@UseCase** 的标签包含 int 元素 **id** 和 String 元素 **description**。注解元素可用的类型如下所示:\n", - "\n", - "- 所有基本类型(int、float、boolean等)\n", - "- String\n", - "- Class\n", - "- enum\n", - "- Annotation\n", - "- 以上类型的数组\n", - "\n", - "如果你使用了其他类型,编译器就会报错。注意,也不允许使用任何包装类型,但是由于自动装箱的存在,这不算是什么限制。注解也可以作为元素的类型。稍后你会看到,注解嵌套是一个非常有用的技巧。\n", - "\n", - "### 默认值限制\n", - "\n", - "编译器对于元素的默认值有些过于挑剔。首先,元素不能有不确定的值。也就是说,元素要么有默认值,要么就在使用注解时提供元素的值。\n", - "\n", - "这里有另外一个限制:任何非基本类型的元素, 无论是在源代码声明时还是在注解接口中定义默认值时,都不能使用 null 作为其值。这个限制使得处理器很难表现一个元素的存在或者缺失的状态,因为在每个注解的声明中,所有的元素都存在,并且具有相应的值。为了绕开这个约束,可以自定义一些特殊的值,比如空字符串或者负数用于表达某个元素不存在。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/SimulatingNull.java\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.METHOD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface SimulatingNull {\n", - " int id() default -1;\n", - " String description() default \"\";\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个在定义注解的习惯用法。\n", - "\n", - "### 生成外部文件\n", - "\n", - "当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。\n", - "\n", - "假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。\n", - "\n", - "以下是一个注解的定义,它告诉注解处理器应该创建一个数据库表:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/DBTable.java\n", - "package annotations.database;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.TYPE) // Applies to classes only\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface DBTable {\n", - " String name() default \"\";\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `@Target` 注解中指定的每一个 **ElementType** 就是一个约束,它告诉编译器,这个自定义的注解只能用于指定的类型。你可以指定 **enum ElementType** 中的一个值,或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 **ElementType**,那么可以省去 `@Target` 注解,但是这并不常见。\n", - "\n", - "注意 **@DBTable** 中有一个 `name()` 元素,该注解通过这个元素为处理器创建数据库时提供表的名字。\n", - "\n", - "如下是修饰字段的注解:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/Constraints.java\n", - "package annotations.database;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.FIELD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface Constraints {\n", - " boolean primaryKey() default false;\n", - " boolean allowNull() default true;\n", - " boolean unique() default false;\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/SQLString.java\n", - "package annotations.database;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.FIELD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface SQLString {\n", - " int value() default 0;\n", - " String name() default \"\";\n", - " Constraints constraints() default @Constraints;\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/SQLInteger.java\n", - "package annotations.database;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.FIELD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface SQLInteger {\n", - " String name() default \"\";\n", - " Constraints constraints() default @Constraints;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@Constraints** 注解允许处理器提供数据库表的元数据。**@Constraints** 代表了数据库通常提供的约束的一小部分,但是它所要表达的思想已经很清楚了。`primaryKey()`,`allowNull()` 和 `unique()` 元素明显的提供了默认值,从而使得在大多数情况下,该注解的使用者不需要输入太多东西。\n", - "\n", - "另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过为为示例,两个元素足够了。\n", - "\n", - "这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值是 **@Constraints**。由于在 **@Constraints** 注解类型之后,没有在括号中指明 **@Constraints** 元素的值,因此,**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的 **@Constraints** 注解中的 `unique()` 元素为 true,并作为 `constraints()` 元素的默认值,你可以像如下定义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/Uniqueness.java\n", - "// Sample of nested annotations\n", - "package annotations.database;\n", - "public @interface Uniqueness {\n", - " Constraints constraints()\n", - " default @Constraints(unique = true);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "下面是一个简单的,使用了如上注解的类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/Member.java\n", - "package annotations.database;\n", - "@DBTable(name = \"MEMBER\")\n", - "public class Member {\n", - " @SQLString(30) String firstName;\n", - " @SQLString(50) String lastName;\n", - " @SQLInteger Integer age;\n", - " @SQLString(value = 30,\n", - " constraints = @Constraints(primaryKey = true))\n", - " String reference;\n", - " static int memberCount;\n", - " public String getReference() { return reference; }\n", - " public String getFirstName() { return firstName; }\n", - " public String getLastName() { return lastName; }\n", - " @Override\n", - " public String toString() { return reference; }\n", - " public Integer getAge() { return age; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@SQLString(30)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "处理器将在创建表的时候使用该值设置 SQL 列的大小。\n", - "\n", - "默认值的语法虽然很灵巧,但是它很快就变的复杂起来。以 **reference** 字段的注解为例,上面拥有 **@SQLString** 注解,但是这个字段也将成为表的主键,因此在嵌入的 **@Constraint** 注解中设定 **primaryKey** 元素的值。这时事情就变的复杂了。你不得不为这个嵌入的注解使用很长的键—值对的形式,来指定元素名称和 **@interface** 的名称。同时,由于有特殊命名的 **value** 也不是唯一需要赋值的元素,因此不能再使用快捷方式特性。如你所见,最终结果不算清晰易懂。\n", - "\n", - "### 替代方案\n", - "\n", - "可以使用多种不同的方式来定义自己的注解用于上述任务。例如,你可以使用一个单一的注解类 **@TableColumn**,它拥有一个 **enum** 元素,元素值定义了 **STRING**,**INTEGER**,**FLOAT** 等类型。这消除了每个 SQL 类型都需要定义一个 **@interface** 的负担,不过也使得用额外信息修饰 SQL 类型变的不可能,这些额外的信息例如长度或精度等,都可能是非常有用的。\n", - "\n", - "你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解助力器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。\n", - "\n", - "第三种可行的方案是一起使用两个注解,**@Constraints** 和相应的 SQL 类型(例如,**@SQLInteger**)去注解同一个字段。这可能会让代码有些混乱,但是编译器允许你对同一个目标使用多个注解。在 Java 8,在使用多个注解的时候,你可以重复使用同一个注解。\n", - "\n", - "### 注解不支持继承\n", - "\n", - "你不能使用 **extends** 关键字来继承 **@interfaces**。这真是一个遗憾,如果可以定义 **@TableColumn** 注解(参考前面的建议),同时嵌套一个 **@SQLType** 类型的注解,将成为一个优雅的设计。按照这种方式,你可以通过继承 **@SQLType** 来创造各种 SQL 类型。例如 **@SQLInteger** 和 **@SQLString**。如果支持继承,就会大大减少打字的工作量并且使得语法更整洁。在 Java 的未来版本中,似乎没有任何关于让注解支持继承的提案,所以在当前情况下,上例中的解决方案可能已经是最佳方案了。\n", - "\n", - "### 实现处理器\n", - "\n", - "下面是一个注解处理器的例子,他将读取一个类文件,检查上面的数据库注解,并生成用于创建数据库的 SQL 命令:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/database/TableCreator.java\n", - "// Reflection-based annotation processor\n", - "// {java annotations.database.TableCreator\n", - "// annotations.database.Member}\n", - "package annotations.database;\n", - "\n", - "import java.lang.annotation.Annotation;\n", - "import java.lang.reflect.Field;\n", - "import java.util.ArrayList;\n", - "import java.util.List;\n", - "\n", - "public class TableCreator {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " if (args.length < 1) {\n", - " System.out.println(\n", - " \"arguments: annotated classes\");\n", - " System.exit(0);\n", - " }\n", - " for (String className : args) {\n", - " Class cl = Class.forName(className);\n", - " DBTable dbTable = cl.getAnnotation(DBTable.class);\n", - " if (dbTable == null) {\n", - " System.out.println(\n", - " \"No DBTable annotations in class \" +\n", - " className);\n", - " continue;\n", - " }\n", - " String tableName = dbTable.name();\n", - " // If the name is empty, use the Class name:\n", - " if (tableName.length() < 1)\n", - " tableName = cl.getName().toUpperCase();\n", - " List columnDefs = new ArrayList<>();\n", - " for (Field field : cl.getDeclaredFields()) {\n", - " String columnName = null;\n", - " Annotation[] anns =\n", - " field.getDeclaredAnnotations();\n", - " if (anns.length < 1)\n", - " continue; // Not a db table column\n", - " if (anns[0] instanceof SQLInteger) {\n", - " SQLInteger sInt = (SQLInteger) anns[0];\n", - " // Use field name if name not specified\n", - " if (sInt.name().length() < 1)\n", - " columnName = field.getName().toUpperCase();\n", - " else\n", - " columnName = sInt.name();\n", - " columnDefs.add(columnName + \" INT\" +\n", - " getConstraints(sInt.constraints()));\n", - " }\n", - " if (anns[0] instanceof SQLString) {\n", - " SQLString sString = (SQLString) anns[0];\n", - " // Use field name if name not specified.\n", - " if (sString.name().length() < 1)\n", - " columnName = field.getName().toUpperCase();\n", - " else\n", - " columnName = sString.name();\n", - " columnDefs.add(columnName + \" VARCHAR(\" +\n", - " sString.value() + \")\" +\n", - " getConstraints(sString.constraints()));\n", - " }\n", - " StringBuilder createCommand = new StringBuilder(\n", - " \"CREATE TABLE \" + tableName + \"(\");\n", - " for (String columnDef : columnDefs)\n", - " createCommand.append(\n", - " \"\\n \" + columnDef + \",\");\n", - " // Remove trailing comma\n", - " String tableCreate = createCommand.substring(\n", - " 0, createCommand.length() - 1) + \");\";\n", - " System.out.println(\"Table Creation SQL for \" +\n", - " className + \" is:\\n\" + tableCreate);\n", - " }\n", - " }\n", - " }\n", - "\n", - " private static String getConstraints(Constraints con) {\n", - " String constraints = \"\";\n", - " if (!con.allowNull())\n", - " constraints += \" NOT NULL\";\n", - " if (con.primaryKey())\n", - " constraints += \" PRIMARY KEY\";\n", - " if (con.unique())\n", - " constraints += \" UNIQUE\";\n", - " return constraints;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "sql" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Table Creation SQL for annotations.database.Member is:\n", - "CREATE TABLE MEMBER(\n", - " FIRSTNAME VARCHAR(30));\n", - "Table Creation SQL for annotations.database.Member is:\n", - "CREATE TABLE MEMBER(\n", - " FIRSTNAME VARCHAR(30),\n", - " LASTNAME VARCHAR(50));\n", - "Table Creation SQL for annotations.database.Member is:\n", - "CREATE TABLE MEMBER(\n", - " FIRSTNAME VARCHAR(30),\n", - " LASTNAME VARCHAR(50),\n", - " AGE INT);\n", - "Table Creation SQL for annotations.database.Member is:\n", - "CREATE TABLE MEMBER(\n", - " FIRSTNAME VARCHAR(30),\n", - " LASTNAME VARCHAR(50),\n", - " AGE INT,\n", - " REFERENCE VARCHAR(30) PRIMARY KEY);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "主方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载,并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在,将表名存储起来。然后读取这个类的所有字段,并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话,在对应的处理块中将构造出相应的数据库列的字符串片段。注意,由于注解没有继承机制,如果要获取近似多态的行为,使用 `getDeclaredAnnotations()` 似乎是唯一的方式。\n", - "\n", - "嵌套的 **@Constraint** 注解被传递给 `getConstraints()`方法,并用它来构造一个包含 SQL 约束的 String 对象。\n", - "\n", - "需要提醒的是,上面演示的技巧对于真实的对象/映射关系而言,是十分幼稚的。使用 **@DBTable** 的注解来获取表的名称,这使得如果要修改表的名字,则迫使你重新编译 Java 代码。这种效果并不理想。现在已经有了很多可用的框架,用于将对象映射到数据库中,并且越来越多的框架开始使用注解了。\n", - "\n", - "\n", - "\n", - "## 使用javac处理注解\n", - "\n", - "通过 **javac**,你可以通过创建编译时(compile-time)注解处理器在 Java 源文件上使用注解,而不是编译之后的 class 文件。但是这里有一个重大限制:你不能通过处理器来改变源代码。唯一影响输出的方式就是创建新的文件。\n", - "\n", - "如果你的注解处理器创建了新的源文件,在新一轮处理中注解会检查源文件本身。工具在检测一轮之后持续循环,直到不再有新的源文件产生。然后它编译所有的源文件。\n", - "\n", - "每一个你编写的注解都需要处理器,但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类,并且你可以添加监听器用于监听注解处理完成后接到通知。\n", - "\n", - "本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和StackOverflow 的准备。\n", - "\n", - "### 最简单的处理器\n", - "\n", - "让我们开始定义我们能想到的最简单的处理器,只是为了编译和测试。如下是注解的定义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/simplest/Simple.java\n", - "// A bare-bones annotation\n", - "package annotations.simplest;\n", - "import java.lang.annotation.Retention;\n", - "import java.lang.annotation.RetentionPolicy;\n", - "import java.lang.annotation.Target;\n", - "import java.lang.annotation.ElementType;\n", - "@Retention(RetentionPolicy.SOURCE)\n", - "@Target({ElementType.TYPE, ElementType.METHOD,\n", - " ElementType.CONSTRUCTOR,\n", - " ElementType.ANNOTATION_TYPE,\n", - " ElementType.PACKAGE, ElementType.FIELD,\n", - " ElementType.LOCAL_VARIABLE})\n", - "public @interface Simple {\n", - " String value() default \"-default-\";\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@Retention** 的参数现在为 **SOURCE**,这意味着注解不会再存留在编译后的代码。这在编译时处理注解是没有必要的,它只是指出,在这里,**javac** 是唯一有机会处理注解的代理。\n", - "\n", - "**@Target** 声明了几乎所有的目标类型(除了 **PACKAGE**) ,同样是为了演示。下面是一个测试示例。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/simplest/SimpleTest.java\n", - "// Test the \"Simple\" annotation\n", - "// {java annotations.simplest.SimpleTest}\n", - "package annotations.simplest;\n", - "@Simple\n", - "public class SimpleTest {\n", - " @Simple\n", - " int i;\n", - " @Simple\n", - " public SimpleTest() {}\n", - " @Simple\n", - " public void foo() {\n", - " System.out.println(\"SimpleTest.foo()\");\n", - " }\n", - " @Simple\n", - " public void bar(String s, int i, float f) {\n", - " System.out.println(\"SimpleTest.bar()\");\n", - " }\n", - " @Simple\n", - " public static void main(String[] args) {\n", - " @Simple\n", - " SimpleTest st = new SimpleTest();\n", - " st.foo();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "SimpleTest.foo()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里我们使用 **@Simple** 注解了所有 **@Target** 声明允许的地方。\n", - "\n", - "**SimpleTest.java** 只需要 **Simple.java** 就可以编译成功。当我们编译的时候什么都没有发生。\n", - "\n", - "**javac** 允许 **@Simple** 注解(只要它存在)在我们创建处理器并将其 hook 到编译器之前,不做任何事情。\n", - "\n", - "如下是一个十分简单的处理器,其所作的事情就是把注解相关的信息打印出来:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/simplest/SimpleProcessor.java\n", - "// A bare-bones annotation processor\n", - "package annotations.simplest;\n", - "import javax.annotation.processing.*;\n", - "import javax.lang.model.SourceVersion;\n", - "import javax.lang.model.element.*;\n", - "import java.util.*;\n", - "@SupportedAnnotationTypes(\n", - " \"annotations.simplest.Simple\")\n", - "@SupportedSourceVersion(SourceVersion.RELEASE_8)\n", - "public class SimpleProcessor\n", - " extends AbstractProcessor {\n", - " @Override\n", - " public boolean process(\n", - " Set annotations,\n", - " RoundEnvironment env) {\n", - " for(TypeElement t : annotations)\n", - " System.out.println(t);\n", - " for(Element el :\n", - " env.getElementsAnnotatedWith(Simple.class))\n", - " display(el);\n", - " return false;\n", - " }\n", - " private void display(Element el) {\n", - " System.out.println(\"==== \" + el + \" ====\");\n", - " System.out.println(el.getKind() +\n", - " \" : \" + el.getModifiers() +\n", - " \" : \" + el.getSimpleName() +\n", - " \" : \" + el.asType());\n", - " if(el.getKind().equals(ElementKind.CLASS)) {\n", - " TypeElement te = (TypeElement)el;\n", - " System.out.println(te.getQualifiedName());\n", - " System.out.println(te.getSuperclass());\n", - " System.out.println(te.getEnclosedElements());\n", - " }\n", - " if(el.getKind().equals(ElementKind.METHOD)) {\n", - " ExecutableElement ex = (ExecutableElement)el;\n", - " System.out.print(ex.getReturnType() + \" \");\n", - " System.out.print(ex.getSimpleName() + \"(\");\n", - " System.out.println(ex.getParameters() + \")\");\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的示例关于注解如何简化你的代码)。\n", - "\n", - "你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。\n", - "\n", - "**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。\n", - "\n", - "动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。\n", - "\n", - "如果只是通过平常的方式来编译 **SimpleTest.java**,你不会得到任何结果。为了得到注解输出,你必须增加一个 **processor** 标志并且连接注解处理器类" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "javac -processor annotations.simplest.SimpleProcessor SimpleTest.java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在编译器有了输出" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.simplest.Simple\n", - "==== annotations.simplest.SimpleTest ====\n", - "CLASS : [public] : SimpleTest : annotations.simplest.SimpleTest\n", - "annotations.simplest.SimpleTest\n", - "java.lang.Object\n", - "i,SimpleTest(),foo(),bar(java.lang.String,int,float),main(java.lang.String[])\n", - "==== i ====\n", - "FIELD : [] : i : int\n", - "==== SimpleTest() ====\n", - "CONSTRUCTOR : [public] : : ()void\n", - "==== foo() ====\n", - "METHOD : [public] : foo : ()void\n", - "void foo()\n", - "==== bar(java.lang.String,int,float) ====\n", - "METHOD : [public] : bar : (java.lang.String,int,float)void\n", - "void bar(s,i,f)\n", - "==== main(java.lang.String[]) ====\n", - "METHOD : [public, static] : main : (java.lang.String[])void\n", - "void main(args)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这给了你一些可以发现的东西,包括参数名和类型、返回值等。\n", - "\n", - "### 更复杂的处理器\n", - "\n", - "当你创建用于 javac 注解处理器时,你不能使用 Java 的反射特性,因为你处理的是源代码,而并非是编译后的 class 文件。各种 mirror[^3 ] 解决这个问题的方法是,通过允许你在未编译的源代码中查看方法、字段和类型。\n", - "\n", - "如下是一个用于提取类中方法的注解,所以它可以被抽取成为一个接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/ifx/ExtractInterface.java\n", - "// javac-based annotation processing\n", - "package annotations.ifx;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.TYPE)\n", - "@Retention(RetentionPolicy.SOURCE)\n", - "public @interface ExtractInterface {\n", - " String interfaceName() default \"-!!-\";\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**RetentionPolicy** 的值为 **SOURCE**,这是为了在提取类中的接口之后不再将注解信息保留在 class 文件中。接下来的测试类提供了一些公用方法,这些方法可以成为接口的一部分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/ifx/Multiplier.java\n", - "// javac-based annotation processing\n", - "// {java annotations.ifx.Multiplier}\n", - "package annotations.ifx;\n", - "@ExtractInterface(interfaceName=\"IMultiplier\")\n", - "public class Multiplier {\n", - " public boolean flag = false;\n", - " private int n = 0;\n", - " public int multiply(int x, int y) {\n", - " int total = 0;\n", - " for(int i = 0; i < x; i++)\n", - " total = add(total, y);\n", - " return total;\n", - " }\n", - " public int fortySeven() { return 47; }\n", - " private int add(int x, int y) {\n", - " return x + y;\n", - " }\n", - " public double timesTen(double arg) {\n", - " return arg * 10;\n", - " }\n", - " public static void main(String[] args) {\n", - " Multiplier m = new Multiplier();\n", - " System.out.println(\n", - " \"11 * 16 = \" + m.multiply(11, 16));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "11 * 16 = 176" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Multiplier** 类(只能处理正整数)拥有一个 `multiply()` 方法,这个方法会多次调用私有方法 `add()` 来模拟乘法操作。` add()` 是私有方法,因此不能成为接口的一部分。其他的方法提供了语法多样性。注解被赋予 **IMultiplier** 的 **InterfaceName** 作为要创建的接口的名称。\n", - "\n", - "这里有一个编译时处理器用于提取有趣的方法,并创建一个新的 interface 源代码文件(这个源文件将会在下一轮中被自动编译):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/ifx/IfaceExtractorProcessor.java\n", - "// javac-based annotation processing\n", - "package annotations.ifx;\n", - "import javax.annotation.processing.*;\n", - "import javax.lang.model.SourceVersion;\n", - "import javax.lang.model.element.*;\n", - "import javax.lang.model.util.*;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.io.*;\n", - "@SupportedAnnotationTypes(\n", - " \"annotations.ifx.ExtractInterface\")\n", - "@SupportedSourceVersion(SourceVersion.RELEASE_8)\n", - "public class IfaceExtractorProcessor\n", - " extends AbstractProcessor {\n", - " private ArrayList\n", - " interfaceMethods = new ArrayList<>();\n", - " Elements elementUtils;\n", - " private ProcessingEnvironment processingEnv;\n", - " @Override\n", - " public void init(\n", - " ProcessingEnvironment processingEnv) {\n", - " this.processingEnv = processingEnv;\n", - " elementUtils = processingEnv.getElementUtils();\n", - " }\n", - " @Override\n", - " public boolean process(\n", - " Set annotations,\n", - " RoundEnvironment env) {\n", - " for(Element elem:env.getElementsAnnotatedWith(\n", - " ExtractInterface.class)) {\n", - " String interfaceName = elem.getAnnotation(\n", - " ExtractInterface.class).interfaceName();\n", - " for(Element enclosed :\n", - " elem.getEnclosedElements()) {\n", - " if(enclosed.getKind()\n", - " .equals(ElementKind.METHOD) &&\n", - " enclosed.getModifiers()\n", - " .contains(Modifier.PUBLIC) &&\n", - " !enclosed.getModifiers()\n", - " .contains(Modifier.STATIC)) {\n", - " interfaceMethods.add(enclosed);\n", - " }\n", - " }\n", - " if(interfaceMethods.size() > 0)\n", - " writeInterfaceFile(interfaceName);\n", - " }\n", - " return false;\n", - " }\n", - " private void\n", - " writeInterfaceFile(String interfaceName) {\n", - " try(\n", - " Writer writer = processingEnv.getFiler()\n", - " .createSourceFile(interfaceName)\n", - " .openWriter()\n", - " ) {\n", - " String packageName = elementUtils\n", - " .getPackageOf(interfaceMethods\n", - " .get(0)).toString();\n", - " writer.write(\n", - " \"package \" + packageName + \";\\n\");\n", - " writer.write(\"public interface \" +\n", - " interfaceName + \" {\\n\");\n", - " for(Element elem : interfaceMethods) {\n", - " ExecutableElement method =\n", - " (ExecutableElement)elem;\n", - " String signature = \" public \";\n", - " signature += method.getReturnType() + \" \";\n", - " signature += method.getSimpleName();\n", - " signature += createArgList(\n", - " method.getParameters());\n", - " System.out.println(signature);\n", - " writer.write(signature + \";\\n\");\n", - " }\n", - " writer.write(\"}\");\n", - " } catch(Exception e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " private String createArgList(\n", - " List parameters) {\n", - " String args = parameters.stream()\n", - " .map(p -> p.asType() + \" \" + p.getSimpleName())\n", - " .collect(Collectors.joining(\", \"));\n", - " return \"(\" + args + \")\";\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Elements** 对象实例 **elementUtils** 是一组静态方法的工具;我们用它来寻找 **writeInterfaceFile()** 中含有的包名。\n", - "\n", - "`getEnclosedElements()`方法会通过指定的元素生成所有的“闭包”元素。在这里,这个类闭包了它的所有元素。通过使用 `getKind()` 我们会找到所有的 **public** 和 **static** 方法,并将其添加到 **interfaceMethods** 列表中。接下来 `writeInterfaceFile()` 使用 **interfaceMethods** 列表里面的值生成新的接口定义。注意,在 `writeInterfaceFile()` 使用了向下转型到 **ExecutableElement**,这使得我们可以获取所有的方法信息。**createArgList()** 是一个帮助方法,用于生成参数列表。\n", - "\n", - "**Filer**是 `getFiler()` 生成的,并且是 **PrintWriter** 的一种实例,可以用于创建新文件。我们使用 **Filer** 对象,而不是原生的 **PrintWriter** 原因是,这个对象可以运行 **javac** 追踪你创建的新文件,这使得它可以在新一轮中检查新文件中的注解并编译文件。\n", - "\n", - "如下是一个命令行,可以在编译的时候使用处理器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "javac -processor annotations.ifx.IfaceExtractorProcessor Multiplier.java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "新生成的 **IMultiplier.java** 的文件,正如你通过查看上面处理器的 `println()` 语句所猜测的那样,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "package annotations.ifx;\n", - "public interface IMultiplier {\n", - " public int multiply(int x, int y);\n", - " public int fortySeven();\n", - " public double timesTen(double arg);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个类同样会被 **javac** 编译(在某一轮中),所以你会在同一个目录中看到 **IMultiplier.class** 文件。\n", - "\n", - "\n", - "\n", - "## 基于注解的单元测试\n", - "\n", - "单元测试是对类中每个方法提供一个或者多个测试的一种事件,其目的是为了有规律的测试一个类中每个部分是否具备正确的行为。在 Java 中,最著名的单元测试工具就是 **JUnit**。**JUnit** 4 版本已经包含了注解。在注解版本之前的 JUnit 一个最主要的问题是,为了启动和运行 **JUnit** 测试,有大量的“仪式”需要标注。这种负担已经减轻了一些,**但是**注解使得测试更接近“可以工作的最简单的测试系统”。\n", - "\n", - "在注解版本之前的 JUnit,你必须创建一个单独的文件来保存单元测试。通过注解,我们可以将单元测试集成在需要被测试的类中,从而将单元测试的时间和麻烦降到了最低。这种方式有额外的好处,就是使得测试私有方法和公有方法变的一样容易。\n", - "\n", - "这个基于注解的测试框架叫做 **@Unit**。其最基本的测试形式,可能也是你使用的最多的一个注解是 **@Test**,我们使用 **@Test** 来标记测试方法。测试方法不带参数,并返回 **boolean** 结果来说明测试方法成功或者失败。你可以任意命名它的测试方法。同时 **@Unit** 测试方法可以是任意你喜欢的访问修饰方法,包括 **private**。\n", - "\n", - "要使用 **@Unit**,你必须导入 **onjava.atunit** 包,并且使用 **@Unit** 的测试标记为合适的方法和字段打上标签(在接下来的例子中你会学到),然后让你的构建系统对编译后的类运行 **@Unit**,下面是一个简单的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AtUnitExample1.java\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AtUnitExample1.class}\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AtUnitExample1 {\n", - " public String methodOne() {\n", - " return \"This is methodOne\";\n", - " }\n", - " public int methodTwo() {\n", - " System.out.println(\"This is methodTwo\");\n", - " return 2;\n", - " }\n", - " @Test\n", - " boolean methodOneTest() {\n", - " return methodOne().equals(\"This is methodOne\");\n", - " }\n", - " @Test\n", - " boolean m2() { return methodTwo() == 2; }\n", - " @Test\n", - " private boolean m3() { return true; }\n", - " // Shows output for failure:\n", - " @Test\n", - " boolean failureTest() { return false; }\n", - " @Test\n", - " boolean anotherDisappointment() {\n", - " return false;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AtUnitExample1\n", - ". m3\n", - ". methodOneTest\n", - ". m2 This is methodTwo\n", - ". failureTest (failed)\n", - ". anotherDisappointment (failed)\n", - "(5 tests)\n", - ">>> 2 FAILURES <<<\n", - "annotations.AtUnitExample1: failureTest\n", - "annotations.AtUnitExample1: anotherDisappointment" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 **@Unit** 进行测试的类必须定义在某个包中(即必须包括 **package** 声明)。\n", - "\n", - "**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 a`notherDisappointment()` 方法之前,它们告诉 **@Unit** 方法作为单元测试来运行。同时 **@Test** 确保这些方法没有任何参数并且返回值为 **boolean** 或者 **void**。当你填写单元测试时,唯一需要做的就是决定测试是成功还是失败,(对于返回值为 **boolean** 的方法)应该返回 **ture** 还是 **false**。\n", - "\n", - "如果你熟悉 **JUnit**,你还将注意到 **@Unit** 输出的信息更多。你会看到现在正在运行的测试的输出更有用,最后它会告诉你导致失败的类和测试。\n", - "\n", - "你并非必须将测试方法嵌入到原来的类中,有时候这种事情根本做不到。要生产一个非嵌入式的测试,最简单的方式就是继承:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AUExternalTest.java\n", - "// Creating non-embedded tests\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AUExternalTest.class}\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AUExternalTest extends AtUnitExample1 {\n", - " @Test\n", - " boolean _MethodOne() {\n", - " return methodOne().equals(\"This is methodOne\");\n", - " }\n", - " @Test\n", - " boolean _MethodTwo() {\n", - " return methodTwo() == 2;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AUExternalTest\n", - ". tMethodOne\n", - ". tMethodTwo This is methodTwo\n", - "OK (2 tests)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个示例还表现出灵活命名的价值。在这里,**@Test** 方法被命名为下划线前缀加上要测试的方法名称(我并不认为这是一种理想的命名形式,这只是表现一种可能性罢了)。\n", - "\n", - "你也可以使用组合来创建非嵌入式的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AUComposition.java\n", - "// Creating non-embedded tests\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AUComposition.class}\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AUComposition {\n", - " AtUnitExample1 testObject = new AtUnitExample1();\n", - " @Test\n", - " boolean tMethodOne() {\n", - " return testObject.methodOne()\n", - " .equals(\"This is methodOne\");\n", - " }\n", - " @Test\n", - " boolean tMethodTwo() {\n", - " return testObject.methodTwo() == 2;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AUComposition\n", - ". tMethodTwo This is methodTwo\n", - ". tMethodOne\n", - "OK (2 tests)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为在每一个测试里面都会创建 **AUComposition** 对象,所以创建新的成员变量 **testObject** 用于以后的每一个测试方法。\n", - "\n", - "因为 **@Unit** 中没有 **JUnit** 中特殊的 **assert** 方法,不过另一种形式的 **@Test** 方法仍然允许返回值为 **void**(如果你还想使用 **true** 或者 **false** 的话,也可以使用 **boolean** 作为方法返回值类型)。为了表示测试成功,可以使用 Java 的 **assert** 语句。Java 断言机制需要你在 java 命令行行加上 **-ea** 标志来开启,但是 **@Unit** 已经自动开启了该功能。要表示测试失败的话,你甚至可以使用异常。**@Unit** 的设计目标之一就是尽可能减少添加额外的语法,而 Java 的 **assert** 和异常对于报告错误而言,即已经足够了。一个失败的 **assert** 或者从方法从抛出的异常都被视为测试失败,但是 **@Unit** 不会在这个失败的测试上卡住,它会继续运行,直到所有测试完毕,下面是一个示例程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AtUnitExample2.java\n", - "// Assertions and exceptions can be used in @Tests\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AtUnitExample2.class}\n", - "package annotations;\n", - "import java.io.*;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AtUnitExample2 {\n", - " public String methodOne() {\n", - " return \"This is methodOne\";\n", - " }\n", - " public int methodTwo() {\n", - " System.out.println(\"This is methodTwo\");\n", - " return 2;\n", - " }\n", - " @Test\n", - " void assertExample() {\n", - " assert methodOne().equals(\"This is methodOne\");\n", - " }\n", - " @Test\n", - " void assertFailureExample() {\n", - " assert 1 == 2: \"What a surprise!\";\n", - " }\n", - " @Test\n", - " void exceptionExample() throws IOException {\n", - " try(FileInputStream fis =\n", - " new FileInputStream(\"nofile.txt\")) {} // Throws\n", - " }\n", - " @Test\n", - " boolean assertAndReturn() {\n", - " // Assertion with message:\n", - " assert methodTwo() == 2: \"methodTwo must equal 2\";\n", - " return methodOne().equals(\"This is methodOne\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AtUnitExample2\n", - ". exceptionExample java.io.FileNotFoundException:\n", - "nofile.txt (The system cannot find the file specified)\n", - "(failed)\n", - ". assertExample\n", - ". assertAndReturn This is methodTwo\n", - ". assertFailureExample java.lang.AssertionError: What\n", - "a surprise!\n", - "(failed)\n", - "(4 tests)\n", - ">>> 2 FAILURES <<<\n", - "annotations.AtUnitExample2: exceptionExample\n", - "annotations.AtUnitExample2: assertFailureExample" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如下是一个使用非嵌入式测试的例子,并且使用了断言,它将会对 **java.util.HashSet** 进行一些简单的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/HashSetTest.java\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/HashSetTest.class}\n", - "package annotations;\n", - "import java.util.*;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class HashSetTest {\n", - " HashSet testObject = new HashSet<>();\n", - " @Test\n", - " void initialization() {\n", - " assert testObject.isEmpty();\n", - " }\n", - " @Test\n", - " void _Contains() {\n", - " testObject.add(\"one\");\n", - " assert testObject.contains(\"one\");\n", - " }\n", - " @Test\n", - " void _Remove() {\n", - " testObject.add(\"one\");\n", - " testObject.remove(\"one\");\n", - " assert testObject.isEmpty();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "采用继承的方式可能会更简单,也没有一些其他的约束。\n", - "\n", - "对每一个单元测试而言,**@Unit** 都会使用默认的无参构造器,为该测试类所属的类创建出一个新的实例。并在此新创建的对象上运行测试,然后丢弃该对象,以免对其他测试产生副作用。如此创建对象导致我们依赖于类的默认构造器。如果你的类没有默认构造器,或者对象需要复杂的构造过程,那么你可以创建一个 **static** 方法专门负责构造对象,然后使用 **@TestObjectCreate** 注解标记该方法,例子如下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AtUnitExample3.java\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AtUnitExample3.class}\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AtUnitExample3 {\n", - " private int n;\n", - " public AtUnitExample3(int n) { this.n = n; }\n", - " public int getN() { return n; }\n", - " public String methodOne() {\n", - " return \"This is methodOne\";\n", - " }\n", - " public int methodTwo() {\n", - " System.out.println(\"This is methodTwo\");\n", - " return 2;\n", - " }\n", - " @TestObjectCreate\n", - " static AtUnitExample3 create() {\n", - " return new AtUnitExample3(47);\n", - " }\n", - " @Test\n", - " boolean initialization() { return n == 47; }\n", - " @Test\n", - " boolean methodOneTest() {\n", - " return methodOne().equals(\"This is methodOne\");\n", - " }\n", - " @Test\n", - " boolean m2() { return methodTwo() == 2; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AtUnitExample3\n", - ". initialization\n", - ". m2 This is methodTwo\n", - ". methodOneTest\n", - "OK (3 tests)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@TestObjectCreate** 修饰的方法必须声明为 **static** ,且必须返回一个你正在测试的类型对象,这一切都由 **@Unit** 负责确保成立。\n", - "\n", - "有的时候,你需要向单元测试中增加一些字段。这时候可以使用 **@TestProperty** 注解,由它注解的字段表示只在单元测试中使用(因此,在你将产品发布给客户之前,他们应该被删除)。在下面的例子中,一个 **String** 通过 `String.split()` 方法进行分割,从其中读取一个值,这个值将会被生成测试对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AtUnitExample4.java\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AtUnitExample4.class}\n", - "// {VisuallyInspectOutput}\n", - "package annotations;\n", - "import java.util.*;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AtUnitExample4 {\n", - " static String theory = \"All brontosauruses \" +\n", - " \"are thin at one end, much MUCH thicker in the \" +\n", - " \"middle, and then thin again at the far end.\";\n", - " private String word;\n", - " private Random rand = new Random(); // Time-based seed\n", - " public AtUnitExample4(String word) {\n", - " this.word = word;\n", - " }\n", - " public String getWord() { return word; }\n", - " public String scrambleWord() {\n", - " List chars = Arrays.asList(\n", - " ConvertTo.boxed(word.toCharArray()));\n", - " Collections.shuffle(chars, rand);\n", - " StringBuilder result = new StringBuilder();\n", - " for(char ch : chars)\n", - " result.append(ch);\n", - " return result.toString();\n", - " }\n", - " @TestProperty\n", - " static List input =\n", - " Arrays.asList(theory.split(\" \"));\n", - " @TestProperty\n", - " static Iterator words = input.iterator();\n", - " @TestObjectCreate\n", - " static AtUnitExample4 create() {\n", - " if(words.hasNext())\n", - " return new AtUnitExample4(words.next());\n", - " else\n", - " return null;\n", - " }\n", - " @Test\n", - " boolean words() {\n", - " System.out.println(\"'\" + getWord() + \"'\");\n", - " return getWord().equals(\"are\");\n", - " }\n", - " @Test\n", - " boolean scramble1() {\n", - "// Use specific seed to get verifiable results:\n", - " rand = new Random(47);\n", - " System.out.println(\"'\" + getWord() + \"'\");\n", - " String scrambled = scrambleWord();\n", - " System.out.println(scrambled);\n", - " return scrambled.equals(\"lAl\");\n", - " }\n", - " @Test\n", - " boolean scramble2() {\n", - " rand = new Random(74);\n", - " System.out.println(\"'\" + getWord() + \"'\");\n", - " String scrambled = scrambleWord();\n", - " System.out.println(scrambled);\n", - " return scrambled.equals(\"tsaeborornussu\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AtUnitExample4\n", - ". words 'All'\n", - "(failed)\n", - ". scramble1 'brontosauruses'\n", - "ntsaueorosurbs\n", - "(failed)\n", - ". scramble2 'are'\n", - "are\n", - "(failed)\n", - "(3 tests)\n", - ">>> 3 FAILURES <<<\n", - "annotations.AtUnitExample4: words\n", - "annotations.AtUnitExample4: scramble1\n", - "annotations.AtUnitExample4: scramble2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**@TestProperty** 也可以用来标记那些只在测试中使用的方法,但是它们本身不是测试方法。\n", - "\n", - "如果你的测试对象需要执行某些初始化工作,并且使用完成之后还需要执行清理工作,那么可以选择使用 **static** 的 **@TestObjectCleanup** 方法,当测试对象使用结束之后,该方法会为你执行清理工作。在下面的示例中,**@TestObjectCleanup** 为每一个测试对象都打开了一个文件,因此必须在丢弃测试的时候关闭该文件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/AtUnitExample5.java\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/AtUnitExample5.class}\n", - "package annotations;\n", - "import java.io.*;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class AtUnitExample5 {\n", - " private String text;\n", - " public AtUnitExample5(String text) {\n", - " this.text = text;\n", - " }\n", - " @Override\n", - " public String toString() { return text; }\n", - " @TestProperty\n", - " static PrintWriter output;\n", - " @TestProperty\n", - " static int counter;\n", - " @TestObjectCreate\n", - " static AtUnitExample5 create() {\n", - " String id = Integer.toString(counter++);\n", - " try {\n", - " output = new PrintWriter(\"Test\" + id + \".txt\");\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " return new AtUnitExample5(id);\n", - " }\n", - " @TestObjectCleanup\n", - " static void cleanup(AtUnitExample5 tobj) {\n", - " System.out.println(\"Running cleanup\");\n", - " output.close();\n", - " }\n", - " @Test\n", - " boolean test1() {\n", - " output.print(\"test1\");\n", - " return true;\n", - " }\n", - " @Test\n", - " boolean test2() {\n", - " output.print(\"test2\");\n", - " return true;\n", - " }\n", - " @Test\n", - " boolean test3() {\n", - " output.print(\"test3\");\n", - " return true;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.AtUnitExample5\n", - ". test1\n", - "Running cleanup\n", - ". test3\n", - "Running cleanup\n", - ". test2\n", - "Running cleanup\n", - "OK (3 tests)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在输出中我们可以看到,清理方法会在每个测试方法结束之后自动运行。\n", - "\n", - "### 在 @Unit 中使用泛型\n", - "\n", - "泛型为 **@Unit** 出了一个难题,因为我们不可能“通用测试”。我们必须针对某个特定类型的参数或者参数集才能进行测试。解决方法十分简单,让测试类继承自泛型类的一个特定版本即可:\n", - "\n", - "下面是一个 **stack** 的简单实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/StackL.java\n", - "// A stack built on a LinkedList\n", - "package annotations;\n", - "import java.util.*;\n", - "public class StackL {\n", - " private LinkedList list = new LinkedList<>();\n", - " public void push(T v) { list.addFirst(v); }\n", - " public T top() { return list.getFirst(); }\n", - " public T pop() { return list.removeFirst(); }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了测试 String 版本,我们直接让测试类继承一个 Stack\\ :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/StackLStringTst.java\n", - "// Applying @Unit to generics\n", - "// {java onjava.atunit.AtUnit\n", - "// build/classes/main/annotations/StackLStringTst.class}\n", - "package annotations;\n", - "import onjava.atunit.*;\n", - "import onjava.*;\n", - "public class\n", - "StackLStringTst extends StackL {\n", - " @Test\n", - " void tPush() {\n", - " push(\"one\");\n", - " assert top().equals(\"one\");\n", - " push(\"two\");\n", - " assert top().equals(\"two\");\n", - " }\n", - " @Test\n", - " void tPop() {\n", - " push(\"one\");\n", - " push(\"two\");\n", - " assert pop().equals(\"two\");\n", - " assert pop().equals(\"one\");\n", - " }\n", - " @Test\n", - " void tTop() {\n", - " push(\"A\");\n", - " push(\"B\");\n", - " assert top().equals(\"B\");\n", - " assert top().equals(\"B\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "annotations.StackLStringTst\n", - ". tTop\n", - ". tPush\n", - ". tPop\n", - "OK (3 tests)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这种方法存在的唯一缺点是,继承使我们失去了访问被测试的类中 **private** 方法的能力。这对你非常重要,那你要么把 private 方法变为 **protected**,要么添加一个非 **private** 的 **@TestProperty** 方法,由它来调用 **private** 方法(稍后我们会看到,**AtUnitRemover** 会删除产品中的 **@TestProperty** 方法)。\n", - "\n", - "**@Unit** 搜索那些包含合适注解的类文件,然后运行 **@Test** 方法。我的主要目标就是让 **@Unit** 测试系统尽可能的透明,使得人们使用它的时候只需要添加 **@Test** 注解,而不需要特殊的编码和知识(现在版本的 **JUnit** 符合这个实践)。不过,如果说编写测试不会遇到任何困难,也不太可能,因此 **@Unit** 会尽量让这些困难变的微不足道,希望通过这种方式,你们会更乐意编写测试。\n", - "\n", - "### 实现 @Unit\n", - "\n", - "首先我们需要定义所有的注解类型。这些都是简单的标签,并且没有任何字段。@Test 标签在本章开头已经定义过了,这里是其他所需要的注解:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/TestObjectCreate.java\n", - "// The @Unit @TestObjectCreate tag\n", - "package onjava.atunit;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.METHOD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface TestObjectCreate {}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/TestObjectCleanup.java\n", - "// The @Unit @TestObjectCleanup tag\n", - "package onjava.atunit;\n", - "import java.lang.annotation.*;\n", - "@Target(ElementType.METHOD)\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface TestObjectCleanup {}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/TestProperty.java\n", - "// The @Unit @TestProperty tag\n", - "package onjava.atunit;\n", - "import java.lang.annotation.*;\n", - "// Both fields and methods can be tagged as properties:\n", - "@Target({ElementType.FIELD, ElementType.METHOD})\n", - "@Retention(RetentionPolicy.RUNTIME)\n", - "public @interface TestProperty {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "所有测试的保留属性都为 **RUNTIME**,这是因为 **@Unit** 必须在编译后的代码中发现这些注解。\n", - "\n", - "要实现系统并运行测试,我们还需要反射机制来提取注解。下面这个程序通过注解中的信息,决定如何构造测试对象,并在测试对象上运行测试。正是由于注解帮助,这个程序才会如此短小而直接:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/AtUnit.java\n", - "// An annotation-based unit-test framework\n", - "// {java onjava.atunit.AtUnit}\n", - "package onjava.atunit;\n", - "import java.lang.reflect.*;\n", - "import java.io.*;\n", - "import java.util.*;\n", - "import java.nio.file.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "public class AtUnit implements ProcessFiles.Strategy {\n", - " static Class testClass;\n", - " static List failedTests= new ArrayList<>();\n", - " static long testsRun = 0;\n", - " static long failures = 0;\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " ClassLoader.getSystemClassLoader()\n", - " .setDefaultAssertionStatus(true); // Enable assert\n", - " new ProcessFiles(new AtUnit(), \"class\").start(args);\n", - " if(failures == 0)\n", - " System.out.println(\"OK (\" + testsRun + \" tests)\");\n", - " else {\n", - " System.out.println(\"(\" + testsRun + \" tests)\");\n", - " System.out.println(\n", - " \"\\n>>> \" + failures + \" FAILURE\" +\n", - " (failures > 1 ? \"S\" : \"\") + \" <<<\");\n", - " for(String failed : failedTests)\n", - " System.out.println(\" \" + failed);\n", - " }\n", - " }\n", - " @Override\n", - " public void process(File cFile) {\n", - " try {\n", - " String cName = ClassNameFinder.thisClass(\n", - " Files.readAllBytes(cFile.toPath()));\n", - " if(!cName.startsWith(\"public:\"))\n", - " return;\n", - " cName = cName.split(\":\")[1];\n", - " if(!cName.contains(\".\"))\n", - " return; // Ignore unpackaged classes\n", - " testClass = Class.forName(cName);\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " TestMethods testMethods = new TestMethods();\n", - " Method creator = null;\n", - " Method cleanup = null;\n", - " for(Method m : testClass.getDeclaredMethods()) {\n", - " testMethods.addIfTestMethod(m);\n", - " if(creator == null)\n", - " creator = checkForCreatorMethod(m);\n", - " if(cleanup == null)\n", - " cleanup = checkForCleanupMethod(m);\n", - " }\n", - " if(testMethods.size() > 0) {\n", - " if(creator == null)\n", - " try {\n", - " if(!Modifier.isPublic(testClass\n", - " .getDeclaredConstructor()\n", - " .getModifiers())) {\n", - " System.out.println(\"Error: \" + testClass +\n", - " \" no-arg constructor must be public\");\n", - " System.exit(1);\n", - " }\n", - " } catch(NoSuchMethodException e) {\n", - "// Synthesized no-arg constructor; OK\n", - " }\n", - " System.out.println(testClass.getName());\n", - " }\n", - " for(Method m : testMethods) {\n", - " System.out.print(\" . \" + m.getName() + \" \");\n", - " try {\n", - " Object testObject = createTestObject(creator);\n", - " boolean success = false;\n", - " try {\n", - " if(m.getReturnType().equals(boolean.class))\n", - " success = (Boolean)m.invoke(testObject);\n", - " else {\n", - " m.invoke(testObject);\n", - " success = true; // If no assert fails\n", - " }\n", - " } catch(InvocationTargetException e) {\n", - "// Actual exception is inside e:\n", - " System.out.println(e.getCause());\n", - " }\n", - " System.out.println(success ? \"\" : \"(failed)\");\n", - " testsRun++;\n", - " if(!success) {\n", - " failures++;\n", - " failedTests.add(testClass.getName() +\n", - " \": \" + m.getName());\n", - " }\n", - " if(cleanup != null)\n", - " cleanup.invoke(testObject, testObject);\n", - " } catch(IllegalAccessException |\n", - " IllegalArgumentException |\n", - " InvocationTargetException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " }\n", - " public static\n", - " class TestMethods extends ArrayList {\n", - " void addIfTestMethod(Method m) {\n", - " if(m.getAnnotation(Test.class) == null)\n", - " return;\n", - " if(!(m.getReturnType().equals(boolean.class) ||\n", - " m.getReturnType().equals(void.class)))\n", - " throw new RuntimeException(\"@Test method\" +\n", - " \" must return boolean or void\");\n", - " m.setAccessible(true); // If it's private, etc.\n", - " add(m);\n", - " }\n", - " }\n", - " private static\n", - " Method checkForCreatorMethod(Method m) {\n", - " if(m.getAnnotation(TestObjectCreate.class) == null)\n", - " return null;\n", - " if(!m.getReturnType().equals(testClass))\n", - " throw new RuntimeException(\"@TestObjectCreate \" +\n", - " \"must return instance of Class to be tested\");\n", - " if((m.getModifiers() &\n", - " java.lang.reflect.Modifier.STATIC) < 1)\n", - " throw new RuntimeException(\"@TestObjectCreate \" +\n", - " \"must be static.\");\n", - " m.setAccessible(true);\n", - " return m;\n", - " }\n", - " private static\n", - " Method checkForCleanupMethod(Method m) {\n", - " if(m.getAnnotation(TestObjectCleanup.class) == null)\n", - " return null;\n", - " if(!m.getReturnType().equals(void.class))\n", - " throw new RuntimeException(\"@TestObjectCleanup \" +\n", - " \"must return void\");\n", - " if((m.getModifiers() &\n", - " java.lang.reflect.Modifier.STATIC) < 1)\n", - " throw new RuntimeException(\"@TestObjectCleanup \" +\n", - " \"must be static.\");\n", - " if(m.getParameterTypes().length == 0 ||\n", - " m.getParameterTypes()[0] != testClass)\n", - " throw new RuntimeException(\"@TestObjectCleanup \" +\n", - " \"must take an argument of the tested type.\");\n", - " m.setAccessible(true);\n", - " return m;\n", - " }\n", - " private static Object\n", - " createTestObject(Method creator) {\n", - " if(creator != null) {\n", - " try {\n", - " return creator.invoke(testClass);\n", - " } catch(IllegalAccessException |\n", - " IllegalArgumentException |\n", - " InvocationTargetException e) {\n", - " throw new RuntimeException(\"Couldn't run \" +\n", - " \"@TestObject (creator) method.\");\n", - " }\n", - " } else { // Use the no-arg constructor:\n", - " try {\n", - " return testClass.newInstance();\n", - " } catch(InstantiationException |\n", - " IllegalAccessException e) {\n", - " throw new RuntimeException(\n", - " \"Couldn't create a test object. \" +\n", - " \"Try using a @TestObject method.\");\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然它可能是“过早的重构”(因为它只在书中使用过一次),**AtUnit.java** 使用了 **ProcessFiles** 工具逐步判断命令行中的参数,决定它是一个目录还是文件,并采取相应的行为。这可以应用于不同的解决方法,是因为它包含了一个 可用于自定义的 **Strategy** 接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/ProcessFiles.java\n", - "package onjava;\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "public class ProcessFiles {\n", - " public interface Strategy {\n", - " void process(File file);\n", - " }\n", - " private Strategy strategy;\n", - " private String ext;\n", - " public ProcessFiles(Strategy strategy, String ext) {\n", - " this.strategy = strategy;\n", - " this.ext = ext;\n", - " }\n", - " public void start(String[] args) {\n", - " try {\n", - " if(args.length == 0)\n", - " processDirectoryTree(new File(\".\"));\n", - " else\n", - " for(String arg : args) {\n", - " File fileArg = new File(arg);\n", - " if(fileArg.isDirectory())\n", - " processDirectoryTree(fileArg);\n", - " else {\n", - "// Allow user to leave off extension:\n", - " if(!arg.endsWith(\".\" + ext))\n", - " arg += \".\" + ext;\n", - " strategy.process(\n", - " new File(arg).getCanonicalFile());\n", - " }\n", - " }\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " public void processDirectoryTree(File root) throws IOException {\n", - " PathMatcher matcher = FileSystems.getDefault()\n", - " .getPathMatcher(\"glob:**/*.{\" + ext + \"}\");\n", - " Files.walk(root.toPath())\n", - " .filter(matcher::matches)\n", - " .forEach(p -> strategy.process(p.toFile()));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**AtUnit** 类实现了 **ProcessFiles.Strategy**,其包含了一个 `process()` 方法。在这种方式下,**AtUnit** 实例可以作为参数传递给 **ProcessFiles** 构造器。第二个构造器的参数告诉 **ProcessFiles** 如寻找所有包含 “class” 拓展名的文件。\n", - "\n", - "如下是一个简单的使用示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// annotations/DemoProcessFiles.java\n", - "import onjava.ProcessFiles;\n", - "public class DemoProcessFiles {\n", - " public static void main(String[] args) {\n", - " new ProcessFiles(file -> System.out.println(file),\n", - " \"java\").start(args);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ".\\AtUnitExample1.java\n", - ".\\AtUnitExample2.java\n", - ".\\AtUnitExample3.java\n", - ".\\AtUnitExample4.java\n", - ".\\AtUnitExample5.java\n", - ".\\AUComposition.java\n", - ".\\AUExternalTest.java\n", - ".\\database\\Constraints.java\n", - ".\\database\\DBTable.java\n", - ".\\database\\Member.java\n", - ".\\database\\SQLInteger.java\n", - ".\\database\\SQLString.java\n", - ".\\database\\TableCreator.java\n", - ".\\database\\Uniqueness.java\n", - ".\\DemoProcessFiles.java\n", - ".\\HashSetTest.java\n", - ".\\ifx\\ExtractInterface.java\n", - ".\\ifx\\IfaceExtractorProcessor.java\n", - ".\\ifx\\Multiplier.java\n", - ".\\PasswordUtils.java\n", - ".\\simplest\\Simple.java\n", - ".\\simplest\\SimpleProcessor.java\n", - ".\\simplest\\SimpleTest.java\n", - ".\\SimulatingNull.java\n", - ".\\StackL.java\n", - ".\\StackLStringTst.java\n", - ".\\Testable.java\n", - ".\\UseCase.java\n", - ".\\UseCaseTracker.java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果没有命令行参数,这个程序会遍历当前的目录树。你还可以提供多个参数,这些参数可以是类文件(带或不带.class扩展名)或目录。\n", - "\n", - "回到我们对 **AtUnit.java** 的讨论,因为 **@Unit** 会自动找到可测试的类和方法,所以不需要“套件”机制。\n", - "\n", - "**AtUnit.java** 中存在的一个我们必须要解决的问题是,当它发现类文件时,类文件名中的限定类名(包括包)不明显。为了发现这个信息,必须解析类文件 - 这不是微不足道的,但也不是不可能的。 找到 .class 文件时,会打开它并读取其二进制数据并将其传递给 `ClassNameFinder.thisClass()`。 在这里,我们正在进入“字节码工程”领域,因为我们实际上正在分析类文件的内容:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/atunit/ClassNameFinder.java\n", - "// {java onjava.atunit.ClassNameFinder}\n", - "package onjava.atunit;\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import java.util.*;\n", - "import onjava.*;\n", - "public class ClassNameFinder {\n", - " public static String thisClass(byte[] classBytes) {\n", - " Map offsetTable = new HashMap<>();\n", - " Map classNameTable = new HashMap<>();\n", - " try {\n", - " DataInputStream data = new DataInputStream(\n", - " new ByteArrayInputStream(classBytes));\n", - " int magic = data.readInt(); // 0xcafebabe\n", - " int minorVersion = data.readShort();\n", - " int majorVersion = data.readShort();\n", - " int constantPoolCount = data.readShort();\n", - " int[] constantPool = new int[constantPoolCount];\n", - " for(int i = 1; i < constantPoolCount; i++) {\n", - " int tag = data.read();\n", - " // int tableSize;\n", - " switch(tag) {\n", - " case 1: // UTF\n", - " int length = data.readShort();\n", - " char[] bytes = new char[length];\n", - " for(int k = 0; k < bytes.length; k++)\n", - " bytes[k] = (char)data.read();\n", - " String className = new String(bytes);\n", - " classNameTable.put(i, className);\n", - " break;\n", - " case 5: // LONG\n", - " case 6: // DOUBLE\n", - " data.readLong(); // discard 8 bytes\n", - " i++; // Special skip necessary\n", - " break;\n", - " case 7: // CLASS\n", - " int offset = data.readShort();\n", - " offsetTable.put(i, offset);\n", - " break;\n", - " case 8: // STRING\n", - " data.readShort(); // discard 2 bytes\n", - " break;\n", - " case 3: // INTEGER\n", - " case 4: // FLOAT\n", - " case 9: // FIELD_REF\n", - " case 10: // METHOD_REF\n", - " case 11: // INTERFACE_METHOD_REF\n", - " case 12: // NAME_AND_TYPE\n", - " case 18: // Invoke Dynamic\n", - " data.readInt(); // discard 4 bytes\n", - " break;\n", - " case 15: // Method Handle\n", - " data.readByte();\n", - " data.readShort();\n", - " break;\n", - " case 16: // Method Type\n", - " data.readShort();\n", - " break;\n", - " default:\n", - " throw\n", - " new RuntimeException(\"Bad tag \" + tag);\n", - " }\n", - " }\n", - " short accessFlags = data.readShort();\n", - " String access = (accessFlags & 0x0001) == 0 ?\n", - " \"nonpublic:\" : \"public:\";\n", - " int thisClass = data.readShort();\n", - " int superClass = data.readShort();\n", - " return access + classNameTable.get(\n", - " offsetTable.get(thisClass)).replace('/', '.');\n", - " } catch(IOException | RuntimeException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " // Demonstration:\n", - " public static void main(String[] args) throws Exception {\n", - " PathMatcher matcher = FileSystems.getDefault()\n", - " .getPathMatcher(\"glob:**/*.class\");\n", - "// Walk the entire tree:\n", - " Files.walk(Paths.get(\".\"))\n", - " .filter(matcher::matches)\n", - " .map(p -> {\n", - " try {\n", - " return thisClass(Files.readAllBytes(p));\n", - " } catch(Exception e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " })\n", - " .filter(s -> s.startsWith(\"public:\"))\n", - "// .filter(s -> s.indexOf('$') >= 0)\n", - " .map(s -> s.split(\":\")[1])\n", - " .filter(s -> !s.startsWith(\"enums.\"))\n", - " .filter(s -> s.contains(\".\"))\n", - " .forEach(System.out::println);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "onjava.ArrayShow\n", - "onjava.atunit.AtUnit$TestMethods\n", - "onjava.atunit.AtUnit\n", - "onjava.atunit.ClassNameFinder\n", - "onjava.atunit.Test\n", - "onjava.atunit.TestObjectCleanup\n", - "onjava.atunit.TestObjectCreate\n", - "onjava.atunit.TestProperty\n", - "onjava.BasicSupplier\n", - "onjava.CollectionMethodDifferences\n", - "onjava.ConvertTo\n", - "onjava.Count$Boolean\n", - "onjava.Count$Byte\n", - "onjava.Count$Character\n", - "onjava.Count$Double\n", - "onjava.Count$Float\n", - "onjava.Count$Integer\n", - "onjava.Count$Long\n", - "onjava.Count$Pboolean\n", - "onjava.Count$Pbyte\n", - "onjava.Count$Pchar\n", - "onjava.Count$Pdouble\n", - "onjava.Count$Pfloat\n", - "onjava.Count$Pint\n", - "onjava.Count$Plong\n", - "onjava.Count$Pshort\n", - "onjava.Count$Short\n", - "onjava.Count\n", - "onjava.CountingIntegerList\n", - "onjava.CountMap\n", - "onjava.Countries\n", - "onjava.Enums\n", - "onjava.FillMap\n", - "onjava.HTMLColors\n", - "onjava.MouseClick\n", - "onjava.Nap\n", - "onjava.Null\n", - "onjava.Operations\n", - "onjava.OSExecute\n", - "onjava.OSExecuteException\n", - "onjava.Pair\n", - "onjava.ProcessFiles$Strategy\n", - "onjava.ProcessFiles\n", - "onjava.Rand$Boolean\n", - "onjava.Rand$Byte\n", - "onjava.Rand$Character\n", - "onjava.Rand$Double\n", - "onjava.Rand$Float\n", - "onjava.Rand$Integer\n", - "onjava.Rand$Long\n", - "onjava.Rand$Pboolean\n", - "onjava.Rand$Pbyte\n", - "onjava.Rand$Pchar\n", - "onjava.Rand$Pdouble\n", - "onjava.Rand$Pfloat\n", - "onjava.Rand$Pint\n", - "onjava.Rand$Plong\n", - "onjava.Rand$Pshort\n", - "onjava.Rand$Short\n", - "onjava.Rand$String\n", - "onjava.Rand\n", - "onjava.Range\n", - "onjava.Repeat\n", - "onjava.RmDir\n", - "onjava.Sets\n", - "onjava.Stack\n", - "onjava.Suppliers\n", - "onjava.TimedAbort\n", - "onjava.Timer\n", - "onjava.Tuple\n", - "onjava.Tuple2\n", - "onjava.Tuple3\n", - "onjava.Tuple4\n", - "onjava.Tuple5\n", - "onjava.TypeCounter" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然无法在这里介绍其中所有的细节,但是每个类文件都必须遵循一定的格式,而我已经尽力用有意义的字段来表示这些从 **ByteArrayInputStream** 中提取出来的数据片段。通过施加在输入流上的读操作,你能看出每个信息片的大小。例如每一个类的头 32 个 bit 总是一个 “神秘数字” **0xcafebabe**,而接下来的两个 **short** 值是版本信息。常量池包含了程序的常量,所以这是一个可变的值。接下来的 **short** 告诉我们这个常量池有多大,然后我们为其创建一个尺寸合适的数组。常量池中的每一个元素,其长度可能是固定式,也可能是可变的值,因此我们必须检查每一个常量的起始标记,然后才能知道该怎么做,这就是 switch 语句的工作。我们并不打算精确的分析类中所有的数据,仅仅是从文件的起始一步一步的走,直到取得我们所需的信息,因此你会发现,在这个过程中我们丢弃了大量的数据。关于类的信息都保存在 **classNameTable** 和 **offsetTable** 中。在读取常量池之后,就找到了 **this_class** 信息,这是 **offsetTable** 的一个坐标,通过它可以找到进入 **classNameTable** 的坐标,然后就可以得到我们所需的类的名字了。\n", - "\n", - "现在让我们回到 **AtUtil.java** 中,process() 方法中拥有了类的名字,然后检查它是否包含“.”,如果有就表示该类定义于一个包中。没有包的类会被忽略。如果一个类在包中,那么我们就可以使用标准的类加载器通过 `Class.forName()` 将其加载进来。现在我们可以对这个类进行 **@Unit** 注解的分析工作了。\n", - "\n", - "我们只需要关注三件事:首先是 **@Test** 方法,它们被保存在 **TestMehtods** 列表中,然后检查其是否具有 @TestObjectCreate 和 **@TestObjectCleanup****** 方法。从代码中可以看到,我们通过调用相应的方法来查询注解从而找到这些方法。\n", - "\n", - "每找到一个 @Test 方法,就打印出来当前类的名字,于是观察者立刻就可以知道发生了什么。接下来开始执行测试,也就是打印出方法名,然后调用 createTestObject() (如果存在一个加了 @TestObjectCreate 注解的方法),或者调用默认构造器。一旦创建出来测试对象,如果调用其上的测试方法。如果测试的返回值为 boolean,就捕获该结果。如果测试方法没有返回值,那么就没有异常发生,我们就假设测试成功,反之,如果当 assert 失败或者有任何异常抛出的时候,就说明测试失败,这时将异常信息打印出来以显示错误的原因。如果有失败的测试发生,那么还要统计失败的次数,并将失败所属的类和方法加入到 failedTests 中,以便最后报告给用户。\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "注解是 Java 引入的一项非常受欢迎的补充,它提供了一种结构化,并且具有类型检查能力的新途径,从而使得你能够为代码中加入元数据,而且不会导致代码杂乱并难以阅读。使用注解能够帮助我们避免编写累赘的部署描述性文件,以及其他的生成文件。而 Javadoc 中的 @deprecated 被 @Deprecated 注解所替代的事实也说明,与注释性文字相比,注解绝对更适用于描述类相关的信息。\n", - "\n", - "Java 提供了很少的内置注解。这意味着如果你在别处找不到可用的类库,那么就只能自己创建新的注解以及相应的处理器。通过将注解处理器链接到 javac,你可以一步完成编译新生成的文件,简化了构造过程。\n", - "\n", - "API 的提供方和框架将会将注解作为他们工具的一部分。通过 @Unit 系统,我们可以想象,注解会极大的改变我们的 Java 编程体验。\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "[^3 ]: The Java designers coyly suggest that a mirror is where you find a reflection." - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/24-Concurrent-Programming.ipynb b/jupyter/24-Concurrent-Programming.ipynb deleted file mode 100644 index f86e1f96..00000000 --- a/jupyter/24-Concurrent-Programming.ipynb +++ /dev/null @@ -1,4536 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第二十四章 并发编程\n", - "\n", - ">爱丽丝:“但是我不想进入疯狂的人群中”\n", - ">\n", - ">猫咪:“oh,你无能为力,我们都疯了,我疯了,你也疯了”\n", - ">\n", - ">爱丽丝:“你怎么知道我疯了”。\n", - ">\n", - ">猫咪:“你一定疯了,否则你不会来到这里”——爱丽丝梦游仙境 第6章。\n", - "\n", - "到目前为止,我们一直在编程,就像文学中的意识流叙事设备一样:首先发生一件事,然后是下一件事。我们完全控制所有步骤及其发生的顺序。如果我们将值设置为5,那么稍后会回来并发现它是47,这将是非常令人惊讶的。\n", - "\n", - "我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能没有。很可能它会在某些条件下有效,而不是在其他条件下,你必须知道和了解这些情况以确定哪些有效。\n", - "\n", - "作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并移动它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。\n", - "\n", - "而不是单一的意识流叙事,我们在同时多条故事线进行的间谍小说里。一个间谍在一个特殊的岩石下李璐下微缩胶片,当第二个间谍来取回包裹时,它可能已经被第三个间谍带走了。但是这部特别的小说并没有把事情搞得一团糟;你可以轻松地走到尽头,永远不会弄明白什么。\n", - "\n", - "构建并发应用程序非常类似于游戏[Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。\n", - "\n", - "本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的Java 8工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够解决问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。\n", - "\n", - "对于更多凌乱,低级别的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读Brian Goetz等人的Java Concurrency in Practice。虽然在写作时,这本书已有十多年的历史,但它仍然包含你必须了解和理解的必需品。理想情况下,本章和附录是该书的精心准备。另一个有价值的资源是**Bill Venner**的Inside the Java Virtual Machine,它详细描述了JVM的最内部工作方式,包括线程。\n", - "\n", - "\n", - "\n", - "## 术语问题\n", - "\n", - "在编程文献中并发、并行、多任务、多处理、多线程、分布式系统(以及可能的其他)使用了许多相互冲突的方式,并且经常被混淆。Brian Goetz在2016年的演讲中指出了这一点[From Concurrent to Parallel](https://www.youtube.com/watch?v=NsDE7E8sIdQ),他提出了一个合理的解释:\n", - "\n", - "- 并发是关于正确有效地控制对共享资源的访问。\n", - "- 并行是使用额外的资源来更快地产生结果。\n", - "\n", - "这些都是很好的定义,但有几十年的混乱产生了反对解决问题的历史。一般来说,当人们使用“并发”这个词时,他们的意思是“一切变得混乱”,事实上,我可能会在很多地方自己陷入这种想法,大多数书籍,包括Brian Goetz的Java Concurrency in Practice,都在标题中使用这个词。\n", - "\n", - "并发通常意味着“不止一个任务正在执行中”,而并行性几乎总是意味着“不止一个任务同时执行。”你可以立即看到这些定义的区别:并行也有不止一个任务“正在进行”。区别在于细节,究竟是如何“执行”发生的。此外,重叠:为并行编写的程序有时可以在单个处理器上运行,而一些并发编程系统可以利用多个处理器。\n", - "\n", - "这是另一种方法,在减速[原文:slowdown]发生的地方写下定义:\n", - "\n", - "_并发_\n", - "\n", - "同时完成多个任务。在开始处理其他任务之前,当前任务不需要完成。并发解决了阻塞发生的问题。当任务无法进一步执行,直到外部环境发生变化时才会继续执行。最常见的例子是I/O,其中任务必须等待一些input(在这种情况下会被阻止)。这个问题产生在I/O密集型。\n", - "\n", - "_并行_\n", - "\n", - "同时在多个地方完成多个任务。这解决了所谓的计算密集型问题,如果将程序分成多个部分并在不同的处理器上编辑不同的部分,程序可以运行得更快。\n", - "\n", - "术语混淆的原因在上面的定义中显示:其中核心是“在同一时间完成多个任务。”并行性通过多个处理器增加分布。更重要的是,两者解决了不同类型的问题:解决I/O密集型问题,并行化可能对你没有任何好处,因为问题不是整体速度,而是阻塞。并且考虑到计算力限制问题并试图在单个处理器上使用并发来解决它可能会浪费时间。两种方法都试图在更短的时间内完成更多,但它们实现加速的方式是不同的,并且取决于问题所带来的约束。\n", - "\n", - "这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制**线程**来实现并发和并行。\n", - "\n", - "我们甚至可以尝试添加细致的粒度去定义(但是,这不是标准化的术语):\n", - "\n", - "- **纯并发**:任务仍然在单个CPU上运行。纯并发系统产生的结果比顺序系统更快,但如果有更多的处理器,则运行速度不会更快\n", - "- **并发-并行**:使用并发技术,结果程序利用更多处理器并更快地生成结果\n", - "- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams**就是一个很好的例子)。\n", - "- **纯并行**:除非有多个处理器,否则不会运行。\n", - "\n", - "在某些情况下,这可能是一个有用的分类法。\n", - "\n", - "对并发性的语言和库支持似乎[Leaky Abstraction](https://en.wikipedia.org/wiki/Leaky_abstraction)是完美候选者。抽象的目标是“抽象出”那些对于手头想法不重要的东西,从不必要的细节中汲取灵感。如果抽象是漏洞,那些碎片和细节会不断重新声明自己是重要的,无论你试图隐藏它们多少\n", - "\n", - "我开始怀疑是否真的有高度抽象。当编写这些类型的程序时,你永远不会被底层系统和工具屏蔽,甚至关于CPU缓存如何工作的细节。最后,如果你非常小心,你创作的东西在特定的情况下起作用,但它在其他情况下不起作用。有时,区别在于两台机器的配置方式,或者程序的估计负载。这不是Java特有的-它是并发和并行编程的本质。\n", - "\n", - "你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果它没有正确调整并且输入速率要么没有被正确估计或被限制(并且限制意味着,在不同情况下不同的东西具有不同的影响),该队列将填满并阻塞或溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式\n", - "\n", - "\n", - "### 并发的新定义\n", - "\n", - "几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它:\n", - ">**并发性是一系列性能技术,专注于减少等待**\n", - "\n", - "这实际上是一个相当多的声明,所以我将其分解:\n", - "\n", - "- 这是一个集合:有许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大\n", - "- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。\n", - "- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。\n", - "\n", - "值得强调的是,这个定义的有效性取决于等待这个词。如果没有什么可以等待,那就没有机会了。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。\n", - "\n", - "## 并发的超能力\n", - "\n", - "想象一下,你置身于一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并沿着走廊向下移动。走廊分开了。\n", - "\n", - "你自己完成这项任务需要一百个生命周期。\n", - "\n", - "现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。\n", - "\n", - "每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出50个自己来搜索这间房间。\n", - "\n", - "一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。\n", - "\n", - "我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是泄漏抽象的(leaky abstraction)。\n", - "\n", - "以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策\n", - "\n", - "让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,那么只是因为搜索者恰好是因为处理器闲置了被锁,等待一扇门被接听。相反,我们希望将处理器应用于搜索,在那里它可以做一些真正的工作,因此需要将处理器从一个任务切换到另一个任务的机制。\n", - "\n", - "许多型号能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。\n", - "\n", - "其中一个最大的影响取决于你是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。\n", - "\n", - "这可能会让你决定,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上值得让它运行得更慢以实现。\n", - "\n", - "在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本会降低一切,在这种情况下,如果你有多个进程,并发通常只会有意义。\n", - "\n", - "在接听电话的客户服务部门,你只有一定数量的人,但是你可以拨打很多电话。那些人(处理器)必须一次拨打一个电话,直到完成电话和额外的电话必须排队。\n", - "\n", - "在“鞋匠和精灵”的童话故事中,鞋匠做了很多工作,当他睡着时,一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时会产生限制 - 例如,如果鞋底需要制作鞋子,这会限制制鞋的速度并改变你设计解决方案的方式。\n", - "\n", - "因此,你尝试解决的问题驱动解决方案的设计。打破一个“独立运行”问题的高级[原文:lovely ]抽象,然后就是实际发生的现实。物理现实不断侵入和震撼,这种抽象。\n", - "\n", - "这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备收到蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先在框中获取蛋糕(通常使用锁定机制来解决问题,因此一个工作人员可以先抓住框并防止蛋糕砸)。\n", - "\n", - "当“同时”执行的任务相互干扰时,会出现问题。他可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,编写仅看起来可行的并发程序更为常见,但是在适当的条件下,将会失败。这些情况可能会发生,或者很少发生,你在测试期间从未看到它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。\n", - "这是推动并发的最强有力的论据之一:如果你忽略它,你可能会被咬。\n", - "\n", - "因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。\n", - "\n", - "\n", - "\n", - "## 并发为速度而生\n", - "\n", - "在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定它没有之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置交换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在孤立的地方。\n", - "\n", - "速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学习利用那些超级处理器,这是并发性给你的一个建议。\n", - "\n", - "使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。\n", - "\n", - "但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。\n", - "\n", - "可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。\n", - "\n", - "单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些长时间运行操作,从而最终忽略用户输入和无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中轮询它。这会产生笨拙的代码,无法保证程序员不会忘记执行检查。没有并发性,生成响应式用户界面的唯一方法是让所有任务定期检查用户输入。通过创建单独的执行线程来响应用户输入,该程序保证了一定程度的响应。\n", - "\n", - "实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和I/O等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。\n", - "\n", - "有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,以防止它们在并发频谱中的适用性(最终你习惯了标准的并发性克制,“这种方法适用于一些情况但不适用于其他情况”)\n", - "\n", - "一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。\n", - "\n", - "Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。\n", - "\n", - "并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;否则,你的代码部分将被迫明确标注通常由并发处理的操作。\n", - "\n", - "\n", - "## 四句格言\n", - "\n", - "在经历了多年的Java并发之后,我总结了以下四个格言:\n", - ">1.不要这样做\n", - ">\n", - ">2.没有什么是真的,一切可能都有问题\n", - ">\n", - ">3.它起作用,并不意味着它没有问题\n", - ">\n", - ">4.你仍然必须理解它\n", - "\n", - "这些特别是关于Java设计中的问题,尽管它也可以应用于其他一些语言。但是,确实存在旨在防止这些问题的语言。\n", - "\n", - "### 1.不要这样做\n", - "\n", - "(不要自己动手)\n", - "\n", - "避免纠缠于并发产生的深层问题的最简单方法就是不要这样做。虽然它是诱人的,并且似乎足够安全,可以尝试做简单的事情,但它存在无数、微妙的陷阱。如果你可以避免它,你的生活会更容易。\n", - "\n", - "证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 首先应用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。\n", - "\n", - "如果你被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。\n", - "\n", - "### 2.没有什么是真的,一切可能都有问题\n", - "\n", - "没有并发性的编程,你会发现你的世界有一定的顺序和一致性。通过简单地将变量赋值给某个值,很明显它应该始终正常工作。\n", - "\n", - "在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。我已经很熟悉的东西,认为它显然有效但实际上并没有。\n", - "\n", - "在非并发程序中你可以忽略的各种事情突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须了解对象构造的深度复杂性,以便你的构造对象不会意外地将数据暴露给其他线程进行更改。问题还有很多。\n", - "\n", - "虽然这些主题太复杂,无法为你提供本章的专业知识(再次参见Java Concurrency in Practice),但你必须了解它们。\n", - "\n", - "### 3.它起作用,并不意味着它没有问题\n", - "\n", - "我们很容易编写出一个看似完美实则有问题的并发程序,并且往往问题直在极端情况下才暴露出来 - 在程序部署后不可避免地会出现用户问题。\n", - "\n", - "- 你不能证明并发程序是正确的,你只能(有时)证明它是不正确的。\n", - "- 大多数情况下你甚至不能这样做:如果它有问题,你可能无法检测到它。\n", - "- 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。\n", - "- 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。\n", - "\n", - "在其他Java主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“你知道的越多,你认为你知道得越多。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。\n", - "\n", - "我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。\n", - "\n", - "在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。你可以在这个情况下做的最糟糕的事情是“自信”。\n", - "\n", - "### 4.你必须仍然理解\n", - "\n", - "在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续保留它。\n", - "\n", - "这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢?\n", - "\n", - "唉,你不能轻易逃脱:\n", - "\n", - "- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** clas那样简单的东西。\n", - "- 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思?\n", - "\n", - "人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。\n", - "\n", - "唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。\n", - "\n", - "Java是一种多线程语言,如果你了解它们是否存在并发问题。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。\n", - "\n", - "\n", - "## 残酷的真相\n", - "\n", - "当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出追去其他的的能力。火的使用促成了文明。\n", - "\n", - "我们现在通过计算机和网络技术创造了一个“外化大脑”,开始了第二次基本转变。虽然我们只是触及表面,但已经引发了其他转变,例如设计生物机制的能力,并且已经看到文化演变的显着加速(过去,人们不得不前往混合文化,但现在他们开始混合互联网)。这些转变的影响和好处已经超出了科幻作家预测它们的能力(他们在预测文化和个人变化,甚至技术转变的次要影响方面都特别困难)。\n", - "\n", - "有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件\n", - "\n", - "Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易就像语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气体相位期间,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。\n", - "\n", - "紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。\n", - "\n", - "[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。\n", - "\n", - "热情使原始Java设计师因为看起来有必要而投入功能。信心(以及原始语言的气味)让他们认为任何问题都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 - 这是非常有信心,相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但保留了对之前版本的支持。\n", - "\n", - "线程包含在Java 1.0中。当然,并发性是影响语言远角的基本语言设计决策,很难想象以后添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。\n", - "\n", - "C语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java的大规模扩展迅速暴露了基本问题。在Thread类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。\n", - "\n", - "不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在函数和方法中,所有不变和防止副作用的方法都会导致简化并发编程(这些是纯函数式编程语言的基础)的变化,但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么对这些选择有所了解,要么认为它们太不同了,并且会抛弃许多潜在的语言采用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。\n", - "\n", - "Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。似乎工作的程序充满了微妙的并发bug。\n", - "\n", - "为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。这艘船航行了;Java将不再是为并发而设计的语言,而只是一种允许它的语言。\n", - "\n", - "尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它还有多远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。\n", - "\n", - "这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然容易受到攻击,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。\n", - "\n", - "\n", - "## 本章其余部分\n", - "\n", - "这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得你的生活比旧的替代品更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。\n", - "\n", - "- Parallel Streams(并行流)\n", - "到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在你对该语法(作为一个粉丝,我希望)感到满意,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式\n", - "\n", - "添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。\n", - "\n", - "- 创建和运行任务\n", - "任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executorto运行。\n", - "\n", - "有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。\n", - "\n", - "- 终止长时间运行的任务\n", - "任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。\n", - "- Completable Futures\n", - "当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,最终你的衣服很干净,你可以拿起它。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。\n", - "\n", - "Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,等待任务没有完成。对于一系列操作,Futures并没有真正帮助那么多。\n", - "\n", - "Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,清理菜肴,储存菜肴”等一系列链式操作。\n", - "\n", - "- 死锁\n", - "某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,等待另一个被阻止的任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入僵局。\n", - "\n", - "如果在运行程序时没有立即出现死锁,则会出现最大的问题。你的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如你的开发机器,但是当你将其部署到不同的硬件时会开始死锁。\n", - "\n", - "死锁通常源于细微的编程错误;一系列无辜的决定,最终意外地创建了一个依赖循环。本节包含一个经典示例,演示了死锁的特性。\n", - "\n", - "我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是完成配置。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来加速计划。\n", - "\n", - "- 努力,复杂,成本\n", - "\n", - "## 并行流\n", - "\n", - "Java 8流的一个显着优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。这产生了相当神奇的结果,即能够简单用parallel()然后流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事\n", - "\n", - "例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/ParallelPrime.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import static java.util.stream.LongStream.*;\n", - "import java.io.*;\n", - "import java.nio.file.*;\n", - "import onjava.Timer;\n", - "\n", - "public class ParallelPrime {\n", - " static final int COUNT = 100_000;\n", - " public static boolean isPrime(long n){\n", - " return rangeClosed(2, (long)Math.sqrt(n)).noneMatch(i -> n % i == 0);\n", - " }\n", - " public static void main(String[] args)\n", - " throws IOException {\n", - " Timer timer = new Timer();\n", - " List primes =\n", - " iterate(2, i -> i + 1)\n", - " .parallel() // [1]\n", - " .filter(ParallelPrime::isPrime)\n", - " .limit(COUNT)\n", - " .mapToObj(Long::toString)\n", - " .collect(Collectors.toList());\n", - " System.out.println(timer.duration());\n", - " Files.write(Paths.get(\"primes.txt\"), primes, StandardOpenOption.CREATE);\n", - " }\n", - " }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " Output:\n", - " 1224" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且消除了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。\n", - "\n", - "当我注释掉[1] parallel()行时,我的结果大约是parallel()的三倍。\n", - "\n", - "并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。\n", - "\n", - "- parallel()不是灵丹妙药\n", - "\n", - "作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:求和数字的增量序列。事实证明这是一个令人惊讶的数量,并且我将冒险将它们进行比较 - 试图小心,但承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“升温”),但我认为它仍然提供了一些有用的指示。\n", - "\n", - "我将从一个计时方法rigorously 开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。\n", - "\n", - "请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。\n", - "\n", - "所有关于时间和内存的数字和讨论都是指“我的机器”。你的经历可能会有所不同。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Summing.java\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "import onjava.Timer;\n", - "public class Summing {\n", - " static void timeTest(String id, long checkValue, LongSupplier operation){\n", - " System.out.print(id + \": \");\n", - " Timer timer = newTimer();\n", - " long result = operation.getAsLong();\n", - " if(result == checkValue)\n", - " System.out.println(timer.duration() + \"ms\");\n", - " else\n", - " System.out.format(\"result: %d%ncheckValue: %d%n\", result, checkValue);\n", - " }\n", - " public static final int SZ = 100_000_000;// This even works://\n", - " public static final int SZ = 1_000_000_000;\n", - " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2; // Gauss's formula\n", - " public static void main(String[] args){\n", - " System.out.println(CHECK);\n", - " timeTest(\"Sum Stream\", CHECK, () ->\n", - " LongStream.rangeClosed(0, SZ).sum());\n", - " timeTest(\"Sum Stream Parallel\", CHECK, () ->\n", - " LongStream.rangeClosed(0, SZ).parallel().sum());\n", - " timeTest(\"Sum Iterated\", CHECK, () ->\n", - " LongStream.iterate(0, i -> i + 1)\n", - " .limit(SZ+1).sum());\n", - " // Slower & runs out of memory above 1_000_000:\n", - " // timeTest(\"Sum Iterated Parallel\", CHECK, () ->\n", - " // LongStream.iterate(0, i -> i + 1)\n", - " // .parallel()\n", - " // .limit(SZ+1).sum());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "5000000050000000\n", - "Sum Stream: 167ms\n", - "Sum Stream Parallel: 46ms\n", - "Sum Iterated: 284ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**CHECK**值是使用Carl Friedrich Gauss在1700年代后期仍在小学时创建的公式计算出来的.\n", - "\n", - " **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于十亿分之一的SZ在没有溢出的情况下处理(我使用较小的数字,因此程序运行时间不长)。使用 **parallel()** 的基本范围操跟快。\n", - "\n", - "如果使用**iterate()**来生成序列,则减速是戏剧性的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,那么结果通常比非并行版本花费的时间更长,但是当**SZ**超过一百万时,它也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察:\n", - "\n", - "- 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。\n", - "- 阵列分割成本低廉,均匀且具有完美的分裂知识。\n", - "- 链接列表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余列表”,这相对无用。\n", - "- 无状态生成器的行为类似于数组;使用上述范围是无可争议的。\n", - "- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。\n", - "\n", - "现在让我们尝试通过在数组中填充值来填充数组来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。\n", - "\n", - "首先我们将尝试一个充满原始**long**的数组:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Summing2.java\n", - "// {ExcludeFromTravisCI}import java.util.*;\n", - "public class Summing2 {\n", - " static long basicSum(long[] ia) {\n", - " long sum = 0;\n", - " int size = ia.length;\n", - " for(int i = 0; i < size; i++)\n", - " sum += ia[i];return sum;\n", - " }\n", - " // Approximate largest value of SZ before\n", - " // running out of memory on mymachine:\n", - " public static final int SZ = 20_000_000;\n", - " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2;\n", - " public static void main(String[] args) {\n", - " System.out.println(CHECK);\n", - " long[] la = newlong[SZ+1];\n", - " Arrays.parallelSetAll(la, i -> i);\n", - " Summing.timeTest(\"Array Stream Sum\", CHECK, () ->\n", - " Arrays.stream(la).sum());\n", - " Summing.timeTest(\"Parallel\", CHECK, () ->\n", - " Arrays.stream(la).parallel().sum());\n", - " Summing.timeTest(\"Basic Sum\", CHECK, () ->\n", - " basicSum(la));// Destructive summation:\n", - " Summing.timeTest(\"parallelPrefix\", CHECK, () -> {\n", - " Arrays.parallelPrefix(la, Long::sum);\n", - " return la[la.length - 1];\n", - " });\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "200000010000000\n", - "Array Stream\n", - "Sum: 104ms\n", - "Parallel: 81ms\n", - "Basic Sum: 106ms\n", - "parallelPrefix: 265ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。”\n", - "\n", - "最后,考虑使用盒装**Long**的效果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Summing3.java\n", - "// {ExcludeFromTravisCI}\n", - "import java.util.*;\n", - "public class Summing3 {\n", - " static long basicSum(Long[] ia) {\n", - " long sum = 0;\n", - " int size = ia.length;\n", - " for(int i = 0; i < size; i++)\n", - " sum += ia[i];\n", - " return sum;\n", - " }\n", - " // Approximate largest value of SZ before\n", - " // running out of memory on my machine:\n", - " public static final int SZ = 10_000_000;\n", - " public static final long CHECK = (long)SZ * ((long)SZ + 1)/2;\n", - " public static void main(String[] args) {\n", - " System.out.println(CHECK);\n", - " Long[] aL = newLong[SZ+1];\n", - " Arrays.parallelSetAll(aL, i -> (long)i);\n", - " Summing.timeTest(\"Long Array Stream Reduce\", CHECK, () ->\n", - " Arrays.stream(aL).reduce(0L, Long::sum));\n", - " Summing.timeTest(\"Long Basic Sum\", CHECK, () ->\n", - " basicSum(aL));\n", - " // Destructive summation:\n", - " Summing.timeTest(\"Long parallelPrefix\",CHECK, ()-> {\n", - " Arrays.parallelPrefix(aL, Long::sum);\n", - " return aL[aL.length - 1];\n", - " });\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "50000005000000\n", - "Long Array\n", - "Stream Reduce: 1038ms\n", - "Long Basic\n", - "Sum: 21ms\n", - "Long parallelPrefix: 3616ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了**basicSum()**,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。\n", - "\n", - "我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Summing4.java\n", - "// {ExcludeFromTravisCI}\n", - "import java.util.*;\n", - "public class Summing4 {\n", - " public static void main(String[] args) {\n", - " System.out.println(Summing3.CHECK);\n", - " Long[] aL = newLong[Summing3.SZ+1];\n", - " Arrays.parallelSetAll(aL, i -> (long)i);\n", - " Summing.timeTest(\"Long Parallel\",\n", - " Summing3.CHECK, () ->\n", - " Arrays.stream(aL)\n", - " .parallel()\n", - " .reduce(0L,Long::sum));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "50000005000000\n", - "Long Parallel: 1014ms" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "它比非parallel()版本略快,但并不显着。\n", - "\n", - "这种时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。\n", - "\n", - "使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是超出缓存。\n", - "\n", - "这些示例使用不同的SZ值来显示内存限制。\n", - "\n", - "为了进行时间比较,以下是SZ设置为最小值1000万的结果:\n", - "\n", - "**Sum Stream: 69msSum\n", - "Stream Parallel: 18msSum\n", - "Iterated: 277ms\n", - "Array Stream Sum: 57ms\n", - "Parallel: 14ms\n", - "Basic Sum: 16ms\n", - "parallelPrefix: 28ms\n", - "Long Array Stream Reduce: 1046ms\n", - "Long Basic Sum: 21ms\n", - "Long parallelPrefix: 3287ms\n", - "Long Parallel: 1008ms**\n", - "\n", - "虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!”我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。\n", - "\n", - "- parallel()/limit()交点\n", - "\n", - "使用parallel()时会有更复杂的问题。从其他语言中吸取的流是围绕无限流模型设计的。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。\n", - "\n", - "Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在Collection和Map中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将Collection转换为存在这些操作的Stream:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CollectionIntoStream.java\n", - "import onjava.*;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class CollectionIntoStream {\n", - " public static void main(String[] args) {\n", - " List strings = Stream.generate(new Rand.String(5))\n", - " .limit(10)\n", - " .collect(Collectors.toList());\n", - " strings.forEach(System.out::println);\n", - " // Convert to a Stream for many more options:\n", - " String result = strings.stream()\n", - " .map(String::toUpperCase)\n", - " .map(s -> s.substring(2))\n", - " .reduce(\":\", (s1, s2) -> s1 + s2);\n", - " System.out.println(result);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pccux\n", - "szgvg\n", - "meinn\n", - "eeloz\n", - "tdvew\n", - "cippc\n", - "ygpoa\n", - "lkljl\n", - "bynxt\n", - ":PENCUXGVGINNLOZVEWPPCPOALJLNXT" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作.**ConcurrentHashMap**对**forEachand**和**reduce**操作有特别广泛的支持。\n", - "\n", - "在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意外。这是一个有趣的难题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/ParallelStreamPuzzle.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "public class ParallelStreamPuzzle {\n", - " static class IntGenerator\n", - " implements Supplier {\n", - " private int current = 0;\n", - " public Integer get() {\n", - " return current++;\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " List x = Stream.generate(newIntGenerator())\n", - " .limit(10)\n", - " .parallel() // [1]\n", - " .collect(Collectors.toList());\n", - " System.out.println(x);\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果[1]注释运行它,它会产生预期的:\n", - "**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]**\n", - "每次。但是包含了parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如:\n", - "**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]**\n", - "这样一个简单的程序怎么会这么破碎呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”“那意味着什么?一堆线程都在拉动一个生成器,在某种程度上选择一组有限的结果?代码使它看起来很简单,但它转向是一个特别凌乱的问题。\n", - "\n", - "为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用**ConcurrentLinkedDeque**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/ParallelStreamPuzzle2.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.concurrent.atomic.*;\n", - "import java.nio.file.*;\n", - "public class ParallelStreamPuzzle2 {\n", - " public static final Deque trace =\n", - " new ConcurrentLinkedDeque<>();\n", - " static class\n", - " IntGenerator implements Supplier {\n", - " private AtomicInteger current =\n", - " new AtomicInteger();\n", - " public Integerget() {\n", - " trace.add(current.get() + \": \" +Thread.currentThread().getName());\n", - " return current.getAndIncrement();\n", - " }\n", - " }\n", - " public static void main(String[] args) throws Exception {\n", - " List x = Stream.generate(newIntGenerator())\n", - " .limit(10)\n", - " .parallel()\n", - " .collect(Collectors.toList());\n", - " System.out.println(x);\n", - " Files.write(Paths.get(\"PSP2.txt\"), trace);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。\n", - "\n", - "在查看 **PSP2.txt**.**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。\n", - "\n", - "**0: main\n", - "1: ForkJoinPool.commonPool-worker-1\n", - "2: ForkJoinPool.commonPool-worker-2\n", - "3: ForkJoinPool.commonPool-worker-2\n", - "4: ForkJoinPool.commonPool-worker-1\n", - "5: ForkJoinPool.commonPool-worker-1\n", - "6: ForkJoinPool.commonPool-worker-1\n", - "7: ForkJoinPool.commonPool-worker-1\n", - "8: ForkJoinPool.commonPool-worker-4\n", - "9: ForkJoinPool.commonPool-worker-4\n", - "10: ForkJoinPool.commonPool-worker-4\n", - "11: main\n", - "12: main\n", - "13: main\n", - "14: main\n", - "15: main...10\n", - "17: ForkJoinPool.commonPool-worker-110\n", - "18: ForkJoinPool.commonPool-worker-610\n", - "19: ForkJoinPool.commonPool-worker-610\n", - "20: ForkJoinPool.commonPool-worker-110\n", - "21: ForkJoinPool.commonPool-worker-110\n", - "22: ForkJoinPool.commonPool-worker-110\n", - "23: ForkJoinPool.commonPool-worker-1**\n", - "\n", - "这个块大小似乎是内部实现的一部分(尝试使用**limit()**的不同参数来查看不同的块大小)。将**parallel()**与**limit()**结合使用可以预取一串值,作为流输出。\n", - "\n", - "试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用get()。添加limit(),你说“只需要这些。”基本上,当你将parallel()与limit()结合使用时,你要求随机输出 - 这可能对你正在解决的问题很好。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。\n", - "\n", - "什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/ParallelStreamPuzzle3.java\n", - "// {VisuallyInspectOutput}\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "public class ParallelStreamPuzzle3 {\n", - " public static void main(String[] args) {\n", - " List x = IntStream.range(0, 30)\n", - " .peek(e -> System.out.println(e + \": \" +Thread.currentThread()\n", - " .getName()))\n", - " .limit(10)\n", - " .parallel()\n", - " .boxed()\n", - " .collect(Collectors.toList());\n", - " System.out.println(x);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "8: main\n", - "6: ForkJoinPool.commonPool-worker-5\n", - "3: ForkJoinPool.commonPool-worker-7\n", - "5: ForkJoinPool.commonPool-worker-5\n", - "1: ForkJoinPool.commonPool-worker-3\n", - "2: ForkJoinPool.commonPool-worker-6\n", - "4: ForkJoinPool.commonPool-worker-1\n", - "0: ForkJoinPool.commonPool-worker-4\n", - "7: ForkJoinPool.commonPool-worker-1\n", - "9: ForkJoinPool.commonPool-worker-2\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。\n", - "\n", - "你还可以看到boxed()的添加,它接受int流并将其转换为Integer流。\n", - "\n", - "现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。\n", - "\n", - "它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。当一个简单的数字序列并行生成时,有点难以想象。如果你使用昂贵的产品,它可能有意义 - 但这都是猜测。唯一知道的是通过测试。记住这句格言:“首先制作它,然后快速制作 - 但只有你必须这样做。”**parallel()**和**limit()**仅供专家使用(并且要清楚,我不认为自己是这里的专家)。\n", - "\n", - "- 并行流只看起来很容易\n", - "\n", - "实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,只需将**parallel()**打到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现你的目标。\n", - "\n", - "## 创建和运行任务\n", - "\n", - "如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。\n", - "\n", - "Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。\n", - "\n", - "- Tasks and Executors\n", - "\n", - "在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。\n", - "\n", - "创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。\n", - "\n", - "首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/NapTask.java\n", - "import onjava.Nap;\n", - "public class NapTask implements Runnable {\n", - " finalint id;\n", - " public NapTask(int id) {\n", - " this.id = id;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " new Nap(0.1);// Seconds\n", - " System.out.println(this + \" \"+\n", - " Thread.currentThread().getName());\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return\"NapTask[\" + id + \"]\";\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这只是一个**Runnable**:一个包含**run()**方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Nap.java\n", - "package onjava;\n", - "import java.util.concurrent.*;\n", - "public class Nap {\n", - " public Nap(double t) { // Seconds\n", - " try {\n", - " TimeUnit.MILLISECONDS.sleep((int)(1000 * t));\n", - " } catch(InterruptedException e){\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " public Nap(double t, String msg) {\n", - " this(t);\n", - " System.out.println(msg);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了消除异常处理的视觉噪声,这被定义为实用程序。第二个构造函数在超时时显示一条消息\n", - "\n", - "对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。\n", - "\n", - "你可以看到**sleep()**抛出一个已检查的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。\n", - "\n", - "要执行任务,我们将从最简单的方法--SingleThreadExecutor开始:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "//concurrent/SingleThreadExecutor.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "public class SingleThreadExecutor {\n", - " public static void main(String[] args) {\n", - " ExecutorService exec =\n", - " Executors.newSingleThreadExecutor();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(NapTask::new)\n", - " .forEach(exec::execute);\n", - " System.out.println(\"All tasks submitted\");\n", - " exec.shutdown();\n", - " while(!exec.isTerminated()) {\n", - " System.out.println(\n", - " Thread.currentThread().getName()+\n", - " \" awaiting termination\");\n", - " new Nap(0.1);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "All tasks submitted\n", - "main awaiting termination\n", - "main awaiting termination\n", - "NapTask[0] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[1] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[2] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[3] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[4] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[5] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[6] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[7] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[8] pool-1-thread-1\n", - "main awaiting termination\n", - "NapTask[9] pool-1-thread-1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4]\n", - "\n", - "我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。\n", - "\n", - "请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。\n", - "\n", - "如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,虽然不需要(!exec.isTerminated())。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/SingleThreadExecutor2.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class SingleThreadExecutor2 {\n", - " public static void main(String[] args)throws InterruptedException {\n", - " ExecutorService exec\n", - " =Executors.newSingleThreadExecutor();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(NapTask::new)\n", - " .forEach(exec::execute);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NapTask[0] pool-1-thread-1\n", - "NapTask[1] pool-1-thread-1\n", - "NapTask[2] pool-1-thread-1\n", - "NapTask[3] pool-1-thread-1\n", - "NapTask[4] pool-1-thread-1\n", - "NapTask[5] pool-1-thread-1\n", - "NapTask[6] pool-1-thread-1\n", - "NapTask[7] pool-1-thread-1\n", - "NapTask[8] pool-1-thread-1\n", - "NapTask[9] pool-1-thread-1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/MoreTasksAfterShutdown.java\n", - "import java.util.concurrent.*;\n", - "public class MoreTasksAfterShutdown {\n", - " public static void main(String[] args) {\n", - " ExecutorService exec\n", - " =Executors.newSingleThreadExecutor();\n", - " exec.execute(newNapTask(1));\n", - " exec.shutdown();\n", - " try {\n", - " exec.execute(newNapTask(99));\n", - " } catch(RejectedExecutionException e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。\n", - "\n", - "- 使用更多线程\n", - "\n", - "使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,你将看到更多选项。例如CachedThreadPool:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CachedThreadPool.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class CachedThreadPool {\n", - " public static void main(String[] args) {\n", - " ExecutorService exec\n", - " =Executors.newCachedThreadPool();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(NapTask::new)\n", - " .forEach(exec::execute);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "NapTask[7] pool-1-thread-8\n", - "NapTask[4] pool-1-thread-5\n", - "NapTask[1] pool-1-thread-2\n", - "NapTask[3] pool-1-thread-4\n", - "NapTask[0] pool-1-thread-1\n", - "NapTask[8] pool-1-thread-9\n", - "NapTask[2] pool-1-thread-3\n", - "NapTask[9] pool-1-thread-10\n", - "NapTask[6] pool-1-thread-7\n", - "NapTask[5] pool-1-thread-6" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当你运行这个程序时,你会发现它完成得更快。这是有道理的,而不是使用相同的线程来顺序运行每个任务,每个任务都有自己的线程,所以它们都并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。\n", - "\n", - "要理解这个问题,我们需要一个更复杂的任务:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/InterferingTask.java\n", - "public class InterferingTask implements Runnable {\n", - " final int id;\n", - " private static Integer val = 0;\n", - " public InterferingTask(int id) {\n", - " this.id = id;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " for(int i = 0; i < 100; i++)\n", - " val++;\n", - " System.out.println(id + \" \"+\n", - " Thread.currentThread().getName() + \" \" + val);\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个任务增加val一百次。这似乎很简单。让我们用CachedThreadPool尝试一下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CachedThreadPool2.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class CachedThreadPool2 {\n", - " public static void main(String[] args) {\n", - " ExecutorService exec\n", - " =Executors.newCachedThreadPool();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(InterferingTask::new)\n", - " .forEach(exec::execute);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0 pool-1-thread-1 200\n", - "1 pool-1-thread-2 200\n", - "4 pool-1-thread-5 300\n", - "5 pool-1-thread-6 400\n", - "8 pool-1-thread-9 500\n", - "9 pool-1-thread-10 600\n", - "2 pool-1-thread-3 700\n", - "7 pool-1-thread-8 800\n", - "3 pool-1-thread-4 900\n", - "6 pool-1-thread-7 1000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们说这样的类不是线程安全的。让我们看看SingleThreadExecutor会发生什么:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/SingleThreadExecutor3.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class SingleThreadExecutor3 {\n", - " public static void main(String[] args)throws InterruptedException {\n", - " ExecutorService exec\n", - " =Executors.newSingleThreadExecutor();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(InterferingTask::new)\n", - " .forEach(exec::execute);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "0 pool-1-thread-1 100\n", - "1 pool-1-thread-1 200\n", - "2 pool-1-thread-1 300\n", - "3 pool-1-thread-1 400\n", - "4 pool-1-thread-1 500\n", - "5 pool-1-thread-1 600\n", - "6 pool-1-thread-1 700\n", - "7 pool-1-thread-1 800\n", - "8 pool-1-thread-1 900\n", - "9 pool-1-thread-1 1000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。线程限制限制了加速,但可以节省很多困难的调试和重写。\n", - "\n", - "- 产生结果\n", - "\n", - "因为**InterferingTask**是一个**Runnable**,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了**CachedThreadPool2.java**。**InterferingTask**中的**val**被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。\n", - "\n", - "避免竞争条件的最好方法是避免可变的共享状态。我们可以称之为自私的孩子原则:什么都不分享。\n", - "\n", - "使用**InterferingTask**,最好删除副作用并返回任务结果。为此,我们创建**Callable**而不是**Runnable**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CountingTask.java\n", - "import java.util.concurrent.*;\n", - "public class CountingTask implements Callable {\n", - " final int id;\n", - " public CountingTask(int id) { this.id = id; }\n", - " @Override\n", - " public Integer call() {\n", - " Integer val = 0;\n", - " for(int i = 0; i < 100; i++)\n", - " val++;\n", - " System.out.println(id + \" \" +\n", - " Thread.currentThread().getName() + \" \" + val);\n", - " return val;\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态\n", - "\n", - "**ExecutorService**允许你使用**invokeAll()**启动集合中的每个Callable:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CachedThreadPool3.java\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class CachedThreadPool3 {\n", - " public static Integer extractResult(Future f) {\n", - " try {\n", - " return f.get();\n", - " } catch(Exception e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " public static void main(String[] args)throws InterruptedException {\n", - " ExecutorService exec =\n", - " Executors.newCachedThreadPool();\n", - " List tasks =\n", - " IntStream.range(0, 10)\n", - " .mapToObj(CountingTask::new)\n", - " .collect(Collectors.toList());\n", - " List> futures =\n", - " exec.invokeAll(tasks);\n", - " Integer sum = futures.stream()\n", - " .map(CachedThreadPool3::extractResult)\n", - " .reduce(0, Integer::sum);\n", - " System.out.println(\"sum = \" + sum);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1 pool-1-thread-2 100\n", - "0 pool-1-thread-1 100\n", - "4 pool-1-thread-5 100\n", - "5 pool-1-thread-6 100\n", - "8 pool-1-thread-9 100\n", - "9 pool-1-thread-10 100\n", - "2 pool-1-thread-3 100\n", - "3 pool-1-thread-4 100\n", - "6 pool-1-thread-7 100\n", - "7 pool-1-thread-8 100\n", - "sum = 1000\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Futures.java\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class Futures {\n", - " public static void main(String[] args)throws InterruptedException, ExecutionException {\n", - " ExecutorService exec\n", - " =Executors.newSingleThreadExecutor();\n", - " Future f =\n", - " exec.submit(newCountingTask(99));\n", - " System.out.println(f.get()); // [1]\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "99 pool-1-thread-1 100\n", - "100" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- [1] 当你的任务尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。\n", - "\n", - "但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。\n", - "\n", - "还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。\n", - "\n", - "因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,你仍会在遗留库中遇到Futures\n", - "\n", - "我们可以使用并行Stream以更简单,更优雅的方式解决这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CountingStream.java\n", - "// {VisuallyInspectOutput}\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class CountingStream {\n", - " public static void main(String[] args) {\n", - " System.out.println(\n", - " IntStream.range(0, 10)\n", - " .parallel()\n", - " .mapToObj(CountingTask::new)\n", - " .map(ct -> ct.call())\n", - " .reduce(0, Integer::sum));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1 ForkJoinPool.commonPool-worker-3 100\n", - "8 ForkJoinPool.commonPool-worker-2 100\n", - "0 ForkJoinPool.commonPool-worker-6 100\n", - "2 ForkJoinPool.commonPool-worker-1 100\n", - "4 ForkJoinPool.commonPool-worker-5 100\n", - "9 ForkJoinPool.commonPool-worker-7 100\n", - "6 main 100\n", - "7 ForkJoinPool.commonPool-worker-4 100\n", - "5 ForkJoinPool.commonPool-worker-2 100\n", - "3 ForkJoinPool.commonPool-worker-3 100\n", - "1000" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这不仅更容易理解,我们需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。\n", - "\n", - "- Lambda和方法引用作为任务\n", - "\n", - "在 `java8` , 你不需要受限于在 `Runnables ` 和 `Callables` 时,使用`lambdas` 和方法引用, 同样也可以通过匹配签名来引用(即,它支持结构一致性)。 所以我们可以将 `notRunnables` 或 `Callables` 的参数传递给`ExecutorService` :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/LambdasAndMethodReferences.java\n", - "import java.util.concurrent.*;\n", - "class NotRunnable {\n", - " public void go() {\n", - " System.out.println(\"NotRunnable\");\n", - " }\n", - "}\n", - "class NotCallable {\n", - " public Integer get() {\n", - " System.out.println(\"NotCallable\");\n", - " return 1;\n", - " }\n", - "}\n", - "public class LambdasAndMethodReferences {\n", - " public static void main(String[] args)throws InterruptedException {\n", - " ExecutorService exec =\n", - " Executors.newCachedThreadPool();\n", - " exec.submit(() -> System.out.println(\"Lambda1\"));\n", - " exec.submit(new NotRunnable()::go);\n", - " exec.submit(() -> {\n", - " System.out.println(\"Lambda2\");\n", - " return 1;\n", - " });\n", - " exec.submit(new NotCallable()::get);\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Lambda1\n", - "NotCallable\n", - "NotRunnable\n", - "Lambda2\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,你可以在后两次调用的情况下提取结果。\n", - "\n", - "\n", - "## 终止耗时任务\n", - "\n", - "并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当你关闭程序时。\n", - "\n", - "最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。\n", - "\n", - "InterruptedException,因为设计的向后兼容性残留。\n", - "\n", - "任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的shutdown进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。\n", - "\n", - "以这种方式终止任务听起来很简单:设置任务可以看到的**boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。\n", - "\n", - "在研究Java文献时,你会发现很多解决这个问题的方法,经常使用**volatile**关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发](./Appendix-Low-Level-Concurrency.md)中有所涉及。\n", - "\n", - "Java 5引入了**Atomic**类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加**AtomicBoolean**标志,告诉任务清理自己并退出。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/QuittableTask.java\n", - "import java.util.concurrent.atomic.AtomicBoolean;import onjava.Nap;\n", - "public class QuittableTask implements Runnable {\n", - " final int id;\n", - " public QuittableTask(int id) {\n", - " this.id = id;\n", - " }\n", - " private AtomicBoolean running =\n", - " new AtomicBoolean(true);\n", - " public void quit() {\n", - " running.set(false);\n", - " }\n", - " @Override\n", - " public void run() {\n", - " while(running.get()) // [1]\n", - " new Nap(0.1);\n", - " System.out.print(id + \" \"); // [2]\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。\n", - "\n", - "- [1]:只要运行标志为true,此任务的run()方法将继续。\n", - "- [2]: 显示仅在任务退出时发生。\n", - "\n", - "需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。\n", - "\n", - "作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/QuittingTasks.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "public class QuittingTasks {\n", - " public static final int COUNT = 150;\n", - " public static void main(String[] args) {\n", - " ExecutorService es =\n", - " Executors.newCachedThreadPool();\n", - " List tasks =\n", - " IntStream.range(1, COUNT)\n", - " .mapToObj(QuittableTask::new)\n", - " .peek(qt -> es.execute(qt))\n", - " .collect(Collectors.toList());\n", - " new Nap(1);\n", - " tasks.forEach(QuittableTask::quit); es.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "24 27 31 8 11 7 19 12 16 4 23 3 28 32 15 20 63 60 68 6764 39 47 52 51 55 40 43 48 59 44 56 36 35 71 72 83 10396 92 88 99 100 87 91 79 75 84 76 115 108 112 104 107111 95 80 147 120 127 119 123 144 143 116 132 124 128\n", - "136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。\n", - "\n", - "## CompletableFuture类\n", - "\n", - "作为介绍,这里是使用CompletableFutures在QuittingTasks.java中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/QuittingCompletable.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "public class QuittingCompletable {\n", - " public static void main(String[] args) {\n", - " List tasks =\n", - " IntStream.range(1, QuittingTasks.COUNT)\n", - " .mapToObj(QuittableTask::new)\n", - " .collect(Collectors.toList());\n", - " List> cfutures =\n", - " tasks.stream()\n", - " .map(CompletableFuture::runAsync)\n", - " .collect(Collectors.toList());\n", - " new Nap(1);\n", - " tasks.forEach(QuittableTask::quit);\n", - " cfutures.forEach(CompletableFuture::join);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2526 27 28 29 30 31 32 33 34 6 35 4 38 39 40 41 42 43 4445 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 6263 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 8081 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 9899 100 101 102 103 104 105 106 107 108 109 110 111 1121 113 114 116 117 118 119 120 121 122 123 124 125 126127 128 129 130 131 132 133 134 135 136 137 138 139 140141 142 143 144 145 146 147 148 149 5 115 37 36 2 3" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "任务是一个 `List`,就像在 `QuittingTasks.java` 中一样,但是在这个例子中,没有 `peek()` 将每个 `QuittableTask` 提交给 `ExecutorService`。相反,在创建 `cfutures` 期间,每个任务都交给 `CompletableFuture::runAsync`。这执行 `VerifyTask.run()` 并返回 `CompletableFuture` 。因为 `run()` 不返回任何内容,所以在这种情况下我只使用 `CompletableFuture` 调用 `join()` 来等待它完成。\n", - "\n", - "在本例中需要注意的重要一点是,运行任务不需要使用 `ExecutorService`。而是直接交给 `CompletableFuture` 管理 (不过你可以向它提供自己定义的 `ExectorService`)。您也不需要调用 `shutdown()`;事实上,除非你像我在这里所做的那样显式地调用 `join()`,否则程序将尽快退出,而不必等待任务完成。\n", - "\n", - "这个例子只是一个起点。你很快就会看到 `ComplempleFuture` 能够做得更多。\n", - "\n", - "### 基本用法\n", - "\n", - "这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Machina.java\n", - "import onjava.Nap;\n", - "public class Machina {\n", - " public enum State {\n", - " START, ONE, TWO, THREE, END;\n", - " State step() {\n", - " if(equals(END))\n", - " return END;\n", - " return values()[ordinal() + 1];\n", - " }\n", - " }\n", - " private State state = State.START;\n", - " private final int id;\n", - " public Machina(int id) {\n", - " this.id = id;\n", - " }\n", - " public static Machina work(Machina m) {\n", - " if(!m.state.equals(State.END)){\n", - " new Nap(0.1);\n", - " m.state = m.state.step();\n", - " }\n", - " System.out.println(m);\n", - " return m;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return\"Machina\" + id + \": \" + (state.equals(State.END)? \"complete\" : state);\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。\n", - "\n", - "**CompletableFuture**可以被用来做的一件事是, 使用**completedFuture()**将它感兴趣的对象进行包装。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletedMachina.java\n", - "import java.util.concurrent.*;\n", - "public class CompletedMachina {\n", - " public static void main(String[] args) {\n", - " CompletableFuture cf =\n", - " CompletableFuture.completedFuture(\n", - " new Machina(0));\n", - " try {\n", - " Machina m = cf.get(); // Doesn't block\n", - " } catch(InterruptedException |\n", - " ExecutionException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。\n", - "\n", - "通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。\n", - "\n", - "当我们将**handle()**包装在**CompletableFuture**中时,发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,使得事情变得更加有趣:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletableApply.java\n", - "import java.util.concurrent.*;\n", - "public class CompletableApply {\n", - " public static void main(String[] args) {\n", - " CompletableFuture cf =\n", - " CompletableFuture.completedFuture(\n", - " new Machina(0));\n", - " CompletableFuture cf2 =\n", - " cf.thenApply(Machina::work);\n", - " CompletableFuture cf3 =\n", - " cf2.thenApply(Machina::work);\n", - " CompletableFuture cf4 =\n", - " cf3.thenApply(Machina::work);\n", - " CompletableFuture cf5 =\n", - " cf4.thenApply(Machina::work);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**输出结果**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Machina0: ONE\n", - "Machina0: TWO\n", - "Machina0: THREE\n", - "Machina0: complete" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是(类似于流中的 `map()` )函数也可以返回不同的类型,这将体现在返回类型上。\n", - "\n", - "你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。\n", - "\n", - "我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletableApplyChained.javaimport java.util.concurrent.*;\n", - "import onjava.Timer;\n", - "public class CompletableApplyChained {\n", - " public static void main(String[] args) {\n", - " Timer timer = new Timer();\n", - " CompletableFuture cf =\n", - " CompletableFuture.completedFuture(\n", - " new Machina(0))\n", - " .thenApply(Machina::work)\n", - " .thenApply(Machina::work)\n", - " .thenApply(Machina::work)\n", - " .thenApply(Machina::work);\n", - " System.out.println(timer.duration());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Machina0: ONE\n", - "Machina0: TWO\n", - "Machina0: THREE\n", - "Machina0: complete\n", - "514" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里我们还添加了一个 `Timer`,它的功能在每一步都显性地增加 100ms 等待时间之外,还将 `CompletableFuture` 内部 `thenApply` 带来的额外开销给体现出来了。 \n", - "**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。\n", - "\n", - "回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletableApplyAsync.java\n", - "import java.util.concurrent.*;\n", - "import onjava.*;\n", - "public class CompletableApplyAsync {\n", - " public static void main(String[] args) {\n", - " Timer timer = new Timer();\n", - " CompletableFuture cf =\n", - " CompletableFuture.completedFuture(\n", - " new Machina(0))\n", - " .thenApplyAsync(Machina::work)\n", - " .thenApplyAsync(Machina::work)\n", - " .thenApplyAsync(Machina::work)\n", - " .thenApplyAsync(Machina::work);\n", - " System.out.println(timer.duration());\n", - " System.out.println(cf.join());\n", - " System.out.println(timer.duration());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "116\n", - "Machina0: ONE\n", - "Machina0: TWO\n", - "Machina0:THREE\n", - "Machina0: complete\n", - "Machina0: complete\n", - "552" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "同步调用(我们通常使用的那种)意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。\n", - "\n", - "事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到cf操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。\n", - "\n", - "这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。\n", - "\n", - "幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道系统的一些具体的变化,否则你更想使用异步调用来实现程序。\n", - "\n", - "- 其他操作\n", - "\n", - "当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务(在本书中,我们忽略了 `Executor` 选项)。\n", - "\n", - "下面的示例展示了所有\"基本\"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常(我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "package onjava;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class CompletableUtilities {\n", - " // Get and show value stored in a CF:\n", - " public static void showr(CompletableFuture c) {\n", - " try {\n", - " System.out.println(c.get());\n", - " } catch(InterruptedException\n", - " | ExecutionException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " // For CF operations that have no value:\n", - " public static void voidr(CompletableFuture c) {\n", - " try {\n", - " c.get(); // Returns void\n", - " } catch(InterruptedException\n", - " | ExecutionException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`showr()` 在 `CompletableFuture` 上调用 `get()`,并显示结果,`try/catch` 两个可能会出现的异常。\n", - "\n", - "`voidr()` 是 `CompletableFuture` 的 `showr()` 版本,也就是说,`CompletableFutures` 只为任务完成或失败时显示信息。\n", - "\n", - "为简单起见,下面的 `CompletableFutures` 只包装整数。`cfi()` 是一个便利的方法,它把一个整数包装在一个完整的 `CompletableFuture` :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletableOperations.java\n", - "import java.util.concurrent.*;\n", - "import static onjava.CompletableUtilities.*;\n", - "\n", - "public class CompletableOperations {\n", - " static CompletableFuture cfi(int i) {\n", - " return\n", - " CompletableFuture.completedFuture(\n", - " Integer.valueOf(i));\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " showr(cfi(1)); // Basic test\n", - " voidr(cfi(2).runAsync(() ->\n", - " System.out.println(\"runAsync\")));\n", - " voidr(cfi(3).thenRunAsync(() ->\n", - " System.out.println(\"thenRunAsync\")));\n", - " voidr(CompletableFuture.runAsync(() ->\n", - " System.out.println(\"runAsync is static\")));\n", - " showr(CompletableFuture.supplyAsync(() -> 99));\n", - " voidr(cfi(4).thenAcceptAsync(i ->\n", - " System.out.println(\"thenAcceptAsync: \" + i)));\n", - " showr(cfi(5).thenApplyAsync(i -> i + 42));\n", - " showr(cfi(6).thenComposeAsync(i -> cfi(i + 99)));\n", - " CompletableFuture c = cfi(7);\n", - " c.obtrudeValue(111);\n", - " showr(c);\n", - " showr(cfi(8).toCompletableFuture());\n", - " c = new CompletableFuture<>();\n", - " c.complete(9);\n", - " showr(c);\n", - " c = new CompletableFuture<>();\n", - " c.cancel(true);\n", - " System.out.println(\"cancelled: \" +\n", - " c.isCancelled());\n", - " System.out.println(\"completed exceptionally: \" +\n", - " c.isCompletedExceptionally());\n", - " System.out.println(\"done: \" + c.isDone());\n", - " System.out.println(c);\n", - " c = new CompletableFuture<>();\n", - " System.out.println(c.getNow(777));\n", - " c = new CompletableFuture<>();\n", - " c.thenApplyAsync(i -> i + 42)\n", - " .thenApplyAsync(i -> i * 12);\n", - " System.out.println(\"dependents: \" +\n", - " c.getNumberOfDependents());\n", - " c.thenApplyAsync(i -> i / 2);\n", - " System.out.println(\"dependents: \" +\n", - " c.getNumberOfDependents());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**输出结果** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "1\n", - "runAsync\n", - "thenRunAsync\n", - "runAsync is static\n", - "99\n", - "thenAcceptAsync: 4\n", - "47\n", - "105\n", - "111\n", - "8\n", - "9\n", - "cancelled: true\n", - "completed exceptionally: true\n", - "done: true\n", - "java.util.concurrent.CompletableFuture@6d311334[Complet ed exceptionally]\n", - "777\n", - "dependents: 1\n", - "dependents: 2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- `main()` 包含一系列可由其 `int` 值引用的测试。\n", - " - `cfi(1)` 演示了 `showr()` 正常工作。\n", - " - `cfi(2)` 是调用 `runAsync()` 的示例。由于 `Runnable` 不产生返回值,因此使用了返回 `CompletableFuture ` 的`voidr()` 方法。\n", - " - 注意使用 `cfi(3)`,`thenRunAsync()` 效果似乎与 上例 `cfi(2)` 使用的 `runAsync()`相同,差异在后续的测试中体现:\n", - " - `runAsync()` 是一个 `static` 方法,所以你通常不会像`cfi(2)`一样调用它。相反你可以在 `QuittingCompletable.java` 中使用它。\n", - " - 后续测试中表明 `supplyAsync()` 也是静态方法,区别在于它需要一个 `Supplier` 而不是`Runnable`, 并产生一个`CompletableFuture` 而不是 `CompletableFuture`。\n", - " - `then` 系列方法将对现有的 `CompletableFuture` 进一步操作。\n", - " - 与 `thenRunAsync()` 不同,`cfi(4)`,`cfi(5)` 和`cfi(6)` \"then\" 方法的参数是未包装的 `Integer`。\n", - " - 通过使用 `voidr()`方法可以看到: \n", - " - `AcceptAsync()`接收了一个 `Consumer`,因此不会产生结果。\n", - " - `thenApplyAsync()` 接收一个`Function`, 并生成一个结果(该结果的类型可以不同于其输入类型)。\n", - " - `thenComposeAsync()` 与 `thenApplyAsync()`非常相似,唯一区别在于其 `Function` 必须产生已经包装在`CompletableFuture`中的结果。\n", - " - `cfi(7)` 示例演示了 `obtrudeValue()`,它强制将值作为结果。\n", - " - `cfi(8)` 使用 `toCompletableFuture()` 从 `CompletionStage` 生成一个`CompletableFuture`。\n", - " - `c.complete(9)` 显示了如何通过给它一个结果来完成一个`task`(`future`)(与 `obtrudeValue()` 相对,后者可能会迫使其结果替换该结果)。\n", - " - 如果你调用 `CompletableFuture`中的 `cancel()`方法,如果已经完成此任务,则正常结束。 如果尚未完成,则使用 `CancellationException` 完成此 `CompletableFuture`。\n", - " - 如果任务(`future`)完成,则**getNow()**方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。\n", - " - 最后,我们看一下依赖(`dependents`)的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。\n", - " - 这表明你可以使用一个`CompletionStage`,当它完成时,可以根据其结果派生多个新任务。\n", - "\n", - "\n", - "\n", - "### 结合 CompletableFuture\n", - "\n", - "第二种类型的 `CompletableFuture` 方法采用两种 `CompletableFuture` 并以各异方式将它们组合在一起。就像两个人在比赛一样, 一个`CompletableFuture`通常比另一个更早地到达终点。这些方法允许你以不同的方式处理结果。\n", - "为了测试这一点,我们将创建一个任务,它有一个我们可以控制的定义了完成任务所需要的时间量的参数。 \n", - "CompletableFuture 先完成:\u0016" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Workable.java\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "public class Workable {\n", - " String id;\n", - " final double duration;\n", - "\n", - " public Workable(String id, double duration) {\n", - " this.id = id;\n", - " this.duration = duration;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"Workable[\" + id + \"]\";\n", - " }\n", - "\n", - " public static Workable work(Workable tt) {\n", - " new Nap(tt.duration); // Seconds\n", - " tt.id = tt.id + \"W\";\n", - " System.out.println(tt);\n", - " return tt;\n", - " }\n", - "\n", - " public static CompletableFuture make(String id, double duration) {\n", - " return CompletableFuture\n", - " .completedFuture(\n", - " new Workable(id, duration)\n", - " )\n", - " .thenApplyAsync(Workable::work);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母W附加到id上,表示工作已经完成。\n", - "\n", - "现在我们可以创建多个竞争的 `CompletableFuture`,并使用 `CompletableFuture` 库中的各种方法来进行操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/DualCompletableOperations.java\n", - "import java.util.concurrent.*;\n", - "import static onjava.CompletableUtilities.*;\n", - "\n", - "public class DualCompletableOperations {\n", - " static CompletableFuture cfA, cfB;\n", - "\n", - " static void init() {\n", - " cfA = Workable.make(\"A\", 0.15);\n", - " cfB = Workable.make(\"B\", 0.10); // Always wins\n", - " }\n", - "\n", - " static void join() {\n", - " cfA.join();\n", - " cfB.join();\n", - " System.out.println(\"*****************\");\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " init();\n", - " voidr(cfA.runAfterEitherAsync(cfB, () ->\n", - " System.out.println(\"runAfterEither\")));\n", - " join();\n", - "\n", - " init();\n", - " voidr(cfA.runAfterBothAsync(cfB, () ->\n", - " System.out.println(\"runAfterBoth\")));\n", - " join();\n", - "\n", - " init();\n", - " showr(cfA.applyToEitherAsync(cfB, w -> {\n", - " System.out.println(\"applyToEither: \" + w);\n", - " return w;\n", - " }));\n", - " join();\n", - "\n", - " init();\n", - " voidr(cfA.acceptEitherAsync(cfB, w -> {\n", - " System.out.println(\"acceptEither: \" + w);\n", - " }));\n", - " join();\n", - "\n", - " init();\n", - " voidr(cfA.thenAcceptBothAsync(cfB, (w1, w2) -> {\n", - " System.out.println(\"thenAcceptBoth: \"\n", - " + w1 + \", \" + w2);\n", - " }));\n", - " join();\n", - "\n", - " init();\n", - " showr(cfA.thenCombineAsync(cfB, (w1, w2) -> {\n", - " System.out.println(\"thenCombine: \"\n", - " + w1 + \", \" + w2);\n", - " return w1;\n", - " }));\n", - " join();\n", - "\n", - " init();\n", - " CompletableFuture\n", - " cfC = Workable.make(\"C\", 0.08),\n", - " cfD = Workable.make(\"D\", 0.09);\n", - " CompletableFuture.anyOf(cfA, cfB, cfC, cfD)\n", - " .thenRunAsync(() ->\n", - " System.out.println(\"anyOf\"));\n", - " join();\n", - "\n", - " init();\n", - " cfC = Workable.make(\"C\", 0.08);\n", - " cfD = Workable.make(\"D\", 0.09);\n", - " CompletableFuture.allOf(cfA, cfB, cfC, cfD)\n", - " .thenRunAsync(() ->\n", - " System.out.println(\"allOf\"));\n", - " join();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**输出结果**:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Workable[BW]\n", - "runAfterEither\n", - "Workable[AW]\n", - "*****************\n", - "Workable[BW]\n", - "Workable[AW]\n", - "runAfterBoth\n", - "*****************\n", - "Workable[BW]\n", - "applyToEither: Workable[BW]\n", - "Workable[BW]\n", - "Workable[AW]\n", - "*****************\n", - "Workable[BW]\n", - "acceptEither: Workable[BW]\n", - "Workable[AW]\n", - "*****************\n", - "Workable[BW]\n", - "Workable[AW]\n", - "thenAcceptBoth: Workable[AW], Workable[BW]\n", - "****************\n", - " Workable[BW]\n", - " Workable[AW]\n", - " thenCombine: Workable[AW], Workable[BW]\n", - " Workable[AW]\n", - " *****************\n", - " Workable[CW]\n", - " anyOf\n", - " Workable[DW]\n", - " Workable[BW]\n", - " Workable[AW]\n", - " *****************\n", - " Workable[CW]\n", - " Workable[DW]\n", - " Workable[BW]\n", - " Workable[AW]\n", - " *****************\n", - " allOf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- 为了方便访问, 将 `cfA` 和 `cfB` 定义为 `static`的。 \n", - " - `init()`方法用于 `A`, `B` 初始化这两个变量,因为 `B` 总是给出比`A`较短的延迟,所以总是 `win` 的一方。\n", - " - `join()` 是在两个方法上调用 `join()` 并显示边框的另一个便利方法。\n", - "- 所有这些 “`dual`” 方法都以一个 `CompletableFuture` 作为调用该方法的对象,第二个 `CompletableFuture` 作为第一个参数,然后是要执行的操作。\n", - "- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载)的 `CompletableFuture`。\n", - "- 方法的名称不言自明,你可以通过查看输出来验证这一点。一个特别有趣的方法是 `combineAsync()`,它等待两个 `CompletableFuture` 完成,然后将它们都交给一个 `BiFunction`,这个 `BiFunction` 可以将结果加入到最终的 `CompletableFuture` 的有效负载中。\n", - "\n", - "\n", - "### 模拟\n", - "\n", - "作为使用 `CompletableFuture` 将一系列操作组合的示例,让我们模拟一下制作蛋糕的过程。在第一阶段,我们准备并将原料混合成面糊:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Batter.java\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "public class Batter {\n", - " static class Eggs {\n", - " }\n", - "\n", - " static class Milk {\n", - " }\n", - "\n", - " static class Sugar {\n", - " }\n", - "\n", - " static class Flour {\n", - " }\n", - "\n", - " static T prepare(T ingredient) {\n", - " new Nap(0.1);\n", - " return ingredient;\n", - " }\n", - "\n", - " static CompletableFuture prep(T ingredient) {\n", - " return CompletableFuture\n", - " .completedFuture(ingredient)\n", - " .thenApplyAsync(Batter::prepare);\n", - " }\n", - "\n", - " public static CompletableFuture mix() {\n", - " CompletableFuture eggs = prep(new Eggs());\n", - " CompletableFuture milk = prep(new Milk());\n", - " CompletableFuture sugar = prep(new Sugar());\n", - " CompletableFuture flour = prep(new Flour());\n", - " CompletableFuture\n", - " .allOf(eggs, milk, sugar, flour)\n", - " .join();\n", - " new Nap(0.1); // Mixing time\n", - " return CompletableFuture.completedFuture(new Batter());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每种原料都需要一些时间来准备。`allOf()` 等待所有的配料都准备好,然后使用更多些的时间将其混合成面糊。接下来,我们把单批面糊放入四个平底锅中烘烤。产品作为 `CompletableFutures` 流返回:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Baked.java\n", - "\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "import onjava.Nap;\n", - "\n", - "public class Baked {\n", - " static class Pan {\n", - " }\n", - "\n", - " static Pan pan(Batter b) {\n", - " new Nap(0.1);\n", - " return new Pan();\n", - " }\n", - "\n", - " static Baked heat(Pan p) {\n", - " new Nap(0.1);\n", - " return new Baked();\n", - " }\n", - "\n", - " static CompletableFuture bake(CompletableFuture cfb) {\n", - " return cfb\n", - " .thenApplyAsync(Baked::pan)\n", - " .thenApplyAsync(Baked::heat);\n", - " }\n", - "\n", - " public static Stream> batch() {\n", - " CompletableFuture batter = Batter.mix();\n", - " return Stream.of(\n", - " bake(batter),\n", - " bake(batter),\n", - " bake(batter),\n", - " bake(batter)\n", - " );\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最后,我们制作了一批糖,并用它对蛋糕进行糖化:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/FrostedCake.java\n", - "\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "import onjava.Nap;\n", - "\n", - "final class Frosting {\n", - " private Frosting() {\n", - " }\n", - "\n", - " static CompletableFuture make() {\n", - " new Nap(0.1);\n", - " return CompletableFuture\n", - " .completedFuture(new Frosting());\n", - " }\n", - "}\n", - "\n", - "public class FrostedCake {\n", - " public FrostedCake(Baked baked, Frosting frosting) {\n", - " new Nap(0.1);\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"FrostedCake\";\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " Baked.batch().forEach(\n", - " baked -> baked\n", - " .thenCombineAsync(Frosting.make(),\n", - " (cake, frosting) ->\n", - " new FrostedCake(cake, frosting))\n", - " .thenAcceptAsync(System.out::println)\n", - " .join());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦你习惯了这种背后的想法, `CompletableFuture` 它们相对易于使用。\n", - "\n", - "### 异常\n", - "\n", - "与 `CompletableFuture` 在处理链中包装对象的方式相同,它也会缓冲异常。这些在处理时调用者是无感的,但仅当你尝试提取结果时才会被告知。为了说明它们是如何工作的,我们首先创建一个类,它在特定的条件下抛出一个异常:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Breakable.java\n", - "import java.util.concurrent.*;\n", - "public class Breakable {\n", - " String id;\n", - " private int failcount;\n", - "\n", - " public Breakable(String id, int failcount) {\n", - " this.id = id;\n", - " this.failcount = failcount;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"Breakable_\" + id + \" [\" + failcount + \"]\";\n", - " }\n", - "\n", - " public static Breakable work(Breakable b) {\n", - " if (--b.failcount == 0) {\n", - " System.out.println(\n", - " \"Throwing Exception for \" + b.id + \"\"\n", - " );\n", - " throw new RuntimeException(\n", - " \"Breakable_\" + b.id + \" failed\"\n", - " );\n", - " }\n", - " System.out.println(b);\n", - " return b;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当`failcount` > 0,且每次将对象传递给 `work()` 方法时, `failcount - 1` 。当`failcount - 1 = 0` 时,`work()` 将抛出一个异常。如果传给 `work()` 的 `failcount = 0` ,`work()` 永远不会抛出异常。\n", - "\n", - "注意,异常信息此示例中被抛出( `RuntimeException` )\n", - "\n", - "在下面示例 `test()` 方法中,`work()` 多次应用于 `Breakable`,因此如果 `failcount` 在范围内,就会抛出异常。然而,在测试`A`到`E`中,你可以从输出中看到抛出了异常,但它们从未出现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletableExceptions.java\n", - "import java.util.concurrent.*;\n", - "public class CompletableExceptions {\n", - " static CompletableFuture test(String id, int failcount) {\n", - " return CompletableFuture.completedFuture(\n", - " new Breakable(id, failcount))\n", - " .thenApply(Breakable::work)\n", - " .thenApply(Breakable::work)\n", - " .thenApply(Breakable::work)\n", - " .thenApply(Breakable::work);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " // Exceptions don't appear ...\n", - " test(\"A\", 1);\n", - " test(\"B\", 2);\n", - " test(\"C\", 3);\n", - " test(\"D\", 4);\n", - " test(\"E\", 5);\n", - " // ... until you try to fetch the value:\n", - " try {\n", - " test(\"F\", 2).get(); // or join()\n", - " } catch (Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " // Test for exceptions:\n", - " System.out.println(\n", - " test(\"G\", 2).isCompletedExceptionally()\n", - " );\n", - " // Counts as \"done\":\n", - " System.out.println(test(\"H\", 2).isDone());\n", - " // Force an exception:\n", - " CompletableFuture cfi =\n", - " new CompletableFuture<>();\n", - " System.out.println(\"done? \" + cfi.isDone());\n", - " cfi.completeExceptionally(\n", - " new RuntimeException(\"forced\"));\n", - " try {\n", - " cfi.get();\n", - " } catch (Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Throwing Exception for A\n", - "Breakable_B [1]\n", - "Throwing Exception for B\n", - "Breakable_C [2]\n", - "Breakable_C [1]\n", - "Throwing Exception for C\n", - "Breakable_D [3]\n", - "Breakable_D [2]\n", - "Breakable_D [1]\n", - "Throwing Exception for D\n", - "Breakable_E [4]\n", - "Breakable_E [3]\n", - "Breakable_E [2]\n", - "Breakable_E [1]\n", - "Breakable_F [1]\n", - "Throwing Exception for F\n", - "java.lang.RuntimeException: Breakable_F failed\n", - "Breakable_G [1]\n", - "Throwing Exception for G\n", - "true\n", - "Breakable_H [1]\n", - "Throwing Exception for H\n", - "true\n", - "done? false\n", - "java.lang.RuntimeException: forced" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试F中调用 `get()` 时,我们才会看到抛出的异常。\n", - "测试 `G` 表明,你可以首先检查在处理期间是否抛出异常,而不抛出该异常。然而,test `H` 告诉我们,不管异常是否成功,它仍然被视为已“完成”。\n", - "代码的最后一部分展示了如何将异常插入到 `CompletableFuture` 中,而不管是否存在任何失败。\n", - "在连接或获取结果时,我们使用 `CompletableFuture` 提供的更复杂的机制来自动响应异常,而不是使用粗糙的 `try-catch`。\n", - "你可以使用与我们看到的所有 `CompletableFuture` 相同的表单来完成此操作:在链中插入一个 `CompletableFuture` 调用。有三个选项 `exceptionally()`,`handle()`, `whenComplete()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CatchCompletableExceptions.java\n", - "import java.util.concurrent.*;\n", - "public class CatchCompletableExceptions {\n", - " static void handleException(int failcount) {\n", - " // Call the Function only if there's an\n", - " // exception, must produce same type as came in:\n", - " CompletableExceptions\n", - " .test(\"exceptionally\", failcount)\n", - " .exceptionally((ex) -> { // Function\n", - " if (ex == null)\n", - " System.out.println(\"I don't get it yet\");\n", - " return new Breakable(ex.getMessage(), 0);\n", - " })\n", - " .thenAccept(str ->\n", - " System.out.println(\"result: \" + str));\n", - "\n", - " // Create a new result (recover):\n", - " CompletableExceptions\n", - " .test(\"handle\", failcount)\n", - " .handle((result, fail) -> { // BiFunction\n", - " if (fail != null)\n", - " return \"Failure recovery object\";\n", - " else\n", - " return result + \" is good\";\n", - " })\n", - " .thenAccept(str ->\n", - " System.out.println(\"result: \" + str));\n", - "\n", - " // Do something but pass the same result through:\n", - " CompletableExceptions\n", - " .test(\"whenComplete\", failcount)\n", - " .whenComplete((result, fail) -> { // BiConsumer\n", - " if (fail != null)\n", - " System.out.println(\"It failed\");\n", - " else\n", - " System.out.println(result + \" OK\");\n", - " })\n", - " .thenAccept(r ->\n", - " System.out.println(\"result: \" + r));\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.println(\"**** Failure Mode ****\");\n", - " handleException(2);\n", - " System.out.println(\"**** Success Mode ****\");\n", - " handleException(0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "**** Failure Mode ****\n", - "Breakable_exceptionally [1]\n", - "Throwing Exception for exceptionally\n", - "result: Breakable_java.lang.RuntimeException:\n", - "Breakable_exceptionally failed [0]\n", - "Breakable_handle [1]\n", - "Throwing Exception for handle\n", - "result: Failure recovery object\n", - "Breakable_whenComplete [1]\n", - "Throwing Exception for whenComplete\n", - "It failed\n", - "**** Success Mode ****\n", - "Breakable_exceptionally [-1]\n", - "Breakable_exceptionally [-2]\n", - "Breakable_exceptionally [-3]\n", - "Breakable_exceptionally [-4]\n", - "result: Breakable_exceptionally [-4]\n", - "Breakable_handle [-1]\n", - "Breakable_handle [-2]\n", - "Breakable_handle [-3]\n", - "Breakable_handle [-4]\n", - "result: Breakable_handle [-4] is good\n", - "Breakable_whenComplete [-1]\n", - "Breakable_whenComplete [-2]\n", - "Breakable_whenComplete [-3]\n", - "Breakable_whenComplete [-4]\n", - "Breakable_whenComplete [-4] OK\n", - "result: Breakable_whenComplete [-4]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- `exceptionally()` 参数仅在出现异常时才运行。`exceptionally()` 局限性在于,该函数只能返回输入类型相同的值。\n", - "\n", - "- `exceptionally()` 通过将一个好的对象插入到流中来恢复到一个可行的状态。\n", - "\n", - "- `handle()` 一致被调用来查看是否发生异常(必须检查fail是否为true)。\n", - "\n", - " - 但是 `handle()` 可以生成任何新类型,所以它允许执行处理,而不是像使用 `exceptionally()`那样简单地恢复。\n", - "\n", - " - `whenComplete()` 类似于handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。\n", - "\n", - "\n", - "### 流异常(Stream Exception)\n", - "\n", - "通过修改**CompletableExceptions.java**,看看 **CompletableFuture**异常与流异常有何不同:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/StreamExceptions.java\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "public class StreamExceptions {\n", - " \n", - " static Stream test(String id, int failcount) {\n", - " return Stream.of(new Breakable(id, failcount))\n", - " .map(Breakable::work)\n", - " .map(Breakable::work)\n", - " .map(Breakable::work)\n", - " .map(Breakable::work);\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " // No operations are even applied ...\n", - " test(\"A\", 1);\n", - " test(\"B\", 2);\n", - " Stream c = test(\"C\", 3);\n", - " test(\"D\", 4);\n", - " test(\"E\", 5);\n", - " // ... until there's a terminal operation:\n", - " System.out.println(\"Entering try\");\n", - " try {\n", - " c.forEach(System.out::println); // [1]\n", - " } catch (Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Entering try\n", - "Breakable_C [2]\n", - "Breakable_C [1]\n", - "Throwing Exception for C\n", - "Breakable_C failed" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用 `CompletableFuture`,我们可以看到测试A到E的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client \n", - "\n", - "`CompletableFuture` 执行工作并捕获任何异常供以后检索。比较这两者并不容易,因为 `Stream` 在没有终端操作的情况下根本不做任何事情——但是流绝对不会存储它的异常。\n", - "\n", - "### 检查性异常\n", - "\n", - "`CompletableFuture` 和 `parallel Stream` 都不支持包含检查性异常的操作。相反,你必须在调用操作时处理检查到的异常,这会产生不太优雅的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/ThrowsChecked.java\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class ThrowsChecked {\n", - " class Checked extends Exception {}\n", - "\n", - " static ThrowsChecked nochecked(ThrowsChecked tc) {\n", - " return tc;\n", - " }\n", - "\n", - " static ThrowsChecked withchecked(ThrowsChecked tc) throws Checked {\n", - " return tc;\n", - " }\n", - "\n", - " static void testStream() {\n", - " Stream.of(new ThrowsChecked())\n", - " .map(ThrowsChecked::nochecked)\n", - " // .map(ThrowsChecked::withchecked); // [1]\n", - " .map(\n", - " tc -> {\n", - " try {\n", - " return withchecked(tc);\n", - " } catch (Checked e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " });\n", - " }\n", - "\n", - " static void testCompletableFuture() {\n", - " CompletableFuture\n", - " .completedFuture(new ThrowsChecked())\n", - " .thenApply(ThrowsChecked::nochecked)\n", - " // .thenApply(ThrowsChecked::withchecked); // [2]\n", - " .thenApply(\n", - " tc -> {\n", - " try {\n", - " return withchecked(tc);\n", - " } catch (Checked e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " });\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出lambda表达式(或者编写一个不会抛出异常的包装器方法)。\n", - "\n", - "## 死锁\n", - "\n", - "由于任务可以被阻塞,因此一个任务有可能卡在等待另一个任务上,而后者又在等待别的任务,这样一直下去,知道这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间相互等待的连续循环, 没有哪个线程能继续, 这称之为死锁[^6]\n", - "如果你运行一个程序,而它马上就死锁了, 你可以立即跟踪下去。真正的问题在于,程序看起来工作良好, 但是具有潜在的死锁危险。这时, 死锁可能发生,而事先却没有任何征兆, 所以 `bug` 会潜伏在你的程序例,直到客户发现它出乎意料的发生(以一种几乎肯定是很难重现的方式发生)。因此在编写并发程序的时候,进行仔细的程序设计以防止死锁是关键部分。\n", - "埃德斯·迪克斯特拉(`Essger Dijkstra`)发明的“哲学家进餐\"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数目)。这些哲学家将花部分时间思考,花部分时间就餐。他们在思考的时候并不需要任何共享资源;但是他们使用的餐具数量有限。在最初的问题描述中,餐具是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子, 显然,每个哲学家都需要两根筷子才能吃饭。\n", - "引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更一般地讲,筷子的数量与哲学家相同)。他们围在桌子周围,每人之间放一根筷子。 当一个哲学家要就餐时,该哲学家必须同时持有左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到可得到必须的筷子。\n", - "\n", - "**StickHolder** 类通过将单根筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/StickHolder.java\n", - "import java.util.concurrent.*;\n", - "public class StickHolder {\n", - " private static class Chopstick {\n", - " }\n", - "\n", - " private Chopstick stick = new Chopstick();\n", - " private BlockingQueue holder =\n", - " new ArrayBlockingQueue<>(1);\n", - "\n", - " public StickHolder() {\n", - " putDown();\n", - " }\n", - "\n", - " public void pickUp() {\n", - " try {\n", - " holder.take(); // Blocks if unavailable\n", - " } catch (InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " public void putDown() {\n", - " try {\n", - " holder.put(stick);\n", - " } catch (InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为简单起见,`Chopstick`(`static`) 实际上不是由 `StickHolder` 生产的,而是在其类中保持私有的。\n", - "\n", - "如果您调用了`pickUp()`,而 `stick` 不可用,那么`pickUp()`将阻塞该 `stick`,直到另一个哲学家调用`putDown()` 将 `stick` 返回。 \n", - "\n", - "注意,该类中的所有线程安全都是通过 `BlockingQueue` 实现的。\n", - "\n", - "每个哲学家都是一项任务,他们试图把筷子分别 `pickUp()` 在左手和右手上,这样筷子才能吃东西,然后通过 `putDown()` 放下 `stick`。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Philosopher.java\n", - "public class Philosopher implements Runnable {\n", - " private final int seat;\n", - " private final StickHolder left, right;\n", - "\n", - " public Philosopher(int seat, StickHolder left, StickHolder right) {\n", - " this.seat = seat;\n", - " this.left = left;\n", - " this.right = right;\n", - " }\n", - "\n", - " @Override\n", - " public String toString() {\n", - " return \"P\" + seat;\n", - " }\n", - "\n", - " @Override\n", - " public void run() {\n", - " while (true) {\n", - " // System.out.println(\"Thinking\"); // [1]\n", - " right.pickUp();\n", - " left.pickUp();\n", - " System.out.println(this + \" eating\");\n", - " right.putDown();\n", - " left.putDown();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。\n", - "\n", - "结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为这种语法更简洁:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/DiningPhilosophers.java\n", - "// Hidden deadlock\n", - "// {ExcludeFromGradle} Gradle has trouble\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "public class DiningPhilosophers {\n", - " private StickHolder[] sticks;\n", - " private Philosopher[] philosophers;\n", - "\n", - " public DiningPhilosophers(int n) {\n", - " sticks = new StickHolder[n];\n", - " Arrays.setAll(sticks, i -> new StickHolder());\n", - " philosophers = new Philosopher[n];\n", - " Arrays.setAll(philosophers, i ->\n", - " new Philosopher(i,\n", - " sticks[i], sticks[(i + 1) % n])); // [1]\n", - " // Fix by reversing stick order for this one:\n", - " // philosophers[1] = // [2]\n", - " // new Philosopher(0, sticks[0], sticks[1]);\n", - " Arrays.stream(philosophers)\n", - " .forEach(CompletableFuture::runAsync); // [3]\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " // Returns right away:\n", - " new DiningPhilosophers(5); // [4]\n", - " // Keeps main() from exiting:\n", - " new Nap(3, \"Shutdown\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "- 当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心不会产生死锁,但两核以上却很容易产生死锁。\n", - "- 此行为使该示例更好地说明了死锁,因为你可能正在具有2核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在2核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。\n", - "- 在 `DiningPhilosophers` 的构造方法中,每个哲学家都获得一个左右筷子的引用。除最后一个哲学家外,都是通过把哲学家放在下一双空闲筷子之间来初始化: \n", - " - 最后一位哲学家得到了第0根筷子作为他的右筷子,所以圆桌就完成。\n", - " - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。\n", - "- 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。\n", - " - 为了让每个哲学家在[3]上运行,调用 `runAsync()`,这意味着DiningPhilosophers的构造函数立即返回到[4]。\n", - " - 如果没有任何东西阻止 `main()` 完成,程序就会退出,不会做太多事情。\n", - " - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出(假设/可能是)死锁程序。\n", - " - 在给定的配置中,哲学家几乎不花时间思考。因此,他们在吃东西的时候都争着用筷子,而且往往很快就会陷入僵局。你可以改变这个:\n", - "\n", - "1. 通过增加[4]的值来添加更多哲学家。\n", - "\n", - "2. 在Philosopher.java中取消注释行[1]。\n", - "\n", - "任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个示例相当有趣,因为它演示了看起来可以正确运行,但实际上会可能发生死锁的程序。\n", - "\n", - "要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:\n", - "\n", - "1) 互斥条件。任务使用的资源中至少有一个不能共享的。 这里,一根筷子一次就只能被一个哲学家使用。\n", - "2) 至少有一个任务它必须持有一个资源且正在等待获取一个被当前别的任务持有的资源。也就是说,要发生死锁,哲学家必须拿着一根筷子并且等待另一根。\n", - "3) 资源不能被任务抢占, 任务必须把资源释放当作普通事件。哲学家很有礼貌,他们不会从其它哲学家那里抢筷子。\n", - "4) 必须有循环等待, 这时,一个任务等待其它任务所持有的资源, 后者又在等待另一个任务所持有的资源, 这样一直下去,知道有一个任务在等待第一个任务所持有的资源, 使得大家都被锁住。 在 `DiningPhilosophers.java` 中, 因为每个哲学家都试图先得到右边的 筷子, 然后得到左边的 筷子, 所以发生了循环等待。\n", - "\n", - "因为必须满足所有条件才能导致死锁,所以要阻止死锁的话,只需要破坏其中一个即可。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。\n", - "\n", - "在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。\n", - "这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。\n", - "没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。\n", - "\n", - "\n", - "\n", - "## 构造方法非线程安全\n", - "\n", - "当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*”\n", - "\n", - "不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。\n", - "\n", - "设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "//concurrent/HasID.java\n", - "public interface HasID {\n", - " int getID();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后 **StaticIDField** 类显式地实现该接口。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/StaticIDField.java\n", - "public class StaticIDField implements HasID {\n", - " private static int counter = 0;\n", - " private int id = counter++;\n", - " public int getID() { return id; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如你所想,该类是个简单无害的类,它甚至都没一个显式的构造器来引发问题。当我们运行多个用于创建此类对象的线程时,究竟会发生什么?为了搞清楚这点,我们做了以下测试。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/IDChecker.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import com.google.common.collect.Sets;\n", - "public class IDChecker {\n", - " public static final int SIZE = 100_000;\n", - "\n", - " static class MakeObjects implements\n", - " Supplier> {\n", - " private Supplier gen;\n", - "\n", - " MakeObjects(Supplier gen) {\n", - " this.gen = gen;\n", - " }\n", - "\n", - " @Override public List get() {\n", - " return Stream.generate(gen)\n", - " .limit(SIZE)\n", - " .map(HasID::getID)\n", - " .collect(Collectors.toList());\n", - " }\n", - " }\n", - "\n", - " public static void test(Supplier gen) {\n", - " CompletableFuture>\n", - " groupA = CompletableFuture.supplyAsync(new\n", - " MakeObjects(gen)),\n", - " groupB = CompletableFuture.supplyAsync(new\n", - " MakeObjects(gen));\n", - "\n", - " groupA.thenAcceptBoth(groupB, (a, b) -> {\n", - " System.out.println(\n", - " Sets.intersection(\n", - " Sets.newHashSet(a),\n", - " Sets.newHashSet(b)).size());\n", - " }).join();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**MakeObjects** 类是一个生产者类,包含一个能够产生 List\\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。\n", - "\n", - "使用 Guava 库中的 **Sets.`intersection()` 方法,计算出这两个返回的 List\\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。\n", - "\n", - "现在我们可以测试上面的 **StaticIDField** 类了。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/TestStaticIDField.java\n", - "public class TestStaticIDField {\n", - "\n", - " public static void main(String[] args) {\n", - " IDChecker.test(StaticIDField::new);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " 13287" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/GuardedIDField.java\n", - "import java.util.concurrent.atomic.*;\n", - "public class GuardedIDField implements HasID { \n", - " private static AtomicInteger counter = new\n", - " AtomicInteger();\n", - "\n", - " private int id = counter.getAndIncrement();\n", - "\n", - " public int getID() { return id; }\n", - "\n", - " public static void main(String[] args) { IDChecker.test(GuardedIDField::new);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "构造器有一种更微妙的状态共享方式:通过构造器参数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/SharedConstructorArgument.java\n", - "import java.util.concurrent.atomic.*;\n", - "interface SharedArg{\n", - " int get();\n", - "}\n", - "\n", - "class Unsafe implements SharedArg{\n", - " private int i = 0;\n", - "\n", - " public int get(){\n", - " return i++;\n", - " }\n", - "}\n", - "\n", - "class Safe implements SharedArg{\n", - " private static AtomicInteger counter = new AtomicInteger();\n", - "\n", - " public int get(){\n", - " return counter.getAndIncrement();\n", - " }\n", - "}\n", - "\n", - "class SharedUser implements HasID{\n", - " private final int id;\n", - "\n", - " SharedUser(SharedArg sa){\n", - " id = sa.get();\n", - " }\n", - "\n", - " @Override\n", - " public int getID(){\n", - " return id;\n", - " }\n", - "}\n", - "\n", - "public class SharedConstructorArgument{\n", - " public static void main(String[] args){\n", - " Unsafe unsafe = new Unsafe();\n", - " IDChecker.test(() -> new SharedUser(unsafe));\n", - "\n", - " Safe safe = new Safe();\n", - " IDChecker.test(() -> new SharedUser(safe));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " 24838\n", - " 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。\n", - "\n", - "同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/SynchronizedConstructor.java\n", - "\n", - "import java.util.concurrent.atomic.*;\n", - "\n", - "class SyncConstructor implements HasID{\n", - " private final int id;\n", - " private static Object constructorLock =\n", - " new Object();\n", - "\n", - " SyncConstructor(SharedArg sa){\n", - " synchronized (constructorLock){\n", - " id = sa.get();\n", - " }\n", - " }\n", - "\n", - " @Override\n", - " public int getID(){\n", - " return id;\n", - " }\n", - "}\n", - "\n", - "public class SynchronizedConstructor{\n", - " public static void main(String[] args){\n", - " Unsafe unsafe = new Unsafe();\n", - " IDChecker.test(() -> new SyncConstructor(unsafe));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Unsafe**类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/SynchronizedFactory.java\n", - "import java.util.concurrent.atomic.*;\n", - "\n", - "final class SyncFactory implements HasID{\n", - " private final int id;\n", - "\n", - " private SyncFactory(SharedArg sa){\n", - " id = sa.get();\n", - " }\n", - "\n", - " @Override\n", - " public int getID(){\n", - " return id;\n", - " }\n", - "\n", - " public static synchronized SyncFactory factory(SharedArg sa){\n", - " return new SyncFactory(sa);\n", - " }\n", - "}\n", - "\n", - "public class SynchronizedFactory{\n", - " public static void main(String[] args){\n", - " Unsafe unsafe = new Unsafe();\n", - " IDChecker.test(() -> SyncFactory.factory(unsafe));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - " 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。\n", - "\n", - "这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。\n", - "\n", - "## 复杂性和代价\n", - "\n", - "假设你正在做披萨,我们把从整个流程的当前步骤到下一个步骤所需的工作量,在这里一一表示为枚举变量的一部分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/Pizza.java import java.util.function.*;\n", - "\n", - "import onjava.Nap;\n", - "public class Pizza{\n", - " public enum Step{\n", - " DOUGH(4), ROLLED(1), SAUCED(1), CHEESED(2),\n", - " TOPPED(5), BAKED(2), SLICED(1), BOXED(0);\n", - " int effort;// Needed to get to the next step \n", - "\n", - " Step(int effort){\n", - " this.effort = effort;\n", - " }\n", - "\n", - " Step forward(){\n", - " if (equals(BOXED)) return BOXED;\n", - " new Nap(effort * 0.1);\n", - " return values()[ordinal() + 1];\n", - " }\n", - " }\n", - "\n", - " private Step step = Step.DOUGH;\n", - " private final int id;\n", - "\n", - " public Pizza(int id){\n", - " this.id = id;\n", - " }\n", - "\n", - " public Pizza next(){\n", - " step = step.forward();\n", - " System.out.println(\"Pizza \" + id + \": \" + step);\n", - " return this;\n", - " }\n", - "\n", - " public Pizza next(Step previousStep){\n", - " if (!step.equals(previousStep))\n", - " throw new IllegalStateException(\"Expected \" +\n", - " previousStep + \" but found \" + step);\n", - " return next();\n", - " }\n", - "\n", - " public Pizza roll(){\n", - " return next(Step.DOUGH);\n", - " }\n", - "\n", - " public Pizza sauce(){\n", - " return next(Step.ROLLED);\n", - " }\n", - "\n", - " public Pizza cheese(){\n", - " return next(Step.SAUCED);\n", - " }\n", - "\n", - " public Pizza toppings(){\n", - " return next(Step.CHEESED);\n", - " }\n", - "\n", - " public Pizza bake(){\n", - " return next(Step.TOPPED);\n", - " }\n", - "\n", - " public Pizza slice(){\n", - " return next(Step.BAKED);\n", - " }\n", - "\n", - " public Pizza box(){\n", - " return next(Step.SLICED);\n", - " }\n", - "\n", - " public boolean complete(){\n", - " return step.equals(Step.BOXED);\n", - " }\n", - "\n", - " @Override\n", - " public String toString(){\n", - " return \"Pizza\" + id + \": \" + (step.equals(Step.BOXED) ? \"complete\" : step);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这只算得上是一个平凡的状态机,就像**Machina**类一样。 \n", - "\n", - "制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/OnePizza.java \n", - "\n", - "import onjava.Timer;\n", - "\n", - "public class OnePizza{\n", - " public static void main(String[] args){\n", - " Pizza za = new Pizza(0);\n", - " System.out.println(Timer.duration(() -> {\n", - " while (!za.complete()) za.next();\n", - " }));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Pizza 0: ROLLED \n", - "Pizza 0: SAUCED \n", - "Pizza 0: CHEESED \n", - "Pizza 0: TOPPED \n", - "Pizza 0: BAKED \n", - "Pizza 0: SLICED \n", - "Pizza 0: BOXED \n", - "\t1622 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "时间以毫秒为单位,加总所有步骤的工作量,会得出与我们的期望值相符的数字。 如果你以这种方式制作了五个披萨,那么你会认为它花费的时间是原来的五倍。 但是,如果这还不够快怎么办? 我们可以从尝试并行流方法开始:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/PizzaStreams.java\n", - "// import java.util.*; import java.util.stream.*;\n", - "\n", - "import onjava.Timer;\n", - "\n", - "public class PizzaStreams{\n", - " static final int QUANTITY = 5;\n", - "\n", - " public static void main(String[] args){\n", - " Timer timer = new Timer();\n", - " IntStream.range(0, QUANTITY)\n", - " .mapToObj(Pizza::new)\n", - " .parallel()//[1]\n", - " \t.forEach(za -> { while(!za.complete()) za.next(); }); \t\t\tSystem.out.println(timer.duration());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Pizza 2: ROLLED\n", - "Pizza 0: ROLLED\n", - "Pizza 1: ROLLED\n", - "Pizza 4: ROLLED\n", - "Pizza 3:ROLLED\n", - "Pizza 2:SAUCED\n", - "Pizza 1:SAUCED\n", - "Pizza 0:SAUCED\n", - "Pizza 4:SAUCED\n", - "Pizza 3:SAUCED\n", - "Pizza 2:CHEESED\n", - "Pizza 1:CHEESED\n", - "Pizza 0:CHEESED\n", - "Pizza 4:CHEESED\n", - "Pizza 3:CHEESED\n", - "Pizza 2:TOPPED\n", - "Pizza 1:TOPPED\n", - "Pizza 0:TOPPED\n", - "Pizza 4:TOPPED\n", - "Pizza 3:TOPPED\n", - "Pizza 2:BAKED\n", - "Pizza 1:BAKED\n", - "Pizza 0:BAKED\n", - "Pizza 4:BAKED\n", - "Pizza 3:BAKED\n", - "Pizza 2:SLICED\n", - "Pizza 1:SLICED\n", - "Pizza 0:SLICED\n", - "Pizza 4:SLICED\n", - "Pizza 3:SLICED\n", - "Pizza 2:BOXED\n", - "Pizza 1:BOXED\n", - "Pizza 0:BOXED\n", - "Pizza 4:BOXED\n", - "Pizza 3:BOXED\n", - "1739" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将**QUANTITY**更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。\n", - "\n", - "**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗?" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/PizzaParallelSteps.java \n", - "\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import onjava.Timer;\n", - "\n", - "public class PizzaParallelSteps{\n", - " static final int QUANTITY = 5;\n", - "\n", - " public static void main(String[] args){\n", - " Timer timer = new Timer();\n", - " IntStream.range(0, QUANTITY)\n", - " .mapToObj(Pizza::new)\n", - " .parallel()\n", - " .map(Pizza::roll)\n", - " .map(Pizza::sauce)\n", - " .map(Pizza::cheese)\n", - " .map(Pizza::toppings)\n", - " .map(Pizza::bake)\n", - " .map(Pizza::slice)\n", - " .map(Pizza::box)\n", - " .forEach(za -> System.out.println(za));\n", - " System.out.println(timer.duration());\n", - " }\n", - "} " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Pizza 2: ROLLED \n", - "Pizza 0: ROLLED \n", - "Pizza 1: ROLLED \n", - "Pizza 4: ROLLED \n", - "Pizza 3: ROLLED \n", - "Pizza 1: SAUCED \n", - "Pizza 0: SAUCED \n", - "Pizza 2: SAUCED \n", - "Pizza 3: SAUCED \n", - "Pizza 4: SAUCED \n", - "Pizza 1: CHEESED \n", - "Pizza 0: CHEESED \n", - "Pizza 2: CHEESED \n", - "Pizza 3: CHEESED \n", - "Pizza 4: CHEESED \n", - "Pizza 0: TOPPED \n", - "Pizza 2: TOPPED\n", - "Pizza 1: TOPPED \n", - "Pizza 3: TOPPED \n", - "Pizza 4: TOPPED \n", - "Pizza 1: BAKED \n", - "Pizza 2: BAKED \n", - "Pizza 0: BAKED \n", - "Pizza 4: BAKED \n", - "Pizza 3: BAKED \n", - "Pizza 0: SLICED \n", - "Pizza 2: SLICED \n", - "Pizza 1: SLICED \n", - "Pizza 3: SLICED \n", - "Pizza 4: SLICED \n", - "Pizza 1: BOXED \n", - "Pizza1: complete \n", - "Pizza 2: BOXED \n", - "Pizza 0: BOXED \n", - "Pizza2: complete \n", - "Pizza0: complete \n", - "Pizza 3: BOXED\n", - "Pizza 4: BOXED \n", - "Pizza4: complete \n", - "Pizza3: complete \n", - "1738 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的 `PizzaParallelSteps.java` 里面展示的一样。\n", - "\n", - "我们可以使用 **CompletableFutures** 重写这个例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// concurrent/CompletablePizza.java \n", - "\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.stream.*;\n", - "import onjava.Timer;\n", - "\n", - "public class CompletablePizza{\n", - " static final int QUANTITY = 5;\n", - "\n", - " public static CompletableFuture makeCF(Pizza za){\n", - " return CompletableFuture\n", - " .completedFuture(za)\n", - " .thenApplyAsync(Pizza::roll)\n", - " .thenApplyAsync(Pizza::sauce)\n", - " .thenApplyAsync(Pizza::cheese)\n", - " .thenApplyAsync(Pizza::toppings)\n", - " .thenApplyAsync(Pizza::bake)\n", - " .thenApplyAsync(Pizza::slice)\n", - " .thenApplyAsync(Pizza::box);\n", - " }\n", - "\n", - " public static void show(CompletableFuture cf){\n", - " try{\n", - " System.out.println(cf.get());\n", - " } catch (Exception e){\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args){\n", - " Timer timer = new Timer();\n", - " List> pizzas =\n", - " IntStream.range(0, QUANTITY)\n", - " .mapToObj(Pizza::new)\n", - " .map(CompletablePizza::makeCF)\n", - " .collect(Collectors.toList());\n", - " System.out.println(timer.duration());\n", - " pizzas.forEach(CompletablePizza::show);\n", - " System.out.println(timer.duration());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "169 \n", - "Pizza 0: ROLLED \n", - "Pizza 1: ROLLED \n", - "Pizza 2: ROLLED \n", - "Pizza 4: ROLLED \n", - "Pizza 3: ROLLED \n", - "Pizza 1: SAUCED \n", - "Pizza 0: SAUCED \n", - "Pizza 2: SAUCED \n", - "Pizza 4: SAUCED\n", - "Pizza 3: SAUCED \n", - "Pizza 0: CHEESED \n", - "Pizza 4: CHEESED \n", - "Pizza 1: CHEESED \n", - "Pizza 2: CHEESED \n", - "Pizza 3: CHEESED \n", - "Pizza 0: TOPPED \n", - "Pizza 4: TOPPED \n", - "Pizza 1: TOPPED \n", - "Pizza 2: TOPPED \n", - "Pizza 3: TOPPED \n", - "Pizza 0: BAKED \n", - "Pizza 4: BAKED \n", - "Pizza 1: BAKED \n", - "Pizza 3: BAKED \n", - "Pizza 2: BAKED \n", - "Pizza 0: SLICED \n", - "Pizza 4: SLICED \n", - "Pizza 1: SLICED \n", - "Pizza 3: SLICED\n", - "Pizza 2: SLICED \n", - "Pizza 4: BOXED \n", - "Pizza 0: BOXED \n", - "Pizza0: complete \n", - "Pizza 1: BOXED \n", - "Pizza1: complete \n", - "Pizza 3: BOXED \n", - "Pizza 2: BOXED \n", - "Pizza2: complete \n", - "Pizza3: complete \n", - "Pizza4: complete \n", - "1797 " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究**Spliterator**的文档)。\n", - "\n", - "而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。\n", - "\n", - "对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。\n", - "\n", - "由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。\n", - "\n", - "使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。\n", - "\n", - "## 本章小结\n", - "\n", - "需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“你的程序运行速度还不够快”。\n", - "\n", - "如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以你应该仔细考虑是否值得为此付出努力,并考虑你能否以其他方式提升速度。\n", - "\n", - "例如,迁移到更快的硬件(这可能比消耗程序员的时间要便宜得多)或者将程序分解成多个部分,然后在不同的机器上运行这些部分。\n", - "\n", - "奥卡姆剃刀是一个经常被误解的原则。 我看过至少一部电影,他们将其定义为”最简单的解决方案是正确的解决方案“,就好像这是某种毋庸置疑的法律。实际上,这是一个准则:面对多种方法时,请先尝试需要最少假设的方法。 在编程世界中,这已演变为“尝试可能可行的最简单的方法”。当你了解了特定工具的知识时——就像你现在了解了有关并发性的知识一样,你可能会很想使用它,或者提前规定你的解决方案必须能够“速度飞快”,从而来证明从一开始就进行并发设计是合理的。但是,我们的奥卡姆剃刀编程版本表示你应该首先尝试最简单的方法(这种方法开发起来也更便宜),然后看看它是否足够好。\n", - "\n", - "由于我出身于底层学术背景(物理学和计算机工程),所以我很容易想到所有小轮子转动的成本。我确定使用最简单的方法不够快的场景出现的次数已经数不过来了,但是尝试后却发现它实际上绰绰有余。\n", - "\n", - "### 缺点\n", - "\n", - "并发编程的主要缺点是:\n", - "\n", - "1. 在线程等待共享资源时会降低速度。 \n", - "\n", - "2. 线程管理产生额外CPU开销。\n", - "\n", - "3. 糟糕的设计决策带来无法弥补的复杂性。\n", - "\n", - "4. 诸如饥饿,竞速,死锁和活锁(多线程各自处理单个任务而整体却无法完成)之类的问题。\n", - "\n", - "5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果你在后者上开发程序,则在分发程序时可能会感到非常惊讶。\n", - "\n", - "另外,并发的应用是一门艺术。 Java旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。\n", - "\n", - "### 共享内存陷阱\n", - "\n", - "并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且你必须确保多个任务不会同时读取和更改该资源。\n", - "\n", - "我花了多年的时间研究并发并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10]\n", - "\n", - "我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。\n", - "\n", - "再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给你一种一切都棒极了的印象。当涉及到共享内存并发编程时,你永远不应该对自己的编程能力变得过于自信。\n", - "\n", - "### This Albatross is Big\n", - "\n", - "如果你对Java并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。\n", - "\n", - "事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。\n", - "\n", - "在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。\n", - "\n", - "### 其他类库\n", - "\n", - "本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是你还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。\n", - "\n", - "### 考虑为并发设计的语言\n", - "\n", - "通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在Java的世界范围内)。\n", - "\n", - "如果你的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与你的主要Java代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11]\n", - "\n", - "你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许你在Scala中完成代码)。\n", - "\n", - "心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。\n", - "\n", - "无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在你最不期望出现的时候咬你。\n", - "\n", - "### 拓展阅读\n", - "\n", - "《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。\n", - "\n", - "[^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。\n", - "[^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论\n", - "[^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。\n", - "[^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为\n", - "[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。\n", - "[^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。\n", - "[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的Java版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩\n", - "[^8]: 库就在那里用于调用,而语言本身就被设计用于此目的,但实际上它很少发生,以至于可以说”没有“。↩\n", - "[^9]: 举例来说,如果没有Flyweight设计模式,在工程中创建数百万个对象用于有限元分析可能在Java中不可行。↩\n", - "[^10]: 在科学中,虽然从来没有一种理论被证实过,但是一种理论必须是可证伪的才有意义。而对于并发性,我们大部分时间甚至都无法得到这种可证伪性。↩\n", - "[^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/25-Patterns.ipynb b/jupyter/25-Patterns.ipynb deleted file mode 100644 index f6bad1f5..00000000 --- a/jupyter/25-Patterns.ipynb +++ /dev/null @@ -1,1674 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 第二十五章 设计模式\n", - "\n", - "\n", - "\n", - "## 概念\n", - "最初,你可以将模式视为解决特定类问题的一种特别巧妙且有深刻见解的方法。这就像前辈已经从所有角度去解决问题,并提出了最通用,最灵活的解决方案。问题可能是你之前看到并解决过的问题,但你的解决方案可能没有你在模式中体现的那种完整性。\n", - "\n", - "虽然它们被称为“设计模式”,但它们实际上并不与设计领域相关联。模式似乎与传统的分析、设计和实现的思维方式不同。相反,模式在程序中体现了一个完整的思想,因此它有时会出现在分析阶段或高级设计阶段。因为模式在代码中有一个直接的实现,所以你可能不会期望模式在低级设计或实现之前出现(而且通常在到达这些阶段之前,你不会意识到需要一个特定的模式)。\n", - "\n", - "模式的基本概念也可以看作是程序设计的基本概念:添加抽象层。当你抽象一些东西的时候,就像在剥离特定的细节,而这背后最重要的动机之一是:\n", - "> **将易变的事物与不变的事物分开**\n", - "\n", - "另一种方法是,一旦你发现程序的某些部分可能因某种原因而发生变化,你要保持这些变化不会引起整个代码中其他变化。 如果代码更容易理解,那么维护起来会更容易。\n", - "\n", - "通常,开发一个优雅且易维护设计中最困难的部分是发现我称之为变化的载体(也就是最易改变的地方)。这意味着找到系统中最重要的变化,换而言之,找到变化会导致最严重后果的地方。一旦发现变化载体,就可以围绕构建设计的焦点。\n", - "\n", - "因此,设计模式的目标是隔离代码中的更改。 如果以这种方式去看,你已经在本书中看到了设计模式。 例如,继承可以被认为是一种设计模式(虽然是由编译器实现的)。它允许你表达所有具有相同接口的对象(即保持相同的行为)中的行为差异(这就是变化的部分)。组合也可以被视为一种模式,因为它允许你动态或静态地更改实现类的对象,从而改变类的工作方式。\n", - "\n", - "你还看到了设计模式中出现的另一种模式:迭代器(Java 1.0和1.1随意地将其称为枚举; Java 2 集合才使用Iterator)。当你逐个选择元素时并逐步处理,这会隐藏集合的特定实现。迭代器允许你编写通用代码,该代码对序列中的所有元素执行操作,而不考虑序列的构建方式。因此,你的通用代码可以与任何可以生成迭代器的集合一起使用。\n", - "\n", - "即使模式是非常有用的,但有些人断言:\n", - "> **设计模式代表语言的失败。**\n", - "\n", - "这是一个非常重要的见解,因为一个模式在 C++ 有意义,可能在JAVA或者其他语言中就没有意义。出于这个原因,所以一个模式可能出现在设计模式书上,不意味着应用于你的编程语言是有用的。\n", - "\n", - "我认为“语言失败”这个观点是有道理的,但是我也认为这个观点过于简单化。如果你试图解决一个特定的问题,而你使用的语言没有直接提供支持你使用的技巧,你可以说这个是语言的失败。但是,你使用特定的技巧的频率的是多少呢?也许平衡是对的:当你使用特定的技巧的时候,你必须付出更多的努力,但是你又没有足够的理由去使得语言支持这个技术。另一方面,没有语言的支持,使用这种技术常常会很混乱,但是在语言支持下,你可能会改变编程方式(例如,Java 8流实现此目的)。\n", - "\n", - "### 单例模式\n", - "也许单例模式是最简单的设计模式,它是一种提供一个且只有一个对象实例的方法。这在java库中使用,但是这有个更直接的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/SingletonPattern.java\n", - "interface Resource {\n", - " int getValue();\n", - " void setValue(int x);\n", - "}\n", - "\n", - "/*\n", - "* 由于这不是从Cloneable基类继承而且没有添加可克隆性,\n", - "* 因此将其设置为final可防止通过继承添加可克隆性。\n", - "* 这也实现了线程安全的延迟初始化:\n", - "*/\n", - "final class Singleton {\n", - " private static final class ResourceImpl implements Resource {\n", - " private int i;\n", - " private ResourceImpl(int i) {\n", - " this.i = i;\n", - " }\n", - " public synchronized int getValue() {\n", - " return i;\n", - " }\n", - " public synchronized void setValue(int x) {\n", - " i = x;\n", - " }\n", - " }\n", - "\n", - " private static class ResourceHolder {\n", - " private static Resource resource = new ResourceImpl(47);\n", - " }\n", - " public static Resource getResource() {\n", - " return ResourceHolder.resource;\n", - " }\n", - "}\n", - "\n", - "public class SingletonPattern {\n", - " public static void main(String[] args) {\n", - " Resource r = Singleton.getResource();\n", - " System.out.println(r.getValue());\n", - " Resource s2 = Singleton.getResource();\n", - " s2.setValue(9);\n", - " System.out.println(r.getValue());\n", - " try { \n", - " // 不能这么做,会发生:compile-time error(编译时错误). \n", - " // Singleton s3 = (Singleton)s2.clone(); \n", - " } catch(Exception e) { \n", - " throw new RuntimeException(e); \n", - " } \n", - " }\n", - "} /* Output: 47 9 */" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "创建单例的关键是防止客户端程序员直接创建对象。 在这里,这是通过在Singleton类中将Resource的实现作为私有类来实现的。\n", - "\n", - "此时,你将决定如何创建对象。在这里,它是按需创建的,在第一次访问的时候创建。 该对象是私有的,只能通过public getResource()方法访问。\n", - "\n", - "\n", - "懒惰地创建对象的原因是它嵌套的私有类resourceHolder在首次引用之前不会加载(在getResource()中)。当Resource对象加载的时候,静态初始化块将被调用。由于JVM的工作方式,这种静态初始化是线程安全的。为保证线程安全,Resource中的getter和setter是同步的。\n", - "\n", - "### 模式分类\n", - "\n", - "“设计模式”一书讨论了23种不同的模式,分为以下三种类别(所有这些模式都围绕着可能变化的特定方面)。\n", - "\n", - "1. **创建型**:如何创建对象。 这通常涉及隔离对象创建的细节,这样你的代码就不依赖于具体的对象的类型,因此在添加新类型的对象时不会更改。单例模式(Singleton)被归类为创作模式,本章稍后你将看到Factory Method的示例。\n", - "\n", - "2. **构造型**:设计对象以满足特定的项目约束。它们处理对象与其他对象连接的方式,以确保系统中的更改不需要更改这些连接。\n", - "\n", - "3. **行为型**:处理程序中特定类型的操作的对象。这些封装要执行的过程,例如解释语言、实现请求、遍历序列(如在迭代器中)或实现算法。本章包含观察者和访问者模式的例子。\n", - "\n", - "《设计模式》一书中每个设计模式都有单独的一个章节,每个章节都有一个或者多个例子,通常使用C++,但有时也使用SmallTalk。 本章不重复设计模式中显示的所有模式,因为该书独立存在,应单独研究。 相反,你会看到一些示例,可以为你提供关于模式的理解以及它们如此重要的原因。\n", - "\n", - "\n", - "## 构建应用程序框架\n", - "\n", - "应用程序框架允许您从一个类或一组类开始,创建一个新的应用程序,重用现有类中的大部分代码,并根据需要覆盖一个或多个方法来定制应用程序。\n", - "\n", - "**模板方法模式**\n", - "\n", - "应用程序框架中的一个基本概念是模板方法模式,它通常隐藏在底层,通过调用基类中的各种方法来驱动应用程序(为了创建应用程序,您已经覆盖了其中的一些方法)。\n", - "\n", - "模板方法模式的一个重要特性是它是在基类中定义的,并且不能更改。它有时是一个 **private** 方法,但实际上总是 **final**。它调用其他基类方法(您覆盖的那些)来完成它的工作,但是它通常只作为初始化过程的一部分被调用(因此框架使用者不一定能够直接调用它)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/TemplateMethod.java\n", - "// Simple demonstration of Template Method\n", - "\n", - "abstract class ApplicationFramework {\n", - " ApplicationFramework() {\n", - " templateMethod();\n", - " }\n", - "\n", - " abstract void customize1();\n", - "\n", - " abstract void customize2(); // \"private\" means automatically \"final\": private void templateMethod() { IntStream.range(0, 5).forEach( n -> { customize1(); customize2(); }); }}// Create a new \"application\": class MyApp extends ApplicationFramework { @Override void customize1() { System.out.print(\"Hello \"); }@Override\n", - "\n", - " void customize2() {\n", - " System.out.println(\"World!\");\n", - " }\n", - "}\n", - "\n", - "public class TemplateMethod {\n", - " public static void main(String[] args) {\n", - " new MyApp();\n", - " }\n", - "}\n", - "/*\n", - "Output:\n", - "Hello World!\n", - "Hello World!\n", - "Hello World!\n", - "Hello World!\n", - "Hello World!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "基类构造函数负责执行必要的初始化,然后启动运行应用程序的“engine”(模板方法模式)(在GUI应用程序中,这个“engine”是主事件循环)。框架使用者只提供\n", - "**customize1()** 和 **customize2()** 的定义,然后“应用程序”已经就绪运行。\n", - "\n", - "![](images/designproxy.png)\n", - "\n", - "\n", - "## 面向实现\n", - "\n", - "代理模式和桥接模式都提供了在代码中使用的代理类;完成工作的真正类隐藏在这个代理类的后面。当您在代理中调用一个方法时,它只是反过来调用实现类中的方法。这两种模式非常相似,所以代理模式只是桥接模式的一种特殊情况。人们倾向于将两者合并,称为代理模式,但是术语“代理”有一个长期的和专门的含义,这可能解释了这两种模式不同的原因。基本思想很简单:从基类派生代理,同时派生一个或多个提供实现的类:创建代理对象时,给它一个可以调用实际工作类的方法的实现。\n", - "\n", - "\n", - "在结构上,代理模式和桥接模式的区别很简单:代理模式只有一个实现,而桥接模式有多个实现。在设计模式中被认为是不同的:代理模式用于控制对其实现的访问,而桥接模式允许您动态更改实现。但是,如果您扩展了“控制对实现的访问”的概念,那么这两者就可以完美地结合在一起\n", - "\n", - "**代理模式**\n", - "\n", - "如果我们按照上面的关系图实现,它看起来是这样的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/ProxyDemo.java\n", - "// Simple demonstration of the Proxy pattern\n", - "interface ProxyBase {\n", - " void f();\n", - "\n", - " void g();\n", - "\n", - " void h();\n", - "}\n", - "\n", - "class Proxy implements ProxyBase {\n", - " private ProxyBase implementation;\n", - "\n", - " Proxy() {\n", - " implementation = new Implementation();\n", - " }\n", - " // Pass method calls to the implementation:\n", - " @Override\n", - " public void f() { implementation.f(); }\n", - " @Override\n", - " public void g() { implementation.g(); }\n", - " @Override\n", - " public void h() { implementation.h(); }\n", - "}\n", - "\n", - "class Implementation implements ProxyBase {\n", - " public void f() {\n", - " System.out.println(\"Implementation.f()\");\n", - " }\n", - "\n", - " public void g() {\n", - " System.out.println(\"Implementation.g()\");\n", - " }\n", - "\n", - " public void h() {\n", - " System.out.println(\"Implementation.h()\");\n", - " }\n", - "}\n", - "\n", - "public class ProxyDemo {\n", - " public static void main(String[] args) {\n", - " Proxy p = new Proxy();\n", - " p.f();\n", - " p.g();\n", - " p.h();\n", - " }\n", - "}\n", - "/*\n", - "Output:\n", - "Implementation.f()\n", - "Implementation.g()\n", - "Implementation.h()\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "具体实现不需要与代理对象具有相同的接口;只要代理对象以某种方式“代表具体实现的方法调用,那么基本思想就算实现了。然而,拥有一个公共接口是很方便的,因此具体实现必须实现代理对象调用的所有方法。\n", - "\n", - "**状态模式**\n", - "\n", - "状态模式向代理对象添加了更多的实现,以及在代理对象的生命周期内从一个实现切换到另一种实现的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/StateDemo.java // Simple demonstration of the State pattern\n", - "interface StateBase {\n", - " void f();\n", - "\n", - " void g();\n", - "\n", - " void h();\n", - "\n", - " void changeImp(StateBase newImp);\n", - "}\n", - "\n", - "class State implements StateBase {\n", - " private StateBase implementation;\n", - "\n", - " State(StateBase imp) {\n", - " implementation = imp;\n", - " }\n", - "\n", - " @Override\n", - " public void changeImp(StateBase newImp) {\n", - " implementation = newImp;\n", - " }// Pass method calls to the implementation: @Override public void f() { implementation.f(); } @Override public void g() { implementation.g(); } @Override\n", - "\n", - " public void h() {\n", - " implementation.h();\n", - " }\n", - "}\n", - "\n", - "class Implementation1 implements StateBase {\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"Implementation1.f()\");\n", - " }\n", - "\n", - " @Override\n", - " public void g() {\n", - " System.out.println(\"Implementation1.g()\");\n", - " }\n", - "\n", - " @Override\n", - " public void h() {\n", - " System.out.println(\"Implementation1.h()\");\n", - " }\n", - "\n", - " @Override\n", - " public void changeImp(StateBase newImp) {\n", - " }\n", - "}\n", - "\n", - "class Implementation2 implements StateBase {\n", - " @Override\n", - " public void f() {\n", - " System.out.println(\"Implementation2.f()\");\n", - " }\n", - "\n", - " @Override\n", - " public void g() {\n", - " System.out.println(\"Implementation2.g()\");\n", - " }\n", - "\n", - " @Override\n", - " public void h() {\n", - " System.out.println(\"Implementation2.h()\");\n", - " }\n", - "\n", - " @Override\n", - " public void changeImp(StateBase newImp) {\n", - " }\n", - "}\n", - "\n", - "public class StateDemo {\n", - " static void test(StateBase b) {\n", - " b.f();\n", - " b.g();\n", - " b.h();\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " StateBase b = new State(new Implementation1());\n", - " test(b);\n", - " b.changeImp(new Implementation2());\n", - " test(b);\n", - " }\n", - "}\n", - "/* Output:\n", - "Implementation1.f()\n", - "Implementation1.g()\n", - "Implementation1.h()\n", - "Implementation2.f()\n", - "Implementation2.g()\n", - "Implementation2.h()\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在main()中,首先使用第一个实现,然后改变成第二个实现。代理模式和状态模式的区别在于它们解决的问题。设计模式中描述的代理模式的常见用途如下:\n", - "\n", - "1. 远程代理。它在不同的地址空间中代理对象。远程方法调用(RMI)编译器rmic会自动为您创建一个远程代理。\n", - "\n", - "2. 虚拟代理。这提供了“懒加载”来根据需要创建“昂贵”的对象。\n", - "\n", - "3. 保护代理。当您希望对代理对象有权限访问控制时使用。\n", - "\n", - "4. 智能引用。要在被代理的对象被访问时添加其他操作。例如,跟踪特定对象的引用数量,来实现写时复制用法,和防止对象别名。一个更简单的例子是跟踪特定方法的调用数量。您可以将Java引用视为一种保护代理,因为它控制在堆上实例对象的访问(例如,确保不使用空引用)。\n", - "\n", - "在设计模式中,代理模式和桥接模式并不是相互关联的,因为它们被赋予(我认为是任意的)不同的结构。桥接模式,特别是使用一个单独的实现,但这似乎对我来说是不必要的,除非你确定该实现是你无法控制的(当然有可能,但是如果您编写所有代码,那么没有理由不从单基类的优雅中受益)。此外,只要代理对象控制对其“前置”对象的访问,代模式理就不需要为其实现使用相同的基类。不管具体情况如何,在代理模式和桥接模式中,代理对象都将方法调用传递给具体实现对象。\n", - "\n", - "**状态机**\n", - "\n", - "桥接模式允许程序员更改实现,状态机利用一个结构来自动地将实现更改到下一个。当前实现表示系统所处的状态,系统在不同状态下的行为不同(因为它使用桥接模式)。基本上,这是一个利用对象的“状态机”。将系统从一种状态移动到另一种状态的代码通常是模板方法模式,如下例所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/state/StateMachineDemo.java\n", - "// The StateMachine pattern and Template method\n", - "// {java patterns.state.StateMachineDemo}\n", - "package patterns.state;\n", - "\n", - "import onjava.Nap;\n", - "\n", - "interface State {\n", - " void run();\n", - "}\n", - "\n", - "abstract class StateMachine {\n", - " protected State currentState;\n", - "\n", - " Nap(0.5);\n", - "System.out.println(\"Washing\"); new\n", - "\n", - " protected abstract boolean changeState();\n", - "\n", - " // Template method:\n", - " protected final void runAll() {\n", - " while (changeState()) // Customizable\n", - " currentState.run();\n", - " }\n", - "}\n", - "\n", - "// A different subclass for each state:\n", - "class Wash implements State {\n", - " @Override\n", - " public void run() {\n", - " }\n", - "}\n", - "\n", - "class Spin implements State {\n", - " @Override\n", - " public void run() {\n", - " System.out.println(\"Spinning\");\n", - " new Nap(0.5);\n", - " }\n", - "}\n", - "\n", - "class Rinse implements State {\n", - " @Override\n", - " public void run() {\n", - " System.out.println(\"Rinsing\");\n", - " new Nap(0.5);\n", - " }\n", - "}\n", - "\n", - "class Washer extends StateMachine {\n", - " private int i = 0;\n", - "\n", - " // The state table:\n", - " private State[] states = {new Wash(), new Spin(), new Rinse(), new Spin(),};\n", - "\n", - " Washer() {\n", - " runAll();\n", - " }\n", - "\n", - " @Override\n", - " public boolean changeState() {\n", - " if (i < states.length) {\n", - " // Change the state by setting the\n", - " // surrogate reference to a new object:\n", - " currentState = states[i++];\n", - " return true;\n", - " } else return false;\n", - " }\n", - "}\n", - "\n", - "public class StateMachineDemo {\n", - " public static void main(String[] args) {\n", - " new Washer();\n", - " }\n", - "}\n", - "/*\n", - "Output:\n", - "Washing\n", - "Spinning\n", - "Rinsing\n", - "Spinning\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这里,控制状态的类(本例中是状态机)负责决定下一个状态。然而,状态对象本身也可以决定下一步移动到什么状态,通常基于系统的某种输入。这是更灵活的解决方案。\n", - "\n", - "\n", - "\n", - "## 工厂模式\n", - "\n", - "当你发现必须将新类型添加到系统中时,合理的第一步是使用多态性为这些新类型创建一个通用接口。这会将你系统中的其余代码与要添加的特定类型的信息分开,使得可以在不改变现有代码的情况下添加新类型……或者看起来如此。起初,在这种设计中,似乎你必须更改代码的唯一地方就是你继承新类型的地方,但这并不是完全正确的。 你仍然必须创建新类型的对象,并且在创建时必须指定要使用的确切构造器。因此,如果创建对象的代码分布在整个应用程序中,那么在添加新类型时,你将遇到相同的问题——你仍然必须追查你代码中新类型碍事的所有地方。恰好是类型的创建碍事,而不是类型的使用(通过多态处理),但是效果是一样的:添加新类型可能会引起问题。\n", - "\n", - "解决方案是强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。\n", - "\n", - "由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。\n", - "\n", - "举例来说,让我们重新看一下**Shape**系统。 首先,我们需要一个用于所有示例的基本框架。 如果无法创建**Shape**对象,则需要抛出一个合适的异常:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/BadShapeCreation.java package patterns.shapes;\n", - "public class BadShapeCreation extends RuntimeException {\n", - " public BadShapeCreation(String msg) {\n", - " super(msg);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "接下来,是一个**Shape**基类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/Shape.java\n", - "package patterns.shapes;\n", - "public class Shape {\n", - "\tprivate static int counter = 0;\n", - " private int id = counter++;\n", - " @Override\n", - " public String toString(){\n", - " return getClass().getSimpleName() + \"[\" + id + \"]\";\n", - " }\n", - " public void draw() {\n", - " System.out.println(this + \" draw\");\n", - " }\n", - " public void erase() {\n", - " System.out.println(this + \" erase\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "该类自动为每一个**Shape**对象创建一个唯一的`id`。\n", - "\n", - "`toString()`使用运行期信息来发现特定的**Shape**子类的名字。\n", - "\n", - "现在我们能很快创建一些**Shape**子类了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/Circle.java\n", - "package patterns.shapes;\n", - "public class Circle extends Shape {}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/Square.java\n", - "package patterns.shapes;\n", - "public class Square extends Shape {}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/Triangle.java\n", - "package patterns.shapes;\n", - "public class Triangle extends Shape {} " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/FactoryMethod.java\n", - "package patterns.shapes;\n", - "public interface FactoryMethod {\n", - " Shape create(String type);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`create()`接收一个参数,这个参数使其决定要创建哪一种**Shape**对象,这里是`String`,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/shapes/FactoryTest.java\n", - "package patterns.shapes;\n", - "import java.util.stream.*;\n", - "public class FactoryTest {\n", - " public static void test(FactoryMethod factory) {\n", - " Stream.of(\"Circle\", \"Square\", \"Triangle\",\n", - " \"Square\", \"Circle\", \"Circle\", \"Triangle\")\n", - " .map(factory::create)\n", - " .peek(Shape::draw)\n", - " .peek(Shape::erase)\n", - " .count(); // Terminal operation\n", - " }\n", - "} " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在主函数`main()`里,要记住除非你在最后使用了一个终结操作,否则**Stream**不会做任何事情。在这里,`count()`的值被丢弃了。\n", - "\n", - "创建工厂的一种方法是显式创建每种类型:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/ShapeFactory1.java\n", - "// A simple static factory method\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import patterns.shapes.*;\n", - "public class ShapeFactory1 implements FactoryMethod {\n", - " public Shape create(String type) {\n", - " switch(type) {\n", - " case \"Circle\": return new Circle();\n", - " case \"Square\": return new Square();\n", - " case \"Triangle\": return new Triangle();\n", - " default: throw new BadShapeCreation(type);\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " FactoryTest.test(new ShapeFactory1());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Circle[0] draw\n", - "Circle[0] erase\n", - "Square[1] draw\n", - "Square[1] erase\n", - "Triangle[2] draw\n", - "Triangle[2] erase\n", - "Square[3] draw\n", - "Square[3] erase\n", - "Circle[4] draw\n", - "Circle[4] erase\n", - "Circle[5] draw\n", - "Circle[5] erase\n", - "Triangle[6] draw\n", - "Triangle[6] erase " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`create()`现在是添加新类型的Shape时系统中唯一需要更改的其他代码。\n", - "\n", - "### 动态工厂\n", - "\n", - "前面例子中的**静态**`create()`方法强制所有创建操作都集中在一个位置,因此这是添加新类型的**Shape**时唯一必须更改代码的地方。这当然是一个合理的解决方案,因为它把创建对象的过程限制在一个框内。但是,如果你在添加新类时无需修改任何内容,那就太好了。 以下版本使用反射在首次需要时将**Shape**的构造器动态加载到工厂列表中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/ShapeFactory2.java\n", - "import java.util.*;\n", - "import java.lang.reflect.*;\n", - "import java.util.stream.*;\n", - "import patterns.shapes.*;\n", - "public class ShapeFactory2 implements FactoryMethod {\n", - " Map factories = new HashMap<>();\n", - " static Constructor load(String id) {\n", - " System.out.println(\"loading \" + id);\n", - " try {\n", - " return Class.forName(\"patterns.shapes.\" + id)\n", - " .getConstructor();\n", - " } catch(ClassNotFoundException |\n", - " NoSuchMethodException e) {\n", - " throw new BadShapeCreation(id);\n", - " }\n", - " }\n", - " public Shape create(String id) {\n", - " try {\n", - " return (Shape)factories\n", - " .computeIfAbsent(id, ShapeFactory2::load)\n", - " .newInstance();\n", - " } catch(InstantiationException |\n", - " IllegalAccessException |\n", - " InvocationTargetException e) {\n", - " throw new BadShapeCreation(id);\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " FactoryTest.test(new ShapeFactory2());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "loading Circle\n", - "Circle[0] draw\n", - "Circle[0] erase\n", - "loading Square\n", - "Square[1] draw\n", - "Square[1] erase\n", - "loading Triangle\n", - "Triangle[2] draw\n", - "Triangle[2] erase\n", - "Square[3] draw\n", - "Square[3] erase\n", - "Circle[4] draw\n", - "Circle[4] erase\n", - "Circle[5] draw\n", - "Circle[5] erase\n", - "Triangle[6] draw\n", - "Triangle[6] erase" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "和之前一样,`create()`方法基于你传递给它的**String**参数生成新的**Shape**s,但是在这里,它是通过在**HashMap**中查找作为键的**String**来实现的。 返回的值是一个构造器,该构造器用于通过调用`newInstance()`创建新的**Shape**对象。\n", - "\n", - "然而,当你开始运行程序时,工厂的`map`为空。`create()`使用`map`的`computeIfAbsent()`方法来查找构造器(如果该构造器已存在于`map`中)。如果不存在则使用`load()`计算出该构造器,并将其插入到`map`中。 从输出中可以看到,每种特定类型的**Shape**都是在第一次请求时才加载的,然后只需要从`map`中检索它。\n", - "\n", - "### 多态工厂\n", - "\n", - "《设计模式》这本书强调指出,采用“工厂方法”模式的原因是可以从基本工厂中继承出不同类型的工厂。 再次修改示例,使工厂方法位于单独的类中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/ShapeFactory3.java\n", - "// Polymorphic factory methods\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import patterns.shapes.*;\n", - "interface PolymorphicFactory {\n", - " Shape create();\n", - "}\n", - "class RandomShapes implements Supplier {\n", - " private final PolymorphicFactory[] factories;\n", - " private Random rand = new Random(42);\n", - " RandomShapes(PolymorphicFactory... factories){\n", - " this.factories = factories;\n", - " }\n", - " public Shape get() {\n", - " return factories[ rand.nextInt(factories.length)].create();\n", - " }\n", - "}\n", - "public class ShapeFactory3 {\n", - " public static void main(String[] args) {\n", - " RandomShapes rs = new RandomShapes(\n", - " Circle::new,\n", - " Square::new,\n", - " Triangle::new);\n", - " Stream.generate(rs)\n", - " .limit(6)\n", - " .peek(Shape::draw)\n", - " .peek(Shape::erase)\n", - " .count();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Triangle[0] draw\n", - "Triangle[0] erase\n", - "Circle[1] draw\n", - "Circle[1] erase\n", - "Circle[2] draw\n", - "Circle[2] erase\n", - "Triangle[3] draw\n", - "Triangle[3] erase\n", - "Circle[4] draw\n", - "Circle[4] erase\n", - "Square[5] draw\n", - "Square[5] erase " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**RandomShapes**实现了**Supplier \\**,因此可用于通过`Stream.generate()`创建**Stream**。 它的构造器采用**PolymorphicFactory**对象的可变参数列表。 变量参数列表以数组形式出现,因此列表是以数组形式在内部存储的。`get()`方法随机获取此数组中一个对象的索引,并在结果上调用`create()`以产生新的**Shape**对象。 添加新类型的**Shape**时,**RandomShapes**构造器是唯一需要更改的地方。 请注意,此构造器需要**Supplier \\**。 我们传递给其**Shape**构造器的方法引用,该引用可满足**Supplier \\**约定,因为Java 8支持结构一致性。\n", - "\n", - "鉴于**ShapeFactory2.java**可能会抛出异常,使用此方法则没有任何异常——它在编译时完全确定。\n", - "\n", - "### 抽象工厂\n", - "\n", - "抽象工厂模式看起来像我们之前所见的工厂对象,但拥有不是一个工厂方法而是几个工厂方法, 每个工厂方法都会创建不同种类的对象。 这个想法是在创建工厂对象时,你决定如何使用该工厂创建的所有对象。 《设计模式》中提供的示例实现了跨各种图形用户界面(GUI)的可移植性:你创建一个适合你正在使用的GUI的工厂对象,然后从中请求菜单,按钮,滑块等等,它将自动为GUI创建适合该项目版本的组件。 因此,你可以将从一个GUI更改为另一个所产生的影响隔离限制在一处。 作为另一个示例,假设你正在创建一个通用游戏环境来支持不同类型的游戏。 使用抽象工厂看起来就像下文那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/abstractfactory/GameEnvironment.java\n", - "// An example of the Abstract Factory pattern\n", - "// {java patterns.abstractfactory.GameEnvironment}\n", - "package patterns.abstractfactory;\n", - "import java.util.function.*;\n", - "interface Obstacle {\n", - " void action();\n", - "}\n", - "\n", - "interface Player {\n", - " void interactWith(Obstacle o);\n", - "}\n", - "\n", - "class Kitty implements Player {\n", - " @Override\n", - " public void interactWith(Obstacle ob) {\n", - " System.out.print(\"Kitty has encountered a \");\n", - " ob.action();\n", - " }\n", - "}\n", - "\n", - "class KungFuGuy implements Player {\n", - " @Override\n", - " public void interactWith(Obstacle ob) {\n", - " System.out.print(\"KungFuGuy now battles a \");\n", - " ob.action();\n", - " }\n", - "}\n", - "\n", - "class Puzzle implements Obstacle {\n", - " @Override\n", - " public void action() {\n", - " System.out.println(\"Puzzle\");\n", - " }\n", - "}\n", - "\n", - "class NastyWeapon implements Obstacle {\n", - " @Override\n", - " public void action() {\n", - " System.out.println(\"NastyWeapon\");\n", - " }\n", - "}\n", - "\n", - "// The Abstract Factory:\n", - "class GameElementFactory {\n", - " Supplier player;\n", - " Supplier obstacle;\n", - "}\n", - "\n", - "// Concrete factories:\n", - "class KittiesAndPuzzles extends GameElementFactory {\n", - " KittiesAndPuzzles() {\n", - " player = Kitty::new;\n", - " obstacle = Puzzle::new;\n", - " }\n", - "}\n", - "\n", - "class KillAndDismember extends GameElementFactory {\n", - " KillAndDismember() {\n", - " player = KungFuGuy::new;\n", - " obstacle = NastyWeapon::new;\n", - " }\n", - "}\n", - "\n", - "public class GameEnvironment {\n", - " private Player p;\n", - " private Obstacle ob;\n", - "\n", - " public GameEnvironment(GameElementFactory factory) {\n", - " p = factory.player.get();\n", - " ob = factory.obstacle.get();\n", - " }\n", - " public void play() {\n", - " p.interactWith(ob);\n", - " }\n", - " public static void main(String[] args) {\n", - " GameElementFactory kp = new KittiesAndPuzzles(), kd = new KillAndDismember();\n", - " GameEnvironment g1 = new GameEnvironment(kp), g2 = new GameEnvironment(kd);\n", - " g1.play();\n", - " g2.play();\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Kitty has encountered a Puzzle\n", - "KungFuGuy now battles a NastyWeapon" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这种环境中,**Player**对象与**Obstacle**对象进行交互,但是根据你所玩游戏的类型,存在不同类型的玩家和障碍物。 你可以通过选择特定的**GameElementFactory**来确定游戏的类型,然后**GameEnvironment**控制游戏的设置和玩法。 在此示例中,设置和玩法非常简单,但是这些活动(初始条件和状态变化)可以决定游戏的大部分结果。 这里,**GameEnvironment**不是为继承而设计的,尽管这样做很有意义。 它还包含“双重调度”和“工厂方法”的示例,稍后将对这两个示例进行说明。\n", - "\n", - "\n", - "\n", - "## 函数对象\n", - "\n", - "一个 *函数对象* 封装了一个函数。其特点就是将被调用函数的选择与那个函数被调用的位置进行解耦。\n", - "\n", - "*《设计模式》* 中也提到了这个术语,但是没有使用。然而,*函数对象* 的话题却在那本书的很多模式中被反复论及。\n", - "\n", - "### 命令模式\n", - "\n", - "从最直观的角度来看,*命令模式* 就是一个函数对象:一个作为对象的函数。我们可以将 *函数对象* 作为参数传递给其他方法或者对象,来执行特定的操作。\n", - "\n", - "在Java 8之前,想要产生单个函数的效果,我们必须明确将方法包含在对象中,而这需要太多的仪式了。而利用Java 8的lambda特性, *命令模式* 的实现将是微不足道的。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/CommandPattern.java\n", - "import java.util.*;\n", - "\n", - "public class CommandPattern {\n", - " public static void main(String[] args) {\n", - " List macro = Arrays.asList(\n", - " () -> System.out.print(\"Hello \"),\n", - " () -> System.out.print(\"World! \"),\n", - " () -> System.out.print(\"I'm the command pattern!\")\n", - " );\n", - " macro.forEach(Runnable::run);\n", - " }\n", - "}\n", - "/* Output:\n", - "Hello World! I'm the command pattern!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "*命令模式* 的主要特点是允许向一个方法或者对象传递一个想要的动作。在上面的例子中,这个对象就是 **macro** ,而 *命令模式* 提供了将一系列需要一起执行的动作集进行排队的方法。在这里,*命令模式* 允许我们动态的创建新的行为,通常情况下我们需要编写新的代码才能完成这个功能,而在上面的例子中,我们可以通过解释运行一个脚本来完成这个功能(如果需要实现的东西很复杂请参考解释器模式)。\n", - "\n", - "*《设计模式》* 认为“命令模式是回调的面向对象的替代品”。尽管如此,我认为\"back\"(回来)这个词是callback(回调)这一概念的基本要素。也就是说,我认为回调(callback)实际上是返回到回调的创建者所在的位置。另一方面,对于 *命令* 对象,通常只需创建它并将其交给某种方法或对象,而不是自始至终以其他方式联系命令对象。不管怎样,这就是我对它的看法。在本章的后面内容中,我将会把一组设计模式放在“回调”的标题下面。\n", - "\n", - "### 策略模式\n", - "\n", - "*策略模式* 看起来像是从同一个基类继承而来的一系列 *命令* 类。但是仔细查看 *命令模式*,你就会发现它也具有同样的结构:一系列分层次的 *函数对象*。不同之处在于,这些函数对象的用法和策略模式不同。就像前面的 `io/DirList.java` 那个例子,使用 *命令* 是为了解决特定问题 -- 从一个列表中选择文件。“不变的部分”是被调用的那个方法,而变化的部分被分离出来放到 *函数对象* 中。我认为 *命令模式* 在编码阶段提供了灵活性,而 *策略模式* 的灵活性在运行时才会体现出来。尽管如此,这种区别却是非常模糊的。\n", - "\n", - "另外,*策略模式* 还可以添加一个“上下文(context)”,这个上下文(context)可以是一个代理类(surrogate class),用来控制对某个特定 *策略* 对象的选择和使用。就像 *桥接模式* 一样!下面我们来一探究竟:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/strategy/StrategyPattern.java\n", - "// {java patterns.strategy.StrategyPattern}\n", - "package patterns.strategy;\n", - "import java.util.function.*;\n", - "import java.util.*;\n", - "\n", - "// The common strategy base type:\n", - "class FindMinima {\n", - " Function, List> algorithm;\n", - "}\n", - "\n", - "// The various strategies:\n", - "class LeastSquares extends FindMinima {\n", - " LeastSquares() {\n", - " // Line is a sequence of points (Dummy data):\n", - " algorithm = (line) -> Arrays.asList(1.1, 2.2);\n", - " }\n", - "}\n", - "\n", - "class Perturbation extends FindMinima {\n", - " Perturbation() {\n", - " algorithm = (line) -> Arrays.asList(3.3, 4.4);\n", - " }\n", - "}\n", - "\n", - "class Bisection extends FindMinima {\n", - " Bisection() {\n", - " algorithm = (line) -> Arrays.asList(5.5, 6.6);\n", - " }\n", - "}\n", - "\n", - "// The \"Context\" controls the strategy:\n", - "class MinimaSolver {\n", - " private FindMinima strategy;\n", - " MinimaSolver(FindMinima strat) {\n", - " strategy = strat;\n", - " }\n", - " List minima(List line) {\n", - " return strategy.algorithm.apply(line);\n", - " }\n", - " void changeAlgorithm(FindMinima newAlgorithm) {\n", - " strategy = newAlgorithm;\n", - " }\n", - "}\n", - "\n", - "public class StrategyPattern {\n", - " public static void main(String[] args) {\n", - " MinimaSolver solver = \n", - " new MinimaSolver(new LeastSquares());\n", - " List line = Arrays.asList(\n", - " 1.0, 2.0, 1.0, 2.0, -1.0,\n", - " 3.0, 4.0, 5.0, 4.0 );\n", - " System.out.println(solver.minima(line)); \n", - " solver.changeAlgorithm(new Bisection()); \n", - " System.out.println(solver.minima(line));\n", - " }\n", - "}\n", - "/* Output:\n", - "[1.1, 2.2]\n", - "[5.5, 6.6]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`MinimaSolver` 中的 `changeAlgorithm()` 方法将一个不同的策略插入到了 `私有` 域 `strategy` 中,这使得在调用 `minima()` 方法时,可以使用新的策略。\n", - "\n", - "我们可以通过将上下文注入到 `FindMinima` 中来简化我们的解决方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/strategy/StrategyPattern2.java // {java patterns.strategy.StrategyPattern2}\n", - "package patterns.strategy;\n", - "import java.util.function.*;\n", - "import java.util.*;\n", - "\n", - "// \"Context\" is now incorporated:\n", - "class FindMinima2 {\n", - " Function, List> algorithm;\n", - " FindMinima2() { leastSquares(); } // default\n", - " // The various strategies:\n", - " void leastSquares() {\n", - " algorithm = (line) -> Arrays.asList(1.1, 2.2);\n", - " }\n", - " void perturbation() {\n", - " algorithm = (line) -> Arrays.asList(3.3, 4.4);\n", - " }\n", - " void bisection() {\n", - " algorithm = (line) -> Arrays.asList(5.5, 6.6);\n", - " }\n", - " List minima(List line) {\n", - " return algorithm.apply(line);\n", - " }\n", - "}\n", - "\n", - "public class StrategyPattern2 {\n", - " public static void main(String[] args) {\n", - " FindMinima2 solver = new FindMinima2();\n", - " List line = Arrays.asList(\n", - " 1.0, 2.0, 1.0, 2.0, -1.0,\n", - " 3.0, 4.0, 5.0, 4.0 );\n", - " System.out.println(solver.minima(line));\n", - " solver.bisection();\n", - " System.out.println(solver.minima(line));\n", - " }\n", - "}\n", - "/* Output:\n", - "[1.1, 2.2]\n", - "[5.5, 6.6]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`FindMinima2` 封装了不同的算法,也包含了“上下文”(Context),所以它便可以在一个单独的类中控制算法的选择了。\n", - "\n", - "### 责任链模式\n", - "\n", - "*责任链模式* 也许可以被看作一个使用了 *策略* 对象的“递归的动态一般化”。此时我们进行一次调用,在一个链序列中的每个策略都试图满足这个调用。这个过程直到有一个策略成功满足该调用或者到达链序列的末尾才结束。在递归方法中,一个方法将反复调用它自身直至达到某个终止条件;使用责任链,一个方法会调用相同的基类方法(拥有不同的实现),这个基类方法将会调用基类方法的其他实现,如此反复直至达到某个终止条件。\n", - "\n", - "除了调用某个方法来满足某个请求以外,链中的多个方法都有机会满足这个请求,因此它有点专家系统的意味。由于责任链实际上就是一个链表,它能够动态创建,因此它可以看作是一个更一般的动态构建的 `switch` 语句。\n", - "\n", - "在上面的 `StrategyPattern.java` 例子中,我们可能想自动发现一个解决方法。而 *责任链* 就可以达到这个目的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/chain/ChainOfResponsibility.java\n", - "// Using the Functional interface\n", - "// {java patterns.chain.ChainOfResponsibility}\n", - "package patterns.chain;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "\n", - "class Result {\n", - " boolean success;\n", - " List line;\n", - " Result(List data) {\n", - " success = true;\n", - " line = data;\n", - " }\n", - " Result() {\n", - " success = false;\n", - " line = Collections.emptyList();\n", - " }\n", - "}\n", - "\n", - "class Fail extends Result {}\n", - "\n", - "interface Algorithm {\n", - " Result algorithm(List line);\n", - "}\n", - "\n", - "class FindMinima {\n", - " public static Result leastSquares(List line) {\n", - " System.out.println(\"LeastSquares.algorithm\");\n", - " boolean weSucceed = false;\n", - " if(weSucceed) // Actual test/calculation here\n", - " return new Result(Arrays.asList(1.1, 2.2));\n", - " else // Try the next one in the chain:\n", - " return new Fail();\n", - " }\n", - " public static Result perturbation(List line) {\n", - " System.out.println(\"Perturbation.algorithm\");\n", - " boolean weSucceed = false;\n", - " if(weSucceed) // Actual test/calculation here\n", - " return new Result(Arrays.asList(3.3, 4.4));\n", - " else\n", - " return new Fail();\n", - " }\n", - " public static Result bisection(List line) {\n", - " System.out.println(\"Bisection.algorithm\");\n", - " boolean weSucceed = true;\n", - " if(weSucceed) // Actual test/calculation here\n", - " return new Result(Arrays.asList(5.5, 6.6));\n", - " else\n", - " return new Fail();\n", - " }\n", - " static List, Result>>\n", - " algorithms = Arrays.asList(\n", - " FindMinima::leastSquares,\n", - " FindMinima::perturbation,\n", - " FindMinima::bisection\n", - " );\n", - " public static Result minima(List line) {\n", - " for(Function, Result> alg :\n", - " algorithms) {\n", - " Result result = alg.apply(line);\n", - " if(result.success)\n", - " return result;\n", - " }\n", - " return new Fail();\n", - " }\n", - "}\n", - "\n", - "public class ChainOfResponsibility {\n", - " public static void main(String[] args) {\n", - " FindMinima solver = new FindMinima();\n", - " List line = Arrays.asList(\n", - " 1.0, 2.0, 1.0, 2.0, -1.0,\n", - " 3.0, 4.0, 5.0, 4.0);\n", - " Result result = solver.minima(line);\n", - " if(result.success)\n", - " System.out.println(result.line);\n", - " else\n", - " System.out.println(\"No algorithm found\");\n", - " }\n", - "}\n", - "/* Output:\n", - "LeastSquares.algorithm\n", - "Perturbation.algorithm\n", - "Bisection.algorithm\n", - "[5.5, 6.6]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们从定义一个 `Result` 类开始,这个类包含一个 `success` 标志,因此接收者就可以知道算法是否成功执行,而 `line` 变量保存了真实的数据。当算法执行失败时, `Fail` 类可以作为返回值。要注意的是,当算法执行失败时,返回一个 `Result` 对象要比抛出一个异常更加合适,因为我们有时可能并不打算解决这个问题,而是希望程序继续执行下去。\n", - "\n", - "每一个 `Algorithm` 接口的实现,都实现了不同的 `algorithm()` 方法。在 `FindMinama` 中,将会创建一个算法的列表(这就是所谓的“链”),而 `minima()` 方法只是遍历这个列表,然后找到能够成功执行的算法而已。\n", - "\n", - "\n", - "## 改变接口\n", - "\n", - "有时候我们需要解决的问题很简单,仅仅是“我没有需要的接口”而已。有两种设计模式用来解决这个问题:*适配器模式* 接受一种类型并且提供一个对其他类型的接口。*外观模式* 为一组类创建了一个接口,这样做只是为了提供一种更方便的方法来处理库或资源。\n", - "\n", - "### 适配器模式(Adapter)\n", - "\n", - "当我们手头有某个类,而我们需要的却是另外一个类,我们就可以通过 *适配器模式* 来解决问题。唯一需要做的就是产生出我们需要的那个类,有许多种方法可以完成这种适配。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/adapt/Adapter.java\n", - "// Variations on the Adapter pattern\n", - "// {java patterns.adapt.Adapter}\n", - "package patterns.adapt;\n", - "\n", - "class WhatIHave {\n", - " public void g() {}\n", - " public void h() {}\n", - "}\n", - "\n", - "interface WhatIWant {\n", - " void f();\n", - "}\n", - "\n", - "class ProxyAdapter implements WhatIWant {\n", - " WhatIHave whatIHave;\n", - " ProxyAdapter(WhatIHave wih) {\n", - " whatIHave = wih;\n", - " }\n", - " @Override\n", - " public void f() {\n", - " // Implement behavior using\n", - " // methods in WhatIHave:\n", - " whatIHave.g();\n", - " whatIHave.h();\n", - " }\n", - "}\n", - "\n", - "class WhatIUse {\n", - " public void op(WhatIWant wiw) {\n", - " wiw.f();\n", - " }\n", - "}\n", - "\n", - "// Approach 2: build adapter use into op():\n", - "class WhatIUse2 extends WhatIUse {\n", - " public void op(WhatIHave wih) {\n", - " new ProxyAdapter(wih).f();\n", - " }\n", - "}\n", - "\n", - "// Approach 3: build adapter into WhatIHave:\n", - "class WhatIHave2 extends WhatIHave implements WhatIWant {\n", - " @Override\n", - " public void f() {\n", - " g();\n", - " h();\n", - " }\n", - "}\n", - "\n", - "// Approach 4: use an inner class:\n", - "class WhatIHave3 extends WhatIHave {\n", - " private class InnerAdapter implements WhatIWant {\n", - " @Override\n", - " public void f() {\n", - " g();\n", - " h();\n", - " }\n", - " }\n", - " public WhatIWant whatIWant() {\n", - " return new InnerAdapter();\n", - " }\n", - "}\n", - "\n", - "public class Adapter {\n", - " public static void main(String[] args) {\n", - " WhatIUse whatIUse = new WhatIUse();\n", - " WhatIHave whatIHave = new WhatIHave();\n", - " WhatIWant adapt= new ProxyAdapter(whatIHave);\n", - " whatIUse.op(adapt);\n", - " // Approach 2:\n", - " WhatIUse2 whatIUse2 = new WhatIUse2();\n", - " whatIUse2.op(whatIHave);\n", - " // Approach 3:\n", - " WhatIHave2 whatIHave2 = new WhatIHave2();\n", - " whatIUse.op(whatIHave2);\n", - " // Approach 4:\n", - " WhatIHave3 whatIHave3 = new WhatIHave3();\n", - " whatIUse.op(whatIHave3.whatIWant());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我想冒昧的借用一下术语“proxy”(代理),因为在 *《设计模式》* 里,他们坚持认为一个代理(proxy)必须拥有和它所代理的对象一模一样的接口。但是,如果把这两个词一起使用,叫做“代理适配器(proxy adapter)”,似乎更合理一些。\n", - "\n", - "### 外观模式(Façade)\n", - "\n", - "当我想方设法试图将需求初步(first-cut)转化成对象的时候,通常我使用的原则是:\n", - "\n", - ">“把所有丑陋的东西都隐藏到对象里去”。\n", - "\n", - "基本上说,*外观模式* 干的就是这个事情。如果我们有一堆让人头晕的类以及交互(Interactions),而它们又不是客户端程序员必须了解的,那我们就可以为客户端程序员创建一个接口只提供那些必要的功能。\n", - "\n", - "外观模式经常被实现为一个符合单例模式(Singleton)的抽象工厂(abstract factory)。当然,你可以通过创建包含 **静态** 工厂方法(static factory methods)的类来达到上述效果。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// patterns/Facade.java\n", - "\n", - "class A { A(int x) {} }\n", - "\n", - "class B { B(long x) {} }\n", - "\n", - "class C { C(double x) {} }\n", - "\n", - "// Other classes that aren't exposed by the\n", - "// facade go here ...\n", - "public class Facade {\n", - " static A makeA(int x) { return new A(x); }\n", - " static B makeB(long x) { return new B(x); }\n", - " static C makeC(double x) { return new C(x); }\n", - " public static void main(String[] args) {\n", - " // The client programmer gets the objects\n", - " // by calling the static methods:\n", - " A a = Facade.makeA(1);\n", - " B b = Facade.makeB(1);\n", - " C c = Facade.makeC(1.0);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "《设计模式》给出的例子并不是真正的 *外观模式* ,而仅仅是一个类使用了其他的类而已。\n", - "\n", - "#### 包(Package)作为外观模式的变体\n", - "\n", - "我感觉,*外观模式* 更倾向于“过程式的(procedural)”,也就是非面向对象的(non-object-oriented):我们是通过调用某些函数才得到对象。它和抽象工厂(Abstract factory)到底有多大差别呢?*外观模式* 关键的一点是隐藏某个库的一部分类(以及它们的交互),使它们对于客户端程序员不可见,这样那些类的接口就更加简练和易于理解了。\n", - "\n", - "其实,这也正是 Java 的 packaging(包)的功能所完成的事情:在库以外,我们只能创建和使用被声明为公共(public)的那些类;所有非公共(non-public)的类只能被同一 package 的类使用。看起来,*外观模式* 似乎是 Java 内嵌的一个功能。\n", - "\n", - "公平起见,*《设计模式》* 主要是写给 C++ 读者的。尽管 C++ 有命名空间(namespaces)机制来防止全局变量和类名称之间的冲突,但它并没有提供类隐藏的机制,而在 Java 里我们可以通过声明 non-public 类来实现这一点。我认为,大多数情况下 Java 的 package 功能就足以解决针对 *外观模式* 的问题了。\n", - "\n", - "\n", - "## 解释器:运行时的弹性\n", - "\n", - "如果程序的用户需要更好的运行时弹性,例如创建脚本来增加需要的系统功能,你就能使用解释器设计模式。这个模式下,你可以创建一个语言解释器并将它嵌入你的程序内。\n", - "\n", - "在开发程序的过程中,设计自己的语言并为它构建一个解释器是一件让人分心且耗时的事。最好的解决方案就是复用代码:使用一个已经构建好并被调试过的解释器。Python 语言可以免费地嵌入营利性的应用中而不需要任何的协议许可、授权费或者是任何的声明。此外,有一个完全使用 Java 字节码实现的 Python 版本(叫做 Jython), 能够轻易地合并到 Java 程序中。Python 是一门非常易学习的脚本语言,代码的读写很有逻辑性。它支持函数与对象,有大量的可用库,并且可运行在所有的平台上。你可以在 [www.Python.org](https://www.python.org/) 上下载 Python 并了解更多信息。\n", - "\n", - "\n", - "## 回调\n", - "\n", - "\n", - "\n", - "## 多次调度\n", - "\n", - "\n", - "\n", - "## 模式重构\n", - "\n", - "\n", - "\n", - "## 抽象用法\n", - "\n", - "\n", - "\n", - "## 多次派遣\n", - "\n", - "\n", - "\n", - "## 访问者模式\n", - "\n", - "\n", - "\n", - "## RTTI的优劣\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Becoming-a-Programmer.ipynb b/jupyter/Appendix-Becoming-a-Programmer.ipynb deleted file mode 100644 index 25b35c24..00000000 --- a/jupyter/Appendix-Becoming-a-Programmer.ipynb +++ /dev/null @@ -1,132 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:成为一名程序员\n", - "\n", - ">我分别于2003,2006,2007和2009年撰写的博客文章混搭\n", - "\n", - "\n", - "## 如何开始\n", - "\n", - "这是一条相当漫长和曲折的道路。我在高一学代数时(1971年),有个非常古怪的老师有一台计算机,还弄到了一台配有一个300波特的音频电话耦合器的ASR-33电传打字机,我学会了如何执行命令并得到响应,以及一个可以在高中区使用的HP-1000计算机上的帐户。我们能够创建和运行BASIC程序并将它们保存在打孔磁带上。我对此非常着迷,所以尽可能地把它带回家后在晚上写程序。我写了一个赛马模拟游戏--HOSRAC.BAS,用星号来代表马的移动,由于是在纸上打印输出,所以需要一点想象力。\n", - "\n", - "我的朋友丹尼尔(就是设计我的书封面的人)有一个兄弟,他有段时间通过向酒吧和餐馆提供弹球机来赚钱。他有一台投币式街机(老虎机),最早的《乓》游戏之一,我对此全然不知,到现在我还忍受不了这东西(现在我几乎不玩电脑游戏,这样看来我可能是个没有幽默的人,但似乎编程比玩电脑游戏更有趣、更具挑战性。)\n", - "\n", - "后来我在高中参与了摄影和新闻工作,在大学的第一年就主修新闻学。我觉得自己已经从学校学到了足够多的东西,又转修了物理学。后来我在加州大学欧文分校完成了物理学位,如果我当时选择了一个特定的工程领域,修了足够的工程课就能拿到双专业,但我试图走得更远一些,所以最后我获得的本科学位是 \"应用物理\"。作为一名本科生,我多多少少学习了一些可以自娱自乐,但又没有任何深度的计算机编程课程。我个人认为在这些课程细细熏陶下,帮我打下了一定的基础,但事实我理解的这些东西没有任何深度。我不知道计算机、编译器或解释器有什么区别(只是对编译器和解释器一点点的理解)。对我来说计算机是绝对可靠的,而且我从来没有想过在程序语言和操作系统中会有出现错误的可能。\n", - "\n", - "后来我去了在加州州立理工大学攻读研究生,主要有三点原因\n", - "\n", - "1. 我真的非常喜欢物理学这个领域\n", - "\n", - "2. 他们接受了我,甚至给了我一份教学工作和奖学金 \n", - "\n", - "3. 出乎意料的是他们给我的工作时间不止一个夏天\n", - "\n", - "而我完全没做好上班的准备。\n", - "\n", - "作为一名物理专业的学生,我学习的是太阳能发电系统,当时太阳能发电系统很大 (如果你的房子上装了太阳能或生意上是关于太阳能系统,加州就会给予税收抵免,因此也兴起很多生意),加州理工大学也承诺会在工程系开设相应的课程。然而因为学校没有提供必要的课程,要想获得在太阳能工程的学位得花好几年时间。所以我学习了研究生其他的工程课,包括介绍机械,太阳能,电气和电子工程。我上的课是非电气工程专业的电气工程导论。最常见的研究生工程课程是计算机工程专业,所以最后我拿了那个学位。我还上了艺术课,几门舞蹈课,还有一些计算机科学课程 (Pascal和数据结构),在计算机工程中,我终于弄清楚了处理器的工作流程,从那以后我一直带着一个处理器在身上。这些就是我学的计算机基础知识。\n", - "\n", - "刚开始工作的时候,凭借着一堆硬件和相对简单低水平的编程,做了一名计算机工程师。因为C语言似乎是理想的嵌入式系统语言,于是我开始自学,并慢慢开始了解更多关于编程语言的东西。我们在这家公司从源代码构建编译器,这让我大开眼界。 (想象一下一个编译器只是另一个软件的一部分!)\n", - "\n", - "当我去华盛顿大学海洋学院为Tom Keffer后来创建了“疯狗浪”)工作时,我们决定使用C++。我只有一本Stroustrup写的非初学者书可以参考,最终不得不通过检查C++预处理器生成的中间C代码来了解语言的功能。这个过程非常痛苦,但学习的效果很好。从那以后我就用相同的方式学习,因为它让我学习了如何剖析一种语言,并看到它本质的能力,与此同时开始有了批判性思维。 \n", - "\n", - "我并没有理解清楚所有的概念。只是在之后的日子里不断反复,我所知道的一切需要时间才能消化吸收。如果我现在能很容易地理解一个新概念,那只是因为它是我已经知道的积累概念的一个变种。在加州理工大学招收非计算机本科学历的计算机科学研究生项目中,学生们曾经说他们花了一年的时间才弄清楚他们对计算机的困惑(他们正在沉浸程序之中)。当人们学习计算机时,他们往往会对自己抱有不切实际的期望,通常是他们听说学计算机编程的好处,就希望在几周内找到一份高薪的工作。但是,最好的学习过程是先对计算机感兴趣,随着时间的推移,学习的越来越多,自然的就开始自学。\n", - "\n", - "这些就是我主要做的事,尽管我通过学计算机工程有还算扎实的基础,但我没上过编程课,而是通过自学。在此期间我也在不断地学习新事物,在这个行业里,不断学习是非常重要的一部分。\n", - "\n", - "\n", - "## 码农生涯\n", - "\n", - "我会定期收到有关职业建议的请求,所以我尝试在这里回答一下这个问题。\n", - "\n", - "人们提出的问题通常是错误的问题:“我应该学习 C++ 还是 Java ?”在本文中,我将尝试阐述我对选择计算机职业所涉及的真正问题的看法。\n", - "\n", - "请注意,我在这里并不是和那些已经知道自己使命的人聊(译者注:指计划成为程序员或者已经从业的程序员,暗指这里是讲给外行的小白的)。因为无论别人怎么说,你都要去做,因为它已经渗入你的血液,并且你将无法摆脱它。你已经知道答案了:你当然会学到 C++ ,Java ,shell 脚本,Python 和许多其他语言和技术。即使你只有14岁,你也已经知道其中几种语言。\n", - "\n", - "问我这个问题的人可能来自另一职业。也许他们来自 Web 开发等领域,他们已经发现 HTML 只是一种类似编程,他们想尝试构建更实质的内容。但是,我特别希望,如果你提出这个问题,你就已经意识到,要在计算机领域取得成功,你必须教自己如何学习,并且永不停止学习。\n", - "\n", - "随着我做的越来越多,在我看来,软件越发比其他任何东西都更像写作。而且我们还没有弄清怎样成为一个好的作家,我们只知道何时我们喜欢别人写的东西。这不是像一些工程那样,我们要做的只是将某些东西放到一端,然后转动曲柄。诱人的是将软件视为确定性的,这就是我们想要的,这就是我们不断推出工具来帮助我们实现所需行为的原因。但是我的经验不断表明事实是相反的:它更多地是关于人而不是过程,并且它在确定性机器上运行的事实变得越来越没有影响力(指运行环境受机器影响,与机器相关这个事实),就像海森堡原理(不确定性原理:不可能同时知道一个粒子的位置和它的速度)不会在人类规模上影响事物一样。\n", - "\n", - "在我青年时期,父亲是建造民居的,我偶尔会为他工作,大部分时间都从事艰苦的工作,有时还得悬挂石膏板。他和他的木匠会告诉我说,他们是为了我才把这些工作交给了我 —— 为了不让我从事这项工作。这确实是有效的。\n", - "\n", - "因此,我也可以用比喻说,建造软件就像盖房子一样。我们并不是指每个在房屋上工作的人都一样。有混凝土泥瓦匠,屋顶工,水管工,电工,石膏板工人,抹灰工,瓷砖铺砌工,普通劳工,粗木匠,精整木匠,当然还有总承包商。这些中的每一个都需要一套不同的技能,这需要花费不同的时间和精力 房屋建造也受制于繁荣和萧条的周期,例如编程。为了快速起步,你可能需要当普通劳工或石膏板工人工作,在那里你可以在没有太多学习曲线的情况下开始获得报酬。只要需求旺盛,你就可以稳定工作,而且如果没有足够的人来工作,你的薪水甚至可能会上涨。但是一旦经济低迷,木匠甚至总承包商就可以自己将石膏板挂起来。\n", - "\n", - "当 Internet 刚兴起时,你所要做的就是花一些时间学习 HTML ,就可以找到一份工作并赚到很多钱。但是,当情况恶化时,你很快就会发现需要的技能层次结构很深,HTML 程序员(例如劳工和石膏板工)排在第一位,而高技能的码农和木匠则被保留。\n", - "\n", - "我想在这里说的是:除非你准备致力于终身学习,否则请不要从事这项业务。有时,编程似乎是一份报酬丰厚,值得信赖的工作,但确保这一点的唯一方法是,始终使自己变得更有价值。\n", - "\n", - "当然,也可以找到例外。总会有一些人只学习一种语言,并且足够精通,甚至足够聪明,那么可以在不用多学很多其他知识的情况下继续工作。但是他们靠运气生存,最终很脆弱。为了减少自身的脆弱性,必须通过阅读,参加用户组,会议和研讨会来不断提高自己的能力。你在该领域的走得越深,你的价值就越大,这意味着你的工作前景更稳定,并且可以获得更高的薪水。\n", - "\n", - "另一种方法是从总体上看待该领域,并找到一个你能成为专家的点。例如,我的兄弟对软件感兴趣,并且涉足软件,但是他的业务是安装计算机,维修计算机和升级计算机。他一直都很细致,因此,当他安装或修理计算机时,你会知道计算机状态良好。不仅是软件,而且一直到电缆,电缆都整齐地捆扎在一起,并且不成束。他的工作多到做不完,而且他从不关心网络泡沫破灭。毋庸置疑,他是不可能失业的。\n", - "\n", - "我在大学待了很长时间,并以各种方式设法度过了难关。我甚至开始在加州大学洛杉矶分校攻读博士学位。这里的课程很短,我欣慰地说是因为我不再爱上大学了,而我在大学待了这么长时间的原因是因为我非常喜欢。但是我喜欢的通常是跑偏的东西。例如艺术,舞蹈课程,在大学报社工作,以及我参加的少数计算机编程课程(由于我是物理本科生和计算机工程专业的研究生,所以也算跑偏)。尽管我在学业上还算是出色的(具有讽刺意味的是,当时许多不接受我作为学生的大学现在都在课程中使用我的书),但我确实很享受大学生的生活,并且完成了博士学位。我可能会走上简单的道路,最终成为一名教授。\n", - "\n", - "但是事实证明,我从大学获得的最大价值一部分来自那些跑偏的课程,这些课程使我的思维超出了“我们已经知道的东西”。我认为在计算机领域尤其如此,因为你总是通过编程来实现其他目标,而你对该目标越了解,你的表现就会越好(我学习了一些欧洲研究生课程,这些课程要求结合其他一些专业研究计算,通过解决这个领域相关的问题,你就会形成一种新的理论体系并可以将它用在别处)。\n", - "\n", - "我还认为,不仅编程,多了解一些其它的知识,还可以大大提高你的解决问题的能力(就像了解一种以上的编程语言可以极大地提高你的编程能力一样)。在很多情况下,我遇到过仅接受过计算机科学训练的人,他们的思维似乎比其他背景(例如数学或物理学)的人更受限制,但其实这些人(数学或物理学领域的人)才更需要严格的思维。\n", - "\n", - "在我组织的一次会议上,主题之一是为理想的求职者提供一系列功能:\n", - "\n", - "- 将学习作为一种生活方式。例如,学习一种以上的语言;没有什么比学习另一种语言更能吸引你的眼球。\n", - "- 知道在哪里以及如何获得新知识。\n", - "- 研究现有技术。\n", - "- 我们是工具使用者,即要善于利用工具。\n", - "- 学习做最简单的事情。\n", - "- 了解业务(阅读杂志。从 *fast company*(国外一家商业杂志)开始,该公司的文章非常简短有趣。然后你就会知道是否要阅读其他的)\n", - "- 应对错误负责。 “我用着没事”是不可接受的策略。查找自己的错误。\n", - "- 成为领导者:那些沟通和鼓舞别人的人。\n", - "- 你在为谁服务?\n", - "- 没有正确的答案……但总是更好的方法。展示和讨论你的代码,不要有情感上的依恋。你不是你的代码。\n", - "- 这是通往完美的渐进旅程。\n", - "\n", - "承担一切可能的风险,最好的风险是那些可怕的风险,但是在尝试时你会比想象中的更加活跃。最好不要刻意去预测某个特定的结果,因为如果你过于重视某个结果,就会经常错过真正的可能性。应该“让我们做一点实验,看看会把我们带到哪里”。这些实验是我最好的冒险。\n", - "\n", - "有些人对这个答案感到失望,然后回答“是的,这都是非常有趣和有用的。但是实际上,我应该学习什么? C++ 还是 Java ?”,以防这些问题,我将在这里重复一遍:我知道似乎所有的 1 和 0 都应该使一切具有确定性因此此类问题应该有一个简单的答案,但事实并非如此。这与做出选择并完成选择无关,这是有关持续学习和有时需要大胆的选择。相信我,这样你的生活会更加令人兴奋。\n", - "\n", - "### 延伸阅读\n", - "* [Teach Yourself Programming In Ten Years](http://norvig.com/21-days.html), by Peter Norvig.\n", - "* [How To Be A Programmer](http://samizdat.mines.edu/howto/HowToBeAProgrammer.html), by Robert Read.\n", - "* A [speech by Steve Jobs](http://news.stanford.edu/news/2005/june15/jobs-061505.html) to inspire a group of graduating college students.\n", - "* Kathy Sierra: [Does College Matter](https://headrush.typepad.com/creating_passionate_users/2005/07/does_college_ma.html)?\n", - "* Paul Graham [on College](http://www.paulgraham.com/college.html).\n", - "* Joel Spolsky: [Advice for Computer Science College Students](https://www.joelonsoftware.com/2005/01/02/advice-for-computer-science-college-students/).\n", - "* James Shore: [Five Design Skills Every Programmer Should Have](https://www.jamesshore.com/Blog/Five-Design-Skills.html).\n", - "* Steve Yegge: [The Truth About Interviewing](http://steve-yegge.blogspot.com/2006/03/truth-about-interviewing.html).\n", - "\n", - "\n", - "## 百分之五的神话\n", - "\n", - "\n", - "\n", - "## 重在动手\n", - "\n", - "\n", - "\n", - "## 像打字般编程\n", - "\n", - "\n", - "\n", - "## 做你喜欢的事\n", - "\n", - "*“1960年,一位研究人员对1500名商学院学生进行了访谈,并将他们分为两类:那些为了钱财来这里上学的人,1245人,以及那些打算利用学位做他们非常关心的事情的人,255人。二十年后,研究人员再次访谈了这些毕业生,发现其中有101位百万富翁,除了其中一位,所有百万富翁都来自追求他们喜欢做的事的那255人!”*\n", - "\n", - "“现在你可能觉得你对巴洛克时期的冰岛诗歌,或者蝴蝶收集,或者高尔夫,抑或是对社会正义的热情,会因为要养家糊口而让你和你喜欢做的事分道扬镳,并非一定要如此。弗拉基米尔·纳博科夫(Vladimir Nabokov)是本世纪最伟大的小说家之一,他对蝴蝶收藏的热情远远超过写作。事实上,他的第一个大学教学工作是关于鳞翅类昆虫。在过去40年里,对40万美国群众的研究表明,即使是部分的、零散的追求培养你的激情,也可以帮助你充分利用你目前的能力,激励你培养新的能力。”--摘自《The Other 90%》 Robert K.Cooper\n", - "\n", - "当然你可以看Po Bronson写的《 What Should I Do With My Life?》这本书,对这些想法进行更多的探索。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb b/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb deleted file mode 100644 index 54cefe16..00000000 --- a/jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb +++ /dev/null @@ -1,42 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:静态语言类型检查\n", - "> 这是一本我多年来撰写的经过编辑过的论文集,论文集试图将静态检查语言和动态语言之间的争论放到一个正确的角度。还有一个前言部分,描述了我最近对这个话题的思考和见解。\n", - "\n", - "\n", - "## 前言\n", - "\n", - "\n", - "\n", - "## 静态类型检查和测试\n", - "\n", - "\n", - "\n", - "## 如何提升打字\n", - "\n", - "\n", - "\n", - "## 生产力的成本\n", - "\n", - "\n", - "\n", - "## 静态和动态\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Collection-Topics.ipynb b/jupyter/Appendix-Collection-Topics.ipynb deleted file mode 100644 index 3c17a322..00000000 --- a/jupyter/Appendix-Collection-Topics.ipynb +++ /dev/null @@ -1,3861 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:集合主题\n", - "\n", - "> 本附录是一些比[第十二章 集合]()中介绍的更高级的内容。\n", - "\n", - "\n", - "## 示例数据\n", - "\n", - "这里创建一些样本数据用于集合示例。 以下数据将颜色名称与HTML颜色的RGB值相关联。请注意,每个键和值都是唯一的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/HTMLColors.java\n", - "// Sample data for collection examples\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class HTMLColors {\n", - " public static final Object[][] ARRAY = {\n", - " { 0xF0F8FF, \"AliceBlue\" },\n", - " { 0xFAEBD7, \"AntiqueWhite\" },\n", - " { 0x7FFFD4, \"Aquamarine\" },\n", - " { 0xF0FFFF, \"Azure\" },\n", - " { 0xF5F5DC, \"Beige\" },\n", - " { 0xFFE4C4, \"Bisque\" },\n", - " { 0x000000, \"Black\" },\n", - " { 0xFFEBCD, \"BlanchedAlmond\" },\n", - " { 0x0000FF, \"Blue\" },\n", - " { 0x8A2BE2, \"BlueViolet\" },\n", - " { 0xA52A2A, \"Brown\" },\n", - " { 0xDEB887, \"BurlyWood\" },\n", - " { 0x5F9EA0, \"CadetBlue\" },\n", - " { 0x7FFF00, \"Chartreuse\" },\n", - " { 0xD2691E, \"Chocolate\" },\n", - " { 0xFF7F50, \"Coral\" },\n", - " { 0x6495ED, \"CornflowerBlue\" },\n", - " { 0xFFF8DC, \"Cornsilk\" },\n", - " { 0xDC143C, \"Crimson\" },\n", - " { 0x00FFFF, \"Cyan\" },\n", - " { 0x00008B, \"DarkBlue\" },\n", - " { 0x008B8B, \"DarkCyan\" },\n", - " { 0xB8860B, \"DarkGoldenRod\" },\n", - " { 0xA9A9A9, \"DarkGray\" },\n", - " { 0x006400, \"DarkGreen\" },\n", - " { 0xBDB76B, \"DarkKhaki\" },\n", - " { 0x8B008B, \"DarkMagenta\" },\n", - " { 0x556B2F, \"DarkOliveGreen\" },\n", - " { 0xFF8C00, \"DarkOrange\" },\n", - " { 0x9932CC, \"DarkOrchid\" },\n", - " { 0x8B0000, \"DarkRed\" },\n", - " { 0xE9967A, \"DarkSalmon\" },\n", - " { 0x8FBC8F, \"DarkSeaGreen\" },\n", - " { 0x483D8B, \"DarkSlateBlue\" },\n", - " { 0x2F4F4F, \"DarkSlateGray\" },\n", - " { 0x00CED1, \"DarkTurquoise\" },\n", - " { 0x9400D3, \"DarkViolet\" },\n", - " { 0xFF1493, \"DeepPink\" },\n", - " { 0x00BFFF, \"DeepSkyBlue\" },\n", - " { 0x696969, \"DimGray\" },\n", - " { 0x1E90FF, \"DodgerBlue\" },\n", - " { 0xB22222, \"FireBrick\" },\n", - " { 0xFFFAF0, \"FloralWhite\" },\n", - " { 0x228B22, \"ForestGreen\" },\n", - " { 0xDCDCDC, \"Gainsboro\" },\n", - " { 0xF8F8FF, \"GhostWhite\" },\n", - " { 0xFFD700, \"Gold\" },\n", - " { 0xDAA520, \"GoldenRod\" },\n", - " { 0x808080, \"Gray\" },\n", - " { 0x008000, \"Green\" },\n", - " { 0xADFF2F, \"GreenYellow\" },\n", - " { 0xF0FFF0, \"HoneyDew\" },\n", - " { 0xFF69B4, \"HotPink\" },\n", - " { 0xCD5C5C, \"IndianRed\" },\n", - " { 0x4B0082, \"Indigo\" },\n", - " { 0xFFFFF0, \"Ivory\" },\n", - " { 0xF0E68C, \"Khaki\" },\n", - " { 0xE6E6FA, \"Lavender\" },\n", - " { 0xFFF0F5, \"LavenderBlush\" },\n", - " { 0x7CFC00, \"LawnGreen\" },\n", - " { 0xFFFACD, \"LemonChiffon\" },\n", - " { 0xADD8E6, \"LightBlue\" },\n", - " { 0xF08080, \"LightCoral\" },\n", - " { 0xE0FFFF, \"LightCyan\" },\n", - " { 0xFAFAD2, \"LightGoldenRodYellow\" },\n", - " { 0xD3D3D3, \"LightGray\" },\n", - " { 0x90EE90, \"LightGreen\" },\n", - " { 0xFFB6C1, \"LightPink\" },\n", - " { 0xFFA07A, \"LightSalmon\" },\n", - " { 0x20B2AA, \"LightSeaGreen\" },\n", - " { 0x87CEFA, \"LightSkyBlue\" },\n", - " { 0x778899, \"LightSlateGray\" },\n", - " { 0xB0C4DE, \"LightSteelBlue\" },\n", - " { 0xFFFFE0, \"LightYellow\" },\n", - " { 0x00FF00, \"Lime\" },\n", - " { 0x32CD32, \"LimeGreen\" },\n", - " { 0xFAF0E6, \"Linen\" },\n", - " { 0xFF00FF, \"Magenta\" },\n", - " { 0x800000, \"Maroon\" },\n", - " { 0x66CDAA, \"MediumAquaMarine\" },\n", - " { 0x0000CD, \"MediumBlue\" },\n", - " { 0xBA55D3, \"MediumOrchid\" },\n", - " { 0x9370DB, \"MediumPurple\" },\n", - " { 0x3CB371, \"MediumSeaGreen\" },\n", - " { 0x7B68EE, \"MediumSlateBlue\" },\n", - " { 0x00FA9A, \"MediumSpringGreen\" },\n", - " { 0x48D1CC, \"MediumTurquoise\" },\n", - " { 0xC71585, \"MediumVioletRed\" },\n", - " { 0x191970, \"MidnightBlue\" },\n", - " { 0xF5FFFA, \"MintCream\" },\n", - " { 0xFFE4E1, \"MistyRose\" },\n", - " { 0xFFE4B5, \"Moccasin\" },\n", - " { 0xFFDEAD, \"NavajoWhite\" },\n", - " { 0x000080, \"Navy\" },\n", - " { 0xFDF5E6, \"OldLace\" },\n", - " { 0x808000, \"Olive\" },\n", - " { 0x6B8E23, \"OliveDrab\" },\n", - " { 0xFFA500, \"Orange\" },\n", - " { 0xFF4500, \"OrangeRed\" },\n", - " { 0xDA70D6, \"Orchid\" },\n", - " { 0xEEE8AA, \"PaleGoldenRod\" },\n", - " { 0x98FB98, \"PaleGreen\" },\n", - " { 0xAFEEEE, \"PaleTurquoise\" },\n", - " { 0xDB7093, \"PaleVioletRed\" },\n", - " { 0xFFEFD5, \"PapayaWhip\" },\n", - " { 0xFFDAB9, \"PeachPuff\" },\n", - " { 0xCD853F, \"Peru\" },\n", - " { 0xFFC0CB, \"Pink\" },\n", - " { 0xDDA0DD, \"Plum\" },\n", - " { 0xB0E0E6, \"PowderBlue\" },\n", - " { 0x800080, \"Purple\" },\n", - " { 0xFF0000, \"Red\" },\n", - " { 0xBC8F8F, \"RosyBrown\" },\n", - " { 0x4169E1, \"RoyalBlue\" },\n", - " { 0x8B4513, \"SaddleBrown\" },\n", - " { 0xFA8072, \"Salmon\" },\n", - " { 0xF4A460, \"SandyBrown\" },\n", - " { 0x2E8B57, \"SeaGreen\" },\n", - " { 0xFFF5EE, \"SeaShell\" },\n", - " { 0xA0522D, \"Sienna\" },\n", - " { 0xC0C0C0, \"Silver\" },\n", - " { 0x87CEEB, \"SkyBlue\" },\n", - " { 0x6A5ACD, \"SlateBlue\" },\n", - " { 0x708090, \"SlateGray\" },\n", - " { 0xFFFAFA, \"Snow\" },\n", - " { 0x00FF7F, \"SpringGreen\" },\n", - " { 0x4682B4, \"SteelBlue\" },\n", - " { 0xD2B48C, \"Tan\" },\n", - " { 0x008080, \"Teal\" },\n", - " { 0xD8BFD8, \"Thistle\" },\n", - " { 0xFF6347, \"Tomato\" },\n", - " { 0x40E0D0, \"Turquoise\" },\n", - " { 0xEE82EE, \"Violet\" },\n", - " { 0xF5DEB3, \"Wheat\" },\n", - " { 0xFFFFFF, \"White\" },\n", - " { 0xF5F5F5, \"WhiteSmoke\" },\n", - " { 0xFFFF00, \"Yellow\" },\n", - " { 0x9ACD32, \"YellowGreen\" },\n", - " };\n", - " public static final Map MAP =\n", - " Arrays.stream(ARRAY)\n", - " .collect(Collectors.toMap(\n", - " element -> (Integer)element[0],\n", - " element -> (String)element[1],\n", - " (v1, v2) -> { // Merge function\n", - " throw new IllegalStateException();\n", - " },\n", - " LinkedHashMap::new\n", - " ));\n", - " // Inversion only works if values are unique:\n", - " public static Map\n", - " invert(Map map) {\n", - " return map.entrySet().stream()\n", - " .collect(Collectors.toMap(\n", - " Map.Entry::getValue,\n", - " Map.Entry::getKey,\n", - " (v1, v2) -> {\n", - " throw new IllegalStateException();\n", - " },\n", - " LinkedHashMap::new\n", - " ));\n", - " }\n", - " public static final Map\n", - " INVMAP = invert(MAP);\n", - " // Look up RGB value given a name:\n", - " public static Integer rgb(String colorName) {\n", - " return INVMAP.get(colorName);\n", - " }\n", - " public static final List LIST =\n", - " Arrays.stream(ARRAY)\n", - " .map(item -> (String)item[1])\n", - " .collect(Collectors.toList());\n", - " public static final List RGBLIST =\n", - " Arrays.stream(ARRAY)\n", - " .map(item -> (Integer)item[0])\n", - " .collect(Collectors.toList());\n", - " public static\n", - " void show(Map.Entry e) {\n", - " System.out.format(\n", - " \"0x%06X: %s%n\", e.getKey(), e.getValue());\n", - " }\n", - " public static void\n", - " show(Map m, int count) {\n", - " m.entrySet().stream()\n", - " .limit(count)\n", - " .forEach(e -> show(e));\n", - " }\n", - " public static void show(Map m) {\n", - " show(m, m.size());\n", - " }\n", - " public static\n", - " void show(Collection lst, int count) {\n", - " lst.stream()\n", - " .limit(count)\n", - " .forEach(System.out::println);\n", - " }\n", - " public static void show(Collection lst) {\n", - " show(lst, lst.size());\n", - " }\n", - " public static\n", - " void showrgb(Collection lst, int count) {\n", - " lst.stream()\n", - " .limit(count)\n", - " .forEach(n -> System.out.format(\"0x%06X%n\", n));\n", - " }\n", - " public static void showrgb(Collection lst) {\n", - " showrgb(lst, lst.size());\n", - " }\n", - " public static\n", - " void showInv(Map m, int count) {\n", - " m.entrySet().stream()\n", - " .limit(count)\n", - " .forEach(e ->\n", - " System.out.format(\n", - " \"%-20s 0x%06X%n\", e.getKey(), e.getValue()));\n", - " }\n", - " public static void showInv(Map m) {\n", - " showInv(m, m.size());\n", - " }\n", - " public static void border() {\n", - " System.out.println(\n", - " \"******************************\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**MAP** 是使用Streams([第十四章 流式编程]())创建的。 二维数组 **ARRAY** 作为流传输到 **Map** 中,但请注意我们不仅仅是使用简单版本的 `Collectors.toMap()` 。 那个版本生成一个 **HashMap** ,它使用散列函数来控制对键的排序。 为了保留原来的顺序,我们必须将键值对直接放入 **TreeMap** 中,这意味着我们需要使用更复杂的 `Collectors.toMap()` 版本。这需要两个函数从每个流元素中提取键和值,就像简单版本的`Collectors.toMap()` 一样。 然后它需要一个*合并函数*(merge function),它解决了与同一个键相关的两个值之间的冲突。这里的数据已经预先审查过,因此绝不会发生这种情况,如果有的话,这里会抛出异常。最后,传递生成所需类型的空map的函数,然后用流来填充它。\n", - "\n", - "`rgb()` 方法是一个便捷函数(convenience function),它接受颜色名称 **String** 参数并生成其数字RGB值。为此,我们需要一个反转版本的 **COLORS** ,它接受一个 **String**键并查找RGB的 **Integer** 值。 这是通过 `invert()` 方法实现的,如果任何 **COLORS** 值不唯一,则抛出异常。\n", - "\n", - "我们还创建包含所有名称的 **LIST** ,以及包含十六进制表示法的RGB值的 **RGBLIST** 。\n", - "\n", - "第一个 `show()` 方法接受一个 **Map.Entry** 并显示以十六进制表示的键,以便轻松地对原始 **ARRAY** 进行双重检查。 名称以 **show** 开头的每个方法都会重载两个版本,其中一个版本采用 **count** 参数来指示要显示的元素数量,第二个版本显示序列中的所有元素。\n", - "\n", - "这里是一个基本的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/HTMLColorTest.java\n", - "import static onjava.HTMLColors.*;\n", - "\n", - "public class HTMLColorTest {\n", - " static final int DISPLAY_SIZE = 20;\n", - " public static void main(String[] args) {\n", - " show(MAP, DISPLAY_SIZE);\n", - " border();\n", - " showInv(INVMAP, DISPLAY_SIZE);\n", - " border();\n", - " show(LIST, DISPLAY_SIZE);\n", - " border();\n", - " showrgb(RGBLIST, DISPLAY_SIZE);\n", - " }\n", - "}\n", - "/* Output:\n", - "0xF0F8FF: AliceBlue\n", - "0xFAEBD7: AntiqueWhite\n", - "0x7FFFD4: Aquamarine\n", - "0xF0FFFF: Azure\n", - "0xF5F5DC: Beige\n", - "0xFFE4C4: Bisque\n", - "0x000000: Black\n", - "0xFFEBCD: BlanchedAlmond\n", - "0x0000FF: Blue\n", - "0x8A2BE2: BlueViolet\n", - "0xA52A2A: Brown\n", - "0xDEB887: BurlyWood\n", - "0x5F9EA0: CadetBlue\n", - "0x7FFF00: Chartreuse\n", - "0xD2691E: Chocolate\n", - "0xFF7F50: Coral\n", - "0x6495ED: CornflowerBlue\n", - "0xFFF8DC: Cornsilk\n", - "0xDC143C: Crimson\n", - "0x00FFFF: Cyan\n", - "******************************\n", - "AliceBlue 0xF0F8FF\n", - "AntiqueWhite 0xFAEBD7\n", - "Aquamarine 0x7FFFD4\n", - "Azure 0xF0FFFF\n", - "Beige 0xF5F5DC\n", - "Bisque 0xFFE4C4\n", - "Black 0x000000\n", - "BlanchedAlmond 0xFFEBCD\n", - "Blue 0x0000FF\n", - "BlueViolet 0x8A2BE2\n", - "Brown 0xA52A2A\n", - "BurlyWood 0xDEB887\n", - "CadetBlue 0x5F9EA0\n", - "Chartreuse 0x7FFF00\n", - "Chocolate 0xD2691E\n", - "Coral 0xFF7F50\n", - "CornflowerBlue 0x6495ED\n", - "Cornsilk 0xFFF8DC\n", - "Crimson 0xDC143C\n", - "Cyan 0x00FFFF\n", - "******************************\n", - "AliceBlue\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "Beige\n", - "Bisque\n", - "Black\n", - "BlanchedAlmond\n", - "Blue\n", - "BlueViolet\n", - "Brown\n", - "BurlyWood\n", - "CadetBlue\n", - "Chartreuse\n", - "Chocolate\n", - "Coral\n", - "CornflowerBlue\n", - "Cornsilk\n", - "Crimson\n", - "Cyan\n", - "******************************\n", - "0xF0F8FF\n", - "0xFAEBD7\n", - "0x7FFFD4\n", - "0xF0FFFF\n", - "0xF5F5DC\n", - "0xFFE4C4\n", - "0x000000\n", - "0xFFEBCD\n", - "0x0000FF\n", - "0x8A2BE2\n", - "0xA52A2A\n", - "0xDEB887\n", - "0x5F9EA0\n", - "0x7FFF00\n", - "0xD2691E\n", - "0xFF7F50\n", - "0x6495ED\n", - "0xFFF8DC\n", - "0xDC143C\n", - "0x00FFFF\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,使用 **LinkedHashMap** 确实能够保留 **HTMLColors.ARRAY** 的顺序。\n", - "\n", - "\n", - "## List行为\n", - "\n", - "**Lists** 是存储和检索对象(次于数组)的最基本方法。基本列表操作包括:\n", - "\n", - "- `add()` 用于插入元素\n", - "- `get()` 用于随机访问元素\n", - "- `iterator()` 获取序列上的一个 **Iterator**\n", - "- `stream()` 生成元素的一个 **Stream**\n", - "\n", - "列表构造方法始终保留元素的添加顺序。\n", - "\n", - "以下示例中的方法各自涵盖了一组不同的行为:每个 **List** 可以执行的操作( `basicTest()` ),使用 **Iterator** ( `iterMotion()` )遍历序列,使用 **Iterator** ( `iterManipulation()` )更改内容,查看 **List** 操作( `testVisual()` )的效果,以及仅可用于 **LinkedLists** 的操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/ListOps.java\n", - "// Things you can do with Lists\n", - "import java.util.*;\n", - "import onjava.HTMLColors;\n", - "\n", - "public class ListOps {\n", - " // Create a short list for testing:\n", - " static final List LIST =\n", - " HTMLColors.LIST.subList(0, 10);\n", - " private static boolean b;\n", - " private static String s;\n", - " private static int i;\n", - " private static Iterator it;\n", - " private static ListIterator lit;\n", - " public static void basicTest(List a) {\n", - " a.add(1, \"x\"); // Add at location 1\n", - " a.add(\"x\"); // Add at end\n", - " // Add a collection:\n", - " a.addAll(LIST);\n", - " // Add a collection starting at location 3:\n", - " a.addAll(3, LIST);\n", - " b = a.contains(\"1\"); // Is it in there?\n", - " // Is the entire collection in there?\n", - " b = a.containsAll(LIST);\n", - " // Lists allow random access, which is cheap\n", - " // for ArrayList, expensive for LinkedList:\n", - " s = a.get(1); // Get (typed) object at location 1\n", - " i = a.indexOf(\"1\"); // Tell index of object\n", - " b = a.isEmpty(); // Any elements inside?\n", - " it = a.iterator(); // Ordinary Iterator\n", - " lit = a.listIterator(); // ListIterator\n", - " lit = a.listIterator(3); // Start at location 3\n", - " i = a.lastIndexOf(\"1\"); // Last match\n", - " a.remove(1); // Remove location 1\n", - " a.remove(\"3\"); // Remove this object\n", - " a.set(1, \"y\"); // Set location 1 to \"y\"\n", - " // Keep everything that's in the argument\n", - " // (the intersection of the two sets):\n", - " a.retainAll(LIST);\n", - " // Remove everything that's in the argument:\n", - " a.removeAll(LIST);\n", - " i = a.size(); // How big is it?\n", - " a.clear(); // Remove all elements\n", - " }\n", - " public static void iterMotion(List a) {\n", - " ListIterator it = a.listIterator();\n", - " b = it.hasNext();\n", - " b = it.hasPrevious();\n", - " s = it.next();\n", - " i = it.nextIndex();\n", - " s = it.previous();\n", - " i = it.previousIndex();\n", - " }\n", - " public static void iterManipulation(List a) {\n", - " ListIterator it = a.listIterator();\n", - " it.add(\"47\");\n", - " // Must move to an element after add():\n", - " it.next();\n", - " // Remove the element after the new one:\n", - " it.remove();\n", - " // Must move to an element after remove():\n", - " it.next();\n", - " // Change the element after the deleted one:\n", - " it.set(\"47\");\n", - " }\n", - " public static void testVisual(List a) {\n", - " System.out.println(a);\n", - " List b = LIST;\n", - " System.out.println(\"b = \" + b);\n", - " a.addAll(b);\n", - " a.addAll(b);\n", - " System.out.println(a);\n", - " // Insert, remove, and replace elements\n", - " // using a ListIterator:\n", - " ListIterator x =\n", - " a.listIterator(a.size()/2);\n", - " x.add(\"one\");\n", - " System.out.println(a);\n", - " System.out.println(x.next());\n", - " x.remove();\n", - " System.out.println(x.next());\n", - " x.set(\"47\");\n", - " System.out.println(a);\n", - " // Traverse the list backwards:\n", - " x = a.listIterator(a.size());\n", - " while(x.hasPrevious())\n", - " System.out.print(x.previous() + \" \");\n", - " System.out.println();\n", - " System.out.println(\"testVisual finished\");\n", - " }\n", - " // There are some things that only LinkedLists can do:\n", - " public static void testLinkedList() {\n", - " LinkedList ll = new LinkedList<>();\n", - " ll.addAll(LIST);\n", - " System.out.println(ll);\n", - " // Treat it like a stack, pushing:\n", - " ll.addFirst(\"one\");\n", - " ll.addFirst(\"two\");\n", - " System.out.println(ll);\n", - " // Like \"peeking\" at the top of a stack:\n", - " System.out.println(ll.getFirst());\n", - " // Like popping a stack:\n", - " System.out.println(ll.removeFirst());\n", - " System.out.println(ll.removeFirst());\n", - " // Treat it like a queue, pulling elements\n", - " // off the tail end:\n", - " System.out.println(ll.removeLast());\n", - " System.out.println(ll);\n", - " }\n", - " public static void main(String[] args) {\n", - " // Make and fill a new list each time:\n", - " basicTest(new LinkedList<>(LIST));\n", - " basicTest(new ArrayList<>(LIST));\n", - " iterMotion(new LinkedList<>(LIST));\n", - " iterMotion(new ArrayList<>(LIST));\n", - " iterManipulation(new LinkedList<>(LIST));\n", - " iterManipulation(new ArrayList<>(LIST));\n", - " testVisual(new LinkedList<>(LIST));\n", - " testLinkedList();\n", - " }\n", - "}\n", - "/* Output:\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "b = [AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", - "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", - "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", - "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", - "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "Bisque\n", - "Black\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet,\n", - "AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige, one,\n", - "47, BlanchedAlmond, Blue, BlueViolet, AliceBlue,\n", - "AntiqueWhite, Aquamarine, Azure, Beige, Bisque, Black,\n", - "BlanchedAlmond, Blue, BlueViolet]\n", - "BlueViolet Blue BlanchedAlmond Black Bisque Beige Azure\n", - "Aquamarine AntiqueWhite AliceBlue BlueViolet Blue\n", - "BlanchedAlmond 47 one Beige Azure Aquamarine\n", - "AntiqueWhite AliceBlue BlueViolet Blue BlanchedAlmond\n", - "Black Bisque Beige Azure Aquamarine AntiqueWhite\n", - "AliceBlue\n", - "testVisual finished\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "[two, one, AliceBlue, AntiqueWhite, Aquamarine, Azure,\n", - "Beige, Bisque, Black, BlanchedAlmond, Blue, BlueViolet]\n", - "two\n", - "two\n", - "one\n", - "BlueViolet\n", - "[AliceBlue, AntiqueWhite, Aquamarine, Azure, Beige,\n", - "Bisque, Black, BlanchedAlmond, Blue]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `basicTest()` 和 `iterMotion()` 中,方法调用是为了展示正确的语法,尽管获取了返回值,但不会使用它。在某些情况下,根本不会去获取返回值。在使用这些方法之前,请查看JDK文档中这些方法的完整用法。\n", - "\n", - "\n", - "## Set行为\n", - "\n", - "**Set** 的主要用处是测试成员身份,不过也可以将其用作删除重复元素的工具。如果不关心元素顺序或并发性, **HashSet** 总是最好的选择,因为它是专门为了快速查找而设计的(这里使用了在[附录:理解equals和hashCode方法]()章节中探讨的散列函数)。\n", - "\n", - "其它的 **Set** 实现产生不同的排序行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/SetOrder.java\n", - "import java.util.*;\n", - "import onjava.HTMLColors;\n", - "\n", - "public class SetOrder {\n", - " static String[] sets = {\n", - " \"java.util.HashSet\",\n", - " \"java.util.TreeSet\",\n", - " \"java.util.concurrent.ConcurrentSkipListSet\",\n", - " \"java.util.LinkedHashSet\",\n", - " \"java.util.concurrent.CopyOnWriteArraySet\",\n", - " };\n", - " static final List RLIST =\n", - " new ArrayList<>(HTMLColors.LIST);\n", - " static {\n", - " Collections.reverse(RLIST);\n", - " }\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " for(String type: sets) {\n", - " System.out.format(\"[-> %s <-]%n\",\n", - " type.substring(type.lastIndexOf('.') + 1));\n", - " @SuppressWarnings(\"unchecked\")\n", - " Set set = (Set)\n", - " Class.forName(type).newInstance();\n", - " set.addAll(RLIST);\n", - " set.stream()\n", - " .limit(10)\n", - " .forEach(System.out::println);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "[-> HashSet <-]\n", - "MediumOrchid\n", - "PaleGoldenRod\n", - "Sienna\n", - "LightSlateGray\n", - "DarkSeaGreen\n", - "Black\n", - "Gainsboro\n", - "Orange\n", - "LightCoral\n", - "DodgerBlue\n", - "[-> TreeSet <-]\n", - "AliceBlue\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "Beige\n", - "Bisque\n", - "Black\n", - "BlanchedAlmond\n", - "Blue\n", - "BlueViolet\n", - "[-> ConcurrentSkipListSet <-]\n", - "AliceBlue\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "Beige\n", - "Bisque\n", - "Black\n", - "BlanchedAlmond\n", - "Blue\n", - "BlueViolet\n", - "[-> LinkedHashSet <-]\n", - "YellowGreen\n", - "Yellow\n", - "WhiteSmoke\n", - "White\n", - "Wheat\n", - "Violet\n", - "Turquoise\n", - "Tomato\n", - "Thistle\n", - "Teal\n", - "[-> CopyOnWriteArraySet <-]\n", - "YellowGreen\n", - "Yellow\n", - "WhiteSmoke\n", - "White\n", - "Wheat\n", - "Violet\n", - "Turquoise\n", - "Tomato\n", - "Thistle\n", - "Teal\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里需要使用 **@SuppressWarnings(“unchecked”)** ,因为这里将一个 **String** (可能是任何东西)传递给了 `Class.forName(type).newInstance()` 。编译器并不能保证这是一次成功的操作。\n", - "\n", - "**RLIST** 是 **HTMLColors.LIST** 的反转版本。因为 `Collections.reverse()` 是通过修改参数来执行反向操作,而不是返回包含反向元素的新 **List** ,所以该调用在 **static** 块内执行。 **RLIST** 可以防止我们意外地认为 **Set** 对其结果进行了排序。\n", - "\n", - "**HashSet** 的输出结果似乎没有可辨别的顺序,因为它是基于散列函数的。 **TreeSet** 和 **ConcurrentSkipListSet** 都对它们的元素进行了排序,它们都实现了 **SortedSet** 接口来标识这个特点。因为实现该接口的 **Set** 按顺序排列,所以该接口还有一些其他的可用操作。 **LinkedHashSet** 和 **CopyOnWriteArraySet** 尽管没有用于标识的接口,但它们还是保留了元素的插入顺序。\n", - "\n", - "**ConcurrentSkipListSet** 和 **CopyOnWriteArraySet** 是线程安全的。\n", - "\n", - "在附录的最后,我们将了解在非 **HashSet** 实现的 **Set** 上添加额外排序的性能成本,以及不同实现中的任何其他功能的成本。\n", - "\n", - "\n", - "## 在Map中使用函数式操作\n", - "\n", - "与 **Collection** 接口一样,`forEach()` 也内置在 **Map** 接口中。但是如果想要执行任何其他的基本功能操作,比如 `map()` ,`flatMap()` ,`reduce()` 或 `filter()` 时,该怎么办? 查看 **Map** 接口发现并没有这些。\n", - "\n", - "可以通过 `entrySet()` 连接到这些方法,该方法会生成一个由 **Map.Entry** 对象组成的 **Set** 。这个 **Set** 包含 `stream()` 和 `parallelStream()` 方法。只需要记住一件事,这里正在使用的是 **Map.Entry** 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/FunctionalMap.java\n", - "// Functional operations on a Map\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import static onjava.HTMLColors.*;\n", - "\n", - "public class FunctionalMap {\n", - " public static void main(String[] args) {\n", - " MAP.entrySet().stream()\n", - " .map(Map.Entry::getValue)\n", - " .filter(v -> v.startsWith(\"Dark\"))\n", - " .map(v -> v.replaceFirst(\"Dark\", \"Hot\"))\n", - " .forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "HotBlue\n", - "HotCyan\n", - "HotGoldenRod\n", - "HotGray\n", - "HotGreen\n", - "HotKhaki\n", - "HotMagenta\n", - "HotOliveGreen\n", - "HotOrange\n", - "HotOrchid\n", - "HotRed\n", - "HotSalmon\n", - "HotSeaGreen\n", - "HotSlateBlue\n", - "HotSlateGray\n", - "HotTurquoise\n", - "HotViolet\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "生成 **Stream** 后,所有的基本功能方法,甚至更多就都可以使用了。\n", - "\n", - "\n", - "## 选择Map片段\n", - "\n", - "由 **TreeMap** 和 **ConcurrentSkipListMap** 实现的 **NavigableMap** 接口解决了需要选择Map片段的问题。下面是一个示例,使用了 **HTMLColors** :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/NavMap.java\n", - "// NavigableMap produces pieces of a Map\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import static onjava.HTMLColors.*;\n", - "\n", - "public class NavMap {\n", - " public static final\n", - " NavigableMap COLORS =\n", - " new ConcurrentSkipListMap<>(MAP);\n", - " public static void main(String[] args) {\n", - " show(COLORS.firstEntry());\n", - " border();\n", - " show(COLORS.lastEntry());\n", - " border();\n", - " NavigableMap toLime =\n", - " COLORS.headMap(rgb(\"Lime\"), true);\n", - " show(toLime);\n", - " border();\n", - " show(COLORS.ceilingEntry(rgb(\"DeepSkyBlue\") - 1));\n", - " border();\n", - " show(COLORS.floorEntry(rgb(\"DeepSkyBlue\") - 1));\n", - " border();\n", - " show(toLime.descendingMap());\n", - " border();\n", - " show(COLORS.tailMap(rgb(\"MistyRose\"), true));\n", - " border();\n", - " show(COLORS.subMap(\n", - " rgb(\"Orchid\"), true,\n", - " rgb(\"DarkSalmon\"), false));\n", - " }\n", - "}\n", - "/* Output:\n", - "0x000000: Black\n", - "******************************\n", - "0xFFFFFF: White\n", - "******************************\n", - "0x000000: Black\n", - "0x000080: Navy\n", - "0x00008B: DarkBlue\n", - "0x0000CD: MediumBlue\n", - "0x0000FF: Blue\n", - "0x006400: DarkGreen\n", - "0x008000: Green\n", - "0x008080: Teal\n", - "0x008B8B: DarkCyan\n", - "0x00BFFF: DeepSkyBlue\n", - "0x00CED1: DarkTurquoise\n", - "0x00FA9A: MediumSpringGreen\n", - "0x00FF00: Lime\n", - "******************************\n", - "0x00BFFF: DeepSkyBlue\n", - "******************************\n", - "0x008B8B: DarkCyan\n", - "******************************\n", - "0x00FF00: Lime\n", - "0x00FA9A: MediumSpringGreen\n", - "0x00CED1: DarkTurquoise\n", - "0x00BFFF: DeepSkyBlue\n", - "0x008B8B: DarkCyan\n", - "0x008080: Teal\n", - "0x008000: Green\n", - "0x006400: DarkGreen\n", - "0x0000FF: Blue\n", - "0x0000CD: MediumBlue\n", - "0x00008B: DarkBlue\n", - "0x000080: Navy\n", - "0x000000: Black\n", - "******************************\n", - "0xFFE4E1: MistyRose\n", - "0xFFEBCD: BlanchedAlmond\n", - "0xFFEFD5: PapayaWhip\n", - "0xFFF0F5: LavenderBlush\n", - "0xFFF5EE: SeaShell\n", - "0xFFF8DC: Cornsilk\n", - "0xFFFACD: LemonChiffon\n", - "0xFFFAF0: FloralWhite\n", - "0xFFFAFA: Snow\n", - "0xFFFF00: Yellow\n", - "0xFFFFE0: LightYellow\n", - "0xFFFFF0: Ivory\n", - "0xFFFFFF: White\n", - "******************************\n", - "0xDA70D6: Orchid\n", - "0xDAA520: GoldenRod\n", - "0xDB7093: PaleVioletRed\n", - "0xDC143C: Crimson\n", - "0xDCDCDC: Gainsboro\n", - "0xDDA0DD: Plum\n", - "0xDEB887: BurlyWood\n", - "0xE0FFFF: LightCyan\n", - "0xE6E6FA: Lavender\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在主方法中可以看到 **NavigableMap** 的各种功能。 因为 **NavigableMap** 具有键顺序,所以它使用了 `firstEntry()` 和 `lastEntry()` 的概念。调用 `headMap()` 会生成一个 **NavigableMap** ,其中包含了从 **Map** 的开头到 `headMap()` 参数中所指向的一组元素,其中 **boolean** 值指示结果中是否包含该参数。调用 `tailMap()` 执行了类似的操作,只不过是从参数开始到 **Map** 的末尾。 `subMap()` 则允许生成 **Map** 中间的一部分。\n", - "\n", - "`ceilingEntry()` 从当前键值对向上搜索下一个键值对,`floorEntry()` 则是向下搜索。 `descendingMap()` 反转了 **NavigableMap** 的顺序。\n", - "\n", - "如果需要通过分割 **Map** 来简化所正在解决的问题,则 **NavigableMap** 可以做到。具有类似的功能的其它集合实现也可以用来帮助解决问题。\n", - "\n", - "\n", - "## 填充集合\n", - "\n", - "与 **Arrays** 一样,这里有一个名为 **Collections** 的伴随类(companion class),包含了一些 **static** 的实用方法,其中包括一个名为 `fill()` 的方法。 `fill()` 只复制整个集合中的单个对象引用。此外,它仅适用于 **List** 对象,但结果列表可以传递给构造方法或 `addAll()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/FillingLists.java\n", - "// Collections.fill() & Collections.nCopies()\n", - "import java.util.*;\n", - "\n", - "class StringAddress {\n", - " private String s;\n", - " StringAddress(String s) { this.s = s; }\n", - " @Override\n", - " public String toString() {\n", - " return super.toString() + \" \" + s;\n", - " }\n", - "}\n", - "\n", - "public class FillingLists {\n", - " public static void main(String[] args) {\n", - " List list = new ArrayList<>(\n", - " Collections.nCopies(4,\n", - " new StringAddress(\"Hello\")));\n", - " System.out.println(list);\n", - " Collections.fill(list,\n", - " new StringAddress(\"World!\"));\n", - " System.out.println(list);\n", - " }\n", - "}\n", - "/* Output:\n", - "[StringAddress@15db9742 Hello, StringAddress@15db9742\n", - "Hello, StringAddress@15db9742 Hello,\n", - "StringAddress@15db9742 Hello]\n", - "[StringAddress@6d06d69c World!, StringAddress@6d06d69c\n", - "World!, StringAddress@6d06d69c World!,\n", - "StringAddress@6d06d69c World!]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个示例展示了两种使用对单个对象的引用来填充 **Collection** 的方法。 第一个: `Collections.nCopies()` ,创建一个 **List**,并传递给 **ArrayList** 的构造方法,进而填充了 **ArrayList** 。\n", - "\n", - "**StringAddress** 中的 `toString()` 方法调用了 `Object.toString()` ,它先生成类名,后跟着对象的哈希码的无符号十六进制表示(哈希吗由 `hashCode()` 方法生成)。 输出显示所有的引用都指向同一个对象。调用第二个方法 `Collections.fill()` 后也是如此。 `fill()` 方法的用处非常有限,它只能替换 **List** 中已有的元素,而且不会添加新元素,\n", - "\n", - "### 使用 Suppliers 填充集合\n", - "\n", - "[第二十章 泛型]()章节中介绍的 **onjava.Suppliers** 类为填充集合提供了通用解决方案。 这是一个使用 **Suppliers** 初始化几种不同类型的 **Collection** 的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/SuppliersCollectionTest.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "\n", - "class Government implements Supplier {\n", - " static String[] foundation = (\n", - " \"strange women lying in ponds \" +\n", - " \"distributing swords is no basis \" +\n", - " \"for a system of government\").split(\" \");\n", - " private int index;\n", - " @Override\n", - " public String get() {\n", - " return foundation[index++];\n", - " }\n", - "}\n", - "\n", - "public class SuppliersCollectionTest {\n", - " public static void main(String[] args) {\n", - " // Suppliers class from the Generics chapter:\n", - " Set set = Suppliers.create(\n", - " LinkedHashSet::new, new Government(), 15);\n", - " System.out.println(set);\n", - " List list = Suppliers.create(\n", - " LinkedList::new, new Government(), 15);\n", - " System.out.println(list);\n", - " list = new ArrayList<>();\n", - " Suppliers.fill(list, new Government(), 15);\n", - " System.out.println(list);\n", - "\n", - " // Or we can use Streams:\n", - " set = Arrays.stream(Government.foundation)\n", - " .collect(Collectors.toSet());\n", - " System.out.println(set);\n", - " list = Arrays.stream(Government.foundation)\n", - " .collect(Collectors.toList());\n", - " System.out.println(list);\n", - " list = Arrays.stream(Government.foundation)\n", - " .collect(Collectors\n", - " .toCollection(LinkedList::new));\n", - " System.out.println(list);\n", - " set = Arrays.stream(Government.foundation)\n", - " .collect(Collectors\n", - " .toCollection(LinkedHashSet::new));\n", - " System.out.println(set);\n", - " }\n", - "}\n", - "/* Output:\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "[ponds, no, a, in, swords, for, is, basis, strange,\n", - "system, government, distributing, of, women, lying]\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "[strange, women, lying, in, ponds, distributing,\n", - "swords, is, no, basis, for, a, system, of, government]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**LinkedHashSet** 中的的元素按插入顺序排列,因为它维护一个链表来保存该顺序。\n", - "\n", - "但是请注意示例的第二部分:大多数情况下都可以使用 **Stream** 来创建和填充 **Collection** 。在本例中的 **Stream** 版本不需要声明 **Supplier** 所想要创建的元素数量;,它直接吸收了 **Stream** 中的所有元素。\n", - "\n", - "尽可能优先选择 **Stream** 来解决问题。\n", - "\n", - "### Map Suppliers\n", - "\n", - "使用 **Supplier** 来填充 **Map** 时需要一个 **Pair** 类,因为每次调用一个 **Supplier** 的 `get()` 方法时,都必须生成一对对象(一个键和一个值):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Pair.java\n", - "package onjava;\n", - "\n", - "public class Pair {\n", - " public final K key;\n", - " public final V value;\n", - " public Pair(K k, V v) {\n", - " key = k;\n", - " value = v;\n", - " }\n", - " public K key() { return key; }\n", - " public V value() { return value; }\n", - " public static Pair make(K k, V v) {\n", - " return new Pair(k, v);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Pair** 是一个只读的 *数据传输对象* (Data Transfer Object)或 *信使* (Messenger)。 这与[第二十章 泛型]()章节中的 **Tuple2** 基本相同,但名字更适合 **Map** 初始化。我还添加了静态的 `make()` 方法,以便为创建 **Pair** 对象提供一个更简洁的名字。\n", - "\n", - "Java 8 的 **Stream** 提供了填充 **Map** 的便捷方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/StreamFillMaps.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "\n", - "class Letters\n", - "implements Supplier> {\n", - " private int number = 1;\n", - " private char letter = 'A';\n", - " @Override\n", - " public Pair get() {\n", - " return new Pair<>(number++, \"\" + letter++);\n", - " }\n", - "}\n", - "\n", - "public class StreamFillMaps {\n", - " public static void main(String[] args) {\n", - " Map m =\n", - " Stream.generate(new Letters())\n", - " .limit(11)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value));\n", - " System.out.println(m);\n", - "\n", - " // Two separate Suppliers:\n", - " Rand.String rs = new Rand.String(3);\n", - " Count.Character cc = new Count.Character();\n", - " Map mcs = Stream.generate(\n", - " () -> Pair.make(cc.get(), rs.get()))\n", - " .limit(8)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value));\n", - " System.out.println(mcs);\n", - "\n", - " // A key Supplier and a single value:\n", - " Map mcs2 = Stream.generate(\n", - " () -> Pair.make(cc.get(), \"Val\"))\n", - " .limit(8)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value));\n", - " System.out.println(mcs2);\n", - " }\n", - "}\n", - "/* Output:\n", - "{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J,\n", - "11=K}\n", - "{b=btp, c=enp, d=ccu, e=xsz, f=gvg, g=mei, h=nne,\n", - "i=elo}\n", - "{p=Val, q=Val, j=Val, k=Val, l=Val, m=Val, n=Val,\n", - "o=Val}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "上面的示例中出现了一个模式,可以使用它来创建一个自动创建和填充 **Map** 的工具:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/FillMap.java\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class FillMap {\n", - " public static Map\n", - " basic(Supplier> pairGen, int size) {\n", - " return Stream.generate(pairGen)\n", - " .limit(size)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value));\n", - " }\n", - " public static Map\n", - " basic(Supplier keyGen,\n", - " Supplier valueGen, int size) {\n", - " return Stream.generate(\n", - " () -> Pair.make(keyGen.get(), valueGen.get()))\n", - " .limit(size)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value));\n", - " }\n", - " public static >\n", - " M create(Supplier keyGen,\n", - " Supplier valueGen,\n", - " Supplier mapSupplier, int size) {\n", - " return Stream.generate( () ->\n", - " Pair.make(keyGen.get(), valueGen.get()))\n", - " .limit(size)\n", - " .collect(Collectors\n", - " .toMap(Pair::key, Pair::value,\n", - " (k, v) -> k, mapSupplier));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "basic() 方法生成一个默认的 **Map** ,而 `create()` 方法允许指定一个确切的 **Map** 类型,并返回那个确切的类型。\n", - "\n", - "下面是一个测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/FillMapTest.java\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.stream.*;\n", - "import onjava.*;\n", - "\n", - "public class FillMapTest {\n", - " public static void main(String[] args) {\n", - " Map mcs = FillMap.basic(\n", - " new Rand.String(4), new Count.Integer(), 7);\n", - " System.out.println(mcs);\n", - " HashMap hashm =\n", - " FillMap.create(new Rand.String(4),\n", - " new Count.Integer(), HashMap::new, 7);\n", - " System.out.println(hashm);\n", - " LinkedHashMap linkm =\n", - " FillMap.create(new Rand.String(4),\n", - " new Count.Integer(), LinkedHashMap::new, 7);\n", - " System.out.println(linkm);\n", - " }\n", - "}\n", - "/* Output:\n", - "{npcc=1, ztdv=6, gvgm=3, btpe=0, einn=4, eelo=5,\n", - "uxsz=2}\n", - "{npcc=1, ztdv=6, gvgm=3, btpe=0, einn=4, eelo=5,\n", - "uxsz=2}\n", - "{btpe=0, npcc=1, uxsz=2, gvgm=3, einn=4, eelo=5,\n", - "ztdv=6}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## 使用享元(Flyweight)自定义Collection和Map\n", - "\n", - "本节介绍如何创建自定义 **Collection** 和 **Map** 实现。每个 **java.util** 中的集合都有自己的 **Abstract** 类,它提供了该集合的部分实现,因此只需要实现必要的方法来生成所需的集合。你将看到通过继承 **java.util.Abstract** 类来创建自定义 **Map** 和 **Collection** 是多么简单。例如,要创建一个只读的 **Set** ,则可以从 **AbstractSet** 继承并实现 `iterator()` 和 `size()` 。最后一个示例是生成测试数据的另一种方法。生成的集合通常是只读的,并且所提供的方法最少。\n", - "\n", - "该解决方案还演示了 *享元* (Flyweight)设计模式。当普通解决方案需要太多对象时,或者当生成普通对象占用太多空间时,可以使用享元。享元设计模式将对象的一部分外部化(externalizes)。相比于把对象的所有内容都包含在对象中,这样做使得对象的部分或者全部可以在更有效的外部表中查找,或通过一些节省空间的其他计算生成。\n", - "\n", - "下面是一个可以是任何大小的 **List** ,并且(有效地)使用 **Integer** 数据进行预初始化。要从 **AbstractList** 创建只读 **List** ,必须实现 `get()` 和 `size()`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/CountingIntegerList.java\n", - "// List of any length, containing sample data\n", - "// {java onjava.CountingIntegerList}\n", - "package onjava;\n", - "import java.util.*;\n", - "\n", - "public class CountingIntegerList\n", - "extends AbstractList {\n", - " private int size;\n", - " public CountingIntegerList() { size = 0; }\n", - " public CountingIntegerList(int size) {\n", - " this.size = size < 0 ? 0 : size;\n", - " }\n", - " @Override\n", - " public Integer get(int index) {\n", - " return index;\n", - " }\n", - " @Override\n", - " public int size() { return size; }\n", - " public static void main(String[] args) {\n", - " List cil =\n", - " new CountingIntegerList(30);\n", - " System.out.println(cil);\n", - " System.out.println(cil.get(500));\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n", - "16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]\n", - "500\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只有当想要限制 **List** 的长度时, **size** 值才是重要的,就像在主方法中那样。即使在这种情况下, `get()` 也会产生任何值。\n", - "\n", - "这个类是享元模式的一个简洁的例子。当需要的时候, `get()` “计算”所需的值,因此没必要存储和初始化实际的底层 **List** 结构。\n", - "\n", - "在大多数程序中,这里所保存的存储结构永远都不会改变。但是,它允许用非常大的 **index** 来调用 `List.get()` ,而 **List** 并不需要填充到这么大。此外,还可以在程序中大量使用 **CountingIntegerLists** 而无需担心存储问题。实际上,享元的一个好处是它允许使用更好的抽象而不用担心资源。\n", - "\n", - "可以使用享元设计模式来实现具有任何大小数据集的其他“初始化”自定义集合。下面是一个 **Map** ,它为每一个 **Integer** 键产生唯一的值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/CountMap.java\n", - "// Unlimited-length Map containing sample data\n", - "// {java onjava.CountMap}\n", - "package onjava;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class CountMap\n", - "extends AbstractMap {\n", - " private int size;\n", - " private static char[] chars =\n", - " \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".toCharArray();\n", - " private static String value(int key) {\n", - " return\n", - " chars[key % chars.length] +\n", - " Integer.toString(key / chars.length);\n", - " }\n", - " public CountMap(int size) {\n", - " this.size = size < 0 ? 0 : size;\n", - " }\n", - " @Override\n", - " public String get(Object key) {\n", - " return value((Integer)key);\n", - " }\n", - " private static class Entry\n", - " implements Map.Entry {\n", - " int index;\n", - " Entry(int index) { this.index = index; }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof Entry &&\n", - " Objects.equals(index, ((Entry)o).index);\n", - " }\n", - " @Override\n", - " public Integer getKey() { return index; }\n", - " @Override\n", - " public String getValue() {\n", - " return value(index);\n", - " }\n", - " @Override\n", - " public String setValue(String value) {\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hashCode(index);\n", - " }\n", - " }\n", - " @Override\n", - " public Set> entrySet() {\n", - " // LinkedHashSet retains initialization order:\n", - " return IntStream.range(0, size)\n", - " .mapToObj(Entry::new)\n", - " .collect(Collectors\n", - " .toCollection(LinkedHashSet::new));\n", - " }\n", - " public static void main(String[] args) {\n", - " final int size = 6;\n", - " CountMap cm = new CountMap(60);\n", - " System.out.println(cm);\n", - " System.out.println(cm.get(500));\n", - " cm.values().stream()\n", - " .limit(size)\n", - " .forEach(System.out::println);\n", - " System.out.println();\n", - " new Random(47).ints(size, 0, 1000)\n", - " .mapToObj(cm::get)\n", - " .forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", - "9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0,\n", - "17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0,\n", - "25=Z0, 26=A1, 27=B1, 28=C1, 29=D1, 30=E1, 31=F1, 32=G1,\n", - "33=H1, 34=I1, 35=J1, 36=K1, 37=L1, 38=M1, 39=N1, 40=O1,\n", - "41=P1, 42=Q1, 43=R1, 44=S1, 45=T1, 46=U1, 47=V1, 48=W1,\n", - "49=X1, 50=Y1, 51=Z1, 52=A2, 53=B2, 54=C2, 55=D2, 56=E2,\n", - "57=F2, 58=G2, 59=H2}\n", - "G19\n", - "A0\n", - "B0\n", - "C0\n", - "D0\n", - "E0\n", - "F0\n", - "\n", - "Y9\n", - "J21\n", - "R26\n", - "D33\n", - "Z36\n", - "N16\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要创建一个只读的 **Map** ,则从 **AbstractMap** 继承并实现 `entrySet()` 。私有的 `value()` 方法计算任何键的值,并在 `get()` 和 `Entry.getValue()` 中使用。可以忽略 **CountMap** 的大小。\n", - "\n", - "这里是使用了 **LinkedHashSet** 而不是创建自定义 **Set** 类,因此并未完全实现享元。只有在调用 `entrySet()` 时才会生成此对象。\n", - "\n", - "现在创建一个更复杂的享元。这个示例中的数据集是世界各国及其首都的 **Map** 。 `capitals()` 方法生成一个国家和首都的 **Map** 。 `names()` 方法生成一个由国家名字组成的 **List** 。 当给定了表示所需大小的 **int** 参数时,两种方法都生成对应大小的列表片段:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/Countries.java\n", - "// \"Flyweight\" Maps and Lists of sample data\n", - "// {java onjava.Countries}\n", - "package onjava;\n", - "import java.util.*;\n", - "\n", - "public class Countries {\n", - " public static final String[][] DATA = {\n", - " // Africa\n", - " {\"ALGERIA\",\"Algiers\"},\n", - " {\"ANGOLA\",\"Luanda\"},\n", - " {\"BENIN\",\"Porto-Novo\"},\n", - " {\"BOTSWANA\",\"Gaberone\"},\n", - " {\"BURKINA FASO\",\"Ouagadougou\"},\n", - " {\"BURUNDI\",\"Bujumbura\"},\n", - " {\"CAMEROON\",\"Yaounde\"},\n", - " {\"CAPE VERDE\",\"Praia\"},\n", - " {\"CENTRAL AFRICAN REPUBLIC\",\"Bangui\"},\n", - " {\"CHAD\",\"N'djamena\"},\n", - " {\"COMOROS\",\"Moroni\"},\n", - " {\"CONGO\",\"Brazzaville\"},\n", - " {\"DJIBOUTI\",\"Dijibouti\"},\n", - " {\"EGYPT\",\"Cairo\"},\n", - " {\"EQUATORIAL GUINEA\",\"Malabo\"},\n", - " {\"ERITREA\",\"Asmara\"},\n", - " {\"ETHIOPIA\",\"Addis Ababa\"},\n", - " {\"GABON\",\"Libreville\"},\n", - " {\"THE GAMBIA\",\"Banjul\"},\n", - " {\"GHANA\",\"Accra\"},\n", - " {\"GUINEA\",\"Conakry\"},\n", - " {\"BISSAU\",\"Bissau\"},\n", - " {\"COTE D'IVOIR (IVORY COAST)\",\"Yamoussoukro\"},\n", - " {\"KENYA\",\"Nairobi\"},\n", - " {\"LESOTHO\",\"Maseru\"},\n", - " {\"LIBERIA\",\"Monrovia\"},\n", - " {\"LIBYA\",\"Tripoli\"},\n", - " {\"MADAGASCAR\",\"Antananarivo\"},\n", - " {\"MALAWI\",\"Lilongwe\"},\n", - " {\"MALI\",\"Bamako\"},\n", - " {\"MAURITANIA\",\"Nouakchott\"},\n", - " {\"MAURITIUS\",\"Port Louis\"},\n", - " {\"MOROCCO\",\"Rabat\"},\n", - " {\"MOZAMBIQUE\",\"Maputo\"},\n", - " {\"NAMIBIA\",\"Windhoek\"},\n", - " {\"NIGER\",\"Niamey\"},\n", - " {\"NIGERIA\",\"Abuja\"},\n", - " {\"RWANDA\",\"Kigali\"},\n", - " {\"SAO TOME E PRINCIPE\",\"Sao Tome\"},\n", - " {\"SENEGAL\",\"Dakar\"},\n", - " {\"SEYCHELLES\",\"Victoria\"},\n", - " {\"SIERRA LEONE\",\"Freetown\"},\n", - " {\"SOMALIA\",\"Mogadishu\"},\n", - " {\"SOUTH AFRICA\",\"Pretoria/Cape Town\"},\n", - " {\"SUDAN\",\"Khartoum\"},\n", - " {\"SWAZILAND\",\"Mbabane\"},\n", - " {\"TANZANIA\",\"Dodoma\"},\n", - " {\"TOGO\",\"Lome\"},\n", - " {\"TUNISIA\",\"Tunis\"},\n", - " {\"UGANDA\",\"Kampala\"},\n", - " {\"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)\",\n", - " \"Kinshasa\"},\n", - " {\"ZAMBIA\",\"Lusaka\"},\n", - " {\"ZIMBABWE\",\"Harare\"},\n", - " // Asia\n", - " {\"AFGHANISTAN\",\"Kabul\"},\n", - " {\"BAHRAIN\",\"Manama\"},\n", - " {\"BANGLADESH\",\"Dhaka\"},\n", - " {\"BHUTAN\",\"Thimphu\"},\n", - " {\"BRUNEI\",\"Bandar Seri Begawan\"},\n", - " {\"CAMBODIA\",\"Phnom Penh\"},\n", - " {\"CHINA\",\"Beijing\"},\n", - " {\"CYPRUS\",\"Nicosia\"},\n", - " {\"INDIA\",\"New Delhi\"},\n", - " {\"INDONESIA\",\"Jakarta\"},\n", - " {\"IRAN\",\"Tehran\"},\n", - " {\"IRAQ\",\"Baghdad\"},\n", - " {\"ISRAEL\",\"Jerusalem\"},\n", - " {\"JAPAN\",\"Tokyo\"},\n", - " {\"JORDAN\",\"Amman\"},\n", - " {\"KUWAIT\",\"Kuwait City\"},\n", - " {\"LAOS\",\"Vientiane\"},\n", - " {\"LEBANON\",\"Beirut\"},\n", - " {\"MALAYSIA\",\"Kuala Lumpur\"},\n", - " {\"THE MALDIVES\",\"Male\"},\n", - " {\"MONGOLIA\",\"Ulan Bator\"},\n", - " {\"MYANMAR (BURMA)\",\"Rangoon\"},\n", - " {\"NEPAL\",\"Katmandu\"},\n", - " {\"NORTH KOREA\",\"P'yongyang\"},\n", - " {\"OMAN\",\"Muscat\"},\n", - " {\"PAKISTAN\",\"Islamabad\"},\n", - " {\"PHILIPPINES\",\"Manila\"},\n", - " {\"QATAR\",\"Doha\"},\n", - " {\"SAUDI ARABIA\",\"Riyadh\"},\n", - " {\"SINGAPORE\",\"Singapore\"},\n", - " {\"SOUTH KOREA\",\"Seoul\"},\n", - " {\"SRI LANKA\",\"Colombo\"},\n", - " {\"SYRIA\",\"Damascus\"},\n", - " {\"TAIWAN (REPUBLIC OF CHINA)\",\"Taipei\"},\n", - " {\"THAILAND\",\"Bangkok\"},\n", - " {\"TURKEY\",\"Ankara\"},\n", - " {\"UNITED ARAB EMIRATES\",\"Abu Dhabi\"},\n", - " {\"VIETNAM\",\"Hanoi\"},\n", - " {\"YEMEN\",\"Sana'a\"},\n", - " // Australia and Oceania\n", - " {\"AUSTRALIA\",\"Canberra\"},\n", - " {\"FIJI\",\"Suva\"},\n", - " {\"KIRIBATI\",\"Bairiki\"},\n", - " {\"MARSHALL ISLANDS\",\"Dalap-Uliga-Darrit\"},\n", - " {\"MICRONESIA\",\"Palikir\"},\n", - " {\"NAURU\",\"Yaren\"},\n", - " {\"NEW ZEALAND\",\"Wellington\"},\n", - " {\"PALAU\",\"Koror\"},\n", - " {\"PAPUA NEW GUINEA\",\"Port Moresby\"},\n", - " {\"SOLOMON ISLANDS\",\"Honaira\"},\n", - " {\"TONGA\",\"Nuku'alofa\"},\n", - " {\"TUVALU\",\"Fongafale\"},\n", - " {\"VANUATU\",\"Port Vila\"},\n", - " {\"WESTERN SAMOA\",\"Apia\"},\n", - " // Eastern Europe and former USSR\n", - " {\"ARMENIA\",\"Yerevan\"},\n", - " {\"AZERBAIJAN\",\"Baku\"},\n", - " {\"BELARUS (BYELORUSSIA)\",\"Minsk\"},\n", - " {\"BULGARIA\",\"Sofia\"},\n", - " {\"GEORGIA\",\"Tbilisi\"},\n", - " {\"KAZAKSTAN\",\"Almaty\"},\n", - " {\"KYRGYZSTAN\",\"Alma-Ata\"},\n", - " {\"MOLDOVA\",\"Chisinau\"},\n", - " {\"RUSSIA\",\"Moscow\"},\n", - " {\"TAJIKISTAN\",\"Dushanbe\"},\n", - " {\"TURKMENISTAN\",\"Ashkabad\"},\n", - " {\"UKRAINE\",\"Kyiv\"},\n", - " {\"UZBEKISTAN\",\"Tashkent\"},\n", - " // Europe\n", - " {\"ALBANIA\",\"Tirana\"},\n", - " {\"ANDORRA\",\"Andorra la Vella\"},\n", - " {\"AUSTRIA\",\"Vienna\"},\n", - " {\"BELGIUM\",\"Brussels\"},\n", - " {\"BOSNIA-HERZEGOVINA\",\"Sarajevo\"},\n", - " {\"CROATIA\",\"Zagreb\"},\n", - " {\"CZECH REPUBLIC\",\"Prague\"},\n", - " {\"DENMARK\",\"Copenhagen\"},\n", - " {\"ESTONIA\",\"Tallinn\"},\n", - " {\"FINLAND\",\"Helsinki\"},\n", - " {\"FRANCE\",\"Paris\"},\n", - " {\"GERMANY\",\"Berlin\"},\n", - " {\"GREECE\",\"Athens\"},\n", - " {\"HUNGARY\",\"Budapest\"},\n", - " {\"ICELAND\",\"Reykjavik\"},\n", - " {\"IRELAND\",\"Dublin\"},\n", - " {\"ITALY\",\"Rome\"},\n", - " {\"LATVIA\",\"Riga\"},\n", - " {\"LIECHTENSTEIN\",\"Vaduz\"},\n", - " {\"LITHUANIA\",\"Vilnius\"},\n", - " {\"LUXEMBOURG\",\"Luxembourg\"},\n", - " {\"MACEDONIA\",\"Skopje\"},\n", - " {\"MALTA\",\"Valletta\"},\n", - " {\"MONACO\",\"Monaco\"},\n", - " {\"MONTENEGRO\",\"Podgorica\"},\n", - " {\"THE NETHERLANDS\",\"Amsterdam\"},\n", - " {\"NORWAY\",\"Oslo\"},\n", - " {\"POLAND\",\"Warsaw\"},\n", - " {\"PORTUGAL\",\"Lisbon\"},\n", - " {\"ROMANIA\",\"Bucharest\"},\n", - " {\"SAN MARINO\",\"San Marino\"},\n", - " {\"SERBIA\",\"Belgrade\"},\n", - " {\"SLOVAKIA\",\"Bratislava\"},\n", - " {\"SLOVENIA\",\"Ljuijana\"},\n", - " {\"SPAIN\",\"Madrid\"},\n", - " {\"SWEDEN\",\"Stockholm\"},\n", - " {\"SWITZERLAND\",\"Berne\"},\n", - " {\"UNITED KINGDOM\",\"London\"},\n", - " {\"VATICAN CITY\",\"Vatican City\"},\n", - " // North and Central America\n", - " {\"ANTIGUA AND BARBUDA\",\"Saint John's\"},\n", - " {\"BAHAMAS\",\"Nassau\"},\n", - " {\"BARBADOS\",\"Bridgetown\"},\n", - " {\"BELIZE\",\"Belmopan\"},\n", - " {\"CANADA\",\"Ottawa\"},\n", - " {\"COSTA RICA\",\"San Jose\"},\n", - " {\"CUBA\",\"Havana\"},\n", - " {\"DOMINICA\",\"Roseau\"},\n", - " {\"DOMINICAN REPUBLIC\",\"Santo Domingo\"},\n", - " {\"EL SALVADOR\",\"San Salvador\"},\n", - " {\"GRENADA\",\"Saint George's\"},\n", - " {\"GUATEMALA\",\"Guatemala City\"},\n", - " {\"HAITI\",\"Port-au-Prince\"},\n", - " {\"HONDURAS\",\"Tegucigalpa\"},\n", - " {\"JAMAICA\",\"Kingston\"},\n", - " {\"MEXICO\",\"Mexico City\"},\n", - " {\"NICARAGUA\",\"Managua\"},\n", - " {\"PANAMA\",\"Panama City\"},\n", - " {\"ST. KITTS AND NEVIS\",\"Basseterre\"},\n", - " {\"ST. LUCIA\",\"Castries\"},\n", - " {\"ST. VINCENT AND THE GRENADINES\",\"Kingstown\"},\n", - " {\"UNITED STATES OF AMERICA\",\"Washington, D.C.\"},\n", - " // South America\n", - " {\"ARGENTINA\",\"Buenos Aires\"},\n", - " {\"BOLIVIA\",\"Sucre (legal)/La Paz(administrative)\"},\n", - " {\"BRAZIL\",\"Brasilia\"},\n", - " {\"CHILE\",\"Santiago\"},\n", - " {\"COLOMBIA\",\"Bogota\"},\n", - " {\"ECUADOR\",\"Quito\"},\n", - " {\"GUYANA\",\"Georgetown\"},\n", - " {\"PARAGUAY\",\"Asuncion\"},\n", - " {\"PERU\",\"Lima\"},\n", - " {\"SURINAME\",\"Paramaribo\"},\n", - " {\"TRINIDAD AND TOBAGO\",\"Port of Spain\"},\n", - " {\"URUGUAY\",\"Montevideo\"},\n", - " {\"VENEZUELA\",\"Caracas\"},\n", - " };\n", - " // Use AbstractMap by implementing entrySet()\n", - " private static class FlyweightMap\n", - " extends AbstractMap {\n", - " private static class Entry\n", - " implements Map.Entry {\n", - " int index;\n", - " Entry(int index) { this.index = index; }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof FlyweightMap &&\n", - " Objects.equals(DATA[index][0], o);\n", - " }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hashCode(DATA[index][0]);\n", - " }\n", - " @Override\n", - " public String getKey() { return DATA[index][0]; }\n", - " @Override\n", - " public String getValue() {\n", - " return DATA[index][1];\n", - " }\n", - " @Override\n", - " public String setValue(String value) {\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " }\n", - " // Implement size() & iterator() for AbstractSet:\n", - " static class EntrySet\n", - " extends AbstractSet> {\n", - " private int size;\n", - " EntrySet(int size) {\n", - " if(size < 0)\n", - " this.size = 0;\n", - " // Can't be any bigger than the array:\n", - " else if(size > DATA.length)\n", - " this.size = DATA.length;\n", - " else\n", - " this.size = size;\n", - " }\n", - " @Override\n", - " public int size() { return size; }\n", - " private class Iter\n", - " implements Iterator> {\n", - " // Only one Entry object per Iterator:\n", - " private Entry entry = new Entry(-1);\n", - " @Override\n", - " public boolean hasNext() {\n", - " return entry.index < size - 1;\n", - " }\n", - " @Override\n", - " public Map.Entry next() {\n", - " entry.index++;\n", - " return entry;\n", - " }\n", - " @Override\n", - " public void remove() {\n", - " throw new UnsupportedOperationException();\n", - " }\n", - " }\n", - " @Override\n", - " public\n", - " Iterator> iterator() {\n", - " return new Iter();\n", - " }\n", - " }\n", - " private static\n", - " Set> entries =\n", - " new EntrySet(DATA.length);\n", - " @Override\n", - " public Set> entrySet() {\n", - " return entries;\n", - " }\n", - " }\n", - " // Create a partial map of 'size' countries:\n", - " static Map select(final int size) {\n", - " return new FlyweightMap() {\n", - " @Override\n", - " public Set> entrySet() {\n", - " return new EntrySet(size);\n", - " }\n", - " };\n", - " }\n", - " static Map map = new FlyweightMap();\n", - " public static Map capitals() {\n", - " return map; // The entire map\n", - " }\n", - " public static Map capitals(int size) {\n", - " return select(size); // A partial map\n", - " }\n", - " static List names =\n", - " new ArrayList<>(map.keySet());\n", - " // All the names:\n", - " public static List names() { return names; }\n", - " // A partial list:\n", - " public static List names(int size) {\n", - " return new ArrayList<>(select(size).keySet());\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(capitals(10));\n", - " System.out.println(names(10));\n", - " System.out.println(new HashMap<>(capitals(3)));\n", - " System.out.println(\n", - " new LinkedHashMap<>(capitals(3)));\n", - " System.out.println(new TreeMap<>(capitals(3)));\n", - " System.out.println(new Hashtable<>(capitals(3)));\n", - " System.out.println(new HashSet<>(names(6)));\n", - " System.out.println(new LinkedHashSet<>(names(6)));\n", - " System.out.println(new TreeSet<>(names(6)));\n", - " System.out.println(new ArrayList<>(names(6)));\n", - " System.out.println(new LinkedList<>(names(6)));\n", - " System.out.println(capitals().get(\"BRAZIL\"));\n", - " }\n", - "}\n", - "/* Output:\n", - "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo,\n", - "BOTSWANA=Gaberone, BURKINA FASO=Ouagadougou,\n", - "BURUNDI=Bujumbura, CAMEROON=Yaounde, CAPE VERDE=Praia,\n", - "CENTRAL AFRICAN REPUBLIC=Bangui, CHAD=N'djamena}\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN\n", - "REPUBLIC, CHAD]\n", - "{BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}\n", - "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", - "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", - "{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}\n", - "[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,\n", - "BURUNDI]\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI]\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI]\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI]\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI]\n", - "Brasilia\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "二维数组 **String DATA** 是 **public** 的,因此可以在别处使用。 **FlyweightMap** 必须实现 `entrySet()` 方法,该方法需要一个自定义 **Set** 实现和一个自定义 **Map.Entry** 类。这是实现享元的另一种方法:每个 **Map.Entry** 对象存储它自身的索引,而不是实际的键和值。当调用 `getKey()` 或 `getValue()` 时,它使用索引返回相应的 **DATA** 元素。 **EntrySet** 确保它的 **size** 不大于 **DATA** 。\n", - "\n", - "享元的另一部分在 **EntrySet.Iterator** 中实现。相比于为 **DATA** 中的每个数据对创建一个 **Map.Entry** 对象,这里每个迭代器只有一个 **Map.Entry** 对象。 **Entry** 对象作为数据的窗口,它只包含 **String** 静态数组的索引。每次为迭代器调用 `next()` 时,**Entry** 中的索引都会递增,因此它会指向下一个数据对,然后从 `next()` 返回 **Iterators** 的单个 **Entry** 对象。[^1]\n", - "\n", - "`select()` 方法生成一个包含所需大小的 **EntrySet** 的 **FlyweightMap** ,这用于在主方法中演示的重载的 `capitals()` 和 `names()` 方法。\n", - "\n", - "\n", - "## 集合功能\n", - "\n", - "下面这个表格展示了可以对 **Collection** 执行的所有操作(不包括自动继承自 **Object** 的方法),因此,可以用 **List** , **Set** , **Queue** 或 **Deque** 执行这里的所有操作(这些接口可能也提供了一些其他的功能)。**Map** 不是从 **Collection** 继承的,所以要单独处理它。\n", - "\n", - "| 方法名 | 描述 |\n", - "| :---: | :--- |\n", - "| **boolean add(T)** | 确保集合包含该泛型类型 **T** 的参数。如果不添加参数,则返回 **false** 。 (这是一种“可选”方法,将在下一节中介绍。) |\n", - "| **boolean addAll(Collection\\)** | 添加参数集合中的所有元素。只要有元素被成功添加则返回 **true**。(“可选的”) |\n", - "| **void clear()** | 删除集合中的所有元素。(“可选的”) |\n", - "| **boolean contains(T)** | 如果目标集合包含该泛型类型 **T** 的参数,则返回 **true** 。 |\n", - "| **boolean containsAll(Collection\\)** | 如果目标集合包含参数集合中的所有元素,则返回 **true** |\n", - "| **boolean isEmpty()** | 如果集合为空,则返回 **true** |\n", - "| **Iterator\\ iterator() Spliterator\\ spliterator()** | 返回一个迭代器来遍历集合中的元素。 **Spliterators** 更复杂一些,它用在并发场景 |\n", - "| **boolean remove(Object)** | 如果目标集合包含该参数,则在集合中删除该参数,如果成功删除则返回 **true** 。(“可选的”) |\n", - "| **boolean removeAll(Collection\\)** | 删除目标集合中,参数集合所包含的全部元素。如果有元素被成功删除则返回 **true** 。 (“可选的”) |\n", - "| **boolean removeIf(Predicate\\)** | 删除此集合中,满足给定断言(predicate)的所有元素 |\n", - "| **Stream\\ stream() Stream\\ parallelStream()** | 返回由该 **Collection** 中元素所组成的一个 **Stream** |\n", - "| **int size()** | 返回集合中所包含元素的个数 |\n", - "| **Object[] toArrat()** | 返回包含该集合所有元素的一个数组 |\n", - "| **\\ T[] toArray(T[] a)** | 返回包含该集合所有元素的一个数组。结果的运行时类型是参数数组而不是普通的 **Object** 数组。 |\n", - "\n", - "这里没有提供用于随机访问元素的 **get()** 方法,因为 **Collection** 还包含 **Set** ,它维护自己的内部排序,所以随机访问查找就没有意义了。因此,要查找 **Collection** 中的元素必须使用迭代器。\n", - "\n", - "下面这个示例演示了 **Collection** 的所有方法。这里以 **ArrayList** 为例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/CollectionMethods.java\n", - "// Things you can do with all Collections\n", - "import java.util.*;\n", - "import static onjava.HTMLColors.*;\n", - "\n", - "public class CollectionMethods {\n", - " public static void main(String[] args) {\n", - " Collection c =\n", - " new ArrayList<>(LIST.subList(0, 4));\n", - " c.add(\"ten\");\n", - " c.add(\"eleven\");\n", - " show(c);\n", - " border();\n", - " // Make an array from the List:\n", - " Object[] array = c.toArray();\n", - " // Make a String array from the List:\n", - " String[] str = c.toArray(new String[0]);\n", - " // Find max and min elements; this means\n", - " // different things depending on the way\n", - " // the Comparable interface is implemented:\n", - " System.out.println(\n", - " \"Collections.max(c) = \" + Collections.max(c));\n", - " System.out.println(\n", - " \"Collections.min(c) = \" + Collections.min(c));\n", - " border();\n", - " // Add a Collection to another Collection\n", - " Collection c2 =\n", - " new ArrayList<>(LIST.subList(10, 14));\n", - " c.addAll(c2);\n", - " show(c);\n", - " border();\n", - " c.remove(LIST.get(0));\n", - " show(c);\n", - " border();\n", - " // Remove all components that are\n", - " // in the argument collection:\n", - " c.removeAll(c2);\n", - " show(c);\n", - " border();\n", - " c.addAll(c2);\n", - " show(c);\n", - " border();\n", - " // Is an element in this Collection?\n", - " String val = LIST.get(3);\n", - " System.out.println(\n", - " \"c.contains(\" + val + \") = \" + c.contains(val));\n", - " // Is a Collection in this Collection?\n", - " System.out.println(\n", - " \"c.containsAll(c2) = \" + c.containsAll(c2));\n", - " Collection c3 =\n", - " ((List)c).subList(3, 5);\n", - " // Keep all the elements that are in both\n", - " // c2 and c3 (an intersection of sets):\n", - " c2.retainAll(c3);\n", - " show(c2);\n", - " // Throw away all the elements\n", - " // in c2 that also appear in c3:\n", - " c2.removeAll(c3);\n", - " System.out.println(\n", - " \"c2.isEmpty() = \" + c2.isEmpty());\n", - " border();\n", - " // Functional operation:\n", - " c = new ArrayList<>(LIST);\n", - " c.removeIf(s -> !s.startsWith(\"P\"));\n", - " c.removeIf(s -> s.startsWith(\"Pale\"));\n", - " // Stream operation:\n", - " c.stream().forEach(System.out::println);\n", - " c.clear(); // Remove all elements\n", - " System.out.println(\"after c.clear():\" + c);\n", - " }\n", - "}\n", - "/* Output:\n", - "AliceBlue\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "ten\n", - "eleven\n", - "******************************\n", - "Collections.max(c) = ten\n", - "Collections.min(c) = AliceBlue\n", - "******************************\n", - "AliceBlue\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "ten\n", - "eleven\n", - "Brown\n", - "BurlyWood\n", - "CadetBlue\n", - "Chartreuse\n", - "******************************\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "ten\n", - "eleven\n", - "Brown\n", - "BurlyWood\n", - "CadetBlue\n", - "Chartreuse\n", - "******************************\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "ten\n", - "eleven\n", - "******************************\n", - "AntiqueWhite\n", - "Aquamarine\n", - "Azure\n", - "ten\n", - "eleven\n", - "Brown\n", - "BurlyWood\n", - "CadetBlue\n", - "Chartreuse\n", - "******************************\n", - "c.contains(Azure) = true\n", - "c.containsAll(c2) = true\n", - "c2.isEmpty() = true\n", - "******************************\n", - "PapayaWhip\n", - "PeachPuff\n", - "Peru\n", - "Pink\n", - "Plum\n", - "PowderBlue\n", - "Purple\n", - "after c.clear():[]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了只演示 **Collection** 接口的方法,而没有其它额外的内容,所以这里创建包含不同数据集的 **ArrayList** ,并向上转型为 **Collection** 对象。\n", - "\n", - "\n", - "## 可选操作\n", - "\n", - "在 **Collection** 接口中执行各种添加和删除操作的方法是 *可选操作* (optional operations)。这意味着实现类不需要为这些方法提供功能定义。\n", - "\n", - "这是一种非常不寻常的定义接口的方式。正如我们所知,接口是一种合约(contract)。它表达的意思是,“无论你如何选择实现这个接口,我保证你可以将这些消息发送到这个对象”(我在这里使用术语“接口”来描述正式的 **interface** 关键字和“任何类或子类都支持的方法”的更一般含义)。但“可选”操作违反了这一基本原则,它表示调用某些方法不会执行有意义的行为。相反,它们会抛出异常!这看起来似乎丢失了编译时的类型安全性。\n", - "\n", - "其实没那么糟糕。如果操作是可选的,编译器仍然能够限制你仅调用该接口中的方法。它不像动态语言那样,可以为任何对象调用任何方法,并在运行时查找特定的调用是否可行。[^2]此外,大多数将 **Collection** 作为参数的方法仅从该 **Collection** 中读取,并且 **Collection** 的所有“读取”方法都不是可选的。\n", - "\n", - "为什么要将方法定义为“可选”的?因为这样做可以防止设计中的接口爆炸。集合库的其他设计往往会产生令人困惑的过多接口来描述主题的每个变体。这甚至使得不可能捕获到接口中的所有特殊情况,因为总有人能发明一个新的接口。“不支持的操作(unsupported operation)”这种方式实现了Java集合库的一个重要目标:集合要易于学习和使用。不支持的操作是一种特殊情况,可以推迟到必要的时候。但是,要使用此方法:\n", - "\n", - "1. **UnsupportedOperationException** 必须是一个罕见的事件。也就是说,对于大多数类,所有操作都应该起作用,并且只有在特殊情况下才应该不支持某项操作。这在Java集合库中是正确的,因为99%的时间使用到的类 —— **ArrayList** , **LinkedList** , **HashSet** 和 **HashMap** ,以及其他具体实现,都支持所有操作。该设计确实为创建一个新的 **Collection** 提供了一个“后门”,可以不为 **Collection** 接口中的所有方法都提供有意义的定义,这些定义仍然适合现有的类库。\n", - "\n", - "2. 当不支持某个操作时, **UnsupportedOperationException** 应该出现在实现阶段,而不是在将产品发送给客户之后。毕竟,这个异常表示编程错误:错误地使用了一个具体实现。\n", - "\n", - "值得注意的是,不支持的操作只能在运行时检测到,因此这代表动态类型检查。如果你来自像 C++ 这样的静态类型语言,Java 可能看起来只是另一种静态类型语言。当然, Java 肯定有静态类型检查,但它也有大量的动态类型,因此很难说它只是静态语言或动态语言。一旦你开始注意到这一点,你就会开始看到 Java 中动态类型检查的其他示例。\n", - "\n", - "\n", - "### 不支持的操作\n", - "\n", - "不支持的操作的常见来源是由固定大小的数据结构所支持的集合。使用 `Arrays.asList()` 方法将数组转换为 **List** 时,就会得到这样的集合。此外,还可以选择使用 **Collections** 类中的“不可修改(unmodifiable)”方法使任何集合(包括 **Map** )抛出 **UnsupportedOperationException** 异常。此示例展示了这两种情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Unsupported.java\n", - "// Unsupported operations in Java collections\n", - "import java.util.*;\n", - "\n", - "public class Unsupported {\n", - " static void\n", - " check(String description, Runnable tst) {\n", - " try {\n", - " tst.run();\n", - " } catch(Exception e) {\n", - " System.out.println(description + \"(): \" + e);\n", - " }\n", - " }\n", - " static void test(String msg, List list) {\n", - " System.out.println(\"--- \" + msg + \" ---\");\n", - " Collection c = list;\n", - " Collection subList = list.subList(1,8);\n", - " // Copy of the sublist:\n", - " Collection c2 = new ArrayList<>(subList);\n", - " check(\"retainAll\", () -> c.retainAll(c2));\n", - " check(\"removeAll\", () -> c.removeAll(c2));\n", - " check(\"clear\", () -> c.clear());\n", - " check(\"add\", () -> c.add(\"X\"));\n", - " check(\"addAll\", () -> c.addAll(c2));\n", - " check(\"remove\", () -> c.remove(\"C\"));\n", - " // The List.set() method modifies the value but\n", - " // doesn't change the size of the data structure:\n", - " check(\"List.set\", () -> list.set(0, \"X\"));\n", - " }\n", - " public static void main(String[] args) {\n", - " List list = Arrays.asList(\n", - " \"A B C D E F G H I J K L\".split(\" \"));\n", - " test(\"Modifiable Copy\", new ArrayList<>(list));\n", - " test(\"Arrays.asList()\", list);\n", - " test(\"unmodifiableList()\",\n", - " Collections.unmodifiableList(\n", - " new ArrayList<>(list)));\n", - " }\n", - "}\n", - "/* Output:\n", - "--- Modifiable Copy ---\n", - "--- Arrays.asList() ---\n", - "retainAll(): java.lang.UnsupportedOperationException\n", - "removeAll(): java.lang.UnsupportedOperationException\n", - "clear(): java.lang.UnsupportedOperationException\n", - "add(): java.lang.UnsupportedOperationException\n", - "addAll(): java.lang.UnsupportedOperationException\n", - "remove(): java.lang.UnsupportedOperationException\n", - "--- unmodifiableList() ---\n", - "retainAll(): java.lang.UnsupportedOperationException\n", - "removeAll(): java.lang.UnsupportedOperationException\n", - "clear(): java.lang.UnsupportedOperationException\n", - "add(): java.lang.UnsupportedOperationException\n", - "addAll(): java.lang.UnsupportedOperationException\n", - "remove(): java.lang.UnsupportedOperationException\n", - "List.set(): java.lang.UnsupportedOperationException\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "因为 `Arrays.asList()` 生成的 **List** 由一个固定大小的数组所支持,所以唯一支持的操作是那些不改变数组大小的操作。任何会导致更改基础数据结构大小的方法都会产生 **UnsupportedOperationException** 异常,来说明这是对不支持的方法的调用(编程错误)。\n", - "\n", - "请注意,始终可以将 `Arrays.asList()` 的结果作为一个参数传递给任何 **Collection** 的构造方法(或使用 `addAll()` 方法或静态的 `Collections.addAll()` 方法)来创建一个允许使用所有方法的常规集合,在主方法中第一次调用 `test()` 时显示了这种情况。这种调用产生了一个新的可调整大小的底层数据结构。\n", - "\n", - "**Collections** 类中的“unmodifiable”方法会将集合包装一个代理中,如果执行任何想要修改集合的操作,则该代理会生成 **UnsupportedOperationException** 异常。使用这些方法的目的是生成一个“常量”集合对象。稍后将描述“unmodifiable“集合方法的完整列表。\n", - "\n", - "`test()` 中的最后一个 `check()` 用于测试**List** 的 `set()` 方法。这里,“不支持的操作”技术的粒度(granularity)就派上用场了,得到的“接口”可以通过一种方法在 `Arrays.asList()` 返回的对象和 `Collections.unmodifiableList()` 返回的对象之间变换。 `Arrays.asList()` 返回固定大小的 **List** ,而 `Collections.unmodifiableList()` 生成无法更改的 **List** 。如输出中所示, `Arrays.asList()` 返回的 **List** 中的元素是可以修改的,因为这不会违反该 **List** 的“固定大小”特性。但很明显, `unmodifiableList()` 的结果不应该以任何方式修改。如果使用接口来描述,则需要两个额外的接口,一个具有可用的 `set()` 方法,而另一个没有。 **Collection** 的各种不可修改的子类型都将需要额外的接口。\n", - "\n", - "如果一个方法将一个集合作为它的参数,那么它的文档应该说明必须实现哪些可选方法。\n", - "\n", - "\n", - "## Set和存储顺序\n", - "\n", - "[第十二章 集合]()章节中的 **Set** 有关示例对 **Set** 的基本操作做了很好的介绍。 但是,这些示例可以方便地使用预定义的 Java 类型,例如 **Integer** 和 **String** ,它们可以在集合中使用。在创建自己的类型时请注意, **Set** (以及稍后会看到的 **Map** )需要一种维护存储顺序的方法,该顺序因 **Set** 的不同实现而异。因此,不同的 **Set** 实现不仅具有不同的行为,而且它们对可以放入特定 **Set** 中的对象类型也有不同的要求:\n", - "\n", - "| **Set** 类型 | 约束 |\n", - "| :---: | :--- |\n", - "| **Set(interface)** | 添加到 **Set** 中的每个元素必须是唯一的,否则,**Set** 不会添加重复元素。添加到 **Set** 的元素必须至少定义 `equals()` 方法以建立对象唯一性。 **Set** 与 **Collection** 具有完全相同的接口。 **Set** 接口不保证它将以任何特定顺序维护其元素。 |\n", - "| **HashSet\\*** | 注重快速查找元素的集合,其中元素必须定义 `hashCode()` 和 `equals()` 方法。 |\n", - "| **TreeSet** | 由树支持的有序 **Set**。这样,就可以从 **Set** 中获取有序序列,其中元素必须实现 **Comparable** 接口。 |\n", - "| **LinkedHashSet** | 具有 **HashSet** 的查找速度,但在内部使用链表维护元素的插入顺序。因此,当在遍历 **Set** 时,结果将按元素的插入顺序显示。元素必须定义 `hashCode()` 和 `equals()` 方法。 |\n", - "\n", - "**HashSet** 上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。\n", - "\n", - "定义 `hashCode()` 方法在[附录:理解equals和hashCode方法]()中进行了描述。必须为散列和树存储结构创建 `equals()` 方法,但只有当把类放在 **HashSet** 中时才需要 `hashCode()` (当然这很有可能,因为 **HashSet** 通常应该是作为 **Set** 实现的首选)或 **LinkedHashSet** 。 但是,作为一种良好的编程风格,在覆盖 `equals()` 时应始终覆盖 `hashCode()` 。\n", - "\n", - "下面的示例演示了成功使用具有特定 **Set** 实现的类型所需的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/TypesForSets.java\n", - "// Methods necessary to put your own type in a Set\n", - "import java.util.*;\n", - "import java.util.function.*;\n", - "import java.util.Objects;\n", - "\n", - "class SetType {\n", - " protected int i;\n", - " SetType(int n) { i = n; }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof SetType &&\n", - " Objects.equals(i, ((SetType)o).i);\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return Integer.toString(i);\n", - " }\n", - "}\n", - "\n", - "class HashType extends SetType {\n", - " HashType(int n) { super(n); }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hashCode(i);\n", - " }\n", - "}\n", - "\n", - "class TreeType extends SetType\n", - "implements Comparable {\n", - " TreeType(int n) { super(n); }\n", - " @Override\n", - " public int compareTo(TreeType arg) {\n", - " return Integer.compare(arg.i, i);\n", - " // Equivalent to:\n", - " // return arg.i < i ? -1 : (arg.i == i ? 0 : 1);\n", - " }\n", - "}\n", - "\n", - "public class TypesForSets {\n", - " static void\n", - " fill(Set set, Function type) {\n", - " for(int i = 10; i >= 5; i--) // Descending\n", - " set.add(type.apply(i));\n", - " for(int i = 0; i < 5; i++) // Ascending\n", - " set.add(type.apply(i));\n", - " }\n", - " static void\n", - " test(Set set, Function type) {\n", - " fill(set, type);\n", - " fill(set, type); // Try to add duplicates\n", - " fill(set, type);\n", - " System.out.println(set);\n", - " }\n", - " public static void main(String[] args) {\n", - " test(new HashSet<>(), HashType::new);\n", - " test(new LinkedHashSet<>(), HashType::new);\n", - " test(new TreeSet<>(), TreeType::new);\n", - " // Things that don't work:\n", - " test(new HashSet<>(), SetType::new);\n", - " test(new HashSet<>(), TreeType::new);\n", - " test(new LinkedHashSet<>(), SetType::new);\n", - " test(new LinkedHashSet<>(), TreeType::new);\n", - " try {\n", - " test(new TreeSet<>(), SetType::new);\n", - " } catch(Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " try {\n", - " test(new TreeSet<>(), HashType::new);\n", - " } catch(Exception e) {\n", - " System.out.println(e.getMessage());\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n", - "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", - "[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]\n", - "[1, 6, 8, 6, 2, 7, 8, 9, 4, 10, 7, 5, 1, 3, 4, 9, 9,\n", - "10, 5, 3, 2, 0, 4, 1, 2, 0, 8, 3, 0, 10, 6, 5, 7]\n", - "[3, 1, 4, 8, 7, 6, 9, 5, 3, 0, 10, 5, 5, 10, 7, 8, 8,\n", - "9, 1, 4, 10, 2, 6, 9, 1, 6, 0, 3, 2, 0, 7, 2, 4]\n", - "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5,\n", - "0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", - "[10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5,\n", - "0, 1, 2, 3, 4, 10, 9, 8, 7, 6, 5, 0, 1, 2, 3, 4]\n", - "SetType cannot be cast to java.lang.Comparable\n", - "HashType cannot be cast to java.lang.Comparable\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了证明特定 **Set** 需要哪些方法,同时避免代码重复,这里创建了三个类。基类 **SetType** 存储一个 **int** 值,并通过 `toString()` 方法打印它。由于存储在 **Set** 中的所有类都必须具有 `equals()` ,因此该方法也放在基类中。基于 `int i` 来判断元素是否相等。\n", - "\n", - "**HashType** 继承自 **SetType** ,并添加了 `hashCode()` 方法,该方法对于 **Set** 的散列实现是必需的。\n", - "\n", - "要在任何类型的有序集合中使用对象,由 **TreeType** 实现的 **Comparable** 接口都是必需的,例如 **SortedSet** ( **TreeSet** 是其唯一实现)。在 `compareTo()` 中,请注意我没有使用“简单明了”的形式: `return i-i2` 。虽然这是一个常见的编程错误,但只有当 **i** 和 **i2** 是“无符号(unsigned)”整型时才能正常工作(如果 Java 有一个“unsigned”关键字的话,不过它没有)。它破坏了 Java 的有符号 **int** ,它不足以代表两个有符号整数的差异。如果 **i** 是一个大的正整数而 **j** 是一个大的负整数, `i-j` 将溢出并返回一个负值,这不是我们所需要的。\n", - "\n", - "通常希望 `compareTo()` 方法生成与 `equals()` 方法一致的自然顺序。如果 `equals()` 对于特定比较产生 **true**,则 `compareTo()` 应该为该比较返回结果 零,并且如果 `equals()` 为比较产生 **false** ,则 `compareTo()` 应该为该比较产生非零结果。\n", - "\n", - "在 **TypesForSets** 中, `fill()` 和 `test()` 都是使用泛型定义的,以防止代码重复。为了验证 **Set** 的行为, `test()` 在测试集上调用 `fill()` 三次,尝试引入重复的对象。 `fill()` 方法的参数可以接收任意一个 **Set** 类型,以及生成该类型的 **Function** 对象。因为此示例中使用的所有对象都有一个带有单个 **int** 参数的构造方法,所以可以将构造方法作为此 **Function** 传递,它将提供用于填充 **Set** 的对象。\n", - "\n", - "请注意, `fill()` 方法按降序添加前五个元素,按升序添加后五个元素,以此来指出生成的存储顺序。输出显示 **HashSet** 按升序保留元素,但是,在[附录:理解equals和hashCode方法]()中,你会发现这只是偶然的,因为散列会创建自己的存储顺序。这里只是因为元素是一个简单的 **int** ,在这种情况下它是升序的。 **LinkedHashSet** 按照插入顺序保存元素,**TreeSet** 按排序顺序维护元素(在此示例中因为 `compareTo()` 的实现方式,所以元素按降序排列。)\n", - "\n", - "特定的 **Set** 类型一般都有所必需的操作,如果尝试使用没能正确支持这些操作的类型,那么事情就会出错。将没有重新定义 `hashCode()` 方法的 **SetType** 或 **TreeType** 对象放入任何散列实现会导致重复值,因此违反了 **Set** 的主要契约。 这是相当令人不安的,因为这甚至不产生运行时错误。但是,默认的 `hashCode()` 是合法的,所以即使它是不正确的,这也是合法的行为。确保此类程序正确性的唯一可靠方法是将单元测试合并到构建系统中。\n", - "\n", - "如果尝试在 **TreeSet** 中使用没有实现 **Comparable** 接口的类型,则会得到更明确的结果:当 **TreeSet** 尝试将对象用作一个 **Comparable** 时,将会抛出异常。\n", - "\n", - "\n", - "### SortedSet\n", - "\n", - "**SortedSet** 中的元素保证按排序规则顺序, **SortedSet** 接口中的以下方法可以产生其他功能:\n", - "\n", - "- `Comparator comparator()` :生成用于此 **Set** 的**Comparator** 或 **null** 来用于自然排序。\n", - "- `Object first()` :返回第一个元素。\n", - "- `Object last()` :返回最后一个元素。\n", - "- `SortedSet subSet(fromElement,toElement)` :使用 **fromElement** (包含)和 **toElement** (不包括)中的元素生成此 **Set** 的一个视图。\n", - "- `SortedSet headSet(toElement)` :使用顺序在 **toElement** 之前的元素生成此 **Set** 的一个视图。\n", - "- `SortedSet tailSet(fromElement)` :使用顺序在 **fromElement** 之后(包含 **fromElement** )的元素生成此 **Set** 的一个视图。\n", - "\n", - "下面是一个简单的演示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/SortedSetDemo.java\n", - "import java.util.*;\n", - "import static java.util.stream.Collectors.*;\n", - "\n", - "public class SortedSetDemo {\n", - " public static void main(String[] args) {\n", - " SortedSet sortedSet =\n", - " Arrays.stream(\n", - " \"one two three four five six seven eight\"\n", - " .split(\" \"))\n", - " .collect(toCollection(TreeSet::new));\n", - " System.out.println(sortedSet);\n", - " String low = sortedSet.first();\n", - " String high = sortedSet.last();\n", - " System.out.println(low);\n", - " System.out.println(high);\n", - " Iterator it = sortedSet.iterator();\n", - " for(int i = 0; i <= 6; i++) {\n", - " if(i == 3) low = it.next();\n", - " if(i == 6) high = it.next();\n", - " else it.next();\n", - " }\n", - " System.out.println(low);\n", - " System.out.println(high);\n", - " System.out.println(sortedSet.subSet(low, high));\n", - " System.out.println(sortedSet.headSet(high));\n", - " System.out.println(sortedSet.tailSet(low));\n", - " }\n", - "}\n", - "/* Output:\n", - "[eight, five, four, one, seven, six, three, two]\n", - "eight\n", - "two\n", - "one\n", - "two\n", - "[one, seven, six, three]\n", - "[eight, five, four, one, seven, six, three]\n", - "[one, seven, six, three, two]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意, **SortedSet** 表示“根据对象的比较函数进行排序”,而不是“根据插入顺序”。可以使用 **LinkedHashSet** 保留元素的插入顺序。\n", - "\n", - "\n", - "## 队列\n", - "\n", - "有许多 **Queue** 实现,其中大多数是为并发应用程序设计的。许多实现都是通过排序行为而不是性能来区分的。这是一个涉及大多数 **Queue** 实现的基本示例,包括基于并发的队列。队列将元素从一端放入并从另一端取出:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/QueueBehavior.java\n", - "// Compares basic behavior\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class QueueBehavior {\n", - " static Stream strings() {\n", - " return Arrays.stream(\n", - " (\"one two three four five six seven \" +\n", - " \"eight nine ten\").split(\" \"));\n", - " }\n", - " static void test(int id, Queue queue) {\n", - " System.out.print(id + \": \");\n", - " strings().map(queue::offer).count();\n", - " while(queue.peek() != null)\n", - " System.out.print(queue.remove() + \" \");\n", - " System.out.println();\n", - " }\n", - " public static void main(String[] args) {\n", - " int count = 10;\n", - " test(1, new LinkedList<>());\n", - " test(2, new PriorityQueue<>());\n", - " test(3, new ArrayBlockingQueue<>(count));\n", - " test(4, new ConcurrentLinkedQueue<>());\n", - " test(5, new LinkedBlockingQueue<>());\n", - " test(6, new PriorityBlockingQueue<>());\n", - " test(7, new ArrayDeque<>());\n", - " test(8, new ConcurrentLinkedDeque<>());\n", - " test(9, new LinkedBlockingDeque<>());\n", - " test(10, new LinkedTransferQueue<>());\n", - " test(11, new SynchronousQueue<>());\n", - " }\n", - "}\n", - "/* Output:\n", - "1: one two three four five six seven eight nine ten\n", - "2: eight five four nine one seven six ten three two\n", - "3: one two three four five six seven eight nine ten\n", - "4: one two three four five six seven eight nine ten\n", - "5: one two three four five six seven eight nine ten\n", - "6: eight five four nine one seven six ten three two\n", - "7: one two three four five six seven eight nine ten\n", - "8: one two three four five six seven eight nine ten\n", - "9: one two three four five six seven eight nine ten\n", - "10: one two three four five six seven eight nine ten\n", - "11:\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Deque** 接口也继承自 **Queue** 。 除优先级队列外,**Queue** 按照元素的插入顺序生成元素。 在此示例中,**SynchronousQueue** 不会产生任何结果,因为它是一个阻塞队列,其中每个插入操作必须等待另一个线程执行相应的删除操作,反之亦然。\n", - "\n", - "\n", - "### 优先级队列\n", - "\n", - "考虑一个待办事项列表,其中每个对象包含一个 **String** 以及主要和次要优先级值。通过实现 **Comparable** 接口来控制此待办事项列表的顺序:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/ToDoList.java\n", - "// A more complex use of PriorityQueue\n", - "import java.util.*;\n", - "\n", - "class ToDoItem implements Comparable {\n", - " private char primary;\n", - " private int secondary;\n", - " private String item;\n", - " ToDoItem(String td, char pri, int sec) {\n", - " primary = pri;\n", - " secondary = sec;\n", - " item = td;\n", - " }\n", - " @Override\n", - " public int compareTo(ToDoItem arg) {\n", - " if(primary > arg.primary)\n", - " return +1;\n", - " if(primary == arg.primary)\n", - " if(secondary > arg.secondary)\n", - " return +1;\n", - " else if(secondary == arg.secondary)\n", - " return 0;\n", - " return -1;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return Character.toString(primary) +\n", - " secondary + \": \" + item;\n", - " }\n", - "}\n", - "\n", - "class ToDoList {\n", - " public static void main(String[] args) {\n", - " PriorityQueue toDo =\n", - " new PriorityQueue<>();\n", - " toDo.add(new ToDoItem(\"Empty trash\", 'C', 4));\n", - " toDo.add(new ToDoItem(\"Feed dog\", 'A', 2));\n", - " toDo.add(new ToDoItem(\"Feed bird\", 'B', 7));\n", - " toDo.add(new ToDoItem(\"Mow lawn\", 'C', 3));\n", - " toDo.add(new ToDoItem(\"Water lawn\", 'A', 1));\n", - " toDo.add(new ToDoItem(\"Feed cat\", 'B', 1));\n", - " while(!toDo.isEmpty())\n", - " System.out.println(toDo.remove());\n", - " }\n", - "}\n", - "/* Output:\n", - "A1: Water lawn\n", - "A2: Feed dog\n", - "B1: Feed cat\n", - "B7: Feed bird\n", - "C3: Mow lawn\n", - "C4: Empty trash\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这展示了通过优先级队列自动排序待办事项。\n", - "\n", - "\n", - "### 双端队列\n", - "\n", - "**Deque** (双端队列)就像一个队列,但是可以从任一端添加和删除元素。 Java 6为 **Deque** 添加了一个显式接口。以下是对实现了 **Deque** 的类的最基本的 **Deque** 方法的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/SimpleDeques.java\n", - "// Very basic test of Deques\n", - "import java.util.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.function.*;\n", - "\n", - "class CountString implements Supplier {\n", - " private int n = 0;\n", - " CountString() {}\n", - " CountString(int start) { n = start; }\n", - " @Override\n", - " public String get() {\n", - " return Integer.toString(n++);\n", - " }\n", - "}\n", - "\n", - "public class SimpleDeques {\n", - " static void test(Deque deque) {\n", - " CountString s1 = new CountString(),\n", - " s2 = new CountString(20);\n", - " for(int n = 0; n < 8; n++) {\n", - " deque.offerFirst(s1.get());\n", - " deque.offerLast(s2.get()); // Same as offer()\n", - " }\n", - " System.out.println(deque);\n", - " String result = \"\";\n", - " while(deque.size() > 0) {\n", - " System.out.print(deque.peekFirst() + \" \");\n", - " result += deque.pollFirst() + \" \";\n", - " System.out.print(deque.peekLast() + \" \");\n", - " result += deque.pollLast() + \" \";\n", - " }\n", - " System.out.println(\"\\n\" + result);\n", - " }\n", - " public static void main(String[] args) {\n", - " int count = 10;\n", - " System.out.println(\"LinkedList\");\n", - " test(new LinkedList<>());\n", - " System.out.println(\"ArrayDeque\");\n", - " test(new ArrayDeque<>());\n", - " System.out.println(\"LinkedBlockingDeque\");\n", - " test(new LinkedBlockingDeque<>(count));\n", - " System.out.println(\"ConcurrentLinkedDeque\");\n", - " test(new ConcurrentLinkedDeque<>());\n", - " }\n", - "}\n", - "/* Output:\n", - "LinkedList\n", - "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", - "27]\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "ArrayDeque\n", - "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", - "27]\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "LinkedBlockingDeque\n", - "[4, 3, 2, 1, 0, 20, 21, 22, 23, 24]\n", - "4 24 3 23 2 22 1 21 0 20\n", - "4 24 3 23 2 22 1 21 0 20\n", - "ConcurrentLinkedDeque\n", - "[7, 6, 5, 4, 3, 2, 1, 0, 20, 21, 22, 23, 24, 25, 26,\n", - "27]\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "7 27 6 26 5 25 4 24 3 23 2 22 1 21 0 20\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我只使用了 **Deque** 方法的“offer”和“poll”版本,因为当 **LinkedBlockingDeque** 的大小有限时,这些方法不会抛出异常。请注意, **LinkedBlockingDeque** 仅填充到它的限制大小为止,然后忽略额外的添加。\n", - "\n", - "\n", - "## 理解Map\n", - "\n", - "正如在[第十二章 集合]()章节中所了解到的,**Map**(也称为 *关联数组* )维护键值关联(对),因此可以使用键来查找值。标准 Java 库包含不同的 **Map** 基本实现,例如 **HashMap** , **TreeMap** , **LinkedHashMap** , **WeakHashMap** , **ConcurrentHashMap** 和 **IdentityHashMap** 。 它们都具有相同的基本 **Map** 接口,但它们的行为不同,包括效率,键值对的保存顺序和呈现顺序,保存对象的时间,如何在多线程程序中工作,以及如何确定键的相等性。 **Map** 接口的实现数量应该告诉你一些关于此工具重要性的信息。\n", - "\n", - "为了更深入地了解 **Map** ,学习如何构造关联数组会很有帮助。下面是一个非常简单的实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/AssociativeArray.java\n", - "// Associates keys with values\n", - "\n", - "public class AssociativeArray {\n", - " private Object[][] pairs;\n", - " private int index;\n", - " public AssociativeArray(int length) {\n", - " pairs = new Object[length][2];\n", - " }\n", - " public void put(K key, V value) {\n", - " if(index >= pairs.length)\n", - " throw new ArrayIndexOutOfBoundsException();\n", - " pairs[index++] = new Object[]{ key, value };\n", - " }\n", - " @SuppressWarnings(\"unchecked\")\n", - " public V get(K key) {\n", - " for(int i = 0; i < index; i++)\n", - " if(key.equals(pairs[i][0]))\n", - " return (V)pairs[i][1];\n", - " return null; // Did not find key\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " StringBuilder result = new StringBuilder();\n", - " for(int i = 0; i < index; i++) {\n", - " result.append(pairs[i][0].toString());\n", - " result.append(\" : \");\n", - " result.append(pairs[i][1].toString());\n", - " if(i < index - 1)\n", - " result.append(\"\\n\");\n", - " }\n", - " return result.toString();\n", - " }\n", - " public static void main(String[] args) {\n", - " AssociativeArray map =\n", - " new AssociativeArray<>(6);\n", - " map.put(\"sky\", \"blue\");\n", - " map.put(\"grass\", \"green\");\n", - " map.put(\"ocean\", \"dancing\");\n", - " map.put(\"tree\", \"tall\");\n", - " map.put(\"earth\", \"brown\");\n", - " map.put(\"sun\", \"warm\");\n", - " try {\n", - " map.put(\"extra\", \"object\"); // Past the end\n", - " } catch(ArrayIndexOutOfBoundsException e) {\n", - " System.out.println(\"Too many objects!\");\n", - " }\n", - " System.out.println(map);\n", - " System.out.println(map.get(\"ocean\"));\n", - " }\n", - "}\n", - "/* Output:\n", - "Too many objects!\n", - "sky : blue\n", - "grass : green\n", - "ocean : dancing\n", - "tree : tall\n", - "earth : brown\n", - "sun : warm\n", - "dancing\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "关联数组中的基本方法是 `put()` 和 `get()` ,但为了便于显示,重写了 `toString()` 方法以打印键值对。为了显示它的工作原理,主方法加载一个带有字符串对的 **AssociativeArray** 并打印生成的映射,然后调用其中一个值的 `get()` 方法。\n", - "\n", - "要使用 `get()` 方法,可以传入要查找的 **key** ,它将生成相关联的值作为结果,如果找不到则返回 **null** 。 `get()` 方法使用可能是效率最低的方法来定位值:从数组的头部开始并使用 `equals()` 来比较键。但这里是侧重于简单,而不是效率。\n", - "\n", - "这个版本很有启发性,但它不是很有效,而且它只有一个固定的大小,这是不灵活的。幸运的是, **java.util** 中的那些 **Map** 没有这些问题。\n", - "\n", - "\n", - "### 性能\n", - "\n", - "性能是 **Map** 的基本问题,在 `get()` 中使用线性方法搜索一个键时会非常慢。这就是 **HashMap** 要加速的地方。它使用一个称为 *哈希码* 的特殊值来替代慢速搜索一个键。哈希码是一种从相关对象中获取一些信息并将其转换为该对象的“相对唯一” **int** 的方法。 `hashCode()` 是根类 **Object** 中的一个方法,因此所有 Java 对象都可以生成哈希码。 **HashMap** 获取对象的 `hashCode()` 并使用它来快速搜索键。这就使得性能有了显著的提升。[^3]\n", - "\n", - "以下是基本的 **Map** 实现。 **HashMap**上的星号表示,在没有其他约束的情况下,这应该是你的默认选择,因为它针对速度进行了优化。其他实现强调其他特性,因此不如 **HashMap** 快。\n", - "\n", - "| **Map** 实现 | 描述 |\n", - "| :---: | :--- |\n", - "| **HashMap\\*** | 基于哈希表的实现。(使用此类来代替 **Hashtable** 。)为插入和定位键值对提供了常数时间性能。可以通过构造方法调整性能,这些构造方法允许你设置哈希表的容量和装填因子。 |\n", - "| **LinkedHashMap** | 与 **HashMap** 类似,但是当遍历时,可以按插入顺序或最近最少使用(LRU)顺序获取键值对。只比 **HashMap** 略慢,一个例外是在迭代时,由于其使用链表维护内部顺序,所以会更快些。 |\n", - "| **TreeMap** | 基于红黑树的实现。当查看键或键值对时,它们按排序顺序(由 **Comparable** 或 **Comparator** 确定)。 **TreeMap** 的侧重点是按排序顺序获得结果。 **TreeMap** 是唯一使用 `subMap()` 方法的 **Map** ,它返回红黑树的一部分。 |\n", - "| **WeakHashMap** | 一种具有 *弱键*(weak keys) 的 **Map** ,为了解决某些类型的问题,它允许释放 **Map** 所引用的对象。如果在 **Map** 外没有对特定键的引用,则可以对该键进行垃圾回收。 |\n", - "| **ConcurrentHashMap** | 不使用同步锁定的线程安全 **Mao** 。这在[第二十四章 并发编程]() 一章中讨论。 |\n", - "| **IdentityHashMap** | 使用 `==` 而不是 `equals()` 来比较键。仅用于解决特殊问题,不适用于一般用途。 |\n", - "\n", - "散列是在 **Map** 中存储元素的最常用方法。\n", - "\n", - "**Map** 中使用的键的要求与 **Set** 中的元素的要求相同。可以在 **TypesForSets.java** 中看到这些。任何键必须具有 `equals()` 方法。如果键用于散列映射,则它还必须具有正确的 `hashCode()` 方法。如果键在 **TreeMap** 中使用,则必须实现 **Comparable** 接口。\n", - "\n", - "以下示例使用先前定义的 **CountMap** 测试数据集显示通过 **Map** 接口可用的操作:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/MapOps.java\n", - "// Things you can do with Maps\n", - "import java.util.concurrent.*;\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class MapOps {\n", - " public static\n", - " void printKeys(Map map) {\n", - " System.out.print(\"Size = \" + map.size() + \", \");\n", - " System.out.print(\"Keys: \");\n", - " // Produce a Set of the keys:\n", - " System.out.println(map.keySet());\n", - " }\n", - " public static\n", - " void test(Map map) {\n", - " System.out.println(\n", - " map.getClass().getSimpleName());\n", - " map.putAll(new CountMap(25));\n", - " // Map has 'Set' behavior for keys:\n", - " map.putAll(new CountMap(25));\n", - " printKeys(map);\n", - " // Producing a Collection of the values:\n", - " System.out.print(\"Values: \");\n", - " System.out.println(map.values());\n", - " System.out.println(map);\n", - " System.out.println(\"map.containsKey(11): \" +\n", - " map.containsKey(11));\n", - " System.out.println(\n", - " \"map.get(11): \" + map.get(11));\n", - " System.out.println(\"map.containsValue(\\\"F0\\\"): \"\n", - " + map.containsValue(\"F0\"));\n", - " Integer key = map.keySet().iterator().next();\n", - " System.out.println(\"First key in map: \" + key);\n", - " map.remove(key);\n", - " printKeys(map);\n", - " map.clear();\n", - " System.out.println(\n", - " \"map.isEmpty(): \" + map.isEmpty());\n", - " map.putAll(new CountMap(25));\n", - " // Operations on the Set change the Map:\n", - " map.keySet().removeAll(map.keySet());\n", - " System.out.println(\n", - " \"map.isEmpty(): \" + map.isEmpty());\n", - " }\n", - " public static void main(String[] args) {\n", - " test(new HashMap<>());\n", - " test(new TreeMap<>());\n", - " test(new LinkedHashMap<>());\n", - " test(new IdentityHashMap<>());\n", - " test(new ConcurrentHashMap<>());\n", - " test(new WeakHashMap<>());\n", - " }\n", - "}\n", - "/* Output: (First 11 Lines)\n", - "HashMap\n", - "Size = 25, Keys: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n", - "12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]\n", - "Values: [A0, B0, C0, D0, E0, F0, G0, H0, I0, J0, K0,\n", - "L0, M0, N0, O0, P0, Q0, R0, S0, T0, U0, V0, W0, X0, Y0]\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", - "9=J0, 10=K0, 11=L0, 12=M0, 13=N0, 14=O0, 15=P0, 16=Q0,\n", - "17=R0, 18=S0, 19=T0, 20=U0, 21=V0, 22=W0, 23=X0, 24=Y0}\n", - "map.containsKey(11): true\n", - "map.get(11): L0\n", - "map.containsValue(\"F0\"): true\n", - "First key in map: 0\n", - "Size = 24, Keys: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,\n", - "12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]\n", - "map.isEmpty(): true\n", - "map.isEmpty(): true\n", - " ...\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`printKeys()` 方法演示了如何生成 **Map** 的 **Collection** 视图。 `keySet()` 方法生成一个由 **Map** 中的键组成的 **Set** 。 打印 `values()` 方法的结果会生成一个包含 **Map** 中所有值的 **Collection** 。(请注意,键必须是唯一的,但值可以包含重复项。)由于这些 **Collection** 由 **Map** 支持,因此 **Collection** 中的任何更改都会反映在所关联的 **Map** 中。\n", - "\n", - "程序的其余部分提供了每个 **Map** 操作的简单示例,并测试了每种基本类型的 **Map** 。\n", - "\n", - "\n", - "### SortedMap\n", - "\n", - "使用 **SortedMap** (由 **TreeMap** 或 **ConcurrentSkipListMap** 实现),键保证按排序顺序,这允许在 **SortedMap** 接口中使用这些方法来提供其他功能:\n", - "\n", - "- `Comparator comparator()` :生成用于此 **Map** 的比较器, **null** 表示自然排序。\n", - "- `T firstKey()` :返回第一个键。\n", - "- `T lastKey()` :返回最后一个键。\n", - "- `SortedMap subMap(fromKey,toKey)` :生成此 **Map** 的视图,其中键从 **fromKey**(包括),到 **toKey** (不包括)。\n", - "- `SortedMap headMap(toKey)` :使用小于 **toKey** 的键生成此 **Map** 的视图。\n", - "- `SortedMap tailMap(fromKey)` :使用大于或等于 **fromKey** 的键生成此 **Map** 的视图。\n", - "\n", - "这是一个类似于 **SortedSetDemo.java** 的示例,显示了 **TreeMap** 的这种额外行为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/SortedMapDemo.java\n", - "// What you can do with a TreeMap\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class SortedMapDemo {\n", - " public static void main(String[] args) {\n", - " TreeMap sortedMap =\n", - " new TreeMap<>(new CountMap(10));\n", - " System.out.println(sortedMap);\n", - " Integer low = sortedMap.firstKey();\n", - " Integer high = sortedMap.lastKey();\n", - " System.out.println(low);\n", - " System.out.println(high);\n", - " Iterator it =\n", - " sortedMap.keySet().iterator();\n", - " for(int i = 0; i <= 6; i++) {\n", - " if(i == 3) low = it.next();\n", - " if(i == 6) high = it.next();\n", - " else it.next();\n", - " }\n", - " System.out.println(low);\n", - " System.out.println(high);\n", - " System.out.println(sortedMap.subMap(low, high));\n", - " System.out.println(sortedMap.headMap(high));\n", - " System.out.println(sortedMap.tailMap(low));\n", - " }\n", - "}\n", - "/* Output:\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0,\n", - "9=J0}\n", - "0\n", - "9\n", - "3\n", - "7\n", - "{3=D0, 4=E0, 5=F0, 6=G0}\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0}\n", - "{3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0, 9=J0}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里,键值对按照键的排序顺序进行排序。因为 **TreeMap** 中存在顺序感,所以“位置”的概念很有意义,因此可以拥有第一个、最后一个元素或子图。\n", - "\n", - "\n", - "### LinkedHashMap\n", - "\n", - "**LinkedHashMap** 针对速度进行哈希处理,但在遍历期间也会按插入顺序生成键值对( `System.out.println()` 可以遍历它,因此可以看到遍历的结果)。 此外,可以在构造方法中配置 **LinkedHashMap** 以使用基于访问的 *最近最少使用*(LRU) 算法,因此未访问的元素(因此是删除的候选者)会出现在列表的前面。 这样可以轻松创建一个能够定期清理以节省空间的程序。下面是一个显示这两个功能的简单示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/LinkedHashMapDemo.java\n", - "// What you can do with a LinkedHashMap\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class LinkedHashMapDemo {\n", - " public static void main(String[] args) {\n", - " LinkedHashMap linkedMap =\n", - " new LinkedHashMap<>(new CountMap(9));\n", - " System.out.println(linkedMap);\n", - " // Least-recently-used order:\n", - " linkedMap =\n", - " new LinkedHashMap<>(16, 0.75f, true);\n", - " linkedMap.putAll(new CountMap(9));\n", - " System.out.println(linkedMap);\n", - " for(int i = 0; i < 6; i++)\n", - " linkedMap.get(i);\n", - " System.out.println(linkedMap);\n", - " linkedMap.get(0);\n", - " System.out.println(linkedMap);\n", - " }\n", - "}\n", - "/* Output:\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}\n", - "{0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 6=G0, 7=H0, 8=I0}\n", - "{6=G0, 7=H0, 8=I0, 0=A0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0}\n", - "{6=G0, 7=H0, 8=I0, 1=B0, 2=C0, 3=D0, 4=E0, 5=F0, 0=A0}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些键值对确实是按照插入顺序进行遍历,即使对于LRU版本也是如此。 但是,在LRU版本中访问前六项(仅限)后,最后三项将移至列表的前面。然后,当再次访问“ **0** ”后,它移动到了列表的后面。\n", - "\n", - "\n", - "## 集合工具类\n", - "\n", - "集合有许多独立的实用工具程序,在 **java.util.Collections** 中表示为静态方法。之前已经见过其中一些,例如 `addAll()` , `reverseOrder()` 和 `binarySearch()` 。以下是其他内容(同步和不可修改的实用工具程序将在后面的章节中介绍)。在此表中,在需要的时候使用了泛型:\n", - "\n", - "| 方法 | 描述 |\n", - "| :--- | :--- |\n", - "| **checkedCollection(Collection\\ c, Class\\ type)**

**checkedList(List\\ list, Class\\ type)**

**checkedMap(Map\\ m, Class\\ keyType, Class\\ valueType)**

**checkedSet(Set\\ s, Class\\ type)**

**checkedSortedMap(SortedMap\\ m, Class\\ keyType, Class\\ valueType)**

**checkedSortedSet(SortedSet\\ s, Class\\ type)** | 生成 **Collection** 的动态类型安全视图或 **Collection** 的特定子类型。 当无法使用静态检查版本时使用这个版本。

这些方法的使用在[第九章 多态]()章节的“动态类型安全”标题下进行了展示。 |\n", - "| **max(Collection)**

**min(Collection)** | 使用 **Collection** 中对象的自然比较方法生成参数集合中的最大或最小元素。 |\n", - "| **max(Collection, Comparator)**

**min(Collection, Comparator)** | 使用 **Comparator** 指定的比较方法生成参数集合中的最大或最小元素。 |\n", - "| **indexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内第一次出现的起始索引,如果不存在则返回 -1。 |\n", - "| **lastIndexOfSubList(List source, List target)** | 返回 **target** 在 **source** 内最后一次出现的起始索引,如果不存在则返回 -1。 |\n", - "| **replaceAll(List\\ list, T oldVal, T newVal)** | 用 **newVal** 替换列表中所有的 **oldVal** 。 |\n", - "| **reverse(List)**| 反转列表 |\n", - "| **reverseOrder()**

**reverseOrder(Comparator\\)** | 返回一个 **Comparator** ,它与集合中实现了 **comparable\\** 接口的对象的自然顺序相反。第二个版本颠倒了所提供的 **Comparator** 的顺序。 |\n", - "| **rotate(List, int distance)** | 将所有元素向前移动 **distance** ,将尾部的元素移到开头。(译者注:即循环移动) |\n", - "| **shuffle(List)**

**shuffle(List, Random)** | 随机置换指定列表(即打乱顺序)。第一个版本使用了默认的随机化源,或者也可以使用第二个版本,提供自己的随机化源。 |\n", - "| **sort(List\\)**

**sort(List\\, Comparator\\ c)** | 第一个版本使用元素的自然顺序排序该 **List\\** 。第二个版本根据提供的 **Comparator** 排序。 |\n", - "| **copy(List\\ dest, List\\ src)** | 将 **src** 中的元素复制到 **dest** 。 |\n", - "| **swap(List, int i, int j)** | 交换 **List** 中位置 **i** 和 位置 **j** 的元素。可能比你手工编写的速度快。 |\n", - "| **fill(List\\, T x)** | 用 **x** 替换 **List** 中的所有元素。|\n", - "| **nCopies(int n, T x)** | 返回大小为 **n** 的不可变 **List\\** ,其引用都指向 **x** 。 |\n", - "| **disjoint(Collection, Collection)** | 如果两个集合没有共同元素,则返回 **true** 。 |\n", - "| **frequency(Collection, Object x)** | 返回 **Collection** 中,等于 **x** 的元素个数。 |\n", - "| **emptyList()**

**emptyMap()**

**emptySet()** | 返回不可变的空 **List** , **Map** 或 **Set** 。这些是泛型的,因此生成的 **Collection** 可以被参数化为所需的类型。 |\n", - "| **singleton(T x)**

**singletonList(T x)**

**singletonMap(K key, V value)** | 生成一个不可变的 **List** , **Set** 或 **Map** ,其中只包含基于给定参数的单个元素。 |\n", - "| **list(Enumeration\\ e)** | 生成一个 **ArrayList\\** ,其中元素为(旧式) **Enumeration** ( **Iterator** 的前身)中的元素。用于从遗留代码向新式转换。 |\n", - "| **enumeration(Collection\\)** | 为参数集合生成一个旧式的 **Enumeration\\** 。 |\n", - "\n", - "请注意, `min()` 和 `max()` 使用 **Collection** 对象,而不使用 **List** ,因此不必担心是否应对 **Collection** 进行排序。(如前所述,在执行 `binarySearch()` 之前,将会对 **List** 或数组进行`sort()` 排序。)\n", - "\n", - "下面是一个示例,展示了上表中大多数实用工具程序的基本用法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Utilities.java\n", - "// Simple demonstrations of the Collections utilities\n", - "import java.util.*;\n", - "\n", - "public class Utilities {\n", - " static List list = Arrays.asList(\n", - " \"one Two three Four five six one\".split(\" \"));\n", - " public static void main(String[] args) {\n", - " System.out.println(list);\n", - " System.out.println(\"'list' disjoint (Four)?: \" +\n", - " Collections.disjoint(list,\n", - " Collections.singletonList(\"Four\")));\n", - " System.out.println(\n", - " \"max: \" + Collections.max(list));\n", - " System.out.println(\n", - " \"min: \" + Collections.min(list));\n", - " System.out.println(\n", - " \"max w/ comparator: \" + Collections.max(list,\n", - " String.CASE_INSENSITIVE_ORDER));\n", - " System.out.println(\n", - " \"min w/ comparator: \" + Collections.min(list,\n", - " String.CASE_INSENSITIVE_ORDER));\n", - " List sublist =\n", - " Arrays.asList(\"Four five six\".split(\" \"));\n", - " System.out.println(\"indexOfSubList: \" +\n", - " Collections.indexOfSubList(list, sublist));\n", - " System.out.println(\"lastIndexOfSubList: \" +\n", - " Collections.lastIndexOfSubList(list, sublist));\n", - " Collections.replaceAll(list, \"one\", \"Yo\");\n", - " System.out.println(\"replaceAll: \" + list);\n", - " Collections.reverse(list);\n", - " System.out.println(\"reverse: \" + list);\n", - " Collections.rotate(list, 3);\n", - " System.out.println(\"rotate: \" + list);\n", - " List source =\n", - " Arrays.asList(\"in the matrix\".split(\" \"));\n", - " Collections.copy(list, source);\n", - " System.out.println(\"copy: \" + list);\n", - " Collections.swap(list, 0, list.size() - 1);\n", - " System.out.println(\"swap: \" + list);\n", - " Collections.shuffle(list, new Random(47));\n", - " System.out.println(\"shuffled: \" + list);\n", - " Collections.fill(list, \"pop\");\n", - " System.out.println(\"fill: \" + list);\n", - " System.out.println(\"frequency of 'pop': \" +\n", - " Collections.frequency(list, \"pop\"));\n", - " List dups =\n", - " Collections.nCopies(3, \"snap\");\n", - " System.out.println(\"dups: \" + dups);\n", - " System.out.println(\"'list' disjoint 'dups'?: \" +\n", - " Collections.disjoint(list, dups));\n", - " // Getting an old-style Enumeration:\n", - " Enumeration e =\n", - " Collections.enumeration(dups);\n", - " Vector v = new Vector<>();\n", - " while(e.hasMoreElements())\n", - " v.addElement(e.nextElement());\n", - " // Converting an old-style Vector\n", - " // to a List via an Enumeration:\n", - " ArrayList arrayList =\n", - " Collections.list(v.elements());\n", - " System.out.println(\"arrayList: \" + arrayList);\n", - " }\n", - "}\n", - "/* Output:\n", - "[one, Two, three, Four, five, six, one]\n", - "'list' disjoint (Four)?: false\n", - "max: three\n", - "min: Four\n", - "max w/ comparator: Two\n", - "min w/ comparator: five\n", - "indexOfSubList: 3\n", - "lastIndexOfSubList: 3\n", - "replaceAll: [Yo, Two, three, Four, five, six, Yo]\n", - "reverse: [Yo, six, five, Four, three, Two, Yo]\n", - "rotate: [three, Two, Yo, Yo, six, five, Four]\n", - "copy: [in, the, matrix, Yo, six, five, Four]\n", - "swap: [Four, the, matrix, Yo, six, five, in]\n", - "shuffled: [six, matrix, the, Four, Yo, five, in]\n", - "fill: [pop, pop, pop, pop, pop, pop, pop]\n", - "frequency of 'pop': 7\n", - "dups: [snap, snap, snap]\n", - "'list' disjoint 'dups'?: true\n", - "arrayList: [snap, snap, snap]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出解释了每种实用方法的行为。请注意由于大小写的缘故,普通版本的 `min()` 和 `max()` 与带有 **String.CASE_INSENSITIVE_ORDER** 比较器参数的版本的区别。\n", - "\n", - "\n", - "### 排序和搜索列表\n", - "\n", - "用于执行排序和搜索 **List** 的实用工具程序与用于排序对象数组的程序具有相同的名字和方法签名,只不过是 **Collections** 的静态方法而不是 **Arrays** 。 这是一个使用 **Utilities.java** 中的 **list** 数据的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/ListSortSearch.java\n", - "// Sorting/searching Lists with Collections utilities\n", - "import java.util.*;\n", - "\n", - "public class ListSortSearch {\n", - " public static void main(String[] args) {\n", - " List list =\n", - " new ArrayList<>(Utilities.list);\n", - " list.addAll(Utilities.list);\n", - " System.out.println(list);\n", - " Collections.shuffle(list, new Random(47));\n", - " System.out.println(\"Shuffled: \" + list);\n", - " // Use ListIterator to trim off last elements:\n", - " ListIterator it = list.listIterator(10);\n", - " while(it.hasNext()) {\n", - " it.next();\n", - " it.remove();\n", - " }\n", - " System.out.println(\"Trimmed: \" + list);\n", - " Collections.sort(list);\n", - " System.out.println(\"Sorted: \" + list);\n", - " String key = list.get(7);\n", - " int index = Collections.binarySearch(list, key);\n", - " System.out.println(\n", - " \"Location of \" + key + \" is \" + index +\n", - " \", list.get(\" + index + \") = \" +\n", - " list.get(index));\n", - " Collections.sort(list,\n", - " String.CASE_INSENSITIVE_ORDER);\n", - " System.out.println(\n", - " \"Case-insensitive sorted: \" + list);\n", - " key = list.get(7);\n", - " index = Collections.binarySearch(list, key,\n", - " String.CASE_INSENSITIVE_ORDER);\n", - " System.out.println(\n", - " \"Location of \" + key + \" is \" + index +\n", - " \", list.get(\" + index + \") = \" +\n", - " list.get(index));\n", - " }\n", - "}\n", - "/* Output:\n", - "[one, Two, three, Four, five, six, one, one, Two,\n", - "three, Four, five, six, one]\n", - "Shuffled: [Four, five, one, one, Two, six, six, three,\n", - "three, five, Four, Two, one, one]\n", - "Trimmed: [Four, five, one, one, Two, six, six, three,\n", - "three, five]\n", - "Sorted: [Four, Two, five, five, one, one, six, six,\n", - "three, three]\n", - "Location of six is 7, list.get(7) = six\n", - "Case-insensitive sorted: [five, five, Four, one, one,\n", - "six, six, three, three, Two]\n", - "Location of three is 7, list.get(7) = three\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "就像使用数组进行搜索和排序一样,如果使用 **Comparator** 进行排序,则必须使用相同的 **Comparator** 执行 `binarySearch()` 。\n", - "\n", - "该程序还演示了 **Collections** 中的 `shuffle()` 方法,该方法随机打乱了 **List** 的顺序。 **ListIterator** 是在打乱后的列表中的特定位置创建的,用于从该位置删除元素,直到列表末尾。\n", - "\n", - "\n", - "### 创建不可修改的 Collection 或 Map\n", - "\n", - "通常,创建 **Collection** 或 **Map** 的只读版本会很方便。 **Collections** 类通过将原始集合传递给一个方法然后返回一个只读版本的集合。 对于 **Collection** (如果不能将 **Collection** 视为更具体的类型), **List** , **Set** 和 **Map** ,这类方法有许多变体。这个示例展示了针对每种类型,正确构建只读版本集合的方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/ReadOnly.java\n", - "// Using the Collections.unmodifiable methods\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class ReadOnly {\n", - " static Collection data =\n", - " new ArrayList<>(Countries.names(6));\n", - " public static void main(String[] args) {\n", - " Collection c =\n", - " Collections.unmodifiableCollection(\n", - " new ArrayList<>(data));\n", - " System.out.println(c); // Reading is OK\n", - " //- c.add(\"one\"); // Can't change it\n", - "\n", - " List a = Collections.unmodifiableList(\n", - " new ArrayList<>(data));\n", - " ListIterator lit = a.listIterator();\n", - " System.out.println(lit.next()); // Reading is OK\n", - " //- lit.add(\"one\"); // Can't change it\n", - "\n", - " Set s = Collections.unmodifiableSet(\n", - " new HashSet<>(data));\n", - " System.out.println(s); // Reading is OK\n", - " //- s.add(\"one\"); // Can't change it\n", - "\n", - " // For a SortedSet:\n", - " Set ss =\n", - " Collections.unmodifiableSortedSet(\n", - " new TreeSet<>(data));\n", - "\n", - " Map m =\n", - " Collections.unmodifiableMap(\n", - " new HashMap<>(Countries.capitals(6)));\n", - " System.out.println(m); // Reading is OK\n", - " //- m.put(\"Ralph\", \"Howdy!\");\n", - "\n", - " // For a SortedMap:\n", - " Map sm =\n", - " Collections.unmodifiableSortedMap(\n", - " new TreeMap<>(Countries.capitals(6)));\n", - " }\n", - "}\n", - "/* Output:\n", - "[ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI]\n", - "ALGERIA\n", - "[BENIN, BOTSWANA, ANGOLA, BURKINA FASO, ALGERIA,\n", - "BURUNDI]\n", - "{BENIN=Porto-Novo, BOTSWANA=Gaberone, ANGOLA=Luanda,\n", - "BURKINA FASO=Ouagadougou, ALGERIA=Algiers,\n", - "BURUNDI=Bujumbura}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为特定类型调用 “unmodifiable” 方法不会导致编译时检查,但是一旦发生转换,对修改特定集合内容的任何方法调用都将产生 **UnsupportedOperationException** 异常。\n", - "\n", - "在每种情况下,在将集合设置为只读之前,必须使用有意义的数据填充集合。填充完成后,最好的方法是用 “unmodifiable” 方法调用生成的引用替换现有引用。这样,一旦使得内容无法修改,那么就不会冒有意外更改内容的风险。另一方面,此工具还允许将可修改的集合保留为类中的**私有**集合,并从方法调用处返回对该集合的只读引用。所以,你可以在类内修改它,但其他人只能读它。\n", - "\n", - "\n", - "### 同步 Collection 或 Map\n", - "\n", - "**synchronized** 关键字是多线程主题的重要组成部分,更复杂的内容在[第二十四章 并发编程]()中介绍。在这里,只需要注意到 **Collections** 类包含一种自动同步整个集合的方法。 语法类似于 “unmodifiable” 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Synchronization.java\n", - "// Using the Collections.synchronized methods\n", - "import java.util.*;\n", - "\n", - "public class Synchronization {\n", - " public static void main(String[] args) {\n", - " Collection c =\n", - " Collections.synchronizedCollection(\n", - " new ArrayList<>());\n", - " List list = Collections\n", - " .synchronizedList(new ArrayList<>());\n", - " Set s = Collections\n", - " .synchronizedSet(new HashSet<>());\n", - " Set ss = Collections\n", - " .synchronizedSortedSet(new TreeSet<>());\n", - " Map m = Collections\n", - " .synchronizedMap(new HashMap<>());\n", - " Map sm = Collections\n", - " .synchronizedSortedMap(new TreeMap<>());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "最好立即通过适当的 “synchronized” 方法传递新集合,如上所示。这样,就不会意外地暴露出非同步版本。\n", - "\n", - "\n", - "#### Fail Fast\n", - "\n", - "Java 集合还具有防止多个进程修改集合内容的机制。如果当前正在迭代集合,然后有其他一些进程介入并插入,删除或更改该集合中的对象,则会出现此问题。也许在集合中已经遍历过了那个元素,也许还没有遍历到,也许在调用 `size()` 之后集合的大小会缩小...有许多灾难情景。 Java 集合库使用一种 *fail-fast* 的机制,该机制可以检测到除了当前进程引起的更改之外,其它任何对集合的更改操作。如果它检测到其他人正在修改集合,则会立即生成 **ConcurrentModificationException** 异常。这就是“fail-fast”的含义——它不会在以后使用更复杂的算法尝试检测问题(快速失败)。\n", - "\n", - "通过创建迭代器并向迭代器指向的集合中添加元素,可以很容易地看到操作中的 fail-fast 机制,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/FailFast.java\n", - "// Demonstrates the \"fail-fast\" behavior\n", - "import java.util.*;\n", - "\n", - "public class FailFast {\n", - " public static void main(String[] args) {\n", - " Collection c = new ArrayList<>();\n", - " Iterator it = c.iterator();\n", - " c.add(\"An object\");\n", - " try {\n", - " String s = it.next();\n", - " } catch(ConcurrentModificationException e) {\n", - " System.out.println(e);\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "java.util.ConcurrentModificationException\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "异常来自于在从集合中获得迭代器之后,又尝试在集合中添加元素。程序的两个部分可能会修改同一个集合,这种可能性的存在会产生不确定状态,因此异常会通知你更改代码。在这种情况下,应先将所有元素添加到集合,然后再获取迭代器。\n", - "\n", - "**ConcurrentHashMap** , **CopyOnWriteArrayList** 和 **CopyOnWriteArraySet** 使用了特定的技术来避免产生 **ConcurrentModificationException** 异常。\n", - "\n", - "\n", - "## 持有引用\n", - "\n", - "**java.lang.ref** 中库包含一组类,这些类允许垃圾收集具有更大的灵活性。特别是当拥有可能导致内存耗尽的大对象时,这些类特别有用。这里有三个从抽象类 **Reference** 继承来的类: **SoftReference** (软引用), **WeakReference** (弱引用)和 **PhantomReference** (虚引用)继承了三个类。如果一个对象只能通过这其中的一个 **Reference** 对象访问,那么这三种类型每个都为垃圾收集器提供不同级别的间接引用(indirection)。\n", - "\n", - "如果一个对象是 *可达的*(reachable),那么意味着在程序中的某个位置可以找到该对象。这可能意味着在栈上有一个直接引用该对象的普通引用,但也有可能是引用了一个对该对象有引用的对象,这可以有很多中间环节。如果某个对象是可达的,则垃圾收集器无法释放它,因为它仍然被程序所使用。如果某个对象是不可达的,则程序无法使用它,那么垃圾收集器回收该对象就是安全的。\n", - "\n", - "使用 **Reference** 对象继续保持对该对象的引用,以到达该对象,但也允许垃圾收集器释放该对象。因此,程序可以使用该对象,但如果内存即将耗尽,则允许释放该对象。\n", - "\n", - "可以通过使用 **Reference** 对象作为你和普通引用之间的中介(代理)来实现此目的。此外,必须没有对象的普通引用(未包含在 **Reference** 对象中的对象)。如果垃圾收集器发现对象可通过普通引用访问,则它不会释放该对象。\n", - "\n", - "按照 **SoftReference** , **WeakReference** 和 **PhantomReference** 的顺序,每个都比前一个更“弱”,并且对应于不同的可达性级别。软引用用于实现对内存敏感的缓存。弱引用用于实现“规范化映射”( canonicalized mappings)——对象的实例可以在程序的多个位置同时使用,以节省存储,但不会阻止其键(或值)被回收。虚引用用于调度 pre-mortem 清理操作,这是一种比 Java 终结机制(Java finalization mechanism)更灵活的方式。\n", - "\n", - "使用 **SoftReference** 和 **WeakReference** ,可以选择是否将它们放在 **ReferenceQueue** (用于 pre-mortem 清理操作的设备)中,但 **PhantomReference** 只能在 **ReferenceQueue** 上构建。下面是一个简单的演示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/References.java\n", - "// Demonstrates Reference objects\n", - "import java.lang.ref.*;\n", - "import java.util.*;\n", - "\n", - "class VeryBig {\n", - " private static final int SIZE = 10000;\n", - " private long[] la = new long[SIZE];\n", - " private String ident;\n", - " VeryBig(String id) { ident = id; }\n", - " @Override\n", - " public String toString() { return ident; }\n", - " @Override\n", - " protected void finalize() {\n", - " System.out.println(\"Finalizing \" + ident);\n", - " }\n", - "}\n", - "\n", - "public class References {\n", - " private static ReferenceQueue rq =\n", - " new ReferenceQueue<>();\n", - " public static void checkQueue() {\n", - " Reference inq = rq.poll();\n", - " if(inq != null)\n", - " System.out.println(\"In queue: \" + inq.get());\n", - " }\n", - " public static void main(String[] args) {\n", - " int size = 10;\n", - " // Or, choose size via the command line:\n", - " if(args.length > 0)\n", - " size = Integer.valueOf(args[0]);\n", - " LinkedList> sa =\n", - " new LinkedList<>();\n", - " for(int i = 0; i < size; i++) {\n", - " sa.add(new SoftReference<>(\n", - " new VeryBig(\"Soft \" + i), rq));\n", - " System.out.println(\n", - " \"Just created: \" + sa.getLast());\n", - " checkQueue();\n", - " }\n", - " LinkedList> wa =\n", - " new LinkedList<>();\n", - " for(int i = 0; i < size; i++) {\n", - " wa.add(new WeakReference<>(\n", - " new VeryBig(\"Weak \" + i), rq));\n", - " System.out.println(\n", - " \"Just created: \" + wa.getLast());\n", - " checkQueue();\n", - " }\n", - " SoftReference s =\n", - " new SoftReference<>(new VeryBig(\"Soft\"));\n", - " WeakReference w =\n", - " new WeakReference<>(new VeryBig(\"Weak\"));\n", - " System.gc();\n", - " LinkedList> pa =\n", - " new LinkedList<>();\n", - " for(int i = 0; i < size; i++) {\n", - " pa.add(new PhantomReference<>(\n", - " new VeryBig(\"Phantom \" + i), rq));\n", - " System.out.println(\n", - " \"Just created: \" + pa.getLast());\n", - " checkQueue();\n", - " }\n", - " }\n", - "}\n", - "/* Output: (First and Last 10 Lines)\n", - "Just created: java.lang.ref.SoftReference@15db9742\n", - "Just created: java.lang.ref.SoftReference@6d06d69c\n", - "Just created: java.lang.ref.SoftReference@7852e922\n", - "Just created: java.lang.ref.SoftReference@4e25154f\n", - "Just created: java.lang.ref.SoftReference@70dea4e\n", - "Just created: java.lang.ref.SoftReference@5c647e05\n", - "Just created: java.lang.ref.SoftReference@33909752\n", - "Just created: java.lang.ref.SoftReference@55f96302\n", - "Just created: java.lang.ref.SoftReference@3d4eac69\n", - "Just created: java.lang.ref.SoftReference@42a57993\n", - "...________...________...________...________...\n", - "Just created: java.lang.ref.PhantomReference@45ee12a7\n", - "In queue: null\n", - "Just created: java.lang.ref.PhantomReference@330bedb4\n", - "In queue: null\n", - "Just created: java.lang.ref.PhantomReference@2503dbd3\n", - "In queue: null\n", - "Just created: java.lang.ref.PhantomReference@4b67cf4d\n", - "In queue: null\n", - "Just created: java.lang.ref.PhantomReference@7ea987ac\n", - "In queue: null\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当运行此程序(将输出重定向到文本文件以查看页面中的输出)时,将会看到对象是被垃圾收集了的,虽然仍然可以通过 **Reference** 对象访问它们(使用 `get()` 来获取实际的对象引用)。 还可以看到 **ReferenceQueue** 始终生成包含 **null** 对象的 **Reference** 。 要使用它,请从特定的 **Reference** 类继承,并为新类添加更多有用的方法。\n", - "\n", - "\n", - "\n", - "### WeakHashMap\n", - "\n", - "集合类库中有一个特殊的 **Map** 来保存弱引用: **WeakHashMap** 。 此类可以更轻松地创建规范化映射。在这种映射中,可以通过仅仅创建一个特定值的实例来节省存储空间。当程序需要该值时,它会查找映射中的现有对象并使用它(而不是从头开始创建一个)。 该映射可以将值作为其初始化的一部分,但更有可能的是在需要时创建该值。\n", - "\n", - "由于这是一种节省存储空间的技术,因此 **WeakHashMap** 允许垃圾收集器自动清理键和值,这是非常方便的。不能对放在 **WeakHashMap** 中的键和值做任何特殊操作,它们由 map 自动包装在 **WeakReference** 中。当键不再被使用的时候才允许清理,如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/CanonicalMapping.java\n", - "// Demonstrates WeakHashMap\n", - "import java.util.*;\n", - "\n", - "class Element {\n", - " private String ident;\n", - " Element(String id) { ident = id; }\n", - " @Override\n", - " public String toString() { return ident; }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hashCode(ident);\n", - " }\n", - " @Override\n", - " public boolean equals(Object r) {\n", - " return r instanceof Element &&\n", - " Objects.equals(ident, ((Element)r).ident);\n", - " }\n", - " @Override\n", - " protected void finalize() {\n", - " System.out.println(\"Finalizing \" +\n", - " getClass().getSimpleName() + \" \" + ident);\n", - " }\n", - "}\n", - "\n", - "class Key extends Element {\n", - " Key(String id) { super(id); }\n", - "}\n", - "\n", - "class Value extends Element {\n", - " Value(String id) { super(id); }\n", - "}\n", - "\n", - "public class CanonicalMapping {\n", - " public static void main(String[] args) {\n", - " int size = 1000;\n", - " // Or, choose size via the command line:\n", - " if(args.length > 0)\n", - " size = Integer.valueOf(args[0]);\n", - " Key[] keys = new Key[size];\n", - " WeakHashMap map =\n", - " new WeakHashMap<>();\n", - " for(int i = 0; i < size; i++) {\n", - " Key k = new Key(Integer.toString(i));\n", - " Value v = new Value(Integer.toString(i));\n", - " if(i % 3 == 0)\n", - " keys[i] = k; // Save as \"real\" references\n", - " map.put(k, v);\n", - " }\n", - " System.gc();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Key** 类必须具有 `hashCode()` 和 `equals()` ,因为它将被用作散列数据结构中的键。 `hashCode()` 的内容在[附录:理解hashCode和equals方法]()中进行了描述。\n", - "\n", - "运行程序,你会看到垃圾收集器每三个键跳过一次。对该键的普通引用也被放置在 **keys** 数组中,因此这些对象不能被垃圾收集。\n", - "\n", - "\n", - "## Java 1.0 / 1.1 的集合类\n", - "\n", - "不幸的是,许多代码是使用 Java 1.0 / 1.1 中的集合编写的,甚至新代码有时也是使用这些类编写的。编写新代码时切勿使用旧集合。旧的集合类有限,所以关于它们的讨论不多。由于它们是不合时宜的,所以我会尽量避免过分强调一些可怕的设计决定。\n", - "\n", - "\n", - "### Vector 和 Enumeration\n", - "\n", - "Java 1.0 / 1.1 中唯一的自扩展序列是 **Vector** ,因此它被用于很多地方。它的缺陷太多了,无法在这里描述(参见《Java编程思想》第1版,可从[www.OnJava8.com](www.OnJava8.com)免费下载)。基本上,你可以将它看作是具有冗长且笨拙的方法名称的 **ArrayList** 。在修订后的 Java 集合库中,**Vector** 已经被调整适配过,因此可以作为 **Collection** 和 **List** 来使用。事实证明这有点不正常,集合类库仍然包含它只是为了支持旧的 Java 代码,但这会让一些人误以为 **Vector** 已经变得更好了。\n", - "\n", - "迭代器的 Java 1.0 / 1.1 版本选择创建一个新名称“enumeration”,而不是使用每个人都熟悉的术语(“iterator”)。 **Enumeration** 接口小于 **Iterator** ,只包含两个方法,并且它使用更长的方法名称:如果还有更多元素,则 `boolean hasMoreElements()` 返回 `true` , `Object nextElement()` 返回此enumeration的下一个元素 (否则会抛出异常)。\n", - "\n", - "**Enumeration** 只是一个接口,而不是一个实现,甚至新的类库有时仍然使用旧的 **Enumeration** ,这是不幸的,但通常是无害的。应该总是在自己的代码中使用 **Iterator** ,但要做好准备应对那些提供 **Enumeration** 的类库。\n", - "\n", - "此外,可以使用 `Collections.enumeration()` 方法为任何 **Collection** 生成 **Enumeration** ,如下例所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Enumerations.java\n", - "// Java 1.0/1.1 Vector and Enumeration\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class Enumerations {\n", - " public static void main(String[] args) {\n", - " Vector v =\n", - " new Vector<>(Countries.names(10));\n", - " Enumeration e = v.elements();\n", - " while(e.hasMoreElements())\n", - " System.out.print(e.nextElement() + \", \");\n", - " // Produce an Enumeration from a Collection:\n", - " e = Collections.enumeration(new ArrayList<>());\n", - " }\n", - "}\n", - "/* Output:\n", - "ALGERIA, ANGOLA, BENIN, BOTSWANA, BURKINA FASO,\n", - "BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN\n", - "REPUBLIC, CHAD,\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要生成 **Enumeration** ,可以调用 `elements()` ,然后可以使用它来执行向前迭代。\n", - "\n", - "最后一行创建一个 **ArrayList** ,并使用 `enumeration() ` 来将 **ArrayList** 适配为一个 **Enumeration** 。 因此,如果有旧代码需要使用 **Enumeration** ,你仍然可以使用新集合。\n", - "\n", - "\n", - "### Hashtable\n", - "\n", - "正如你在本附录中的性能比较中所看到的,基本的 **Hashtable** 与 **HashMap** 非常相似,甚至方法名称都相似。在新代码中没有理由使用 **Hashtable** 而不是 **HashMap** 。\n", - "\n", - "\n", - "### Stack\n", - "\n", - "之前使用 **LinkedList** 引入了栈的概念。 Java 1.0 / 1.1 **Stack** 的奇怪之处在于,不是以组合方式使用 **Vector** ,而是继承自 **Vector** 。 因此它具有 **Vector** 的所有特征和行为以及一些额外的 **Stack** 行为。很难去知道设计师是否有意识地认为这样做是有用的,或者它是否只是太天真了,无论如何,它在进入发行版之前显然没有经过审查,所以这个糟糕的设计仍然存在(但不要使用它)。\n", - "\n", - "这是 **Stack** 的简单演示,向栈中放入枚举中每一个类型的 **String** 形式。它还展示了如何轻松地将 **LinkedList** 用作栈,或者使用在[第十二章:集合]()章节中创建的 **Stack** 类:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Stacks.java\n", - "// Demonstration of Stack Class\n", - "import java.util.*;\n", - "\n", - "enum Month { JANUARY, FEBRUARY, MARCH, APRIL,\n", - " MAY, JUNE, JULY, AUGUST, SEPTEMBER,\n", - " OCTOBER, NOVEMBER }\n", - "\n", - "public class Stacks {\n", - " public static void main(String[] args) {\n", - " Stack stack = new Stack<>();\n", - " for(Month m : Month.values())\n", - " stack.push(m.toString());\n", - " System.out.println(\"stack = \" + stack);\n", - " // Treating a stack as a Vector:\n", - " stack.addElement(\"The last line\");\n", - " System.out.println(\n", - " \"element 5 = \" + stack.elementAt(5));\n", - " System.out.println(\"popping elements:\");\n", - " while(!stack.empty())\n", - " System.out.print(stack.pop() + \" \");\n", - "\n", - " // Using a LinkedList as a Stack:\n", - " LinkedList lstack = new LinkedList<>();\n", - " for(Month m : Month.values())\n", - " lstack.addFirst(m.toString());\n", - " System.out.println(\"lstack = \" + lstack);\n", - " while(!lstack.isEmpty())\n", - " System.out.print(lstack.removeFirst() + \" \");\n", - "\n", - " // Using the Stack class from\n", - " // the Collections Chapter:\n", - " onjava.Stack stack2 =\n", - " new onjava.Stack<>();\n", - " for(Month m : Month.values())\n", - " stack2.push(m.toString());\n", - " System.out.println(\"stack2 = \" + stack2);\n", - " while(!stack2.isEmpty())\n", - " System.out.print(stack2.pop() + \" \");\n", - "\n", - " }\n", - "}\n", - "/* Output:\n", - "stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,\n", - "JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER]\n", - "element 5 = JUNE\n", - "popping elements:\n", - "The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY\n", - "JUNE MAY APRIL MARCH FEBRUARY JANUARY lstack =\n", - "[NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY,\n", - "APRIL, MARCH, FEBRUARY, JANUARY]\n", - "NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL\n", - "MARCH FEBRUARY JANUARY stack2 = [NOVEMBER, OCTOBER,\n", - "SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH,\n", - "FEBRUARY, JANUARY]\n", - "NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL\n", - "MARCH FEBRUARY JANUARY\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**String** 形式是由 **Month** 中的枚举常量生成的,使用 `push()` 压入到栈中,然后使用 `pop()` 从栈顶部取出。为了说明一点,将 **Vector** 的操作也在 **Stack** 对象上执行, 这是可能的,因为凭借继承, **Stack** 是 **Vector** 。 因此,可以在 **Vector** 上执行的所有操作也可以在 **Stack** 上执行,例如 `elementAt()` 。\n", - "\n", - "如前所述,在需要栈行为时使用 **LinkedList** ,或者从 **LinkedList** 类创建的 **onjava.Stack** 类。\n", - "\n", - "\n", - "### BitSet\n", - "\n", - "**BitSet** 用于有效地存储大量的开关信息。仅从尺寸大小的角度来看它是有效的,如果你正在寻找有效的访问,它比使用本机数组(native array)稍慢。\n", - "\n", - "此外, **BitSet** 的最小大小是 **long** :64位。这意味着如果你要存储更小的东西,比如8位, **BitSet** 就是浪费,如果尺寸有问题,你最好创建自己的类,或者只是用一个数组来保存你的标志。(只有在你创建许多包含开关信息列表的对象时才会出现这种情况,并且只应根据分析和其他指标来决定。如果你做出此决定只是因为您认为 **BitSet** 太大,那么最终会产生不必要的复杂性并且浪费大量时间。)\n", - "\n", - "当添加更多元素时,普通集合会扩展, **BitSet**也会这样做。以下示例显示了 **BitSet** 的工作原理:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// collectiontopics/Bits.java\n", - "// Demonstration of BitSet\n", - "import java.util.*;\n", - "\n", - "public class Bits {\n", - " public static void printBitSet(BitSet b) {\n", - " System.out.println(\"bits: \" + b);\n", - " StringBuilder bbits = new StringBuilder();\n", - " for(int j = 0; j < b.size() ; j++)\n", - " bbits.append(b.get(j) ? \"1\" : \"0\");\n", - " System.out.println(\"bit pattern: \" + bbits);\n", - " }\n", - " public static void main(String[] args) {\n", - " Random rand = new Random(47);\n", - " // Take the LSB of nextInt():\n", - " byte bt = (byte)rand.nextInt();\n", - " BitSet bb = new BitSet();\n", - " for(int i = 7; i >= 0; i--)\n", - " if(((1 << i) & bt) != 0)\n", - " bb.set(i);\n", - " else\n", - " bb.clear(i);\n", - " System.out.println(\"byte value: \" + bt);\n", - " printBitSet(bb);\n", - "\n", - " short st = (short)rand.nextInt();\n", - " BitSet bs = new BitSet();\n", - " for(int i = 15; i >= 0; i--)\n", - " if(((1 << i) & st) != 0)\n", - " bs.set(i);\n", - " else\n", - " bs.clear(i);\n", - " System.out.println(\"short value: \" + st);\n", - " printBitSet(bs);\n", - "\n", - " int it = rand.nextInt();\n", - " BitSet bi = new BitSet();\n", - " for(int i = 31; i >= 0; i--)\n", - " if(((1 << i) & it) != 0)\n", - " bi.set(i);\n", - " else\n", - " bi.clear(i);\n", - " System.out.println(\"int value: \" + it);\n", - " printBitSet(bi);\n", - "\n", - " // Test bitsets >= 64 bits:\n", - " BitSet b127 = new BitSet();\n", - " b127.set(127);\n", - " System.out.println(\"set bit 127: \" + b127);\n", - " BitSet b255 = new BitSet(65);\n", - " b255.set(255);\n", - " System.out.println(\"set bit 255: \" + b255);\n", - " BitSet b1023 = new BitSet(512);\n", - " b1023.set(1023);\n", - " b1023.set(1024);\n", - " System.out.println(\"set bit 1023: \" + b1023);\n", - " }\n", - "}\n", - "/* Output:\n", - "byte value: -107\n", - "bits: {0, 2, 4, 7}\n", - "bit pattern: 101010010000000000000000000000000000000000\n", - "0000000000000000000000\n", - "short value: 1302\n", - "bits: {1, 2, 4, 8, 10}\n", - "bit pattern: 011010001010000000000000000000000000000000\n", - "0000000000000000000000\n", - "int value: -2014573909\n", - "bits: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24,\n", - "25, 26, 31}\n", - "bit pattern: 110101010101000000110111111000010000000000\n", - "0000000000000000000000\n", - "set bit 127: {127}\n", - "set bit 255: {255}\n", - "set bit 1023: {1023, 1024}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "随机数生成器用于创建随机 **byte** , **short** 和 **int** ,并且每个都在 **BitSet** 中转换为相应的位模式。这样可以正常工作,因为 **BitSet** 是64位,所以这些都不会导致它的大小增加,然后创建更大的 **BitSet** 。 请注意, **BitSet** 会根据需要进行扩展。\n", - "\n", - "对于可以命名的固定标志集, **EnumSet** (参见[第二十二章:枚举]()章节)通常比 **BitSet** 更好,因为 **EnumSet** 允许操作名称而不是数字位位置,从而可以减少错误。 **EnumSet** 还可以防止意外地添加新的标记位置,这可能会导致一些严重的,难以发现的错误。使用 **BitSet** 而不是 **EnumSet** 的唯一原因是,不知道在运行时需要多少标志,或者为标志分配名称是不合理的,或者需要 **BitSet** 中的一个特殊操作(请参阅 **BitSet** 和 **EnumSet** 的 JDK 文档)。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "集合可以说是编程语言中最常用的工具。有些语言(例如Python)甚至将基本集合组件(列表,映射和集合)作为内置函数包含在其中。\n", - "\n", - "正如在[第十二章:集合]()章节中看到的那样,可以使用集合执行许多非常有用的操作,而不需要太多努力。但是,在某些时候,为了正确地使用它们而不得不更多地了解集合,特别是,必须充分了解散列操作以编写自己的 `hashCode()` 方法(并且必须知道何时需要),并且你必须充分了解各种集合实现,以根据你的需求选择合适的集合。本附录涵盖了这些概念,并讨论了有关集合库的其他有用详细信息。你现在应该已经准备好在日常编程任务中使用 Java 集合了。\n", - "\n", - "集合库的设计很困难(大多数库设计问题都是如此)。在 C++ 中,集合类涵盖了许多不同类的基础。这比之前可用的 C++ 集合类更好,但它没有很好地转换为 Java 。在另一个极端,我看到了一个由单个类“collection”组成的集合库,它同时充当线性序列和关联数组。 Java 集合库试图在功能和复杂性之间取得平衡。结果在某些地方看起来有点奇怪。与早期 Java 库中的一些决策不同,这些奇怪的不是事故,而是在基于复杂性的权衡下而仔细考虑的决策。\n", - "\n", - "\n", - "[^1]: **java.util** 中的 **Map** 使用 **Map** 的 `getKey()` 和 `getValue()` 执行批量复制,因此这是有效的。如果自定义 **Map** 只是复制整个 **Map.Entry** ,那么这种方法就会出现问题。\n", - "\n", - "[^2]: 虽然当我用这种方式描述它的时候听起来很奇怪而且好像没什么用处,但在[第十九章 类型信息]()章节中已经看到过,这种动态行为也可以非常强大有用。\n", - "\n", - "[^3]: 如果这些加速仍然无法满足性能需求,则可以通过编写自己的 **Map** 并将其自定义为特定类型来进一步加速表查找,以避免因向 **对象** 转换而导致的延迟。为了达到更高的性能水平,速度爱好者可以使用 Donald Knuth 的《计算机程序设计艺术(第3卷):排序与查找》(第二版),将溢出桶列表(overflow bucket lists)替换为具有两个额外优势的阵列:它们可以针对磁盘存储进行优化,并且它们可以节省大部分创建和回收个别记录(individual records)的时间。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Data-Compression.ipynb b/jupyter/Appendix-Data-Compression.ipynb deleted file mode 100644 index f6d0a51f..00000000 --- a/jupyter/Appendix-Data-Compression.ipynb +++ /dev/null @@ -1,417 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:数据压缩\n", - "\n", - "Java I/O 类库提供了可以读写压缩格式流的类。你可以将其他 I/O 类包装起来用于提供压缩功能。\n", - "\n", - "这些类不是从 **Reader** 和 **Writer** 类派生的,而是 **InputStream** 和 **OutputStream** 层级结构的一部分。这是由于压缩库处理的是字节,而不是字符。但是,你可能会被迫混合使用两种类型的流(请记住,你可以使用 **InputStreamReader** 和 **OutputStreamWriter**,这两个类可以在字节类型和字符类型之间轻松转换)。\n", - "\n", - "| 压缩类 | 功能 |\n", - "| ------------------------ | ------------------------------------------------------------ |\n", - "| **CheckedInputStream** | `getCheckSum()` 可以对任意 **InputStream** 计算校验和(而不只是解压) |\n", - "| **CheckedOutputStream** | `getCheckSum()` 可以对任意 **OutputStream** 计算校验和(而不只是压缩) |\n", - "| **DeflaterOutputStream** | 压缩类的基类 |\n", - "| **ZipOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 Zip 文件结构 |\n", - "| **GZIPOutputStream** | **DeflaterOutputStream** 类的一种,用于压缩数据到 GZIP 文件结构 |\n", - "| **InflaterInputStream** | 解压类的基类 |\n", - "| **ZipInputStream** | **InflaterInputStream** 类的一种,用于解压 Zip 文件结构的数据 |\n", - "| **GZIPInputStream** | **InflaterInputStream** 类的一种,用于解压 GZIP 文件结构的数据 |\n", - "\n", - "尽管存在很多压缩算法,但是 Zip 和 GZIP 可能是最常见的。你可以使用许多用于读取和写入这些格式的工具,来轻松操作压缩数据。\n", - "\n", - "\n", - "\n", - "## 使用 Gzip 简单压缩\n", - "\n", - "\n", - "\n", - "GZIP 接口十分简单,因此当你有一个需要压缩的数据流(而不是一个包含不同数据分片的容器)时,使用 GZIP 更为合适。如下是一个压缩单个文件的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// compression/GZIPcompress.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// {java GZIPcompress GZIPcompress.java}\n", - "// {VisuallyInspectOutput}\n", - "\n", - "public class GZIPcompress {\n", - " public static void main(String[] args) {\n", - " if (args.length == 0) {\n", - " System.out.println(\n", - " \"Usage: \\nGZIPcompress file\\n\" +\n", - " \"\\tUses GZIP compression to compress \" +\n", - " \"the file to test.gz\");\n", - " System.exit(1);\n", - " }\n", - " try (\n", - " InputStream in = new BufferedInputStream(\n", - " new FileInputStream(args[0]));\n", - " BufferedOutputStream out =\n", - " new BufferedOutputStream(\n", - " new GZIPOutputStream(\n", - " new FileOutputStream(\"test.gz\")))\n", - " ) {\n", - " System.out.println(\"Writing file\");\n", - " int c;\n", - " while ((c = in.read()) != -1)\n", - " out.write(c);\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.println(\"Reading file\");\n", - " try (\n", - " BufferedReader in2 = new BufferedReader(\n", - " new InputStreamReader(new GZIPInputStream(\n", - " new FileInputStream(\"test.gz\"))))\n", - " ) {\n", - " in2.lines().forEach(System.out::println);\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用压缩类非常简单,你只需要把你的输出流包装在 **GZIPOutputStream** 或 **ZipOutputStream** 中,将输入流包装在 **GZIPInputStream** 或 **ZipInputStream**。其他的一切就只是普通的 I/O 读写。这是面向字符流和面向字节流的混合示例;in 使用 Reader 类,而 **GZIPOutputStreams** 构造函数只能接受 **OutputStream** 对象,而不能接受 **Writer** 对象。当打开文件的时候,**GZIPInputStream** 会转换成为 **Reader**。\n", - "\n", - "## 使用 zip 多文件存储\n", - "\n", - "支持 Zip 格式的库比 GZIP 库更广泛。有了它,你可以轻松存储多个文件,甚至还有一个单独的类可以轻松地读取 Zip 文件。该库使用标准 Zip 格式,因此它可以与当前可在 Internet 上下载的所有 Zip 工具无缝协作。以下示例与前一个示例具有相同的形式,但它可以根据需要处理任意数量的命令行参数。此外,它还显示了 **Checksum** 类计算和验证文件的校验和。有两种校验和类型:Adler32(更快)和 CRC32(更慢但更准确)。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// compression/ZipCompress.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// We make no guarantees that this code is fit for any purpose.\n", - "// Visit http://OnJava8.com for more book information.\n", - "// Uses Zip compression to compress any\n", - "// number of files given on the command line\n", - "// {java ZipCompress ZipCompress.java}\n", - "// {VisuallyInspectOutput}\n", - "public class ZipCompress {\n", - " public static void main(String[] args) {\n", - " try (\n", - " FileOutputStream f =\n", - " new FileOutputStream(\"test.zip\");\n", - " CheckedOutputStream csum =\n", - " new CheckedOutputStream(f, new Adler32());\n", - " ZipOutputStream zos = new ZipOutputStream(csum);\n", - " BufferedOutputStream out =\n", - " new BufferedOutputStream(zos)\n", - " ) {\n", - " zos.setComment(\"A test of Java Zipping\");\n", - " // No corresponding getComment(), though.\n", - " for (String arg : args) {\n", - " System.out.println(\"Writing file \" + arg);\n", - " try (\n", - " InputStream in = new BufferedInputStream(\n", - " new FileInputStream(arg))\n", - " ) {\n", - " zos.putNextEntry(new ZipEntry(arg));\n", - " int c;\n", - " while ((c = in.read()) != -1)\n", - " out.write(c);\n", - " }\n", - " out.flush();\n", - " }\n", - " // Checksum valid only after the file is closed!\n", - " System.out.println(\n", - " \"Checksum: \" + csum.getChecksum().getValue());\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // Now extract the files:\n", - " System.out.println(\"Reading file\");\n", - " try (\n", - " FileInputStream fi =\n", - " new FileInputStream(\"test.zip\");\n", - " CheckedInputStream csumi =\n", - " new CheckedInputStream(fi, new Adler32());\n", - " ZipInputStream in2 = new ZipInputStream(csumi);\n", - " BufferedInputStream bis =\n", - " new BufferedInputStream(in2)\n", - " ) {\n", - " ZipEntry ze;\n", - " while ((ze = in2.getNextEntry()) != null) {\n", - " System.out.println(\"Reading file \" + ze);\n", - " int x;\n", - " while ((x = bis.read()) != -1)\n", - " System.out.write(x);\n", - " }\n", - " if (args.length == 1)\n", - " System.out.println(\n", - " \"Checksum: \" + csumi.getChecksum().getValue());\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // Alternative way to open and read Zip files:\n", - " try (\n", - " ZipFile zf = new ZipFile(\"test.zip\")\n", - " ) {\n", - " Enumeration e = zf.entries();\n", - " while (e.hasMoreElements()) {\n", - " ZipEntry ze2 = (ZipEntry) e.nextElement();\n", - " System.out.println(\"File: \" + ze2);\n", - " // ... and extract the data as before\n", - " }\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于要添加到存档的每个文件,必须调用 `putNextEntry()` 并传递 **ZipEntry** 对象。 **ZipEntry** 对象包含一个扩展接口,用于获取和设置 Zip 文件中该特定条目的所有可用数据:名称,压缩和未压缩大小,日期,CRC 校验和,额外字段数据,注释,压缩方法以及它是否是目录条目。但是,即使 Zip 格式有设置密码的方法,Java 的 Zip 库也不支持。虽然 **CheckedInputStream** 和 **CheckedOutputStream** 都支持 Adler32 和 CRC32 校验和,但 **ZipEntry** 类仅支持 CRC 接口。这是对基础 Zip 格式的限制,但它可能会限制你使用更快的 Adler32。\n", - "\n", - "要提取文件,**ZipInputStream** 有一个 `getNextEntry()` 方法,这个方法在有文件存在的情况下调用,会返回下一个 **ZipEntry**。作为一个更简洁的替代方法,你可以使用 **ZipFile** 对象读取该文件,该对象具有方法 entries() 返回一个包裹 **ZipEntries** 的 **Enumeration**。\n", - "\n", - "要读取校验和,你必须以某种方式访问关联的 **Checksum** 对象。这里保留了对 **CheckedOutputStream** 和 **CheckedInputStream** 对象的引用,但你也可以保持对 **Checksum** 对象的引用。 Zip 流中的一个令人困惑的方法是 `setComment()`。如 **ZipCompress** 所示。在 Java 中,你可以在编写文件时设置注释,但是没有办法恢复 **ZipInputStream** 中的注释。注释似乎仅通过 **ZipEntry** 在逐个条目的基础上完全支持。\n", - "\n", - "使用 GZIP 或 Zip 库时,你不仅被限制于文件——你可以压缩任何内容,包括通过网络连接发送的数据。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "## Java 的 jar\n", - "\n", - "Zip 格式也用于 JAR(Java ARchive)文件格式,这是一种将一组文件收集到单个压缩文件中的方法,就像 Zip 一样。但是,与 Java 中的其他所有内容一样,JAR 文件是跨平台的,因此你不必担心平台问题。你还可以将音频和图像文件像类文件一样包含在其中。\n", - "\n", - "JAR 文件由一个包含压缩文件集合的文件和一个描述它们的“清单(manifest)”组成。(你可以创建自己的清单文件;否则,jar 程序将为你执行此操作。)你可以在 JDK 文档中,找到更多关于 JAR 清单的信息。\n", - "\n", - "JDK 附带的 jar 工具会自动压缩你选择的文件。你可以在命令行上调用它:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar [options] destination [manifest] inputfile(s)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "选项是一组字母(不需要连字符或任何其他指示符)。 Unix / Linux 用户会注意到这些选项与 tar 命令选项的相似性。这些是:\n", - "\n", - "| 选项 | 功能 |\n", - "| ---------- | ------------------------------------------------------------ |\n", - "| **c** | 创建一个新的或者空的归档文件 |\n", - "| **t** | 列出内容目录 |\n", - "| **x** | 提取所有文件 |\n", - "| **x** file | 提取指定的文件 |\n", - "| **f** | 这代表着,“传递文件的名称。”如果你不使用它,jar 假定它的输入将来自标准输入,或者,如果它正在创建一个文件,它的输出将转到标准输出。 |\n", - "| **m** | 代表第一个参数是用户创建的清单文件的名称。 |\n", - "| **v** | 生成详细的输出用于表述 jar 所作的事情 |\n", - "| **0** | 仅存储文件;不压缩文件(用于创建放在类路径中的 JAR 文件)。 |\n", - "| **M** | 不要自动创建清单文件 |\n", - "\n", - "如果放入 JAR 文件的文件中包含子目录,则会自动添加该子目录,包括其所有子目录等。还会保留路径信息。\n", - "\n", - "以下是一些调用 jar 的典型方法。以下命令创建名为 myJarFile 的 JAR 文件。 jar 包含当前目录中的所有类文件,以及自动生成的清单文件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar cf myJarFile.jar *.class" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "下一个命令与前面的示例类似,但它添加了一个名为 myManifestFile.mf 的用户创建的清单文件。 :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar cmf myJarFile.jar myManifestFile.mf *.class" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个命令输出了 myJarFile.jar 中的文件目录:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar tf myJarFile.jar" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如下添加了一个“verbose”的标志,用于生成更多关于 myJarFile.jar 中文件的详细信息:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar tvf myJarFile.jar" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "假设 audio,classes 和 image 都是子目录,它将所有子目录组合到文件 myApp.jar 中。还包括“verbose”标志,以便在 jar 程序工作时提供额外的反馈:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "jar cvf myApp.jar audio classes image" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你在创建 JAR 文件时使用了 0(零) 选项,该文件将会被替换在你的类路径(CLASSPATH)中:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "CLASSPATH=\"lib1.jar;lib2.jar;\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "然后 Java 可以搜索到 lib1.jar 和 lib2.jar 的类文件。\n", - "\n", - "jar 工具不像 Zip 实用程序那样通用。例如,你无法将文件添加或更新到现有 JAR 文件;只能从头开始创建 JAR 文件。\n", - "\n", - "此外,你无法将文件移动到 JAR 文件中,在移动文件时将其删除。\n", - "\n", - "但是,在一个平台上创建的 JAR 文件可以通过任何其他平台上的 jar 工具透明地读取(这个问题有时会困扰 Zip 实用程序)。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-IO-Streams.ipynb b/jupyter/Appendix-IO-Streams.ipynb deleted file mode 100644 index de3842cf..00000000 --- a/jupyter/Appendix-IO-Streams.ipynb +++ /dev/null @@ -1,716 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:流式IO\n", - "\n", - "> Java 7 引入了一种简单明了的方式来读写文件和操作目录。大多情况下,[文件](./17-Files.md)这一章所介绍的那些库和技术就足够你用了。但是,如果你必须面对一些特殊的需求和比较底层的操作,或者处理一些老版本的代码,那么你就必须了解本附录中的内容。\n", - "\n", - "对于编程语言的设计者来说,实现良好的输入/输出(I/O)系统是一项比较艰难的任务,不同实现方案的数量就可以证明这点。其中的挑战似乎在于要涵盖所有的可能性,你不仅要覆盖到不同的 I/O 源和 I/O 接收器(如文件、控制台、网络连接等),还要实现多种与它们进行通信的方式(如顺序、随机访问、缓冲、二进制、字符、按行和按字等)。\n", - "\n", - "Java 类库的设计者通过创建大量的类来解决这一难题。一开始,你可能会对 Java I/O 系统提供了如此多的类而感到不知所措。Java 1.0 之后,Java 的 I/O 类库发生了明显的改变,在原来面向字节的类中添加了面向字符和基于 Unicode 的类。在 Java 1.4 中,为了改进性能和功能,又添加了 `nio` 类(全称是 “new I/O”,Java 1.4 引入,到现在已经很多年了)。这部分在[附录:新 I/O](./Appendix-New-IO.md) 中介绍。\n", - "\n", - "因此,要想充分理解 Java I/O 系统以便正确运用它,我们需要学习一定数量的类。另外,理解 I/O 类库的演化过程也很有必要,因为如果缺乏历史的眼光,很快我们就会对什么时候该使用哪些类,以及什么时候不该使用它们而感到困惑。\n", - "\n", - "编程语言的 I/O 类库经常使用**流**这个抽象概念,它将所有数据源或者数据接收器表示为能够产生或者接收数据片的对象。\n", - "\n", - "> **注意**:Java 8 函数式编程中的 `Stream` 类和这里的 I/O stream 没有任何关系。这又是另一个例子,如果再给设计者一次重来的机会,他们将使用不同的术语。\n", - "\n", - "I/O 流屏蔽了实际的 I/O 设备中处理数据的细节:\n", - "\n", - "1. 字节流对应原生的二进制数据;\n", - "2. 字符流对应字符数据,它会自动处理与本地字符集之间的转换;\n", - "3. 缓冲流可以提高性能,通过减少底层 API 的调用次数来优化 I/O。\n", - "\n", - "从 JDK 文档的类层次结构中可以看到,Java 类库中的 I/O 类分成了输入和输出两部分。在设计 Java 1.0 时,类库的设计者们就决定让所有与输入有关系的类都继承自 `InputStream`,所有与输出有关系的类都继承自 `OutputStream`。所有从 `InputStream` 或 `Reader` 派生而来的类都含有名为 `read()` 的基本方法,用于读取单个字节或者字节数组。同样,所有从 `OutputStream` 或 `Writer` 派生而来的类都含有名为 `write()` 的基本方法,用于写单个字节或者字节数组。但是,我们通常不会用到这些方法,它们之所以存在是因为别的类可以使用它们,以便提供更有用的接口。\n", - "\n", - "我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是**装饰器设计模式**)。为了创建一个流,你却要创建多个对象,这也是 Java I/O 类库让人困惑的主要原因。\n", - "\n", - "这里我只会提供这些类的概述,并假定你会使用 JDK 文档来获取它们的详细信息(比如某个类的所以方法的详细列表)。\n", - "\n", - "\n", - "## 输入流类型\n", - "\n", - "`InputStream` 表示那些从不同数据源产生输入的类,如[表 I/O-1](#table-io-1) 所示,这些数据源包括:\n", - "\n", - "1. 字节数组;\n", - "2. `String` 对象;\n", - "3. 文件;\n", - "4. “管道”,工作方式与实际生活中的管道类似:从一端输入,从另一端输出;\n", - "5. 一个由其它种类的流组成的序列,然后我们可以把它们汇聚成一个流;\n", - "6. 其它数据源,如 Internet 连接。\n", - "\n", - "每种数据源都有相应的 `InputStream` 子类。另外,`FilterInputStream` 也属于一种 `InputStream`,它的作用是为“装饰器”类提供基类。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起,这个我们稍后再讨论。\n", - "\n", - "**表 I/O-1 `InputStream` 类型**\n", - "\n", - "| 类 | 功能 | 构造器参数 | 如何使用 |\n", - "| :--: | :-- | :-------- | :----- |\n", - "| `ByteArrayInputStream` | 允许将内存的缓冲区当做 `InputStream` 使用 | 缓冲区,字节将从中取出 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", - "| `StringBufferInputStream` | 将 `String` 转换成 `InputStream` | 字符串。底层实现实际使用 `StringBuffer` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", - "| `FileInputStream` | 用于从文件中读取信息 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", - "| `PipedInputStream` | 产生用于写入相关 `PipedOutputStream` 的数据。实现“管道化”概念 | `PipedOutputSteam` | 作为多线程中的数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", - "| `SequenceInputStream` | 将两个或多个 `InputStream` 对象转换成一个 `InputStream` | 两个 `InputStream` 对象或一个容纳 `InputStream` 对象的容器 `Enumeration` | 作为一种数据源:将其与 `FilterInputStream` 对象相连以提供有用接口 |\n", - "| `FilterInputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它的 `InputStream` 类提供有用的功能。见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) | 见[表 I/O-3](#table-io-3) |\n", - "\n", - "\n", - "## 输出流类型\n", - "\n", - "如[表 I/O-2](#table-io-2) 所示,该类别的类决定了输出所要去往的目标:字节数组(但不是 `String`,当然,你也可以用字节数组自己创建)、文件或管道。\n", - "\n", - "另外,`FilterOutputStream` 为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口与输出流连接了起来,这些稍后会讨论。\n", - "\n", - "**表 I/O-2:`OutputStream` 类型**\n", - "\n", - "| 类 | 功能 | 构造器参数 | 如何使用 |\n", - "| :--: | :-- | :-------- | :----- |\n", - "| `ByteArrayOutputStream` | 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区 | 缓冲区初始大小(可选) | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", - "| `FileOutputStream` | 用于将信息写入文件 | 字符串,表示文件名、文件或 `FileDescriptor` 对象 | 用于指定数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", - "| `PipedOutputStream` | 任何写入其中的信息都会自动作为相关 `PipedInputStream` 的输出。实现“管道化”概念 | `PipedInputStream` | 指定用于多线程的数据的目的地:将其与 `FilterOutputStream` 对象相连以提供有用接口 |\n", - "| `FilterOutputStream` | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其它 `OutputStream` 提供有用功能。见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) | 见[表 I/O-4](#table-io-4) |\n", - "\n", - "\n", - "\n", - "## 添加属性和有用的接口\n", - "\n", - "装饰器在[泛型](./20-Generics.md)这一章引入。Java I/O 类库需要多种不同功能的组合,这正是使用装饰器模式的原因所在[^1]。而之所以存在 **filter**(过滤器)类,是因为让抽象类 **filter** 作为所有装饰器类的基类。装饰器必须具有和它所装饰对象相同的接口,但它也可以扩展接口,不过这种情况只发生在个别 **filter** 类中。\n", - "\n", - "但是,装饰器模式也有一个缺点:在编写程序的时候,它给我们带来了相当多的灵活性(因为我们可以很容易地对属性进行混搭),但它同时也增加了代码的复杂性。Java I/O 类库操作不便的原因在于:我们必须创建许多类(“核心” I/O 类型加上所有的装饰器)才能得到我们所希望的单个 I/O 对象。\n", - "\n", - "`FilterInputStream` 和 `FilterOutputStream` 是用来提供装饰器类接口以控制特定输入流 `InputStream` 和 输出流 `OutputStream` 的两个类,但它们的名字并不是很直观。`FilterInputStream` 和 `FilterOutputStream` 分别从 I/O 类库中的基类 `InputStream` 和 `OutputStream` 派生而来,这两个类是创建装饰器的必要条件(这样它们才能为所有被装饰的对象提供统一接口)。\n", - "\n", - "### 通过 `FilterInputStream` 从 `InputStream` 读取\n", - "\n", - "`FilterInputStream` 类能够完成两件截然不同的事情。其中,`DataInputStream` 允许我们读取不同的基本数据类型和 `String` 类型的对象(所有方法都以 “read” 开头,例如 `readByte()`、`readFloat()`等等)。搭配其对应的 `DataOutputStream`,我们就可以通过数据“流”将基本数据类型的数据从一个地方迁移到另一个地方。具体是那些“地方”是由[表 I/O-1](#table-io-1) 中的那些类决定的。\n", - "\n", - "其它 `FilterInputStream` 类则在内部修改 `InputStream` 的行为方式:是否缓冲,是否保留它所读过的行(允许我们查询行数或设置行数),以及是否允许把单个字符推回输入流等等。最后两个类看起来就像是为了创建编译器提供的(它们被添加进来可能是为了对“用 Java 构建编译器”实现提供支持),因此我们在一般编程中不会用到它们。\n", - "\n", - "在实际应用中,不管连接的是什么 I/O 设备,我们基本上都会对输入进行缓冲。所以当初 I/O 类库如果能默认都让输入进行缓冲,同时将无缓冲输入作为一种特殊情况(或者只是简单地提供一个方法调用),这样会更加合理,而不是像现在这样迫使我们基本上每次都得手动添加缓冲。\n", - "\n", - "\n", - "**表 I/O-3:`FilterInputStream` 类型**\n", - "\n", - "| 类 | 功能 | 构造器参数 | 如何使用 |\n", - "| :--: | :-- | :-------- | :----- |\n", - "| `DataInputStream` | 与 `DataOutputStream` 搭配使用,按照移植方式从流读取基本数据类型(`int`、`char`、`long` 等) | `InputStream` | 包含用于读取基本数据类型的全部接口 |\n", - "| `BufferedInputStream` | 使用它可以防止每次读取时都得进行实际写操作。代表“使用缓冲区” | `InputStream`,可以指定缓冲区大小(可选) | 本质上不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |\n", - "| `LineNumberInputStream` | 跟踪输入流中的行号,可调用 `getLineNumber()` 和 `setLineNumber(int)` | `InputStream` | 仅增加了行号,因此可能要与接口对象搭配使用 |\n", - "| `PushbackInputStream` | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | `InputStream` | 通常作为编译器的扫描器,我们可能永远也不会用到 |\n", - "\n", - "### 通过 `FilterOutputStream` 向 `OutputStream` 写入\n", - "\n", - "与 `DataInputStream` 对应的是 `DataOutputStream`,它可以将各种基本数据类型和 `String` 类型的对象格式化输出到“流”中,。这样一来,任何机器上的任何 `DataInputStream` 都可以读出它们。所有方法都以 “write” 开头,例如 `writeByte()`、`writeFloat()` 等等。\n", - "\n", - "`PrintStream` 最初的目的就是为了以可视化格式打印所有基本数据类型和 `String` 类型的对象。这和 `DataOutputStream` 不同,后者的目的是将数据元素置入“流”中,使 `DataInputStream` 能够可移植地重构它们。\n", - "\n", - "`PrintStream` 内有两个重要方法:`print()` 和 `println()`。它们都被重载了,可以打印各种各种数据类型。`print()` 和 `println()` 之间的差异是,后者在操作完毕后会添加一个换行符。\n", - "\n", - "`PrintStream` 可能会造成一些问题,因为它捕获了所有 `IOException`(因此,我们必须使用 `checkError()` 自行测试错误状态,如果出现错误它会返回 `true`)。另外,`PrintStream` 没有处理好国际化问题。这些问题都在 `PrintWriter` 中得到了解决,这在后面会讲到。\n", - "\n", - "`BufferedOutputStream` 是一个修饰符,表明这个“流”使用了缓冲技术,因此每次向流写入的时候,不是每次都会执行物理写操作。我们在进行输出操作的时候可能会经常用到它。\n", - "\n", - "**表 I/O-4:`FilterOutputStream` 类型**\n", - "\n", - "| 类 | 功能 | 构造器参数 | 如何使用 |\n", - "| :--: | :-- | :-------- | :----- |\n", - "| `DataOutputStream` | 与 `DataInputStream` 搭配使用,因此可以按照移植方式向流中写入基本数据类型(`int`、`char`、`long` 等) | `OutputStream` | 包含用于写入基本数据类型的全部接口 |\n", - "| `PrintStream` | 用于产生格式化输出。其中 `DataOutputStream` 处理数据的存储,`PrintStream` 处理显示 | `OutputStream`,可以用 `boolean` 值指示是否每次换行时清空缓冲区(可选) | 应该是对 `OutputStream` 对象的 `final` 封装。可能会经常用到它 |\n", - "| `BufferedOutputStream` | 使用它以避免每次发送数据时都进行实际的写操作。代表“使用缓冲区”。可以调用 `flush()` 清空缓冲区 | `OutputStream`,可以指定缓冲区大小(可选) | 本质上并不提供接口,只是向进程添加缓冲功能。与接口对象搭配 |\n", - "\n", - "\n", - "\n", - "## Reader和Writer\n", - "\n", - "Java 1.1 对基本的 I/O 流类库做了重大的修改。你初次遇到 `Reader` 和 `Writer` 时,可能会以为这两个类是用来替代 `InputStream` 和 `OutputStream` 的,但实际上并不是这样。尽管一些原始的“流”类库已经过时了(如果使用它们,编译器会发出警告),但是 `InputStream` 和 `OutputStream` 在面向字节 I/O 这方面仍然发挥着极其重要的作用,而 `Reader` 和 `Writer` 则提供兼容 Unicode 和面向字符 I/O 的功能。另外:\n", - "\n", - "1. Java 1.1 往 `InputStream` 和 `OutputStream` 的继承体系中又添加了一些新类,所以这两个类显然是不会被取代的;\n", - "\n", - "2. 有时我们必须把来自“字节”层级结构中的类和来自“字符”层次结构中的类结合起来使用。为了达到这个目的,需要用到“适配器(adapter)类”:`InputStreamReader` 可以把 `InputStream` 转换为 `Reader`,而 `OutputStreamWriter` 可以把 `OutputStream` 转换为 `Writer`。\n", - "\n", - "设计 `Reader` 和 `Writer` 继承体系主要是为了国际化。老的 I/O 流继承体系仅支持 8 比特的字节流,并且不能很好地处理 16 比特的 Unicode 字符。由于 Unicode 用于字符国际化(Java 本身的 `char` 也是 16 比特的 Unicode),所以添加 `Reader` 和 `Writer` 继承体系就是为了让所有的 I/O 操作都支持 Unicode。另外,新类库的设计使得它的操作比旧类库要快。\n", - "\n", - "### 数据的来源和去处\n", - "\n", - "几乎所有原始的 Java I/O 流类都有相应的 `Reader` 和 `Writer` 类来提供原生的 Unicode 操作。但是在某些场合,面向字节的 `InputStream` 和 `OutputStream` 才是正确的解决方案。特别是 `java.util.zip` 类库就是面向字节而不是面向字符的。因此,最明智的做法是尽量**尝试**使用 `Reader` 和 `Writer`,一旦代码没法成功编译,你就会发现此时应该使用面向字节的类库了。\n", - "\n", - "下表展示了在两个继承体系中,信息的来源和去处(即数据物理上来自哪里又去向哪里)之间的对应关系:\n", - "\n", - "| 来源与去处:Java 1.0 类 | 相应的 Java 1.1 类 |\n", - "| :-------------------: | :--------------: |\n", - "| `InputStream` | `Reader`
适配器:`InputStreamReader` |\n", - "| `OutputStream` | `Writer`
适配器:`OutputStreamWriter` |\n", - "| `FileInputStream` | `FileReader` |\n", - "| `FileOutputStream` | `FileWriter` |\n", - "| `StringBufferInputStream`(已弃用) | `StringReader` |\n", - "| (无相应的类) | `StringWriter` |\n", - "| `ByteArrayInputStream` | `CharArrayReader` |\n", - "| `ByteArrayOutputStream` | `CharArrayWriter` |\n", - "| `PipedInputStream` | `PipedReader` |\n", - "| `PipedOutputStream` | `PipedWriter` |\n", - "\n", - "总的来说,这两个不同的继承体系中的接口即便不能说完全相同,但也是非常相似的。\n", - "\n", - "### 更改流的行为\n", - "\n", - "对于 `InputStream` 和 `OutputStream` 来说,我们会使用 `FilterInputStream` 和 `FilterOutputStream` 的装饰器子类来修改“流”以满足特殊需要。`Reader` 和 `Writer` 的类继承体系沿用了相同的思想——但是并不完全相同。\n", - "\n", - "在下表中,左右之间对应关系的近似程度现比上一个表格更加粗略一些。造成这种差别的原因是类的组织形式不同,`BufferedOutputStream` 是 `FilterOutputStream` 的子类,但 `BufferedWriter` 却不是 `FilterWriter` 的子类(尽管 `FilterWriter` 是抽象类,但却没有任何子类,把它放在表格里只是占个位置,不然你可能奇怪 `FilterWriter` 上哪去了)。然而,这些类的接口却又十分相似。\n", - "\n", - "| 过滤器:Java 1.0 类 | 相应 Java 1.1 类 |\n", - "| :--------------- | :-------------- |\n", - "| `FilterInputStream` | `FilterReader` |\n", - "| `FilterOutputStream` | `FilterWriter` (抽象类,没有子类) |\n", - "| `BufferedInputStream` | `BufferedReader`(也有 `readLine()`) |\n", - "| `BufferedOutputStream` | `BufferedWriter` |\n", - "| `DataInputStream` | 使用 `DataInputStream`( 如果必须用到 `readLine()`,那你就得使用 `BufferedReader`。否则,一般情况下就用 `DataInputStream` |\n", - "| `PrintStream` | `PrintWriter` |\n", - "| `LineNumberInputStream` | `LineNumberReader` |\n", - "| `StreamTokenizer` | `StreamTokenizer`(使用具有 `Reader` 参数的构造器) |\n", - "| `PushbackInputStream` | `PushbackReader` |\n", - "\n", - "有一条限制需要明确:一旦要使用 `readLine()`,我们就不应该用 `DataInputStream`(否则,编译时会得到使用了过时方法的警告),而应该使用 `BufferedReader`。除了这种情况之外的情形中,`DataInputStream` 仍是 I/O 类库的首选成员。\n", - "\n", - "为了使用时更容易过渡到 `PrintWriter`,它提供了一个既能接受 `Writer` 对象又能接受任何 `OutputStream` 对象的构造器。`PrintWriter` 的格式化接口实际上与 `PrintStream` 相同。\n", - "\n", - "Java 5 添加了几种 `PrintWriter` 构造器,以便在将输出写入时简化文件的创建过程,你马上就会见到它们。\n", - "\n", - "其中一种 `PrintWriter` 构造器还有一个执行**自动 flush**[^2] 的选项。如果构造器设置了该选项,就会在每个 `println()` 调用之后,自动执行 flush。\n", - "\n", - "### 未发生改变的类\n", - "\n", - "有一些类在 Java 1.0 和 Java 1.1 之间未做改变。\n", - "\n", - "| 以下这些 Java 1.0 类在 Java 1.1 中没有相应类 |\n", - "| --- |\n", - "| `DataOutputStream` |\n", - "| `File` |\n", - "| `RandomAccessFile` |\n", - "| `SequenceInputStream` |\n", - "\n", - "特别是 `DataOutputStream`,在使用时没有任何变化;因此如果想以可传输的格式存储和检索数据,请用 `InputStream` 和 `OutputStream` 继承体系。\n", - "\n", - "\n", - "## RandomAccessFile类\n", - "\n", - "`RandomAccessFile` 适用于由大小已知的记录组成的文件,所以我们可以使用 `seek()` 将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改。文件中记录的大小不一定都相同,只要我们能确定那些记录有多大以及它们在文件中的位置即可。\n", - "\n", - "最初,我们可能难以相信 `RandomAccessFile` 不是 `InputStream` 或者 `OutputStream` 继承体系中的一部分。除了实现了 `DataInput` 和 `DataOutput` 接口(`DataInputStream` 和 `DataOutputStream` 也实现了这两个接口)之外,它和这两个继承体系没有任何关系。它甚至都不使用 `InputStream` 和 `OutputStream` 类中已有的任何功能。它是一个完全独立的类,其所有的方法(大多数都是 `native` 方法)都是从头开始编写的。这么做是因为 `RandomAccessFile` 拥有和别的 I/O 类型本质上不同的行为,因为我们可以在一个文件内向前和向后移动。在任何情况下,它都是自我独立的,直接继承自 `Object`。\n", - "\n", - "从本质上来讲,`RandomAccessFile` 的工作方式类似于把 `DataIunputStream` 和 `DataOutputStream` 组合起来使用。另外它还有一些额外的方法,比如使用 `getFilePointer()` 可以得到当前文件指针在文件中的位置,使用 `seek()` 可以移动文件指针,使用 `length()` 可以得到文件的长度。另外,其构造器还需要传入第二个参数(和 C 语言中的 `fopen()` 相同)用来表示我们是准备对文件进行 “随机读”(r)还是“读写”(rw)。它并不支持只写文件,从这点来看,如果当初 `RandomAccessFile` 能设计成继承自 `DataInputStream`,可能也是个不错的实现方式。\n", - "\n", - "在 Java 1.4 中,`RandomAccessFile` 的大多数功能(但不是全部)都被 nio 中的**内存映射文件**(mmap)取代,详见[附录:新 I/O](./Appendix-New-IO.md)。\n", - "\n", - "\n", - "\n", - "## IO流典型用途\n", - "\n", - "尽管我们可以用不同的方式来组合 I/O 流类,但常用的也就其中几种。你可以下面的例子可以作为 I/O 典型用法的基本参照(在你确定无法使用[文件](./17-Files.md)这一章所述的库之后)。\n", - "\n", - "在这些示例中,异常处理都被简化为将异常传递给控制台,但是这样做只适用于小型的示例和工具。在你自己的代码中,你需要考虑更加复杂的错误处理方式。\n", - "\n", - "### 缓冲输入文件\n", - "\n", - "如果想要打开一个文件进行字符输入,我们可以使用一个 `FileInputReader` 对象,然后传入一个 `String` 或者 `File` 对象作为文件名。为了提高速度,我们希望对那个文件进行缓冲,那么我们可以将所产生的引用传递给一个 `BufferedReader` 构造器。`BufferedReader` 提供了 `line()` 方法,它会产生一个 `Stream` 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/BufferedInputFile.java\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "import java.util.stream.*;\n", - "\n", - "public class BufferedInputFile {\n", - " public static String read(String filename) {\n", - " try (BufferedReader in = new BufferedReader(\n", - " new FileReader(filename))) {\n", - " return in.lines()\n", - " .collect(Collectors.joining(\"\\n\"));\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " System.out.print(\n", - " read(\"BufferedInputFile.java\"));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`Collectors.joining()` 在其内部使用了一个 `StringBuilder` 来累加其运行结果。该文件会通过 `try-with-resources` 子句自动关闭。\n", - "\n", - "### 从内存输入\n", - "\n", - "下面示例中,从 `BufferedInputFile.read()` 读入的 `String` 被用来创建一个 `StringReader` 对象。然后调用其 `read()` 方法,每次读取一个字符,并把它显示在控制台上:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/MemoryInput.java\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "\n", - "public class MemoryInput {\n", - " public static void\n", - " main(String[] args) throws IOException {\n", - " StringReader in = new StringReader(\n", - " BufferedInputFile.read(\"MemoryInput.java\"));\n", - " int c;\n", - " while ((c = in.read()) != -1)\n", - " System.out.print((char) c);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意 `read()` 是以 `int` 形式返回下一个字节,所以必须类型转换为 `char` 才能正确打印。\n", - "\n", - "### 格式化内存输入\n", - "\n", - "要读取格式化数据,我们可以使用 `DataInputStream`,它是一个面向字节的 I/O 类(不是面向字符的)。这样我们就必须使用 `InputStream` 类而不是 `Reader` 类。我们可以使用 `InputStream` 以字节形式读取任何数据(比如一个文件),但这里使用的是字符串。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/FormattedMemoryInput.java\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "\n", - "public class FormattedMemoryInput {\n", - " public static void main(String[] args) {\n", - " try (\n", - " DataInputStream in = new DataInputStream(\n", - " new ByteArrayInputStream(\n", - " BufferedInputFile.read(\n", - " \"FormattedMemoryInput.java\")\n", - " .getBytes()))\n", - " ) {\n", - " while (true)\n", - " System.out.write((char) in.readByte());\n", - " } catch (EOFException e) {\n", - " System.out.println(\"\\nEnd of stream\");\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`ByteArrayInputStream` 必须接收一个字节数组,所以这里我们调用了 `String.getBytes()` 方法。所产生的的 `ByteArrayInputStream` 是一个适合传递给 `DataInputStream` 的 `InputStream`。\n", - "\n", - "如果我们用 `readByte()` 从 `DataInputStream` 一次一个字节地读取字符,那么任何字节的值都是合法结果,因此返回值不能用来检测输入是否结束。取而代之的是,我们可以使用 `available()` 方法得到剩余可用字符的数量。下面例子演示了怎么一次一个字节地读取文件:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/TestEOF.java\n", - "// Testing for end of file\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "\n", - "public class TestEOF {\n", - " public static void main(String[] args) {\n", - " try (\n", - " DataInputStream in = new DataInputStream(\n", - " new BufferedInputStream(\n", - " new FileInputStream(\"TestEOF.java\")))\n", - " ) {\n", - " while (in.available() != 0)\n", - " System.out.write(in.readByte());\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意,`available()` 的工作方式会随着所读取媒介类型的不同而有所差异,它的字面意思就是“在没有阻塞的情况下所能读取的字节数”。对于文件,能够读取的是整个文件;但是对于其它类型的“流”,可能就不是这样,所以要谨慎使用。\n", - "\n", - "我们也可以通过捕获异常来检测输入的末尾。但是,用异常作为控制流是对异常的一种错误使用方式。\n", - "\n", - "### 基本文件的输出\n", - "\n", - "`FileWriter` 对象用于向文件写入数据。实际使用时,我们通常会用 `BufferedWriter` 将其包装起来以增加缓冲的功能(可以试试移除此包装来感受一下它对性能的影响——缓冲往往能显著地增加 I/O 操作的性能)。在本例中,为了提供格式化功能,它又被装饰成了 `PrintWriter`。按照这种方式创建的数据文件可作为普通文本文件来读取。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/BasicFileOutput.java\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "\n", - "public class BasicFileOutput {\n", - " static String file = \"BasicFileOutput.dat\";\n", - "\n", - " public static void main(String[] args) {\n", - " try (\n", - " BufferedReader in = new BufferedReader(\n", - " new StringReader(\n", - " BufferedInputFile.read(\n", - " \"BasicFileOutput.java\")));\n", - " PrintWriter out = new PrintWriter(\n", - " new BufferedWriter(new FileWriter(file)))\n", - " ) {\n", - " in.lines().forEach(out::println);\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // Show the stored file:\n", - " System.out.println(BufferedInputFile.read(file));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`try-with-resources` 语句会自动 flush 并关闭文件。\n", - "\n", - "### 文本文件输出快捷方式\n", - "\n", - "Java 5 在 `PrintWriter` 中添加了一个辅助构造器,有了它,你在创建并写入文件时,就不必每次都手动执行一些装饰的工作。下面的代码使用这种快捷方式重写了 `BasicFileOutput.java`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/FileOutputShortcut.java\n", - "// {VisuallyInspectOutput}\n", - "import java.io.*;\n", - "\n", - "public class FileOutputShortcut {\n", - " static String file = \"FileOutputShortcut.dat\";\n", - "\n", - " public static void main(String[] args) {\n", - " try (\n", - " BufferedReader in = new BufferedReader(\n", - " new StringReader(BufferedInputFile.read(\n", - " \"FileOutputShortcut.java\")));\n", - " // Here's the shortcut:\n", - " PrintWriter out = new PrintWriter(file)\n", - " ) {\n", - " in.lines().forEach(out::println);\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.println(BufferedInputFile.read(file));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "使用这种方式仍具备了缓冲的功能,只是现在不必自己手动添加缓冲了。但遗憾的是,其它常见的写入任务都没有快捷方式,因此典型的 I/O 流依旧涉及大量冗余的代码。本书[文件](./17-Files.md)一章中介绍的另一种方式,对此类任务进行了极大的简化。\n", - "\n", - "### 存储和恢复数据\n", - "\n", - "`PrintWriter` 是用来对可读的数据进行格式化。但如果要输出可供另一个“流”恢复的数据,我们可以用 `DataOutputStream` 写入数据,然后用 `DataInputStream` 恢复数据。当然,这些流可能是任何形式,在下面的示例中使用的是一个文件,并且对读写都进行了缓冲。注意 `DataOutputStream` 和 `DataInputStream` 是面向字节的,因此要使用 `InputStream` 和 `OutputStream` 体系的类。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/StoringAndRecoveringData.java\n", - "import java.io.*;\n", - "\n", - "public class StoringAndRecoveringData {\n", - " public static void main(String[] args) {\n", - " try (\n", - " DataOutputStream out = new DataOutputStream(\n", - " new BufferedOutputStream(\n", - " new FileOutputStream(\"Data.txt\")))\n", - " ) {\n", - " out.writeDouble(3.14159);\n", - " out.writeUTF(\"That was pi\");\n", - " out.writeDouble(1.41413);\n", - " out.writeUTF(\"Square root of 2\");\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " try (\n", - " DataInputStream in = new DataInputStream(\n", - " new BufferedInputStream(\n", - " new FileInputStream(\"Data.txt\")))\n", - " ) {\n", - " System.out.println(in.readDouble());\n", - " // Only readUTF() will recover the\n", - " // Java-UTF String properly:\n", - " System.out.println(in.readUTF());\n", - " System.out.println(in.readDouble());\n", - " System.out.println(in.readUTF());\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "3.14159\n", - "That was pi\n", - "1.41413\n", - "Square root of 2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们使用 `DataOutputStream` 进行数据写入,那么 Java 就保证了即便读和写数据的平台多么不同,我们仍可以使用 `DataInputStream` 准确地读取数据。这一点很有价值,众所周知,人们曾把大量精力耗费在数据的平台相关性问题上。但现在,只要两个平台上都有 Java,就不会存在这样的问题[^3]。\n", - "\n", - "当我们使用 `DastaOutputStream` 时,写字符串并且让 `DataInputStream` 能够恢复它的唯一可靠方式就是使用 UTF-8 编码,在这个示例中是用 `writeUTF()` 和 `readUTF()` 来实现的。UTF-8 是一种多字节格式,其编码长度根据实际使用的字符集会有所变化。如果我们使用的只是 ASCII 或者几乎都是 ASCII 字符(只占 7 比特),那么就显得及其浪费空间和带宽,所以 UTF-8 将 ASCII 字符编码成一个字节的形式,而非 ASCII 字符则编码成两到三个字节的形式。另外,字符串的长度保存在 UTF-8 字符串的前两个字节中。但是,`writeUTF()` 和 `readUTF()` 使用的是一种适用于 Java 的 UTF-8 变体(JDK 文档中有这些方法的详尽描述),因此如果我们用一个非 Java 程序读取用 `writeUTF()` 所写的字符串时,必须编写一些特殊的代码才能正确读取。\n", - "\n", - "有了 `writeUTF()` 和 `readUTF()`,我们就可以在 `DataOutputStream` 中把字符串和其它数据类型混合使用。因为字符串完全可以作为 Unicode 格式存储,并且可以很容易地使用 `DataInputStream` 来恢复它。\n", - "\n", - "`writeDouble()` 将 `double` 类型的数字存储在流中,并用相应的 `readDouble()` 恢复它(对于其它的书类型,也有类似的方法用于读写)。但是为了保证所有的读方法都能够正常工作,我们必须知道流中数据项所在的确切位置,因为极有可能将保存的 `double` 数据作为一个简单的字节序列、`char` 或其它类型读入。因此,我们必须:要么为文件中的数据采用固定的格式;要么将额外的信息保存到文件中,通过解析额外信息来确定数据的存放位置。注意,对象序列化和 XML (二者都在[附录:对象序列化](Appendix-Object-Serialization.md)中介绍)是存储和读取复杂数据结构的更简单的方式。\n", - "\n", - "### 读写随机访问文件\n", - "\n", - "使用 `RandomAccessFile` 就像是使用了一个 `DataInputStream` 和 `DataOutputStream` 的结合体(因为它实现了相同的接口:`DataInput` 和 `DataOutput`)。另外,我们还可以使用 `seek()` 方法移动文件指针并修改对应位置的值。\n", - "\n", - "在使用 `RandomAccessFile` 时,你必须清楚文件的结构,否则没法正确使用它。`RandomAccessFile` 有一套专门的方法来读写基本数据类型的数据和 UTF-8 编码的字符串:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// iostreams/UsingRandomAccessFile.java\n", - "import java.io.*;\n", - "\n", - "public class UsingRandomAccessFile {\n", - " static String file = \"rtest.dat\";\n", - "\n", - " public static void display() {\n", - " try (\n", - " RandomAccessFile rf =\n", - " new RandomAccessFile(file, \"r\")\n", - " ) {\n", - " for (int i = 0; i < 7; i++)\n", - " System.out.println(\n", - " \"Value \" + i + \": \" + rf.readDouble());\n", - " System.out.println(rf.readUTF());\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " try (\n", - " RandomAccessFile rf =\n", - " new RandomAccessFile(file, \"rw\")\n", - " ) {\n", - " for (int i = 0; i < 7; i++)\n", - " rf.writeDouble(i * 1.414);\n", - " rf.writeUTF(\"The end of the file\");\n", - " rf.close();\n", - " display();\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " try (\n", - " RandomAccessFile rf =\n", - " new RandomAccessFile(file, \"rw\")\n", - " ) {\n", - " rf.seek(5 * 8);\n", - " rf.writeDouble(47.0001);\n", - " rf.close();\n", - " display();\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Value 0: 0.0\n", - "Value 1: 1.414\n", - "Value 2: 2.828\n", - "Value 3: 4.242\n", - "Value 4: 5.656\n", - "Value 5: 7.069999999999999\n", - "Value 6: 8.484\n", - "The end of the file\n", - "Value 0: 0.0\n", - "Value 1: 1.414\n", - "Value 2: 2.828\n", - "Value 3: 4.242\n", - "Value 4: 5.656\n", - "Value 5: 47.0001\n", - "Value 6: 8.484\n", - "The end of the file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`display()` 方法打开了一个文件,并以 `double` 值的形式显示了其中的七个元素。在 `main()` 中,首先创建了文件,然后打开并修改了它。因为 `double` 总是 8 字节长,所以如果要用 `seek()` 定位到第 5 个(从 0 开始计数) `double` 值,则要传入的地址值应该为 `5*8`。\n", - "\n", - "正如前面所诉,虽然 `RandomAccess` 实现了 `DataInput` 和 `DataOutput` 接口,但实际上它和 I/O 继承体系中的其它部分是分离的。它不支持装饰,故而不能将其与 `InputStream` 及 `OutputStream` 子类中的任何一个组合起来,所以我们也没法给它添加缓冲的功能。\n", - "\n", - "该类的构造器还有第二个必选参数:我们可以指定让 `RandomAccessFile` 以“只读”(r)方式或“读写”\n", - "(rw)方式打开文件。\n", - "\n", - "除此之外,还可以使用 `nio` 中的“内存映射文件”代替 `RandomAccessFile`,这在[附录:新 I/O](Appendix-New-IO.md)中有介绍。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "Java 的 I/O 流类库的确能够满足我们的基本需求:我们可以通过控制台、文件、内存块,甚至因特网进行读写。通过继承,我们可以创建新类型的输入和输出对象。并且我们甚至可以通过重新定义“流”所接受对象类型的 `toString()` 方法,进行简单的扩展。当我们向一个期望收到字符串的方法传送一个非字符串对象时,会自动调用对象的 `toString()` 方法(这是 Java 中有限的“自动类型转换”功能之一)。\n", - "\n", - "在 I/O 流类库的文档和设计中,仍留有一些没有解决的问题。例如,我们打开一个文件用于输出,如果在我们试图覆盖这个文件时能抛出一个异常,这样会比较好(有的编程系统只有当该文件不存在时,才允许你将其作为输出文件打开)。在 Java 中,我们应该使用一个 `File` 对象来判断文件是否存在,因为如果我们用 `FileOutputStream` 或者 `FileWriter` 打开,那么这个文件肯定会被覆盖。\n", - "\n", - "I/O 流类库让我们喜忧参半。它确实挺有用的,而且还具有可移植性。但是如果我们没有理解“装饰器”模式,那么这种设计就会显得不是很直观。所以,它的学习成本相对较高。而且它并不完善,比如说在过去,我不得不编写相当数量的代码去实现一个读取文本文件的工具——所幸的是,Java 7 中的 nio 消除了此类需求。\n", - "\n", - "一旦你理解了装饰器模式,并且开始在某些需要这种灵活性的场景中使用该类库,那么你就开始能从这种设计中受益了。到那时候,为此额外多写几行代码的开销应该不至于让人觉得太麻烦。但还是请务必检查一下,确保使用[文件](./17-Files.md)一章中的库和技术没法解决问题后,再考虑使用本章的 I/O 流库。\n", - "\n", - "[^1]: 很难说这就是一个很好的设计选择,尤其是与其它编程语言中简单的 I/O 类库相比较。但它确实是如此选择的一个正当理由。\n", - "\n", - "[^2]: 译者注:“flush” 直译是“清空”,意思是把缓冲中的数据清空,输送到对应的目的地(如文件和屏幕)。\n", - "\n", - "[^3]: XML 是另一种方式,可以解决在不同计算平台之间移动数据,而不依赖于所有平台上都有 Java 这一问题。XML 将在[附录:对象序列化](./Appendix-Object-Serialization.md)一章中进行介绍。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Javadoc.ipynb b/jupyter/Appendix-Javadoc.ipynb deleted file mode 100644 index c54561ce..00000000 --- a/jupyter/Appendix-Javadoc.ipynb +++ /dev/null @@ -1,400 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:文档注释\n", - "\n", - "编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,那么每次更改代码时更改文档都会变得很繁琐。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这完整的画面,您需要一个特殊的注释语法来标记文档,以及一个工具来将这些注释提取为有用的表单中。这就是Java所做的。\n", - "\n", - "提取注释的工具称为Javadoc,它是 JDK 安装的一部分。它使用Java编译器中的一些技术来寻找特殊的注释标记。它不仅提取由这些标记所标记的信息,还提取与注释相邻的类名或方法名。通过这种方式,您就可以用最少的工作量来生成合适的程序文档。\n", - "\n", - "Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于Javadoc,您有一个简单的标准来创建文档,因此您可以期望所有Java libraries都有文档。\n", - "\n", - "此外,您可以编写自己的Javadoc处理程序doclet,对于 Javadoc(例如,以不同的格式生成输出)。\n", - "\n", - "以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。\n", - "\n", - "## 句法规则\n", - "\n", - "所有Javadoc指令都发生在以 **/**** 开头(但仍然以 ***/** 结尾)的注释中。\n", - "\n", - "使用Javadoc有两种主要方法:\n", - "\n", - "嵌入HTML或使用“doc标签”。独立的doc标签是指令它以 **@** 开头,放在注释行的开头。(然而,前面的 ***** 将被忽略。)可能会出现内联doc标签\n", - "\n", - "Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被花括号包围。\n", - "\n", - "有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// javadoc/Documentation1.java \n", - "/** 一个类注释 */\n", - "public class Documentation1 {\n", - " /** 一个属性注释 */\n", - " public int i;\n", - " /** 一个方法注释 */ \n", - " public void f() {}\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 \n", - "\n", - "默认情况下,将忽略对 **私有成员** 和包访问成员的注释(请参阅[\"隐藏实现\"](/docs/book/07-Implementation-Hiding.md)一章),并且您将看不到任何输出。 \n", - "\n", - "这是有道理的,因为仅客户端程序员的观点是,在文件外部可以使用 **公共成员** 和 **受保护成员** 。 您可以使用 **-private** 标志和包含 **私人** 成员。\n", - "\n", - "要通过Javadoc处理前面的代码,命令是:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "cmd" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "javadoc Documentation1.java" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览你的类。\n", - "\n", - "## 内嵌 HTML\n", - "\n", - "Javadoc传递未修改的HTML代码,用以生成的HTML文档。这使你可以充分利用HTML。但是,这样做的主要目的是让你格式化代码,例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// javadoc/Documentation2.java\n", - "/**
\n",
-    "* System.out.println(new Date());\n",
-    "* 
\n", - "*/\n", - "public class Documentation2 {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "您你也可以像在其他任何Web文档中一样使用HTML来格式化说明中的文字:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// javadoc/Documentation3.java\n", - "/** You can even insert a list:\n", - "*
    \n", - "*
  1. Item one\n", - "*
  2. Item two\n", - "*
  3. Item three\n", - "*
\n", - "*/\n", - "public class Documentation3 {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意,在文档注释中,Javadoc删除了行首的星号以及前导空格。 Javadoc重新格式化了所有内容,使其符合标准文档的外观。不要将诸如 \\或 \\之类的标题用作嵌入式HTML,因为Javadoc会插入自己的标题,后插入的标题将对其生成的文档产生干扰。\n", - "\n", - "所有类型的注释文档(类,字段和方法)都可以支持嵌入式HTML。\n", - "\n", - "## 示例标签\n", - "\n", - "以下是一些可用于代码文档的Javadoc标记。在尝试使用Javadoc进行任何认真的操作之前,请查阅JDK文档中的Javadoc参考,以了解使用Javadoc的所有不同方法。\n", - "\n", - "### @see\n", - "\n", - "这个标签可以将其他的类连接到文档中,Javadoc 将使用 @see 标记超链接到其他文档中,形式为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@see classname\n", - "@see fully-qualified-classname\n", - "@see fully-qualified-classname#method-name" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个都向生成的文档中添加超链接的“另请参阅”条目。 Javadoc 不会检查超链接的有效性。\n", - "\n", - "### {@link package.class#member label}\n", - "\n", - "和 @see 非常相似,不同之处在于它可以内联使用,并使用标签作为超链接文本,而不是“另请参阅”。\n", - "\n", - "### {@docRoot}\n", - "\n", - "生成文档根目录的相对路径。对于显式超链接到文档树中的页面很有用。\n", - "\n", - "### {@inheritDoc}\n", - "\n", - "将文档从此类的最近基类继承到当前文档注释中。\n", - "\n", - "### @version\n", - "\n", - "其形式为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@version version-information" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中 version-information 是你认为适合包含的任何重要信息。当在Javadoc命令行上放置 -version 标志时,特别在生成的HTML文档中用于生成version信息。\n", - "\n", - "### @author\n", - "\n", - "其形式为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@author author-information" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "author-information 大概率是你的名字,但是一样可以包含你的 email 地址或者其他合适的信息。当在 Javadoc 命令行上放置 -author 标志的时候,在生成的HTML文档中特别注明了作者信息。\n", - "\n", - "你可以对作者列表使用多个作者标签,但是必须连续放置它们。所有作者信息都集中在生成的HTML中的单个段落中。\n", - "\n", - "### @since\n", - "\n", - "此标记指示此代码的版本开始使用特定功能。例如,它出现在HTML Java文档中,以指示功能首次出现的JDK版本。\n", - "\n", - "### @param\n", - "\n", - "这将生成有关方法参数的文档:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@param parameter-name description" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中parameter-name是方法参数列表中的标识符,description 是可以在后续行中继续的文本。当遇到新的文档标签时,说明被视为完成。@param 标签的可以任意使用,大概每个参数一个。\n", - "\n", - "### @return\n", - "\n", - "这记录了返回值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@return description" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中description给出了返回值的含义。它可延续到后面的行内。\n", - "\n", - "### @throws\n", - "\n", - "一个方法可以产生许多不同类型的异常,所有这些异常都需要描述。异常标记的形式为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "@throws fully-qualified-class-name description" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "fully-qualified-class-name 给出明确的异常分类名称,并且 description (可延续到后面的行内)告诉你为什么这特定类型的异常会在方法调用后出现。\n", - "\n", - "### @deprecated\n", - "\n", - "这表示已被改进的功能取代的功能。deprecated 标记表明你不再使用此特定功能,因为将来有可能将其删除。标记为@不赞成使用的方法会导致编译器在使用时发出警告。在Java 5中,@deprecated Javadoc 标记已被 @Deprecated 注解取代(在[注解]()一章中进行了描述)。 \n", - "\n", - "## 文档示例\n", - "\n", - "**objects/HelloDate.java** 是带有文档注释的例子。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// javadoc/HelloDateDoc.java\n", - "import java.util.*;\n", - "/** The first On Java 8 example program.\n", - " * Displays a String and today's date.\n", - " * @author Bruce Eckel\n", - " * @author www.MindviewInc.com\n", - " * @version 5.0\n", - " */\n", - "public class HelloDateDoc {\n", - " /** Entry point to class & application.\n", - " * @param args array of String arguments\n", - " * @throws exceptions No exceptions thrown\n", - " */\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Hello, it's: \");\n", - " System.out.println(new Date());\n", - " }\n", - "}\n", - "/* Output:\n", - "Hello, it's:\n", - "Tue May 09 06:07:27 MDT 2017\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你可以在Java标准库的源代码中找到许多Javadoc注释文档的示例。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Low-Level-Concurrency.ipynb b/jupyter/Appendix-Low-Level-Concurrency.ipynb deleted file mode 100644 index ec54e455..00000000 --- a/jupyter/Appendix-Low-Level-Concurrency.ipynb +++ /dev/null @@ -1,2446 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:并发底层原理\n", - "\n", - "> 尽管不建议你自己编写底层 Java 并发代码,但是这样通常有助于了解它是如何工作的。\n", - "\n", - "[并发编程](./24-Concurrent-Programming.md) 章节中介绍了一些用于高级并发的概念,包括为 Java 并发编程而最新提出的,更安全的概念( parallel Streams 和 CompletableFutures )。本附录则介绍在 Java 中底层并发概念,因此在阅读本篇时,你能有所了解掌握这些代码。你还会将进一步了解并发的普遍问题。\n", - "\n", - "在 Java 的早期版本中, 底层并发概念是并发编程的重要组成部分。我们会着眼于围绕这些技巧的复杂性以及为何你应该避免它们而谈。 “并发编程” 章节展示最新的 Java 版本(尤其是 Java 8)所提供的改进技巧,这些技巧使得并发的使用,如果本来不容易使用,也会变得更容易些。\n", - "\n", - "\n", - "## 什么是线程?\n", - "\n", - "并发将程序划分成独立分离运行的任务。每个任务都由一个 *执行线程* 来驱动,我们通常将其简称为 *线程* 。而一个 *线程* 就是操作系统进程中单一顺序的控制流。因此,单个进程可以有多个并发执行的任务,但是你的程序使得每个任务都好像有自己的处理器一样。此线程模型为编程带来了便利,它简化了在单一程序中处理变戏法般的多任务过程。操作系统则从处理器上分配时间片到你程序的所有线程中。\n", - "\n", - "Java 并发的核心机制是 **Thread** 类,在该语言最初版本中, **Thread (线程)** 是由程序员直接创建和管理的。随着语言的发展以及人们发现了更好的一些方法,中间层机制 - 特别是 **Executor** 框架 - 被添加进来,以消除自己管理线程时候的心理负担(及错误)。 最终,甚至发展出比 **Executor** 更好的机制,如 [并发编程](./24-Concurrent-Programming.md) 一章所示。\n", - "\n", - "**Thread(线程)** 是将任务关联到处理器的软件概念。虽然创建和使用 **Thread** 类看起来与任何其他类都很相似,但实际上它们是非常不同的。当你创建一个 **Thread** 时,JVM 将分配一大块内存到专为线程保留的特殊区域上,用于提供运行任务时所需的一切,包括:\n", - "\n", - "* 程序计数器,指明要执行的下一个 JVM 字节码指令。\n", - "* 用于支持 Java 代码执行的栈,包含有关此线程已到达当时执行位置所调用方法的信息。它也包含每个正在执行的方法的所有局部变量(包括原语和堆对象的引用)。每个线程的栈通常在 64K 到 1M 之间 [^1] 。\n", - "* 第二个则用于 native code(本机方法代码)执行的栈\n", - "* *thread-local variables* (线程本地变量)的存储区域\n", - "* 用于控制线程的状态管理变量\n", - "\n", - "包括 `main()` 在内的所有代码都会在某个线程内运行。 每当调用一个方法时,当前程序计数器被推到该线程的栈上,然后栈指针向下移动以足够来创建一个栈帧,其栈帧里存储该方法的所有局部变量,参数和返回值。所有基本类型变量都直接在栈上,虽然方法中创建(或方法中使用)对象的任何引用都位于栈帧中,但对象本身存于堆中。这仅且只有一个堆,被程序中所有线程所共享。\n", - "\n", - "除此以外,线程必须绑定到操作系统,这样它就可以在某个时候连接到处理器。这是作为线程构建过程的一部分为你管理的。Java 使用底层操作系统中的机制来管理线程的执行。\n", - "\n", - "### 最佳线程数\n", - "\n", - "如果你查看第 24 章 [并发编程](./24-Concurrent-Programming.md) 中使用 *CachedThreadPool* 的用例,你会发现 **ExecutorService** 为每个我们提交的任务分配一个线程。然而,并行流(**parallel Stream**)在 [**CountingStream.java** ](https://github.com/BruceEckel/OnJava8-Examples/blob/master/concurrent/CountingStream.java\n", - ") 中只分配了 8 个线程(id 中 1-7 为工作线程,8 为 `main()` 方法的主线程,它巧妙地将其用作额外的并行流)。如果你尝试提高 `range()` 方法中的上限值,你会看到没有创建额外的线程。这是为什么?\n", - "\n", - "我们可以查出当前机器上处理器的数量:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "Java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/NumberOfProcessors.java\n", - "\n", - "public class NumberOfProcessors {\n", - " public static void main(String[] args) {\n", - " System.out.println(\n", - " Runtime.getRuntime().availableProcessors());\n", - " }\n", - "}\n", - "/* Output:\n", - "8\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在我的机器上(使用英特尔酷睿i7),我有四个内核,每个内核呈现两个*超线程*(指一种硬件技巧,能在单个处理器上产生非常快速的上下文切换,在某些情况下可以使内核看起来像运行两个硬件线程)。虽然这是 “最近” 计算机上的常见配置(在撰写本文时),但你可能会看到不同的结果,包括 **CountingStream.java ** 中同等数量的默认线程。\n", - "\n", - "你的操作系统可能有办法来查出关于处理器的更多信息,例如,在Windows 10上,按下 “开始” 键,输入 “任务管理器” 和 Enter 键。点击 “详细信息” 。选择 “性能” 标签,你将会看到各种各样的关于你的硬件信息,包括“内核” 和 “逻辑处理器” 。\n", - "\n", - "事实证明,“通用”线程的最佳数量就算是可用处理器的数量(对于特定的问题可能不是这样)。这原因来自在Java线程之间切换上下文的代价:存储被挂起线程的当前状态,并检索另一个线程的当前状态,以便从它进入挂起的位置继续执行。对于 8 个处理器和 8 个(计算密集型)Java线程,JVM 在运行这8个任务时从不需要切换上下文。对于比处理器数量少的任务,分配更多线程没有帮助。\n", - "\n", - "定义了 “逻辑处理器” 数量的 Intel 超线程,但并没有增加计算能力 - 该特性在硬件级别维护额外的线程上下文,从而加快了上下文切换,这有助于提高用户界面的响应能力。对于计算密集型任务,请考虑将线程数量与物理内核(而不是超线程)的数量匹配。尽管Java认为每个超线程都是一个处理器,但这似乎是由于 Intel 对超线程的过度营销造成的错误。尽管如此,为了简化编程,我只允许 JVM 决定默认的线程数。 你将需要试验你的产品应用。 这并不意味着将线程数与处理器数相匹配就适用于所有问题; 相反,它主要用于计算密集型解决方案。\n", - "\n", - "### 我可以创建多少个线程?\n", - "\n", - "Thread(线程)对象的最大部分是用于执行方法的 Java 堆栈。查看 Thread (线程)对象的大小因操作系统而异。该程序通过创建 Thread 对象来测试它,直到 JVM 内存不足为止:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/ThreadSize.java\n", - "// {ExcludeFromGradle} Takes a long time or hangs\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "public class ThreadSize {\n", - " static class Dummy extends Thread {\n", - " @Override\n", - " public void run() { new Nap(1); }\n", - " }\n", - " public static void main(String[] args) {\n", - " ExecutorService exec =\n", - " Executors.newCachedThreadPool();\n", - " int count = 0;\n", - " try {\n", - " while(true) {\n", - " exec.execute(new Dummy());\n", - " count++;\n", - " }\n", - " } catch(Error e) {\n", - " System.out.println(\n", - " e.getClass().getSimpleName() + \": \" + count);\n", - " System.exit(0);\n", - " } finally {\n", - " exec.shutdown();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只要你不断递交任务,**CachedThreadPool** 就会继续创建线程。将 **Dummy** 对象递交到 `execute()` 方法以开始任务,如果线程池无可用线程,则分配一个新线程。执行的暂停方法 `pause()` 运行时间必须足够长,使任务不会开始即完成(从而为新任务释放现有线程)。只要任务不断进入而没有完成,**CachedThreadPool** 最终就会耗尽内存。\n", - "\n", - "我并不总是能够在我尝试的每台机器上造成内存不足的错误。在一台机器上,我看到这样的结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "> java ThreadSize\n", - "OutOfMemoryError: 2816" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以使用 **-Xss** 标记减少每个线程栈分配的内存大小。允许的最小线程栈大小是 64k:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ">java -Xss64K ThreadSize\n", - "OutOfMemoryError: 4952" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们将线程栈大小增加到 2M ,我们就可以分配更少的线程。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ">java -Xss2M ThreadSize\n", - "OutOfMemoryError: 722" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Windows 操作系统默认栈大小是 320K,我们可以通过验证它给出的数字与我们完全不设置栈大小时的数字是大致相同:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ">java -Xss320K ThreadSize\n", - "OutOfMemoryError: 2816" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "你还可以使用 **-Xmx** 标志增加 JVM 的最大内存分配:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "shell" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - ">java -Xss64K -Xmx5M ThreadSize\n", - "OutOfMemoryError: 5703" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "请注意的是操作系统还可能对允许的线程数施加限制。\n", - "\n", - "因此,“我可以拥有多少线程”这一问题的答案是“几千个”。但是,如果你发现自己分配了数千个线程,那么你可能需要重新考虑你的做法; 恰当的问题是“我需要多少线程?”\n", - "\n", - "### The WorkStealingPool (工作窃取线程池)\n", - "\n", - "这是一个 **ExecutorService** ,它使用所有可用的(由JVM报告) 处理器自动创建线程池。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/WorkStealingPool.java\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "class ShowThread implements Runnable {\n", - " @Override\n", - " public void run() {\n", - " System.out.println(\n", - " Thread.currentThread().getName());\n", - " }\n", - "}\n", - "\n", - "public class WorkStealingPool {\n", - " public static void main(String[] args)\n", - " throws InterruptedException {\n", - " System.out.println(\n", - " Runtime.getRuntime().availableProcessors());\n", - " ExecutorService exec =\n", - " Executors.newWorkStealingPool();\n", - " IntStream.range(0, 10)\n", - " .mapToObj(n -> new ShowThread())\n", - " .forEach(exec::execute);\n", - " exec.awaitTermination(1, TimeUnit.SECONDS);\n", - " }\n", - "}\n", - "/* Output:\n", - "8\n", - "ForkJoinPool-1-worker-2\n", - "ForkJoinPool-1-worker-1\n", - "ForkJoinPool-1-worker-2\n", - "ForkJoinPool-1-worker-3\n", - "ForkJoinPool-1-worker-2\n", - "ForkJoinPool-1-worker-1\n", - "ForkJoinPool-1-worker-3\n", - "ForkJoinPool-1-worker-1\n", - "ForkJoinPool-1-worker-4\n", - "ForkJoinPool-1-worker-2\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "工作窃取算法允许已经耗尽输入队列中的工作项的线程从其他队列“窃取”工作项。目标是在处理器之间分配工作项,从而最大限度地利用所有可用的处理器来完成计算密集型任务。这项算法也用于 Java 的fork/join 框架。\n", - "\n", - "\n", - "## 异常捕获\n", - "\n", - "这可能会让你感到惊讶:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SwallowedException.java\n", - "import java.util.concurrent.*;\n", - "\n", - "public class SwallowedException {\n", - " public static void main(String[] args)\n", - " throws InterruptedException {\n", - " ExecutorService exec =\n", - " Executors.newSingleThreadExecutor();\n", - " exec.submit(() -> {\n", - " throw new RuntimeException();\n", - " });\n", - " exec.shutdown();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这个程序什么也不输出(然而,如果你用 **execute** 方法替换 `submit()` 方法,你就将会看到异常抛出。这说明在线程中抛出异常是很棘手的,需要特别注意的事情。\n", - "\n", - "你无法捕获到从线程逃逸的异常。一旦异常越过了任务的 `run()` 方法,它就会传递至控制台,除非你采取特殊步骤来捕获此类错误异常。\n", - "\n", - "下面是一个抛出异常的代码,该异常会传递到它的 `run()` 方法之外,而 `main()` 方法会显示运行它时会发生什么:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/ExceptionThread.java\n", - "// {ThrowsException}\n", - "import java.util.concurrent.*;\n", - "\n", - "public class ExceptionThread implements Runnable {\n", - " @Override\n", - " public void run() {\n", - " throw new RuntimeException();\n", - " }\n", - " public static void main(String[] args) {\n", - " ExecutorService es =\n", - " Executors.newCachedThreadPool();\n", - " es.execute(new ExceptionThread());\n", - " es.shutdown();\n", - " }\n", - "}\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"pool-1-thread-1\"\n", - "java.lang.RuntimeException\n", - " at ExceptionThread.run(ExceptionThread.java:8)\n", - " at java.util.concurrent.ThreadPoolExecutor.runW\n", - "orker(ThreadPoolExecutor.java:1142)\n", - " at java.util.concurrent.ThreadPoolExecutor$Work\n", - "er.run(ThreadPoolExecutor.java:617)\n", - " at java.lang.Thread.run(Thread.java:745)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出是(经过调整一些限定符以适应阅读):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Exception in thread \"pool-1-thread-1\" RuntimeException\n", - " at ExceptionThread.run(ExceptionThread.java:9)\n", - " at ThreadPoolExecutor.runWorker(...)\n", - " at ThreadPoolExecutor$Worker.run(...)\n", - " at java.lang.Thread.run(Thread.java:745)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "即使在 `main()` 方法体内包裹 **try-catch** 代码块来捕获异常也不成功:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/NaiveExceptionHandling.java\n", - "// {ThrowsException}\n", - "import java.util.concurrent.*;\n", - "\n", - "public class NaiveExceptionHandling {\n", - " public static void main(String[] args) {\n", - " ExecutorService es =\n", - " Executors.newCachedThreadPool();\n", - " try {\n", - " es.execute(new ExceptionThread());\n", - " } catch(RuntimeException ue) {\n", - " // This statement will NOT execute!\n", - " System.out.println(\"Exception was handled!\");\n", - " } finally {\n", - " es.shutdown();\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "___[ Error Output ]___\n", - "Exception in thread \"pool-1-thread-1\"\n", - "java.lang.RuntimeException\n", - " at ExceptionThread.run(ExceptionThread.java:8)\n", - " at java.util.concurrent.ThreadPoolExecutor.runW\n", - "orker(ThreadPoolExecutor.java:1142)\n", - " at java.util.concurrent.ThreadPoolExecutor$Work\n", - "er.run(ThreadPoolExecutor.java:617)\n", - " at java.lang.Thread.run(Thread.java:745)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这会产生与前一个示例相同的结果:未捕获异常。\n", - "\n", - "为解决这个问题,需要改变 **Executor** (执行器)生成线程的方式。 **Thread.UncaughtExceptionHandler** 是一个添加给每个 **Thread** 对象,用于进行异常处理的接口。\n", - "\n", - "当该线程即将死于未捕获的异常时,将自动调用 `Thread.UncaughtExceptionHandler.uncaughtException()`\n", - " 方法。为了调用该方法,我们创建一个新的 **ThreadFactory** 类型来让 **Thread.UncaughtExceptionHandler** 对象附加到每个它所新创建的 **Thread**(线程)对象上。我们赋值该工厂对象给 **Executors** 对象的 方法,让它的方法来生成新的 **ExecutorService** 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/CaptureUncaughtException.java\n", - "import java.util.concurrent.*;\n", - "\n", - "class ExceptionThread2 implements Runnable {\n", - " @Override\n", - " public void run() {\n", - " Thread t = Thread.currentThread();\n", - " System.out.println(\"run() by \" + t.getName());\n", - " System.out.println(\n", - " \"eh = \" + t.getUncaughtExceptionHandler());\n", - " throw new RuntimeException();\n", - " }\n", - "}\n", - "\n", - "class MyUncaughtExceptionHandler implements\n", - "Thread.UncaughtExceptionHandler {\n", - " @Override\n", - " public void uncaughtException(Thread t, Throwable e) {\n", - " System.out.println(\"caught \" + e);\n", - " }\n", - "}\n", - "\n", - "class HandlerThreadFactory implements ThreadFactory {\n", - " @Override\n", - " public Thread newThread(Runnable r) {\n", - " System.out.println(this + \" creating new Thread\");\n", - " Thread t = new Thread(r);\n", - " System.out.println(\"created \" + t);\n", - " t.setUncaughtExceptionHandler(\n", - " new MyUncaughtExceptionHandler());\n", - " System.out.println(\n", - " \"eh = \" + t.getUncaughtExceptionHandler());\n", - " return t;\n", - " }\n", - "}\n", - "\n", - "public class CaptureUncaughtException {\n", - " public static void main(String[] args) {\n", - " ExecutorService exec =\n", - " Executors.newCachedThreadPool(\n", - " new HandlerThreadFactory());\n", - " exec.execute(new ExceptionThread2());\n", - " exec.shutdown();\n", - " }\n", - "}\n", - "/* Output:\n", - "HandlerThreadFactory@4e25154f creating new Thread\n", - "created Thread[Thread-0,5,main]\n", - "eh = MyUncaughtExceptionHandler@70dea4e\n", - "run() by Thread-0\n", - "eh = MyUncaughtExceptionHandler@70dea4e\n", - "caught java.lang.RuntimeException\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "额外会在代码中添加跟踪机制,用来验证工厂对象创建的线程是否获得新 **UncaughtExceptionHandler** 。现在未捕获的异常由 **uncaughtException** 方法捕获。\n", - "\n", - "上面的示例根据具体情况来设置处理器。如果你知道你将要在代码中处处使用相同的异常处理器,那么更简单的方式是在 **Thread** 类中设置一个 **static**(静态) 字段,并将这个处理器设置为默认的未捕获异常处理器:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SettingDefaultHandler.java\n", - "import java.util.concurrent.*;\n", - "\n", - "public class SettingDefaultHandler {\n", - " public static void main(String[] args) {\n", - " Thread.setDefaultUncaughtExceptionHandler(\n", - " new MyUncaughtExceptionHandler());\n", - " ExecutorService es =\n", - " Executors.newCachedThreadPool();\n", - " es.execute(new ExceptionThread());\n", - " es.shutdown();\n", - " }\n", - "}\n", - "/* Output:\n", - "caught java.lang.RuntimeException\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只有在每个线程没有设置异常处理器时候,默认处理器才会被调用。系统会检查线程专有的版本,如果没有,则检查是否线程组中有专有的 `uncaughtException()` 方法;如果都没有,就会调用 **defaultUncaughtExceptionHandler** 方法。\n", - "\n", - "可以将此方法与 **CompletableFuture** 的改进方法进行比较。\n", - "\n", - "\n", - "## 资源共享\n", - "\n", - "你可以将单线程程序看作一个孤独的实体,在你的问题空间中移动并同一时间只做一件事。因为只有一个实体,你永远不会想到两个实体试图同时使用相同资源的问题:问题犹如两个人试图同时停放在同一个空间,同时走过一扇门,甚至同时说话。\n", - "\n", - "通过并发,事情不再孤单,但现在两个或更多任务可能会相互干扰。如果你不阻止这种冲突,你将有两个任务同时尝试访问同一个银行帐户,打印到同一个打印机,调整同一个阀门,等等。\n", - "\n", - "### 资源竞争\n", - "\n", - "当你启动一个任务来执行某些工作时,可以通过两种不同的方式捕获该工作的结果:通过副作用或通过返回值。\n", - "\n", - "从编程方式上看,副作用似乎更容易:你只需使用结果来操作环境中的某些东西。例如,你的任务可能会执行一些计算,然后直接将其结果写入集合。\n", - "\n", - "伴随这种方式的问题是集合通常是共享资源。当运行多个任务时,任何任务都可能同时读写 *共享资源* 。这揭示了 *资源竞争* 问题,这是处理任务时的主要陷阱之一。\n", - "\n", - "在单线程系统中,你不需要考虑资源竞争,因为你永远不可能同时做多件事。当你有多个任务时,你就必须始终防止资源竞争。\n", - "\n", - "解决此问题的的一种方法是使用能够应对资源竞争的集合,如果多个任务同时尝试对此类集合进行写入,那么此类集合可以应付该问题。在 Java 并发库中,你将发现许多尝试解决资源竞争问题的类;在本附录中,你将看到其中的一些,但覆盖范围并不全面。\n", - "\n", - "请思考以下的示例,其中一个任务负责生成偶数,其他任务则负责消费这些数字。在这里,消费者任务的唯一工作就是检查偶数的有效性。\n", - "\n", - "我们将定义消费者任务 **EvenChecker** 类,以便在后续示例中可复用。为了将 **EvenChecker** 与我们的各种实验生成器类解耦,我们首先创建名为 **IntGenerator** 的抽象类,它包含 **EvenChecker** 必须知道的最低必要方法:它包含 `next()` 方法,以及可以取消它执行生成的方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/IntGenerator.java\n", - "import java.util.concurrent.atomic.AtomicBoolean;\n", - "\n", - "public abstract class IntGenerator {\n", - " private AtomicBoolean canceled =\n", - " new AtomicBoolean();\n", - " public abstract int next();\n", - " public void cancel() { canceled.set(true); }\n", - " public boolean isCanceled() {\n", - " return canceled.get();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`cancel()` 方法改变 **AtomicBoolean** 类型的 **canceled** 标志位的状态, 而 `isCanceled()` 方法则告诉标志位是否设置。因为 **canceled** 标志位是 **AtomicBoolean** 类型,由于它是原子性的,这意味着分配和值返回等简单操作发生时没有中断的可能性,因此你无法在这些简单操作中看到该字段处于中间状态。你将在本附录的后面部分了解有关原子性和 **Atomic** 类的更多信息\n", - "\n", - "任何 **IntGenerator** 都可以使用下面的 **EvenChecker** 类进行测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/EvenChecker.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import onjava.TimedAbort;\n", - "\n", - "public class EvenChecker implements Runnable {\n", - " private IntGenerator generator;\n", - " private final int id;\n", - " public EvenChecker(IntGenerator generator, int id) {\n", - " this.generator = generator;\n", - " this.id = id;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " while(!generator.isCanceled()) {\n", - " int val = generator.next();\n", - " if(val % 2 != 0) {\n", - " System.out.println(val + \" not even!\");\n", - " generator.cancel(); // Cancels all EvenCheckers\n", - " }\n", - " }\n", - " }\n", - " // Test any IntGenerator:\n", - " public static void test(IntGenerator gp, int count) {\n", - " List> checkers =\n", - " IntStream.range(0, count)\n", - " .mapToObj(i -> new EvenChecker(gp, i))\n", - " .map(CompletableFuture::runAsync)\n", - " .collect(Collectors.toList());\n", - " checkers.forEach(CompletableFuture::join);\n", - " }\n", - " // Default value for count:\n", - " public static void test(IntGenerator gp) {\n", - " new TimedAbort(4, \"No odd numbers discovered\");\n", - " test(gp, 10);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`test()` 方法开启了许多访问同一个 **IntGenerator** 的 **EvenChecker**。**EvenChecker** 任务们会不断读取和测试与其关联的 **IntGenerator** 对象中的生成值。如果 **IntGenerator** 导致失败,`test()` 方法会报告并返回。\n", - "\n", - "依赖于 **IntGenerator** 对象的所有 **EvenChecker** 任务都会检查它是否已被取消。如果 `generator.isCanceled()` 返回值为 true ,则 `run()` 方法返回。 任何 **EvenChecker** 任务都可以在 **IntGenerator** 上调用 `cancel()` ,这会导致使用该 **IntGenerator** 的其他所有 **EvenChecker** 正常关闭。\n", - "\n", - "在本设计中,共享公共资源( **IntGenerator** )的任务会监视该资源的终止信号。这消除所谓的竞争条件,其中两个或更多的任务竞争响应某个条件并因此冲突或不一致结果的情况。\n", - "\n", - "你必须仔细考虑并防止并发系统失败的所有可能途径。例如,一个任务不能依赖于另一个任务,因为任务关闭的顺序无法得到保证。这里,通过使任务依赖于非任务对象,我们可以消除潜在的竞争条件。\n", - "\n", - "一般来说,我们假设 `test()` 方法最终失败,因为各个 **EvenChecker** 的任务在 **IntGenerator** 处于 “不恰当的” 状态时,仍能够访问其中的信息。但是,直到 **IntGenerator** 完成许多循环之前,它可能无法检测到问题,具体取决于操作系统的详细信息和其他实现细节。为确保本书的自动构建不会卡住,我们使用 **TimedAbort** 类,在此处定义:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/TimedAbort.java\n", - "// Terminate a program after t seconds\n", - "package onjava;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class TimedAbort {\n", - " private volatile boolean restart = true;\n", - " public TimedAbort(double t, String msg) {\n", - " CompletableFuture.runAsync(() -> {\n", - " try {\n", - " while(restart) {\n", - " restart = false;\n", - " TimeUnit.MILLISECONDS\n", - " .sleep((int)(1000 * t));\n", - " }\n", - " } catch(InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.println(msg);\n", - " System.exit(0);\n", - " });\n", - " }\n", - " public TimedAbort(double t) {\n", - " this(t, \"TimedAbort \" + t);\n", - " }\n", - " public void restart() { restart = true; }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们使用 lambda 表达式创建一个 **Runnable** ,该表达式使用 **CompletableFuture** 的 `runAsync()` 静态方法执行。 `runAsync()` 方法的值会立即返回。 因此,**TimedAbort** 不会保持任何打开的任务,否则已完成任务,但如果它需要太长时间,它仍将终止该任务( **TimedAbort** 有时被称为守护进程)。\n", - "\n", - "**TimedAbort** 还允许你 `restart()` 方法重启任务,在有某些有用的活动进行时保持程序打开。\n", - "\n", - "我们可以看到正在运行的 **TimedAbort** 示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/TestAbort.java\n", - "import onjava.*;\n", - "\n", - "public class TestAbort {\n", - " public static void main(String[] args) {\n", - " new TimedAbort(1);\n", - " System.out.println(\"Napping for 4\");\n", - " new Nap(4);\n", - " }\n", - "}\n", - "/* Output:\n", - "Napping for 4\n", - "TimedAbort 1.0\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果你注释掉 **Nap** 创建实列那行,程序执行会立即退出,表明 **TimedAbort** 没有维持程序打开。\n", - "\n", - "我们将看到第一个 **IntGenerator** 示例有一个生成一系列偶数值的 `next()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/EvenProducer.java\n", - "// When threads collide\n", - "// {VisuallyInspectOutput}\n", - "\n", - "public class EvenProducer extends IntGenerator {\n", - " private int currentEvenValue = 0;\n", - " @Override\n", - " public int next() {\n", - " ++currentEvenValue; // [1]\n", - " ++currentEvenValue;\n", - " return currentEvenValue;\n", - " }\n", - " public static void main(String[] args) {\n", - " EvenChecker.test(new EvenProducer());\n", - " }\n", - "}\n", - "/* Output:\n", - "419 not even!\n", - "425 not even!\n", - "423 not even!\n", - "421 not even!\n", - "417 not even!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [1] 一个任务有可能在另外一个任务执行第一个对 **currentEvenValue** 的自增操作之后,但是没有执行第二个操作之前,调用 `next()` 方法。这将使这个值处于 “不恰当” 的状态。\n", - "\n", - "为了证明这是可能发生的, `EvenChecker.test()` 创建了一组 **EventChecker** 对象,以连续读取 **EvenProducer** 的输出并测试检查每个数值是否都是偶数。如果不是,就会报告错误,而程序也将关闭。\n", - "\n", - "多线程程序的部分问题是,即使存在 bug ,如果失败的可能性很低,程序仍然可以正确显示。\n", - "\n", - "重要的是要注意到自增操作自身需要多个步骤,并且在自增过程中任务可能会被线程机制挂起 - 也就是说,在 Java 中,自增不是原子性的操作。因此,如果不保护任务,即使单纯的自增也不是线程安全的。\n", - "\n", - "该示例程序并不总是在第一次非偶数产生时终止。所有任务都不会立即关闭,这是并发程序的典型特征。\n", - "\n", - "### 解决资源竞争\n", - "\n", - "前面的示例揭示了当你使用线程时的基本问题:你永远不知道线程哪个时刻运行。想象一下坐在一张桌子上,用叉子,将最后一块食物放在盘子上,当叉子到达时,食物突然消失...仅因为你的线程被挂起而另一个用餐者进来吃了食物了。这就是在编写并发程序时要处理的问题。为了使并发工作有效,你需要某种方式来阻止两个任务访问同一个资源,至少在关键时期是这样。\n", - "\n", - "防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它,而在其被解锁时候,另一个任务就可以锁定并使用它,以此类推。如果汽车前排座位是受限资源,那么大喊着 “冲呀” 的孩子就会(在这次旅途过程中)获得该资源的锁。\n", - "\n", - "为了解决线程冲突的问题,基本的并发方案将序列化访问共享资源。这意味着一次只允许一个任务访问共享资源。这通常是通过在访问资源的代码片段周围加上一个子句来实现的,该子句一次只允许一个任务访问这段代码。因为这个子句产生 *互斥* 效果,所以这种机制的通常称为是 *mutex* (互斥量)。\n", - "\n", - "考虑一下屋子里的浴室:多个人(即多个由线程驱动的任务)都希望能独立使用浴室(即共享资源)。为了使用浴室,一个人先敲门来看看是否可用。如果没人的话,他就能进入浴室并锁上门。任何其他想使用浴室的任务就会被 “阻挡”,因此这些任务就在门口等待,直到浴室是可用的。\n", - "\n", - "当浴室使用完毕,就是时候给其他任务进入,这时比喻就有点不准确了。事实上没有人排队,我们也不知道下一个使用浴室是谁,因为线程调度机制并不是确定性的。相反,就好像在浴室前面有一组被阻止的任务一样,当锁定浴室的任务解锁并出现时,线程调度机制将会决定下一个要进入的任务。\n", - "\n", - "Java 以提供关键字 **synchronized** 的形式,为防止资源冲突提供了内置支持。当任务希望执行被 **synchronized** 关键字保护的代码片段的时候,Java 编译器会生成代码以查看锁是否可用。如果可用,该任务获取锁,执行代码,然后释放锁。\n", - "\n", - "共享资源一般是以对象形式存在的内存片段,但也可以是文件、I/O 端口,或者类似打印机的东西。要控制对共享资源的访问,得先把它包装进一个对象。然后把任何访问该资源的方法标记为 **synchronized** 。 如果一个任务在调用其中一个 **synchronized** 方法之内,那么在这个任务从该方法返回之前,其他所有要调用该对象的 **synchronized** 方法的任务都会被阻塞。\n", - "\n", - "通常你会将字段设为 **private**,并仅通过方法访问这些字段。你可用通过使用 **synchronized** 关键字声明方法来防止资源冲突。如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "synchronized void f() { /* ... */ }\n", - "synchronized void g() { /* ... */ }" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "所有对象都自动包含独立的锁(也称为 *monitor*,即监视器)。当你调用对象上任何 **synchronized** 方法,此对象将被加锁,并且该对象上的的其他 **synchronized** 方法调用只有等到前一个方法执行完成并释放了锁之后才能被调用。如果一个任务对对象调用了 `f()` ,对于同一个对象而言,就只能等到 `f()` 调用结束并释放了锁之后,其他任务才能调用 `f()` 和 `g()`。所以,某个特定对象的所有 **synchronized** 方法共享同一个锁,这个锁可以防止多个任务同时写入对象内存。\n", - "\n", - "在使用并发时,将字段设为 **private** 特别重要;否则,**synchronized** 关键字不能阻止其他任务直接访问字段,从而产生资源冲突。\n", - "\n", - "一个线程可以获取对象的锁多次。如果一个方法调用在同一个对象上的第二个方法,而后者又在同一个对象上调用另一个方法,就会发生这种情况。 JVM 会跟踪对象被锁定的次数。如果对象已解锁,则其计数为 0 。当一个线程首次获得锁时,计数变为 1 。每次同一线程在同一对象上获取另一个锁时,计数就会自增。显然,只有首先获得锁的线程才允许多次获取多个锁。每当线程离开 **synchronized** 方法时,计数递减,直到计数变为 0 ,完全释放锁以给其他线程使用。每个类也有一个锁(作为该类的 **Class** 对象的一部分),因此 **synchronized** 静态方法可以在类范围的基础上彼此锁定,不让同时访问静态数据。\n", - "\n", - "你应该什么时候使用同步呢?可以永远 *Brian* 的同步法则[^2]。\n", - "\n", - "> 如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。\n", - "\n", - "如果在你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关方法。如果只同步其中一个方法,那么其他方法可以忽略对象锁,并且可以不受惩罚地调用。这是很重要的一点:每个访问临界共享资源的方法都必须被同步,否则将不会正确地工作。\n", - "\n", - "### 同步控制 EventProducer\n", - "\n", - "通过在 **EvenProducer.java** 文件中添加 **synchronized** 关键字,可以防止不希望的线程访问:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SynchronizedEvenProducer.java\n", - "// Simplifying mutexes with the synchronized keyword\n", - "import onjava.Nap;\n", - "\n", - "public class\n", - "SynchronizedEvenProducer extends IntGenerator {\n", - " private int currentEvenValue = 0;\n", - " @Override\n", - " public synchronized int next() {\n", - " ++currentEvenValue;\n", - " new Nap(0.01); // Cause failure faster\n", - " ++currentEvenValue;\n", - " return currentEvenValue;\n", - " }\n", - " public static void main(String[] args) {\n", - " EvenChecker.test(new SynchronizedEvenProducer());\n", - " }\n", - "}\n", - "/* Output:\n", - "No odd numbers discovered\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在两个自增操作之间插入 `Nap()` 构造器方法,以提高在 **currentEvenValue** 是奇数的状态时上下文切换的可能性。因为互斥锁可以阻止多个任务同时进入临界区,所有这不会产生失败。第一个进入 `next()` 方法的任务将获得锁,任何试图获取锁的后续任务都将被阻塞,直到第一个任务释放锁。此时,调度机制选择另一个等待锁的任务。通过这种方式,任何时刻只能有一个任务通过互斥锁保护的代码。\n", - "\n", - "\n", - "## volatile 关键字\n", - "\n", - "**volatile** 可能是 Java 中最微妙和最难用的关键字。幸运的是,在现代 Java 中,你几乎总能避免使用它,如果你确实看到它在代码中使用,你应该保持怀疑态度和怀疑 - 这很有可能代码是过时的,或者编写代码的人不清楚使用它在大体上(或两者都有)易变性(**volatile**) 或并发性的后果。\n", - "\n", - "使用 **volatile** 有三个理由。\n", - "\n", - "### 字分裂\n", - "\n", - "当你的 Java 数据类型足够大(在 Java 中 **long** 和 **double** 类型都是 64 位),写入变量的过程分两步进行,就会发生 *Word tearing* (字分裂)情况。 JVM 被允许将64位数量的读写作为两个单独的32位操作执行[^3],这增加了在读写过程中发生上下文切换的可能性,因此其他任务会看到不正确的结果。这被称为 *Word tearing* (字分裂),因为你可能只看到其中一部分修改后的值。基本上,任务有时可以在第一步之后但在第二步之前读取变量,从而产生垃圾值(对于例如 **boolean** 或 **int** 类型的小变量是没有问题的;任何 **long** 或 **double** 类型则除外)。\n", - "\n", - "在缺乏任何其他保护的情况下,用 **volatile** 修饰符定义一个 **long** 或 **double** 变量,可阻止字分裂情况。然而,如果使用 **synchronized** 或 **java.util.concurrent.atomic** 类之一保护这些变量,则 **volatile** 将被取代。此外,**volatile** 不会影响到增量操作并不是原子操作的事实。\n", - "\n", - "### 可见性\n", - "\n", - "第二个问题属于 [Java 并发的四句格言](./24-Concurrent-Programming.md#四句格言)里第二句格言 “一切都重要” 的部分。你必须假设每个任务拥有自己的处理器,并且每个处理器都有自己的本地内存缓存。该缓存准许处理器允许的更快,因为处理器并不总是需要从比起使用缓存显著花费更多时间的主内存中获取数据。\n", - "\n", - "出现这个问题是因为 Java 尝试尽可能地提高执行效率。缓存的主要目的是避免从主内存中读取数据。当并发时,有时不清楚 Java 什么时候应该将值从主内存刷新到本地缓存 — 而这个问题称为 *缓存一致性* ( *cache coherence* )。\n", - "\n", - "每个线程都可以在处理器缓存中存储变量的本地副本。将字段定义为 **volatile** 可以防止这些编译器优化,这样读写就可以直接进入内存,而不会被缓存。一旦该字段发生写操作,所有任务的读操作都将看到更改。如果一个 **volatile** 字段刚好存储在本地缓存,则会立即将其写入主内存,并且该字段的任何读取都始终发生在主内存中。\n", - "\n", - "**volatile** 应该在何时适用于变量:\n", - "\n", - "1. 该变量同时被多个任务访问。\n", - "2. 这些访问中至少有一个是写操作。\n", - "3. 你尝试避免同步 (在现代 Java 中,你可以使用高级工具来避免进行同步)。\n", - "\n", - "举个例字,如果你使用变量作为停止任务的标志值。那么该变量至少必须声明为 **volatile** (尽管这并不一定能保证这种标志的线程安全)。否则,当一个任务更改标志值时,这些更改可以存储在本地处理器缓存中,而不会刷新到主内存。当另一个任务查看标记值时,它不会看到更改。我更喜欢在 [并发编程](./24-Concurrent-Programming.md) 中 [终止耗时任务](./24-Concurrent-Programming.md#终止耗时任务) 章节中使用 **AtomicBoolean** 类型作为标志值的办法\n", - "\n", - "任务对其自身变量所做的任何写操作都始终对该任务可见,因此,如果只在任务中使用变量,你不需要使其变量声明为 **volatile** 。\n", - "\n", - "如果单个线程对变量写入而其他线程只读取它,你可以放弃该变量声明为 **volatile**。通常,如果你有多个线程对变量写入,**volatile** 无法解决你的问题,并且你必须使用 **synchronized** 来防止竞争条件。 这有一个特殊的例外:可以让多个线程对该变量写入,*只要它们不需要先读取它并使用该值创建新值来写入变量* 。如果这些多个线程在结果中使用旧值,则会出现竞争条件,因为其余一个线程之一可能会在你的线程进行计算时修改该变量。即使你开始做对了,想象一下在代码修改或维护过程中忘记和引入一个重大变化是多么容易,或者对于不理解问题的不同程序员来说是多么容易(这在 Java 中尤其成问题因为程序员倾向于严重依赖编译时检查来告诉他们,他们的代码是否正确)。\n", - "\n", - "重要的是要理解原子性和可见性是两个不同的概念。在非 **volatile** 变量上的原子操作是不能保证是否将其刷新到主内存。\n", - "\n", - "同步也会让主内存刷新,所以如果一个变量完全由 **synchronized** 的方法或代码段(或者 **java.util.concurrent.atomic** 库里类型之一)所保护,则不需要让变量用 **volatile**。\n", - "\n", - "### 重排与 *Happen-Before* 原则\n", - "\n", - "只要结果不会改变程序表现,Java 可以通过重排指令来优化性能。然而,重排可能会影响本地处理器缓存与主内存交互的方式,从而产生细微的程序 bug 。直到 Java 5 才理解并解决了这个无法阻止重排的问题。现在,**volatile** 关键字可以阻止重排 **volatile** 变量周围的读写指令。这种重排规则称为 *happens before* 担保原则 。\n", - "\n", - "这项原则保证在 **volatile** 变量读写之前发生的指令先于它们的读写之前发生。同样,任何跟随 **volatile** 变量之后读写的操作都保证发生在它们的读写之后。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/ReOrdering.java\n", - "\n", - "public class ReOrdering implements Runnable {\n", - " int one, two, three, four, five, six;\n", - " volatile int volaTile;\n", - " @Override\n", - " public void run() {\n", - " one = 1;\n", - " two = 2;\n", - " three = 3;\n", - " volaTile = 92;\n", - " int x = four;\n", - " int y = five;\n", - " int z = six;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "例子中 **one**,**two**,**three** 变量赋值操作就可以被重排,只要它们都发生在 **volatile** 变量写操作之前。同样,只要 **volatile** 变量写操作发生在所有语句之前, **x**,**y**,**z** 语句可以被重排。这种 **volatile** (易变性)操作通常称为 *memory barrier* (内存屏障)。 *happens before* 担保原则确保 **volatile** 变量的读写指令不能跨过内存屏障进行重排。\n", - "\n", - "*happens before* 担保原则还有另一个作用:当线程向一个 **volatile** 变量写入时,在线程写入之前的其他所有变量(包括非 **volatile** 变量)也会刷新到主内存。当线程读取一个 **volatile** 变量时,它也会读取其他所有变量(包括非 **volatile** 变量)与 **volatile** 变量一起刷新到主内存。尽管这是一个重要的特性,它解决了 Java 5 版本之前出现的一些非常狡猾的 bug ,但是你不应该依赖这项特性来“自动”使周围的变量变得易变性 ( **volatile** )的 。如果你希望变量是易变性 ( **volatile** )的,那么维护代码的任何人都应该清楚这一点。\n", - "\n", - "### 什么时候使用 volatile\n", - "\n", - "对于 Java 早期版本,编写一个证明需要 **volatile** 的示例并不难。如果你进行搜索,你可以找到这样的例子,但是如果你在 Java 8 中尝试这些例子,它们就不起作用了(我没有找到任何一个)。我努力写这样一个例子,但没什么用。这可能原因是 JVM 或者硬件,或两者都得到了改进。这种效果对现有的应该 **volatile** (易变性) 但不 **volatile** 的存储的程序是有益的;对于此类程序,失误发生的频率要低得多,而且问题更难追踪。\n", - "\n", - "如果你尝试使用 **volatile** ,你可能更应该尝试让一个变量线程安全而不是引起同步的成本。因为 **volatile** 使用起来非常微妙和棘手,所以我建议根本不要使用它;相反,请使用本附录后面介绍的 **java.util.concurrent.atomic** 里面类之一。它们以比同步低得多的成本提供了完全的线程安全性。\n", - "\n", - "如果你正在尝试调试其他人的并发代码,请首先查找使用 **volatile** 的代码并将其替换为**Atomic** 变量。除非你确定程序员对并发性有很高的理解,否则它们很可能会误用 **volatile** 。\n", - "\n", - "\n", - "## 原子性\n", - "\n", - "在 Java 线程的讨论中,经常反复提交但不正确的知识是:“原子操作不需要同步”。 一个 *原子操作* 是不能被线程调度机制中断的操作;一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前(切换到其他线程执行)执行完毕。依赖于原子性是很棘手且很危险的,如果你是一个并发编程专家,或者你得到了来自这样的专家的帮助,你才应该使用原子性来代替同步,如果你认为自己足够聪明可以应付这种玩火似的情况,那么请接受下面的测试:\n", - "\n", - "> Goetz 测试:如果你可以编写用于现代微处理器的高性能 JVM ,那么就有资格考虑是否可以避免同步[^4] 。\n", - "\n", - "了解原子性是很有用的,并且知道它与其他高级技术一起用于实现一些更加巧妙的 **java.util.concurrent** 库组件。 但是要坚决抵制自己依赖它的冲动。\n", - "\n", - "原子性可以应用于除 **long** 和 **double** 之外的所有基本类型之上的 “简单操作”。对于读写和写入除 **long** 和 **double** 之外的基本类型变量这样的操作,可以保证它们作为不可分 (原子) 的操作执行。\n", - "\n", - "\n", - "因为原子操作不能被线程机制中断。专家程序员可以利用这个来编写无锁代码(*lock-free code*),这些代码不需要被同步。但即使这样也过于简单化了。有时候,甚至看起来应该是安全的原子操作,实际上也可能不安全。本书的读者通常不会通过前面提到的 Goetz 测试,因此也就不具备用原子操作来替换同步的能力。尝试着移除同步通常是一种表示不成熟优化的信号,并且会给你带来大量的麻烦,可能不会获得太多或任何的好处。\n", - "\n", - "在多核处理器系统,相对于单核处理器而言,可见性问题远比原子性问题多得多。一个任务所做的修改,即使它们是原子性的,也可能对其他任务不可见(例如,修改只是暂时性存储在本地处理器缓存中),因此不同的任务对应用的状态有不同的视图。另一方面,同步机制强制多核处理器系统上的一个任务做出的修改必须在应用程序中是可见的。如果没有同步机制,那么修改时可见性将无法确认。\n", - "\n", - "什么才属于原子操作时?对于属性中的值做赋值和返回操作通常都是原子性的,但是在 C++ 中,甚至下面的操作都可能是原子性的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "c++" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "i++; // Might be atomic in C++\n", - "i += 2; // Might be atomic in C++" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是在 C++ 中,这取决于编译器和处理器。你无法编写出依赖于原子性的 C++ 跨平台代码,因为 C++ [^5]没有像 Java 那样的一致 *内存模型* (memory model)。\n", - "\n", - "在 Java 中,上面的操作肯定不是原子性的,正如下面的方法产生的 JVM 指令中可以看到的那样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/NotAtomic.java\n", - "// {javap -c NotAtomic}\n", - "// {VisuallyInspectOutput}\n", - "\n", - "public class NotAtomic {\n", - " int i;\n", - " void f1() { i++; }\n", - " void f2() { i += 3; }\n", - "}\n", - "/* Output:\n", - "Compiled from \"NotAtomic.java\"\n", - "public class NotAtomic {\n", - " int i;\n", - "\n", - " public NotAtomic();\n", - " Code:\n", - " 0: aload_0\n", - " 1: invokespecial #1 // Method\n", - "java/lang/Object.\"\":()V\n", - " 4: return\n", - "\n", - " void f1();\n", - " Code:\n", - " 0: aload_0\n", - " 1: dup\n", - " 2: getfield #2 // Field\n", - "i:I\n", - " 5: iconst_1\n", - " 6: iadd\n", - " 7: putfield #2 // Field\n", - "i:I\n", - " 10: return\n", - "\n", - " void f2();\n", - " Code:\n", - " 0: aload_0\n", - " 1: dup\n", - " 2: getfield #2 // Field\n", - "i:I\n", - " 5: iconst_3\n", - " 6: iadd\n", - " 7: putfield #2 // Field\n", - "i:I\n", - " 10: return\n", - "}\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每条指令都会产生一个 “get” 和 “put”,它们之间还有一些其他指令。因此在获取指令和放置指令之间,另有一个任务可能会修改这个属性,所有,这些操作不是原子性的。\n", - "\n", - "让我们通过定义一个抽象类来测试原子性的概念,这个抽象类的方法是将一个整数类型进行偶数自增,并且 `run()` 不断地调用这个方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/IntTestable.java\n", - "import java.util.function.*;\n", - "\n", - "public abstract class\n", - "IntTestable implements Runnable, IntSupplier {\n", - " abstract void evenIncrement();\n", - " @Override\n", - " public void run() {\n", - " while(true)\n", - " evenIncrement();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**IntSupplier** 是一个带 `getAsInt()` 方法的函数式接口。\n", - "\n", - "现在我们可以创建一个测试,它作为一个独立的任务启动 `run()` 方法 ,然后获取值来检查它们是否为偶数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/Atomicity.java\n", - "import java.util.concurrent.*;\n", - "import onjava.TimedAbort;\n", - "\n", - "public class Atomicity {\n", - " public static void test(IntTestable it) {\n", - " new TimedAbort(4, \"No failures found\");\n", - " CompletableFuture.runAsync(it);\n", - " while(true) {\n", - " int val = it.getAsInt();\n", - " if(val % 2 != 0) {\n", - " System.out.println(\"failed with: \" + val);\n", - " System.exit(0);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "很容易盲目地应用原子性的概念。在这里,`getAsInt()` 似乎是安全的原子性方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/UnsafeReturn.java\n", - "import java.util.function.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class UnsafeReturn extends IntTestable {\n", - " private int i = 0;\n", - " public int getAsInt() { return i; }\n", - " public synchronized void evenIncrement() {\n", - " i++; i++;\n", - " }\n", - " public static void main(String[] args) {\n", - " Atomicity.test(new UnsafeReturn());\n", - " }\n", - "}\n", - "/* Output:\n", - "failed with: 79\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "但是, `Atomicity.test()` 方法还是出现有非偶数的失败。尽管,返回 **i** 变量确实是原子操作,但是同步缺失允许了在对象处于不稳定的中间状态时读取值。最重要的是,由于 **i** 也不是 **volatile** 变量,所以存在可见性问题。包括 `getValue()` 和 `evenIncrement()` 都必须同步(这也顾及到没有使用 **volatile** 修饰的 **i** 变量):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SafeReturn.java\n", - "import java.util.function.*;\n", - "import java.util.concurrent.*;\n", - "\n", - "public class SafeReturn extends IntTestable {\n", - " private int i = 0;\n", - " public synchronized int getAsInt() { return i; }\n", - " public synchronized void evenIncrement() {\n", - " i++; i++;\n", - " }\n", - " public static void main(String[] args) {\n", - " Atomicity.test(new SafeReturn());\n", - " }\n", - "}\n", - "/* Output:\n", - "No failures found\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "只有并发编程专家有能力去尝试做像前面例子情况的优化;再次强调,请遵循 Brain 的同步法则。\n", - "\n", - "### Josh 的序列号\n", - "\n", - "作为第二个示例,考虑某些更简单的东西:创建一个产生序列号的类,灵感启发于 Joshua Bloch 的 *Effective Java Programming Language Guide* (Addison-Wesley 出版社, 2001) 第 190 页。每次调用 `nextSerialNumber()` 都必须返回唯一值。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SerialNumbers.java\n", - "\n", - "public class SerialNumbers {\n", - " private volatile int serialNumber = 0;\n", - " public int nextSerialNumber() {\n", - " return serialNumber++; // Not thread-safe\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**SerialNumbers** 是你可以想象到最简单的类,如果你具备 C++ 或者其他底层的知识背景,你可能会认为自增是一个原子操作,因为 C++ 的自增操作通常被单个微处理器指令所实现(尽管不是以任何一致,可靠,跨平台的方式)。但是,正如前面所提到的,Java 自增操作不是原子性的,并且操作同时涉及读取和写入,因此即使在这样一个简单的操作中,也存在有线程问题的空间。\n", - "\n", - "我们在这里加入 volatile ,看看它是否有帮助。然而,真正的问题是 `nextSerialNumber()` 方法在不进行线程同步的情况下访问共享的可变变量值。\n", - "\n", - "为了测试 **SerialNumbers**,我们将创建一个不会耗尽内存的集合,假如需要很长时间来检测问题。这里展示的 **CircularSet** 重用了存储 **int** 变量的内存,最终新值会覆盖旧值(复制的速度通常发生足够快,你也可以使用 **java.util.Set** 来代替):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/CircularSet.java\n", - "// Reuses storage so we don't run out of memory\n", - "import java.util.*;\n", - "\n", - "public class CircularSet {\n", - " private int[] array;\n", - " private int size;\n", - " private int index = 0;\n", - " public CircularSet(int size) {\n", - " this.size = size;\n", - " array = new int[size];\n", - " // Initialize to a value not produced\n", - " // by SerialNumbers:\n", - " Arrays.fill(array, -1);\n", - " }\n", - " public synchronized void add(int i) {\n", - " array[index] = i;\n", - " // Wrap index and write over old elements:\n", - " index = ++index % size;\n", - " }\n", - " public synchronized boolean contains(int val) {\n", - " for(int i = 0; i < size; i++)\n", - " if(array[i] == val) return true;\n", - " return false;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`add()` 和 `contains()` 方法是线程同步的,以防止线程冲突。\n", - "The add() and contains() methods are synchronized to prevent thread collisions.\n", - "\n", - "**SerialNumberChecker** 类包含一个存储最近序列号的 **CircularSet** 变量,以及一个填充数值给 **CircularSet** 和确保它里面的序列号是唯一的 `run()` 方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SerialNumberChecker.java\n", - "// Test SerialNumbers implementations for thread-safety\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "public class SerialNumberChecker implements Runnable {\n", - " private CircularSet serials = new CircularSet(1000);\n", - " private SerialNumbers producer;\n", - " public SerialNumberChecker(SerialNumbers producer) {\n", - " this.producer = producer;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " while(true) {\n", - " int serial = producer.nextSerialNumber();\n", - " if(serials.contains(serial)) {\n", - " System.out.println(\"Duplicate: \" + serial);\n", - " System.exit(0);\n", - " }\n", - " serials.add(serial);\n", - " }\n", - " }\n", - " static void test(SerialNumbers producer) {\n", - " for(int i = 0; i < 10; i++)\n", - " CompletableFuture.runAsync(\n", - " new SerialNumberChecker(producer));\n", - " new Nap(4, \"No duplicates detected\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`test()` 方法创建多个任务来竞争单独的 **SerialNumbers** 对象。这时参于竞争的的 SerialNumberChecker 任务们就会试图生成重复的序列号(这情况在具有更多内核处理器的机器上发生得更快)。\n", - "\n", - "当我们测试基本的 **SerialNumbers** 类,它会失败(产生重复序列号):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SerialNumberTest.java\n", - "\n", - "public class SerialNumberTest {\n", - " public static void main(String[] args) {\n", - " SerialNumberChecker.test(new SerialNumbers());\n", - " }\n", - "}\n", - "/* Output:\n", - "Duplicate: 148044\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**volatile** 在这里没有帮助。要解决这个问题,将 **synchronized** 关键字添加到 `nextSerialNumber()` 方法 :" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SynchronizedSerialNumbers.java\n", - "\n", - "public class\n", - "SynchronizedSerialNumbers extends SerialNumbers {\n", - " private int serialNumber = 0;\n", - " public synchronized int nextSerialNumber() {\n", - " return serialNumber++;\n", - " }\n", - " public static void main(String[] args) {\n", - " SerialNumberChecker.test(\n", - " new SynchronizedSerialNumbers());\n", - " }\n", - "}\n", - "/* Output:\n", - "No duplicates detected\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**volatile** 不再是必需的,因为 **synchronized** 关键字保证了 volatile (易变性) 的特性。\n", - "\n", - "读取和赋值原语应该是安全的原子操作。然后,正如在 **UnsafeReturn.java** 中所看到,使用原子操作访问处于不稳定中间状态的对象仍然很容易。对这个问题做出假设既棘手又危险。最明智的做法就是遵循 Brian 的同步规则(如果可以,首先不要共享变量)。\n", - "\n", - "### 原子类\n", - "\n", - "Java 5 引入了专用的原子变量类,例如 **AtomicInteger**、**AtomicLong**、**AtomicReference** 等。这些提供了原子性升级。这些快速、无锁的操作,它们是利用了现代处理器上可用的机器级原子性。\n", - "\n", - "下面,我们可以使用 **atomicinteger** 重写 **unsafereturn.java** 示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/AtomicIntegerTest.java\n", - "import java.util.concurrent.*;\n", - "import java.util.concurrent.atomic.*;\n", - "import java.util.*;\n", - "import onjava.*;\n", - "\n", - "public class AtomicIntegerTest extends IntTestable {\n", - " private AtomicInteger i = new AtomicInteger(0);\n", - " public int getAsInt() { return i.get(); }\n", - " public void evenIncrement() { i.addAndGet(2); }\n", - " public static void main(String[] args) {\n", - " Atomicity.test(new AtomicIntegerTest());\n", - " }\n", - "}\n", - "/* Output:\n", - "No failures found\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在,我们通过使用 **AtomicInteger** 来消除了 **synchronized** 关键字。\n", - "\n", - "下面使用 **AtomicInteger** 来重写 **SynchronizedEvenProducer.java** 示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/AtomicEvenProducer.java\n", - "// Atomic classes: occasionally useful in regular code\n", - "import java.util.concurrent.atomic.*;\n", - "\n", - "public class AtomicEvenProducer extends IntGenerator {\n", - " private AtomicInteger currentEvenValue =\n", - " new AtomicInteger(0);\n", - " @Override\n", - " public int next() {\n", - " return currentEvenValue.addAndGet(2);\n", - " }\n", - " public static void main(String[] args) {\n", - " EvenChecker.test(new AtomicEvenProducer());\n", - " }\n", - "}\n", - "/* Output:\n", - "No odd numbers discovered\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "再次,使用 **AtomicInteger** 消除了对所有其他同步方式的需要。\n", - "\n", - "下面是一个使用 **AtomicInteger** 实现 **SerialNumbers** 的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/AtomicSerialNumbers.java\n", - "import java.util.concurrent.atomic.*;\n", - "\n", - "public class\n", - "AtomicSerialNumbers extends SerialNumbers {\n", - " private AtomicInteger serialNumber =\n", - " new AtomicInteger();\n", - " public synchronized int nextSerialNumber() {\n", - " return serialNumber.getAndIncrement();\n", - " }\n", - " public static void main(String[] args) {\n", - " SerialNumberChecker.test(\n", - " new AtomicSerialNumbers());\n", - " }\n", - "}\n", - "/* Output:\n", - "No duplicates detected\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这些都是对单一字段的简单示例; 当你创建更复杂的类时,你必须确定哪些字段需要保护,在某些情况下,你可能仍然最后在方法上使用 **synchronized** 关键字。\n", - "\n", - "\n", - "## 临界区\n", - "\n", - "有时,你只是想防止多线程访问方法中的部分代码,而不是整个方法。要隔离的代码部分称为临界区,它使用我们用于保护整个方法相同的 **synchronized** 关键字创建,但使用不同的语法。语法如下, **synchronized** 指定某个对象作为锁用于同步控制花括号内的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "synchronized(syncObject) {\n", - " // This code can be accessed\n", - " // by only one task at a time\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这也被称为 *同步控制块* (synchronized block);在进入此段代码前,必须得到 **syncObject** 对象的锁。如果一些其他任务已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。当发生这种情况时,尝试获取该锁的任务就会挂起。线程调度会定期回来并检查锁是否已经释放;如果释放了锁则唤醒任务。\n", - "\n", - "使用同步控制块而不是同步控制整个方法的主要动机是性能(有时,算法确实聪明,但还是要特别警惕来自并发性问题上的聪明)。下面的示例演示了同步控制代码块而不是整个方法可以使方法更容易被其他任务访问。该示例会统计成功访问 `method()` 的计数并且发起一些任务来尝试竞争调用 `method()` 方法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SynchronizedComparison.java\n", - "// speeds up access.\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.concurrent.atomic.*;\n", - "import onjava.Nap;\n", - "\n", - "abstract class Guarded {\n", - " AtomicLong callCount = new AtomicLong();\n", - " public abstract void method();\n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName() +\n", - " \": \" + callCount.get();\n", - " }\n", - "}\n", - "\n", - "class SynchronizedMethod extends Guarded {\n", - " public synchronized void method() {\n", - " new Nap(0.01);\n", - " callCount.incrementAndGet();\n", - " }\n", - "}\n", - "\n", - "class CriticalSection extends Guarded {\n", - " public void method() {\n", - " new Nap(0.01);\n", - " synchronized(this) {\n", - " callCount.incrementAndGet();\n", - " }\n", - " }\n", - "}\n", - "\n", - "class Caller implements Runnable {\n", - " private Guarded g;\n", - " Caller(Guarded g) { this.g = g; }\n", - " private AtomicLong successfulCalls =\n", - " new AtomicLong();\n", - " private AtomicBoolean stop =\n", - " new AtomicBoolean(false);\n", - " @Override\n", - " public void run() {\n", - " new Timer().schedule(new TimerTask() {\n", - " public void run() { stop.set(true); }\n", - " }, 2500);\n", - " while(!stop.get()) {\n", - " g.method();\n", - " successfulCalls.getAndIncrement();\n", - " }\n", - " System.out.println(\n", - " \"-> \" + successfulCalls.get());\n", - " }\n", - "}\n", - "\n", - "public class SynchronizedComparison {\n", - " static void test(Guarded g) {\n", - " List> callers =\n", - " Stream.of(\n", - " new Caller(g),\n", - " new Caller(g),\n", - " new Caller(g),\n", - " new Caller(g))\n", - " .map(CompletableFuture::runAsync)\n", - " .collect(Collectors.toList());\n", - " callers.forEach(CompletableFuture::join);\n", - " System.out.println(g);\n", - " }\n", - " public static void main(String[] args) {\n", - " test(new CriticalSection());\n", - " test(new SynchronizedMethod());\n", - " }\n", - "}\n", - "/* Output:\n", - "-> 243\n", - "-> 243\n", - "-> 243\n", - "-> 243\n", - "CriticalSection: 972\n", - "-> 69\n", - "-> 61\n", - "-> 83\n", - "-> 36\n", - "SynchronizedMethod: 249\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Guarded** 类负责跟踪 **callCount** 中成功调用 `method()` 的次数。**SynchronizedMethod** 的方式是同步控制整个 `method` 方法,而 **CriticalSection** 的方式是使用同步控制块来仅同步 `method` 方法的一部分代码。这样,耗时的 **Nap** 对象可以被排除到同步控制块外。输出会显示 **CriticalSection** 中可用的 `method()` 有多少。\n", - "\n", - "请记住,使用同步控制块是有风险;它要求你确切知道同步控制块外的非同步代码是实际上要线程安全的。\n", - "\n", - "**Caller** 是尝试在给定的时间周期内尽可能多地调用 `method()` 方法(并报告调用次数)的任务。为了构建这个时间周期,我们会使用虽然有点过时但仍然可以很好地工作的 **java.util.Timer** 类。此类接收一个 **TimerTask** 参数, 但该参数并不是函数式接口,所以我们不能使用 **lambda** 表达式,必须显式创建该类对象(在这种情况下,使用匿名内部类)。当超时的时候,定时对象将设置 **AtomicBoolean** 类型的 **stop** 字段为 true ,这样循环就会退出。\n", - "\n", - "`test()` 方法接收一个 **Guarded** 类对象并创建四个 **Caller** 任务。所有这些任务都添加到同一个 **Guarded** 对象上,因此它们竞争来获取使用 `method()` 方法的锁。\n", - "\n", - "你通常会看到从一次运行到下一次运行的输出变化。结果表明, **CriticalSection** 方式比起 **SynchronizedMethod** 方式允许更多地访问 `method()` 方法。这通常是使用 **synchronized** 块取代同步控制整个方法的原因:允许其他任务更多访问(只要这样做是线程安全的)。\n", - "\n", - "### 在其他对象上同步\n", - "\n", - "**synchronized** 块必须给定一个在其上进行同步的对象。并且最合理的方式是,使用其方法正在被调用的当前对象: **synchronized(this)**,这正是前面示例中 **CriticalSection** 采取的方式。在这种方式中,当 **synchronized** 块获得锁的时候,那么该对象其他的 **synchronized** 方法和临界区就不能被调用了。因此,在进行同步时,临界区的作用是减小同步的范围。\n", - "\n", - "有时必须在另一个对象上同步,但是如果你要这样做,就必须确保所有相关的任务都是在同一个任务上同步的。下面的示例演示了当对象中的方法在不同的锁上同步时,两个任务可以同时进入同一对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/SyncOnObject.java\n", - "// Synchronizing on another object\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import onjava.Nap;\n", - "\n", - "class DualSynch {\n", - " ConcurrentLinkedQueue trace =\n", - " new ConcurrentLinkedQueue<>();\n", - " public synchronized void f(boolean nap) {\n", - " for(int i = 0; i < 5; i++) {\n", - " trace.add(String.format(\"f() \" + i));\n", - " if(nap) new Nap(0.01);\n", - " }\n", - " }\n", - " private Object syncObject = new Object();\n", - " public void g(boolean nap) {\n", - " synchronized(syncObject) {\n", - " for(int i = 0; i < 5; i++) {\n", - " trace.add(String.format(\"g() \" + i));\n", - " if(nap) new Nap(0.01);\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "public class SyncOnObject {\n", - " static void test(boolean fNap, boolean gNap) {\n", - " DualSynch ds = new DualSynch();\n", - " List> cfs =\n", - " Arrays.stream(new Runnable[] {\n", - " () -> ds.f(fNap), () -> ds.g(gNap) })\n", - " .map(CompletableFuture::runAsync)\n", - " .collect(Collectors.toList());\n", - " cfs.forEach(CompletableFuture::join);\n", - " ds.trace.forEach(System.out::println);\n", - " }\n", - " public static void main(String[] args) {\n", - " test(true, false);\n", - " System.out.println(\"****\");\n", - " test(false, true);\n", - " }\n", - "}\n", - "/* Output:\n", - "f() 0\n", - "g() 0\n", - "g() 1\n", - "g() 2\n", - "g() 3\n", - "g() 4\n", - "f() 1\n", - "f() 2\n", - "f() 3\n", - "f() 4\n", - "****\n", - "f() 0\n", - "g() 0\n", - "f() 1\n", - "f() 2\n", - "f() 3\n", - "f() 4\n", - "g() 1\n", - "g() 2\n", - "g() 3\n", - "g() 4\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`DualSync.f()` 方法(通过同步整个方法)在 **this** 上同步,而 `g()` 方法有一个在 **syncObject** 上同步的 **synchronized** 块。因此,这两个同步是互相独立的。在 `test()` 方法中运行的两个调用 `f()` 和 `g()` 方法的独立任务演示了这一点。**fNap** 和 **gNap** 标志变量分别指示 `f()` 和 `g()` 是否应该在其 **for** 循环中调用 `Nap()` 方法。例如,当 f() 线程休眠时 ,该线程继续持有它的锁,但是你可以看到这并不阻止调用 `g()` ,反之亦然。\n", - "\n", - "### 使用显式锁对象\n", - "\n", - "**java.util.concurrent** 库包含在 **java.util.concurrent.locks** 中定义的显示互斥锁机制。 必须显式地创建,锁定和解锁 **Lock** 对象,因此它产出的代码没有内置 **synchronized** 关键字那么优雅。然而,它在解决某些类型的问题时更加灵活。下面是使用显式 **Lock** 对象重写 **SynchronizedEvenProducer.java** 代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/MutexEvenProducer.java\n", - "// Preventing thread collisions with mutexes\n", - "import java.util.concurrent.locks.*;\n", - "import onjava.Nap;\n", - "\n", - "public class MutexEvenProducer extends IntGenerator {\n", - " private int currentEvenValue = 0;\n", - " private Lock lock = new ReentrantLock();\n", - " @Override\n", - " public int next() {\n", - " lock.lock();\n", - " try {\n", - " ++currentEvenValue;\n", - " new Nap(0.01); // Cause failure faster\n", - " ++currentEvenValue;\n", - " return currentEvenValue;\n", - " } finally {\n", - " lock.unlock();\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " EvenChecker.test(new MutexEvenProducer());\n", - " }\n", - "}\n", - "/*\n", - "No odd numbers discovered\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**MutexEvenProducer** 添加一个名为 **lock** 的互斥锁并在 `next()` 中使用 `lock()` 和 `unlock()` 方法创建一个临界区。当你使用 **Lock** 对象时,使用下面显示的习惯用法很重要:在调用 `Lock()` 之后,你必须放置 **try-finally** 语句,该语句在 **finally** 子句中带有 `unlock()` 方法 - 这是确保锁总是被释放的惟一方法。注意,**return** 语句必须出现在 **try** 子句中,以确保 **unlock()** 不会过早发生并将数据暴露给第二个任务。\n", - "\n", - "尽管 **try-finally** 比起使用 **synchronized** 关键字需要用得更多代码,但它也代表了显式锁对象的优势之一。如果使用 **synchronized** 关键字失败,就会抛出异常,但是你没有机会进行任何清理以保持系统处于良好状态。而使用显式锁对象,可以使用 **finally** 子句在系统中维护适当的状态。\n", - "\n", - "一般来说,当你使用 **synchronized** 的时候,需要编写的代码更少,并且用户出错的机会也大大减少,因此通常只在解决特殊问题时使用显式锁对象。例如,使用 **synchronized** 关键字,你不能尝试获得锁并让其失败,或者你在一段时间内尝试获得锁,然后放弃 - 为此,你必须使用这个并发库。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/AttemptLocking.java\n", - "// Locks in the concurrent library allow you\n", - "// to give up on trying to acquire a lock\n", - "import java.util.concurrent.*;\n", - "import java.util.concurrent.locks.*;\n", - "import onjava.Nap;\n", - "\n", - "public class AttemptLocking {\n", - " private ReentrantLock lock = new ReentrantLock();\n", - " public void untimed() {\n", - " boolean captured = lock.tryLock();\n", - " try {\n", - " System.out.println(\"tryLock(): \" + captured);\n", - " } finally {\n", - " if(captured)\n", - " lock.unlock();\n", - " }\n", - " }\n", - " public void timed() {\n", - " boolean captured = false;\n", - " try {\n", - " captured = lock.tryLock(2, TimeUnit.SECONDS);\n", - " } catch(InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " try {\n", - " System.out.println(\n", - " \"tryLock(2, TimeUnit.SECONDS): \" + captured);\n", - " } finally {\n", - " if(captured)\n", - " lock.unlock();\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " final AttemptLocking al = new AttemptLocking();\n", - " al.untimed(); // True -- lock is available\n", - " al.timed(); // True -- lock is available\n", - " // Now create a second task to grab the lock:\n", - " CompletableFuture.runAsync( () -> {\n", - " al.lock.lock();\n", - " System.out.println(\"acquired\");\n", - " });\n", - " new Nap(0.1); // Give the second task a chance\n", - " al.untimed(); // False -- lock grabbed by task\n", - " al.timed(); // False -- lock grabbed by task\n", - " }\n", - "}\n", - "/* Output:\n", - "tryLock(): true\n", - "tryLock(2, TimeUnit.SECONDS): true\n", - "acquired\n", - "tryLock(): false\n", - "tryLock(2, TimeUnit.SECONDS): false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**ReentrantLock** 可以尝试或者放弃获取锁,因此如果某些任务已经拥有锁,你可以决定放弃并执行其他操作,而不是一直等到锁释放,就像 `untimed()` 方法那样。而在 `timed()` 方法中,则尝试获取可能在 2 秒后没成功而放弃的锁。在 `main()` 方法中,一个单独的线程被匿名类所创建,并且它会获得锁,因此让 `untimed()` 和 `timed() ` 方法有东西可以去竞争。\n", - "\n", - "显式锁比起内置同步锁提供更细粒度的加锁和解锁控制。这对于实现专门的同步并发结构,比如用于遍历链表节点的 *交替锁* ( *hand-over-hand locking* ) ,也称为 *锁耦合* ( *lock coupling* )- 该遍历代码要求必须在当前节点的解锁之前捕获下一个节点的锁。\n", - "\n", - "\n", - "## 库组件\n", - "\n", - "**java.util.concurrent** 库提供大量旨在解决并发问题的类,可以帮助你生成更简单,更鲁棒的并发程序。但请注意,这些工具是比起并行流和 **CompletableFuture** 更底层的机制。\n", - "\n", - "在本节中,我们将看一些使用不同组件的示例,然后讨论一下 *lock-free*(无锁) 库组件是如何工作的。\n", - "\n", - "### DelayQueue\n", - "\n", - "这是一个无界阻塞队列 ( **BlockingQueue** ),用于放置实现了 **Delayed** 接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,因此队首对象的延迟到期的时间最长。如果没有任何延迟到期,那么就不会有队首元素,并且 `poll()` 将返回 **null**(正因为这样,你不能将 **null** 放置到这种队列中)。\n", - "\n", - "下面是一个示例,其中的 **Delayed** 对象自身就是任务,而 **DelayedTaskConsumer** 将最“紧急”的任务(到期时间最长的任务)从队列中取出,然后运行它。注意的是这样 **DelayQueue** 就成为了优先级队列的一种变体。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/DelayQueueDemo.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import static java.util.concurrent.TimeUnit.*;\n", - "\n", - "class DelayedTask implements Runnable, Delayed {\n", - " private static int counter = 0;\n", - " private final int id = counter++;\n", - " private final int delta;\n", - " private final long trigger;\n", - " protected static List sequence =\n", - " new ArrayList<>();\n", - " DelayedTask(int delayInMilliseconds) {\n", - " delta = delayInMilliseconds;\n", - " trigger = System.nanoTime() +\n", - " NANOSECONDS.convert(delta, MILLISECONDS);\n", - " sequence.add(this);\n", - " }\n", - " @Override\n", - " public long getDelay(TimeUnit unit) {\n", - " return unit.convert(\n", - " trigger - System.nanoTime(), NANOSECONDS);\n", - " }\n", - " @Override\n", - " public int compareTo(Delayed arg) {\n", - " DelayedTask that = (DelayedTask)arg;\n", - " if(trigger < that.trigger) return -1;\n", - " if(trigger > that.trigger) return 1;\n", - " return 0;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " System.out.print(this + \" \");\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return\n", - " String.format(\"[%d] Task %d\", delta, id);\n", - " }\n", - " public String summary() {\n", - " return String.format(\"(%d:%d)\", id, delta);\n", - " }\n", - " public static class EndTask extends DelayedTask {\n", - " EndTask(int delay) { super(delay); }\n", - " @Override\n", - " public void run() {\n", - " sequence.forEach(dt ->\n", - " System.out.println(dt.summary()));\n", - " }\n", - " }\n", - "}\n", - "\n", - "public class DelayQueueDemo {\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " DelayQueue tasks =\n", - " Stream.concat( // Random delays:\n", - " new Random(47).ints(20, 0, 4000)\n", - " .mapToObj(DelayedTask::new),\n", - " // Add the summarizing task:\n", - " Stream.of(new DelayedTask.EndTask(4000)))\n", - " .collect(Collectors\n", - " .toCollection(DelayQueue::new));\n", - " while(tasks.size() > 0)\n", - " tasks.take().run();\n", - " }\n", - "}\n", - "/* Output:\n", - "[128] Task 12 [429] Task 6 [551] Task 13 [555] Task 2\n", - "[693] Task 3 [809] Task 15 [961] Task 5 [1258] Task 1\n", - "[1258] Task 20 [1520] Task 19 [1861] Task 4 [1998] Task\n", - "17 [2200] Task 8 [2207] Task 10 [2288] Task 11 [2522]\n", - "Task 9 [2589] Task 14 [2861] Task 18 [2868] Task 7\n", - "[3278] Task 16 (0:4000)\n", - "(1:1258)\n", - "(2:555)\n", - "(3:693)\n", - "(4:1861)\n", - "(5:961)\n", - "(6:429)\n", - "(7:2868)\n", - "(8:2200)\n", - "(9:2522)\n", - "(10:2207)\n", - "(11:2288)\n", - "(12:128)\n", - "(13:551)\n", - "(14:2589)\n", - "(15:809)\n", - "(16:3278)\n", - "(17:1998)\n", - "(18:2861)\n", - "(19:1520)\n", - "(20:1258)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**DelayedTask** 包含一个称为 **sequence** 的 **List<DelayedTask>** ,它保存了任务被创建的顺序,因此我们可以看到排序是按照实际发生的顺序执行的。\n", - "\n", - "**Delay** 接口有一个方法, `getDelay()` , 该方法用来告知延迟到期有多长时间,或者延迟在多长时间之前已经到期了。这个方法强制我们去使用 **TimeUnit** 类,因为这就是参数类型。这会产生一个非常方便的类,因为你可以很容易地转换单位而无需作任何声明。例如,**delta** 的值是以毫秒为单位存储的,但是 `System.nanoTime()` 产生的时间则是以纳秒为单位的。你可以转换 **delta** 的值,方法是声明它的单位以及你希望以什么单位来表示,就像下面这样:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "NANOSECONDS.convert(delta, MILLISECONDS);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在 `getDelay()` 中, 所希望的单位是作为 **unit** 参数传递进来的,你使用它将当前时间与触发时间之间的差转换为调用者要求的单位,而无需知道这些单位是什么(这是*策略*设计模式的一个简单示例,在这种模式中,算法的一部分是作为参数传递进来的)。\n", - "\n", - "为了排序, **Delayed** 接口还继承了 **Comparable** 接口,因此必须实现 `compareTo()` , 使其可以产生合理的比较。\n", - "\n", - "从输出中可以看到,任务创建的顺序对执行顺序没有任何影响 - 相反,任务是按照所期望的延迟顺序所执行的。\n", - "\n", - "### PriorityBlockingQueue\n", - "\n", - "这是一个很基础的优先级队列,它具有可阻塞的读取操作。在下面的示例中, **Prioritized** 对象会被赋予优先级编号。几个 **Producer** 任务的实例会插入 **Prioritized** 对象到 **PriorityBlockingQueue** 中,但插入之间会有随机延时。然后,单个 **Consumer** 任务在执行 `take()` 时会显示多个选项,**PriorityBlockingQueue** 会将当前具有最高优先级的 **Prioritized** 对象提供给它。\n", - "\n", - "在 **Prioritized** 中的静态变量 **counter** 是 **AtomicInteger** 类型。这是必要的,因为有多个 **Producer** 并行运行;如果不是 **AtomicInteger** 类型,你将会看到重复的 **id** 号。 这个问题在 [并发编程](./24-Concurrent-Programming.md) 的 [构造函数非线程安全](./24-Concurrent-Programming.md) 一节中讨论过。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// lowlevel/PriorityBlockingQueueDemo.java\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.concurrent.*;\n", - "import java.util.concurrent.atomic.*;\n", - "import onjava.Nap;\n", - "\n", - "class Prioritized implements Comparable {\n", - " private static AtomicInteger counter =\n", - " new AtomicInteger();\n", - " private final int id = counter.getAndIncrement();\n", - " private final int priority;\n", - " private static List sequence =\n", - " new CopyOnWriteArrayList<>();\n", - " Prioritized(int priority) {\n", - " this.priority = priority;\n", - " sequence.add(this);\n", - " }\n", - " @Override\n", - " public int compareTo(Prioritized arg) {\n", - " return priority < arg.priority ? 1 :\n", - " (priority > arg.priority ? -1 : 0);\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return String.format(\n", - " \"[%d] Prioritized %d\", priority, id);\n", - " }\n", - " public void displaySequence() {\n", - " int count = 0;\n", - " for(Prioritized pt : sequence) {\n", - " System.out.printf(\"(%d:%d)\", pt.id, pt.priority);\n", - " if(++count % 5 == 0)\n", - " System.out.println();\n", - " }\n", - " }\n", - " public static class EndSentinel extends Prioritized {\n", - " EndSentinel() { super(-1); }\n", - " }\n", - "}\n", - "\n", - "class Producer implements Runnable {\n", - " private static AtomicInteger seed =\n", - " new AtomicInteger(47);\n", - " private SplittableRandom rand =\n", - " new SplittableRandom(seed.getAndAdd(10));\n", - " private Queue queue;\n", - " Producer(Queue q) {\n", - " queue = q;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " rand.ints(10, 0, 20)\n", - " .mapToObj(Prioritized::new)\n", - " .peek(p -> new Nap(rand.nextDouble() / 10))\n", - " .forEach(p -> queue.add(p));\n", - " queue.add(new Prioritized.EndSentinel());\n", - " }\n", - "}\n", - "\n", - "class Consumer implements Runnable {\n", - " private PriorityBlockingQueue q;\n", - " private SplittableRandom rand =\n", - " new SplittableRandom(47);\n", - " Consumer(PriorityBlockingQueue q) {\n", - " this.q = q;\n", - " }\n", - " @Override\n", - " public void run() {\n", - " while(true) {\n", - " try {\n", - " Prioritized pt = q.take();\n", - " System.out.println(pt);\n", - " if(pt instanceof Prioritized.EndSentinel) {\n", - " pt.displaySequence();\n", - " break;\n", - " }\n", - " new Nap(rand.nextDouble() / 10);\n", - " } catch(InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " }\n", - "}\n", - "\n", - "public class PriorityBlockingQueueDemo {\n", - " public static void main(String[] args) {\n", - " PriorityBlockingQueue queue =\n", - " new PriorityBlockingQueue<>();\n", - " CompletableFuture.runAsync(new Producer(queue));\n", - " CompletableFuture.runAsync(new Producer(queue));\n", - " CompletableFuture.runAsync(new Producer(queue));\n", - " CompletableFuture.runAsync(new Consumer(queue))\n", - " .join();\n", - " }\n", - "}\n", - "/* Output:\n", - "[15] Prioritized 2\n", - "[17] Prioritized 1\n", - "[17] Prioritized 5\n", - "[16] Prioritized 6\n", - "[14] Prioritized 9\n", - "[12] Prioritized 0\n", - "[11] Prioritized 4\n", - "[11] Prioritized 12\n", - "[13] Prioritized 13\n", - "[12] Prioritized 16\n", - "[14] Prioritized 18\n", - "[15] Prioritized 23\n", - "[18] Prioritized 26\n", - "[16] Prioritized 29\n", - "[12] Prioritized 17\n", - "[11] Prioritized 30\n", - "[11] Prioritized 24\n", - "[10] Prioritized 15\n", - "[10] Prioritized 22\n", - "[8] Prioritized 25\n", - "[8] Prioritized 11\n", - "[8] Prioritized 10\n", - "[6] Prioritized 31\n", - "[3] Prioritized 7\n", - "[2] Prioritized 20\n", - "[1] Prioritized 3\n", - "[0] Prioritized 19\n", - "[0] Prioritized 8\n", - "[0] Prioritized 14\n", - "[0] Prioritized 21\n", - "[-1] Prioritized 28\n", - "(0:12)(2:15)(1:17)(3:1)(4:11)\n", - "(5:17)(6:16)(7:3)(8:0)(9:14)\n", - "(10:8)(11:8)(12:11)(13:13)(14:0)\n", - "(15:10)(16:12)(17:12)(18:14)(19:0)\n", - "(20:2)(21:0)(22:10)(23:15)(24:11)\n", - "(25:8)(26:18)(27:-1)(28:-1)(29:16)\n", - "(30:11)(31:6)(32:-1)\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "与前面的示例一样,**Prioritized** 对象的创建顺序在 **sequence** 的 **list** 对象上所记入,以便与实际执行顺序进行比较。 **EndSentinel** 是用于告知 **Consumer** 对象关闭的特殊类型。\n", - "\n", - "**Producer** 使用 **AtomicInteger** 变量为 **SplittableRandom** 设置随机生成种子,以便不同的 **Producer** 生成不同的队列。 这是必需的,因为多个生产者并行创建,如果不是这样,创建过程并不会是线程安全的。\n", - "\n", - "**Producer** 和 **Consumer** 通过 **PriorityBlockingQueue** 相互连接。因为阻塞队列的性质提供了所有必要的同步,因为阻塞队列的性质提供了所有必要的同步,请注意,显式同步是并不需要的 — 从队列中读取数据时,你不用考虑队列中是否有任何元素,因为队列在没有元素时将阻塞读取。\n", - "\n", - "### 无锁集合\n", - "\n", - "[集合](./12-Collections.md) 章节强调集合是基本的编程工具,这也要求包含并发性。因此,早期的集合比如 **Vector** 和 **Hashtable** 有许多使用 **synchronized** 机制的方法。当这些集合不是在多线程应用中使用时,这就导致了不可接收的开销。在 Java 1.2 版本中,新的集合库是非同步的,而给 **Collection** 类赋予了各种 **static** **synchronized** 修饰的方法来同步不同的集合类型。虽然这是一个改进,因为它让你可以选择是否对集合使用同步,但是开销仍然基于同步锁定。 Java 5 版本添加新的集合类型,专门用于增加线程安全性能,使用巧妙的技术来消除锁定。\n", - "\n", - "无锁集合有一个有趣的特性:只要读取者仅能看到已完成修改的结果,对集合的修改就可以同时发生在读取发生时。这是通过一些策略实现的。为了让你了解它们是如何工作的,我们来看看其中的一些。\n", - "\n", - "#### 复制策略\n", - "\n", - "使用“复制”策略,修改是在数据结构一部分的单独副本(或有时是整个数据的副本)上进行的,并且在整个修改过程期间这个副本是不可见的。仅当修改完成时,修改后的结构才与“主”数据结构安全地交换,然后读取者才会看到修改。\n", - "\n", - "在 **CopyOnWriteArrayList** ,写入操作会复制整个底层数组。保留原来的数组,以便在修改复制的数组时可以线程安全地进行读取。当修改完成后,原子操作会将其交换到新数组中,以便新的读取操作能够看到新数组内容。 **CopyOnWriteArrayList** 的其中一个好处是,当多个迭代器遍历和修改列表时,它不会抛出 **ConcurrentModificationException** 异常,因此你不用就像过去必须做的那样,编写特殊的代码来防止此类异常。\n", - "\n", - "**CopyOnWriteArraySet** 使用 **CopyOnWriteArrayList** 来实现其无锁行为。\n", - "\n", - "**ConcurrentHashMap** 和 **ConcurrentLinkedQueue** 使用类似的技术来允许并发读写,但是只复制和修改集合的一部分,而不是整个集合。然而,读取者仍然不会看到任何不完整的修改。**ConcurrentHashMap** **不会抛出concurrentmodificationexception** 异常。\n", - "\n", - "#### 比较并交换 (CAS)\n", - "\n", - "在 比较并交换 (CAS) 中,你从内存中获取一个值,并在计算新值时保留原始值。然后使用 CAS 指令,它将原始值与当前内存中的值进行比较,如果这两个值是相等的,则将内存中的旧值替换为计算新值的结果,所有操作都在一个原子操作中完成。如果原始值比较失败,则不会进行交换,因为这意味着另一个线程同时修改了内存。在这种情况下,你的代码必须再次尝试,获取一个新的原始值并重复该操作。\n", - "\n", - "如果内存仅轻量竞争,CAS操作几乎总是在没有重复尝试的情况下完成,因此它非常快。相反,**synchronized** 操作需要考虑每次获取和释放锁的成本,这要昂贵得多,而且没有额外的好处。随着内存竞争的增加,使用 CAS 的操作会变慢,因为它必须更频繁地重复自己的操作,但这是对更多资源竞争的动态响应。这确实是一种优雅的方法。\n", - "\n", - "最重要的是,许多现代处理器的汇编语言中都有一条 CAS 指令,并且也被 JVM 中的 CAS 操作(例如 **Atomic** 类中的操作)所使用。CAS 指令在硬件层面中是原子性的,并且与你所期望的操作一样快。\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "本附录主要是为了让你在遇到底层并发代码时能对此有一定的了解,尽管本文还远没对这个主题进行全面的讨论。为此,你需要先从阅读由 Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea (Addison-Wesley 出版社, 2006)所著作的 *Java Concurrency in Practice* (国内译名:Java并发编程实战)开始了解。理想情况下,这本书会完全吓跑你在 Java 中尝试去编写底层并发代码。如果没有,那么你几乎肯定患上了达克效应(DunningKruger Effect),这是一种认知偏差,“你知道的越少,对自己的能力就越有信心”。请记住,当前的语言设计人员仍然在清理早期语言设计人员过于自信造成的混乱(例如,查看 Thread 类中有多少方法被弃用,而 volatile 直到 Java 5 才正确工作)。\n", - "\n", - "以下是并发编程的步骤:\n", - "\n", - "1. 不要使用它。想一些其他方法来使你写的程序变的更快。\n", - "2. 如果你必须使用它,请使用在 [并发编程](./24-Concurrent-Programming.md) - parallel Streams and CompletableFutures 中展示的现代高级工具。\n", - "3. 不要在任务间共享变量,在任务之间必须传递的任何信息都应该使用 Java.util.concurrent 库中的并发数据结构。\n", - "4. 如果必须在任务之间共享变量,请使用 java.util.concurrent.atomic 里面其中一种类型,或在任何直接或间接访问这些变量的方法上应用 synchronized。 当你不这样做时,很容易被愚弄,以为你已经把所有东西都包括在内。 说真的,尝试使用步骤 3。\n", - "5. 如果步骤 4 产生的结果太慢,你可以尝试使用volatile 或其他技术来调整代码,但是如果你正在阅读本书并认为你已经准备好尝试这些方法,那么你就超出了你的深度。 返回步骤#1。\n", - "\n", - "通常可以只使用 java.util.concurrent 库组件来编写并发程序,完全避免来自应用 volatile 和 synchronized 的挑战。注意,我可以通过 [并发编程](./24-Concurrent-Programming.md) 中的示例来做到这一点。\n", - "\n", - "[^1]: 在某些平台上,特别是 Windows ,默认值可能非常难以查明。你可以使用 -Xss 标志调整堆栈大小。\n", - "\n", - "[^2]: 引自 Brian Goetz, Java Concurrency in Practice 一书的作者 , 该书由 Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, and Doug Lea 联合著作 (Addison-Wesley 出版社, 2006)。↩\n", - "\n", - "[^3]: 请注意,在64位处理器上可能不会发生这种情况,从而消除了这个问题。\n", - "\n", - "[^4]: 这个测试的推论是,“如果某人表示线程是容易并且简单的,请确保这个人没有对你的项目做出重要的决策。如果那个人已经做出,那么你就已经陷入麻烦之中了。”\n", - "\n", - "[^5]: 这在即将产生的 C++ 的标准中得到了补救。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-New-IO.ipynb b/jupyter/Appendix-New-IO.ipynb deleted file mode 100644 index e5cd36b7..00000000 --- a/jupyter/Appendix-New-IO.ipynb +++ /dev/null @@ -1,1446 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:新IO\n", - "\n", - "\n", - "> Java 新I/O 库是在 1.4 版本引入到 `Java .nio.* package` 中的,旨在更快速。\n", - "\n", - "实际上,新 I/O 使用 **NIO**(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 **NIO** 的种种优点。即使我们不显式地使用 **NIO** 方式来编写代码,也能带来性能和速度的提高。这种提升不仅仅体现在文件读写(File I/O),同时也体现在网络读写(Network I/O)中。例如,网络编程。\n", - "\n", - "速度的提升来自于使用了更接近操作系统 I/O 执行方式的结构:**Channel**(通道) 和 **Buffer**(缓冲区)。我们可以想象一个煤矿:通道就是连接矿层(数据)的矿井,缓冲区是运送煤矿的小车。通过小车装煤,再从车里取矿。换句话说,我们不能直接和 **Channel** 交互; 我们需要与 **Buffer** 交互并将 **Buffer** 中的数据发送到 **Channel** 中;**Channel** 需要从 **Buffer** 中提取或放入数据。\n", - "\n", - "本篇我们将深入探讨 `nio` 包。虽然 像 I/O 流这样的高级库使用了 **NIO**,但多数时候,我们考虑这个层次的问题。使用Java 7 和 8 版本,理想情况下我们甚至不必费心去处理 I/O 流。当然,一些特殊情况除外。在[文件](./17-Files.md)(**File**)一章中基本涵盖了我们日常使用的相关内容。只有在遇到性能瓶颈(例如内存映射文件)或创建自己的 I/O 库时,我们才需要去理解 **NIO**。\n", - "\n", - "\n", - "\n", - "## ByteBuffer\n", - "\n", - "\n", - "有且仅有 **ByteBuffer**(字节缓冲区,保存原始字节的缓冲区)这一类型可直接与通道交互。查看 `java.nio.`**ByteBuffer** 的 JDK 文档,你会发现它是相当基础的:通过初始化某个大小的存储空间,再使用一些方法以原始字节形式或原始数据类型来放置和获取数据。但是我们无法直接存放对象,即使是最基本的 **String** 类型数据。这是一个相当底层的操作,也正因如此,使得它与大多数操作系统的映射更加高效。\n", - "\n", - "\n", - "旧式 I/O 中的三个类分别被更新成 **FileChannel**(文件通道),分别是:**FileInputStream**、**FileOutputStream**,以及用于读写的 **RandomAccessFile** 类。\n", - "\n", - "注意,这些都是符合底层 **NIO** 特性的字节操作流。 另外,还有 **Reader** 和 **Writer** 字符模式的类是不产生通道的。但 `java.nio.channels.`**Channels** 类具有从通道中生成 **Reader** 和 **Writer** 的实用方法。\n", - "\n", - "下面来练习上述三种类型的流生成可读、可写、可读/写的通道:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们不保证这段代码用于其他用途时是否有效\n", - "// 访问 http://OnJava8.com 了解更多信息\n", - "// 从流中获取通道\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class GetChannel {\n", - " private static String name = \"data.txt\";\n", - " private static final int BSIZE = 1024;\n", - " public static void main(String[] args) {\n", - " // 写入一个文件:\n", - " try(\n", - " FileChannel fc = new FileOutputStream(name)\n", - " .getChannel()\n", - " ) {\n", - " fc.write(ByteBuffer\n", - " .wrap(\"Some text \".getBytes()));\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // 在文件尾添加:\n", - " try(\n", - " FileChannel fc = new RandomAccessFile(\n", - " name, \"rw\").getChannel()\n", - " ) {\n", - " fc.position(fc.size()); // 移动到结尾\n", - " fc.write(ByteBuffer\n", - " .wrap(\"Some more\".getBytes()));\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // 读取文件e:\n", - " try(\n", - " FileChannel fc = new FileInputStream(name)\n", - " .getChannel()\n", - " ) {\n", - " ByteBuffer buff = ByteBuffer.allocate(BSIZE);\n", - " fc.read(buff);\n", - " buff.flip();\n", - " while(buff.hasRemaining())\n", - " System.out.write(buff.get());\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.flush();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Some text Some more" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们这里所讲的任何流类,都可以通过调用 `getChannel( )` 方法生成一个 **FileChannel**(文件通道)。**FileChannel** 的操作相当基础:操作 **ByteBuffer** 来用于读写,并独占式访问和锁定文件区域(稍后将对此进行描述)。\n", - "\n", - "将字节放入 **ByteBuffer** 的一种方法是直接调用 `put()` 方法将一个或多个字节放入 **ByteBuffer**;当然也可以是其它基本类型的数据。此外,参考上例,我们还可以调用 `wrap()` 方法包装现有字节数组到 **ByteBuffer**。执行此操作时,不会复制底层数组,而是将其用作生成的 **ByteBuffer** 存储。这样产生的 **ByteBuffer** 是数组“支持”的。\n", - "\n", - "data.txt 文件被 **RandomAccessFile** 重新打开。**注意**,你可以在文件中移动 **FileChanne**。 在这里,它被移动到末尾,以便添加额外的写操作。\n", - "\n", - "对于只读访问,必须使用静态 `allocate()` 方法显式地分配 **ByteBuffer**。**NIO** 的目标是快速移动大量数据,因此 **ByteBuffer** 的大小应该很重要 —— 实际上,这里设置的 1K 都可能偏小了(我们在工作中应该反复测试以找到最佳大小)。\n", - "\n", - "通过使用 `allocateDirect()` 而不是 `allocate()` 来生成与操作系统具备更高耦合度的“直接”缓冲区,也有可能获得更高的速度。然而,这种分配的开销更大,而且实际效果因操作系统的不同而有所不同,因此,在工作中你必须再次测试程序,以检验直接缓冲区是否能为你带来速度上的优势。\n", - "\n", - "一旦调用 **FileChannel** 类的 `read()` 方法将字节数据存储到 **ByteBuffer** 中,你还必须调用缓冲区上的 `flip()` 方法来准备好提取字节(这听起来有点粗糙,实际上这已是非常低层的操作,且为了达到最高速度)。如果要进一步调用 `read()` 来使用 **ByteBuffer** ,还需要每次 `clear()` 来准备缓冲区。下面是个简单的代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/ChannelCopy.java\n", - "\n", - "// 使用 channels and buffers 移动文件\n", - "// {java ChannelCopy ChannelCopy.java test.txt}\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class ChannelCopy {\n", - " private static final int BSIZE = 1024;\n", - " public static void main(String[] args) {\n", - " if(args.length != 2) {\n", - " System.out.println(\n", - " \"arguments: sourcefile destfile\");\n", - " System.exit(1);\n", - " }\n", - " try(\n", - " FileChannel in = new FileInputStream(\n", - " args[0]).getChannel();\n", - " FileChannel out = new FileOutputStream(\n", - " args[1]).getChannel()\n", - " ) {\n", - " ByteBuffer buffer = ByteBuffer.allocate(BSIZE);\n", - " while(in.read(buffer) != -1) {\n", - " buffer.flip(); // 准备写入\n", - " out.write(buffer);\n", - " buffer.clear(); // 准备读取\n", - " }\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**第一个FileChannel** 用于读取;**第二个FileChannel** 用于写入。当 **ByteBuffer** 分配好存储,调用 **FileChannel** 的 `read()` 方法返回 **-1**(毫无疑问,这是来源于 Unix 和 C 语言)时,说明输入流读取完了。在每次 `read()` 将数据放入缓冲区之后,`flip()` 都会准备好缓冲区,以便 `write()` 提取它的信息。在 `write()` 之后,数据仍然在缓冲区中,我们需要 `clear()` 来重置所有内部指针,以便在下一次 `read()` 中接受数据。 \n", - "\n", - "但是,上例并不是处理这种操作的理想方法。方法 `transferTo()` 和 `transferFrom()` 允许你直接连接此通道到彼通道:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/TransferTo.java\n", - "\n", - "// 使用 transferTo() 在通道间传输\n", - "// {java TransferTo TransferTo.java TransferTo.txt}\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class TransferTo {\n", - " public static void main(String[] args) {\n", - " if(args.length != 2) {\n", - " System.out.println(\n", - " \"arguments: sourcefile destfile\");\n", - " System.exit(1);\n", - " }\n", - " try(\n", - " FileChannel in = new FileInputStream(\n", - " args[0]).getChannel();\n", - " FileChannel out = new FileOutputStream(\n", - " args[1]).getChannel()\n", - " ) {\n", - " in.transferTo(0, in.size(), out);\n", - " // Or:\n", - " // out.transferFrom(in, 0, in.size());\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可能不会经常用到,但知道这一点很好。\n", - "\n", - "\n", - "\n", - "## 数据转换\n", - "\n", - "\n", - "为了将 **GetChannel.java** 文件中的信息打印出来。在 Java 中,我们每次提取一个字节的数据并将其转换为字符。看起来很简单 —— 如果你有看过 `ava.nio.`**CharBuffer** 类,你会发现一个 `toString()` 方法。该方法的作用是“返回一个包含此缓冲区字符的字符串”。\n", - "\n", - "既然 **ByteBuffer** 可以通过 **CharBuffer** 类的 `asCharBuffer()` 方法查看,那我们就来尝试一样。从下面输出语句的第一行可以看出,这并不正确:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/BufferToText.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// text 和 ByteBuffers 互转\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.nio.charset.*;\n", - "import java.io.*;\n", - "\n", - "public class BufferToText {\n", - " private static final int BSIZE = 1024;\n", - " public static void main(String[] args) {\n", - "\n", - " try(\n", - " FileChannel fc = new FileOutputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.write(ByteBuffer.wrap(\"Some text\".getBytes()));\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "\n", - " ByteBuffer buff = ByteBuffer.allocate(BSIZE);\n", - "\n", - " try(\n", - " FileChannel fc = new FileInputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.read(buff);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "\n", - " buff.flip();\n", - " // 无法运行\n", - " System.out.println(buff.asCharBuffer());\n", - " // 使用默认系统默认编码解码\n", - " buff.rewind();\n", - " String encoding =\n", - " System.getProperty(\"file.encoding\");\n", - " System.out.println(\"Decoded using \" +\n", - " encoding + \": \"\n", - " + Charset.forName(encoding).decode(buff));\n", - "\n", - " // 编码和打印\n", - " try(\n", - " FileChannel fc = new FileOutputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.write(ByteBuffer.wrap(\n", - " \"Some text\".getBytes(\"UTF-16BE\")));\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "\n", - " // 尝试再次读取:\n", - " buff.clear();\n", - " try(\n", - " FileChannel fc = new FileInputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.read(buff);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "\n", - " buff.flip();\n", - " System.out.println(buff.asCharBuffer());\n", - " // 通过 CharBuffer 写入:\n", - " buff = ByteBuffer.allocate(24);\n", - " buff.asCharBuffer().put(\"Some text\");\n", - "\n", - " try(\n", - " FileChannel fc = new FileOutputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.write(buff);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // 读取和显示:\n", - " buff.clear();\n", - "\n", - " try(\n", - " FileChannel fc = new FileInputStream(\n", - " \"data2.txt\").getChannel()\n", - " ) {\n", - " fc.read(buff);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "\n", - " buff.flip();\n", - " System.out.println(buff.asCharBuffer());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "????\n", - "Decoded using windows-1252: Some text\n", - "Some text\n", - "Some textNULNULNUL" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "缓冲区包含普通字节,为了将这些字节转换为字符,我们必须在输入时对它们进行编码(这样它们输出时就有意义了),或者在输出时对它们进行解码。我们可以使用 `java.nio.charset.`**Charset** 字符集工具类来完成。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/AvailableCharSets.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// 展示 Charsets 和 aliases\n", - "import java.nio.charset.*;\n", - "import java.util.*;\n", - "\n", - "public class AvailableCharSets {\n", - "\n", - " public static void main(String[] args) {\n", - " SortedMap charSets =\n", - " Charset.availableCharsets();\n", - "\n", - " for(String csName : charSets.keySet()) {\n", - " System.out.print(csName);\n", - " Iterator aliases = charSets.get(csName)\n", - " .aliases().iterator();\n", - " if(aliases.hasNext())\n", - " System.out.print(\": \");\n", - " \n", - " while(aliases.hasNext()) {\n", - " System.out.print(aliases.next());\n", - " if(aliases.hasNext())\n", - " System.out.print(\", \");\n", - " }\n", - " System.out.println();\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Big5: csBig5\n", - "Big5-HKSCS: big5-hkscs, big5hk, Big5_HKSCS, big5hkscs\n", - "CESU-8: CESU8, csCESU-8\n", - "EUC-JP: csEUCPkdFmtjapanese, x-euc-jp, eucjis,\n", - "Extended_UNIX_Code_Packed_Format_for_Japanese, euc_jp,\n", - "eucjp, x-eucjp\n", - "EUC-KR: ksc5601-1987, csEUCKR, ksc5601_1987, ksc5601,\n", - "5601,\n", - "euc_kr, ksc_5601, ks_c_5601-1987, euckr\n", - "GB18030: gb18030-2000\n", - "GB2312: gb2312, euc-cn, x-EUC-CN, euccn, EUC_CN,\n", - "gb2312-80,\n", - "gb2312-1980\n", - " ..." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "回到 **BufferToText.java** 中,如果你 `rewind()` 缓冲区(回到数据的开头),使用该平台的默认字符集 `decode()` 数据,那么生成的 **CharBuffer** 数据将在控制台上正常显示。可以通过 `System.getProperty(“file.encoding”)` 方法来查看平台默认字符集名称。传递该名称参数到 `Charset.forName()` 方法可以生成对应的 `Charset` 对象用于解码字符串。\n", - "\n", - "另一种方法是使用字符集 `encode()` 方法,该字符集在读取文件时生成可打印的内容,如你在 **BufferToText.java** 的第三部分中所看到的。上例中,**UTF-16BE** 被用于将文本写入文件,当文本被读取时,你所要做的就是将其转换为 **CharBuffer**,并生成预期的文本。\n", - "\n", - "最后,如果将 **CharBuffer** 写入 **ByteBuffer**,你会看到发生了什么(更多详情,稍后了解)。**注意**,为 **ByteBuffer** 分配了24个字节,按照每个字符占用 2 个自字节, 12 个字符的空间已经足够了。由于“some text”只有 9 个字符,受其 `toString()` 方法影响,剩下的 0 字节部分也出现在了 **CharBuffer** 的展示中,如输出所示。\n", - "\n", - "\n", - "\n", - "## 基本类型获取\n", - "\n", - "\n", - "虽然 **ByteBuffer** 只包含字节,但它包含了一些方法,用于从其所包含的字节中生成各种不同的基本类型数据。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/GetData.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// 从 ByteBuffer 中获取不同的数据展示\n", - "import java.nio.*;\n", - "\n", - "public class GetData {\n", - " private static final int BSIZE = 1024;\n", - " public static void main(String[] args) {\n", - " ByteBuffer bb = ByteBuffer.allocate(BSIZE);\n", - " // 自动分配 0 到 ByteBuffer:\n", - " int i = 0;\n", - " while(i++ < bb.limit())\n", - " if(bb.get() != 0)\n", - " System.out.println(\"nonzero\");\n", - " System.out.println(\"i = \" + i);\n", - " bb.rewind();\n", - " // 保存和读取 char 数组:\n", - " bb.asCharBuffer().put(\"Howdy!\");\n", - " char c;\n", - " while((c = bb.getChar()) != 0)\n", - " System.out.print(c + \" \");\n", - " System.out.println();\n", - " bb.rewind();\n", - " // 保存和读取 short:\n", - " bb.asShortBuffer().put((short)471142);\n", - " System.out.println(bb.getShort());\n", - " bb.rewind();\n", - " // 保存和读取 int:\n", - " bb.asIntBuffer().put(99471142);\n", - " System.out.println(bb.getInt());\n", - " bb.rewind();\n", - " // 保存和读取 long:\n", - " bb.asLongBuffer().put(99471142);\n", - " System.out.println(bb.getLong());\n", - " bb.rewind();\n", - " // 保存和读取 float:\n", - " bb.asFloatBuffer().put(99471142);\n", - " System.out.println(bb.getFloat());\n", - " bb.rewind();\n", - " // 保存和读取 double:\n", - " bb.asDoubleBuffer().put(99471142);\n", - " System.out.println(bb.getDouble());\n", - " bb.rewind();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "i = 1025\n", - "H o w d y !\n", - "12390\n", - "99471142\n", - "99471142\n", - "9.9471144E7\n", - "9.9471142E7" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在分配 **ByteBuffer** 之后,我们检查并确认它的 1,024 元素被初始化为 0。(截至到达 `limit()` 结果的位置)。\n", - "\n", - "将基本类型数据插入 **ByteBuffer** 的最简单方法就是使用 `asCharBuffer()`、`asShortBuffer()` 等方法获取该缓冲区适当的“视图”(View),然后调用该“视图”的 `put()` 方法。\n", - "\n", - "这是针对每种基本数据类型执行的。其中唯一有点奇怪的是 **ShortBuffer** 的 `put()`,它需要类型强制转换。其他视图缓冲区不需要在其 `put()` 方法中进行转换。\n", - "\n", - "\n", - "\n", - "## 视图缓冲区\n", - "\n", - "\n", - "“视图缓冲区”(view buffer)是通过特定的基本类型的窗口来查看底层 **ByteBuffer**。**ByteBuffer** 仍然是“支持”视图的实际存储,因此对视图所做的任何更改都反映在对 **ByteBuffer** 中的数据的修改中。\n", - "\n", - "如前面的示例所示,这方便地将基本类型插入 **ByteBuffer**。视图缓冲区还可以从 **ByteBuffer** 读取基本类型数据,每次单个(**ByteBuffer** 规定),或者批量读取到数组。下面是一个通过 **IntBuffer** 在 **ByteBuffer** 中操作 **int** 的例子:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/IntBufferDemo.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// 利用 IntBuffer 保存 int 数据到 ByteBuffer\n", - "import java.nio.*;\n", - "\n", - "public class IntBufferDemo {\n", - " private static final int BSIZE = 1024;\n", - " public static void main(String[] args) {\n", - " ByteBuffer bb = ByteBuffer.allocate(BSIZE);\n", - " IntBuffer ib = bb.asIntBuffer();\n", - " // 保存 int 数组:\n", - " ib.put(new int[]{ 11, 42, 47, 99, 143, 811, 1016 });\n", - " //绝对位置读写:\n", - " System.out.println(ib.get(3));\n", - " ib.put(3, 1811);\n", - " // 在重置缓冲区前设置新的限制\n", - " \n", - " ib.flip();\n", - " while(ib.hasRemaining()) {\n", - " int i = ib.get();\n", - " System.out.println(i);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "99\n", - "11\n", - "42\n", - "47\n", - "1811\n", - "143\n", - "811\n", - "1016" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`put()` 方法重载,首先用于存储 **int** 数组。下面的 `get()` 和 `put()` 方法调用直接访问底层 **ByteBuffer** 中的 **int** 位置。**注意**,通过直接操作 **ByteBuffer** ,这些绝对位置访问也可以用于基本类型。\n", - "\n", - "一旦底层 **ByteBuffer** 通过视图缓冲区填充了 **int** 或其他基本类型,那么就可以直接将该 **ByteBuffer** 写入通道。你可以轻松地从通道读取数据,并使用视图缓冲区将所有内容转换为特定的基本类型。下面是一个例子,通过在同一个 **ByteBuffer** 上生成不同的视图缓冲区,将相同的字节序列解释为 **short**、**int**、**float**、**long** 和 **double**。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/ViewBuffers.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "import java.nio.*;\n", - "\n", - "public class ViewBuffers {\n", - " public static void main(String[] args) {\n", - " ByteBuffer bb = ByteBuffer.wrap(\n", - " new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });\n", - " bb.rewind();\n", - " System.out.print(\"Byte Buffer \");\n", - " while(bb.hasRemaining())\n", - " System.out.print(\n", - " bb.position()+ \" -> \" + bb.get() + \", \");\n", - " System.out.println();\n", - " CharBuffer cb =\n", - " ((ByteBuffer)bb.rewind()).asCharBuffer();\n", - " System.out.print(\"Char Buffer \");\n", - " while(cb.hasRemaining())\n", - " System.out.print(\n", - " cb.position() + \" -> \" + cb.get() + \", \");\n", - " System.out.println();\n", - " FloatBuffer fb =\n", - " ((ByteBuffer)bb.rewind()).asFloatBuffer();\n", - " System.out.print(\"Float Buffer \");\n", - " while(fb.hasRemaining())\n", - " System.out.print(\n", - " fb.position()+ \" -> \" + fb.get() + \", \");\n", - " System.out.println();\n", - " IntBuffer ib =\n", - " ((ByteBuffer)bb.rewind()).asIntBuffer();\n", - " System.out.print(\"Int Buffer \");\n", - " while(ib.hasRemaining())\n", - " System.out.print(\n", - " ib.position()+ \" -> \" + ib.get() + \", \");\n", - " System.out.println();\n", - " LongBuffer lb =\n", - " ((ByteBuffer)bb.rewind()).asLongBuffer();\n", - " System.out.print(\"Long Buffer \");\n", - " while(lb.hasRemaining())\n", - " System.out.print(\n", - " lb.position()+ \" -> \" + lb.get() + \", \");\n", - " System.out.println();\n", - " ShortBuffer sb =\n", - " ((ByteBuffer)bb.rewind()).asShortBuffer();\n", - " System.out.print(\"Short Buffer \");\n", - " while(sb.hasRemaining())\n", - " System.out.print(\n", - " sb.position()+ \" -> \" + sb.get() + \", \");\n", - " System.out.println();\n", - " DoubleBuffer db =\n", - " ((ByteBuffer)bb.rewind()).asDoubleBuffer();\n", - " System.out.print(\"Double Buffer \");\n", - " while(db.hasRemaining())\n", - " System.out.print(\n", - " db.position()+ \" -> \" + db.get() + \", \");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5\n", - "-> 0, 6 -> 0, 7 -> 97,\n", - "Char Buffer 0 -> NUL, 1 -> NUL, 2 -> NUL, 3 -> a,\n", - "Float Buffer 0 -> 0.0, 1 -> 1.36E-43,\n", - "Int Buffer 0 -> 0, 1 -> 97,\n", - "Long Buffer 0 -> 97,\n", - "Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97,\n", - "Double Buffer 0 -> 4.8E-322," - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**ByteBuffer** 通过“包装”一个 8 字节数组生成,然后通过所有不同基本类型的视图缓冲区显示该数组。下图显示了从不同类型的缓冲区读取数据时,数据显示的差异:\n", - "\n", - "![1554546258113](../images/1554546258113.png)\n", - "\n", - "\n", - "### 字节存储次序\n", - "\n", - "不同的机器可以使用不同的字节存储顺序(Endians)来存储数据。“高位优先”(Big Endian):将最重要的字节放在最低内存地址中,而“低位优先”(Little Endian):将最重要的字节放在最高内存地址中。\n", - "\n", - "当存储大于单字节的数据时,如 **int**、**float** 等,我们可能需要考虑字节排序问题。**ByteBuffer** 以“高位优先”形式存储数据;通过网络发送的数据总是使用“高位优先”形式。我们可以 使用 **ByteOrder** 的 `order()` 方法和参数 **ByteOrder.BIG_ENDIAN** 或 **ByteOrder.LITTLE_ENDIAN** 来改变它的字节存储次序。\n", - "\n", - "下例是一个包含两个字节的 **ByteBuffer** :\n", - "\n", - "![1554546378822](../images/1554546378822.png)\n", - "\n", - "\n", - "将数据作为 **short** 型来读取(`ByteBuffer.asshortbuffer()`)),生成数字 97 (00000000 01100001)。更改为“低位优先”后 将生成数字 24832 (01100001 00000000)。\n", - "\n", - "这显示了字节顺序的变化取决于字节存储次序设置:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/Endians.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// 不同字节存储次序的存储\n", - "import java.nio.*;\n", - "import java.util.*;\n", - "\n", - "public class Endians {\n", - " public static void main(String[] args) {\n", - " ByteBuffer bb = ByteBuffer.wrap(new byte[12]);\n", - " bb.asCharBuffer().put(\"abcdef\");\n", - " System.out.println(Arrays.toString(bb.array()));\n", - " bb.rewind();\n", - " bb.order(ByteOrder.BIG_ENDIAN);\n", - " bb.asCharBuffer().put(\"abcdef\");\n", - " System.out.println(Arrays.toString(bb.array()));\n", - " bb.rewind();\n", - " bb.order(ByteOrder.LITTLE_ENDIAN);\n", - " bb.asCharBuffer().put(\"abcdef\");\n", - " System.out.println(Arrays.toString(bb.array()));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]\n", - "[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]\n", - "[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**ByteBuffer** 分配空间将 **charArray** 中的所有字节作为外部缓冲区保存,因此可以调用 `array()` 方法来显示底层字节。`array()` 方法是“可选的”,你只能在数组支持的缓冲区上调用它,否则将抛出 **UnsupportedOperationException** 异常。\n", - "\n", - "**charArray** 通过 **CharBuffer** 视图插入到 **ByteBuffer** 中。当显示底层字节时,默认排序与后续“高位”相同,而“地位”交换字节\n", - "\n", - "\n", - "## 缓冲区数据操作\n", - "\n", - "\n", - "下图说明了 **nio** 类之间的关系,展示了如何移动和转换数据。例如,要将字节数组写入文件,使用 **ByteBuffer.**`wrap()` 方法包装字节数组,使用 `getChannel()` 在 **FileOutputStream** 上打开通道,然后从 **ByteBuffer** 将数据写入 **FileChannel**。\n", - "\n", - "![1554546452861](../images/1554546452861.png)\n", - "\n", - "**ByteBuffer** 是将数据移入和移出通道的唯一方法,我们只能创建一个独立的基本类型缓冲区,或者使用 `as` 方法从 **ByteBuffer** 获得一个新缓冲区。也就是说,不能将基本类型缓冲区转换为 **ByteBuffer**。但我们能够通过视图缓冲区将基本类型数据移动到 **ByteBuffer** 中或移出 **ByteBuffer**。\n", - "\n", - " \n", - "\n", - "### 缓冲区细节\n", - "\n", - "缓冲区由数据和四个索引组成,以有效地访问和操作该数据:mark、position、limit 和 capacity(标记、位置、限制和容量)。伴随着的还有一组方法可以设置和重置这些索引,并可查询它们的值。\n", - "\n", - "| | |\n", - "| :----- | :----- |\n", - "| **capacity()** | 返回缓冲区的 capacity |\n", - "|**clear()** |清除缓冲区,将 position 设置为零并 设 limit 为 capacity;可调用此方法来覆盖现有缓冲区|\n", - "|**flip()** | 将 limit 设置为 position,并将 position 设置为 0;此方法用于准备缓冲区,以便在数据写入缓冲区后进行读取|\n", - "|**limit()** |返回 limit 的值|\n", - "|**limit(int limit)**| 重设 limit|\n", - "|**mark()** |设置 mark 为当前的 position |\n", - "|**position()** |返回 position |\n", - "|**position(int pos)**| 设置 position|\n", - "|**remaining()** |返回 limit 到 position |\n", - "|**hasRemaining()**| 如果在 position 与 limit 中间有元素,返回 `true`|\n", - "\n", - "从缓冲区插入和提取数据的方法通过更新索引来反映所做的更改。下例使用一种非常简单的算法(交换相邻字符)来对 **CharBuffer** 中的字符进行加扰和解扰。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/UsingBuffers.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "import java.nio.*;\n", - "\n", - "public class UsingBuffers {\n", - " private static\n", - " void symmetricScramble(CharBuffer buffer) {\n", - " while(buffer.hasRemaining()) {\n", - " buffer.mark();\n", - " char c1 = buffer.get();\n", - " char c2 = buffer.get();\n", - " buffer.reset();\n", - " buffer.put(c2).put(c1);\n", - " }\n", - " }\n", - "\n", - " public static void main(String[] args) {\n", - " char[] data = \"UsingBuffers\".toCharArray();\n", - " ByteBuffer bb =\n", - " ByteBuffer.allocate(data.length * 2);\n", - " CharBuffer cb = bb.asCharBuffer();\n", - " cb.put(data);\n", - " System.out.println(cb.rewind());\n", - " symmetricScramble(cb);\n", - " System.out.println(cb.rewind());\n", - " symmetricScramble(cb);\n", - " System.out.println(cb.rewind());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "UsingBuffers\n", - "sUniBgfuefsr\n", - "UsingBuffers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "虽然可以通过使用 **char** 数组调用 `wrap()` 直接生成 **CharBuffer**,但是底层的 **ByteBuffer** 将被分配,而 **CharBuffer** 将作为 **ByteBuffer** 上的视图生成。这强调了目标始终是操作 **ByteBuffer**,因为它与通道交互。\n", - "\n", - "下面是程序在 `symmetricgrab()` 方法入口时缓冲区的样子:\n", - "\n", - "![1554546627710](../images/1554546627710.png)\n", - "\n", - "position 指向缓冲区中的第一个元素,capacity 和 limit 紧接在最后一个元素之后。在`symmetricgrab()` 中,**while** 循环迭代到 position 等于 limit。当在缓冲区上调用相对位置的 `get()` 或 `put()` 函数时,缓冲区的位置会发生变化。你可以调用绝对位置的 `get()` 和 `put()` 方法,它们包含索引参数:`get()` 或 `put()` 发生的位置。这些方法不修改缓冲区 position 的值。\n", - "\n", - "当控件进入 **while** 循环时,使用 `mark()` 设置 mark 的值。缓冲区的状态为:\n", - "\n", - "![1554546666685](../images/1554546666685.png)\n", - "\n", - "两个相对 `get()` 调用将前两个字符的值保存在变量 `c1` 和 `c2` 中。在这两个调用之后,缓冲区看起来是这样的:\n", - "\n", - "![1554546693664](../images/1554546693664.png)\n", - "\n", - "为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 position 的值设置为 mark:\n", - "\n", - "![1554546847181](../images/1554546847181.png)\n", - "\n", - "两个 `put()` 方法分别编写 `c2` 和 `c1` :\n", - "\n", - "![1554546861836](../images/1554546861836.png)\n", - "\n", - "在下一次循环中,将 mark 设置为 position 的当前值:\n", - "\n", - "![1554546881189](../images/1554546881189.png)\n", - "\n", - " 该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾,position 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()` 将 position 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态(mark 的值变成未定义):\n", - "\n", - "![1554546890132](../images/1554546890132.png)\n", - "\n", - "再次调用 `symmetricgrab()` 方法时,**CharBuffer** 将经历相同的过程并恢复到原始状态。\n", - "\n", - "\n", - "\n", - "## 内存映射文件\n", - "\n", - "内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/LargeMappedFiles.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// 使用内存映射来创建一个大文件\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class LargeMappedFiles {\n", - " static int length = 0x8000000; // 128 MB\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " try(\n", - " RandomAccessFile tdat =\n", - " new RandomAccessFile(\"test.dat\", \"rw\")\n", - " ) {\n", - " MappedByteBuffer out = tdat.getChannel().map(\n", - " FileChannel.MapMode.READ_WRITE, 0, length);\n", - " for(int i = 0; i < length; i++)\n", - " out.put((byte)'x');\n", - " System.out.println(\"Finished writing\");\n", - " for(int i = length/2; i < length/2 + 6; i++)\n", - " System.out.print((char)out.get(i));\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Finished writing\n", - "xxxxxx" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了读写,我们从 **RandomAccessFile** 开始,获取该文件的通道,然后调用 `map()` 来生成 **MappedByteBuffer** ,这是一种特殊的直接缓冲区。你必须指定要在文件中映射的区域的起始点和长度—这意味着你可以选择映射大文件的较小区域。\n", - "\n", - "**MappedByteBuffer** 继承了 **ByteBuffer**,所以拥有**ByteBuffer** 全部的方法。这里只展示了 `put()` 和 `get()` 的最简单用法,但是你也可以使用 `asCharBuffer()` 等方法。\n", - "\n", - "使用前面的程序创建的文件长度为 128MB,可能比你的操作系统单次所允许的操作的内存要大。该文件似乎可以同时访问,因为它只有一部分被带进内存,而其他部分被交换出去。这样,一个非常大的文件(最多 2GB)可以很容易地修改。**注意**,操作系统底层的文件映射工具用于性能的最大化。\n", - "\n", - "\n", - "### 性能\n", - "\n", - "虽然旧的 I/O 流的性能通过使用 **NIO** 实现得到了改进,但是映射文件访问往往要快得多。下例带来一个简单的性能比较。代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/MappedIO.java\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "import java.util.*;\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class MappedIO {\n", - " private static int numOfInts = 4_000_000;\n", - " private static int numOfUbuffInts = 100_000;\n", - " private abstract static class Tester {\n", - " private String name;\n", - " Tester(String name) {\n", - " this.name = name;\n", - " }\n", - "\n", - " public void runTest() {\n", - " System.out.print(name + \": \");\n", - " long start = System.nanoTime();\n", - " test();\n", - " double duration = System.nanoTime() - start;\n", - " System.out.format(\"%.3f%n\", duration/1.0e9);\n", - " }\n", - "\n", - " public abstract void test();\n", - " }\n", - "\n", - " private static Tester[] tests = {\n", - " new Tester(\"Stream Write\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " DataOutputStream dos =\n", - " new DataOutputStream(\n", - " new BufferedOutputStream(\n", - " new FileOutputStream(\n", - " new File(\"temp.tmp\"))))\n", - " ) {\n", - " for(int i = 0; i < numOfInts; i++)\n", - " dos.writeInt(i);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " },\n", - " new Tester(\"Mapped Write\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " FileChannel fc =\n", - " new RandomAccessFile(\"temp.tmp\", \"rw\")\n", - " .getChannel()\n", - " ) {\n", - " IntBuffer ib =\n", - " fc.map(FileChannel.MapMode.READ_WRITE,\n", - " 0, fc.size()).asIntBuffer();\n", - " for(int i = 0; i < numOfInts; i++)\n", - " ib.put(i);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " },\n", - " new Tester(\"Stream Read\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " DataInputStream dis =\n", - " new DataInputStream(\n", - " new BufferedInputStream(\n", - " new FileInputStream(\"temp.tmp\")))\n", - " ) {\n", - " for(int i = 0; i < numOfInts; i++)\n", - " dis.readInt();\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " },\n", - " new Tester(\"Mapped Read\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " FileChannel fc = new FileInputStream(\n", - " new File(\"temp.tmp\")).getChannel()\n", - " ) {\n", - " IntBuffer ib =\n", - " fc.map(FileChannel.MapMode.READ_ONLY,\n", - " 0, fc.size()).asIntBuffer();\n", - " while(ib.hasRemaining())\n", - " ib.get();\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " },\n", - " new Tester(\"Stream Read/Write\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " RandomAccessFile raf =\n", - " new RandomAccessFile(\n", - " new File(\"temp.tmp\"), \"rw\")\n", - " ) {\n", - " raf.writeInt(1);\n", - " for(int i = 0; i < numOfUbuffInts; i++) {\n", - " raf.seek(raf.length() - 4);\n", - " raf.writeInt(raf.readInt());\n", - " }\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " },\n", - " new Tester(\"Mapped Read/Write\") {\n", - " @Override\n", - " public void test() {\n", - " try(\n", - " FileChannel fc = new RandomAccessFile(\n", - " new File(\"temp.tmp\"), \"rw\").getChannel()\n", - " ) {\n", - " IntBuffer ib =\n", - " fc.map(FileChannel.MapMode.READ_WRITE,\n", - " 0, fc.size()).asIntBuffer();\n", - " ib.put(0);\n", - " for(int i = 1; i < numOfUbuffInts; i++)\n", - " ib.put(ib.get(i - 1));\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " }\n", - " };\n", - " public static void main(String[] args) {\n", - " Arrays.stream(tests).forEach(Tester::runTest);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Stream Write: 0.615\n", - "Mapped Write: 0.050\n", - "Stream Read: 0.577\n", - "Mapped Read: 0.015\n", - "Stream Read/Write: 4.069\n", - "Mapped Read/Write: 0.013" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Tester** 使用了模板方法(Template Method)模式,它为匿名内部子类中定义的 `test()` 的各种实现创建一个测试框架。每个子类都执行一种测试,因此 `test()` 方法还提供了执行各种I/O 活动的原型。\n", - "\n", - "虽然映射的写似乎使用 **FileOutputStream**,但是文件映射中的所有输出必须使用 **RandomAccessFile**,就像前面代码中的读/写一样。\n", - "\n", - "请注意,`test()` 方法包括初始化各种 I/O 对象的时间,因此,尽管映射文件的设置可能很昂贵,但是与流 I/O 相比,总体收益非常可观。\n", - "\n", - "\n", - "## 文件锁定\n", - "\n", - "文件锁定可同步访问,因此文件可以共享资源。但是,争用同一文件的两个线程可能位于不同的 JVM 中,或者一个可能是 Java 线程,另一个可能是操作系统中的本机线程。文件锁对其他操作系统进程可见,因为 Java 文件锁定直接映射到本机操作系统锁定工具。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/FileLocking.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "import java.nio.channels.*;\n", - "import java.util.concurrent.*;\n", - "import java.io.*;\n", - "\n", - "public class FileLocking {\n", - " public static void main(String[] args) {\n", - " try(\n", - " FileOutputStream fos =\n", - " new FileOutputStream(\"file.txt\");\n", - " FileLock fl = fos.getChannel().tryLock()\n", - " ) {\n", - " if(fl != null) {\n", - " System.out.println(\"Locked File\");\n", - " TimeUnit.MILLISECONDS.sleep(100);\n", - " fl.release();\n", - " System.out.println(\"Released Lock\");\n", - " }\n", - " } catch(IOException | InterruptedException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Locked File\n", - "Released Lock" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通过调用 **FileChannel** 上的 `tryLock()` 或 `lock()`,可以获得整个文件的 **FileLock**。(**SocketChannel**、**DatagramChannel** 和 **ServerSocketChannel** 不需要锁定,因为它们本质上是单进程实体;通常不会在两个进程之间共享一个网络套接字)。\n", - "\n", - "`tryLock()` 是非阻塞的。它试图获取锁,若不能获取(当其他进程已经持有相同的锁,并且它不是共享的),它只是从方法调用返回。\n", - "\n", - "`lock()` 会阻塞,直到获得锁,或者调用 `lock()` 的线程中断,或者调用 `lock()` 方法的通道关闭。使用 **FileLock.**`release()` 释放锁。\n", - "\n", - "还可以使用\n", - "\n", - " > `tryLock(long position, long size, boolean shared)`\n", - "\n", - " 或 \n", - "\n", - " > `lock(long position, long size, boolean shared)` \n", - " \n", - " 锁定文件的一部分,锁住 **size-position** 区域。第三个参数指定是否共享此锁。\n", - "\n", - "虽然零参数锁定方法适应文件大小的变化,但是如果文件大小发生变化,具有固定大小的锁不会发生变化。如果从一个位置到另一个位置获得一个锁,并且文件的增长超过了 position + size ,那么超出 position + size 的部分没有被锁定。零参数锁定方法锁定整个文件,即使它在增长。\n", - "\n", - "底层操作系统必须提供对独占锁或共享锁的支持。如果操作系统不支持共享锁并且对一个操作系统发出请求,则使用独占锁。可以使用 **FileLock.**`isShared()` 查询锁的类型(共享或独占)。\n", - "\n", - "\n", - "### 映射文件的部分锁定\n", - "\n", - "文件映射通常用于非常大的文件。你可能需要锁定此类文件的某些部分,以便其他进程可以修改未锁定的部分。例如,数据库必须同时对许多用户可用。这里你可以看到两个线程,每个线程都锁定文件的不同部分:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// newio/LockingMappedFiles.java\n", - "// (c)2017 MindView LLC: see Copyright.txt\n", - "// 我们无法保证该代码是否适用于其他用途。\n", - "// 访问 http://OnJava8.com 了解更多本书信息。\n", - "// Locking portions of a mapped file\n", - "import java.nio.*;\n", - "import java.nio.channels.*;\n", - "import java.io.*;\n", - "\n", - "public class LockingMappedFiles {\n", - " static final int LENGTH = 0x8FFFFFF; // 128 MB\n", - " static FileChannel fc;\n", - " public static void\n", - " main(String[] args) throws Exception {\n", - " fc = new RandomAccessFile(\"test.dat\", \"rw\")\n", - " .getChannel();\n", - " MappedByteBuffer out = fc.map(\n", - " FileChannel.MapMode.READ_WRITE, 0, LENGTH);\n", - " for(int i = 0; i < LENGTH; i++)\n", - " out.put((byte)'x');\n", - " new LockAndModify(out, 0, 0 + LENGTH/3);\n", - " new LockAndModify(\n", - " out, LENGTH/2, LENGTH/2 + LENGTH/4);\n", - " }\n", - "\n", - " private static class LockAndModify extends Thread {\n", - " private ByteBuffer buff;\n", - " private int start, end;\n", - " LockAndModify(ByteBuffer mbb, int start, int end) {\n", - " this.start = start;\n", - " this.end = end;\n", - " mbb.limit(end);\n", - " mbb.position(start);\n", - " buff = mbb.slice();\n", - " start();\n", - " }\n", - "\n", - " @Override\n", - " public void run() {\n", - " try {\n", - " // Exclusive lock with no overlap:\n", - " FileLock fl = fc.lock(start, end, false);\n", - " System.out.println(\n", - " \"Locked: \"+ start +\" to \"+ end);\n", - " // Perform modification:\n", - " while(buff.position() < buff.limit() - 1)\n", - " buff.put((byte)(buff.get() + 1));\n", - " fl.release();\n", - " System.out.println(\n", - " \"Released: \" + start + \" to \" + end);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Locked: 75497471 to 113246206\n", - "Locked: 0 to 50331647\n", - "Released: 75497471 to 113246206\n", - "Released: 0 to 50331647" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**LockAndModify** 线程类设置缓冲区并创建要修改的 `slice()`,在 `run()` 中,锁在文件通道上获取(不能在缓冲区上获取锁—只能在通道上获取锁)。`lock()` 的调用非常类似于获取对象上的线程锁 —— 现在有了一个“临界区”,可以对文件的这部分进行独占访问。[^1]\n", - "\n", - "当 JVM 退出或关闭获取锁的通道时,锁会自动释放,但是你也可以显式地调用 **FileLock** 对象上的 `release()`,如上所示。\n", - "\n", - "\n", - "\n", - "[^1]:更多详情可参考[附录:并发底层原理](./book/Appendix-Low-Level-Concurrency.md)。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Object-Serialization.ipynb b/jupyter/Appendix-Object-Serialization.ipynb deleted file mode 100644 index a09d12ca..00000000 --- a/jupyter/Appendix-Object-Serialization.ipynb +++ /dev/null @@ -1,1374 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:对象序列化\n", - "\n", - "当你创建对象时,只要你需要,它就会一直存在,但是在程序终止时,无论如何它都不会继续存在。尽管这么做肯定是有意义的,但是仍旧存在某些情况,如果对象能够在程序不运行的情况下仍能存在并保存其信息,那将非常有用。这样,在下次运行程序时,该对象将被重建并且拥有的信息与在程序上次运行时它所拥有的信息相同。当然,你可以通过将信息写入文件或数据库来达到相同的效果,但是在使万物都成为对象的精神中,如果能够将一个对象声明为是“持久性”的,并为我们处理掉所有细节,那将会显得十分方便。\n", - "\n", - "Java 的对象序列化将那些实现了 Serializable 接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,这意味着序列化机制能自动弥补不同操作系统之间的差异。也就是说,可以在运行 Windows 系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行 Unix 系统的计算机,然后在那里准确地重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心宇节的顺序或者其他任何细节。\n", - "\n", - "就其本身来说,对象序列化可以实现轻量级持久性(lightweight persistence),“持久性”意味着一个对象的生存周期并不取决于程序是否正在执行它可以生存于程序的调用之间。通过将一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种\"persistent\"(持久)关键字来简单地定义一个对象,并让系统自动维护其他细节问题(尽管将来有可能实现)。相反,对象必须在程序中显式地序列化(serialize)和反序列化还原(deserialize),如果需要个更严格的持久性机制,可以考虑像 Hibemate 之类的工具。\n", - "\n", - "对象序列化的概念加入到语言中是为了支持两种主要特性。一是 Java 的远程方法调用(Remote Method Invocation,RMI),它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。\n", - "\n", - "再者,对 Java Beans 来说,对象的序列化也是必需的(在撰写本文时被视为失败的技术),使用一个 Bean 时,一般情况下是在设计阶段对它的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复,这种具体工作就是由对象序列化完成的。\n", - "\n", - "只要对象实现了 Serializable 接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准库类都发生了改变,以便具备序列化特性-其中包括所有基本数据类型的封装器、所有容器类以及许多其他的东西。甚至 Class 对象也可以被序列化。\n", - "\n", - "要序列化一个对象,首先要创建某些 OutputStream 对象,然后将其封装在一个 ObjectOutputStream 对象内。这时,只需调用 writeObject() 即可将对象序列化,并将其发送给 OutputStream(对象化序列是基于字节的,因要使用 InputStream 和 OutputStream 继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个 InputStream 封装在 ObjectInputStream 内,然后调用 readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的 Object,所以必须向下转型才能直接设置它们。\n", - "\n", - "对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪,依此类推。这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组以及成员对象。如果必须保持一套自己的对象序列化机制,那么维护那些可追踪到所有链接的代码可能会显得非常麻烦。然而,由于 Java 的对象序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子通过对链接的对象生成一个 worm(蠕虫)对序列化机制进行了测试。每个对象都与 worm 中的下一段链接,同时又与属于不同类(Data)的对象引用数组链接:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/Worm.java\n", - "// Demonstrates object serialization\n", - "import java.io.*;\n", - "import java.util.*;\n", - "class Data implements Serializable {\n", - " private int n;\n", - " Data(int n) { this.n = n; }\n", - " @Override\n", - " public String toString() {\n", - " return Integer.toString(n);\n", - " }\n", - "}\n", - "public class Worm implements Serializable {\n", - " private static Random rand = new Random(47);\n", - " private Data[] d = {\n", - " new Data(rand.nextInt(10)),\n", - " new Data(rand.nextInt(10)),\n", - " new Data(rand.nextInt(10))\n", - " };\n", - " private Worm next;\n", - " private char c;\n", - " // Value of i == number of segments\n", - " public Worm(int i, char x) {\n", - " System.out.println(\"Worm constructor: \" + i);\n", - " c = x;\n", - " if(--i > 0)\n", - " next = new Worm(i, (char)(x + 1));\n", - " }\n", - " public Worm() {\n", - " System.out.println(\"No-arg constructor\");\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " StringBuilder result = new StringBuilder(\":\");\n", - " result.append(c);\n", - " result.append(\"(\");\n", - " for(Data dat : d)\n", - " result.append(dat);\n", - " result.append(\")\");\n", - " if(next != null)\n", - " result.append(next);\n", - " return result.toString();\n", - " }\n", - " public static void\n", - " main(String[] args) throws ClassNotFoundException,\n", - " IOException {\n", - " Worm w = new Worm(6, 'a');\n", - " System.out.println(\"w = \" + w);\n", - " try(\n", - " ObjectOutputStream out = new ObjectOutputStream(\n", - " new FileOutputStream(\"worm.dat\"))\n", - " ) {\n", - " out.writeObject(\"Worm storage\\n\");\n", - " out.writeObject(w);\n", - " }\n", - " try(\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(\"worm.dat\"))\n", - " ) {\n", - " String s = (String)in.readObject();\n", - " Worm w2 = (Worm)in.readObject();\n", - " System.out.println(s + \"w2 = \" + w2);\n", - " }\n", - " try(\n", - " ByteArrayOutputStream bout =\n", - " new ByteArrayOutputStream();\n", - " ObjectOutputStream out2 =\n", - " new ObjectOutputStream(bout)\n", - " ) {\n", - " out2.writeObject(\"Worm storage\\n\");\n", - " out2.writeObject(w);\n", - " out2.flush();\n", - " try(\n", - " ObjectInputStream in2 = new ObjectInputStream(\n", - " new ByteArrayInputStream(\n", - " bout.toByteArray()))\n", - " ) {\n", - " String s = (String)in2.readObject();\n", - " Worm w3 = (Worm)in2.readObject();\n", - " System.out.println(s + \"w3 = \" + w3);\n", - " }\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Worm constructor: 6\n", - "Worm constructor: 5\n", - "Worm constructor: 4\n", - "Worm constructor: 3\n", - "Worm constructor: 2\n", - "Worm constructor: 1\n", - "w = :a(853):b(119):c(802):d(788):e(199):f(881)\n", - "Worm storage\n", - "w2 = :a(853):b(119):c(802):d(788):e(199):f(881)\n", - "Worm storage\n", - "w3 = :a(853):b(119):c(802):d(788):e(199):f(881)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "更有趣的是,Worm 内的 Data 对象数组是用随机数初始化的(这样就不用怀疑编译器保留了某种原始信息),每个 Worm 段都用一个 char 加以标记。该 char 是在递归生成链接的 Worm 列表时自动产生的。要创建一个 Worm,必须告诉构造器你所希望的它的长度。在产生下一个引用时,要调用 Worm 构造器,并将长度减 1,以此类推。最后一个 next 引用则为 null(空),表示已到达 Worm 的尾部\n", - "\n", - "以上这些操作都使得事情变得更加复杂,从而加大了对象序列化的难度。然而,真正的序列化过程却是非常简单的。一旦从另外某个流创建了 ObjectOutputstream,writeObject() 就会将对象序列化。注意也可以为一个 String 调用 writeObject() 也可以用与 DataOutputStream 相同的方法写人所有基本数据类型(它们具有同样的接口)。\n", - "\n", - "有两段看起来相似的独立的代码。一个读写的是文件,而另一个读写的是字节数组(ByteArray),可利用序列化将对象读写到任何 DatalnputStream 或者 DataOutputStream。\n", - "\n", - "从输出中可以看出,被还原后的对象确实包含了原对象中的所有链接。\n", - "\n", - "注意在对一个 Serializable 对象进行还原的过程中,没有调用任何构造器,包括默认的构造器。整个对象都是通过从 InputStream 中取得数据恢复而来的。\n", - "\n", - "\n", - "\n", - "## 查找类\n", - "\n", - "你或许会奇怪,将一个对象从它的序列化状态中恢复出来,有哪些工作是必须的呢?举个例子来说,假如我们将一个对象序列化,并通过网络将其作为文件传送给另一台计算机,那么,另一台计算机上的程序可以只利用该文件内容来还原这个对象吗?\n", - "\n", - "回答这个问题的最好方法就是做一个实验。下面这个文件位于本章的子目录下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/Alien.java\n", - "// A serializable class\n", - "import java.io.*;\n", - "public class Alien implements Serializable {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "而用于创建和序列化一个 Alien 对象的文件也位于相同的目录下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/FreezeAlien.java\n", - "// Create a serialized output file\n", - "import java.io.*;\n", - "public class FreezeAlien {\n", - " public static void main(String[] args) throws Exception {\n", - " try(\n", - " ObjectOutputStream out = new ObjectOutputStream(\n", - " new FileOutputStream(\"X.file\"));\n", - " ) {\n", - " Alien quellek = new Alien();\n", - " out.writeObject(quellek);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "一旦该程序被编译和运行,它就会在 c12 目录下产生一个名为 X.file 的文件。以下代码位于一个名为 xiles 的子目录下:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/xfiles/ThawAlien.java\n", - "// Recover a serialized file\n", - "// {java serialization.xfiles.ThawAlien}\n", - "// {RunFirst: FreezeAlien}\n", - "package serialization.xfiles;\n", - "import java.io.*;\n", - "public class ThawAlien {\n", - " public static void main(String[] args) throws Exception {\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(new File(\"X.file\")));\n", - " Object mystery = in.readObject();\n", - " System.out.println(mystery.getClass());\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "class Alien" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了正常运行,必须保证 Java 虚拟机能找到相关的.class 文件。\n", - "\n", - "\n", - "\n", - "## 控制序列化\n", - "\n", - "正如大家所看到的,默认的序列化机制并不难操纵。然而,如果有特殊的需要那又该怎么办呢?例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化。\n", - "\n", - "在这些特殊情况下,可通过实现 Externalizable 接口——代替实现 Serializable 接口-来对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal0 和 readExternal0。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。\n", - "\n", - "下面这个例子展示了 Externalizable 接口方法的简单实现。注意 Blip1 和 Blip2 除了细微的差别之外,几乎完全一致(研究一下代码,看看你能否发现):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/Blips.java\n", - "// Simple use of Externalizable & a pitfall\n", - "import java.io.*;\n", - "class Blip1 implements Externalizable {\n", - " public Blip1() {\n", - " System.out.println(\"Blip1 Constructor\");\n", - " }\n", - " @Override\n", - " public void writeExternal(ObjectOutput out)\n", - " throws IOException {\n", - " System.out.println(\"Blip1.writeExternal\");\n", - " }\n", - " @Override\n", - " public void readExternal(ObjectInput in)\n", - " throws IOException, ClassNotFoundException {\n", - " System.out.println(\"Blip1.readExternal\");\n", - " }\n", - "}\n", - "class Blip2 implements Externalizable {\n", - " Blip2() {\n", - " System.out.println(\"Blip2 Constructor\");\n", - " }\n", - " @Override\n", - " public void writeExternal(ObjectOutput out)\n", - " throws IOException {\n", - " System.out.println(\"Blip2.writeExternal\");\n", - " }\n", - " @Override\n", - " public void readExternal(ObjectInput in)\n", - " throws IOException, ClassNotFoundException {\n", - " System.out.println(\"Blip2.readExternal\");\n", - " }\n", - "}\n", - "public class Blips {\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Constructing objects:\");\n", - " Blip1 b1 = new Blip1();\n", - " Blip2 b2 = new Blip2();\n", - " try(\n", - " ObjectOutputStream o = new ObjectOutputStream(\n", - " new FileOutputStream(\"Blips.serialized\"))\n", - " ) {\n", - " System.out.println(\"Saving objects:\");\n", - " o.writeObject(b1);\n", - " o.writeObject(b2);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // Now get them back:\n", - " System.out.println(\"Recovering b1:\");\n", - " try(\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(\"Blips.serialized\"))\n", - " ) {\n", - " b1 = (Blip1)in.readObject();\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // OOPS! Throws an exception:\n", - " //- System.out.println(\"Recovering b2:\");\n", - " //- b2 = (Blip2)in.readObject();\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Constructing objects:\n", - "Blip1 Constructor\n", - "Blip2 Constructor\n", - "Saving objects:\n", - "Blip1.writeExternal\n", - "Blip2.writeExternal\n", - "Recovering b1:\n", - "Blip1 Constructor\n", - "Blip1.readExternal" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "没有恢复 Blip2 对象的原因是那样做会导致一个异常。你找出 Blip1 和 Blip2 之间的区别了吗?Blipl 的构造器是“公共的”(pablic),Blip2 的构造器却不是,这样就会在恢复时造成异常。试试将 Blip2 的构造器变成 public 的,然后删除//注释标记,看看是否能得到正确的结果。\n", - "\n", - "恢复 b1 后,会调用 Blip1 默认构造器。这与恢复一个 Serializable 对象不同。对于 Serializable 对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个 Externalizable 对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用 readExternal() 必须注意这一点--所有默认的构造器都会被调用,才能使 Externalizable 对象产生正确的行为。\n", - "\n", - "下面这个例子示范了如何完整保存和恢复一个 Externalizable 对象:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/Blip3.java\n", - "// Reconstructing an externalizable object\n", - "import java.io.*;\n", - "public class Blip3 implements Externalizable {\n", - " private int i;\n", - " private String s; // No initialization\n", - " public Blip3() {\n", - " System.out.println(\"Blip3 Constructor\");\n", - "// s, i not initialized\n", - " }\n", - " public Blip3(String x, int a) {\n", - " System.out.println(\"Blip3(String x, int a)\");\n", - " s = x;\n", - " i = a;\n", - "// s & i initialized only in non-no-arg constructor.\n", - " }\n", - " @Override\n", - " public String toString() { return s + i; }\n", - " @Override\n", - " public void writeExternal(ObjectOutput out)\n", - " throws IOException {\n", - " System.out.println(\"Blip3.writeExternal\");\n", - "// You must do this:\n", - " out.writeObject(s);\n", - " out.writeInt(i);\n", - " }\n", - " @Override\n", - " public void readExternal(ObjectInput in)\n", - " throws IOException, ClassNotFoundException {\n", - " System.out.println(\"Blip3.readExternal\");\n", - "// You must do this:\n", - " s = (String)in.readObject();\n", - " i = in.readInt();\n", - " }\n", - " public static void main(String[] args) {\n", - " System.out.println(\"Constructing objects:\");\n", - " Blip3 b3 = new Blip3(\"A String \", 47);\n", - " System.out.println(b3);\n", - " try(\n", - " ObjectOutputStream o = new ObjectOutputStream(\n", - " new FileOutputStream(\"Blip3.serialized\"))\n", - " ) {\n", - " System.out.println(\"Saving object:\");\n", - " o.writeObject(b3);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - "// Now get it back:\n", - " System.out.println(\"Recovering b3:\");\n", - " try(\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(\"Blip3.serialized\"))\n", - " ) {\n", - " b3 = (Blip3)in.readObject();\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.println(b3);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Constructing objects:\n", - "Blip3(String x, int a)\n", - "A String 47\n", - "Saving object:\n", - "Blip3.writeExternal\n", - "Recovering b3:\n", - "Blip3 Constructor\n", - "Blip3.readExternal\n", - "A String 47" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "其中,字段 s 和只在第二个构造器中初始化,而不是在默认的构造器中初始化。这意味着假如不在 readExternal0 中初始化 s 和 i,s 就会为 null,而就会为零(因为在创建对象的第一步中将对象的存储空间清理为 0)。如果注释掉跟随于\"You must do this”后面的两行代码,然后运行程序,就会发现当对象被还原后,s 是 null,而 i 是零。\n", - "\n", - "我们如果从一个 Externalizable 对象继承,通常需要调用基类版本的 writeExternal() 和 readExternal() 来为基类组件提供恰当的存储和恢复功能。\n", - "\n", - "因此,为了正常运行,我们不仅需要在 writeExternal() 方法(没有任何默认行为来为 Externalizable 对象写入任何成员对象)中将来自对象的重要信息写入,还必须在 readExternal() 方法中恢复数据。起先,可能会有一点迷惑,因为 Externalizable 对象的默认构造行为使其看起来似乎像某种自动发生的存储与恢复操作。但实际上并非如此。\n", - "\n", - "### transient 关键字\n", - "\n", - "当我们对序列化进行控制时,可能某个特定子对象不想让 Java 的序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况。即使对象中的这些信息是 private(私有)属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问到它。\n", - "\n", - "有一种办法可防止对象的敏感部分被序列化,就是将类实现为 Externalizable,如前面所示。这样一来,没有任何东西可以自动序列化,并且可以在 writeExternal() 内部只对所需部分进行显式的序列化。\n", - "\n", - "然而,如果我们正在操作的是一个 Seralizable 对象,那么所有序列化操作都会自动进行。为了能够予以控制,可以用 transient(瞬时)关键字逐个字段地关闭序列化,它的意思是“不用麻烦你保存或恢复数据——我自己会处理的\"。\n", - "\n", - "例如,假设某个 Logon 对象保存某个特定的登录会话信息,登录的合法性通过校验之后,我们想把数据保存下来,但不包括密码。为做到这一点,最简单的办法是实现 Serializable,并将 password 字段标志为 transient,下面是具体的代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/Logon.java\n", - "// Demonstrates the \"transient\" keyword\n", - "import java.util.concurrent.*;\n", - "import java.io.*;\n", - "import java.util.*;\n", - "import onjava.Nap;\n", - "public class Logon implements Serializable {\n", - " private Date date = new Date();\n", - " private String username;\n", - " private transient String password;\n", - " public Logon(String name, String pwd) {\n", - " username = name;\n", - " password = pwd;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"logon info: \\n username: \" +\n", - " username + \"\\n date: \" + date +\n", - " \"\\n password: \" + password;\n", - " }\n", - " public static void main(String[] args) {\n", - " Logon a = new Logon(\"Hulk\", \"myLittlePony\");\n", - " System.out.println(\"logon a = \" + a);\n", - " try(\n", - " ObjectOutputStream o =\n", - " new ObjectOutputStream(\n", - " new FileOutputStream(\"Logon.dat\"))\n", - " ) {\n", - " o.writeObject(a);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " new Nap(1);\n", - "// Now get them back:\n", - " try(\n", - " ObjectInputStream in = new ObjectInputStream(\n", - " new FileInputStream(\"Logon.dat\"))\n", - " ) {\n", - " System.out.println(\n", - " \"Recovering object at \" + new Date());\n", - " a = (Logon)in.readObject();\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " System.out.println(\"logon a = \" + a);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "logon a = logon info:\n", - "username: Hulk\n", - "date: Tue May 09 06:07:47 MDT 2017\n", - "password: myLittlePony\n", - "Recovering object at Tue May 09 06:07:49 MDT 2017\n", - "logon a = logon info:\n", - "username: Hulk\n", - "date: Tue May 09 06:07:47 MDT 2017\n", - "password: null" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,其中的 date 和 username 是一般的(不是 transient 的),所以它们会被自动序列化。而 password 是 transient 的,所以不会被自动保存到磁盘;另外,自动序列化机制也不会尝试去恢复它。当对象被恢复时,password 就会变成 null。注意,虽然 toString() 是用重载后的+运算符来连接 String 对象,但是 null 引用会被自动转换成字符串 null。\n", - "\n", - "我们还可以发现:date 字段被存储了到磁盘并从磁盘上被恢复了出来,而且没有再重新生成。由于 Externalizable 对象在默认情况下不保存它们的任何字段,所以 transient 关键字只能和 Serializable 对象一起使用。\n", - "\n", - "### Externalizable 的替代方法\n", - "\n", - "如果不是特别坚持实现 Externalizable 接口,那么还有另一种方法。我们可以实现 Serializable 接口,并添加(注意我说的是“添加”,而非“覆盖”或者“实现”)名为 writeObject() 和 readObject() 的方法。这样一旦对象被序列化或者被反序列化还原,就会自动地分别调用这两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制。\n", - "\n", - "这些方法必须具有准确的方法特征签名:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "private void writeObject(ObjectOutputStream stream) throws IOException\n", - "\n", - "private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "从设计的观点来看,现在事情变得真是不可思议。首先,我们可能会认为由于这些方法不是基类或者 Serializable 接口的一部分,所以应该在它们自己的接口中进行定义。但是注意它们被定义成了 private,这意味着它们仅能被这个类的其他成员调用。然而,实际上我们并没有从这个类的其他方法中调用它们,而是 ObjectOutputStream 和 ObjectInputStream 对象的 writeObject() 和 readobject() 方法调用你的对象的 writeObject() 和 readObject() 方法(注意关于这里用到的相同方法名,我尽量抑制住不去谩骂。一句话:混乱)。读者可能想知道 ObjectOutputStream 和 ObjectInputStream 对象是怎样访问你的类中的 private 方法的。我们只能假设这正是序列化神奇的一部分。\n", - "\n", - "在接口中定义的所有东西都自动是 public 的,因此如果 writeObject() 和 readObject() 必须是 private 的,那么它们不会是接口的一部分。因为我们必须要完全遵循其方法特征签名,所以其效果就和实现了接口一样。\n", - "\n", - "在调用 ObjectOutputStream.writeObject() 时,会检查所传递的 Serializable 对象,看看是否实现了它自己的 writeObject()。如果是这样,就跳过正常的序列化过程并调用它的 writeObiect()。readObject() 的情形与此相同。\n", - "\n", - "还有另外一个技巧。在你的 writeObject() 内部,可以调用 defaultWriteObject() 来选择执行默认的 writeObject()。类似地,在 readObject() 内部,我们可以调用 defaultReadObject(),下面这个简单的例子演示了如何对一个 Serializable 对象的存储与恢复进行控制:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/SerialCtl.java\n", - "// Controlling serialization by adding your own\n", - "// writeObject() and readObject() methods\n", - "import java.io.*;\n", - "public class SerialCtl implements Serializable {\n", - " private String a;\n", - " private transient String b;\n", - " public SerialCtl(String aa, String bb) {\n", - " a = \"Not Transient: \" + aa;\n", - " b = \"Transient: \" + bb;\n", - " }\n", - " @Override\n", - " public String toString() { return a + \"\\n\" + b; }\n", - " private void writeObject(ObjectOutputStream stream)\n", - " throws IOException {\n", - " stream.defaultWriteObject();\n", - " stream.writeObject(b);\n", - " }\n", - " private void readObject(ObjectInputStream stream)\n", - " throws IOException, ClassNotFoundException {\n", - " stream.defaultReadObject();\n", - " b = (String)stream.readObject();\n", - " }\n", - " public static void main(String[] args) {\n", - " SerialCtl sc = new SerialCtl(\"Test1\", \"Test2\");\n", - " System.out.println(\"Before:\\n\" + sc);\n", - " try (\n", - " ByteArrayOutputStream buf =\n", - " new ByteArrayOutputStream();\n", - " ObjectOutputStream o =\n", - " new ObjectOutputStream(buf);\n", - " ) {\n", - " o.writeObject(sc);\n", - "// Now get it back:\n", - " try (\n", - " ObjectInputStream in =\n", - " new ObjectInputStream(\n", - " new ByteArrayInputStream(\n", - " buf.toByteArray()));\n", - " ) {\n", - " SerialCtl sc2 = (SerialCtl)in.readObject();\n", - " System.out.println(\"After:\\n\" + sc2);\n", - " }\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Before:\n", - "Not Transient: Test1\n", - "Transient: Test2\n", - "After:\n", - "Not Transient: Test1\n", - "Transient: Test2" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个例子中,有一个 String 字段是普通字段,而另一个是 transient 字段,用来证明非 transient 字段由 defaultWriteObject() 方法保存,而 transient 字段必须在程序中明确保存和恢复。字段是在构造器内部而不是在定义处进行初始化的,以此可以证实它们在反序列化还原期间没有被一些自动化机制初始化。\n", - "\n", - "如果我们打算使用默认机制写入对象的非 transient 部分,那么必须调用 defaultwriteObject() 作为 writeObject() 中的第一个操作,并让 defaultReadObject() 作为 readObject() 中的第一个操作。这些都是奇怪的方法调用。例如,如果我们正在为 ObjectOutputStream 调用 defaultWriteObject() 且没有传递任何参数,然而不知何故它却可以运行,并且知道对象的引用以及如何写入非 transient 部分。真是奇怪之极。\n", - "\n", - "对 transient 对象的存储和恢复使用了我们比较熟悉的代码。请再考虑一下在这里所发生的事情。在 main0)中,创建 SerialCtl 对象,然后将其序列化到 ObjectOutputStream(注意在这种情况下,使用的是缓冲区而不是文件-这对于 ObjectOutputStream 来说是完全一样的)。序列化发生在下面这行代码当中" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "o.writeObject(sc);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "writeObject() 方法必须检查 sc,判断它是否拥有自己的 writeObject() 方法(不是检查接口——这里根本就没有接口,也不是检查类的类型,而是利用反射来真正地搜索方法)。如果有,那么就会使用它。对 readObject() 也采用了类似的方法。或许这是解决这个问题的唯一切实可行的方法,但它确实有点古怪。\n", - "\n", - "### 版本控制\n", - "\n", - "有时可能想要改变可序列化类的版本(比如源类的对象可能保存在数据库中)。虽然 Java 支持这种做法,但是你可能只在特殊的情况下才这样做,此外,还需要对它有相当深程度的了解(在这里我们就不再试图达到这一点)。从 http://java.oracle.com 下的 JDK 文档中对这一主题进行了非常彻底的论述。\n", - "\n", - "\n", - "\n", - "## 使用持久化\n", - "\n", - "个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象-它们都具有指向第三个对象的引用-进行序列化,会发生什么情况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢?\n", - "\n", - "下面这个例子说明了上述问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/MyWorld.java\n", - "import java.io.*;\n", - "import java.util.*;\n", - "class House implements Serializable {}\n", - "class Animal implements Serializable {\n", - " private String name;\n", - " private House preferredHouse;\n", - " Animal(String nm, House h) {\n", - " name = nm;\n", - " preferredHouse = h;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return name + \"[\" + super.toString() +\n", - " \"], \" + preferredHouse + \"\\n\";\n", - " }\n", - "}\n", - "public class MyWorld {\n", - " public static void main(String[] args) {\n", - " House house = new House();\n", - " List animals = new ArrayList<>();\n", - " animals.add(\n", - " new Animal(\"Bosco the dog\", house));\n", - " animals.add(\n", - " new Animal(\"Ralph the hamster\", house));\n", - " animals.add(\n", - " new Animal(\"Molly the cat\", house));\n", - " System.out.println(\"animals: \" + animals);\n", - " try(\n", - " ByteArrayOutputStream buf1 =\n", - " new ByteArrayOutputStream();\n", - " ObjectOutputStream o1 =\n", - " new ObjectOutputStream(buf1)\n", - " ) {\n", - " o1.writeObject(animals);\n", - " o1.writeObject(animals); // Write a 2nd set\n", - "// Write to a different stream:\n", - " try(\n", - " ByteArrayOutputStream buf2 = new ByteArrayOutputStream();\n", - " ObjectOutputStream o2 = new ObjectOutputStream(buf2)\n", - " ) {\n", - " o2.writeObject(animals);\n", - "// Now get them back:\n", - " try(\n", - " ObjectInputStream in1 =\n", - " new ObjectInputStream(\n", - " new ByteArrayInputStream(\n", - " buf1.toByteArray()));\n", - " ObjectInputStream in2 =\n", - " new ObjectInputStream(\n", - " new ByteArrayInputStream(\n", - " buf2.toByteArray()))\n", - " ) {\n", - " List\n", - " animals1 = (List)in1.readObject(),\n", - " animals2 = (List)in1.readObject(),\n", - " animals3 = (List)in2.readObject();\n", - " System.out.println(\n", - " \"animals1: \" + animals1);\n", - " System.out.println(\n", - " \"animals2: \" + animals2);\n", - " System.out.println(\n", - " \"animals3: \" + animals3);\n", - " }\n", - " }\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "animals: [Bosco the dog[Animal@15db9742],\n", - "House@6d06d69c\n", - ", Ralph the hamster[Animal@7852e922], House@6d06d69c\n", - ", Molly the cat[Animal@4e25154f], House@6d06d69c\n", - "]\n", - "animals1: [Bosco the dog[Animal@7ba4f24f],\n", - "House@3b9a45b3\n", - ", Ralph the hamster[Animal@7699a589], House@3b9a45b3\n", - ", Molly the cat[Animal@58372a00], House@3b9a45b3\n", - "]\n", - "animals2: [Bosco the dog[Animal@7ba4f24f],\n", - "House@3b9a45b3\n", - ", Ralph the hamster[Animal@7699a589], House@3b9a45b3\n", - ", Molly the cat[Animal@58372a00], House@3b9a45b3\n", - "]\n", - "animals3: [Bosco the dog[Animal@4dd8dc3],\n", - "House@6d03e736\n", - ", Ralph the hamster[Animal@568db2f2], House@6d03e736\n", - ", Molly the cat[Animal@378bf509], House@6d03e736\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里有一件有趣的事:我们可以通过一个字节数组来使用对象序列化,从而实现对任何可 Serializable 对象的“深度复制\"(deep copy)—— 深度复制意味着我们复制的是整个对象网,而不仅仅是基本对象及其引用。复制对象将在本书的 [附录:传递和返回对象 ]() 一章中进行深入地探讨。\n", - "\n", - "在这个例子中,Animal 对象包含有 House 类型的字段。在 main() 方法中,创建了一个 Animal 列表并将其两次序列化,分别送至不同的流。当其被反序列化还原并被打印时,我们可以看到所示的执行某次运行后的结果(每次运行时,对象将会处在不同的内存地址)。\n", - "\n", - "当然,我们期望这些反序列化还原后的对象地址与原来的地址不同。但请注意,在 animals1 和 animals2 中却出现了相同的地址,包括二者共享的那个指向 House 对象的引用。另一方面,当恢复 animals3 时,系统无法知道另一个流内的对象是第一个流内的对象的别名,因此它会产生出完全不同的对象网。\n", - "\n", - "只要将任何对象序列化到单一流中,就可以恢复出与我们写出时一样的对象网,并且没有任何意外重复复制出的对象。当然,我们可以在写出第一个对象和写出最后一个对象期间改变这些对象的状态,但是这是我们自己的事,无论对象在被序列化时处于什么状态(无论它们和其他对象有什么样的连接关系),它们都可以被写出。\n", - "\n", - "最安全的做法是将其作为“原子”操作进行序列化。如果我们序列化了某些东西,再去做其他一些工作,再来序列化更多的东西,如此等等,那么将无法安全地保存系统状态。取而代之的是,将构成系统状态的所有对象都置入单一容器内,并在一个操作中将该容器直接写出。然后同样只需一次方法调用,即可以将其恢复。\n", - "\n", - "下面这个例子是一个想象的计算机辅助设计(CAD)系统,该例演示了这一方法。此外,它还引入了 static 字段的问题:如果我们查看 JDK 文档,就会发现 Class 是 Serializable 的,因此只需直接对 Class 对象序列化,就可以很容易地保存 static 字段。在任何情况下,这都是一种明智的做法。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/AStoreCADState.java\n", - "// Saving the state of a fictitious CAD system\n", - "import java.io.*;\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "enum Color { RED, BLUE, GREEN }\n", - "abstract class Shape implements Serializable {\n", - " private int xPos, yPos, dimension;\n", - " private static Random rand = new Random(47);\n", - " private static int counter = 0;\n", - " public abstract void setColor(Color newColor);\n", - " public abstract Color getColor();\n", - " Shape(int xVal, int yVal, int dim) {\n", - " xPos = xVal;\n", - " yPos = yVal;\n", - " dimension = dim;\n", - " }\n", - " public String toString() {\n", - " return getClass() + \"color[\" + getColor() +\n", - " \"] xPos[\" + xPos + \"] yPos[\" + yPos +\n", - " \"] dim[\" + dimension + \"]\\n\";\n", - " }\n", - " public static Shape randomFactory() {\n", - " int xVal = rand.nextInt(100);\n", - " int yVal = rand.nextInt(100);\n", - " int dim = rand.nextInt(100);\n", - " switch(counter++ % 3) {\n", - " default:\n", - " case 0: return new Circle(xVal, yVal, dim);\n", - " case 1: return new Square(xVal, yVal, dim);\n", - " case 2: return new Line(xVal, yVal, dim);\n", - " }\n", - " }\n", - "}\n", - "class Circle extends Shape {\n", - " private static Color color = Color.RED;\n", - " Circle(int xVal, int yVal, int dim) {\n", - " super(xVal, yVal, dim);\n", - " }\n", - " public void setColor(Color newColor) {\n", - " color = newColor;\n", - " }\n", - " public Color getColor() { return color; }\n", - "}\n", - "class Square extends Shape {\n", - " private static Color color = Color.RED;\n", - " Square(int xVal, int yVal, int dim) {\n", - " super(xVal, yVal, dim);\n", - " }\n", - " public void setColor(Color newColor) {\n", - " color = newColor;\n", - " }\n", - " public Color getColor() { return color; }\n", - "}\n", - "class Line extends Shape {\n", - " private static Color color = Color.RED;\n", - " public static void\n", - " serializeStaticState(ObjectOutputStream os)\n", - " throws IOException { os.writeObject(color); }\n", - " public static void\n", - " deserializeStaticState(ObjectInputStream os)\n", - " throws IOException, ClassNotFoundException {\n", - " color = (Color)os.readObject();\n", - " }\n", - " Line(int xVal, int yVal, int dim) {\n", - " super(xVal, yVal, dim);\n", - " }\n", - " public void setColor(Color newColor) {\n", - " color = newColor;\n", - " }\n", - " public Color getColor() { return color; }\n", - "}\n", - "public class AStoreCADState {\n", - " public static void main(String[] args) {\n", - " List> shapeTypes =\n", - " Arrays.asList(\n", - " Circle.class, Square.class, Line.class);\n", - " List shapes = IntStream.range(0, 10)\n", - " .mapToObj(i -> Shape.randomFactory())\n", - " .collect(Collectors.toList());\n", - " // Set all the static colors to GREEN:\n", - " shapes.forEach(s -> s.setColor(Color.GREEN));\n", - " // Save the state vector:\n", - " try(\n", - " ObjectOutputStream out =\n", - " new ObjectOutputStream(\n", - " new FileOutputStream(\"CADState.dat\"))\n", - " ) {\n", - " out.writeObject(shapeTypes);\n", - " Line.serializeStaticState(out);\n", - " out.writeObject(shapes);\n", - " } catch(IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " // Display the shapes:\n", - " System.out.println(shapes);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[class Circlecolor[GREEN] xPos[58] yPos[55] dim[93]\n", - ", class Squarecolor[GREEN] xPos[61] yPos[61] dim[29]\n", - ", class Linecolor[GREEN] xPos[68] yPos[0] dim[22]\n", - ", class Circlecolor[GREEN] xPos[7] yPos[88] dim[28]\n", - ", class Squarecolor[GREEN] xPos[51] yPos[89] dim[9]\n", - ", class Linecolor[GREEN] xPos[78] yPos[98] dim[61]\n", - ", class Circlecolor[GREEN] xPos[20] yPos[58] dim[16]\n", - ", class Squarecolor[GREEN] xPos[40] yPos[11] dim[22]\n", - ", class Linecolor[GREEN] xPos[4] yPos[83] dim[6]\n", - ", class Circlecolor[GREEN] xPos[75] yPos[10] dim[42]\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Shape 类实现了 Serializable,所以任何自 Shape 继承的类也都会自动是 Serializable 的。每个 Shape 都含有数据,而且每个派生自 Shape 的类都包含一个 static 字段,用来确定各种 Shape 类型的颜色(如果将 static 字段置入基类,只会产生一个 static 字段,因为 static 字段不能在派生类中复制)。可对基类中的方法进行重载,以便为不同的类型设置颜色(static 方法不会动态绑定,所以这些都是普通的方法)。每次调用 randomFactory() 方法时,它都会使用不同的随机数作为 Shape 的数据,从而创建不同的 Shape。\n", - "\n", - "在 main() 中,一个 ArrayList 用于保存 Class 对象,而另一个用于保存几何形状。\n", - "\n", - "恢复对象相当直观:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/RecoverCADState.java\n", - "// Restoring the state of the fictitious CAD system\n", - "// {RunFirst: AStoreCADState}\n", - "import java.io.*;\n", - "import java.util.*;\n", - "public class RecoverCADState {\n", - " @SuppressWarnings(\"unchecked\")\n", - " public static void main(String[] args) {\n", - " try(\n", - " ObjectInputStream in =\n", - " new ObjectInputStream(\n", - " new FileInputStream(\"CADState.dat\"))\n", - " ) {\n", - "// Read in the same order they were written:\n", - " List> shapeTypes =\n", - " (List>)in.readObject();\n", - " Line.deserializeStaticState(in);\n", - " List shapes =\n", - " (List)in.readObject();\n", - " System.out.println(shapes);\n", - " } catch(IOException | ClassNotFoundException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[class Circlecolor[RED] xPos[58] yPos[55] dim[93]\n", - ", class Squarecolor[RED] xPos[61] yPos[61] dim[29]\n", - ", class Linecolor[GREEN] xPos[68] yPos[0] dim[22]\n", - ", class Circlecolor[RED] xPos[7] yPos[88] dim[28]\n", - ", class Squarecolor[RED] xPos[51] yPos[89] dim[9]\n", - ", class Linecolor[GREEN] xPos[78] yPos[98] dim[61]\n", - ", class Circlecolor[RED] xPos[20] yPos[58] dim[16]\n", - ", class Squarecolor[RED] xPos[40] yPos[11] dim[22]\n", - ", class Linecolor[GREEN] xPos[4] yPos[83] dim[6]\n", - ", class Circlecolor[RED] xPos[75] yPos[10] dim[42]\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到,xPos,yPos 以及 dim 的值都被成功地保存和恢复了,但是对 static 信息的读取却出现了问题。所有读回的颜色应该都是“3”,但是真实情况却并非如此。Circle 的值为 1(定义为 RED),而 Square 的值为 0(记住,它们是在构造器中被初始化的)。看上去似乎 static 数据根本没有被序列化!确实如此——尽管 Class 类是 Serializable 的,但它却不能按我们所期望的方式运行。所以假如想序列化 static 值,必须自己动手去实现。\n", - "\n", - "这正是 Line 中的 serializeStaticState() 和 deserializeStaticState() 两个 static 方法的用途。可以看到,它们是作为存储和读取过程的一部分被显式地调用的。(注意必须维护写入序列化文件和从该文件中读回的顺序。)因此,为了使 CADStatejava 正确运转起来,你必须:\n", - "\n", - "1. 为几何形状添加 serializeStaticState() 和 deserializeStaticState()\n", - "2. 移除 ArrayList shapeTypes 以及与之有关的所有代码。\n", - "3. 在几何形状内添加对新的序列化和反序列化还原静态方法的调用。\n", - "\n", - "另一个要注意的问题是安全,因为序列化也会将 private 数据保存下来。如果你关心安全问题,那么应将其标记成 transient,但是这之后,还必须设计一种安全的保存信息的方法,以便在执行恢复时可以复位那些 private 变量。\n", - "\n", - "## XML\n", - "\n", - "对象序列化的一个重要限制是它只是 Java 的解决方案:只有 Java 程序才能反序列化这种对象。一种更具互操作性的解决方案是将数据转换为 XML 格式,这可以使其被各种各样的平台和语言使用。\n", - "\n", - "因为 XML 十分流行,所以用它来编程时的各种选择不胜枚举,包括随 JDK 发布的 javax.xml.*类库。我选择使用 Elliotte Rusty Harold 的开源 XOM 类库(可从 www.xom.nu 下载并获得文档),因为它看起来最简单,同时也是最直观的用 Java 产生和修改 XML 的方式。另外,XOM 还强调了 XML 的正确性。\n", - "\n", - "作为一个示例,假设有一个 APerson 对象,它包含姓和名,你想将它们序列化到 XML 中。下面的 APerson 类有一个 getXML() 方法,它使用 XOM 来产生被转换为 XML 的 Element 对象的 APerson 数据;还有一个构造器,接受 Element 并从中抽取恰当的 APerson 数据(注意,XML 示例都在它们自己的子目录中):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/APerson.java\n", - "// Use the XOM library to write and read XML\n", - "// nu.xom.Node comes from http://www.xom.nu\n", - "import nu.xom.*;\n", - "import java.io.*;\n", - "import java.util.*;\n", - "public class APerson {\n", - " private String first, last;\n", - " public APerson(String first, String last) {\n", - " this.first = first;\n", - " this.last = last;\n", - " }\n", - " // Produce an XML Element from this APerson object:\n", - " public Element getXML() {\n", - " Element person = new Element(\"person\");\n", - " Element firstName = new Element(\"first\");\n", - " firstName.appendChild(first);\n", - " Element lastName = new Element(\"last\");\n", - " lastName.appendChild(last);\n", - " person.appendChild(firstName);\n", - " person.appendChild(lastName);\n", - " return person;\n", - " }\n", - " // Constructor restores a APerson from XML:\n", - " public APerson(Element person) {\n", - " first = person\n", - " .getFirstChildElement(\"first\").getValue();\n", - " last = person\n", - " .getFirstChildElement(\"last\").getValue();\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return first + \" \" + last;\n", - " }\n", - " // Make it human-readable:\n", - " public static void\n", - " format(OutputStream os, Document doc)\n", - " throws Exception {\n", - " Serializer serializer =\n", - " new Serializer(os,\"ISO-8859-1\");\n", - " serializer.setIndent(4);\n", - " serializer.setMaxLength(60);\n", - " serializer.write(doc);\n", - " serializer.flush();\n", - " }\n", - " public static void main(String[] args) throws Exception {\n", - " List people = Arrays.asList(\n", - " new APerson(\"Dr. Bunsen\", \"Honeydew\"),\n", - " new APerson(\"Gonzo\", \"The Great\"),\n", - " new APerson(\"Phillip J.\", \"Fry\"));\n", - " System.out.println(people);\n", - " Element root = new Element(\"people\");\n", - " for(APerson p : people)\n", - " root.appendChild(p.getXML());\n", - " Document doc = new Document(root);\n", - " format(System.out, doc);\n", - " format(new BufferedOutputStream(\n", - " new FileOutputStream(\"People.xml\")), doc);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出为:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "xml" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]\n", - "\n", - "\n", - " \n", - " Dr. Bunsen\n", - " Honeydew\n", - " \n", - " \n", - " Gonzo\n", - " The Great\n", - " \n", - " \n", - " Phillip J.\n", - " Fry\n", - " \n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "XOM 的方法都具有相当的自解释性,可以在 XOM 文档中找到它们。XOM 还包含一个 Serializer 类,你可以在 format() 方法中看到它被用来将 XML 转换为更具可读性的格式。如果只调用 toXML(),那么所有东西都会混在一起,因此 Serializer 是一种便利工具。\n", - "\n", - "从 XML 文件中反序列化 Person 对象也很简单:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// serialization/People.java\n", - "// nu.xom.Node comes from http://www.xom.nu\n", - "// {RunFirst: APerson}\n", - "import nu.xom.*;\n", - "import java.io.File;\n", - "import java.util.*;\n", - "public class People extends ArrayList {\n", - " public People(String fileName) throws Exception {\n", - " Document doc =\n", - " new Builder().build(new File(fileName));\n", - " Elements elements =\n", - " doc.getRootElement().getChildElements();\n", - " for(int i = 0; i < elements.size(); i++)\n", - " add(new APerson(elements.get(i)));\n", - " }\n", - " public static void main(String[] args) throws Exception {\n", - " People p = new People(\"People.xml\");\n", - " System.out.println(p);\n", - " }\n", - "}\n", - "/* Output:\n", - "[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "People 构造器使用 XOM 的 Builder.build() 方法打开并读取一个文件,而 getChildElements() 方法产生了一个 Elements 列表(不是标准的 Java List,只是一个拥有 size() 和 get() 方法的对象,因为 Harold 不想强制人们使用特定版本的 Java,但是仍旧希望使用类型安全的容器)。在这个列表中的每个 Element 都表示一个 Person 对象,因此它可以传递给第二个 Person 构造器。注意,这要求你提前知道 XML 文件的确切结构,但是这经常会有些问题。如果文件结构与你预期的结构不匹配,那么 XOM 将抛出异常。对你来说,如果你缺乏有关将来的 XML 结构的信息,那么就有可能会编写更复杂的代码去探测 XML 文档,而不是只对其做出假设。\n", - "\n", - "为了获取这些示例去编译它们,你必须将 XOM 发布包中的 JAR 文件放置到你的类路径中。\n", - "\n", - "这里只给出了用 Java 和 XOM 类库进行 XML 编程的简介,更详细的信息可以浏览 www.xom.nu 。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Passing-and-Returning-Objects.ipynb b/jupyter/Appendix-Passing-and-Returning-Objects.ipynb deleted file mode 100644 index 1ecfd6a2..00000000 --- a/jupyter/Appendix-Passing-and-Returning-Objects.ipynb +++ /dev/null @@ -1,88 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:对象传递和返回\n", - "\n", - "> 到现在为止,你已经对“传递”对象实际上是传递引用这一想法想法感到满意。\n", - "\n", - "在许多编程语言中,你可以使用该语言的“常规”方式来传递对象,并且大多数情况下一切正常。 但是通常会出现这种情况,你必须做一些不平常的事情,突然事情变得更加复杂。 Java也不例外,当您传递对象并对其进行操作时,准确了解正在发生的事情很重要。 本附录提供了这种见解。\n", - "\n", - "提出本附录问题的另一种方法是,如果你之前使用类似C++的编程语言,则是“ Java是否有指针?” Java中的每个对象标识符(除原语外)都是这些指针之一,但它们的用法是不仅受编译器的约束,而且受运行时系统的约束。 换一种说法,Java有指针,但没有指针算法。 这些就是我一直所说的“引用”,您可以将它们视为“安全指针”,与小学的安全剪刀不同-它们不敏锐,因此您不费吹灰之力就无法伤害自己,但是它们有时可能很乏味。\n", - "\n", - "\n", - "\n", - "## 传递引用\n", - "\n", - "\n", - "\n", - "当你将引用传递给方法时,它仍指向同一对象。 一个简单的实验演示了这一点:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// references/PassReferences.java\n", - "public class PassReferences {\n", - "public static void f(PassReferences h) {\n", - " \tSystem.out.println(\"h inside f(): \" + h);\n", - " }\n", - " public static void main(String[] args) {\n", - " PassReferences p = new PassReferences();\n", - " System.out.println(\"p inside main(): \" + p);\n", - " f(p);\n", - " }\n", - "}\n", - "/* Output:\n", - "p inside main(): PassReferences@15db9742\n", - "h inside f(): PassReferences@15db9742\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "方法 `toString() ` 在打印语句中自动调用,并且 `PassReferences` 直接从 `Object` 继承而无需重新定义 `toString()` 。 因此,使用的是 `Object` 的 `toString()` 版本,它打印出对象的类,然后打印出该对象所在的地址(不是引用,而是实际的对象存储)。\n", - "\n", - "## 本地拷贝\n", - "\n", - "\n", - "\n", - "## 控制克隆\n", - "\n", - "\n", - "\n", - "## 不可变类\n", - "\n", - "\n", - "\n", - "## 本章小结\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Programming-Guidelines.ipynb b/jupyter/Appendix-Programming-Guidelines.ipynb deleted file mode 100644 index c7a87fc8..00000000 --- a/jupyter/Appendix-Programming-Guidelines.ipynb +++ /dev/null @@ -1,187 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:编程指南\n", - "\n", - "> 本附录包含了有助于指导你进行低级程序设计和编写代码的建议。\n", - "\n", - "当然,这些只是指导方针,而不是规则。我们的想法是将它们用作灵感,并记住偶尔会违反这些指导方针的特殊情况。\n", - "\n", - "\n", - "## 设计\n", - "\n", - "1. **优雅总是会有回报**。从短期来看,似乎需要更长的时间才能找到一个真正优雅的问题解决方案,但是当该解决方案第一次应用并能轻松适应新情况,而不需要数小时,数天或数月的挣扎时,你会看到奖励(即使没有人可以测量它们)。它不仅为你提供了一个更容易构建和调试的程序,而且它也更容易理解和维护,这也正是经济价值所在。这一点可以通过一些经验来理解,因为当你想要使一段代码变得优雅时,你可能看起来效率不是很高。抵制急于求成的冲动,它只会减慢你的速度。\n", - "\n", - "2. **先让它工作,然后再让它变快**。即使你确定一段代码非常重要并且它是你系统中的主要瓶颈**,也是如此。不要这样做。使用尽可能简单的设计使系统首先运行。然后如果速度不够快,请对其进行分析。你几乎总会发现“你的”瓶颈不是问题。节省时间,才是真正重要的东西。\n", - "\n", - "3. **记住“分而治之”的原则**。如果所面临的问题太过混乱**,就去想象一下程序的基本操作,因为存在一个处理困难部分的神奇“片段”(piece)。该“片段”是一个对象,编写使用该对象的代码,然后查看该对象并将其困难部分封装到其他对象中,等等。\n", - "\n", - "4. **将类创建者与类用户(客户端程序员)分开**。类用户是“客户”,不需要也不想知道类幕后发生了什么。类创建者必须是设计类的专家,他们编写类,以便新手程序员都可以使用它,并仍然可以在应用程序中稳健地工作。将该类视为其他类的*服务提供者*(service provider)。只有对其它类透明,才能很容易地使用这个类。\n", - "\n", - "5. **创建类时,给类起个清晰的名字,就算不需要注释也能理解这个类**。你的目标应该是使客户端程序员的接口在概念上变得简单。为此,在适当时使用方法重载来创建直观,易用的接口。\n", - "\n", - "6. **你的分析和设计必须至少能够产生系统中的类、它们的公共接口以及它们与其他类的关系,尤其是基类**。 如果你的设计方法产生的不止于此,就该问问自己,该方法生成的所有部分是否在程序的生命周期内都具有价值。如果不是,那么维护它们会很耗费精力。对于那些不会影响他们生产力的东西,开发团队的成员往往不会去维护,这是许多设计方法都没有考虑的生活现实。\n", - "\n", - "7. **让一切自动化**。首先在编写类之前,编写测试代码,并将其与类保持一致。通过构建工具自动运行测试。你可能会使用事实上的标准Java构建工具Gradle。这样,通过运行测试代码可以自动验证任何更改,将能够立即发现错误。因为你知道自己拥有测试框架的安全网,所以当发现需要时,可以更大胆地进行彻底的更改。请记住,语言的巨大改进来自内置的测试,包括类型检查,异常处理等,但这些内置功能很有限,你必须完成剩下的工作,针对具体的类或程序,去完善这些测试内容,从而创建一个强大的系统。\n", - "\n", - "8. **在编写类之前,先编写测试代码,以验证类的设计是完善的**。如果不编写测试代码,那么就不知道类是什么样的。此外,通过编写测试代码,往往能够激发出类中所需的其他功能或约束。而这些功能或约束并不总是出现在分析和设计过程中。测试还会提供示例代码,显示了如何使用这个类。\n", - "\n", - "9. **所有的软件设计问题,都可以通过引入一个额外的间接概念层次(extra level of conceptual indirection)来解决**。这个软件工程的基本规则[^1]是抽象的基础,是面向对象编程的主要特征。在面向对象编程中,我们也可以这样说:“如果你的代码太复杂,就要生成更多的对象。”\n", - "\n", - "10. **间接(indirection)应具有意义(与准则9一致)**。这个含义可以简单到“将常用代码放在单个方法中。”如果添加没有意义的间接(抽象,封装等)级别,那么它就像没有足够的间接性那样糟糕。\n", - "\n", - "11. **使类尽可能原子化**。 为每个类提供一个明确的目的,它为其他类提供一致的服务。如果你的类或系统设计变得过于复杂,请将复杂类分解为更简单的类。最直观的指标是尺寸大小,如果一个类很大,那么它可能是做的事太多了,应该被拆分。建议重新设计类的线索是:\n", - " - 一个复杂的*switch*语句:考虑使用多态。\n", - " - 大量方法涵盖了很多不同类型的操作:考虑使用多个类。\n", - " - 大量成员变量涉及很多不同的特征:考虑使用多个类。\n", - " - 其他建议可以参见Martin Fowler的*Refactoring: Improving the Design of Existing Code*(重构:改善既有代码的设计)(Addison-Wesley 1999)。\n", - "\n", - "12. **注意长参数列表**。那样方法调用会变得难以编写,读取和维护。相反,尝试将方法移动到更合适的类,并且(或者)将对象作为参数传递。\n", - "\n", - "13. **不要重复自己**。如果一段代码出现在派生类的许多方法中,则将该代码放入基类中的单个方法中,并从派生类方法中调用它。这样不仅可以节省代码空间,而且可以轻松地传播更改。有时,发现这个通用代码会为接口添加有价值的功能。此指南的更简单版本也可以在没有继承的情况下发生:如果类具有重复代码的方法,则将该重复代码放入一个公共方,法并在其他方法中调用它。\n", - "\n", - "14. **注意*switch*语句或链式*if-else*子句**。一个*类型检查编码*(type-check coding)的指示器意味着需要根据某种类型信息选择要执行的代码(确切的类型最初可能不明显)。很多时候可以用继承和多态替换这种代码,多态方法调用将会执行类型检查,并提供了更可靠和更容易的可扩展性。 \n", - "\n", - "15. **从设计的角度,寻找和分离那些因不变的事物而改变的事物**。也就是说,在不强制重新设计的情况下搜索可能想要更改的系统中的元素,然后将这些元素封装在类中。\n", - "\n", - "16. **不要通过子类扩展基本功能**。如果一个接口元素对于类来说是必不可少的,则它应该在基类中,而不是在派生期间添加。如果要在继承期间添加方法,请考虑重新设计。\n", - "\n", - "17. **少即是多**。从一个类的最小接口开始,尽可能小而简单,以解决手头的问题,但不要试图预测类的所有使用方式。在使用该类时,就将会了解如何扩展接口。但是,一旦这个类已经在使用了,就无法在不破坏客户端代码的情况下缩小接口。如果必须添加更多方法,那很好,它不会破坏代码。但即使新方法取代旧方法的功能,也只能是保留现有接口(如果需要,可以结合底层实现中的功能)。如果必须通过添加更多参数来扩展现有方法的接口,请使用新参数创建重载方法,这样,就不会影响到对现有方法的任何调用。\n", - "\n", - "18. **大声读出你的类以确保它们合乎逻辑**。将基类和派生类之间的关系称为“is-a”,将成员对象称为“has-a”。\n", - "\n", - "19. **在需要在继承和组合之间作决定时,问一下自己是否必须向上转换为基类型**。如果不是,则使用组合(成员对象)更好。这可以消除对多种基类型的感知需求(perceived need)。如果使用继承,则用户会认为他们应该向上转型。\n", - "\n", - "20. **注意重载**。方法不应该基于参数的值而有条件地执行代码。在这里,应该创建两个或多个重载方法。\n", - "\n", - "21. **使用异常层次结构**,最好是从标准Ja​​va异常层次结构中的特定适当类派生。然后,捕获异常的人可以为特定类型的异常编写处理程序,然后为基类型编写处理程序。如果添加新的派生异常,现有客户端代码仍将通过基类型捕获异常。\n", - "\n", - "22. **有时简单的聚合可以完成工作**。航空公司的“乘客舒适系统”由独立的元素组成:座位,空调,影视等,但必须在飞机上创建许多这样的元素。你创建私有成员并建立一个全新的接口了吗?如果不是,在这种情况下,组件也应该是公共接口的一部分,因此应该创建公共成员对象。这些对象有自己的私有实现,这些实现仍然是安全的。请注意,简单聚合不是经常使用的解决方案,但确实会有时候会用到。\n", - "\n", - "23. **考虑客户程序员和维护代码的人的观点**。设计类以便尽可能直观地被使用。预测要进行的更改,并精心设计类,以便轻松地进行更改。\n", - "\n", - "24. **注意“巨型对象综合症”**(giant object syndrome)。这通常是程序员的痛苦,他们是面向对象编程的新手,总是编写面向过程程序并将其粘贴在一个或两个巨型对象中。除应用程序框架外,对象代表应用程序中的概念,而不是应用程序本身。\n", - "\n", - "25. **如果你必须做一些丑陋的事情,至少要把类内的丑陋本地化**。\n", - "\n", - "26. **如果必须做一些不可移植的事情,那就对这个事情做一个抽象,并在一个类中进行本地化**。这种额外的间接级别可防止在整个程序中扩散这种不可移植性。 (这个原则也体现在*桥接*模式中,等等)。\n", - "\n", - "27. **对象不应该仅仅只是持有一些数据**。它们也应该有明确的行为。有时候,“数据传输对象”(data transfer objects)是合适的,但只有在泛型集合不合适时,才被明确用于打包和传输一组元素。\n", - "\n", - "28. **在从现有类创建新类时首先选择组合**。仅在设计需要时才使用继承。如果在可以使用组合的地方使用继承,那么设计将会变得很复杂,这是没必要的。\n", - "\n", - "29. **使用继承和覆盖方法来表达行为的差异,而不是使用字段来表示状态的变化**。如果发现一个类使用了状态变量,并且有一些方法是基于这些变量切换行为的,那么请重新设计它,以表示子类和覆盖方法中的行为差异。一个极端的反例是继承不同的类来表示颜色,而不是使用“颜色”字段。\n", - "\n", - "30. **注意*协变*(variance)**。两个语义不同的对象可能具有相同的操作或职责。为了从继承中受益,会试图让其中一个成为另一个的子类,这是一种很自然的诱惑。这称为协变,但没有真正的理由去强制声明一个并不存在的父子类关系。更好的解决方案是创建一个通用基类,并为两者生成一个接口,使其成为这个通用基类的派生类。这仍然可以从继承中受益,并且这可能是关于设计的一个重要发现。\n", - "\n", - "31. **在继承期间注意*限定*(limitation)**。最明确的设计为继承的类增加了新的功能。含糊的设计在继承期间删除旧功能而不添加新功能。但是规则是用来打破的,如果是通过调用一个旧的类库来工作,那么将一个现有类限制在其子类型中,可能比重构层次结构更有效,因此新类适合在旧类的上层。\n", - "\n", - "32. **使用设计模式来消除“裸功能”(naked functionality)**。也就是说,如果类只需要创建一个对象,请不要推进应用程序并写下注释“只生成一个。”应该将其包装成一个单例(singleton)。如果主程序中有很多乱七八糟的代码去创建对象,那么找一个像工厂方法一样的创建模式,可以在其中封装创建过程。消除“裸功能”不仅会使代码更易于理解和维护,而且还会使其能够更加防范应对后面的善意维护者(well-intentioned maintainers)。\n", - "\n", - "33. **注意“分析瘫痪”(analysis paralysis)**。记住,不得不经常在不了解整个项目的情况下推进项目,并且通常了解那些未知因素的最好、最快的方式是进入下一步而不是尝试在脑海中弄清楚。在获得解决方案之前,往往无法知道解决方案。Java有内置的防火墙,让它们为你工作。你在一个类或一组类中的错误不会破坏整个系统的完整性。\n", - "\n", - "34. **如果认为自己有很好的分析,设计或实施,请做一个演练**。从团队外部带来一些人,不一定是顾问,但可以是公司内其他团体的人。用一双新眼睛评审你的工作,可以在一个更容易修复它们的阶段发现问题,而不仅仅是把大量时间和金钱全扔到演练过程中。\n", - "\n", - "\n", - "## 实现\n", - "\n", - "36. **遵循编码惯例**。有很多不同的约定,例如,[谷歌使用的约定](https://google.github.io/styleguide/javaguide.html)(本书中的代码尽可能地遵循这些约定)。如果坚持使用其他语言的编码风格,那么读者就会很难去阅读。无论决定采用何种编码约定,都要确保它们在整个项目中保持一致。集成开发环境通常包含内置的重新格式化(reformatter)和检查器(checker)。\n", - "\n", - "37. **无论使用何种编码风格,如果你的团队(甚至更好是公司)对其进行标准化,它就确实会产生重大影响**。这意味着,如果不符合这个标准,那么每个人都认为修复别人的编码风格是公平的游戏。标准化的价值在于解析代码可以花费较少的脑力,因此可以更专注于代码的含义。\n", - "\n", - "38. **遵循标准的大写规则**。类名的第一个字母大写。字段,方法和对象(引用)的第一个字母应为小写。所有标识符应该将各个单词组合在一起,并将所有中间单词的首字母大写。例如:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "- **ThisIsAClassName**\n", - "- **thisIsAMethodOrFieldName**\n", - "\n", - "将 **static final** 类型的标识符的所有字母全部大写,并用下划线分隔各个单词,这些标识符在其定义中具有常量初始值。这表明它们是编译时常量。\n", - "- **包是一个特例**,它们都是小写的字母,即使是中间词。域扩展(com,org,net,edu等)也应该是小写的。这是Java 1.1和Java 2之间的变化。" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "39. **不要创建自己的“装饰”私有字段名称**。这通常以前置下划线和字符的形式出现。匈牙利命名法(译者注:一种命名规范,基本原则是:变量名=属性+类型+对象描述。Win32程序风格采用这种命名法,如`WORD wParam1;LONG lParam2;HANDLE hInstance`)是最糟糕的例子,你可以在其中附加额外字符用于指示数据类型,用途,位置等,就好像你正在编写汇编语言一样,编译器根本没有提供额外的帮助。这些符号令人困惑,难以阅读,并且难以执行和维护。让类和包来指定名称范围。如果认为必须装饰名称以防止混淆,那么代码就可能过于混乱,这应该被简化。\n", - "\n", - "40. 在创建一般用途的类时,**遵循“规范形式”**。包括**equals()**,**hashCode()**,**toString()**,**clone()**的定义(实现**Cloneable**,或选择其他一些对象复制方法,如序列化),并实现**Comparable**和**Serializable**。\n", - "\n", - "41. **对读取和更改私有字段的方法使用“get”,“set”和“is”命名约定**。这种做法不仅使类易于使用,而且也是命名这些方法的标准方法,因此读者更容易理解。\n", - "\n", - "42. **对于所创建的每个类,请包含该类的JUnit测试**(请参阅*junit.org*以及[第十六章:代码校验]()中的示例)。无需删除测试代码即可在项目中使用该类,如果进行更改,则可以轻松地重新运行测试。测试代码也能成为如何使用这个类的示例。\n", - "\n", - "43. **有时需要继承才能访问基类的protected成员**。这可能导致对多种基类型的感知需求(perceived need)。如果不需要向上转型,则可以首先派生一个新类来执行受保护的访问。然后把该新类作为使用它的任何类中的成员对象,以此来代替直接继承。\n", - "\n", - "44. **为了提高效率,避免使用*final*方法**。只有在分析后发现方法调用是瓶颈时,才将**final**用于此目的。\n", - "\n", - "45. **如果两个类以某种功能方式相互关联(例如集合和迭代器),则尝试使一个类成为另一个类的内部类**。这不仅强调了类之间的关联,而且通过将类嵌套在另一个类中,可以允许在单个包中重用类名。Java集合库通过在每个集合类中定义内部**Iterator**类来实现此目的,从而为集合提供通用接口。使用内部类的另一个原因是作为**私有**实现的一部分。这里,内部类将有利于实现隐藏,而不是上面提到的类关联和防止命名空间污染。\n", - "\n", - "46. **只要你注意到类似乎彼此之间具有高耦合,请考虑如果使用内部类可能获得的编码和维护改进**。内部类的使用不会解耦类,而是明确耦合关系,并且更方便。\n", - "\n", - "47. **不要成为过早优化的牺牲品**。过早优化是很疯狂的行为。特别是,不要担心编写(或避免)本机方法(native methods),将某些方法设置为**final**,或者在首次构建系统时调整代码以使其高效。你的主要目标应该是验证设计。即使设计需要一定的效率,也*先让它工作,然后再让它变快*。\n", - "\n", - "48. **保持作用域尽可能小,以便能见度和对象的寿命尽可能小**。这减少了在错误的上下文中使用对象并隐藏了难以发现的bug的机会。例如,假设有一个集合和一段迭代它的代码。如果复制该代码以用于一个新集合,那么可能会意外地将旧集合的大小用作新集合的迭代上限。但是,如果旧集合比较大,则会在编译时捕获错误。\n", - "\n", - "49. **使用标准Java库中的集合**。熟练使用它们,将会大大提高工作效率。首选**ArrayList**用于序列,**HashSet**用于集合,**HashMap**用于关联数组,**LinkedList**用于堆栈(而不是**Stack**,尽管也可以创建一个适配器来提供堆栈接口)和队列(也可以使用适配器,如本书所示)。当使用前三个时,将其分别向上转型为**List**,**Set**和**Map**,那么就可以根据需要轻松更改为其他实现。\n", - "\n", - "50. **为使整个程序健壮,每个组件必须健壮**。在所创建的每个类中,使用Java所提供的所有工具,如访问控制,异常,类型检查,同步等。这样,就可以在构建系统时安全地进入下一级抽象。\n", - "\n", - "51. **编译时错误优于运行时错误**。尝试尽可能在错误发生点处理错误。在最近的处理程序中尽其所能地捕获它能处理的所有异常。在当前层面处理所能处理的所有异常,如果解决不了,就重新抛出异常。 \n", - "\n", - "52. **注意长方法定义**。方法应该是简短的功能单元,用于描述和实现类接口的离散部分。维护一个冗长而复杂的方法是很困难的,而且代价很大,并且这个方法可能是试图做了太多事情。如果看到这样的方法,这表明,至少应该将它分解为多种方法。也可能建议去创建一个新类。小的方法也可以促进类重用。(有时方法必须很大,但它们应该只做一件事。)\n", - "\n", - "53. **保持“尽可能私有”**。一旦公开了你的类库中的一个方面(一个方法,一个类,一个字段),你就永远无法把它拿回来。如果这样做,就将破坏某些人的现有代码,迫使他们重写和重新设计。如果你只公开了必须公开的内容,就可以轻易地改变其他一切,而不会对其他人造成影响,而且由于设计趋于发展,这是一个重要的自由。通过这种方式,更改具体实现将对派生类造成的影响最小。在处理多线程时,私有尤其重要,只有**私有**字段可以防止不同步使用。具有包访问权限的类应该仍然具有**私有**字段,但通常有必要提供包访问权限的方法而不是将它们**公开**。\n", - "\n", - "54. **大量使用注释,并使用*Javadoc commentdocumentation*语法生成程序文档**。但是,注释应该为代码增加真正的意义,如果注释只是重申了代码已经清楚表达的内容,这是令人讨厌的。请注意,Java类和方法名称的典型详细信息减少了对某些注释的需求。\n", - "\n", - "55. **避免使用“魔法数字”**。这些是指硬编码到代码中的数字。如果后续必须要更改它们,那将是一场噩梦,因为你永远不知道“100”是指“数组大小”还是“完全不同的东西”。相反,创建一个带有描述性名称的常量并在整个程序中使用常量标识符。这使程序更易于理解,更易于维护。\n", - "\n", - "56. **在创建构造方法时,请考虑异常**。最好的情况是,构造方法不会做任何抛出异常的事情。次一级的最佳方案是,该类仅由健壮的类组成或继承自健壮的类,因此如果抛出异常则不需要处理。否则,必须清除**finally**子句中的组合类。如果构造方法必然失败,则适当的操作是抛出异常,因此调用者不会认为该对象是正确创建的而盲目地继续下去。\n", - "\n", - "57. **在构造方法内部,只需要将对象设置为正确的状态**。主动避免调用其他方法(**final**方法除外),因为这些方法可以被其他人覆盖,从而在构造期间产生意外结果。(有关详细信息,请参阅[第六章:初始化和清理]()章节。)较小,较简单的构造方法不太可能抛出异常或导致问题。\n", - "\n", - "58. **如果类在客户端程序员用完对象时需要进行任何清理,请将清理代码放在一个明确定义的方法中**,并使用像 **dispose()** 这样的名称来清楚地表明其目的。另外,在类中放置一个 **boolean** 标志来指示是否调用了 **dispose()** ,因此 **finalize()** 可以检查“终止条件”(参见[第六章:初始化和清理]()章节)。\n", - "\n", - "59. ***finalize()* 的职责只能是验证对象的“终止条件”以进行调试**。(参见[第六章:初始化和清理]()一章)在特殊情况下,可能需要释放垃圾收集器无法释放的内存。因为可能无法为对象调用垃圾收集器,所以无法使用 **finalize()** 执行必要的清理。为此,必须创建自己的 **dispose()** 方法。在类的 **finalize()** 方法中,检查以确保对象已被清理,如果没有被清理,则抛出一个派生自**RuntimeException**的异常,以指示编程错误。在依赖这样的计划之前,请确保 **finalize()** 适用于你的系统。(可能需要调用 **System.gc()** 来确保此行为。)\n", - "\n", - "60. **如果必须在特定范围内清理对象(除了通过垃圾收集器),请使用以下准则:** 初始化对象,如果成功,立即进入一个带有 **finally** 子句的 **try** 块,并在 **finally**中执行清理操作。\n", - "\n", - "61. **在继承期间覆盖 *finalize()* 时,记得调用 *super.finalize()***。(如果是直接继承自 **Object** 则不需要这样做。)调用 **super.finalize()** 作为重写的 **finalize()** 的最终行为而不是在第一行调用它,这样可以确保基类组件在需要时仍然有效。\n", - "\n", - "62. **创建固定大小的对象集合时,将它们转换为数组,** 尤其是在从方法中返回此集合时。这样就可以获得数组编译时类型检查的好处,并且数组的接收者可能不需要在数组中强制转换对象来使用它们。请注意,集合库的基类 **java.util.Collection** 有两个 **toArray()** 方法来完成此任务。\n", - "\n", - "63. **优先选择 *接口* 而不是 *抽象类***。如果知道某些东西应该是基类,那么第一选择应该是使其成为一个接口,并且只有在需要方法定义或成员变量时才将其更改为抽象类。一个接口关心客户端想要做什么,而一个类倾向于关注(或允许)实现细节。\n", - "\n", - "64. **为了避免非常令人沮丧的经历,请确保类路径中的每个名称只对应一个不在包中的类**。否则,编译器可以首先找到具有相同名称的其他类,并报告没有意义的错误消息。如果你怀疑自己有类路径问题,请尝试在类路径的每个起始点查找具有相同名称的 **.class** 文件。理想情况下,应该将所有类放在包中。\n", - "\n", - "65. **注意意外重载**。如果尝试覆盖基类方法但是拼写错误,则最终会添加新方法而不是覆盖现有方法。但是,这是完全合法的,因此在编译时或运行时不会获得任何错误消息,但代码将无法正常工作。始终使用 **@Override** 注释来防止这种情况。\n", - "\n", - "66. **注意过早优化**。先让它工作,然后再让它变快。除非发现代码的特定部分存在性能瓶颈。除非是使用分析器发现瓶颈,否则过早优化会浪费时间。性能调整所隐藏的额外成本是代码将变得难以理解和维护。\n", - "\n", - "67. **请注意,相比于编写代码,代码被阅读的机会更多**。清晰的设计可能产生易于理解的程序,但注释,详细解释,测试和示例是非常宝贵的,它们可以帮助你和你的所有后继者。如果不出意外,试图从JDK文档中找出有用信息的挫败感应该可以说服你。\n", - "\n", - "[^1]: Andrew Koenig向我解释了它。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Standard-IO.ipynb b/jupyter/Appendix-Standard-IO.ipynb deleted file mode 100644 index 493d055f..00000000 --- a/jupyter/Appendix-Standard-IO.ipynb +++ /dev/null @@ -1,335 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:标准IO\n", - "\n", - ">*标准 I/O*这个术语参考Unix中的概念,指程序所使用的单一信息流(这种思想在大多数操作系统中,也有相似形式的实现)。\n", - "\n", - "程序的所有输入都可以来自于*标准输入*,其所有输出都可以流向*标准输出*,并且其所有错误信息均可以发送到*标准错误*。*标准 I/O* 的意义在于程序之间可以很容易地连接起来,一个程序的标准输出可以作为另一个程序的标准输入。这是一个非常强大的工具。\n", - "\n", - "## 从标准输入中读取\n", - "\n", - "遵循标准 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` 之前必须先对其进行包装。\n", - "\n", - "我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader` 把 `System.in` 转换[^2]成 `Reader` 。下面这个例子将键入的每一行显示出来:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// standardio/Echo.java\n", - "// How to read from standard input\n", - "import java.io.*;\n", - "import onjava.TimedAbort;\n", - "\n", - "public class Echo {\n", - " public static void main(String[] args) {\n", - " TimedAbort abort = new TimedAbort(2);\n", - " new BufferedReader(\n", - " new InputStreamReader(System.in))\n", - " .lines()\n", - " .peek(ln -> abort.restart())\n", - " .forEach(System.out::println);\n", - " // Ctrl-Z or two seconds inactivity\n", - " // terminates the program\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。\n", - "\n", - "## 将`System.out` 转换成 `PrintWriter`\n", - "\n", - "`System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// standardio/ChangeSystemOut.java\n", - "// Turn System.out into a PrintWriter\n", - "\n", - "import java.io.*;\n", - "\n", - "public class ChangeSystemOut {\n", - " public static void main(String[] args) {\n", - " PrintWriter out =\n", - " new PrintWriter(System.out, true);\n", - " out.println(\"Hello, world\");\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "输出结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Hello, world" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "要使用 `PrintWriter` 带有两个参数的构造器,并设置第二个参数为 `true`,从而使能自动刷新到输出缓冲区的功能;否则,可能无法看到打印输出。\n", - "\n", - "## 重定向标准 I/O\n", - "\n", - "Java的 `System` 类提供了简单的 `static` 方法调用,从而能够重定向标准输入流、标准输出流和标准错误流:\n", - "- setIn(InputStream)\n", - "- setOut(PrintStream)\n", - "- setErr(PrintStream)\n", - "\n", - "如果我们突然需要在显示器上创建大量的输出,而这些输出滚动的速度太快以至于无法阅读时,重定向输出就显得格外有用,可把输出内容重定向到文件中供后续查看。对于我们想重复测试特定的用户输入序列的命令行程序来说,重定向输入就很有价值。下例简单演示了这些方法的使用:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// standardio/Redirecting.java\n", - "// Demonstrates standard I/O redirection\n", - "import java.io.*;\n", - "\n", - "public class Redirecting {\n", - " public static void main(String[] args) {\n", - " PrintStream console = System.out;\n", - " try (\n", - " BufferedInputStream in = new BufferedInputStream(\n", - " new FileInputStream(\"Redirecting.java\"));\n", - " PrintStream out = new PrintStream(\n", - " new BufferedOutputStream(\n", - " new FileOutputStream(\"Redirecting.txt\")))\n", - " ) {\n", - " System.setIn(in);\n", - " System.setOut(out);\n", - " System.setErr(out);\n", - " new BufferedReader(\n", - " new InputStreamReader(System.in))\n", - " .lines()\n", - " .forEach(System.out::println);\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " } finally {\n", - " System.setOut(console);\n", - " }\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "该程序将文件中内容载入到标准输入,并把标准输出和标准错误重定向到另一个文件。它在程序的开始保存了最初对 `System.out` 对象的引用,并且在程序结束时将系统输出恢复到了该对象上。\n", - "\n", - "I/O重定向操作的是字节流而不是字符流,因此使用 `InputStream` 和 `OutputStream`,而不是 `Reader` 和 `Writer`。\n", - "\n", - "\n", - "## 执行控制\n", - "\n", - "你经常需要在Java内部直接执行操作系统的程序,并控制这些程序的输入输出,Java类库提供了执行这些操作的类。\n", - "\n", - "一项常见的任务是运行程序并将输出结果发送到控制台。本节包含了一个可以简化此任务的实用工具。\n", - "\n", - "在使用这个工具时可能会产生两种类型的错误:导致异常的普通错误——对于这些错误我们只需要重新抛出一个 `RuntimeException` 即可,以及进程自身的执行过程中导致的错误——我们需要用单独的异常来报告这些错误:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/OSExecuteException.java\n", - "package onjava;\n", - "\n", - "public class OSExecuteException extends RuntimeException {\n", - " public OSExecuteException(String why) {\n", - " super(why);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了运行程序,我们需要传递给 `OSExecute.command()` 一个 `String command`,我们可以在控制台键入同样的指令运行程序。该命令传递给 `java.lang.ProcessBuilder` 的构造器(需要将其作为 `String` 对象的序列),然后启动生成的 `ProcessBuilder` 对象。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// onjava/OSExecute.java\n", - "// Run an operating system command\n", - "// and send the output to the console\n", - "package onjava;\n", - "import java.io.*;\n", - "\n", - "public class OSExecute {\n", - " public static void command(String command) {\n", - " boolean err = false;\n", - " try {\n", - " Process process = new ProcessBuilder(\n", - " command.split(\" \")).start();\n", - " try (\n", - " BufferedReader results = new BufferedReader(\n", - " new InputStreamReader(\n", - " process.getInputStream()));\n", - " BufferedReader errors = new BufferedReader(\n", - " new InputStreamReader(\n", - " process.getErrorStream()))\n", - " ) {\n", - " results.lines()\n", - " .forEach(System.out::println);\n", - " err = errors.lines()\n", - " .peek(System.err::println)\n", - " .count() > 0;\n", - " }\n", - " } catch (IOException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " if (err)\n", - " throw new OSExecuteException(\n", - " \"Errors executing \" + command);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "为了捕获在程序执行时产生的标准输出流,我们可以调用 `getInputStream()`。这是因为 `InputStream` 是我们可以从中读取信息的流。\n", - "\n", - "这里这些行只是被打印了出来,但是你也可以从 `command()` 捕获和返回它们。\n", - "\n", - "该程序的错误被发送到了标准错误流,可以调用 `getErrorStream()` 捕获。如果存在任何错误,它们都会被打印并且抛出 `OSExcuteException` ,以便调用程序处理这个问题。\n", - "\n", - "下面是展示如何使用 `OSExecute` 的示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// standardio/OSExecuteDemo.java\n", - "// Demonstrates standard I/O redirection\n", - "// {javap -cp build/classes/main OSExecuteDemo}\n", - "import onjava.*;\n", - "\n", - "public class OSExecuteDemo {}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里使用 `javap` 反编译器(随JDK发布)来反编译程序,编译结果:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Compiled from \"OSExecuteDemo.java\"\n", - "public class OSExecuteDemo {\n", - " public OSExecuteDemo();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[^1]: 译者注:这里用到了**装饰器模式**。\n", - "\n", - "[^2]: 译者注:这里用到了**适配器模式**。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Supplements.ipynb b/jupyter/Appendix-Supplements.ipynb deleted file mode 100644 index 71c9d00d..00000000 --- a/jupyter/Appendix-Supplements.ipynb +++ /dev/null @@ -1,39 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:补充\n", - "\n", - "> 本书有许多补充内容,包括MindView网站提供的项目和服务。\n", - "\n", - "本附录介绍了这些补充内容,你可以自行决定它们是否对你有所帮助。\n", - "\n", - "\n", - "## 可下载的补充\n", - "\n", - "可以从 [https://github.com/BruceEckel/OnJava8-examples](https://github.com/BruceEckel/OnJava8-examples) 免费下载本书的代码。这里包括Gradle构建文件和其它一些必要的支持文件,以便成功构建和执行本书中所有的示例代码。\n", - "\n", - "\n", - "## 通过Thinking-in-C来巩固Java基础\n", - "\n", - "在 [www.OnJava8.com](www.OnJava8.com) 上,可以免费下载*Thinking in C*的演示文稿。 此演示文稿由Chuck Allison创建,由MindView有限责任公司开发。这是一个电子演示文稿,介绍了Java语法所基于的C语法,运算符和函数。\n", - "\n", - "\n", - "## Hand-On Java 电子演示文稿\n", - "\n", - "*Hand-On Java 电子演示文稿*(Hands-On Java eSeminar)是基于*Thinking in Java*第2版。对应于该书中的每一章,它附带有一个音频讲解和相应的幻灯片。我创建了这个电子演示文稿,并讲述了这些材料。这个资料是HTML5格式的,所以它应该可以在大多数现代浏览器上运行。该演示文稿将在[www.OnJava8.com](www.OnJava8.com)上发售,你可以在该网站上找到该产品的试用版演示。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb b/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb deleted file mode 100644 index ac347181..00000000 --- a/jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb +++ /dev/null @@ -1,44 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "# 附录:C++和Java的优良传统\n", - "\n", - "> 在各种讨论声中,有一些人认为C++是一种设计糟糕的语言。 我认为理解C++和Java语言的选择有助于了解更大的视角。\n", - "\n", - "也就是说,我几乎不再使用C++了。当我使用它的时候,要么是用来检查遗留代码,要么是编写性能关键(performance-critical)部分,程序通常尽可能小,以便用其他语言编写的其他程序来调用。\n", - "\n", - "因为我在最初的8年里一直在C++标准委员会工作,所以我见证了那些被做出的决定。它们都经过了极其谨慎的考虑,远远超过了许多在Java中做出的决定。\n", - "\n", - "然而,正如人们正确地指出的那样,由此产生的语言使用起来既复杂又痛苦,而且只要我一段时间不使用它,我就会忘记那些古怪的规则。在我写书的时候,我是从第一原理(first principles)处了解这些规则的,而不是记住了它们。\n", - "\n", - "为了理解C++语言为何既令人不愉快且复杂,同时又是精心设计的,必须要牢记C++中所有内容的主要设计决策:与C. Bjarne Stroustrup(该语言的创造者,即“C++之父”)的兼容性决定。这样的设计似乎是为了可以让大量的C程序员透明地转移到对象(代指C++)上:允许他们在C++下编译他们的C代码。这是一个巨大的限制,一直是C++最大的优势......而且也是它的祸根。这就是使得C++成功的原因,也是使它复杂的原因。\n", - "\n", - "它也欺骗了那些不太了解C++的Java设计师。例如,他们认为运算符重载对于程序员来说很难正确使用。这在C++中基本上是正确的,因为C++既有栈分配又有堆分配,你必须重载运算符来处理所有情况而且不要造成内存泄漏。这确实很困难。然而,Java有单一的内存分配机制和一个垃圾收集器,这使得运算符重载变得微不足道,正如C#中那样(但在早于Java的Python中已经可以看到)。但多年来,来自Java团队的一贯态度是“运算符重载太复杂了”。这里还有许多决策,所做的事明显不应该是他们做的。正是由于这些原因,让我有了蔑视Gosling(即“Java之父”)和Java团队决策的名声。(Java 7和8由于某种原因包含了更好的决策。但是向后兼容性这个约束总是会阻碍真正的改进。语言永远不会是它本来的样子。)\n", - "\n", - "还有很多其他的例子。“为了提高效率,必须包含基本类型”;坚持“万物皆对象”是正确的;当对性能有要求的时候,提供一个陷阱门(trap door)来做低级别的活动(lower-level activities)(这里也可以使用hotspot技术透明地提高性能,正如他们最终做的那样);不能直接使用浮点处理器去计算超越函数,它用软件来完成。我已经尽可能多地提出了这样的问题,但我得到的却一直是类似“这是Java方式”这样的回复。\n", - "\n", - "当我提出关于泛型的设计有多糟糕的时候,我得到了相同的回复,以及“我们必须向后兼容那样以前用Java做出的决策”(即使它们是糟糕的决策)。最近越来越多的人已经获得了足够的泛型经验,可以发现泛型真的很难用。事实上,C++模板更强大、更一致(现在更容易使用,因为编译器的错误消息是可以容忍的)。人们一直在认真对待物化(reification),这可能是有用的东西,但是在那种被严格约束所削弱的设计中并没有多大影响。\n", - "\n", - "这样的例子还有很多很多。这是否意味着Java失败了?绝对不。Java将程序员的主流带入了垃圾收集、虚拟机和一致的错误处理模型的世界。由于它的所有缺陷,它将我们提升到了一个水平,现在我们已经准备好使用更高级别的语言了。\n", - "\n", - "有一点,C++是领先的语言,人们认为它总是如此。许多人对Java有同样的看法,但由于JVM,Java使得取代自己变得更加容易。现在有可能会有人创建一种新语言,并使其在短时间内像Java一样高效运行。以前,为新语言开发一个正确有效的编译器需要花费大部分开发时间。\n", - "\n", - "这种情况已经发生了,包括像Scala这样的高级静态语言,以及动态语言,新的且可移植的,如Groovy,Clojure,JRuby和Jython。这是未来,并且过渡很顺畅,因为可以很轻易地将这些新语言与现有Java代码结合使用,并且必要时可以重写那些在Java中的瓶颈。\n", - "\n", - "在撰写本文时,Java是世界上的头号编程语言。然而,Java最终将会减弱,就像C++一样,沦只在特殊情况下使用(或者只是用来支持传统的代码,因为它不能像C++那样和硬件连接)。但是无意中的好处,也是Java真正意外的光彩之处在于它为自己的替代品创造了一条非常畅通的道路,即使Java本身已经达到了无法再发展的程度。未来所有的语言都应该从中学习:要么创建一个可以重构的文化(像Python和Ruby做的那样),要么就让竞争者茁壮成长。\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb b/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb deleted file mode 100644 index 2f0ad1c4..00000000 --- a/jupyter/Appendix-Understanding-equals-and-hashCode.ipynb +++ /dev/null @@ -1,1340 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[TOC]\n", - "\n", - "\n", - "\n", - "\n", - "# 附录:理解equals和hashCode方法\n", - "假设有一个容器使用hash函数,当你创建一个放到这个容器时,你必须定义 **hashCode()** 函数和 **equals()** 函数。这两个函数一起被用于hash容器中的查询操作。\n", - "\n", - "\n", - "\n", - "## equals规范\n", - "当你创建一个类的时候,它自动继承自 **Objcet** 类。如果你不覆写 **equals()** ,你将会获得 **Objcet** 对象的 **equals()** 函数。默认情况下,这个函数会比较对象的地址。所以只有你在比较同一个对象的时候,你才会获得**true**。默认的情况是\"区分度最高的\"。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/DefaultComparison.java\n", - "class DefaultComparison {\n", - " private int i, j, k;\n", - " DefaultComparison(int i, int j, int k) {\n", - " this.i = i;\n", - " this.j = j;\n", - " this.k = k;\n", - " }\n", - " \n", - " public static void main(String[] args) {\n", - " DefaultComparison \n", - " a = new DefaultComparison(1, 2, 3),\n", - " b = new DefaultComparison(1, 2, 3);\n", - " System.out.println(a == a);\n", - " System.out.println(a == b);\n", - " } \n", - "} \n", - "/*\n", - "Output:\n", - "true\n", - "false\n", - "*/\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "通常你会希望放宽这个限制。一般来说如果两个对象有相同的类型和相同的字段,你会认为这两个对象相等,但也会有一些你不想加入 **equals()** 函数中来比较的字段。这是类型设计的一部分。\n", - "\n", - "一个合适的 **equals()**函数必须满足以下五点条件:\n", - "1. 反身性:对于任何 **x**, **x.equals(x)** 应该返回 **true**。\n", - "2. 对称性:对于任何 **x** 和 **y**, **x.equals(y)** 应该返回 **true**当且仅当 **y.equals(x)** 返回 **true** 。\n", - "3. 传递性:对于任何**x**,**y**,还有**z**,如果 **x.equals(y)** 返回 **true** 并且 **y.equals(z)** 返回 **true**,那么 **x.equals(z)** 应该返回 **true**。\n", - "4. 一致性:对于任何 **x**和**y**,在对象没有被改变的情况下,多次调用 **x.equals(y)** 应该总是返回 **true** 或者**false**。\n", - "5. 对于任何非**null**的**x**,**x.equals(null)**应该返回**false**。\n", - "\n", - "下面是满足这些条件的测试,并且判断对象是否和自己相等(我们这里称呼其为**右值**):\n", - "1. 如果**右值**是**null**,那么不相等。\n", - "2. 如果**右值**是**this**,那么两个对象相等。\n", - "3. 如果**右值**不是同一个类型或者子类,那么两个对象不相等。\n", - "4. 如果所有上面的检查通过了,那么你必须决定 **右值** 中的哪些字段是重要的,然后比较这些字段。\n", - "Java 7 引入了 **Objects** 类型来帮助这个流程,这样我们能够写出更好的 **equals()** 函数。\n", - "\n", - "下面的例子比较了不同类型的 **Equality**类。为了避免重复的代码,我们使用*工厂函数设计模*式来实现样例。 **EqualityFactory**接口提供**make()**函数来生成一个**Equaity**对象,这样不同的**EqualityFactory**能够生成**Equality**不同的子类。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/EqualityFactory.java\n", - "import java.util.*;\n", - "interface EqualityFactory {\n", - " Equality make(int i, String s, double d);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "现在我们来定义 **Equality**,它包含三个字段(所有的字段我们认为在比较中都很重要)和一个 **equals()** 函数用来满足上述的四种检查。构造函数展示了它的类名来保证我们在执行我们想要的测试:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/Equality.java\n", - "import java.util.*;\n", - "public class Equality {\n", - " protected int i;\n", - " protected String s;\n", - " protected double d;public Equality(int i, String s, double d) {\n", - " this.i = i;\n", - " this.s = s;\n", - " this.d = d;\n", - " System.out.println(\"made 'Equality'\");\n", - " } \n", - " \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " if(rval == null)\n", - " return false;\n", - " if(rval == this)\n", - " return true;\n", - " if(!(rval instanceof Equality))\n", - " return false;\n", - " Equality other = (Equality)rval;\n", - " if(!Objects.equals(i, other.i))\n", - " return false;\n", - " if(!Objects.equals(s, other.s))\n", - " return false;\n", - " if(!Objects.equals(d, other.d))return false;\n", - " return true;\n", - " } \n", - " \n", - " public void test(String descr, String expected, Object rval) {\n", - " System.out.format(\"-- Testing %s --%n\" + \"%s instanceof Equality: %s%n\" +\n", - " \"Expected %s, got %s%n\",\n", - " descr, descr, rval instanceof Equality,\n", - " expected, equals(rval));\n", - " } \n", - " \n", - " public static void testAll(EqualityFactory eqf) {\n", - " Equality\n", - " e = eqf.make(1, \"Monty\", 3.14),\n", - " eq = eqf.make(1, \"Monty\", 3.14),\n", - " neq = eqf.make(99, \"Bob\", 1.618);\n", - " e.test(\"null\", \"false\", null);\n", - " e.test(\"same object\", \"true\", e);\n", - " e.test(\"different type\",\n", - " \"false\", Integer.valueOf(99));e.test(\"same values\", \"true\", eq);\n", - " e.test(\"different values\", \"false\", neq);\n", - " } \n", - " \n", - " public static void main(String[] args) {\n", - " testAll( (i, s, d) -> new Equality(i, s, d));\n", - " } \n", - " \n", - "} \n", - "/*\n", - "Output:\n", - "made 'Equality'\n", - "made 'Equality'\n", - "made 'Equality'\n", - "-- Testing null --\n", - "null instanceof Equality: false\n", - "Expected false, got false\n", - "-- Testing same object --\n", - "same object instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different type --\n", - "different type instanceof Equality: false\n", - "Expected false, got false-- Testing same values --\n", - "same values instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different values --\n", - "different values instanceof Equality: true\n", - "Expected false, got false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**testAll()** 执行了我们期望的所有不同类型对象的比较。它使用工厂创建了**Equality**对象。\n", - "\n", - "在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示**make()**函数。\n", - "\n", - "上述的 **equals()** 函数非常繁琐,并且我们能够将其简化成规范的形式,请注意:\n", - "1. **instanceof**检查减少了**null**检查的需要。\n", - "2. 和**this**的比较是多余的。一个正确书写的 **equals()** 函数能正确地和自己比较。\n", - "\n", - "\n", - "因为 **&&** 是一个短路比较,它会在第一次遇到失败的时候退出并返回**false**。所以,通过使用 **&&** 将检查链接起来,我们可以写出更精简的 **equals()** 函数:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SuccinctEquality.java\n", - "import java.util.*;\n", - "public class SuccinctEquality extends Equality {\n", - " public SuccinctEquality(int i, String s, double d) {\n", - " super(i, s, d);\n", - " System.out.println(\"made 'SuccinctEquality'\");\n", - " } \n", - " \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof SuccinctEquality &&\n", - " Objects.equals(i, ((SuccinctEquality)rval).i) &&\n", - " Objects.equals(s, ((SuccinctEquality)rval).s) &&\n", - " Objects.equals(d, ((SuccinctEquality)rval).d);\n", - " } \n", - " public static void main(String[] args) {\n", - " Equality.testAll( (i, s, d) ->\n", - " new SuccinctEquality(i, s, d));\n", - " } \n", - " \n", - "}\n", - "/* Output:\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "-- Testing null --\n", - "null instanceof Equality: false\n", - "Expected false, got false\n", - "-- Testing same object --\n", - "same object instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different type --\n", - "different type instanceof Equality: false\n", - "Expected false, got false\n", - "-- Testing same values --\n", - "same values instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different values --different values instanceof Equality: true\n", - "Expected false, got false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于每个 **SuccinctEquality**,基类构造函数在派生类构造函数前被调用,输出显示我们依然获得了正确的结果,你可以发现短路返回已经发生了,不然的话,**null**测试和“不同类型”的测试会在 **equals()** 函数下面的比较中强制转化的时候抛出异常。\n", - " **Objects.equals()** 会在你组合其他类型的时候发挥很大的作用。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/ComposedEquality.java\n", - "import java.util.*;\n", - "class Part {\n", - " String ss;\n", - " double dd;\n", - " \n", - " Part(String ss, double dd) {\n", - " this.ss = ss;\n", - " this.dd = dd;\n", - " }\n", - " \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof Part &&\n", - " Objects.equals(ss, ((Part)rval).ss) &&\n", - " Objects.equals(dd, ((Part)rval).dd);\n", - " } \n", - " \n", - "} \n", - " \n", - "public class ComposedEquality extends SuccinctEquality {\n", - " Part part;\n", - " public ComposedEquality(int i, String s, double d) {\n", - " super(i, s, d);\n", - " part = new Part(s, d);\n", - " System.out.println(\"made 'ComposedEquality'\");\n", - " }\n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof ComposedEquality &&\n", - " super.equals(rval) &&\n", - " Objects.equals(part,\n", - " ((ComposedEquality)rval).part);\n", - " \n", - " } \n", - " \n", - " public static void main(String[] args) {\n", - " Equality.testAll( (i, s, d) ->\n", - " new ComposedEquality(i, s, d));\n", - " }\n", - "}\n", - "/*\n", - "Output:\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "made 'ComposedEquality'\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "made 'ComposedEquality'\n", - "made 'Equality'\n", - "made 'SuccinctEquality'\n", - "made 'ComposedEquality'\n", - "-- Testing null --null instanceof Equality: false\n", - "Expected false, got false\n", - "-- Testing same object --\n", - "same object instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different type --\n", - "different type instanceof Equality: false\n", - "Expected false, got false\n", - "-- Testing same values --\n", - "same values instanceof Equality: true\n", - "Expected true, got true\n", - "-- Testing different values --\n", - "different values instanceof Equality: true\n", - "Expected false, got false\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意super.equals()这个调用,没有必要重新发明它(因为你不总是有权限访问基类所有的必要字段)\n", - "\n", - "\n", - "### 不同子类的相等性\n", - "继承意味着两个不同子类的对象当其向上转型的时候可以是相等的。假设你有一个Animal对象的集合。这个集合天然接受**Animal**的子类。在这个例子中是**Dog**和**Pig**。每个**Animal**有一个**name**和**size**,还有唯一的内部**id**数字。\n", - "\n", - "我们通过**Objects**类,以规范的形式定义 **equals()**函数和**hashCode()**。但是我们只能在基类**Animal**中定义他们。并且我们在这两个函数中没有包含**id**字段。从**equals()**函数的角度看待,这意味着我们只关心它是否是**Animal**,而不关心是否是**Animal**的某个子类。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SubtypeEquality.java\n", - "import java.util.*;\n", - "enum Size { SMALL, MEDIUM, LARGE }\n", - "class Animal {\n", - " private static int counter = 0;\n", - " private final int id = counter++;\n", - " private final String name;\n", - " private final Size size;\n", - " Animal(String name, Size size) {\n", - " this.name = name;\n", - " this.size = size;\n", - " } \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof Animal &&\n", - " // Objects.equals(id, ((Animal)rval).id) && // [1]\n", - " Objects.equals(name, ((Animal)rval).name) &&\n", - " Objects.equals(size, ((Animal)rval).size);\n", - " } \n", - " \n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hash(name, size);\n", - " // return Objects.hash(name, size, id); // [2]\n", - " } \n", - " \n", - " @Override\n", - " public String toString() {\n", - " return String.format(\"%s[%d]: %s %s %x\",\n", - " getClass().getSimpleName(), id,\n", - " name, size, hashCode());\n", - " } \n", - "} \n", - " \n", - "class Dog extends Animal {\n", - " Dog(String name, Size size) {\n", - " super(name, size);\n", - " } \n", - "} \n", - "\n", - "class Pig extends Animal {\n", - " Pig(String name, Size size) {\n", - " super(name, size);\n", - " } \n", - "} \n", - " \n", - "public class SubtypeEquality {\n", - " public static void main(String[] args) {\n", - " Set pets = new HashSet<>();\n", - " pets.add(new Dog(\"Ralph\", Size.MEDIUM));\n", - " pets.add(new Pig(\"Ralph\", Size.MEDIUM));\n", - " pets.forEach(System.out::println);\n", - " } \n", - "} \n", - "/*\n", - "Output:\n", - "Dog[0]: Ralph MEDIUM a752aeee\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "如果我们只考虑类型的话,某些情况下它的确说得通——只从基类的角度看待问题,这是李氏替换原则的基石。这个代码完美符合替换理论因为派生类没有添加任何额外不再基类中的额外函数。派生类只是在表现上不同,而不是在接口上。(当然这不是常态)\n", - "\n", - "但是当我们提供了两个有着相同数据的不同的对象类型,然后将他们放置在 **HashSet** 中。只有他们中的一个能存活。这强调了 **equals()** 不是完美的数学理论,而只是机械般的理论。\n", - " **hashCode()** 和 **equals()** 必须能够允许类型在hash数据结构中正常工作。例子中 **Dog** 和 **Pig** 会被映射到同 **HashSet** 的同一个桶中。这个时候,**HashSet** 回退到 **equals()** 来区分对象,但是 **equals()** 也认为两个对象是相同的。**HashSet**因为已经有一个相同的对象了,所以没有添加 **Pig**。\n", - "我们依然能够通过使得其他字段对象不同来让例子能够正常工作。在这里每个 **Animal** 已经有了一个独一无二的 **id** ,所以你能够取消 **equals()** 函数中的 **[1]** 行注释,或者取消 **hashCode()** 函数中的 **[2]** 行注释。按照规范,你应该同时完成这两个操作,如此能够将所有“不变的”字段包含在两个操作中(“不变”所以 **equals()** 和 **hashCode()** 在哈希数据结构中的排序和取值时,不会生成不同的值。我将“不变的”放在引号中因为你必须计算出是否已经发生变化)。\n", - "\n", - "> **旁注**: 在**hashCode()**中,如果你只能够使用一个字段,使用**Objcets.hashCode()**。如果你使用多个字段,那么使用 **Objects.hash()**。\n", - "\n", - "我们也可以通过标准方式,将 **equals()** 定义在子类中(不包含 **id** )解决这个问题:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SubtypeEquality2.java\n", - "import java.util.*;\n", - "class Dog2 extends Animal {\n", - " Dog2(String name, Size size) {\n", - " super(name, size);\n", - " } \n", - " \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof Dog2 &&super.equals(rval);\n", - " } \n", - "} \n", - "\n", - "class Pig2 extends Animal {\n", - " Pig2(String name, Size size) {\n", - " super(name, size);\n", - " } \n", - " \n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof Pig2 &&\n", - " super.equals(rval);\n", - " } \n", - "}\n", - "\n", - "public class SubtypeEquality2 {\n", - " public static void main(String[] args) {\n", - " Set pets = new HashSet<>();\n", - " pets.add(new Dog2(\"Ralph\", Size.MEDIUM));\n", - " pets.add(new Pig2(\"Ralph\", Size.MEDIUM));\n", - " pets.forEach(System.out::println);\n", - " }\n", - "} \n", - "/*\n", - "Output:\n", - "Dog2[0]: Ralph MEDIUM a752aeee\n", - "Pig2[1]: Ralph MEDIUM a752aeee\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "注意 **hashCode()** 是独一无二的,但是因为对象不再 **equals()** ,所以两个函数都出现在**HashSet**中。另外,**super.equals()** 意味着我们不需要访问基类的**private**字段。\n", - "\n", - "\n", - "一种说法是Java从**equals()** 和**hashCode()** 的定义中分离了可替代性。我们仍然能够将**Dog**和**Pig**放置在 **Set\\** 中,无论 **equals()** 和 **hashCode()** 是如何定义的,但是对象不会在哈希数据结构中正常工作,除非这些函数能够被合理定义。不幸的是,**equals()** 不总是和 **hashCode()** 一起使用,这在你尝试为了某个特殊类型避免定义它的时候会让问题复杂化。并且这也是为什么遵循规范是有价值的。然而这会变得更加复杂,因为你不总是需要定义其中一个函数。\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "## 哈希和哈希码\n", - "\n", - "在 [集合]() 章节中,我们使用预先定义的类作为 HashMap 的键。这些示例之所以有用,是因为预定义的类具有所有必需的连线,以使它们正确地充当键。\n", - "\n", - "当创建自己的类作为HashMap的键时,会发生一个常见的陷阱,从而忘记进行必要的接线。例如,考虑一个将Earthhog 对象与 Prediction 对象匹配的天气预报系统。这似乎很简单:使用Groundhog作为键,使用Prediction作为值:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/Groundhog.java\n", - "// Looks plausible, but doesn't work as a HashMap key\n", - "public class Groundhog {\n", - " protected int number;\n", - " public Groundhog(int n) { number = n; }\n", - " @Override\n", - " public String toString() {\n", - " return \"Groundhog #\" + number;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/Prediction.java\n", - "// Predicting the weather\n", - "import java.util.*;\n", - "public class Prediction {\n", - " private static Random rand = new Random(47);\n", - " @Override\n", - " public String toString() {\n", - " return rand.nextBoolean() ?\n", - " \"Six more weeks of Winter!\" : \"Early Spring!\";\n", - " }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SpringDetector.java\n", - "// What will the weather be?\n", - "import java.util.*;\n", - "import java.util.stream.*;\n", - "import java.util.function.*;\n", - "import java.lang.reflect.*;\n", - "public class SpringDetector {\n", - " public static \n", - " void detectSpring(Class type) {\n", - " try {\n", - " Constructor ghog =\n", - " type.getConstructor(int.class);\n", - " Map map =\n", - " IntStream.range(0, 10)\n", - " .mapToObj(i -> {\n", - " try {\n", - " return ghog.newInstance(i);\n", - " } catch(Exception e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " })\n", - " .collect(Collectors.toMap(\n", - " Function.identity(),\n", - " gh -> new Prediction()));\n", - " map.forEach((k, v) ->\n", - " System.out.println(k + \": \" + v));\n", - " Groundhog gh = ghog.newInstance(3);\n", - " System.out.println(\n", - " \"Looking up prediction for \" + gh);\n", - " if(map.containsKey(gh))\n", - " System.out.println(map.get(gh));\n", - " else\n", - " System.out.println(\"Key not found: \" + gh);\n", - " } catch(NoSuchMethodException |\n", - " IllegalAccessException |\n", - " InvocationTargetException |\n", - " InstantiationException e) {\n", - " throw new RuntimeException(e);\n", - " }\n", - " }\n", - " public static void main(String[] args) {\n", - " detectSpring(Groundhog.class);\n", - " }\n", - "}\n", - "/* Output:\n", - "Groundhog #3: Six more weeks of Winter!\n", - "Groundhog #0: Early Spring!\n", - "Groundhog #8: Six more weeks of Winter!\n", - "Groundhog #6: Early Spring!\n", - "Groundhog #4: Early Spring!\n", - "Groundhog #2: Six more weeks of Winter!\n", - "Groundhog #1: Early Spring!\n", - "Groundhog #9: Early Spring!\n", - "Groundhog #5: Six more weeks of Winter!\n", - "Groundhog #7: Six more weeks of Winter!\n", - "Looking up prediction for Groundhog #3\n", - "Key not found: Groundhog #3\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "每个 Groundhog 都被赋予了一个常数,因此你可以通过如下的方式在 HashMap 中寻找对应的 Prediction。“给我一个和 Groundhog#3 相关联的 Prediction”。而 Prediction 通过一个随机生成的 boolean 来选择天气。`detectSpring()` 方法通过反射来实例化 Groundhog 类,或者它的子类。稍后,当我们继承一种新型的“Groundhog ”以解决此处演示的问题时,这将派上用场。\n", - "\n", - "这里的 HashMap 被 Groundhog 和其相关联的 Prediction 充满。并且上面展示了 HashMap 里面填充的内容。接下来我们使用填充了常数 3 的 Groundhog 作为 key 用于寻找对应的 Prediction 。(这个键值对肯定在 Map 中)。\n", - "\n", - "这看起来十分简单,但是这样做并没有奏效 —— 它无法找到数字3这个键。问题出在Groundhog自动地继承自基类Object,所以这里使用Object的hashCode0方法生成散列码,而它默认是使用对象的地址计算散列码。因此,由Groundhog(3)生成的第一个实例的散列码与由Groundhog(3)生成的第二个实例的散列码是不同的,而我们正是使用后者进行查找的。\n", - "\n", - "我们需要恰当的重写hashCode()方法。但是它仍然无法正常运行,除非你同时重写 equals()方法,它也是Object的一部分。HashMap使用equals()判断当前的键是否与表中存在的键相同。\n", - "\n", - "这是因为默认的Object.equals()只是比较对象的地址,所以一个Groundhog(3)并不等于另一个Groundhog(3),因此,如果要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals(),如下所示:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/Groundhog2.java\n", - "// A class that's used as a key in a HashMap\n", - "// must override hashCode() and equals()\n", - "import java.util.*;\n", - "public class Groundhog2 extends Groundhog {\n", - " public Groundhog2(int n) { super(n); }\n", - " @Override\n", - " public int hashCode() { return number; }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof Groundhog2 &&\n", - " Objects.equals(\n", - " number, ((Groundhog2)o).number);\n", - " }\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SpringDetector2.java\n", - "// A working key\n", - "public class SpringDetector2 {\n", - " public static void main(String[] args) {\n", - " SpringDetector.detectSpring(Groundhog2.class);\n", - " }\n", - "}\n", - "/* Output:\n", - "Groundhog #0: Six more weeks of Winter!\n", - "Groundhog #1: Early Spring!\n", - "Groundhog #2: Six more weeks of Winter!\n", - "Groundhog #3: Early Spring!\n", - "Groundhog #4: Early Spring!\n", - "Groundhog #5: Six more weeks of Winter!\n", - "Groundhog #6: Early Spring!\n", - "Groundhog #7: Early Spring!\n", - "Groundhog #8: Six more weeks of Winter!\n", - "Groundhog #9: Six more weeks of Winter!\n", - "Looking up prediction for Groundhog #3\n", - "Early Spring!\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Groundhog2.hashCode0返回Groundhog的标识数字(编号)作为散列码。在此例中,程序员负责确保不同的Groundhog具有不同的编号。hashCode()并不需要总是能够返回唯一的标识码(稍后你会理解其原因),但是equals() 方法必须严格地判断两个对象是否相同。此处的equals()是判断Groundhog的号码,所以作为HashMap中的键,如果两个Groundhog2对象具有相同的Groundhog编号,程序就出错了。\n", - "\n", - "如何定义 equals() 方法在上一节 [equals 规范]()中提到了。输出表明我们现在的输出是正确的。\n", - "\n", - "### 理解 hashCode\n", - "\n", - "前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖hashCode() 和equals() ,那么使用散列的数据结构(HashSet,HashMap,LinkedHashst或LinkedHashMap)就无法正确处理你的键。然而,要很好地解决此问题,你必须了解这些数据结构的内部构造。\n", - "\n", - "首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者你自己实现的Map也可以达到此目的。与散列实现相反,下面的示例用一对ArrayLists实现了一个Map,与AssociativeArray.java不同,这其中包含了Map接口的完整实现,因此提供了entrySet()方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SlowMap.java\n", - "// A Map implemented with ArrayLists\n", - "import java.util.*;\n", - "import onjava.*;\n", - "public class SlowMap extends AbstractMap {\n", - " private List keys = new ArrayList<>();\n", - " private List values = new ArrayList<>();\n", - " @Override\n", - " public V put(K key, V value) {\n", - " V oldValue = get(key); // The old value or null\n", - " if(!keys.contains(key)) {\n", - " keys.add(key);\n", - " values.add(value);\n", - " } else\n", - " values.set(keys.indexOf(key), value);\n", - " return oldValue;\n", - " }\n", - " @Override\n", - " public V get(Object key) { // key: type Object, not K\n", - " if(!keys.contains(key))\n", - " return null;\n", - " return values.get(keys.indexOf(key));\n", - " }\n", - " @Override\n", - " public Set> entrySet() {\n", - " Set> set= new HashSet<>();\n", - " Iterator ki = keys.iterator();\n", - " Iterator vi = values.iterator();\n", - " while(ki.hasNext())\n", - " set.add(new MapEntry<>(ki.next(), vi.next()));\n", - " return set;\n", - " }\n", - " public static void main(String[] args) {\n", - " SlowMap m= new SlowMap<>();\n", - " m.putAll(Countries.capitals(8));\n", - " m.forEach((k, v) ->\n", - " System.out.println(k + \"=\" + v));\n", - " System.out.println(m.get(\"BENIN\"));\n", - " m.entrySet().forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "CAMEROON=Yaounde\n", - "ANGOLA=Luanda\n", - "BURKINA FASO=Ouagadougou\n", - "BURUNDI=Bujumbura\n", - "ALGERIA=Algiers\n", - "BENIN=Porto-Novo\n", - "CAPE VERDE=Praia\n", - "BOTSWANA=Gaberone\n", - "Porto-Novo\n", - "CAMEROON=Yaounde\n", - "ANGOLA=Luanda\n", - "BURKINA FASO=Ouagadougou\n", - "BURUNDI=Bujumbura\n", - "ALGERIA=Algiers\n", - "BENIN=Porto-Novo\n", - "CAPE VERDE=Praia\n", - "BOTSWANA=Gaberone\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "put()方法只是将键与值放入相应的ArrayList。为了与Map接口保持一致,它必须返回旧的键,或者在没有任何旧键的情况下返回null。\n", - "\n", - "同样遵循了Map规范,get()会在键不在SlowMap中的时候产生null。如果键存在,它将被用来查找表示它在keys列表中的位置的数值型索引,并且这个数字被用作索引来产生与values列表相关联的值。注意,在get()中key的类型是Object,而不是你所期望的参数化类型K(并且是在AssociativeArrayjava中真正使用的类型),这是将泛型注入到Java语言中的时刻如此之晚所导致的结果-如果泛型是Java语言最初就具备的属性,那么get()就可以执行其参数的类型。\n", - "\n", - "Map.entrySet() 方法必须产生一个Map.Entry对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型,就必须同时定义Map.Entry的实现:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/MapEntry.java\n", - "// A simple Map.Entry for sample Map implementations\n", - "import java.util.*;\n", - "public class MapEntry implements Map.Entry {\n", - " private K key;\n", - " private V value;\n", - " public MapEntry(K key, V value) {\n", - " this.key = key;\n", - " this.value = value;\n", - " }\n", - " @Override\n", - " public K getKey() { return key; }\n", - " @Override\n", - " public V getValue() { return value; }\n", - " @Override\n", - " public V setValue(V v) {\n", - " V result = value;\n", - " value = v;\n", - " return result;\n", - " }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hash(key, value);\n", - " }\n", - " @SuppressWarnings(\"unchecked\")\n", - " @Override\n", - " public boolean equals(Object rval) {\n", - " return rval instanceof MapEntry &&\n", - " Objects.equals(key,\n", - " ((MapEntry)rval).getKey()) &&\n", - " Objects.equals(value,\n", - " ((MapEntry)rval).getValue());\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return key + \"=\" + value;\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "这里 equals 方法的实现遵循了[equals 规范]()。在 Objects 类中有一个非常熟悉的方法可以帮助创建 hashCode() 方法: Objects.hash()。当你定义含有超过一个属性的对象的 `hashCode()` 时,你可以使用这个方法。如果你的对象只有一个属性,可以直接使用 ` Objects.hashCode()`。\n", - "\n", - "尽管这个解决方案非常简单,并且看起来在SlowMap.main() 的琐碎测试中可以正常工作,但是这并不是一个恰当的实现,因为它创建了键和值的副本。entrySet() 的恰当实现应该在Map中提供视图,而不是副本,并且这个视图允许对原始映射表进行修改(副本就不行)。\n", - "\n", - "### 为了速度而散列\n", - "\n", - "SlowMap.java 说明了创建一种新的Map并不困难。但是正如它的名称SlowMap所示,它不会很快,所以如果有更好的选择,就应该放弃它。它的问题在于对键的查询,键没有按照任何特定顺序保存,所以只能使用简单的线性查询,而线性查询是最慢的查询方式。\n", - "\n", - "散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。\n", - "\n", - "散列则更进一步,它将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息(请小心留意,我是说键的信息,而不是键本身)。但是因为数组不能调整容量,因此就有一个问题:我们希望在Map中保存数量不确定的值,但是如果键的数量被数组的容量限制了,该怎么办呢?\n", - "\n", - "答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(在计算机科学的术语中称为散列函数)生成。\n", - "\n", - "于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。\n", - "\n", - "理解了散列的原理,我们就能够实现一个简单的散列Map了:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/SimpleHashMap.java\n", - "// A demonstration hashed Map\n", - "import java.util.*;\n", - "import onjava.*;\n", - "public\n", - "class SimpleHashMap extends AbstractMap {\n", - " // Choose a prime number for the hash table\n", - "// size, to achieve a uniform distribution:\n", - " static final int SIZE = 997;\n", - " // You can't have a physical array of generics,\n", - "// but you can upcast to one:\n", - " @SuppressWarnings(\"unchecked\")\n", - " LinkedList>[] buckets =\n", - " new LinkedList[SIZE];\n", - " @Override\n", - " public V put(K key, V value) {\n", - " V oldValue = null;\n", - " int index = Math.abs(key.hashCode()) % SIZE;\n", - " if(buckets[index] == null)\n", - " buckets[index] = new LinkedList<>();\n", - " LinkedList> bucket = buckets[index];\n", - " MapEntry pair = new MapEntry<>(key, value);\n", - " boolean found = false;\n", - " ListIterator> it =\n", - " bucket.listIterator();\n", - " while(it.hasNext()) {\n", - " MapEntry iPair = it.next();\n", - " if(iPair.getKey().equals(key)) {\n", - " oldValue = iPair.getValue();\n", - " it.set(pair); // Replace old with new\n", - " found = true;\n", - " break;\n", - " }\n", - " }\n", - " if(!found)\n", - " buckets[index].add(pair);\n", - " return oldValue;\n", - " }\n", - " @Override\n", - " public V get(Object key) {\n", - " int index = Math.abs(key.hashCode()) % SIZE;\n", - " if(buckets[index] == null) return null;\n", - " for(MapEntry iPair : buckets[index])\n", - " if(iPair.getKey().equals(key))\n", - " return iPair.getValue();\n", - " return null;\n", - " }\n", - " @Override\n", - " public Set> entrySet() {\n", - " Set> set= new HashSet<>();\n", - " for(LinkedList> bucket : buckets) {\n", - " if(bucket == null) continue;\n", - " for(MapEntry mpair : bucket)\n", - " set.add(mpair);\n", - " }\n", - " return set;\n", - " }\n", - " public static void main(String[] args) {\n", - " SimpleHashMap m =\n", - " new SimpleHashMap<>();\n", - " m.putAll(Countries.capitals(8));\n", - " m.forEach((k, v) ->\n", - " System.out.println(k + \"=\" + v));\n", - " System.out.println(m.get(\"BENIN\"));\n", - " m.entrySet().forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "CAMEROON=Yaounde\n", - "ANGOLA=Luanda\n", - "BURKINA FASO=Ouagadougou\n", - "BURUNDI=Bujumbura\n", - "ALGERIA=Algiers\n", - "BENIN=Porto-Novo\n", - "CAPE VERDE=Praia\n", - "BOTSWANA=Gaberone\n", - "Porto-Novo\n", - "CAMEROON=Yaounde\n", - "ANGOLA=Luanda\n", - "BURKINA FASO=Ouagadougou\n", - "BURUNDI=Bujumbura\n", - "ALGERIA=Algiers\n", - "BENIN=Porto-Novo\n", - "CAPE VERDE=Praia\n", - "BOTSWANA=Gaberone\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于散列表中的“槽位”(slot)通常称为桶位(bucket),因此我们将表示实际散列表的数组命名为bucket,为使散列分布均匀,桶的数量通常使用质数[^2]。注意,为了能够自动处理冲突,使用了一个LinkedList的数组;每一个新的元素只是直接添加到list尾的某个特定桶位中。即使Java不允许你创建泛型数组,那你也可以创建指向这种数组的引用。这里,向上转型为这种数组是很方便的,这样可以防止在后面的代码中进行额外的转型。\n", - "\n", - "对于put() 方法,hashCode() 将针对键而被调用,并且其结果被强制转换为正数。为了使产生的数字适合bucket数组的大小,取模操作符将按照该数组的尺寸取模。如果数组的某个位置是 null,这表示还没有元素被散列至此,所以,为了保存刚散列到该定位的对象,需要创建一个新的LinkedList。一般的过程是,查看当前位置的ist中是否有相同的元素,如果有,则将旧的值赋给oldValue,然后用新的值取代旧的值。标记found用来跟踪是否找到(相同的)旧的键值对,如果没有,则将新的对添加到list的末尾。\n", - "\n", - "get()方法按照与put()方法相同的方式计算在buckets数组中的索引(这很重要,因为这样可以保证两个方法可以计算出相同的位置)如果此位置有LinkedList存在,就对其进行查询。\n", - "\n", - "注意,这个实现并不意味着对性能进行了调优,它只是想要展示散列映射表执行的各种操作。如果你浏览一下java.util.HashMap的源代码,你就会看到一个调过优的实现。同样,为了简单,SimpleHashMap使用了与SlowMap相同的方式来实现entrySet(),这个方法有些过于简单,不能用于通用的Map。\n", - "\n", - "### 重写 hashCode()\n", - "\n", - "在明白了如何散列之后,编写自己的hashCode()就更有意义了。\n", - "\n", - "首先,你无法控制bucket数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子(本章稍后会介绍这个术语)有关。hashCode()生成的结果,经过处理后成为桶位的下标(在SimpleHashMap中,只是对其取模,模数为bucket数组的大小)。\n", - "\n", - "设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。\n", - "\n", - "此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this值,这只能产生很糟糕的hashCode(),因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。这正是SpringDetector.java的问题所在,因为它默认的hashCode0使用的是对象的地址。所以,应该使用对象内有意义的识别信息。\n", - "\n", - "下面以String类为例。String有个特点:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。所以new String(\"hello\")生成的两个实例,虽然是相互独立的,但是对它们使用hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/StringHashCode.java\n", - "public class StringHashCode {\n", - " public static void main(String[] args) {\n", - " String[] hellos = \"Hello Hello\".split(\" \");\n", - " System.out.println(hellos[0].hashCode());\n", - " System.out.println(hellos[1].hashCode());\n", - " }\n", - "}\n", - "/* Output:\n", - "69609650\n", - "69609650\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "对于String而言,hashCode() 明显是基于String的内容的。\n", - "\n", - "因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过 hashCode() 和 equals() ,必须能够完全确定对象的身份。\n", - "\n", - "因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int即可。\n", - "\n", - "还有另一个影响因素:好的hashCode() 应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。\n", - "\n", - "在Effective Java Programming Language Guide(Addison-Wesley 2001)这本书中,Joshua Bloch为怎样写出一份像样的hashCode()给出了基本的指导: \n", - "\n", - "1. 给int变量result赋予某个非零值常量,例如17。\n", - "2. 为对象内每个有意义的字段(即每个可以做equals)操作的字段计算出一个int散列码c:\n", - "\n", - "| 字段类型 | 计算公式 |\n", - "| ------------------------------------------------------ | ------------------------------------------------------------ |\n", - "| boolean | c = (f ? 0 : 1) |\n", - "| byte , char , short , or int | c = (int)f |\n", - "| long | c = (int)(f ^ (f>>>32)) |\n", - "| float | c = Float.floatToIntBits(f); |\n", - "| double | long l =Double.doubleToLongBits(f);
c = (int)(l ^ (l >>> 32)) |\n", - "| Object , where equals() calls equals() for this field | c = f.hashCode() |\n", - "| Array | 应用以上规则到每一个元素中 |\n", - "\n", - "3. 合并计算得到的散列码: **result = 37 * result + c;​**\n", - "4. 返回 result。\n", - "5. 检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。\n", - "\n", - "下面便是遵循这些指导的一个例子。提示,你没有必要书写像如下的代码 —— 相反,使用 `Objects.hash()` 去用于散列多字段的对象(如同在本例中的那样),然后使用 `Objects.hashCode()` 如散列单字段的对象。" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/CountedString.java\n", - "// Creating a good hashCode()\n", - "import java.util.*;\n", - "public class CountedString {\n", - " private static List created =\n", - " new ArrayList<>();\n", - " private String s;\n", - " private int id = 0;\n", - " public CountedString(String str) {\n", - " s = str;\n", - " created.add(s);\n", - "// id is the total number of instances\n", - "// of this String used by CountedString:\n", - " for(String s2 : created)\n", - " if(s2.equals(s))\n", - " id++;\n", - " }\n", - " @Override\n", - " public String toString() {\n", - " return \"String: \" + s + \" id: \" + id +\n", - " \" hashCode(): \" + hashCode();\n", - " }\n", - " @Override\n", - " public int hashCode() {\n", - "// The very simple approach:\n", - "// return s.hashCode() * id;\n", - "// Using Joshua Bloch's recipe:\n", - " int result = 17;\n", - " result = 37 * result + s.hashCode();\n", - " result = 37 * result + id;\n", - " return result;\n", - " }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof CountedString &&\n", - " Objects.equals(s, ((CountedString)o).s) &&\n", - " Objects.equals(id, ((CountedString)o).id);\n", - " }\n", - " public static void main(String[] args) {\n", - " Map map = new HashMap<>();\n", - " CountedString[] cs = new CountedString[5];\n", - " for(int i = 0; i < cs.length; i++) {\n", - " cs[i] = new CountedString(\"hi\");\n", - " map.put(cs[i], i); // Autobox int to Integer\n", - " }\n", - " System.out.println(map);\n", - " for(CountedString cstring : cs) {\n", - " System.out.println(\"Looking up \" + cstring);\n", - " System.out.println(map.get(cstring));\n", - " }\n", - " }\n", - "}\n", - "/* Output:\n", - "{String: hi id: 4 hashCode(): 146450=3, String: hi id:\n", - "5 hashCode(): 146451=4, String: hi id: 2 hashCode():\n", - "146448=1, String: hi id: 3 hashCode(): 146449=2,\n", - "String: hi id: 1 hashCode(): 146447=0}\n", - "Looking up String: hi id: 1 hashCode(): 146447\n", - "0\n", - "Looking up String: hi id: 2 hashCode(): 146448\n", - "1\n", - "Looking up String: hi id: 3 hashCode(): 146449\n", - "2\n", - "Looking up String: hi id: 4 hashCode(): 146450\n", - "3\n", - "Looking up String: hi id: 5 hashCode(): 146451\n", - "4\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "CountedString由一个String和一个id组成,此id代表包含相同String的CountedString对象的编号。所有的String都被存储在static ArrayList中,在构造器中通过选代遍历此ArrayList完成对id的计算。\n", - "\n", - "hashCode()和equals() 都基于CountedString的这两个字段来生成结果;如果它们只基于String或者只基于id,不同的对象就可能产生相同的值。\n", - "\n", - "在main)中,使用相同的String创建了多个CountedString对象。这说明,虽然String相同,但是由于id不同,所以使得它们的散列码并不相同。在程序中,HashMap被打印了出来,因此可以看到它内部是如何存储元素的(以无法辨别的次序),然后单独查询每一个键,以此证明查询机制工作正常。\n", - "\n", - "作为第二个示例,请考虑Individual类,它被用作[类型信息]()中所定义的typeinfo.pet类库的基类。Individual类在那一章中就用到了,而它的定义则放到了本章,因此你可以正确地理解其实现。\n", - "\n", - "在这里替换了手工去计算 `hashCode()`,我们使用了更合适的方式 ` Objects.hash() `:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// typeinfo/pets/Individual.java\n", - "package typeinfo.pets;\n", - "import java.util.*;\n", - "public class\n", - "Individual implements Comparable {\n", - " private static long counter = 0;\n", - " private final long id = counter++;\n", - " private String name;\n", - " public Individual(String name) { this.name = name; }\n", - " // 'name' is optional:\n", - " public Individual() {}\n", - " @Override\n", - " public String toString() {\n", - " return getClass().getSimpleName() +\n", - " (name == null ? \"\" : \" \" + name);\n", - " }\n", - " public long id() { return id; }\n", - " @Override\n", - " public boolean equals(Object o) {\n", - " return o instanceof Individual &&\n", - " Objects.equals(id, ((Individual)o).id);\n", - " }\n", - " @Override\n", - " public int hashCode() {\n", - " return Objects.hash(name, id);\n", - " }\n", - " @Override\n", - " public int compareTo(Individual arg) {\n", - " // Compare by class name first:\n", - " String first = getClass().getSimpleName();\n", - " String argFirst = arg.getClass().getSimpleName();\n", - " int firstCompare = first.compareTo(argFirst);\n", - " if(firstCompare != 0)\n", - " return firstCompare;\n", - " if(name != null && arg.name != null) {\n", - " int secondCompare = name.compareTo(arg.name);\n", - " if(secondCompare != 0)\n", - " return secondCompare;\n", - " }\n", - " return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));\n", - " }\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "compareTo() 方法有一个比较结构,因此它会产生一个排序序列,排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后按照创建的顺序排序。下面的示例说明了它是如何工作的:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "// equalshashcode/IndividualTest.java\n", - "import collections.MapOfList;\n", - "import typeinfo.pets.*;\n", - "import java.util.*;\n", - "public class IndividualTest {\n", - " public static void main(String[] args) {\n", - " Set pets = new TreeSet<>();\n", - " for(List lp :\n", - " MapOfList.petPeople.values())\n", - " for(Pet p : lp)\n", - " pets.add(p);\n", - " pets.forEach(System.out::println);\n", - " }\n", - "}\n", - "/* Output:\n", - "Cat Elsie May\n", - "Cat Pinkola\n", - "Cat Shackleton\n", - "Cat Stanford\n", - "Cymric Molly\n", - "Dog Margrett\n", - "Mutt Spot\n", - "Pug Louie aka Louis Snorkelstein Dupree\n", - "Rat Fizzy\n", - "Rat Freckly\n", - "Rat Fuzzy\n", - "*/" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "由于所有的宠物都有名字,因此它们首先按照类型排序,然后在同类型中按照名字排序。\n", - "\n", - "\n", - "\n", - "## 调优 HashMap\n", - "\n", - "我们有可能手动调优HashMap以提高其在特定应用程序中的性能。为了理解调整HashMap时的性能问题,一些术语是必要的:\n", - "\n", - "- 容量(Capacity):表中存储的桶数量。\n", - "- 初始容量(Initial Capacity):当表被创建时,桶的初始个数。 HashMap 和 HashSet 有可以让你指定初始容量的构造器。\n", - "- 个数(Size):目前存储在表中的键值对的个数。\n", - "- 负载因子(Load factor):通常表现为 $\\frac{size}{capacity}$。当负载因子大小为 0 的时候表示为一个空表。当负载因子大小为 0.5 表示为一个半满表(half-full table),以此类推。轻负载的表几乎没有冲突,因此是插入和查找的最佳选择(但会减慢使用迭代器进行遍历的过程)。 HashMap 和 HashSet 有可以让你指定负载因子的构造器。当表内容量达到了负载因子,集合就会自动扩充为原始容量(桶的数量)的两倍,并且会将原始的对象存储在新的桶集合中(也被称为 rehashing)\n", - "\n", - "HashMap 中负载因子的大小为 0.75(当表内容量大小不足四分之三的时候,不会发生 rehashing 现象)。这看起来是一个非常好的同时考虑到时间和空间消耗的平衡策略。更高的负载因子会减少空间的消耗,但是会增加查询的耗时。重要的是,查询操作是你使用的最频繁的一个操作(包括 `get()` 和 `put()` 方法)。\n", - "\n", - "如果你知道存储在 HashMap 中确切的条目个数,直接创建一个足够容量大小的 HashMap,以避免自动发生的 rehashing 操作。\n", - "\n", - "[^1]: \n", - "[^2]: 事实证明,质数实际上并不是散列桶的理想容量。近来,(经过广泛的测试)Java的散列函数都使用2的整数次方。对现代的处理器来说,除法与求余数是最慢的操作。使用2的整数次方长度的散列表,可用掩码代替除法。\n", - "\n", - "\n", - "\n", - "
" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/jupyter/GLOSSARY.ipynb b/jupyter/GLOSSARY.ipynb deleted file mode 100644 index e8f7bc3b..00000000 --- a/jupyter/GLOSSARY.ipynb +++ /dev/null @@ -1,23 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 词汇表\n", - "\n", - "| 词汇 | 解释 |\n", - "| ----------------| ----------|\n", - "| **OOP** (*Object-oriented programming*) | 面向对象编程,一种编程思维模式和编程架构|\n", - "| **UML** (*Unified Modeling Language*) | 统一建模语言,类图 |\n", - "| **Aggregation** | 聚合,关联关系的一种,是强的关联关系|\n", - "| **Composition** | 组合,关联关系的一种,是比聚合关系强的关系 |\n", - "| **STL**(*the Standard Template Library*)| C++ 标准模板库|\n", - "| **Fibonacci Sequence**| [斐波那契数列](https://zh.wikipedia.org/wiki/斐波那契数列),又称黄金分割数列 |" - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} From 8f103177d1968c24adb7b0d924ca2b2b6c0e3ddc Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 29 May 2020 06:00:50 +0800 Subject: [PATCH 244/371] =?UTF-8?q?=E6=A0=A1=E8=AE=A2=20=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=BC=96=E7=A8=8B-=E5=89=8D=E8=A8=80=E9=83=A8=E5=88=86=20issue?= =?UTF-8?q?=20#413?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 684ef810..57d3d55d 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -3,27 +3,28 @@ # 第二十四章 并发编程 ->爱丽丝:“但是我不想进入疯狂的人群中” +>爱丽丝:“我可不想到疯子中间去” > ->猫咪:“oh,你无能为力,我们都疯了,我疯了,你也疯了” +>猫咪:“啊,那就无办法了,我们这都是疯子。我疯了,你也疯了” > >爱丽丝:“你怎么知道我疯了”。 > ->猫咪:“你一定疯了,否则你不会来到这里”——爱丽丝梦游仙境 第6章。 +>猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第6章。 -到目前为止,我们一直在编程,就像文学中的意识流叙事设备一样:首先发生一件事,然后是下一件事。我们完全控制所有步骤及其发生的顺序。如果我们将值设置为5,那么稍后会回来并发现它是47,这将是非常令人惊讶的。 -我们现在进入了一个奇怪的并发世界,在此这个结果并不令人惊讶。你信赖的一切都不再可靠。它可能有效,也可能无效。很可能它只会在某些条件下有效。你必须知道和了解这些情况以确定哪些有效。 +在本章之前,我们惯用一种简单顺序地叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,结果再看时它已变成47的话,这就很匪夷所思了。 -作为类比,你的正常生活是在牛顿力学中发生的。物体具有质量:它们会下降并转移它们的动量。电线具有阻力,光线可以直线传播。但是,如果你进入非常小、热、冷、或者大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质变为超导体。 +现在,我们来到了陌生的并发世界。对于这样的结果一点都不奇怪。你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 -假设我们在同时多条故事线进行的间谍小说里,而非单一意识流地叙事。第一个间谍在特殊的岩石处留下了微缩胶片。当第二个间谍过来准备取回包裹时,胶片可能已被第三个间谍带走了。但是小说并没有交代此处的细节。直到故事结尾,我们都没搞清楚这里到底发生了什么。 +作为类比,我们正常生活是发生在经典牛顿力学中的。物体具有质量:会落下并转移动量。电线有电阻,光直线传播。假如我们进入极小、极热、极冷、或是极大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质会变为超导体。 -构建并发应用程序非常类似于游戏 [Jenga](https://en.wikipedia.org/wiki/Jenga),每当你拉出一个块并将其放置在塔上时,一切都会崩溃。每个塔楼和每个应用程序都是独一无二的,有自己的作用。你从构建系统中学到的东西可能不适用于下一个系统。 +假设我们处在条故事线并行的间谍小说里,而非单一意识流地叙事。第一个间谍在某个特别的岩石底下藏了微缩胶片。当第二个间谍来取回包裹时,胶片可能已被第三个间谍带走了。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 -本章是对并发性的一个非常基本的介绍。虽然我使用了最现代的 Java 8 工具来演示原理,但这一章远非对该主题的全面处理。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全的通过这些鲨鱼肆虐的困难水域。 +构建并发应用好比[搭积木](https://en.wikipedia.org/wiki/Jenga)。每拉出一块放在塔顶时,整个都可能会崩塌。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的东西并不一定适用于下一个系统。 -对于更麻烦和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。尽管在撰写本文时,该书已有十多年的历史了,但它仍然包含我们必须了解和理解的要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也是很有价值的资源。它详细描述了 JVM 的内部工作方式,包括线程。 +本章是对并发的基本介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但本章还远未全面论述并发。我的目标是为你提供足够的基础知识,你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 + +更多麻烦和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 From 5457b3d6d8b33c8f835470a4af0e74b8b719897d Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 29 May 2020 06:17:26 +0800 Subject: [PATCH 245/371] =?UTF-8?q?=E6=A0=A1=E8=AE=A2=20=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=BC=96=E7=A8=8B-=E5=89=8D=E8=A8=80=E9=83=A8=E5=88=86=20issue?= =?UTF-8?q?=20#413?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 57d3d55d..d44a1dcd 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -5,26 +5,26 @@ >爱丽丝:“我可不想到疯子中间去” > ->猫咪:“啊,那就无办法了,我们这都是疯子。我疯了,你也疯了” +>猫咪:“啊,那没辙了,我们这都是疯子。我疯了,你也疯了” > >爱丽丝:“你怎么知道我疯了”。 > >猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第6章。 -在本章之前,我们惯用一种简单顺序地叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,结果再看时它已变成47的话,这就很匪夷所思了。 +在本章之前,我们惯用一种简单顺序地叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,再看时它已变成47的话,结果就很匪夷所思了。 -现在,我们来到了陌生的并发世界。对于这样的结果一点都不奇怪。你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 +现在,我们来到了陌生的并发世界。这样的结果一点都不奇怪,因为你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 -作为类比,我们正常生活是发生在经典牛顿力学中的。物体具有质量:会落下并转移动量。电线有电阻,光直线传播。假如我们进入极小、极热、极冷、或是极大的世界(我们不能生存),这些事情会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质会变为超导体。 +作为类比,我们正常生活是发生在经典牛顿力学中的。物体具有质量:会坠落并转移动量。电线有电阻,光直线传播。假如我们进入极小、极热、极冷、或是极大的世界(我们不能生存),这些现象就会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质还会变为超导体。 -假设我们处在条故事线并行的间谍小说里,而非单一意识流地叙事。第一个间谍在某个特别的岩石底下藏了微缩胶片。当第二个间谍来取回包裹时,胶片可能已被第三个间谍带走了。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 +假设我们处在多条故事线并行的间谍小说里,非单一意识流地叙事:第一个间谍在岩石底留下了微缩胶片。当第二个间谍来取时,胶片可能已被第三个间谍拿走。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 -构建并发应用好比[搭积木](https://en.wikipedia.org/wiki/Jenga)。每拉出一块放在塔顶时,整个都可能会崩塌。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的东西并不一定适用于下一个系统。 +构建并发程序好比玩[搭积木](https://en.wikipedia.org/wiki/Jenga)游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 -本章是对并发的基本介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但本章还远未全面论述并发。我的目标是为你提供足够的基础知识,你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 +本章是对并发概念最基本的介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但还远未及全面论述并发。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 -更多麻烦和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 +更多繁琐和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 From 6621fb02d778521fbf53ee41d582be401fa8ac87 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Mon, 1 Jun 2020 08:22:03 +0800 Subject: [PATCH 246/371] =?UTF-8?q?=E6=A0=A1=E8=AE=A2=20=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=BC=96=E7=A8=8B-=E6=9C=AF=E8=AF=AD=E9=97=AE=E9=A2=98=20issue?= =?UTF-8?q?=20#413?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index d44a1dcd..baa79c7b 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -12,7 +12,7 @@ >猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第6章。 -在本章之前,我们惯用一种简单顺序地叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,再看时它已变成47的话,结果就很匪夷所思了。 +在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,再看时它已变成47的话,结果就很匪夷所思了。 现在,我们来到了陌生的并发世界。这样的结果一点都不奇怪,因为你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 @@ -24,31 +24,37 @@ 本章是对并发概念最基本的介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但还远未及全面论述并发。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 -更多繁琐和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 +更多繁琐和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 ## 术语问题 -在编程文献中并发、并行、多任务、多处理、多线程、分布式系统(以及可能的其他)使用了许多相互冲突的方式,并且经常被混淆。Brian Goetz在2016年的演讲中指出了这一点[From Concurrent to Parallel](https://www.youtube.com/watch?v=NsDE7E8sIdQ),他提出了一个合理的解释: +术语“并发”,“并行”,“多任务”,“多处理”,“多线程”,分布式系统(可能还有其他)在整个编程文献中都以多种相互冲突的方式使用,并且经常被混为一谈。 +*Brian Goetz* 在他 2016 年《从并发到并行》的演讲中指出了这一点,之后提出了合理的二分法: - 并发是关于正确有效地控制对共享资源的访问。 + - 并行是使用额外的资源来更快地产生结果。 -这些都是很好的定义,但有几十年的混乱产生了反对解决问题的历史。一般来说,当人们使用“并发”这个词时,他们的意思是“一切变得混乱”,事实上,我可能会在很多地方自己陷入这种想法,大多数书籍,包括Brian Goetz的Java Concurrency in Practice,都在标题中使用这个词。 +这些定义很好,但是我们已有几十年混乱使用和抗拒解决此问题的历史了。一般来说,当人们使用“并发”这个词时,他们的意思是“所有的一切”。事实上,我自己也经常陷入这样的想法。在大多数书籍中,包括 *Brian Goetz* 的 《Java Concurrency in Practice》,都在标题中使用这个词。 + +“并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 + +还有另一种方法,在减速发生的地方写下定义(原文Here’s another approach, writing the definitions around where the +slowdown occurs): -并发通常意味着“不止一个任务正在执行中”,而并行性几乎总是意味着“不止一个任务同时执行。”你可以立即看到这些定义的区别:并行也有不止一个任务“正在进行”。区别在于细节,究竟是如何“执行”发生的。此外,重叠:为并行编写的程序有时可以在单个处理器上运行,而一些并发编程系统可以利用多个处理器。 +**并发** -这是另一种方法,在减速[原文:slowdown]发生的地方写下定义: +同时完成多任务。无需等待当前任务完成即可执行其他任务。“并发”解决了程序因外部控制而无法进一步执行的阻塞问题。最常见的例子就是 I/O 操作,任务必须等待数据输入(在一些例子中也称阻塞)。这个问题常见于 I/O 密集型任务。 -_并发_ +**并行** -同时完成多个任务。在开始处理其他任务之前,当前任务不需要完成。并发解决了阻塞发生的问题。当任务无法进一步执行,直到外部环境发生变化时才会继续执行。最常见的例子是I/O,其中任务必须等待一些input(在这种情况下会被阻止)。这个问题产生在I/O密集型。 +同时在多个位置完成多任务。这解决了所谓的 CPU 密集型问题:将程序分为多部分,在多个处理器上同时处理不同部分来加快程序执行效率。 -_并行_ +上面的定义告诉了我们术语令人困惑的原因:两者的核心是“同时完成多个任务”。并行增加了跨多个处理器的分布。更重要的是,这两种方法可以解决不同类型的问题:解决I / O绑定问题和并行化可能对您没有任何好处,因为该问题不是整体速度,而是阻塞。采取计算约束问题并尝试在单个处理器上使用并发性解决问题可能会浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式却有所不同,并且取决于问题所施加的约束。 -同时在多个地方完成多个任务。这解决了所谓的计算密集型问题,如果将程序分成多个部分并在不同的处理器上编辑不同的部分,程序可以运行得更快。 术语混淆的原因在上面的定义中显示:其中核心是“在同一时间完成多个任务。”并行性通过多个处理器增加分布。更重要的是,两者解决了不同类型的问题:解决I/O密集型问题,并行化可能对你没有任何好处,因为问题不是整体速度,而是阻塞。并且考虑到计算力限制问题并试图在单个处理器上使用并发来解决它可能会浪费时间。两种方法都试图在更短的时间内完成更多,但它们实现加速的方式是不同的,并且取决于问题所带来的约束。 From 170345e1470b6fd3ad1373ad3bc108ce321ddcbe Mon Sep 17 00:00:00 2001 From: RexHuang Date: Wed, 3 Jun 2020 10:25:11 +0800 Subject: [PATCH 247/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AC=AC=E5=8D=81?= =?UTF-8?q?=E5=85=AB=E7=AB=A0=E5=AD=97=E7=AC=A6=E4=B8=B2start()=E5=92=8Cen?= =?UTF-8?q?d()=E6=BA=90=E7=A0=81=E6=A0=BC=E5=BC=8F=20(#465)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一源码格式,防止理解偏差 --- docs/book/18-Strings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 4282fc00..90113eb3 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -1079,9 +1079,9 @@ public class StartEnd { while(m.find()) d.display("find() '" + m.group() + "' start = "+ m.start() + " end = " + m.end()); - if(m.lookingAt()) // No reset() necessary - d.display("lookingAt() start = " - + m.start() + " end = " + m.end()); + if(m.lookingAt()) // No reset() necessary + d.display("lookingAt() start = " + + m.start() + " end = " + m.end()); if(m.matches()) // No reset() necessary d.display("matches() start = " + m.start() + " end = " + m.end()); From 2852eb7e696d6843fd1bf07e2b7eab27344d96fc Mon Sep 17 00:00:00 2001 From: LingCoder <34231795+LingCoder@users.noreply.github.com> Date: Fri, 5 Jun 2020 06:18:00 +0800 Subject: [PATCH 248/371] Update README.md --- README.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/README.md b/README.md index 3524cc79..9d501d37 100644 --- a/README.md +++ b/README.md @@ -59,24 +59,6 @@ - [x] [附录:C++ 和 Java 的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) - [ ] [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) -## INSTALL - -1. 首先安装[Jupyter Lab](https://jupyter.org/) -2. 安装[Java Kernel](https://github.com/SpencerPark/IJava) - 注意: 打开文件后,在工具栏最右边选择 `Java`。 Mac 下按 `CMD + Enter` 可以运行 Code。 - Java SDK 需要 1.9 及以上。可以用[sdkman](sdkman.io)安装. -3. 代码运行。 - - ```java - public class Hello { - public static void main(String [] args){ - System.out.println("Hello, world!") - } - } - - //调用静态方法main - Hello.main(new String [0]); - ``` ## 一起交流 From 87ef7fd1bf1f959d380293157b6039b2d485d13f Mon Sep 17 00:00:00 2001 From: legendyql Date: Mon, 8 Jun 2020 10:26:06 +0800 Subject: [PATCH 249/371] =?UTF-8?q?Appendix:=20Javadoc=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=20(#467)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改原翻译中错误、拗口的地方 --- docs/book/Appendix-Javadoc.md | 36 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/docs/book/Appendix-Javadoc.md b/docs/book/Appendix-Javadoc.md index 56d62c40..50dc8c2e 100644 --- a/docs/book/Appendix-Javadoc.md +++ b/docs/book/Appendix-Javadoc.md @@ -3,25 +3,19 @@ # 附录:文档注释 -编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,那么每次更改代码时更改文档都会变得很繁琐。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这完整的画面,您需要一个特殊的注释语法来标记文档,以及一个工具来将这些注释提取为有用的表单中。这就是Java所做的。 +编写代码文档的最大问题可能是维护该文档。如果文档和代码是分开的,每次更改代码时都要很繁琐地再去更改文档。解决方案似乎很简单:将代码链接到文档。最简单的方法是将所有内容放在同一个文件中。然而,要完成这个任务,需要一个特殊的注释语法来标记文档,以及一个工具将这些注释提取为有用的形式,这就是Java所做的。 提取注释的工具称为Javadoc,它是 JDK 安装的一部分。它使用Java编译器中的一些技术来寻找特殊的注释标记。它不仅提取由这些标记所标记的信息,还提取与注释相邻的类名或方法名。通过这种方式,您就可以用最少的工作量来生成合适的程序文档。 -Javadoc输出为一个html文件,您可以使用web浏览器查看它。对于Javadoc,您有一个简单的标准来创建文档,因此您可以期望所有Java libraries都有文档。 +Javadoc的输出是一个html文件,可以用web浏览器查看它。有了Javadoc,就有一个简单的标准来创建文档,因此你可以期望所有Java库都有文档。 -此外,您可以编写自己的Javadoc处理程序doclet,对于 Javadoc(例如,以不同的格式生成输出)。 +此外,你可以编写自己的Javadoc处理程序doclet,对javadoc处理的信息做特殊的处理(例如以不同的格式生成输出)。 以下是对Javadoc基础知识的介绍和概述。在 JDK 文档中可以找到完整的描述。 ## 句法规则 -所有Javadoc指令都发生在以 **/**** 开头(但仍然以 ***/** 结尾)的注释中。 - -使用Javadoc有两种主要方法: - -嵌入HTML或使用“doc标签”。独立的doc标签是指令它以 **@** 开头,放在注释行的开头。(然而,前面的 ***** 将被忽略。)可能会出现内联doc标签 - -Javadoc注释中的任何位置,也可以,以一个 **@** 开头,但是被花括号包围。 +所有Javadoc指令都发生在以 `/**` 开头(但仍然以 `*/`)结尾)的注释中。使用Javadoc有两种主要方法:嵌入HTML或使用“doc标签”。独立的doc标签是以 **@** 开头并且放在注释行的开头的指令(注释行开头的`*`将被忽略)。内联的doc标签可以出现在Javadoc注释的任何位置,它也以 `@` 开头,但被花括号包围。 有三种类型的注释文档,它们对应于注释前面的元素:类、字段或方法。也就是说,类注释出现在类定义之前,字段注释出现在字段定义之前,方法注释出现在方法定义之前。举个简单的例子: @@ -37,11 +31,7 @@ public class Documentation1 { ``` -Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 - -默认情况下,将忽略对 **私有成员** 和包访问成员的注释(请参阅["隐藏实现"](/docs/book/07-Implementation-Hiding.md)一章),并且您将看不到任何输出。 - -这是有道理的,因为仅客户端程序员的观点是,在文件外部可以使用 **公共成员** 和 **受保护成员** 。 您可以使用 **-private** 标志和包含 **私人** 成员。 +Javadoc仅处理 **公共成员** 和 **继承访问权限成员** 的注释文档。 默认情况下,将忽略对 **私有成员** 和**包访问权限成员**的注释(请参阅["隐藏实现"](/docs/book/07-Implementation-Hiding.md)一章),并且你将看不到任何输出。 这是有道理的,因为从客户端程序员的视角看,在文件外部只有 **公共成员** 和 **继承访问权限成员** 是可用的。 你可以使用 **-private** 标志来包含 **私有成员**。 要通过Javadoc处理前面的代码,命令是: @@ -49,11 +39,11 @@ Javadoc处理注释文档仅适用于 **公共** 和 **受保护** 的成员。 javadoc Documentation1.java ``` -这将产生一组HTML文件。 如果您在浏览器中打开index.html,您将看到结果与所有其他Java文档具有相同的标准格式,因此用户对这种格式很熟悉,并可以轻松地浏览你的类。 +这将产生一组HTML文件。如果你在浏览器中打开index.html,将看到输出的结果与其他Java文档具有相同的标准格式,因此使用者对这种格式很熟悉,并可以轻松地浏览你的类。 ## 内嵌 HTML -Javadoc传递未修改的HTML代码,用以生成的HTML文档。这使你可以充分利用HTML。但是,这样做的主要目的是让你格式化代码,例如: +Javadoc不作修改地将HTML代码传递到生成的HTML文档。这使你可以充分利用HTML。但是这样做的主要目的是让你格式化代码,例如: ```java // javadoc/Documentation2.java @@ -64,7 +54,7 @@ Javadoc传递未修改的HTML代码,用以生成的HTML文档。这使你可 public class Documentation2 {} ``` -您你也可以像在其他任何Web文档中一样使用HTML来格式化说明中的文字: +你也可以像在其他任何Web文档中一样使用HTML来格式化说明中的文字: ```java // javadoc/Documentation3.java @@ -78,7 +68,7 @@ public class Documentation2 {} public class Documentation3 {} ``` -请注意,在文档注释中,Javadoc删除了行首的星号以及前导空格。 Javadoc重新格式化了所有内容,使其符合标准文档的外观。不要将诸如 \或 \之类的标题用作嵌入式HTML,因为Javadoc会插入自己的标题,后插入的标题将对其生成的文档产生干扰。 +请注意,在文档注释中,Javadoc会删除行首的星号以及前导空格。 Javadoc重新格式化了所有内容,使其符合文档的标准外观。不要将`

` 和`
` 之类的标题用作嵌入式HTML,因为Javadoc会插入自己的标题,你插入的标题将对其产生干扰。 所有类型的注释文档(类,字段和方法)都可以支持嵌入式HTML。 @@ -88,7 +78,7 @@ public class Documentation3 {} ### @see -这个标签可以将其他的类连接到文档中,Javadoc 将使用 @see 标记超链接到其他文档中,形式为: +这个标签可以将其它的类链接到本文档中。Javadoc 用 `@see` 标签产生链接到其它类的的HTML。格式为: ```java @see classname @@ -144,7 +134,7 @@ public class Documentation3 {} @param parameter-name description ``` -其中parameter-name是方法参数列表中的标识符,description 是可以在后续行中继续的文本。当遇到新的文档标签时,说明被视为完成。@param 标签的可以任意使用,大概每个参数一个。 +其中 `parameter-name` 是方法参数列表中的标识符, `description` 是可以在后续行中继续的文本。当遇到新的文档标签时,这个说明被视为结束。`@param`标签可以使用任意次,大概每个参数使用一次。 ### @return @@ -164,11 +154,11 @@ public class Documentation3 {} @throws fully-qualified-class-name description ``` - fully-qualified-class-name 给出明确的异常分类名称,并且 description (可延续到后面的行内)告诉你为什么这特定类型的异常会在方法调用后出现。 +*fully-qualified-class-name* 给出异常类的确切名称,并且 description (可在后面的行内继续展开)告诉你为什么这个特定类型的异常会在方法调用后出现。 ### @deprecated -这表示已被改进的功能取代的功能。deprecated 标记表明你不再使用此特定功能,因为将来有可能将其删除。标记为@不赞成使用的方法会导致编译器在使用时发出警告。在Java 5中,@deprecated Javadoc 标记已被 @Deprecated 注解取代(在[注解]()一章中进行了描述)。 +表示已被改进的功能取代的功能。deprecated 标记建议你不要再使用此功能,因为将来它有可能被删除。使用标记为 `@deprecated` 的方法会使编译器发出警告。在Java 5中,`@deprecated` Javadoc 标记被 `@Deprecated` *注解* 取代(在[注解]()一章中进行了描述)。 ## 文档示例 From cd316b4dc00f03fab458cb4c7ce050cf371a2d6f Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Tue, 9 Jun 2020 18:35:37 +0800 Subject: [PATCH 250/371] =?UTF-8?q?=E7=AC=AC=E5=85=AB=E7=AB=A0=20=E5=90=91?= =?UTF-8?q?=E4=B8=8A=E8=BD=AC=E5=9E=8B=20=E8=BF=99=E4=B8=80=E8=8A=82?= =?UTF-8?q?=E4=B8=AD=20=E5=9B=BE=E7=89=87=E4=B8=A2=E5=A4=B1=20(#470)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` 该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是: ![Wind 类图](../images/1561774164644.png) ``` 其中1561774164644.png 文件不存在 --- docs/images/1561774164644.png | Bin 0 -> 57171 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/1561774164644.png diff --git a/docs/images/1561774164644.png b/docs/images/1561774164644.png new file mode 100644 index 0000000000000000000000000000000000000000..219f0abd6036478c2064faad74c985fa5c2bac6b GIT binary patch literal 57171 zcmXtg2RzmL|35O0tV8xDviHc|6tc2sb{WauBOD`HAuC%ll3AH0TL{^T>@71H8UL62 z{r%6Qd+Sl%bI#{IUa#kR-DldGcL=UiUq?ejBT&7obRP{3g8&T;T>yarzvIe@9SuL> zy52SRKtm(!xcUeEB^MzL8X6;-s*=2(Z`M|jPav88>4mtE$7*UK;Vqrsy;=kVW>X+vy|GPJFt@w zB~Ivo$-?qQDmQSy!EU8Lqd{mV;Md>g6af|I$ttV%$T&{M(L>Dtey#E;@*Ug%e&tnl zh$5g7b)GCC{Frm~*9d6FTE}m|Z&X*1Jc{qIdS7{{*Oq~w9+y~_g; z*30vq5LVUe*x{{~L;U!vNnYl~X-40P3nQ1aH>Fpe|dfjW>Q<$kd` zA$B+l8Qm#FT|8cNOf|52PiO2*U!P2wm_iAOsWC!%Gs#x{<#U3+e2(oZFOit7Lm7R) zZDTn?8T0j!SY4ADx1NsHi0EwMATd=COyy_BlG!yCo00hfDth`}Vzgyt*x@F>2fDia zqOR7zgru?JhFVxFW09V_*v0JSzYxNxo0Ryygofr?znjDEtCi^JXi+w5a@fV%+1i#H zRo~OsS3o4_Pu4%O+{C33EknI=2$LfSM_`PJd@!!Hm&r}Izv0?AIP|*Y@pt35xZBv$ ztQUK$)tI431tocQcJ?7*g6+y5-eFT>m;Y_MzCx;<;zO4NoX{9KM#ertD~zHAs(8Jh z1+@m<6j=Fz7Uj6%$N~py1=1uICTpEn$<;bI|Mws2ke;ocNTo!_Rtb5%sBq;gv^f=RHMx`Yp$@P}A}!Tn|^u4=L%kC7FuI($=h`aOzkG|7&&Kh9DOzGmI;v?~z8-FAR4g*=A9((0^1rtkl|W`jcH@#?W9pc9 z?db0A?&?xYS~~PU`u)?;#3bf4c6WEz;GGGbwEv&uy~V}~T{UH$(L*5?ylSgO+W+op z7U4ZcRHmvd2Gdq-+HOi&SqvkJyKji9ZrWWvija<$@P&W^N5kJAPIb;xHU5W=s!7&1 zHaLW|_1?dKEOsZf<3tQ8>$gvos>&lo5FZ{#Kc~8?1BK)Hn-arTbc8rH=9MiMr@ZD8 zJ5#k!7Q5nPax0-q%ryCEX>NNgbUYg!)q`zbx25N)u_vx5Dc*ZlOR)2^Ui-fj>g)GZ z4w0W)Bw&Z)FHhsJO1bZ-9Zq~JNr;QJFa0MMeM-6Bw=$xgk+PhBBiu5MD+WF^#3Q|;5RJ^w)-FAL` zYOE0Iif54h{OEf@bF)-bI};-8jng}m4~?fAZ|$O-`EN=}2K;LFQ&v_^8NN9G=d&@& z!OW~cs-$9MVexCLay3iLt-!F-;_KJ1>+9>!o;{0pJlpGL;4&y5@-j!Fwr84-*1q3l zW}fCZa&TCWr;`jmKQyGJKBRpZV2{D%J-5XCzuMvTRDEpTm2sF+b@)lkxu|tVG;GWA z%F4>>>O))Gt)F##tgNh*_>E?~?ffh{j??vfnIcX#wY5L%p3K%h9JKi4&-37Ov;JFy zpwmB7O+Gu*4IWtXYeV^p?z2m;U%wV{8h=_@1x4jx<(s7Y(%oE{ApfK7^@Ee(f4}SC z=nzo4w&yEG!O?I^>+|~kVdC%apSNXYWm#DxkYOBShsrfNAy%f*qRyid+IH{Quc!Wn z#TRp(d-de=RHN71?!|_)eNDwE!XS8f93pyJI=Z;t#|n4V)Oz1&yo#rnK0O$eO-o5Z zMMPM^U8t)xZxWT2z3g~)t(G~Rs5vB&kK&b8A7&ZnkIIR_#S*d>v(o{eC(^{ zP^_69^IUyp5Dro<(MZSY0V@m3T4Vgav|6e8&f`MP$>#_fQAfI%f-mWEQfN;S=%xLS zcdd+5O+PwM6(~Q4FK~<1oGE5OOZNPASy@?Wa0^4}`vUgUKp7oUWu5+>o}S*`#Ee!uJY7PGxyb$l_jx= z)5fQ#Q&9h;FK}sv0=(b$Hs0A7pZ%D|E(}@qO^hY0UzPjGy|8x z$J%-U{)mn+iajP&PL5Q$i}I0_+#V?dgRE^&B7CLk|6b?+j*iwxa0IZ`N()M)*kT0i zDY3mNl&fJ!-@i{uUkWNMEseDLGSg8izp+a)RWkTrIsZVI*vTA5;1)8b|7m#m@0&Mo z95Zjl6tvIp`hQ~6%!-bQNn-iBql?K&De+xo#Z^q$yijRWWF=+ByvdB1oO-Wi@Uxir zRyuo(j*iYX7X&eDZ!~Xo$GWGIN(95Vfh5|nGLG*AH-jz&bqXSsPriTuF3V?%@nJqO z>y!T>Cl62OcjDY3uNiyHIvtL!V}K(c2+P;Rot%x0rz*@jo;|}2$02f=tYRt}U3O<> zWAoXX6c7?h*8SSHjv~44`s!^NH3><9g9$$+K6ZGd)t`+qJf6~gG*88y;Uc{FvA2em zlj%k*qxSapDyTctuI&WrT6HcnNy*8J3k$xkZ7#gFh18RO|NB@S&qwg!gzb#6IvOJr z<;FLtsWVeXc{oZcxPJoc`;s6 zO-)TfK>@!Vn(d!@8Zo;%>QQBRe7rc{jjs=TdwUBCZxYe5^_d zdi3)a6A~fpJh=-sp3AUeDHYS#ar0DSs-*b;wkO)s2=I?;()H`t0ojdweEhR3j;6_s zH+L`%Ua6@o2PbB{hMJlK8)HtjwY55tJTVI^-(H;ux4dzH9q;VyggZom$;8I?aQ1T$ z51h^6*SJKY&=HhVmxqRizP-W|ef)z35mvF;SaG@AP6SWn=H|wm#w8+hST`H2C~T@j ziqeATt@z1wo#y42D}*>I;oBrZYL6Gj*jG^__`t;E$FENTeoc*XjC0;i`uaTF+`p>k zecJgwXBbi1YOv%kl#%QbEULW&1K;|k6gXbm8BZKu!yzmxE>_G&D#Sd0ezY??_?TF4 zxWSB9U;pabhg=4Cu*{7rxQP|trcjn#{;I-SwfNshTdL+I2rFR33M6!gcgie~+u7M6 z$HN$(awQ^}`vj@U9Wo|J4ZLvO{L8z zW?XIyyzLN=(95=9p*f`ZU-Jt!5@tqExzD}yeJTZs;m?Y;FOi2$pyt^Y));w-&5F&6 zF2XKuH#X$eCTxFKbBbn{CZ(jT!a+qx=j--NDfF^f9b<1|=902)&qgw3GG!d5FqW?c zYY1!+01{c~zdJ7{4!vr0w5Fax$X_0aKYnB68`dR>#5?9^PoBRS!Qum@f@im1Zwp@z*;3DkrLLt#y-&|_! zrS(S{*krq#l7A?q(Gr?Vuj7P!a5FQPS5+Oyve_2WxBr!#s((t1z(g=zo=&vThn%@7 zxe(7t8!jrI<=+dfqbs^KFs3sgE2f<1###Pz4)a~{*OF2eZW<=@WA72ZvZ#SO9Aqd`yQX_#7#>B7%$+aBn;eXwdf)(Jgy4WZ39- z=pCYbGw*b;l|g^cX#87wt{hhK~2(qwNfj7z1^w$^ea$E!G9Kfnf4$U_fB`2{@a+C z7-nW>Qe`)Z>{Z)9?o?$RJyNJiTU*X8vpcS0O5Gep0}k(^MmCbDGwJjnYikaf&Ki`N zbBbooWqc^VzlP1!QI)9!5E#KM|7{N5@FI9(CRRXAHI3K2OxBo&g4P-DdlyzL11vaz zw<@OLk&$AJS1JhcC@-|#Pl&Lm`?vLq^o5PfnI5Q*59O8wpT+QTjtPq#6=(mgSFp>o zuL@lptdEeRl2%r?3QT##-=&?(q)KAr-BS1*`BB}6?jPLkaT zEqxe;s7H4GX5)zQ2~)XRL1efgb#6%)v8PH&Z`B-U>nV@>^M<53hW)%cuf*hFwov9K zeAVh`n+HS0!89-7s=d6XnuunDZ*Cn=D_#?LvJcI@kGHAZXXlrBlXvH$yru@bk_)~} zw@p`r!ZXzsMQ)Buse}=J{AP<9Th*4}b9WDq=g*%vdH=q?O&829gJEIW+5H@wf9rG5 zzm#{e4-#j!d zclDl2d3)yXGF7wuCG^@cxd(voxdhn@-)=e|OPjm5%^GWOSg_l4ZJ)7Wqd(}JE_RG> zYgsTgIJ7Eyv8pC=r)V*mS}dFMysThgPFnZfjSJl-{?PjyxNf12iRrA%vBa)~Kj3iu z%hp7HM1)|P`}y5V8j0ZVVo~+~JN5AAdk3N5v*QRniq8PLfC*6w+2do9=iI2!lbccO zFQ%}?Jrjs5)kK_ZysgX$+*b|k`P81o@)#iRVuI|r*ZFqS4)!7jd97=Z>$WI9mOcIM zGi!DLNbJCmGR52$0Hx0c?0n*DIU{*6A8TuzYIkx4xy}zKuBiXsr=wp1>qTj&-w%U1nF0?Ne z@Oc_syu8?4B21Xn(MI@;%^!N@wb4Q~5coI5W)B$EC~c+ze;% z@vr8L^b6*T%hQz;WiI7(gpQZ2?6(I5;3vzTy#OWoA6B5+@h`c7kMPfsc&8Gc9)b0l%2D z48`E$gCnf#=;#<69L!H?R&9+Hua2;a1+?%oB?X?y>f-DKu0Rw|+xcB6c#Rbd zvcZ9UsXPVj_to+V4T09V&bLiZn|-#pg?g0v5$@lU&nNa@lUQR4EX5fRn6!jAm17b_ zve#E|Cuo_LWG_#&G&KnY^dD$4q2 z%~==h#mZ&W?^cpaH@M~ociT(5Igc8ryF_{YC!6VtpH^fBOrqiu+!u|uUz2d?eHoDmR8-VMd;3Tfirb>O9*AXZwSY}m;K_2DfPg@7%L628 zeWaKOc<*S5j_moL&Ug$;qmb5BP8F&awJT{GTH{?2SU+>bu+EG-d+9K&R|m3r zOh1CIuyVmViOEmFUKCRxbvzetXzn%kz3|S_)>Lv%4m}ms$q%PWER0ZK1i`24#ZD6y z8d_R|y}cX2>~*!IiyVGTJO~L|erZTZE&K@H^Tmr79eSE!jKVI{^>-5JGv9cv53`Dj zrmk|6=o+-qB@l(T&H*d^<+qv*YYIw-l8Q<^wFoCAeq_|uizd=-$}UZymI=Lx^xwtl zW+mLSmerg9APFjnP;)_`hr`47D<@uMWu2WJ47TI`9Sphf`2FLwT>+OkMBdpaG>>_&-A~9R155^{Zbq`-akU z{bR_v+;{S#?DQiuXT>JhOwr1l_Keeb&L1~I?1X73B%Gyn=zX#4pR4HWS5DB-(^r%^ zG9u+(QEdKAG=mHMm*z?HysMa}dBXGOo3J`SA6T#0!MIn@a_`HSf=8KfAj)CQwS|JP zRB(%ZRt8zf2aM=&!sSt|-#aL&6-I=Wfs%js`UHi==a=6@pn$lzeura*y1KeR6XpBg zb8ULiBQU=xpz{$}|9F(N=-reXeCnVvqNc8XC!W?=PcH>{;GsY4L%`+v5gbsBBo?I> zAwj|M$_EM77#;ir;j_3N2BaXRl$ABwf1`GH?88*ja8*Gi#?jnfSz4+$y{jI*=$WI#4+gnBGPzMQf%2@u>1mJ{%v?h${gS1jt zEA3mRmhfBy>_Q)WJRf|18W9okEk~M$!1nZLC*Sgjqa=I-oGE+FkBZIV$MsU0?M}3 zlk4R7t*x1FfmGYx)`nmTW3;umKRv(T`+QvRa4;tuI?dQyiaYY7vipn z;t>wj;aDo6zgtt`y0t4j+?)$j zw|=2)edVJ21Ndd0v()sX-o_6kst%R>(g}@KIFuC}K~5s1mrBpbcy|15b|>m_HI!L+ z@}f6yQeVCVjxT$0ya+}5=AHPn<+KMX@w8t@vH-X=*>uNWUL3W^au;7lzgp`W*jW_;dU~yuL^Qas(zI%enY8JL4o4J#nkHyXM6y%rSjDHkeV5WD9a0B1q~`Y&~!b1IrR+tCU#*g0%c8SDOh$r zundd~q^&WzvJNtu5vlWfO|2shPrJ*A3n_$~Zsh(Gzdi3BGI|YZ7aEO16s+axz1B;y zqp;B3P1-X^vfYaK`!-UZmasTqAZ_c-Jw|L1{5IDC_~r(u*FQr+(D`R3HHf?rv%>h z>x`c{iai_SmDwcjZVPAO{{8zPoA3*|mkbOxlJHEX@|a>*QePJ3^)VGT+<%=y zFXg*?el$x&L=+W6fQu_$cOYK)QYP?e4foAGe2R>K_BkrWI>$$DW=3nykKF9ZBX)D~ zTc~$F#(DCwE2D<0%;v0EXt7M0%?ZXS%{+M1@SfQ&>3J;O8yxLY#)U--bGLBr$%cxl znJK&uYjd$Gd+GOQO{bk-P*9LQaJLnD&8LF_Nn&DRUomJ$@MS4&JXZRr8a&t($^oj~ zxOFdG=po^>!QCgnryFV+5wOnU;^N&243%$QQ{kP#TW>>oySzBB;ePVty~Ex@Ct(y& z)($dqK8t3@G0v!V9X-82!|Hr+_}OmVV)moZk~tbgjucn2t3gjTFd!dJWiOh3gC0Tr z`W+Mu=-5Czpn`+eLZ(b58+`WOVPx8+ZYLf%{_(a_W7r;h@&iH_uL0!ehFQ17Oml%<-0xOSi?Y zE9ecY2W6l3w#zg;Bw!8)N);eAu!!U|G_PK~&>0Jyd+ZD6laBBX@H`-yDzTJ%U#=0| zOlcUM;``4$Y zk#*E4Ii}UmkB==~W1^!=JO!I2hYv;6WDy`&nXz-K>yZ-clDOMFW+s~+kw zkOKg(djn4ClsO^WS^4(sSrwaN{_gHI6Pshg?_ifE0eR9&c(TjL(3gb?cpvZ118Ng_ zG{nfkfvdA^$=_3p8l>se1xXU<=Ch3F_&XVl2tJS#U=uAu&JQ)SC3Mt)1I+Gp^HDM| z0OkDWPEqS`lpEDn(6+YrNC9DR)dz>Br(H{i zsF~d2;u-0KWn&GMoBmL+?%yY&zgzc{j)Ed8qS=ZPtZ65(eH!q|%_o|?HVw`Lef=vK z5peXGP+u;G0A0V9mA?;ojfj%}=^e&s2Vx+Ac`WPmj@Z#MLv zgrQCUo88;l9iOHdP;8?U64=Nu@Y!+2xCUmHD@p3h0Dr{C#Vu1LzAXWWU+9~7DKT1J z1S;X9J8qgA=vqyA6>INX@wR=!+EjYvlUQE==iN|IQThB5Cn*X8@0s~ancOa#0JgsV zizRHDuvJE&+(K2a9jFUKlHBB*zNAHWL;;zA{z%tY!N$T;Ze05YE><}9%xi~CmT0N> zF3JsFEYfs0^W&bAe4Je;b=4X0LQ9L~KYPc)Fdj=Je6!f@zuFjLK>kKq69JzHH^_ zsAPLB%kuf>?-fq;)rdUOfzf$aIn#q(v2C13)R^L~M+&N#;jMlLD}XZMXvIm6z9wtQ zhzFnf)>kFgv_7=h`|#VdF~w^0neJ1)84lTe;jUXWXYs@qIFHWHCw!6%P?kBi)Tvv!|hKSo$M`2=`ADcQlQenaRtFYIV~-U z4DDVNxOHTtq~&G}?%xZP{rvp2z0}iqv&B7Dt}xO2_rhmLNuTZMYMVR8##F)`&_um= ze#NQv%P}S%~Pu<^XYno>LrG0GH zQFX4l+)CGKMxCUK#ZH8#t=>ReZM$dsz;xE+fu3i$>(sZP2WRsg`PEdjcr<1n-$Vi< z16RT#?)_|(PTN`*)@=|^8;M@dq9hZ_9!NN5Kk}FhQB{4$-3K)VUL~4$t`U_F^{ube zO0nB??B6hY$F(`PHEPS|zH!bdNjsgRO=<@^z?Flor>AFTcGU!cM52^Gy>SSC(&#su zeRK8OKTQJc7C^~MyzrLLFTih&BBG-{nzTv2rD4+Sy9azTEj#>d1bm0v!aRaEPiS~Re~&mo?On7&?WICqLBbNh+}l>PS0 z$sP}tM2xQn9nJWFrwZTU;Al1^7tB~dRxdiZ5n=4OK;lQB)_mNV6aZ>hoEuEN)dsv; zT~*cZQGYsURfGJoplGF~rff|&Bolx1Sk+Y4xiVL_rbhDQ!>{_PthaMVG_lkOu1pCT zmw}R!63eYy_meX%CJvztL({$~E6dmU&gSD!D-*TG>&Lph#=*2je1 zzV;z>8t1XT*BL3l2Ytp|vGbwY*zmHn6XWjl>muKs-?jcO`ytoxT)W+g`5B3tepu^V zqt|9Uz4(6an2dcf9%ZQN+Q0LuL>Lx@dKkVHKW z@)E>d*YWU@ladxPMa`Ri-58O_prHi(-O5N$uLm_%jSyl z%ZOpdWGB+_Q1RrQ;rFYMsyA-`%|vfqy{#YewBvzTX7swPERnaC_)(C?oO>g(R8vcf zLa7=+C_kUD#wKh~WhjNxk2e}LQ%pC7cw}UPp??xd?aIMVg#`sUL4S=E5Zj3~F7;Nx z^Ig&c)`yG083z6PA1tYoju~8!-T8LEQqgT@)ujD{17fo9vM)O{#KcT7%=J`6k9y0gQ<2eG-`bHQ{wq)%8$zmH54?CPUt#9XO z(Wu#%=D{BBO{KtJF)Ra!<~q!Mzw+Tcp7ZQ5CERQ_f^%^ zn@(07rIApacK1OWn|6{k-jmzu~r`{HvJtOIOyMjh$HGa zdN=7cs0JJ?EU!UdgWp;y$4?pV@9*z?PH{sal8Awxj4a}eHW8QCyzwfg^Z4h-Puq=~ z(G4XJKKXmWo)}h`sUjNI-@F2Qn9qJNoO>afx_RMIFow}ayZcX09+ zP*;EKw|jU#-l-8OR#*D$&QZOza;Gvt=S%>=BU-X?bFF|5Gpyt)ftxg$ReVimi$bA1 z;b_5=#uQw6M7aOGjD{@Vp>@sz69N8y&A3x)5;8LaY7S752TVC)zNko7_4Kv$tGaF`H()vEQ%t&12v&>t9 zgCK=eSXdbFXKjAZx6%7I?7mLda{ zcV=h#W3NE!;~&DDoMl(KT&6G+>Ry(Z&cvZ5zb`1JvtASC5Mcl)2M9^L{IpYJ}o5d+`NtJE3JgZfd`4EAca8qprUzSEvkL*_{O4_eacC5XAx_< zqpEQ!-`wR~`?_Wwm*nkL-(Sxbh6~_FQq97;kPPBELe_i17c3zc7Z>QU@X}FrvvSjc zAM!d(7yEd4``7oimR9KoC0_)b84XlV-)!iv0sUAl<^T`RJ%QohSsx#Q6-A=ZBQn8q z`Ev6~A$>tG578!1I;%cz?k*)CGbV(Qa&AAdfUBXYsR`HMmR3%zrc4;)){hTPa5y2a zq@}L@{MoZBSBdcNKUve2jGA2jN4P$2ZiXb5rAo2hLOAR~#+!a8PNkVJJPVyT> zzKs-oDw1qo6RWgYidb)t0il_BPubCt+-PraFTR%{xG^9}WkZNNx(%MczOG#qx8xF$ z?3pX50uYXgqZaXBdU59$uxN#=02ny5>@iTkbi-Ta!?EY5E1R|@x+Wjj%|y}={beAD z@G@r*bIp6>0N4*D5Ng{1Z5c8^-4KKvFWuaG zBlCCCMoVh10}2yDzy~})#!EUk6R8dQgear15?D1gB_+(t`YZHuc5;yKFvRQtJR6k= zK@->(tyiLP0;$Q#wJtM_SK`2}TV4FIu%6#?Wgko&f^2mGZqDVu*-O~tuvSYYr4FG% zDWSmQ-9LZ+pir1x6WX6Jh_ZGYnBvWUUI=t~_Q}8Fp}Z)UXBsE9ZJ*m#Q@9I=5v*~@ z!KkaLk&uum(4fjP%V_SUnwR<4IP2c_)YsoR`6qpQ##7h8zyJ^?gtU}&lOD_xG=#u>tmpi9{PvGtKvK-`}}=EcOvf1Zz|`3%wkJO>U2dhv+y{riG&l%{G| zUAxfPDQCn~O>Bx9SYN)>ovc@~+-g#roOVWp9@knr*!_Sz(%`ZB`TXxsNb^8U zt#!l{6d`Kr4fh6ErNk{zm2GWbS8T=<(8>g+-_J}>k4`30-h-Y2fHrE`9;Z?l4up zLGhaUg=#kkaI+s``w|V+Qahp*$Z~VzE6_Hp_Bf-9a5u(km4R`H|-Lt;zpw?2_=f9{ajC@FTwhSv-r>+(5SjG~b%)Xsd*3d)#ZJV@S8~|k` zKGpu;pHH+hg}<*JK>u)nGQu{JHU{q)ues(*YyWi-{RWt>HgK?R z!%gYvP@)A3`3WuDb9Z;az?&T;&%|dbK4Bv;F~S(HTp%I)elhnYggD=~MlNM#-7XWC zugW}9sm+O4MWV-EVU9y4@?ZN7scf>)%7()Yu5k-m-a~|e)Hv(A7zZx!W}^kDm2jT& z?8s~1w-lu?AIozermt>rPSwj@p~lvYTvqaY+(LSiRFC0j1&A_DsVrX&L}(ql)*Lk1 ziBjESwj5t(Y3Z4DPkUQiuovth=}SN@{FHhAwR4uBQmS24JMoPhwIDYD(CX>A+cgP@ zNoT0ecW(i%f;*3b#(<7>s%=z6#IpiQa`LMHEX0VxKp;_oV|ryA0e}DnX$AtTm2hQy zUvk+Q^Rl!19?wTn@R*F&yV(r*n-b|`H47xOX$5apcY~r+q8e^AallEG6V;3B;&#V) z1k=|jLP(uN9j(82MBgI-qmQs6725mVyFDd3g_aF}HzyJ@%?+!EAjo{gUx~wcM>JUF zPMzC#2*`lT)@TlBOkpz3-ki>1=iH zagL7DH-x)xsS$e-c%&?)I7^I*|$EAB_t`6=v)Pn|=_9KmIe&65v zMC>7ji%2)|twp7mors+yCBeNvFp7SeFf)!!+~m z*g+e_#eKn2T3(J{5kimMeKThFnhm#*kPsgqpM-=2FE8P597-FDN{Rqt7m@Wo_2DO0Y6Rh7afQ684)DOm-uGiS8N)T zFO$9qscWN1tY54*YE-UGSz_LCo5FYeI$UFBFlK7LywPaE%H z>^^%_t7L-8E_UjCitN}l4THv0ws{&%?8L^eavA020B=ZOk)0fFKXzn_om zfEfQy%~y1-wEm*D$Slgs0W#0WFCK=zl1}!28JWpTg zUGZV>tKq%ocZls1sH+-AKM#g*#!tgB83clc7KNy$5WdhYtF%lozo1nmZ6)8!$Uye!|Aa#;Hl``xN>|#y0f=c9>;o1g;4YCvznw@T#vE2grMM4kNde zb%gbSH$G8QyN+QAF7zFskFZBHkY;D^4FL!Yf7huug#*l6z-z;=RiU(r;Mbb9YwQA>w;fludq-^Qqh*faZ`|&_4P$=-#&t~ zDdV<)sm6OOn0$vrJK-Cvq>}7wS-l7shqjnyp$o}u&DR}yqw&T+GTYeVmsw067}>B< z@aFk|({v8y&$c_B?sdgf6sNhHt{OxijMnUoe}O1r;@m=UDnI@GG2b2~zZS_#V}j45 z&Cw@#M1dRjv}>X{2>HR4m_!$MUeRKD)4R!!{UtJtRrE<9im|(M-skVX^Lj$$2U8{(Ico|^fn@d`v3p{~5chXYy0CMFVa;{hwoLPkjTY&U#l9WWFA@%a@Mi|)Qb zMz!wUAi;8rM6I@_20!@^g-L!%Nwfg8iLXysxMiS+4*eR)#B-A7Gu1Z>1M&X@77G1X zk&>RCaQ!MsR$NpBz!X0o;T$`u*kH>h7Ck4AQJ28o2NN{PUxE-R_kYPGunEF^3{s29 zBPBhFbkQitfI?WQwvLMl^I?Z-;s)*boVPYXZ_U=xKRe_2n3!Ra-#`5hOg=b9bciR` zKz}MLW@Y*F7{U-)xjV|l(Kbj@C86eYEkFt#~_!G^O&S$WPGLEg#0sTc+WCM z({pn#+lgeCR#w1Qk>=oV=!_u)UhW~04R^{3&L9l#fcOmznVf;4a`P0}Z8IeGyNnbR z+CYR`+Vk}^pZ(yJHnDj%2)i{^OQyUI%uyc#WWZA(SO`QIh$YN_6;@P^JixfeTc!JS z6W8Ly4^6+C4K%`wk7m6$1YW}AT1)}pn;7j((4w-krt{>l$s^%|MS+Qeh%p9QIT;oe zLEh9c(AkRvkk0`Is+f0zmVj?QA^f}N#mzO3i7Qf{Eh#IrT@8R9;vE;`rcdHrCS5@l z+1uOWCmtRiegiHoh^+AEimG`9%n(YWVv&_?(J7?V#CTJ-0q~}B>pR^abg2~cZBJ)2 zM@aZQp8qgm5wO=uadr08)~_UjNY*YRSea~}Ung*neSZHCVP&_NZKPV>P4ASLLcLA!IUXJ=<1w8{&) z9TeRE+~AMr`&&D`tCZ++A$|Ou<7S7YZ>X#_e;XwehHRq#L)St4BGPN~9{RY{kxbXa z+SW29GKrb?1f45CW~!PRnw1WFVfRX9OR)qEAp^&_DdvrL8OpGVThp4jMIB2GQ@e5 zDm~6b&e5@q5wXVGO&PG7?ZaNAC4C4Zq9JE{Fh&NKX~^q5lE!6ix_ujofzEUuuB;~zjqS~sLfQ?j$;Y~)j% ze>KBAPd9zQy`6Ko+X4z>Yi9@cC@DEP1S263NT>Mnq?g+CuX@|~=Q5lr|PZr)z5o{;T`RV>vGLqoHS_wz?_tae4pteup# zqo%2bt_68(9bOX;q&F&B$jB*3)5yt4mscrPf<*o~DX98)UEDbFYv@kc8!zrY(4>`g zlUg-$@pd7+*!ofon}wD2-Gi3TB_$(LMSleH2vn^6f;g5I#JPg;K8`H|kYe7aRTs50aC(9=_xdftl9qS!Bow;=8m z{)Ysk*WrU+-F`wSgWFR{NGHK~7ZJ>M93rCe3TQB-DL=ya@a~+vXEqFUgHVse%!lr; zy%K4a_bJ=TyXp}bEMhY(svwS=z#Jk-Cv|`e0YmIDz8+cU=Hh|@3PmO|j33|#>}vPb z&_aHdJ!XtTo|m0fzW<}nE~K*px;6?%n@__RlH6b73(!$h1Lo?QAjm4Z`ad5)vH<+8 zbx+VDVv!iHwD>bNs@;sPUX(kxBUl_*2Ef#yrpEF!$YQ;h#8PPpjsx7rM`LAUS2=x% zqpByp2fGu#^*f7C)M^yyWF-uys%8uImMm=%SamuBWX0Z}R3U@6m=+k9)AEeqz^%t;1%==1zN@Gz4 z`E3ep7f{`wsS-f1V$4sZ!s>E&MYl4tPkm&>vAQwmpUoro^Q9GK2}O9KwCo)|pK`}| zC*&KdM*Foe7RN|l36^ZCFqU54;d9qaZ*QAgRbCz=j4FbUB-DR#qp>33(=QO+n~xWsgM^EE zYXz{{H4Mh;K89EZ6fwEvaIYKB0Tw^*B(5QZ-P{h=^Y-PjuI*U?r08uhpHlg$`-^^w%wu zcqxrpJ|uPW0D&L&r$cF!rXPgMbb7>qwy222Oz6HQL|V6bqW48~g9hjdS3P9Buf$i# zIUIrW2^Jyf=5-|QmUa}>)M^|vA0NL0O#~M=LpJ0B^tCy)R69TLMnE`<2OWE(?Cp5D z@xjSzZo6*iowTE-Hz_K2?i9a%y|laxD$^N=oRDdhqe2&0XRaR#MGK>hD8yYM6p?gm zdtLwZOcRCdngp!YJL~?{h)swijk=>nK^c$b!|V>u=ImGdQ9_&w*jIigqzbT>zQuo9yRs0JxcNGy606FhZ(*kDc8BU{^9Ib>1q=u312zY z5g*;YFDfJgfcBiD!}7Iz8$dLM~y787fl#2lrZ<=3yBv@xj~bwuC7iQhFOX^2!3jUou{hG zgo0t*#D6uoP?wmX{C2#iOR=MRB0y^Jf+97Ez5DIm0g4CQ+6RXsY{%BI;7{fb;zSV4 z)p#5cH$Ugclpl8q(yTy5|)NlsOEH7hD0Cga<_?`j0ay~VYn%~QY_ zKkAgo zXABafH13qFE4|F=wSpDh5x22y`So#sym+<8#G8gMU5S(JH9hk$NU2-`N4!`s&MmNm ze+B!4szRMA>R~2ItT1FlhBvH!qxr6O)%&0d0zKB0!S`tIhU2*2&2SyPJT$F9>s=?3 zId&-4E8Q>g3;fl^8ix1vt1Eiz55=&$7jjt{EpP1kZi?MNFzwPq++H5}h3~njAZ#08 zC!vP}OBi7ZgSv~G9XB7W%9ZCt1-eEI4(4ldWjdKQ8@wmEJI2H8Si)c4lqSUCY}Q-# z``Hf+TGHv=DB9vwm%?#c%AA60vVu&ufI|RD0(tBOv2dbd1|G+v!kkX%nQX??pW`;p zATc(%ykM~Cpsqq*{}H5e=}t$udPf}3?&|+;b0crc`XIO3n^(~NHIl%3^O*^TlDD{_ zNZMl6HQ%9h*~I2o1SrU(K|=rbzSVKh*681&W&+{{mh)?jBSBDcCf=EX*8A#~My7VM zz)=>%+p<2>?|1a#ZbpxC8<&(FD`|Wzr+>*=tEQ1$_E}8mYFB6o}*i=5Igwm1WZSFWXd) zG>z<~e8X=p&LLyJ+Q z>H8iI$p-4-&Mx`*aq8{;C30K~*Te8{ysbzq-tRXPq}n7uWakD~lw*f(jz~n83CNel z2&9Y%i|COU92p*}DC+Y$Ll1jDJ+*pcnp^Jw@%5HrQGMV0ztRmumz31dsdP(scXy|R zpu_+}cSwUYNQsn$G=c&W4k;xJAt529_}{!g-`~@}*X5I8=FFLM&fa_NweI`1;PYK` z+aqYq)G58btm2$n5W+C|r|Vz0gY`tnIhrQTu-fm!p9X~YP3C2rHfp;t%85cgbChf{ zl($5`s-Q8tFfd?&Y~v-US}ku4Qf!Ja7iwH- zzZXn)6*TT*Hi{5flDvBvZ-(~`jvlBb`;aeV6aOa42-Vv|hu=OamR@bo*MRyPKW7m| zyBZ(0<3J7X6?V2-F_Qi?;iCaHeJuSZZ*tV0Z0YHDN{L}y;ZKyYRttXYi1o`inPo2r zHoObC0?vaboeP%8TeJ2R>ymUWcX3~d)z^2SUDjR<##c^3M}-k$!1T9r6uzc~wtotg zi+b;L=(V*j(9qB>JI$iSDVX3lk<&3Bjysj*{CHlH7}3Uh#raU$Rr_o`e?X79b+#=r zQeWZIB(5yXrzx72uM3k-``Aj`WbNC?9$6G9v)~)YzUJ9sl zAM#6$4ta}Rjf)Yq5(va~R%z;F`dKOssMYjmKTIBu&o@EbZf`BLrgLL6qP$%xd1|~} zX=A|p#=Ew%T3^ppdgx@^oxt&1tC_IEMMxyC;Z+Enj~ow!b9G@PIw7Q%tphWL2*p-k zyGRzkjy_I6X=l9cGb%)$Z~PZB@y6j1rT~_}SJU3`iw^;`JVjS_BUA((0=EZS#@vM7 zY*>-nN8Pz5+&V|yd;~~gu@64%75&!Qq|f;D2mi9AfJyHm{7)EQWr^aL7bRjab8^&5=k`h*_(&L$^6{-?^T3M zIMIO7P!pw$w%!+!u0^Btl&#C#=2Hh&GU}0yDDO@}0;xK;ZS$nTM~3VjTiLPBEoo5% zc~(sB;XdBUPW%Ld8U8i}CP+}??T8)a*nIn&HpjUGWua~*)Kw~3xOk!V<<)CYVrhj} zy2RMr7wAB$36#QtzL)F^s}B-Q1j?lZYSg%%D(SVR0tf{yW1V?@(L-JQ(JJy9%Op6` zit2|J&48hMFO}D@SzXs)d6cB)|`Oj*2EVY_+2l0~^8K;9> z_bX7C2k5TD=TUcfQ)Inhg**gPxfT{bp1i@l>S#nJD7{evnYYLiuwFL$M8RCaJHRR% z>m&0$r>IB@_<+H(dUrmn_4((DH^J!r_FXDe#B@CT0Rb}l{kx0q`mI(sgeTat4{Z+C zT#Ix_-Tpqj*I@B$c3vlfqz&GW6Wgp0lVIU~Bw;kI0zz=$RdfbA1W@Eva#ZQBy%uTW z1Gxy#o{Mil48(2R^wE?Hh*_oF7x+Y8dB<8yz684k@CG2y7#SWmX&kh|9Fd*qw&uIK z+DegsZ^W@yH{tPidtD4cvYn}5@ReGMzRs^^b9t5(UHhGakhu7=nPE&&w1<{?=>3Hv zHq^;XOvF2CXcpTBH8Whh0qVBlUSnFD3cY!e(D!DZvjx`-A_P{$ zRaI4kpOP&&!X~dp;>%8a>c5`mrJ$;@Q{n;Z3Xx(;fK+@LNcdp#*2WQTQG5+p4Y5o; zb`m4dPqv{17vznj4>m$RLWY5x`^&a(X<hfYvLB#1?wjCe3@j%u{F8=6X2)DfwI z)Jjd-`=>ss$}ycAPV-+B-*$f+I($B>7aaUg3=E5rW112a- zNoo9+f%1WZe&1573CEx^l{NB{m(4}&+#%pBxborIf#`cKy`9w<>%A)>89k0V#*J?F zHVB-$IyyQ47YXdfHWD7N2bpAtNfpoT4H1_bB0s%TE4hM*-LsrP9@y=oj0d9ijr>Xa?gX%wP%a6Bb->kc*mU~?o<1hR}E zNs{3B)ct^m@2zVN`C{y@5Lo&n_D3j2ho=&V;a8H5=KyhQi$v#6WX&Dm^(F2zpQ0@i**S(&q1y9b} zaY|z1=kf6t3>|9&zKqKXL!`_57w|>r9>WypH=5e7f*(Mx72LCPv-|@jp)@(fbPNlOLOU zC(IdLML9`o5O#jqY`&Hdw0Fx&9#MJr=0mmy5}<+u$29Jw1~@95vWM>G$%^%D{dH1t zE{=sfs&lE@U^n|aheWS2k_U$dNgGhl7Q{`~*M5R>Nq>os*ue8J-(1%u zGj-E2;FM&4m2{)6l3oPN%L4;)x{G-(U`GlnF&haSzBeI{Hwym5m7(&olR?S2K_I7J zo$?#4xd{&-dt67GJuZ6TT7z0y^c$z$Ge0&>Tg zE`(*l1&K>BNoo&Q`lHJi?v1O|FHDJG1^=eL=f|oTtOjqb`5hU~(`cYZhI4Tfjvou8M!`I^FBJ*SqY z#w&NrXtf_7>p(xU5Q&bfUmm-^_WJ9bm>jGDXP8(7+2$dA`}18^Ru;E;%GNvJvFhsT z0wba5ZSV^i7Ddv^0ST3%KACA1y2jgnW-+#xkL6&Y;k7^26!I!1!K1;wY*8wvZ)kX* ze5*~eM&dt}Gg3ObR?z#1TlHDHnxAg|VWRi7OmzDbA)#(S`vXO#suWjMoRNF~roBL1 zNecVXW4RZM^w((U{B*l*F&L;{D^tKHgg4kZIBwcs-L}WXV0^RH!#7Ag26?1c0MP)Q zRu=R42W{ovHwzW?_UWGZTNZ_XPO! zG^p+m#!>LcVdY@!$BLju%lE_>CY?yHUFv9ynirmdchI%a-L2brYR|bd+W3Cw0C#uUa+R;znggAqf3vSL3pnRbJ z^Mr?xq9cWi@l2Gc_x{biB=m)SrUqV^4sL3} z;APnEw;KKN%dlMgHvyfbj1~G%i0AUhkRAU*;{s7g+k=4YV(VAOK^KF8^KY6O(nErI zRGc&vIjm|=n;8;M9lEsg6_mrqQkiPH)8wt8tzYJZ+;TMGdNJfMYf9%g;en?_*CJda z3-M%}?95YT+l?HjkkUtK{B>AQwsXz-UT)N^pMN^rlc@cxhJXP9wP{z=WXs zNz@3TFh(Y%p~jlg)i8MUqpQJVIqmW&JxWZ2Pcpd3DX(&d^o%IDN-Nv|pdVKW`=219 z_FJ&p>5U}bw6*;PYol$V>`1@d$4$uBH!E_`S3uD3lz6z%{00cWgFTm$r%8K{t8{B3 zBT)>HnQ%uS!f6j>vbqt2#YWJ&r!qUx8a+8Lz;EVc8axPBjsWpgGL;|(1a0GGqz-U!X$MbcLY zi@^S*%!ATr9hBkq(Dvuwi{6KpcmFpzbDMV%vDjrjl>C1~7}AMg6L>Jv6ngCoOX#P@ z-3lr<|G)9t2lMB{&YegLBF-X7xgYN)_i#?g< z@Re-1A50yzX}33;9Kmuu^0MRDcX>KD(EH)U zBQ*>B_~NRipWlDr2U;hRMB^cADbBxA6N%`h62OX5-Ii5*CPl?y}6E z|2K_9OO=*%3y6C|!@(Ctmv`a1}o{f03`ADF(sQ#Er zLriJ-gAiJNW{Yi)HENY~L7oay-K(Oiy{>~UQJO@+6`wkz9gSdyC710--bZsE;r}@U z1vN;R%G0-eCA(5^{VmI6$VXVSb#;%DZ-T!Q?c&Q};dCp=VF|zmdCEUdPJUv5P$t>Y zPk#C7Oze`!ioQz5Xeq0DI33&eV6uZbF8wx_#Og|vOc<7#eika4tBN-AJyjQn(YNNG zFd_S{!Vr~HXQh{jQm&-;-^8n#Kbs^Bc?*{@HVjh@ZqCFHa$y4b?0X_c>0P$OayU4_ zQ5|3gjy~KBX{dBrClXL17l6)CQ=XpbOjnRRi8<&h@Ecm)V6HOb+;OIBa>84+PArvj zG_^n4{U!7`eXBb1Ba}?Z#88|*UQz}{FP`DopZM(aFe`+A6OL8+C7B(U(XHwgim!2p zVc^|q9G^Fy#8i|z0zDJ^*(c^}7s^-6p{&o8 zQmRDv$%t&VOi(sle@^$ehBsBswjCOCVGtM8UAPj&j8vTaln70RA+pjXMk{l(GJf%m z$T~vHE9F@S^TColH^zWVl;$hrU_OmrB7!Egc~)6vE~6m=vC-c>>leN5^`cbd$QlT5 zrtK8;;k){#KC5r)7(^dPl>W(Hf=;VKcQ*=UhqS0w6T^^^y<3YEE$oMCye{3#Z603> zBUP6?(Tsil9O{#xp9`UaD%M~UC{nGkZKixqLVftdULtQ~{uidxmlK6c9_#<71+YV+ z7h}oS#1~XOtHR?x)r~C^uwEZO%dYE(Z3XkqSGuUm{xbfoS<%5!$HHP?Tz|QD5=2vJ zgSsAGQ;<*al55zOpAl_eM-CT_e!g|3o?_08EEy$?)U(?>NlElKq;U84B^UX`Cu=zS zVN&W$o5w^8pLBtrE2|}^k#(X9hXX@@K@*0ZEi~i-BSn7Kv4Jnig z14yjmY~2xC{)rZLB8zw5tKML@A+ak|?&)Rak zN572m`msd(*H5P;&eVb2_S=DYo)*z;1He{;U8XMk&`zX2+H1XP98Hea$;+^3iMloV zc6N`2Ovbcx#O4sBatm!?z+qWrRYOF9*M62jgn|5%Wn`IF_?|xf6EpgkK&bJX2_F{` zw;gE+n{9bMH9wQ0xswt8&oykd46+5XwcGEPCA0pmCa1bERcT!`%;QktP<(FjG)>vQ zaN*DDFYOZ>`%KX%$?Y_AOpvrp;-&N(JrVsr+|b43a3U0&ofbW=&gnmM@&;WpY!YI9 z4BOZL7T1HNwiFU!ym~eIY$=)#E$aX{z4R~3OUvZY9;#{_6&nlMW0B-HAu;2u0qLh{ zYaFVsd6iot&Bl0@T4-6%@vZT_%P8&Cw{%spw@Mmx+A8+do1RD+K5sPzxT8Z(5?4%V z0sIa$eF+|jOWw}JM4|!QI)W~l8xJt7V{7=N~xPxGU&-n~ zjVP^R z01>glH2R#bTv1NuzF-dIaMTGDJI|GaBfS+eGJSzNF_wUC$Z?W=ve2i+cZy9w^8+=f z?K|xr`50R!zo#?56&#mpGXVmGO4OHGq?;pW**x^?HpU>|Krd|xNiG^!Tk1gQ8vPS@ zN=Sun4g82GEpsNGs`q%gjXUZnL#81HhFUQYo#u)FweF&x5TvVeaCDHwQ83*(Hgp?! z9ic;%nU_qBhnhI5e;*$B(P9?G@eDHW4h%Fum=*paR6Z(#G)JJT&gNQ-viN;nKka_X zM2j^|m9kTS@_~eQsnTU+%V(8uXuDkJgzF)1Bpk*oI*Tu_43$K%FlEp7X4J-5un#6v zkdssAEQSbU8eOSxB?|F$9^bA^t8D!!4ixF9oA*aQdrB+=W;a$%)|QXHl4Z{oI}D0# z^5n3T8^@34vi|(0Cs9=}*mtb@^fs@uLKn*76kHVeTC_QMLO;^4!ABmebWbdx?4Y`{ z-yZM5w<_}_wl!-?42~?>Yc-@MdPe2JR{gcGtOTeQQl9~7B-sT6D%ENvP;}}H=<9Sg{~pHcCf8f zS(&#IGcuF6gnLwQmBl_8b5%yVWqly~G|2Hc%a3m8qENca6+09CbslA!LvVcG?aZ`Dcw;|7hMYD;!pdVu%W(L>0-t-P1sFc%()oV&PBn zR;vr=CPR3UuJwo5lEYxSWXkxC@*FNl(PY%k1m~Ajnf=;sqy)8w22S>3Q^|F+@u=uk z^{RnNsQ40+>^P|eJre@LTz}uykdLuArTeISicuV?K$a`txCYxud^Y{VbQz}gi|J=U z=KqT`t8*S8JahD+OOF=3r+Wv#5zlpf>;(}b;vyy{CP*Ur&{A7lrgVQ%)f8D^cM3J8 z0FQ1miNVPml3tTh=`sVNM4o}jhy|KdvP>H7Jpp4Bb#Xhc)w37 z{>CvJRWS$UHQ~wuX3q@ry{*WZf4BXKK67v}RanM|qr&Apuid1zTvvxc_A|pjY@wBA zqgD5r`ltuwI=v%0!KzQFO(`*d$00~@7mRaSxA}$i+L^?Zsx+VC1{S$=31o&zqg&G) z?Cgw(d1!5*TTGn7fRM(VLX<%~ ze+);~w1l=1K{>skdtN1l?z2H=MFRz+euTiI#?b4T%x@3W^Wd4Ox$3L{$0nmn^aVWV z4kqK9rOJIRxX>G7)+3vQf)MkOEUJVtO1&_pJkvTR?Iz1yU91|_N0{yK(XHLhES~Gn zv!a%%T_5+dEcidmu^X?PbiqupoFBQ0kC)64lI-QHL8DLLSouoNvkzECLz(2Ibj9D7 zmC0qvp|EULbE&Z!~wXL8QtE6ggYwz6G9qgvec zxWt7>_RieGP#7QHyI22u<3Dg7M!(0FTMek;I3n7)4THT@4Kl4CH=* zG+K~rQ^ecNp;F*AHl{+1+=ZwVF!HD7rm8Ei`CzXe!FVf3d!+&8ipd9vB{gP6oDyB+ z;QAaDA!q(5r<%nN1hwVdMA+F8@*mK`SL;JDfEN1j-tYJ!koS-9>(+bBFwjiSBM_js z#R^dC1NqJk5NP8~V3-^C!vtAA)s}0FELa4tg5D>+_X=eS?AJ=Zmxa9O*1ydW+`PQ^ zmRrE^sB}@svQ7MbwAyYv9Dj2Yg44+)f-qRrbeM}Vf!52z-lMVTS8$W{XVqG{F9cO= zJ|6h(!>I5?`u8@L1p@d0|HNs9M*0m1L+WSaVJ+x=)!JG zqtz0-3adr?OAOW**{|q~Ljb(;Y7qZv0Z3w8AdHG7f(6JEfLPxaG$80|;5BQjFD)$v zO8qQVsGb2wmacjpKhE_R7!e)P`8N|~MuVGMo+D*mrX0eD?o@MqjB}yQBhS&sW&=1U zEf9d_aQIr#3A9__<)s&NB!%3c^~2$autB~=1t!Ld#)B>*y61lF%1W89+;S_)G^qaTBRR@p)7VK;yE7zj8si5EP8;0@bdxypI{Ku zbpsJL5E6!yROv$bur+4!2B!mhou-aJi9VzVRi0-|5{3#5%PkZ^CZ-{;yCwq)Z+mek zGVrK_V8nn90e=`t8f&>7!bxV!i_1`_hu>yaq9DtTZ(2tI!uv z*#}qN-}G<$${98l&R@f_q%9G?9L_dc(PlGj?NngK$*%-@0xwYb@SgmtB&&K2oX;Tt z#rIl0r08A{2u9n#J*`*wr0)VzKj0+q0mg-S#M1`j=1oAsIYUm}lR_M*YBJw}2mgN1 z1;#9G1b+v{#{1ykGT6b3Ni4A8UW5%K^V(VXQBhx2e$Pr^wzVE~>FNSb3@hg9+7r)} zp!JUr5dmL+I01$+D1Se}d)svrq@nQz_$}_eXh5pA0>1qH`}gz=j}#^#_)T=G@NUvutpRz`y4K)Qiua zq3h0keKQKsL}msC(FS_%P&10kiG$tXA|$TStkANG?_~5je2k)DtCFan-{mt5)c<<` z?~7za>m>BJ@9rEae$=sbZ%zOjN-8^Wiuc7MptBT85>0Lg5_I53G?{H(eCKqJkOmbV z;97X9(NoIU3@#OL;zYjs4DipOsys%s3~brJxq@i4`Uk|Lev>TM$@%f4IBIHY6kNvP z@1N6pfjVNH;oHcYNJ5aJGCKr3mdnsGXn7{UP1q*hE0#GcLY1GQ5acxR}q+XAqoT4DQopo_=~K&1iK(Czu2A1EQK zL34PmlD!`>Du8po$IIN)3eIP&Dl);tWZq=`9h@wfJ?lO*==3H;B7>Wa-GSKSp5iGH zYzxpBz_n)zrT}2v13m;W%9)<*ROWk910W^1Ye1ST>O5TnOb$d4)1&GjHu&o{5BYL3 zxs~V39)Lr3?`^cOuxPaGsRT9zP!IzqKMGP(@5zaY3Eu1k~{|D8*x3~jQ zU<%FCBXHgB>u&w+zLF|A4>CtH{peDL!H3$97eF9(ug~9>$pehJpNmhsZ;yVDkAuZE zm){Q9{q7Y2z$pV#>W1I${Vm|c-!?Y(0c+Dp@}m#nJ}xaS9W;FK1U@5Blsf{1B%p40 z`jUrk6@AJbk#oNkWjlbxGN|PV0$b&4KATXEEYrT1Jq{*u3Uafpz#}%I-?I95Ie3)K zn-bK}9;TN|)LGP52aP@?0DqNZ`~jX3jahYOVg*<$Hj~_MrclB$Twy%<*#)3D1OTK@ zqQ~=fhd9pwhC^GNevEZHxX#hjY957J< zbo;Qdn0Y{x($o>c+)smP(K?rUE8VAN?=wXMnV`B_pk%(!Vo$wU*x=&jWq14G>RSc> zU2g}Ilo^m2*vGhxp6IBT3_Bgpxx@R1tfz6Llh*k=V`a(bKhbswO1Gel3ADsMU<*j~ znkynz1wA$M|>h@y=nwDw_Akf3>Er zk#41@KA(B*?9qtxtQpPM!v((g(ScmuEndfOMr##QsDeB8*W(e{{q@8N`G~1*x(N*~ z3mKcw3p!xjne;eb?6f~s=?71+nYno=sIfPgyi-Isc>09(_4hk}0g!|jF(mv+YA%G> z>au^Z9G%x1BvGWyT*A-Li_+=q{XW8sZ-h7whKvJCx!oita23PnzkM6%e0y+1|L;nv z$%ADT&;&+(3gY8&mfJwaS*5|n6qw))wh0|{Q)Ej2)PVf~n1)_giUNozpwd_XA^P_A zHrVP0g9Hyq!#JCK1C}B7g+HJq{8%v&6z`m!ox=kk9yw>Rx#u}rSgZog`a<8Ue_#{= zCo>3#&JPe;7sRQ$-{$8b5Oc$(uveQT#e7MW>L*Sz~Yi`hY^A0H#+(k$W+bE&4s(~cTJxEo^l#r z%fQR8iH9U-#(k6NlmxS(Q0P_D>FFu3CO_sPB<%xbBLLO7cP9c%4IGQ?vA+c(;7EkP zbVM&t+iJUh3u zfuE+c;RRS0K(vC*2;fK*Kl+=%l&)PpOd_$Nd@vgj2B^3YOcpueG$tWT5jK>|^u}7c zpkCC=@JwH@yxjYZWo2c*#wM`$y!laM_0E@l~lb@4D{Y{1}ON{ zT=-bqp}oL0PzK2SuqnScd-f8McBBm3T9ZNGSPrYebNPJUzzEdu?y~~T#_Fn|M^X{E zZnFVBU0tSF902Qp_t=4r(hkheayUbuWV}#2eJt9`%bBxOfmsklEa2(^2f7<525avC zD08BKIo5-SBlZdjza2bVFW@!p4608B87c5ISJ%}=nQ+bglOksLRf|J_p2Z~mH#BSH zps}yW?`|`J7ifdD{(~KBP)ct^mPN#*DKi829BW28GqSs_t&RWd!W$(*vOd7JOdL|l zHU9V-Ok!KO|A5i>JJ_B$j8NHuwU3vRQ`~=-=qK7VuE*4NcVWeTx8PoNDjWP`Zl$;D z3mEx>ZHk+r--qG=k1h&)z@SvAoeI^f1bnHzh2}i4E{7Ch%93T>(T+yd+f$AJXFN%q z0@$I!UxLnpzza!K6L%nr1OpUY6F}|zB{AhwP}k744~(DyKLDE%)kmaakq&VluYPDR zDOm^mlu%#}4O{bacLzkYTl}Xh>$mp{b93`A5aIyHTclY7Q1Ntk(~kE06xa(azBPL~ z(E_%-U=0Q;#Dr77YzKZ@fY%yPp%U@74h{yHq_KH9Y^XtWE0|Kjjt8)}Vh6o|<^7)V z1-Re}+C9>Uv&`dg;*m(zsc}9!0xllVDwZ_^91gV@w4mtb4eh|L$UI^CNDzmI2e$>U z(}>zKYia{105HL=cMu3z>o*td@4GS%&DH3EZE|{z;_5nx`HG)-P(Q&-rdhE*yc<5# ziL_4am!biwY}Ws!vU^d-d7mGGYWC)@tGSvC02o6|PnWouf@viAQ5~4=jEuGEOtyFP zDhZ(21=e5opL%u%+)?y-U%q}<_2S~^VaV^BfPV)f1do&8Fs%bb^PF?f^2H6aQx`JWb^beO1ViW0b7Xq@2~ZYPpy2YEnV7^?Ix;!G&p)Uc zcVk$?C(9{)9BF{{FI24#gjv8?xB+}djWspk7Bb+VgA5`Ozd;apmMS(Qm!QR0H?X>c zCCZ}J`RhGqHf0pt2;2*T9FW{TUGI+qIDM1TU+c*iFuJD1Ei>FTAEc?n{;{&mGUw>Vw|S~)AgQl=|1H(de~?}P<98aY zJ1>gbk7c$&>~CaXFie?YzHAkI(q|du^J8oSgobN9PK}i=pd?>tpU1nH@6}V-x(Wu2 zAXp{VUMn+$c^r|d3=SEKyc4k*fC2DzUPjfEo}SKTe$kL~<;UcK63yVZ=H=O`Kb6?v zZ+P@p>h34~zx#e7U{}Yq^y~W(c&u<#A}ib=zS(~hY_MC%^eGgG-+wch@cwk#JQl?O_-Zs6-Gc78y=w7Ro7H46+?it)xKh93ML3^-GV6Y15Uv$trT+-6eb+BL0|1EJ0 zILr$mFsQYvw6%$I1~&cN8E^w1Tgn+E3FB>@Y=G7hw1dPl&ND)GKq%qu@0Q011IY#P zZb+p|SlhGt6gB=`Lerkdx(W|@Jl5E49772#_C;J(pFQ6Ue;19A)%N+n{`rhI5 zMxaqVIjRFydDLR2i<2?neX~}Yg9*pL3lMC9iBd7b_%jlOF=m)Ajdd{QZ?;s@tl(f+)p}*f1YgFihBpi-81F)0| zI{5=s=GtXD=nSL4&c_7<7YKaMEuUtAz%x37EZB1;AX{*-Lb;GVj3}8xY{%^EAKFta z!a$E=V=*lc>b0DOgc^O6WM@VN2|BfV1&<5$Ss3Bx+=_%)zbQWs;XRV#$(SYCr4 zG|U3Ao}*R)dR)`Q?1(@oJ1a|dlTy=Rsd*!L2%4p#YgWpP})L;6KNhYmB18^BJI>>pcJ>!I!GhFL%Y-56fNl z6IGZtf;F2$dm9d#bN7OaCPgv^F2mFiu!G%1i8W+P^vt7zJOoDv-da3$Fa`u3kx9H1(}4k<-h%Vvrh01B=+9*vtLXIAA@$) z`u7?B{ta&GkBi6HCPi`z_*r?bT97VSIm|37D(1v)36(`kp>oj@{3F>s7C6^f>L@PN z7Ek6i+wxUQ6N5@bYCWAfvRb%s)lqc02kfC(Y5D;!L}H*Jg3JK3h!Cz=`&bDlH^fFq zUmIUyA&8`Ld0Wj#C79CttD&_EAGa||ZtbAu)5@h~e(Y}c$h3Vcn7IFjc3Q%^*$JN=>KnU4-K{29@7s4J zb_{p0CL!muN|(*|`zoVyh4iHx%_TY90tqzK)Z-HqU}Fgo|6n@?cFJ)OH0y+A5(gA}u=H?V4o4M%X33&eK#FxJZ;Kp4ONMuvnv*ZlOZ>kA$sZQq77`=<A?`=I-C13!=G8wP25 zm|}pm#~s{)AtPodqMW%H+ZN@Ah!>(;!;$;?`QD8|8xKl1=x(J{*rQeFE}(8>@^I;xLf_U&)xZA3-!j+bqVNSoPD0M z?6A*^&aMlG}`Bs=$D0hF0j1r5dqkQ1@E13X7vkM|Y{_rdT^cErK=v>+>P8odhXJ%u>tM5(jY~C-PxLyP)@NisjDE zoN5Ota~m#_8mX(gc1D>t-mW?!wfht6Ut6ULH2RpPi{RyFRJo}4+8l2MBsUmrB49)@ z4Ih%_6%4)qZ9~mS%iI&VP2ReC z*@Y}QzK*1H`b;sPvIu%)=8TcVoR&tKr#*{sIGtV1>TEdZMN)O~psojS{_KiudHZO> zs(9!b(=G?QiYmg#-{L7gDsSl8<@A-TYC3V!6G^R)IOTpCK~hq62VEl_$YAQN0-ufR zy#*$#-O7kB5B{}nZWBEw@`{{Sf7+$I2wK*r`)Q_^^Oh48hmAlYlt z*Z6p1RbVboYe0 zOwLR1dTQ@!00s>K&=%Ctm?HvIdXd4#vQ`}m<$EWjpA?u%55U|ytMCGwD^^}1rc?#0 zSA&LJ3GkQ|3R~MQFyoJccPJ+EGfFa|ZRuh~|hKE!aMYEosgngeAz}gclA!QK6BAApFh1FIo=@*jX_E(8`8Un6D;$(DrT)Ge2!{+jI+i}v!@qx?)R=pOcN6W08ALK| zjb|SR6sdR@E%(%zi*TGL0uG(S-S+>}@Hc>pKP)MWAA_W{ zXQrgxQR#*}t8@J4Z*E5M{ssM7)%VW&u1fRe$$G$N#dNIT?$Ff<;FMOL;Mvn`DwmdzQB(m(G{JKDo|uq@VKbTxbUeOPC4 z=O6Q~DuguTRmw^*gDNxL2tJfu5Yn*ktD~mL4v=*L3qeO)gAr$=xO6braz6cajl0BS zc?V4M4OOU#voLfQ5kBy-t6}-MK;hk9@fvDvOp)s0P-AeZmeAi*k=S@G|L*0cvv9A{ z%|d&2WKpy)1k{S8kRjx(UsW19pGZLT#Xr;JjSFlT>=dYVg==ZUm4yRek*hvBre)9q zQWn08MEuwPoK3}9xO1xiaAilovS!SbVnpS%`qp7=P@-2U@9b+LB>TxGHE(tsgB^GE zWApxxB>`7bf#rPeP>vtHjubbda$>T|?aBu#{W!G~)(P^Fqy@d^c5E*%#_?*#r4{Z~JE?80?ntXz+)(w}o|7lK=rdgXEbj_SD8yZD4UI)xIVVy{E$H+2?$K7GfY<%{xtt)7$gKR*d{8{m53yA?T> zx{4#_gtdbZa{Rf3lW=w_yiO?H^RP;znfKUz2j@c z02Q8(4?y*dz%5T;3|{h16`{}sd3Q9HvQlC)x1Si8SE_2VC;UEE>bpL+DJVZM<~I%Rv4M#pulxbdWI z^^QGvn;03*(WZXIZ9KBMY!;u1;@H=&(6Z4Ct%oM`&iJt{aK0#zTcE`{&pJ@)I*lBV zmv2?JyowoGI^WuO;n?$vZl+`h{?hMk@l>MyqgtM2v5(Jl!=?Q_+lVyru;ra$;}Obc zE({f%x58hCYYRFR^}1FHw)qZh-y2joi^b9hhfP%0W=qeX^R=CJ(#HW+4vxUst@d49 zL%lkYPKI{IH{jl>nZLemRqx|ToXMd0#_M{(*K%}GpIF`lYtd9R6OL(fjn(){9rHJh zzLRgFJI=1=+4xK(_W>~qA(eIh)+JS@WT*-5UECA?JMwHUc{dlyfybGQDPFr;9zEOW zW4KZf%aqniT7~{64q2jfZZFrjGK%Ui-Fw2**he=2X8G& z(m>{T#V&E9QMq46viG@Gr~Vp_O&_JaygcengiN!{V8Qd?f;_2e{sE_t~)^YWE` zR~f!)_6}rJGCJx%)R|;4EdDhqc2(XEAUd*J^a}iDl@Shp=A%ODR#y=N1Msou?l8RS zj}M^^EAhcau~pWc0kfcl)x?zmKVdU?PxN@Sv8%yv(jZWArK!dfmLkOV@%hJ}FY(Ns z^Rs3Bs@79XoH0K%VOJ0_(WP+CrsA=fG6fPIutXN+0h#_~C}H1CZ=+jR`5F3W{^nY~ zGE)&eLM?wcd! z+-kcDay=$m;mYauL*`S${_@KGR1^ZM>O|Y>O=2RT9l%I^?%@KHvq*IV%$^GdAGWQ| z0HVTgny@^^+5u^zN*yEC31-;Ra%JqN3+jWsqrlbJV;EKGfbPbWsKVj1^5zrtpu#ySbc$0Hj>hai^4YEK_)q{y`~+~3cnA=*S)zN{3GjH(fe4M>icKqRVLqLUg6 z_a>QWA&x_fZ-^2seI;OsBT%eb;Q8UG%>hrKnzgFL+*M7%9S69>dxzqbhoUU1@SQ{NB zEAKfN*%KG*yG8t3bLooD)UH;a+h;XH%ADjlqCYNkE9q}3qbU`7dy@Az%U z^!qxRjkKS%(V0zvx^{M|_3jM_JhP?6D+jcDu z7cz>Ex7wY3(tNR0X*X*PyW?)pf(OeXKBg%pvJWa2e`xf0<5!`AAb~)FsELRvL=@iUf`*Al~fSx zJIUF(q9l=`GhvQ_=9w#>YA==FzBEF0iz=-S`#&RrVANqJ=V9|hbsulS%TS`8=08#q z+W1a3%ZwNO!r$Td;d0L1$-wW)dBNa^cG?)>sjw~duU>Q&JLy-s#_^c#a8lbLk-9=l zCHnwxayuShj!uC2)gPt&IfT;niWeDgpTBZN`L}{t$_p zei#r)`eYpsAh%-sV%mGdYOV?c=$opuJ`e2^vd*o+GYj!xChgAOkIE$g?SVr9De6|d5%Ry<)qln?*T$5Z#5*RG-Ztt6%K#jJhw!flX-;;~ ze>ob2Bk)29!tI#hLt9PjP|??>h=?m!u7JG>nRKew5-16@*Isgu!@qQF2r;5*&^&!xWqseSU{n1yyELBQBPZtMH8CbIKbB7`#V zcE_ro$+Tg1QQmU3tV2{jcaJ z9^0MOc8T!nA6=Q}Nk3K4-47uN6A?VxL$lGyeW&wCPbhSko7NwDfwpQQz1e)Ok%(#} zU=)M6h@VIhZ(UW^293|M#rl8%gQOb1US|8x?tONm=jgi!%8CxtT?A8jY++-|3K8g+ z;~iy}jPF>!aUJt+J=gT#Gb9=<9fDVA`capLXCZ5#LabOV<~+;}ErnD&D1}#F^A=^v zU9wc-ikP^@@~de#+_`;TZ!@KAC2~uo=!C*25!y0)d1K8*QsJA-tdD%Z*eQ&B_@DG_?)2(3drCqjPe4{|m!Rj}pW0{wQ)~+hJRE8f;*IKsy7k$2ut#4T#_0e} z%(412Xxs$WQ^0f=EQ~SIe4LSy0SW21fBwOOdj{O`b5ep@zrpUE^a16W4TlD5(Q579 zu48{dxFXPV<>cfXBK@tOi(TkQV1o*1*H5t-)yWm0>>%rItfn@>>vED10xL?t)CW~Q z(3xoaQTOe0-!jOSssxL0*aslkf%Sg&&(D!ReG{!9hXrF81cJcMMyIMAr6cIPW#=km z-T#2mz|#S)Ernt)?1T0RK1naankp}kdkLzGXf zPd-YFwFjxR9&fwjK1mX-26iyM1&reQxk?6jxU4;ue4a(TqwA4Vn0|AQ<806DwIP8E zgUrQ5vt4USKW=JiI@&jktA_u}#P$3)Eu?y*a|)VB2x0GUNa+tut^_M_3FU)a*a?!^ z0xDOA8*Ru|+aMJoEOexSc3cfIVp(aaL&7L%v|IoEf{lqJ-K*7Z(I7Fa7wZp#ZgZ<* zxD8LnzgS+-c*e6{v>cP8Cx}VoaXfI;&)D=05#_oA;#wFsP$%w9Es!QA()kDq|NAK~ zC3qu-@|dW}!nLB8cI1T~5y?kqGAEiYuBtwz88{bvOvttLLnZkbt;Y7sod}3OZ()vCGG z;dt_(_ngUJClXt;zZRB6t^&>$LB3|wHx&dlSbGi6t~bUP{-@-H1FI$DGpQwT2S%El z`9=1uMsX-T`14w1dSQM@#d-Yq4WaB8P~-v$${P%!Xll#*riO;-2VfltvK;hmAT;{B z$;(rQatcIui0hrt^Lk_#CgO~h8@y|CyL!#H;rK($65J3EE$q~jD)7@#aUMyqb>=xN ze|rs}wH0K0?Xrxz@snBLg6?HxV?+J(WqJ7`PUJACoo6Hd4vZ4v8-1ym7JVhIC7taP7%8d zAWL{olsVrWR5B@+AhNiXW3k&tRFBRcHPJWOA*+G}Q4r>qK0Fb+-&!N)a1pe6o=Gr~ zwz|(X`hpsbanGD_&R>&|kj+)+9<^>TwErT&LxU~p)S++CQyUrEsdcwNw?ptdKwJbV zo;pS7Li+uMI`l%dKM5NuE_)ySgPBdqZ6C*{Q>`$c6XK{Hd?uBc+TM%{rZAW9i-)N zFjYGUhhVgqU-37%v0!Kf4(K0KxE)O@ldv~df=x%5L7hdTRmL>qFKJPGVWOpLNweKUWTsEgEG21$AMHJgby8 z!1Ps8`!2zm1GHu|Kf>{`YGDmxFX`j^?ws=8SEEXZSv6S^Ugi(ZM{G}Vk3qy8lZeWR ziyK1`|IWpw$J(n2Ucq)#fHla3f8*%*8@C=TZ6hfxetJBDWsv4bT3V9VLG4WEI3Uv) zz4um<(Wk*dQeIS_Z#3PvA8*=N@7F8ZiSwt+KtV4SafyVYp`~$&TQ%HjfAyLXX-`eF z33|jGS3E&5#2!dH(|`H}vyH}N%J3zKhZFqQ!ttX);&rhe!XPLcc0FJoiJ4dNT#RZi z$Lb3cBeR*QDQ~hFTRqMR!MClvwU~m6&OA;0$%$U~Qx;8}j_^9;)#UISJM{l?0b+T2 zNtAX_aU*qq{#;jaA+&Bjve+G-h+~j&CzQvTo^@mgCMK?PcRclb$0A{~{joAU0A)CS zV72+;{qvriy_MnCXhdNPL2r0CMdK}WuI>5m3B#Je%rbPki|&wJ^l9&RU>*(EpXKJz zB;JsDc-FryRQsI^}DG~A|) zr(bq-t)Zi%L$u-rk`OT-l)JPA#Z65#K_3Rd-iQz^Phf&u8IX;?WQLrn-F{#!n@8|LmJ@T z9zS4rNK%G@&d5C1quDEFIi7GR5GuIxjBeV0Lv}$aN8n3(Hmsbm9>N+#0FEZ1AY4t# zA&iJ)i=Dr6U|=m{+&&W`1-wWTDJjfRJVpR3M4N?V?yHs7xR%!JNnZzEgW z_>{YEsKs7kUkjH3YS;g@a_=mo2H3`qD63<|EQ-(|(0`#S2UcL?5>DSsct%uJ zc_UPJA8(1`km7CQ4EkCf_|sDIh`cVFBVJ7%a#!YUj*OTuz7sQZ?7D0BNF(&+d0}|< z1*PO7gyeofDE(vtYqjr8ciDc*X6Kb)j#=Hr-(gE=JqUfF7sTO7o{PP}Np9R4T?+r9 zYkNF!k+rZSTR9XQ{rbknaQys)YEZ=27l#LG%E7WOapmn{&vb2xB0+jPb>4?+A2Y$e z^DAoD?koAL^NOwkeDwnYWF~*2GZ_^Ll6tl;eVrX0rx2NcgxFlVbfl~AKK?k?q)JC7 z5;hhA2g=6fYZ7ScA3uHS|K+JWnh_hg#32XU-FgK!u*=&tAMEk*?5yWtnAf<`E&){x ztC@Sk_tmrzQ?g`2Rtfdh(v_vl%a1MYucPKuMa^=`^Zlusf;;q4mi&A(%-kV@_HG*Y zxVJNOia>3C-njl4DH@feA|N1wtr!7|0Xco_4=B=2_O1IReTV!&>B9QK3-^Iyye5eD z5-u%}vb9W_*YfY;l%Z=VD=Q0)1vR}ElxOi*115?ta9zj{3?uti<@6@;B=;46)I)^I zk{sVgDqZiJZ_knL?il;$9f1+23-mu7?O>JCSpIyjag~X<7Nx<@+ls$FKXE+^x_MDD z%EE|+$Iro4<1fB&-0>nR2Q-@H|6?GJKXo6NUs%}vwX`FiqLXs}KDkJC==>vq4~W;f z7l%OLmY)TM7puodtOJvd{tB@!;~&P3;%_>m(IltOBf>DN8#f|kOab40D*Gik8aedznVhtp#J>D93fbjj#Qugofr zwd93DdQFPb?U#jaP>hI4YV{S-<=kY@v)QLgDeHv@H{*V4YIHJxrH&54ZrFL1F*&-O zS3+1g4Lh~v7Dw$>;yb6NI#OOw^(r*YmzIV8`E|=xAn6VKKUZ?s_|2b@r+o^z0-d>dduG;VW}jYN4q}XXm|HP~tgmgLTm_sFrM>~CpxjDsSXzm;et+#UO+Kr? zy!BZYI|07r?;LU$KcmjuL8JW-*|%+ZXZgBn*d)Vl#ARD9F71?GSI(F|RD2Lq)m3_t zSIzu2y@r9>MEC6vv?&1w#Ogdz{3XP1a!V+MsaZJM>0bVsxJ}MSN;*$Ulz+86o|lJ? zbZ^3;G`3G-{txTx-u>y6m@Vr6IC_XzZ8G)G9i<2J^Tq-1F2AdnXdVAA`Xhxvw&(Et zC|^Cp*C6vYENKZGIt2aJ_4$trs3je14jDQ)@G01>hzeUJkEs zG$Aj+@~3I1smWd+c}||FLQ^ba!H^SPIMAzpeP$iu@)A17z-S=r@xTS5- z)utZv=Ar7;f1wgFv~Sg{KCf$e)$rOCn~+j?I+Y*&)$l!Q*=-}YOXJCJiBH6f%x8F3 z_kA$y3`ox37dGtkw^3A+Txy1ukhnV5WC_;(|gluTvThFUJ-=0=Qo zPmqv+03l9*qP&Z`3wGb@XrQh@xtq5Bb!+Rbz)Ku7;sqzYDZQ2CE`3SP%+xKBWO*RImlmzHzfkl~=^q(6M5R9nh~nO`lFu}^~kPrIvc!0RWM4vqQe+Z}LC z$fIo<`MHy`yP5O#tJR&(r1W$cv*g^pdzGK~3ei8C=E!A6_Q=gufqqS|^~rBeYO6|5 zl`$rso=lk~=SWk(&jWMffW4A&Oasm={M*cfi&gGbsK8BE2xd`bQ0iH zq{yMNbMGD9j%lomW7{*{G1z)z%D$6Q-opISaiY@2eUS-ncNXbSBr+V{nq-}iWv^D_ zP73jB7Biup98Qp`3L)mJb^9Z9OG{Db z)cgN1LLz=6p!-88uDW{oB2IerSDn#<()dMV>RdNI!TM-5(j%Uy#t%o8ME%5`-(7)I zYi=5(cYd{~@ZS-g38bfb?X_~`86B}tULEILJxt8832httL%@0nHcv1cy4HU3m3jTB z4TzlGd?RX94j#A2^M_9Cw%+BPqBe03lDK%eD)8<>-k2v{791&+FF$zG2FJ+Kkn&OM zgzjh4I~DJ+%=@VQ2z3C=>5jnOdP&k_P}usuXKRSoC4F9oBOj9H>yql+DFPMMNn5D2 zv)%UQy^1_;Tt9$AvEQMx_ zzz3d^=bz^!9qPV{dla8qk+iD)NUWyL>&#rsANN$Rd+ShZA_-X&$=)dPQ}@k+Ywbll zZ(mRNUc~8m!bSFFqNC)V)3WccnPr+QMhOOJ?)FTY_Qxw~ky&&-SkCYM)KD15y0_*? z)xPTtj+7K<$ku3sC(5pp-89fMy0oWcnpNb$Mkbv>v-7ODY2b}!Pgf&;6Q?H_jmg|8 zUO3*KVn3|@=YxM@Ac?6_Ggof6?};)A2Q?E@?MT0s?e~b!0*qa*C3P#cwp}C z?&<02cx;=f@Wh<>IUQprx%a%?&$fuo1ydmbO(A0L0$%FHBsYdn7G1Z)$7N*XODM@5 zlIFCCPo;kJTVY6MBBE&d9**pfGOTRjFS=x(o$U}`GKW-#$CS;yrF_J-X>1nK7RDmf z{d}3c{1>@7zf_Bv97s~4bxO#mzh4;AEbAeoE!{CL-0$?9&>f)Kamxql|(V`?xpC7ntYv5E{p4$ zLjm=pXN-uFZuzRyqv>cm9PbPHPDr8uE$}+_CeC+Zn}LVnlwiz1cHPsHiFY}#A~C|3 z3w0|1+n|fkzsHgcx-E@3wC79M6-<4$byQl~?)4W8YO$vQyQ_L!_^Vb#5fPO241btx z^1BXSPLLWbKQPu_dxC?}>>wA>=f=}SMy5=MZZxQjQ)zrZA*s1?l9s3ZaOwjlMhpG8 zo--kL3!zEy0{0WYEyS1~4&1nF!CDYkeb3^);3)&sIYW`>%=!mvl;WfVUzCbW6Ua`EcQHvWRi> zIEE7qcuQC|o1hDg+ zBS(Zoh*Apr9Fl4k-;r%!&M1Dh{F>q>i$v_+`E=K8TzobtG6072iJ>)_V)}ha?wF~2 z;-N5wj;@i@ib=vmS;Sn!>sqF!CQExWX<*i;YqnqeISs8~7KQ$R0Y5RnaK9KA2gjnf zyoQE`TA}r=Du+wd_kxZ66>tiU(1=VAX3|NU>iOJRdI!)7tRs%!sSdAHA8YP}eS-OJ*} zQ}92Yl#~R7LB)GqZFR^dD5|8@=vPv}!s3~_3T!n0?vZHnm*-%N3BnRAKs^|7UL6c1 zk8l4Xd-3x!uDe|mngZ@)o*c2h#~~*eO_b*7GO@HUhFxjzb z2&SWFA~l9xGPWlpFrNUN68)!nx6u6yhQ|aq!PV*0l3tRB>PK(K?c<2Yxg@j?_o<$b zaB_1m`MyMbwJ($R_BU=;ilPJFwrQu=R#(x`BEM8*WpUg|xZDUWcIcHnIOA$*`2}@- zm&{Yt&IB!wky@&_eN%semC%|mpo51GdBntPlF0+5r5GS{9QU6?<)I7*=5~n|<94Ud zDZMV2A#;JImX3oXVc;($LP&YdDy%}H2gF{r&%|!TxXgv?%vz-8jE;`JF`lj9S4=P~ zt_!$~WP2I49x!1%Q%zlht*@@ct3eX8$VcXyJGYyn)Q55^OGdr1nepZ?_U9nuvZpCEvF`e)Ch=;07Vd3T=)3mil zs=!E^IAcdO4ew;w&gLu~9UVc30xBDDp2KHTsrh^aQ5s+fVI>%K7y{Dt?5x*tjexYY zG%xSUuMxLIAAt~ zH!bEADMD^!V7lJ+CaM0<;^|X*{_YtcJtnNoAK#COIe_oUUz1LnUd0#Fm!A6%1^sbj ze~I~)bn)Ml5?;rMi?7OXiYp$Id==9`WMV0HACC<-aQIjWECdvdMn*>e+kMASYz`7F zMn?Tt@W8=BA)pc%ki+B>K=5UZN)00fWM!F%7SLHA515B-dm&E$i4!OI*v=ag)Kl;| z7A*a{lASIsTdSK2goX0fi_w}gIpRrd@gs!gx06=wOW(r&rGb-bU`N`?IUYJa5&M|s z8qMG%XGAQt6*gnU*LeLk=`~m4e4k6XpK$H3E?@WyMhNEl*kvF7ze+9yFc+0@Yaq^t z6y*ynX-2y|ES-l*Uv_fxAdXUBUmt9)(MItxGjKS=i<$Xm5l;2S5$4;~s}GY*m}oVG zw==j8{)`1qvW?|@_-Y?gRJ@GOqJ7&a4hHjc_-4SF*07jt8$59hUc`8Rph(PPGEGKC zCL$u@EqgXo?&mcYo35@dc}yu3ml~d6x{a^}6BR&S1ThnQeh?4e{kb>{Knub#?BBg^ zn&dEPZvyifpyr$h76P6Po|HTHeLwJLb?B5V$-U&JLcu5fc-$xVVT* zx(nAR=9`3>scXWevBrOxuHkLWqI!diGZQB#ArzV)g=IX!vNtcW0$Y5m}0%tCS6Yf8^HHy11oCKo`4vx1@@(g`{@czn5zhK%y zZgh^3V3Lb9*>`W=tc1>H63 z;ZJZwAkgWMFxX{1c;b5fdbWbUQj+Sa-L%q{9D_pA9F7t}^bQgiUp<3xeDMt0doz#C z&d%0biI?>P2dM%r1}NfDF|mtnadg~Qej7rk>uLsEmLInIAm&f^aw-K&Dk`2Ic#0hS zu(%kO*WFVq4S#m}$ry4plxa$uAD(@196KO0uFV1Wva{bY6(NN@Wssi@tbY}v)rJfc z$-+8w>tJ2?@Pn|%hby`Bq1EOGhN@d9%)v*Q-3Ib=-p#G#qX(z7((ncXIU?ih2_V#bb{ zj%YlN*HP@K16EPspeE{|zwZ3Z^x$kWg9`fHrtVCrGdOG!bo03rcM zKozC)Fg`lMOh`}U6cm~o8Uzw6cIHLnYjD0HlbWI~t z?}g8DBqOQx&DPz0J7kx|i4NS1kJr%D6ltJ5qiPP?(Jz28TpRE5Zec)|@76@WBmX;s7|EtbbIKSNsu z?j@r6=B9Tv<|QtSn(|)T2{wLj~*eIOLS3@I(T|k^sOU;d9&>!U(&|O zvJ2ASpbop@)FIMaE_#I2Sc<0Gu}?<-597jDH~G(%wyJl(kB^Td^x=OeH_lmmfd^V; zj8n0(v4N+!i#v@?cZI;k&xo{uGb>6x!otF!=#eCNHe1?R2N#*)f4}VOtIWl4mtCc= zNHkUDR#h#7BZ2S0BCXKs@&qGa=7zSk77x|tr#XOfU;YiV6Ah>4Ab;M4NJSyg3-XL)#dgocKq1jGoD z)e*839Flg;2r`C2fn9fb0fx6#z!eLG5YIcY(ePTIfsHxdB48&UwpW5N@)0{>k1e0g z%~W^Je=HM<>3a80knw2rpmX*p)nr*#7*PX7VY*aoVMK$td}PmzMzFk(!rKFuOoKLe zI-^H~H9v2NK3%%1rj_?puP*IH0weLh>$3YQ)hRW*eh;Z~HPceMCX|+P>1Q4f@POH~ zhp4@ChU|sd-n}eD#BW(_f+q+g0Niav7s7ZR!l>97eFA^yvsH5}|q_*u6F2buV4U{bn8}Y+-mH{mb|ClVcw1xs& zh-a-#2Xk|C5lYWw%28Fw3QlFvilB8EzDR1g1?&xvaUAS60#OW!zaGpFTNHcHkQw*K z|Hnr@i~<0DWXHjs#9Y7?t-i3me2)us%`jnr#dJ9NAXR?(P@DdQL zg68_{F&DxQXyr%8ns)zwEs91Asg8$qB%r#}npB>LCuLIsz4w&hm0e7^*>ZLEH zc`bpLAu0rYGIFlFnVDOFOC$T$Q)0rtRM~5GYyv?18CWl_DC_%9!lvPKkuHn%kZFY) zBTs|v$?ZpBo;MbVQWbpk60M()5IQW0roTgQgjO>{&xk~_=fi=2LS3}KdykC~1g=q% z;zzX_K%K6yGy+3!xX9h&R#iEEJQR%yhE%Ky4qCW4I9q5yOiWBt#jWp3`T)m76@}m( z{YbL}v=F$<`B++J*wlnuA(rHqg09hCJt<&p{`Po|dRYn4oD_q;L{EZIjVagv zlR>!5PfblB@ZvoiA#8&Iz_V*&$f#X>-1i*{mHU4O&ek{XHlhvkR(ExEbu0mQ4JEyP znk27)fHPhk?)^;8iCd6}j{w3ns|0^$IXUK23TRVZTm)k-9#`SIb3i-6{B)%^;WY_Y zwNj)@>N@N#b6c}h5D_WT;r+@rnANX2PP=b}$EB;t^K$mpFpLp{5VTH!1h0j%k} zt$v>APA3BgM;LG?F>CZ~_yYBzi*h3vBF$oCn&Xty^K#x_>%<9iUal*@KM*vuP*2xY zAmzhO8$n4(Ny&;dQkhDt!0bO%V+r<^-ijDF(|P1{PUYQoTVt!xsJk{)mY7>`aNdt6 z)V#WchUzq}1^~2uyrm#A9fYnJG{qRTt4Dr%rc#J;yrZ00wa?tMhwIg8Ep-!KEf~5N zwnuFGm4LQ9{k_nn_aab;zg zA?Fns0mctlK%k^rwr42@q2Toa!H$}Y%+Sf+We@ww*X6GZ|NKFgjL(1S@iG3iz)qcc zJ>Ap$wv(}0^o)!IoB|^o!Y@z@FuMV751`CxUEQPe4ZlT8g{lEa&CSh$iAKl&!I?|8 zoo=A5q5pJ4Vug=NOOc61%7Af4xJSwX9rv?hzD)O;7Lv}YfHQpt*#w8d@~PW|?@O#Q z+)9CUy`@Q;!t;@zPh5Kcjz`k~2t|{!pNjo=YSZ>LDP+ZJuxwyTYWK0!6dI^E;^XTu zss+4=z1rZ+-v0=kVVreNNjo)MITVDbH&#K*;G?6Lq3f;2wIp17#&N#*mtJEk9iI^y zN!9m9K8<0hmocs|88@aDu)oF{OUrls_+QMYwov4shqe+58RTa?<=@y0eEq=cI3*m? zAwdE*Jd!c~mq31m*^eK{(+Ua-)Yl!67w^DG4phQ^AZmzEjoYg|(7}R4R0v9B7Td6U znVA%A0YAU2gP9Zx&RAZbrgO_A4TpT+gm+iG$+X zKm7()w@7OGr%thA$PB&R{DK1X1;>Ceqp-u{l7@C5+AIJ~7l$gW_4M@o{rwA0+}Z)( zfo89u0lS?@%L+op9**YECKxl{ymjjp#@W~Mycc^l4K_tYPG7^fSB8?>>+`@R*4EYt zV-}nZXD6rJSkYInPTucxNI(Vs6M?h=+NkN3tTvKo`zgC_7xqY37b^N$+`s+iO2Xv? z-Qu1^tpq6msqE=i|B>;~2P*zF*)PO&?ZI-Qicx{l4{!P0`xDdz(j(iZa6AIEBL)n&;t%n&;AhwqU^c?+owx|77cv-@ODci;LJ;(b zdY@#&;^W`LZokK~7`lTHbcsUJG$q<%uMTF!U63sBHM*yUckb`5yd$pXil z?fn2G@uHEj{nGUbl{8NA%=9$Q9*$yNZmu0T00spZ=l#RcSpg=1Y7B9q)V4{5_!)*% zgM%j1-l$Q<1O@lU3L4PHue#xyql7`Xagc!_npD_&=onzd)t*YV4~KT;df#<*-J;C) zduK6oq~^8rQKG+#&YlheXOmX0|7Me!1)8IG_AEiXyLIAf7V)e)Do~J%vG7Ni#D{>CcK&A2$XvBp;;KeHi!?D%%Z_aB-U0{simoddr(IY*Iv4PIkZhDOU9^((6F z7dSBx+>QTZ=$vjsTZaqozx3@29y!_}Jr5dik3&}4S+3>%Ao-}jeB*^fVG2w4QVtIV zJIbt&=uL)7O({H|f@W9(j2e)57DP*b!3;U5eiTt5^70X7<vBp`~nwfXJ zrZ4FpJ<5q4T#{|TV9?7AR#)Qe#q2yci zN8a6RohZTH1w?$~DtVe|HqYm}$Sa~VCh+%<2k`9`mzGX^EqiZ@M4vkU@Qa#{9InH%|1;V23!EMZ@yKV2S!O z198B{n5Y8zDJ{)NN%;so1+n=|j>;>1eh6Vc@*MEpEvKC%&>!&Jti^{kX_N`eWz*Bs z*dp-yToox5h`WS><2jIdFw)(~emY$CvFPHmRqxnK^Ie)YUAMFRZoqyiX&X&L#+H}5 zCq&I3e-hBWPvEMK_d>Th6zf8vzvuQ|KTOT^s>(2D`LDsI`mq^~t+0^9?|skS@QYr3 zZAf;ll{I6pN@SW^N7}%TK_?bdh^qTr{9?&u7UyQYM|({BnZWqR0#ePgkD%x;;i|$6 zC`}5imp@?s^Bv_#jIo&e?^tCiX-s85sX(r~0i*YEVITUO7FPe^t2aFq57#&}9Ujr% z(`2WVAeEa0@EhykHo~pG?~+p<7S5=VkO%{ zGb?HHu1gobnXGhj6U?DX>Mpmbi7rp=*U98MZ*XcrsC&q*#+~Q>P3h`9BY6($`q3SH z555J~Vn0&iaoUXY{2p$DH{An|xeIt5j!RT;-qE7sI}qp3SfegdLgwz0<(GWF)sU~Y z+j)pwx|QC7rG_lSQ@G$XQH-GI4xjRQ(~p>@hG9~I*pDoZT^Cjvx4US*b7PSH2Sfu^8DUW9G1kx_M)p7x~Oy(^TF@`XPX9-JBNWA7KP%4qR; z)`*jZ837G7HSz%~su%!jx2FGmaiS`ptjj9+Z4llq?Bp-!cD>hyf{IERsSBksHVOY? z^M#Dj6=4*J)%jPU0p8x;Sl12>{R@gSp!;`)PY6W>jq>k4wEVo}3J>0)Kaxdo#(ElX zINEvw?<6a$0nEaVw4NEreiJBwhst?1V@Y}Eu~Y}a)ZOh_RYVcO7z=1wJFA~O2MQj0 zZ|`L+U$DzvOvAYQ-aY$j^9-;OK^8>&oIx*{A`kq2#*4G_sw!&IzJ(C@G`IKwK zMK;^I8Ze z30AIr^r4{au1;rRTxs&;Gj&e?;f!P_(>sfmWTFhrZ04;iyk#OPdw#T?TecXGdEV(* zZEm7%pLIc;#h$93NUY6+tY7A_`;?$@PImr6C>|HERr%Svce!o3@r*~`JTy&KPRnDp zEe^ZrLO*S9O!S=Rz3?R$L8`-yhxUiHd_u9`Lo8tz&qkg>##_SoP+XBxTI9`#n`}&G zcRG!6*b#a0XyfAIu;Rl!+eLcJbpEhvj^wji&nG6$cxeZ(y{xd`3A0{_a^ikqj{@+mLtBbYnYD$-|l( z2c0Xmv;#T#yokGqr6@nr?r6y}oZfBMd2Ol9NTqcvd#2rZg(;1=+|(pB=XL*h`c>{N z8vY{o79|pi@<%mQRC~yC1P{_1{63t#@9sgq;av5;Lu$QwfuxT(6N{rhcDzq_+N8d| zFQ7-+lL|Y=xjgN!BW<6L^qTt+y&Mj3%FgxCG&@pkBiO8yxDmyFwY^hYJ1|E;I?nI< z-9sf)eF}`4Px!@ej^_C#pOnjyJQ}Tn*RNFkV}VbB%@f?y+nWW%_CXzBn!bp&8rt2F3`W+%OAt~+L?_>+Id{^ zu6s&p#KD&o@-e+GYorqzzE@>#j8|&QjZ8@Qrb)#*{Pq7VeEB;K|I@@|_C2}P_g1e@ zMGN!4D2SBR{!g65^5w;?Q~E`5hwr75YLp$m>_)+xW`9op!I?PQ+>_o(lI{mur^-J$ z^gAY~9Geojx@h!lk5r+Kn#uv)eZ@3ooO+zh!zW|j2)vf=zaPsnK0{jA#+XQ7LX&b> zQJd&V`&p-ikj-yY);ts58f^tqGc(0pMZWCkPyH3t&{QTXH(S6I6ywuISfT0<5cX$~ ztdSP18^c2IV1Sud7(b*w#VQEQ_BV3<63VS($AZvcVLU@aLnHKxQdUApKLcSVDSJz#92P|@w8y0k+Y4Zh$iHZRUt+=QNW9L;Y zUkaws=39LH^6$`~9~=MKWbM?J!T5{U$(Fl9_my58y8Uur(5dKV6}dW@NRouJ9@2;S z{m-?l=hN=Dw#ts1R6wjb+xd5 zQgX6WwK=}p2UL25@vyC*&nG4(Q1d}tAVJDWh%hk2w) z13axB&wwJJbUQ~{4Z1R@@q{Jg`1}M|GQh2{cH}3rF<6V(s#IokM9c)RTY7hf;#$I8 zQPF>O!{g4{vO5%q)%f-gvn7~(bJNW2ofq)Dt9RH*{a`5b=#P6;#O~7$r3DLFQcN=m zGBl2Q!r$mBI(%d%J_J}x<{fR(J3sWckcr&z<+H_+4H7;BrM+V_PesewyfcuZoc`^r(Cb~xq5pUe+6DHoL-1L z@s3g>mM)$w{~eoXxLoY)*G~J1cs_sYEk3L`x$rxAOLsDIxD@e{~dciI9v0xFI!&Q4nYoRSaK+*}h% zv+Bk3-u^vsRjtpA6NOY;}rd4fHR(jAty z7hj@ddgbJksYC5dPfRqnxOV;txq!}zrL>dF3e>Y%))K|9AEr$`Ywuk$R@l4xXvOsO zSJB3nw(;Htt1qsb>L)EaR}>Zl46S(c-30d9doN0OI}QQznrl>f5E}-aSbEU#Icc*k8*Uq)N9nDBt{y zWwaIicAW8T{;eyb*8lWXFR)W$(cNs;Sa4m$#u9F9IG zT`t$5wrPkwWDpUQAyMONnAI;iRTJJrb+3D|ZiQs~zvpG5qQSg3t-XX&*K7NmPHa{T z{8|mU{r0QI9LLs8rm;7-%r%>+Dk}~~=4FLx7&5--YNi)9BxOD;t|{NeE`96uVLZ=b+x!w?}tZ+nkvPhN)9k8vhx_DGFvpF4(cN@B*7OeJk)m58AUloQx?BG(70XT4HHY+OmJchRBui;qC8Jd{4%+oz@ty2n3(v`V#Gy7t zs@mK@+ZJG^?mrtC$zm%ZKrA@y5@W+6Qlw<2(zG7rln^(NZr3ls+hi8MOWtm-r{*n~ zCc&?ETDZyS@tZ)k!1~cs*9=8tQ_4GqVwSsVd^$8pY+9liU+=v_B&OAs>q~ix>OA$nc zDIPLEy=q^~@8aR?^1etWHTB2&wT#RVO%ZNG?Uo)(w??;yfrah>nz$yC26gSP3y
BMGQ z_ntjX2>I`k~(qmt*M{5%sFR^0XL0^R;u%?eanO^G&&hyMG-o#rKZ zyH&foR7l=cK$fh7cEofwo;E@1S}Oa2sFa;==gD5*dDbgru+My{{ZGgTt$Vs(CA_3< z{)m+P4L@e>{pi6|)KH`>zi!+lmu;MP>rVk{x~Js4DG5f+R5OkP3+$(lu6;@ESBtr6 zz}U0*lsC zxe|0u?oGh0gG}{0ueq&M=_?a8ZnQUL+jee?9#vdPq>T3}d%+^NZzEGOu4AXfRpgP! z%z>kUF}pAJ<^0*&{>gd_FH+Jsq^Zn9Uvvn?TzsPzQd@W+ zceegReKXY?#TUJ&?)^Aw+V;C{Z$b+N!wJ8;L!>skOU^3!ULx~vlz5JE8fK9C@PDqf zW5(u_13fp>g@^fi!c&`+Y`SC(btytZ#fExlHBqra9Qp z!d-pE&BZk3a!=l#f@Rn38%;MDXXAXdXX3S=&DDuJUHdXZq+wZjL#-v3lbzkNn=Wkr z+ObuY?=9)>A+N^NZ5nGohTO0XvG3h@xnWx&Z5uDoeDc{cUn*&>Mf6Xz1YKLUv9B*k z1%8w9-n?~pX~=x@lW2WmX*8d1fpHsS>6wX=<_}SfGvr|uptHT^Eh)JdoTgOQE3OestnJu5}><#W@N=YqoQajH)HTSmDOnhO>z>t`9y}U3s zG-*%%z9f=RL+uXNH^f&}H3LLUjVw8ivhk76tqFzq-l$vJW-Z7*IrevYEOMot#F*-q zj1$Stk&B~6MkISmgDgUquS-NsC9m!@|9wRp^`r6J!3~F<20FpUcj?_-UoLiKEzfzj zC}oKxRP$Xow$CE_?sGx!{{6gsMLp^8?LLRBJr?&%swih0N=oM1S!^K-AsB1mEO8`f z?U9D}5nY>4bB)0(eBNqBmN9vc?XK-fJ$)_9uY@uF;`cf*O6;XX$y z%3}M+wSAR+_DfUjzn{;1*y_Ju6D>;ih=nB-U+-&!=v5a@jKCc!uceuoTEFn-O}Z8u z_U*l?S18>ciJ3hTq0{Z>-VWUgdma4Xw^C*0_KIr6Nj3ZSZ=E1w*%9BI3Ea;xDNJ(U zKv_)=Va=RCKeZ_Y5Rhb1`r46sUca)@xkd zd!)=^uIWgjVHf|_jYRWvk^kv@$(WOfd0$tt^yS&Y)>6~S7*;8o=bi75Z{1VeW^?M+ zN$m-ZaGzs&^F<@BCbo_C5G&RF z_qhL=tTRGkJ&X6JTX?=46j3jc;&(djJ+|cDFSI9rFtB0LXPZ@cdvcUJe7lQbr@2zO z?fpgxU&6;KbH+O-T@%tDY(J-;d&(|)?(fH4PlnyAO)UqA+fqZG_IFWroYQ=))Nsl0 zUZ!8M@YgB21f~%h^|)Lb)=p9Nz3L6LKcll;U8-dFy}goL%ID3duN!sv@eA?ue+_Nk#*sC@whqRuRe|yR+ z{iUwv)CSuyT_=`^xv!ohoN8wfTDM>1)PaIW#`~p}x3MKO9;+ zw$;1MGu|BjGu-)iL6@Cw@j^R$Wd)09IU4z4FP;sXvrk#CHyh`@IAZleQbK}8;Jsb_=ZwyDBu zX{g2EIBAvUVZohlnjBIJ3M^kg9gdnyo2%nuVlr(M`X+DZh^go+jP*!$kz-bUFKn%E zrjqEeElqo^^aeIG3w;^;Z<%Fjd@H>sDXp~B;NBy)6W-pZ;I7IyQkXfrelpIgG}fbp z@H_KB%F}*BSrg6w{iZMZ&hXKIitwA3v~*MJ|NT92l!uDp|Nhp1Dl#|Y?Em{}O`iWB dUp~IOH@Irp+~>HZ=N|kzt$jwTLc=cV{{R{fHgEs{ literal 0 HcmV?d00001 From ad926ab28d171ee2b3bd1a110622fdb2cdf46876 Mon Sep 17 00:00:00 2001 From: RexHuang Date: Thu, 11 Jun 2020 14:17:32 +0800 Subject: [PATCH 251/371] Update 24-Concurrent-Programming.md (#473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 源码错误 --- docs/book/24-Concurrent-Programming.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index baa79c7b..b24e4569 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -360,8 +360,9 @@ public class Summing { else System.out.format("result: %d%ncheckValue: %d%n", result, checkValue); } - public static final int SZ = 100_000_000;// This even works:// - public static final int SZ = 1_000_000_000; + public static final int SZ = 100_000_000; + // This even works: + // public static final int SZ = 1_000_000_000; public static final long CHECK = (long)SZ * ((long)SZ + 1)/2; // Gauss's formula public static void main(String[] args){ System.out.println(CHECK); @@ -3159,4 +3160,4 @@ Pizza4: complete -
\ No newline at end of file +
From 02cece9f394f6e8a4d52d1d90c7e66455347d888 Mon Sep 17 00:00:00 2001 From: ZhiQiang Jiang <34731641+JerryQiang@users.noreply.github.com> Date: Thu, 11 Jun 2020 20:28:33 -0500 Subject: [PATCH 252/371] update words (#476) --- docs/book/11-Inner-Classes.md | 58 +++++++++++++------------- docs/book/13-Functional-Programming.md | 2 +- docs/book/24-Concurrent-Programming.md | 6 +-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index ca4abfc2..3265c2fc 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -6,7 +6,7 @@ > 一个定义在另一个类中的类,叫作内部类。 -内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。 +内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外部类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。 最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,"Why inner classes?"就应该使得内部类的益处明确显现了。 @@ -16,7 +16,7 @@ ## 创建内部类 -创建内部类的方式就如同你想的一样——把类的定义置于外围类的里面: +创建内部类的方式就如同你想的一样——把类的定义置于外部类的里面: ```java // innerclasses/Parcel1.java @@ -119,7 +119,7 @@ Tasmania ## 链接外部类 -到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。 +到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用,但还不是最引人注目的,它还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外部对象(enclosing object)之间就有了一种联系,所以它能访问其外部对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外部类的所有元素的访问权。 ```java // innerclasses/Sequence.java @@ -173,9 +173,9 @@ public class Sequence { **Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。 这里,**SequenceSelector** 是提供 **Selector** 功能的 **private** 类。可以看到,在 `main()` 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 `selector()` 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。 -最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外围类中的一个 **private** 字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。 +最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外部类中的一个 **private** 字段。然而内部类可以访问其外部类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。 -所以内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。 +所以内部类自动拥有对其外部类所有成员的访问权。这是如何做到的呢?当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外部类对象的引用。然后,在你访问此外部类的成员时,就是用那个引用来选择外部类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外部类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 **static** 类时)。构建内部类对象时,需要一个指向其外部类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员操心。 ## 使用 .this 和 .new @@ -597,10 +597,10 @@ Over budget! ## 嵌套类 -如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: +如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: -1. 要创建嵌套类的对象,并不需要其外围类的对象。 -2. 不能从嵌套类的对象中访问非静态的外围类对象。 +1. 要创建嵌套类的对象,并不需要其外部类的对象。 +2. 不能从嵌套类的对象中访问非静态的外部类对象。 嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西: @@ -644,11 +644,11 @@ public class Parcel11 { 在 `main()` 中,没有任何 **Parcel11** 的对象是必需的;而是使用选取 **static** 成员的普通语法来调用方法-这些方法返回对 **Contents** 和 **Destination** 的引用。 -就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外围类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。 +就像你在本章前面看到的那样,在一个普通的(非 **static**)内部类中,通过一个特殊的 **this** 引用可以链接到其外部类对象。嵌套类就没有这个特殊的 **this** 引用,这使得它类似于一个 **static** 方法。 ### 接口内部的类 -嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样: +嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 **public** 和 **static** 的。因为类是 **static** 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部接口,就像下面这样: ```java // innerclasses/ClassInInterface.java @@ -702,7 +702,7 @@ f() ### 从多层嵌套类中访问外部类的成员 -一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员,如下所示: +一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外部类的所有成员,如下所示: ```java // innerclasses/MultiNestingAccess.java @@ -738,11 +738,11 @@ public class MultiNestingAccess { 至此,我们已经看到了许多描述内部类的语法和语义,但是这并不能同答“为什么需要内部类”这个问题。那么,Java 设计者们为什么会如此费心地增加这项基本的语言特性呢? -一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。 +一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口。 -内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是: +内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外部类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做。”那么内部类实现一个接口与外部类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以,使用内部类最吸引人的原因是: -> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 +> 每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。也就是说,内部类允许继承多个非接口类型(译注:类或抽象类)。 @@ -811,17 +811,17 @@ public class MultiImplementation { 如果不需要解决“多重继承”的问题,那么自然可以用别的方式编码,而不需要使用内部类。但如果使用内部类,还可以获得其他一些特性: -1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。 -2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 +1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立。 +2. 在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。 稍后就会展示一个这样的例子。 -3. 创建内部类对象的时刻并不依赖于外围类对象的创建 +3. 创建内部类对象的时刻并不依赖于外部类对象的创建 4. 内部类并没有令人迷惑的"is-a”关系,它就是一个独立的实体。 举个例子,如果 **Sequence.java** 不使用内部类,就必须声明"**Sequence** 是一个 **Selector**",对于某个特定的 **Sequence** 只能有一个 **Selector**,然而使用内部类很容易就能拥有另一个方法 `reverseSelector()`,用它来生成一个反方向遍历序列的 **Selector**,只有内部类才有这种灵活性。 ### 闭包与回调 -闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。 +闭包(**closure**)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括 **private** 成员。 在 Java 8 之前,内部类是实现闭包的唯一方式。在 Java 8 中,我们可以使用 lambda 表达式来实现闭包行为,并且语法更加优雅和简洁,你将会在 [函数式编程 ]() 这一章节中学习相关细节。尽管相对于内部类,你可能更喜欢使用 lambda 表达式实现闭包,但是你会看到并需要理解那些在 Java 8 之前通过内部类方式实现闭包的代码,因此仍然有必要来理解这种方式。 @@ -910,7 +910,7 @@ Other operation 3 ``` -这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。 +这个例子进一步展示了外部类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外部类的接口中添加东西,也没有修改外部类的接口。 注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。 内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。 @@ -989,7 +989,7 @@ public class Controller { 这正是内部类要做的事情,内部类允许: 1. 控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的 `action()`。 -2. 内部类能够很容易地访问外围类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。 +2. 内部类能够很容易地访问外部类的任意成员,所以可以避免这种实现变得笨拙。如果没有这种能力,代码将变得令人讨厌,以至于你肯定会选择别的方法。 考虑此控制框架的一个特定实现,如控制温室的运作:控制灯光、水、温度调节器的开关,以及响铃和重新启动系统,每个行为都是完全不同的。控制框架的设计使得分离这些不同的代码变得非常容易。使用内部类,可以在单一的类里面产生对同一个基类 **Event** 的多种派生版本。对于温室系统的每一种行为,都继承创建一个新的 **Event** 内部类,并在要实现的 `action()` 中编写控制代码。 @@ -1144,9 +1144,9 @@ public class GreenhouseControls extends Controller { } ``` -注意,**light**,**water** 和 **thermostat** 都属于外围类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。 +注意,**light**,**water** 和 **thermostat** 都属于外部类 **GreenhouseControls**,而这些内部类能够自由地访问那些字段,无需限定条件或特殊许可。而且,`action()` 方法通常都涉及对某种硬件的控制。 -大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外围类 **GreenhouseContrlos** 的所有方法。 +大多数 **Event** 类看起来都很相似,但是 **Bell** 和 **Restart** 则比较特别。**Bell** 控制响铃,然后在事件列表中增加一个 **Bell** 对象,于是过一会儿它可以再次响铃。读者可能注意到了内部类是多么像多重继承:**Bell** 和 **Restart** 有 **Event** 的所有方法,并且似乎也拥有外部类 **GreenhouseContrlos** 的所有方法。 一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。 @@ -1218,7 +1218,7 @@ Terminating ## 继承内部类 -因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: +因为内部类的构造器必须连接到指向其外部类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外部类对象的“秘密的”引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联: ```java // innerclasses/InheritInner.java @@ -1238,7 +1238,7 @@ public class InheritInner extends WithInner.Inner { } ``` -可以看到,**InheritInner** 只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法: +可以看到,**InheritInner** 只继承自内部类,而不是外部类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外部类对象的引用。此外,必须在构造器内使用如下语法: ```java enclosingClassReference.super(); @@ -1250,7 +1250,7 @@ enclosingClassReference.super(); ## 内部类可以被覆盖么? -如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外围类的一个方法,其实并不起什么作用: +如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外部类的一个方法,其实并不起什么作用: ```java // innerclasses/BigEgg.java @@ -1288,7 +1288,7 @@ Egg.Yolk() 默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。 -这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的: +这个例子说明,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的: ```java // innerclasses/BigEgg2.java @@ -1341,7 +1341,7 @@ BigEgg2.Yolk.f() ## 局部内部类 -前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。 +前面提到过,可以在代码块里创建内部类,典型的方式是在一个方法体的里面创建。局部内部类不能有访问说明符,因为它不是外部类的一部分;但是它可以访问当前代码块内的常量,以及此外部类的所有成员。下面的例子对局部内部类与匿名内部类的创建进行了比较。 ```java // innerclasses/LocalInnerClass.java @@ -1421,7 +1421,7 @@ Anonymous inner 9 由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 **Class** 对象)。 -你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“**$**",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: +你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“**$**",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: ```java Counter.class @@ -1430,7 +1430,7 @@ LocalInnerClass$LocalCounter.class LocalInnerClass.class ``` -如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“**$**”的后面。 +如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与“**$**”的后面。 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index ed33d550..c22b1314 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1152,7 +1152,7 @@ public class Closure4 { 如果 `x` 和 `i` 的值在方法中的其他位置发生改变(但不在返回的函数内部),则编译器仍将视其为错误。每个递增操作则会分别产生错误消息。代码示例: ```java -/ functional/Closure5.java +// functional/Closure5.java // {无法编译成功} import java.util.function.*; diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index b24e4569..3e7ceadd 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -41,7 +41,7 @@ 这些定义很好,但是我们已有几十年混乱使用和抗拒解决此问题的历史了。一般来说,当人们使用“并发”这个词时,他们的意思是“所有的一切”。事实上,我自己也经常陷入这样的想法。在大多数书籍中,包括 *Brian Goetz* 的 《Java Concurrency in Practice》,都在标题中使用这个词。 “并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 - + 还有另一种方法,在减速发生的地方写下定义(原文Here’s another approach, writing the definitions around where the slowdown occurs): @@ -452,7 +452,7 @@ parallelPrefix: 265ms 第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。” -最后,考虑使用盒装**Long**的效果: +最后,考虑使用包装类**Long**的效果: ```java // concurrent/Summing3.java @@ -3112,7 +3112,7 @@ Pizza4: complete 并发性的主要困难之一是因为可能有多个任务共享一个资源(例如对象中的内存),并且你必须确保多个任务不会同时读取和更改该资源。 -我花了多年的时间研究并发并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] +我花了多年的时间研究并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] 我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 From bce0767ec5d8e74d5274cb8ecad32b204fc338bd Mon Sep 17 00:00:00 2001 From: ZhiQiang Jiang <34731641+JerryQiang@users.noreply.github.com> Date: Mon, 15 Jun 2020 02:09:08 -0500 Subject: [PATCH 253/371] update words (#478) From c957565e45ade99b1fb677ef607c0aa2192eb695 Mon Sep 17 00:00:00 2001 From: tangzhentao Date: Wed, 17 Jun 2020 11:21:57 +0800 Subject: [PATCH 254/371] =?UTF-8?q?=E5=A3=B0=E6=98=8E=E7=B1=BB=E5=BA=93?= =?UTF-8?q?=E8=AF=AD=E5=8F=A5=E5=B0=91=E4=BA=86=E4=B8=AA=E5=88=86=E5=8F=B7?= =?UTF-8?q?=20(#479)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/07-Implementation-Hiding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index 8db30838..cec6b2b2 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -77,7 +77,7 @@ package hiding; ```java // hiding/mypackage/MyClass.java -package hiding.mypackage +package hiding.mypackage; public class MyClass { // ... From 0d17320789cf82ad3fba29442573018a8aeb36bd Mon Sep 17 00:00:00 2001 From: Ideaye Date: Thu, 18 Jun 2020 15:21:10 +0800 Subject: [PATCH 255/371] Update 00-Introduction.md (#481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 邮箱订阅地址多包含了一个汉字 --- docs/book/00-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index ad07f27a..19cdb13f 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -79,7 +79,7 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中, ## 邮箱订阅 -你可以在 [www.OnJava8.com上](http://www.OnJava8.com) 订阅邮件。邮件不含广告并尽量提供干货。 +你可以在 [www.OnJava8.com](http://www.OnJava8.com) 上订阅邮件。邮件不含广告并尽量提供干货。 ## Java图形界面 From d443b253581a88c303c40969c05499fd1789293c Mon Sep 17 00:00:00 2001 From: theFruitcat <34080012+theFruitcat@users.noreply.github.com> Date: Fri, 19 Jun 2020 17:06:10 +0800 Subject: [PATCH 256/371] =?UTF-8?q?Fix=20issue=20#482:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=83=A8=E5=88=86=E5=B9=B6=E5=8F=91=E7=AB=A0=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=20(#483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gaoziming --- docs/book/24-Concurrent-Programming.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 3e7ceadd..b75d5cc2 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -83,7 +83,7 @@ slowdown occurs): 这实际上是一个相当多的声明,所以我将其分解: -- 这是一个集合:有许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大 +- 这是一个技术类集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大 - 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 - “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 @@ -105,26 +105,28 @@ slowdown occurs): 以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 -让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,那么只是因为搜索者恰好是因为处理器闲置了被锁,等待一扇门被接听。相反,我们希望将处理器应用于搜索,在那里它可以做一些真正的工作,因此需要将处理器从一个任务切换到另一个任务的机制。 +让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 -许多型号能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 +许多模型能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 其中一个最大的影响取决于你是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。 -这可能会让你决定,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上值得让它运行得更慢以实现。 +这可能会让你认为,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上为了这个目的值得让它运行得更慢。 -在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本会降低一切,在这种情况下,如果你有多个进程,并发通常只会有意义。 +在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本反而会使任务变慢,在这种情况下,如果你有多个进程,并发通常只会有意义。 -在接听电话的客户服务部门,你只有一定数量的人,但是你可以拨打很多电话。那些人(处理器)必须一次拨打一个电话,直到完成电话和额外的电话必须排队。 +假设你正在尝试破解某种密码,在同一时间内参与破解的线程越多,你越快得到答案的可能性就越大。每个线程都能持续使用你所分配的处理器时间,在这种情况下(一个计算约束问题),你的代码中分配的线程数应该和你拥有的处理器的数量保持一致。 -在“鞋匠和精灵”的童话故事中,鞋匠做了很多工作,当他睡着时,一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时会产生限制 - 例如,如果鞋底需要制作鞋子,这会限制制鞋的速度并改变你设计解决方案的方式。 +在接听电话的客户服务部门,你只有一定数量的员工,但是你的部门可能会收到很多电话。这些员工(处理器)一次只能处理一个电话,直到完成,与此同时,额外的电话必须排队。 -因此,你尝试解决的问题驱动解决方案的设计。打破一个“独立运行”问题的高级[原文:lovely ]抽象,然后就是实际发生的现实。物理现实不断侵入和震撼,这种抽象。 +在“鞋匠和精灵”的童话故事中,鞋匠有很多工作要做,当他睡着时,出现了一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时也会产生限制 - 例如,鞋底需要大量的时间去制作,这会限制制鞋的速度并改变你设计解决方案的方式。 -这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备收到蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先在框中获取蛋糕(通常使用锁定机制来解决问题,因此一个工作人员可以先抓住框并防止蛋糕砸)。 +因此,你尝试解决的问题驱动解决方案的设计。有一个迷人的抽象那就是将一个问题分解为子问题并且让它们独立运行,然后就是赤裸裸的现实。物理现实一次又一次地打了这种抽象的脸。 -当“同时”执行的任务相互干扰时,会出现问题。他可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,编写仅看起来可行的并发程序更为常见,但是在适当的条件下,将会失败。这些情况可能会发生,或者很少发生,你在测试期间从未看到它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 -这是推动并发的最强有力的论据之一:如果你忽略它,你可能会被咬。 +这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备存放蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先将蛋糕放入盒子中(通常使用锁机制来解决问题,因此一个工作人员可以先拿到盒子并防止蛋糕被砸烂)。 + +当“同时”执行的任务相互干扰时,会出现问题。它可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,我们编写的并发程序似乎都能正常工作,但是在适当的条件下,将会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 +这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。 @@ -132,7 +134,7 @@ slowdown occurs): ## 并发为速度而生 -在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定它没有之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置交换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在孤立的地方。 +在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定用它之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置替换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在必要的地方去使用它。 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学习利用那些超级处理器,这是并发性给你的一个建议。 From cff9d8d767e6b7a731678e22c4c05601974518b8 Mon Sep 17 00:00:00 2001 From: theFruitcat <34080012+theFruitcat@users.noreply.github.com> Date: Wed, 24 Jun 2020 11:43:11 +0800 Subject: [PATCH 257/371] =?UTF-8?q?Fix=20issue=20#482:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=B9=B6=E5=8F=91=E7=AB=A0=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=20(#484)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gaoziming --- docs/book/24-Concurrent-Programming.md | 69 +++++++++++++------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index b75d5cc2..a03a4732 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -136,7 +136,7 @@ slowdown occurs): 在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定用它之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置替换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在必要的地方去使用它。 -速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学习利用那些超级处理器,这是并发性给你的一个建议。 +速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学会如何利用那些额外的处理器,这是并发性给你的一个建议。 使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。 @@ -148,13 +148,13 @@ slowdown occurs): 实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和I/O等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 -有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,以防止它们在并发频谱中的适用性(最终你习惯了标准的并发性克制,“这种方法适用于一些情况但不适用于其他情况”) +有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,从而阻止了在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”) -一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 +一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 -并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;否则,你的代码部分将被迫明确标注通常由并发处理的操作。 +并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;除此以外,你必须特别关注那些使用了并发操作的代码。 ## 四句格言 @@ -168,15 +168,15 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 > >4.你仍然必须理解它 -这些特别是关于Java设计中的问题,尽管它也可以应用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 +这些格言专门针对Java的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 ### 1.不要这样做 (不要自己动手) -避免纠缠于并发产生的深层问题的最简单方法就是不要这样做。虽然它是诱人的,并且似乎足够安全,可以尝试做简单的事情,但它存在无数、微妙的陷阱。如果你可以避免它,你的生活会更容易。 +避免纠缠于并发产生的深层问题的最简单方法就是不要这样做。虽然它是诱人的,并且在做一些简单的事情时似乎足够安全,但它存在无数、微妙的陷阱。如果你可以避免它,你的生活会更容易。 -证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 首先应用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。 +证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 应该首先用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。 如果你被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。 @@ -184,11 +184,11 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 没有并发性的编程,你会发现你的世界有一定的顺序和一致性。通过简单地将变量赋值给某个值,很明显它应该始终正常工作。 -在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。我已经很熟悉的东西,认为它显然有效但实际上并没有。 +在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。对于在并发中遇到那些看起来有效但实际上无效的东西,我已经很习惯了。 -在非并发程序中你可以忽略的各种事情突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须了解对象构造的深度复杂性,以便你的构造对象不会意外地将数据暴露给其他线程进行更改。问题还有很多。 +在非并发程序中你可以忽略的各种事情在并发程序中突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须深入了解对象构造的复杂性,以便你的构造器不会意外地将数据暴露给其他线程进行更改。问题还有很多。 -虽然这些主题太复杂,无法为你提供本章的专业知识(再次参见Java Concurrency in Practice),但你必须了解它们。 +因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见Java Concurrency in Practice),但你必须了解它们。 ### 3.它起作用,并不意味着它没有问题 @@ -201,92 +201,93 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 -我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。 +我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码前了解所有并发问题。 -在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。你可以在这个情况下做的最糟糕的事情是“自信”。 +在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 ### 4.你必须仍然理解 -在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续保留它。 +在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢? 唉,你不能轻易逃脱: -- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** clas那样简单的东西。 +- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** class那样简单的东西。 - 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? 人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。 唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 -Java是一种多线程语言,如果你了解它们是否存在并发问题。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 +Java是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 ## 残酷的真相 -当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出追去其他的的能力。火的使用促成了文明。 +当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出能量去发展其他的能力。火的使用促成了文明。 -我们现在通过计算机和网络技术创造了一个“外化大脑”,开始了第二次基本转变。虽然我们只是触及表面,但已经引发了其他转变,例如设计生物机制的能力,并且已经看到文化演变的显着加速(过去,人们不得不前往混合文化,但现在他们开始混合互联网)。这些转变的影响和好处已经超出了科幻作家预测它们的能力(他们在预测文化和个人变化,甚至技术转变的次要影响方面都特别困难)。 +我们现在通过计算机和网络技术创造了一个“外化大脑”,开始了第二次基本转变。虽然我们只是触及表面,但已经引发了其他转变,例如设计生物机制的能力,并且已经看到文化演变的显著加速(过去,人们通过旅游进行文化交流,但现在他们开始在互联网上做这件事)。这些转变的影响和好处已经超出了科幻作家预测它们的能力(他们在预测文化和个人变化,甚至技术转变的次要影响方面都特别困难)。 -有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件 +有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件。 -Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易就像语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气体相位期间,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 +Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易感觉语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气态阶段,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 [Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 -热情使原始Java设计师因为看起来有必要而投入功能。信心(以及原始语言的气味)让他们认为任何问题都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 - 这是非常有信心,相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但保留了对之前版本的支持。 +热情使原始Java设计师加入了一些似乎有必要的特性。信心(以及气态的初始语言)让他们认为任何问题随后都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 -他们非常有信心,并相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但只能在语言中继续保留它以便对之前版本的支持。 -线程包含在Java 1.0中。当然,并发性是影响语言远角的基本语言设计决策,很难想象以后添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。 +线程包含在Java 1.0中。当然,对java来说支持并发是一个很基本的设计决定,该特性影响了这个语言的各个角落,我们很难想象以后在以后的版本添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。 C语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java的大规模扩展迅速暴露了基本问题。在Thread类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。 -不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在函数和方法中,所有不变和防止副作用的方法都会导致简化并发编程(这些是纯函数式编程语言的基础)的变化,但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么对这些选择有所了解,要么认为它们太不同了,并且会抛弃许多潜在的语言采用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 +不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。似乎工作的程序充满了微妙的并发bug。 为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。这艘船航行了;Java将不再是为并发而设计的语言,而只是一种允许它的语言。 -尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它还有多远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 +尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 -这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然容易受到攻击,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 +这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然很脆弱,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 ## 本章其余部分 -这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。使用这些使得你的生活比旧的替代品更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 +这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。相比于旧的替代品,使用这些会使你的生活更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 - Parallel Streams(并行流) -到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在你对该语法(作为一个粉丝,我希望)感到满意,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 +到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在该语法(作为一个粉丝,我希望)会使你感到舒适,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 - 创建和运行任务 -任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures:Executor更复杂的机制。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executorto运行。 +任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures更简单的机制:Executor。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executor去运行。 有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 - 终止长时间运行的任务 任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。 - Completable Futures -当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,最终你的衣服很干净,你可以拿起它。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 +当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,当你的衣服洗干净时你可以把它取走。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 -Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,等待任务没有完成。对于一系列操作,Futures并没有真正帮助那么多。 +Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,如果任务没有完成你还需要等待。对于一系列操作,Futures并没有真正帮助那么多。 -Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,清理菜肴,储存菜肴”等一系列链式操作。 +Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,收拾餐具,储存餐具”等一系列链式操作。 - 死锁 -某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,等待另一个被阻止的任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入僵局。 +某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,另一个被阻止的任务也在等待其他任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入死锁。 -如果在运行程序时没有立即出现死锁,则会出现最大的问题。你的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上运行正常,例如你的开发机器,但是当你将其部署到不同的硬件时会开始死锁。 +如果在运行程序时没有立即出现死锁,则会出现最大的问题。你的系统可能容易出现死锁,并且只会在某些条件下死锁。程序可能在某个平台上(例如在你的开发机器)运行正常,但是当你将其部署到不同的硬件时会开始死锁。 死锁通常源于细微的编程错误;一系列无辜的决定,最终意外地创建了一个依赖循环。本节包含一个经典示例,演示了死锁的特性。 -我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是完成配置。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来加速计划。 +* 努力,复杂,成本 + +我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是CompletableFutures。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来使你的程序变得更快。 -- 努力,复杂,成本 ## 并行流 From 2dc7cf7520b548d8e8fb0135733d625d9f9f05b3 Mon Sep 17 00:00:00 2001 From: YellowPixels Date: Sat, 27 Jun 2020 20:49:46 +0800 Subject: [PATCH 258/371] Update 20-Generics.md (#487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除多余空格,使markdown正常显示 --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index e207114d..6f244bfa 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2957,7 +2957,7 @@ public class UnboundedWildcards2 { ``` 但是,当你拥有的全都是无界通配符时,就像在 `Map` 中看到的那样,编译器看起来就无法将其与原生 **Map** 区分开了。另外, **UnboundedWildcards1.java** 展示了编译器处理 `List` 和 `List` 是不同的。 -令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List ** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。” +令人困惑的是,编译器并非总是关注像 `List` 和 `List` 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 `List` 看起来等价于 `List` ,而 **List** 实际上也是 `List` ——除非这些语句都不为真。**List** 实际上表示“持有任何 **Object** 类型的原生 **List** ”,而 `List` 表示“具有某种特定类型的非原生 **List** ,只是我们不知道类型是什么。” 编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 `Holder` 类,它包含接受 **Holder** 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数: ```java From 7a425a21e83ea90f1c51953fb2695b613680d908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=86=9B=E7=A5=A5=20=28Junxiang=20Zheng=29?= Date: Sun, 5 Jul 2020 15:39:20 +0800 Subject: [PATCH 259/371] fix some typos and translations in 07-Implementation-Hiding (#489) --- docs/book/07-Implementation-Hiding.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index cec6b2b2..874ba9ce 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -270,7 +270,7 @@ public class Range { } ``` -这个文件的位置一定是在某个以一个 CLASSPATH 位置开始,然后接着是 **onjava** 的目录下。编译完之后,就可以在系统的任何地方使用 **import static** 语句来使用这些方法了。 +这个文件的位置一定是在某个以一个 CLASSPATH 位置开始,然后接着是 **onjava** 的目录下。编译完之后,就可以在系统的任何地方使用 **import onjava** 语句来使用这些方法了。 从现在开始,无论何时你创建了有用的新工具,都可以把它加入到自己的类库中。在本书中,你将会看到更多的组件加入到 **onjava** 库。 @@ -611,7 +611,7 @@ import hiding.*; 2. **public** 类的名称必须与含有该编译单元的文件名相同,包括大小写。所以对于 **Widget** 来说,文件名必须是 **Widget.java**,不能是 **widget.java** 或 **WIDGET.java**。再次强调,如果名字不匹配,编译器会报错。 3. 虽然不是很常见,但是编译单元内没有 **public** 类也是可能的。这时可以随意命名文件(尽管随意命名会让代码的阅读者和维护者感到困惑)。 -如果获取了一个在 **hiding** 包中的类,只用来完成 **Widget** 或 **hiding** 包下一些其他 **public** 类所要执行的任务,怎么办呢? 你不想自找麻烦为客户端程序员创建说明文档,并且你认为不久后会完全改变原有方案并将旧版本删除,替换成新版本。为了保留此灵活性,需要确保客户端程序员不依赖隐藏在 **hiding** 中的任何特定细节,那么把 **public** 关键字从类中去掉,给予它包访问权限,就可以了。 +如果是一个在 **hiding** 包中的类,只用来完成 **Widget** 或 **hiding** 包下一些其他 **public** 类所要执行的任务,怎么设置它的访问权限呢? 你不想自找麻烦为客户端程序员创建说明文档,并且你认为不久后会完全改变原有方案并将旧版本删除,替换成新版本。为了保留此灵活性,需要确保客户端程序员不依赖隐藏在 **hiding** 中的任何特定细节,那么把 **public** 关键字从类中去掉,给予它包访问权限,就可以了。 当你创建了一个包访问权限的类,把类中的属性声明为 **private** 仍然是有意义的——应该尽可能将所有属性都声明为 **private**,但是通常把方法声明成与类(包访问权限)相同的访问权限也是合理的。一个包访问权限的类只能被用于包内,除非强制将某些方法声明为 **public**,这种情况下,编译器会告诉你。 @@ -686,7 +686,7 @@ public class Lunch { 类的 **public** 接口是用户真正看到的,所以在分析和设计阶段决定这部分接口是最重要的部分。尽管如此,你仍然有改变的空间。如果最初没有创建出正确的接口,可以添加更多的方法,只要你不删除那些客户端程序员已经在他们的代码中使用的东西。 -注意到访问权限控制关注的是类库创建者和外部使用者之间的关系,一种交流方式。很多情况下,事实并非如此。例如,你自己编写了所有的代码,或者在一个小组中工作,所有的东西都放在同一个包下。这些情况下,交流方式则是另外一种,此时严格地遵循访问权限规则也许不是最佳选择,默认(包)访问权限也许就足够好了。 +注意到访问权限控制关注的是类库创建者和外部使用者之间的关系,这是一种交流方式。很多情况下,事实并非如此。例如,你自己编写了所有的代码,或者在一个小组中工作,所有的东西都放在同一个包下。这些情况下,交流方式则是另外一种,此时严格地遵循访问权限规则也许不是最佳选择,默认(包)访问权限也许就足够好了。 From f111af86cf5388bd4d02811b46be36c2fae37541 Mon Sep 17 00:00:00 2001 From: AHBICJ <810988732@qq.com> Date: Sun, 5 Jul 2020 15:54:04 +0800 Subject: [PATCH 260/371] Update 06-Housekeeping.md (#492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 从上下文阅读来看,这里应该是2 --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 7874b2bd..7cef1630 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -437,7 +437,7 @@ public class BananaPeel { ```java Banana.peel(a, 1) -Banana.peel(b, 1) +Banana.peel(b, 2) ``` 这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,但能说明到底发生了什么。假设现在在方法内部,你想获得对当前对象的引用。但是,对象引用是被秘密地传达给编译器——并不在参数列表中。方便的是,有一个关键字: **this** 。**this** 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,**this** 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用其他该类中的方法,不要使用 **this**,直接调用即可,**this** 自动地应用于其他方法上了。因此你可以像这样: From 5879bf23c53d2e23b9ee8b65c6366678de3b4f14 Mon Sep 17 00:00:00 2001 From: tangzhentao Date: Wed, 8 Jul 2020 14:04:01 +0800 Subject: [PATCH 261/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AC=94=E8=AF=AF=20?= =?UTF-8?q?(#493)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改笔误 --- docs/book/10-Interfaces.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 7625e07b..493fb91f 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -73,8 +73,8 @@ abstract class Basic3 { } public class AbstractWithoutAbstracts { - // Basic b3 = new Basic3(); - // error: Basic 3 is abstract; cannot be instantiated + // Basic3 b3 = new Basic3(); + // error: Basic3 is abstract; cannot be instantiated } ``` From 9e733a25c331510b59d1dce45403eb5a9ea43c44 Mon Sep 17 00:00:00 2001 From: sleepingraven <32897701+sleepingraven@users.noreply.github.com> Date: Thu, 9 Jul 2020 18:09:13 +0800 Subject: [PATCH 262/371] =?UTF-8?q?=E5=8D=8A=E8=A7=92=E6=8B=AC=E5=8F=B7?= =?UTF-8?q?=E3=80=81=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E7=A9=BA=E6=A0=BC?= =?UTF-8?q?=20(#494)=20(#495)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/05-Control-Flow.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md index e2699007..854d6dc5 100644 --- a/docs/book/05-Control-Flow.md +++ b/docs/book/05-Control-Flow.md @@ -767,7 +767,7 @@ RED 一旦理解了 **switch**,你会明白这其实就是一个逻辑扩展的语法糖。新的编码方式能使得结果更清晰,更易于理解和维护。 -作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、 [0,1)、(0,1] 、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”) +作为 **switch** 字符串的第二个例子,我们重新访问 `Math.random()`。 它是否产生从 0 到 1 的值,包括还是不包括值 1 呢?在数学术语中,它属于 (0,1)、[0,1)、(0,1]、[0,1] 中的哪种呢?(方括号表示“包括”,而括号表示“不包括”) 下面是一个可能提供答案的测试程序。 所有命令行参数都作为 **String** 对象传递,因此我们可以 **switch** 参数来决定要做什么。 那么问题来了:如果用户不提供参数 ,索引到 `args` 的数组就会导致程序失败。 解决这个问题,我们需要预先检查数组的长度,若长度为 0,则使用**空字符串** `""` 替代;否则,选择 `args` 数组中的第一个元素: @@ -810,7 +810,7 @@ java RandomBounds lower java RandomBounds upper ``` -使用 `onjava` 包中的 **TimedAbort** 类可使程序在三秒后中止。从结果来看,似乎 `Math.random()` 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 **double** 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:`Math.random()` 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1)来表示。由此可知,我们必须小心分析实验并了解它们的局限性。 +使用 `onjava` 包中的 **TimedAbort** 类可使程序在三秒后中止。从结果来看,似乎 `Math.random()` 产生的随机值里不包含 0.0 或 1.0。 这就是该测试容易混淆的地方:若要考虑 0 至 1 之间所有不同 **double** 数值的可能性,那么这个测试的耗费的时间可能超出一个人的寿命了。 这里我们直接给出正确的结果:`Math.random()` 的结果集范围包含 0.0 ,不包含 1.0。 在数学术语中,可用 [0,1) 来表示。由此可知,我们必须小心分析实验并了解它们的局限性。 ## 本章小结 From ad74befe7132b9eb719dfe39a427bae4fe04587f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A6=AC=E5=AE=B6=E7=94=BB?= Date: Fri, 10 Jul 2020 11:57:28 +0900 Subject: [PATCH 263/371] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=86=97=E4=BD=99?= =?UTF-8?q?=E5=AD=97=20(#497)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/00-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index 19cdb13f..bcd233c0 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -33,7 +33,7 @@ Java 语言曾规划设计的许多功能并未按照承诺兑现。本书中, - 控制语句(例如 **if**),循环结构(例如 **while**) -可能你已在学校、书籍或网络上了学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。 +可能你已在学校、书籍或网络上学过这些。只要你觉得对上述的编程基本概念熟悉,你就可以完成本书的学习。 你可以通过在 On Java 8 的网站上免费下载 《Think in C》来补充学习 Java 所需要的前置知识。本书介绍了 Java 语言的基本控制机制以及面向对象编程(OOP)的概念。在本书中我引述了一些 C/C++ 语言中的一些特性来帮助读者更好的理解 Java。毕竟 Java 是在它们的基础之上发明的,理解他们之间的区别,有助于读者更好地学习 Java。我会试图简化这些引述,尽量让没有 C/C++ 基础的读者也能很好地理解。 From f348698b91a6cd9fc50474ec7168bd973f2a8784 Mon Sep 17 00:00:00 2001 From: theFruitcat <34080012+theFruitcat@users.noreply.github.com> Date: Wed, 15 Jul 2020 16:09:42 +0800 Subject: [PATCH 264/371] =?UTF-8?q?Fix=20issue=20#482:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=B9=B6=E5=8F=91=E7=AB=A0=E8=8A=82=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=20(#498)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix issue #482: 优化并发章节翻译 * Fix issue #482: 优化并发章节翻译 Co-authored-by: gaoziming --- docs/book/24-Concurrent-Programming.md | 93 +++++++++++++------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index a03a4732..18a07565 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -245,9 +245,9 @@ C语言是面向过程语言,这限制了它的野心。这些限制使附加 不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 -Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。似乎工作的程序充满了微妙的并发bug。 +Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。表面上看起来正常工作的程序实际上充满了微妙的并发bug。 -为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。这艘船航行了;Java将不再是为并发而设计的语言,而只是一种允许它的语言。 +为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。木已成舟;Java 将不再是为并发而设计的语言,而只是一种允许并发的语言。 尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 @@ -291,7 +291,7 @@ Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作 ## 并行流 -Java 8流的一个显着优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。这产生了相当神奇的结果,即能够简单用parallel()然后流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 +Java 8流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: @@ -332,19 +332,19 @@ public class ParallelPrime { 1224 ``` -请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且消除了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。 +请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止编译器过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且终止了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。 -当我注释掉[1] parallel()行时,我的结果大约是parallel()的三倍。 +当我注释掉[1] parallel()行时,我的结果用时大约是parallel()的三倍。 并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 - parallel()不是灵丹妙药 -作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:求和数字的增量序列。事实证明这是一个令人惊讶的数量,并且我将冒险将它们进行比较 - 试图小心,但承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“升温”),但我认为它仍然提供了一些有用的指示。 +作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:对增长的数字序列进行求和。事实证明有大量的方式去实现它,并且我将冒险用计时器将它们进行比较 - 我会尽量小心,但我承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“热身”),但我认为它仍然提供了一些有用的指示。 -我将从一个计时方法rigorously 开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。 +我将从一个计时方法**timeTest()**开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。 -请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。 +请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。 所有关于时间和内存的数字和讨论都是指“我的机器”。你的经历可能会有所不同。 @@ -394,19 +394,19 @@ Sum Stream Parallel: 46ms Sum Iterated: 284ms ``` -**CHECK**值是使用Carl Friedrich Gauss在1700年代后期仍在小学时创建的公式计算出来的. +**CHECK**值是使用Carl Friedrich Gauss(高斯)在1700年代后期还在上小学的时候创建的公式计算出来的. - **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于十亿分之一的SZ在没有溢出的情况下处理(我使用较小的数字,因此程序运行时间不长)。使用 **parallel()** 的基本范围操跟快。 + **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使SZ为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 -如果使用**iterate()**来生成序列,则减速是戏剧性的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,那么结果通常比非并行版本花费的时间更长,但是当**SZ**超过一百万时,它也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: +如果使用**iterate()**来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,当**SZ**超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: - 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。 -- 阵列分割成本低廉,均匀且具有完美的分裂知识。 -- 链接列表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余列表”,这相对无用。 -- 无状态生成器的行为类似于数组;使用上述范围是无可争议的。 +- 数组分割成本低,分割均匀且对分割的大小有着完美的掌控。 +- 链表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余元素”,这相对无用。 +- 无状态生成器的行为类似于数组;上面使用的 **range()** 就是无状态的。 - 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。 -现在让我们尝试通过在数组中填充值来填充数组来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。 +现在让我们尝试通过在数组中填充值并对数组求和来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。 首先我们将尝试一个充满原始**long**的数组: @@ -453,9 +453,9 @@ Basic Sum: 106ms parallelPrefix: 265ms ``` -第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。” +第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。 -最后,考虑使用包装类**Long**的效果: +最后,考虑使用包装类**Long**的效果: ```java // concurrent/Summing3.java @@ -532,9 +532,9 @@ Long Parallel: 1014ms 它比非parallel()版本略快,但并不显着。 -这种时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。 +导致时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。 -使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是超出缓存。 +使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是不在缓存中。 这些示例使用不同的SZ值来显示内存限制。 @@ -552,13 +552,13 @@ Long Basic Sum: 21ms Long parallelPrefix: 3287ms Long Parallel: 1008ms** -虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!”我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 +虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!” 我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 - parallel()/limit()交点 -使用parallel()时会有更复杂的问题。从其他语言中吸取的流是围绕无限流模型设计的。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 +使用**parallel()**时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 -Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在Collection和Map中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将Collection转换为存在这些操作的Stream: +Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在**Collection**和**Map**中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将**Collection**转换为存在这些操作的**Stream**: ```java // concurrent/CollectionIntoStream.java @@ -584,6 +584,7 @@ public class CollectionIntoStream { 输出结果: ``` +btpen pccux szgvg meinn @@ -596,9 +597,9 @@ bynxt :PENCUXGVGINNLOZVEWPPCPOALJLNXT ``` -**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作.**ConcurrentHashMap**对**forEachand**和**reduce**操作有特别广泛的支持。 +**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作。**ConcurrentHashMap**对**forEach**和**reduce**操作有特别广泛的支持。 -在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意外。这是一个有趣的难题: +在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意想不到的结果。这是一个有趣的难题: ```java // concurrent/ParallelStreamPuzzle.java @@ -609,6 +610,7 @@ public class ParallelStreamPuzzle { static class IntGenerator implements Supplier { private int current = 0; + @Override public Integer get() { return current++; } @@ -630,7 +632,7 @@ public class ParallelStreamPuzzle { **[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]** 每次。但是包含了parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如: **[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]** -这样一个简单的程序怎么会这么破碎呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”“那意味着什么?一堆线程都在拉动一个生成器,在某种程度上选择一组有限的结果?代码使它看起来很简单,但它转向是一个特别凌乱的问题。 +这样一个简单的程序怎么会如此糟糕呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”那意味着什么?一堆线程都在从一个生成器取值,然后以某种方式选择有限的结果集?代码看起来很简单,但它变成了一个特别棘手的问题。 为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用**ConcurrentLinkedDeque**: @@ -643,14 +645,15 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.nio.file.*; public class ParallelStreamPuzzle2 { - public static final Deque trace = + public static final Deque TRACE = new ConcurrentLinkedDeque<>(); static class IntGenerator implements Supplier { private AtomicInteger current = new AtomicInteger(); - public Integerget() { - trace.add(current.get() + ": " +Thread.currentThread().getName()); + @Override + public Integer get() { + TRACE.add(current.get() + ": " +Thread.currentThread().getName()); return current.getAndIncrement(); } } @@ -660,7 +663,7 @@ public class ParallelStreamPuzzle2 { .parallel() .collect(Collectors.toList()); System.out.println(x); - Files.write(Paths.get("PSP2.txt"), trace); + Files.write(Paths.get("PSP2.txt"), TRACE); } } ``` @@ -699,9 +702,9 @@ current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞 22: ForkJoinPool.commonPool-worker-110 23: ForkJoinPool.commonPool-worker-1** -这个块大小似乎是内部实现的一部分(尝试使用**limit()**的不同参数来查看不同的块大小)。将**parallel()**与**limit()**结合使用可以预取一串值,作为流输出。 +这个块大小似乎是内部实现的一部分(尝试使用`limit()` 的不同参数来查看不同的块大小)。将`parallel()`与`limit()`结合使用可以预取一串值,作为流输出。 -试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用get()。添加limit(),你说“只需要这些。”基本上,当你将parallel()与limit()结合使用时,你要求随机输出 - 这可能对你正在解决的问题很好。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。 +试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用`get()`。添加`limit()`,你说“只需要这些。”基本上,当你为了随机输出而选择将`parallel()`与`limit()`结合使用时,这种方法可能对你正在解决的问题有效。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。 什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示: @@ -742,15 +745,15 @@ public class ParallelStreamPuzzle3 { 为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -你还可以看到boxed()的添加,它接受int流并将其转换为Integer流。 +你还可以看到**boxed()**的添加,它接受**int**流并将其转换为**Integer**流。 现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 -它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。当一个简单的数字序列并行生成时,有点难以想象。如果你使用昂贵的产品,它可能有意义 - 但这都是猜测。唯一知道的是通过测试。记住这句格言:“首先制作它,然后快速制作 - 但只有你必须这样做。”**parallel()**和**limit()**仅供专家使用(并且要清楚,我不认为自己是这里的专家)。 +它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”**parallel()**和**limit()**仅供专家使用(把话说在前面,我不认为自己是这里的专家)。 - 并行流只看起来很容易 -实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,只需将**parallel()**打到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。有个错误认识是认为并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码以便并行运行它。流什么都不做的是取代理解并行性如何工作的需要,以及它是否有助于实现你的目标。 +实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将**parallel()**加到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 ## 创建和运行任务 @@ -762,7 +765,7 @@ Java并发的历史始于非常原始和有问题的机制,并且充满了各 在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 -创建所有这些线程的开销变得非常重要,现在不鼓励采用实际操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 +创建所有这些线程的开销变得非常重要,现在不鼓励采用手动操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: @@ -807,11 +810,11 @@ public class Nap { } } ``` -为了消除异常处理的视觉噪声,这被定义为实用程序。第二个构造函数在超时时显示一条消息 +为了消除异常处理的视觉干扰,这被定义为实用程序。第二个构造函数在超时时显示一条消息 对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 -你可以看到**sleep()**抛出一个已检查的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 +你可以看到**sleep()**抛出一个受检的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 要执行任务,我们将从最简单的方法--SingleThreadExecutor开始: @@ -872,7 +875,7 @@ NapTask[9] pool-1-thread-1 请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。 -如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,虽然不需要(!exec.isTerminated())。 +如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,不需要**while(!exec.isTerminated())**。 ```java // concurrent/SingleThreadExecutor2.java @@ -968,7 +971,7 @@ NapTask[6] pool-1-thread-7 NapTask[5] pool-1-thread-6 ``` -当你运行这个程序时,你会发现它完成得更快。这是有道理的,而不是使用相同的线程来顺序运行每个任务,每个任务都有自己的线程,所以它们都并行运行。似乎没有缺点,很难看出为什么有人会使用SingleThreadExecutor。 +当你运行这个程序时,你会发现它完成得更快。这是有道理的,每个任务都有自己的线程,所以它们都并行运行,而不是使用相同的线程来顺序运行每个任务。这似乎没毛病,很难理解为什么有人会使用SingleThreadExecutor。 要理解这个问题,我们需要一个更复杂的任务: @@ -1024,7 +1027,7 @@ public class CachedThreadPool2 { 6 pool-1-thread-7 1000 ``` -输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们说这样的类不是线程安全的。让我们看看SingleThreadExecutor会发生什么: +输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们称这样的类是线程不安全的。让我们看看SingleThreadExecutor会发生什么: ```java // concurrent/SingleThreadExecutor3.java @@ -1057,7 +1060,7 @@ public class SingleThreadExecutor3 { 9 pool-1-thread-1 1000 ``` -现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程限制,因为在单线程上运行任务限制了它们的影响。线程限制限制了加速,但可以节省很多困难的调试和重写。 +现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程封闭,因为在单线程上运行任务限制了它们的影响。线程封闭限制了加速,但可以节省很多困难的调试和重写。 - 产生结果 @@ -1164,13 +1167,13 @@ public class Futures { 100 ``` -- [1] 当你的任务尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。 +- [1] 当你的任务在尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。 但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。 还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。 -因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成的问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,支持Java 8的**CompletableFuture**,我们将在本章后面探讨。当然,你仍会在遗留库中遇到Futures +因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成才暴露问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,我们推荐Java 8的**CompletableFuture**,这将在本章后面探讨。当然,你仍会在遗留库中遇到Futures。 我们可以使用并行Stream以更简单,更优雅的方式解决这个问题: @@ -1208,11 +1211,11 @@ public class CountingStream { 1000 ``` -这不仅更容易理解,我们需要做的就是将**parallel()**插入到其他顺序操作中,然后一切都在同时运行。 +这不仅更容易理解,而且我们需要做的就是将 `parallel()` 插入到其他顺序操作中,然后一切都在同时运行。 - Lambda和方法引用作为任务 -在 `java8` , 你不需要受限于在 `Runnables ` 和 `Callables` 时,使用`lambdas` 和方法引用, 同样也可以通过匹配签名来引用(即,它支持结构一致性)。 所以我们可以将 `notRunnables` 或 `Callables` 的参数传递给`ExecutorService` : +在 **java8** 有了 **lambdas** 和方法引用,你不需要受限于只能使用 **Runnable** 和 **Callable** 。因为 java8 的**lambdas** 和方法引用可以通过匹配方法签名来使用(即,它支持结构一致性),所以我们可以将非 **Runnable** 或 **Callable** 的参数传递给 `ExecutorService` : ```java // concurrent/LambdasAndMethodReferences.java From fc5cdcf028f2cbf694ad873f24ee9b673f3fdcda Mon Sep 17 00:00:00 2001 From: sleepingraven <32897701+sleepingraven@users.noreply.github.com> Date: Fri, 17 Jul 2020 10:36:48 +0800 Subject: [PATCH 265/371] =?UTF-8?q?Fix=20issue=20#496=EF=BC=9A=E5=89=8D?= =?UTF-8?q?=E8=A8=80=E9=83=A8=E5=88=86=20(#502)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 半角括号、删除多余空格 (#494) * 前言 (#496) --- docs/book/00-Preface.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index daea9e85..d614e816 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -3,11 +3,11 @@ > 本书基于 Java 8 版本来教授当前 Java 编程的最优实践。 -此前,我的另一本 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想》 第 4 版 Prentice Hall 2006)依然适用于 Java 5 编程。Android 编程就是始于此语言版本。 +我之前的 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想 (第4版)》 Prentice Hall 2006)依然适用于 Java 5 编程,在此版本 Java 语言开始用作 Android 编程。此后,这门语言的许多地方发生了翻天覆地的变化,特别是 Java 8 的转变,以至于新的 Java 代码读起来的感觉也不尽相同。这也促使我时隔多年,创作了这本新书。 -随着 Java 8 的出现,这门语言在许多地方发生了翻天覆地的变化。在新的版本中,代码的运用和实现上与以往不尽相同。这也促使了我时隔多年后再次创作了这本新书。《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站上补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。 +《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。与几年前我们依赖印刷媒体时相比,像 YouTube、博客和 StackOverFlow 这样的网站使得寻找答案变得非常简单。如果将本书作为编程入门书籍,请结合这些学习途径努力坚持下去。同时,本书也适合想要扩展知识的在职程序员。 -与几年前我们依赖印刷媒体相比,YouTube,博客和 StackOverflow 等网站的出现让寻找答案变得简单。请结合这些学习途径和努力坚持下去。本书可作为编程入门书籍,同时也适用于想要扩展知识的在职程序员。每次在世界各地的演讲中,我都非常感谢 《*Thinking in Java*》 这本书给我带来的所有荣誉。它对于我重塑 [Reinventing Business](http://www.reinventing-business.com) 项目和促进交流是非常宝贵的。最后,写这本书的原因之一 希望这本书可以为我的这个项目众筹。似乎下一步要创建一个所谓的蓝绿色组织(Teal Organization)才合乎逻辑的。 +得益于《*Thinking in Java*》,我得以到世界各地演讲,我对此由衷感激。它为我的 [Reinventing Business](http://www.reinventing-business.com) 项目在与人员及公司建立联系方面提供了宝贵的帮助。我最终撰写本书的原因之一就是想支持这一项目的研究,而下一个合乎逻辑的步骤似乎是实际创建一个所谓的蓝绿色组织(Teal Organization)。我希望本书可以成为该项目的一种众筹。 ## 教学目标 From c49d1cbf07774b745f5b82f2fac40099ec52763b Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Fri, 17 Jul 2020 12:04:47 +0800 Subject: [PATCH 266/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E7=9A=84=E6=96=87=E4=B8=8D=E5=AF=B9=E6=84=8F?= =?UTF-8?q?=20(#503)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改翻译中的的文不对意 --- docs/book/13-Functional-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index c22b1314..75d85ce0 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1428,7 +1428,7 @@ Hup Hey **[1]** 这一连串的箭头很巧妙。*注意*,在函数接口声明中,第二个参数是另一个函数。 -**[2]** 柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “无参函数” 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。 +**[2]** 柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “自由函数”(free argumnet) 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。 我们可以通过添加级别来柯里化一个三参数函数: From 6dac2c1392cfcca8191136373a7972d8347fce5d Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Fri, 17 Jul 2020 18:32:57 +0800 Subject: [PATCH 267/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E7=9A=84=E6=96=87=E4=B8=8D=E5=AF=B9=E6=84=8F?= =?UTF-8?q?=20=20(#504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改翻译中的的文不对意 修改翻译中的的文不对意 * 文不对意 原文:anyMatch(Predicate): Returns true if any element of the stream produces true when provided to the supplied Predicate. This will short-circuit upon the first true. 所以是取决于第一个 true --- docs/book/14-Streams.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 5977128d..7b3e5c30 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1957,8 +1957,8 @@ Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结 ### 匹配 - `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。 -- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。 -- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。 +- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 true 时,则停止执行计算。 +- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时,停止执行计算。 我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: From d8260c3fc813b3e931fb62218823d007a107438c Mon Sep 17 00:00:00 2001 From: Yibin Yang Date: Sun, 19 Jul 2020 13:24:06 +0200 Subject: [PATCH 268/371] =?UTF-8?q?=E4=BF=AE=E6=94=B913=E7=AB=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=BC=8F=E7=BC=96=E7=A8=8B=E4=B8=AD=E4=B8=8D=E7=AC=A6?= =?UTF-8?q?=E5=90=88=E4=B8=AD=E6=96=87=E8=A1=A8=E8=BE=BE=E4=B9=A0=E6=83=AF?= =?UTF-8?q?=E7=9A=84=E8=A1=A8=E8=BF=B0=20(#507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/13-Functional-Programming.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 75d85ce0..beaf61db 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -104,21 +104,21 @@ Hello there Hello there **Strategy** 接口提供了单一的 `approach()` 方法来承载函数式功能。通过创建不同的 **Strategy** 对象,我们可以创建不同的行为。 -传统上,我们通过创建一个实现 **Strategy** 接口的类来实现此行为,比如在 **Soft**。 +我们一般通过创建一个实现**Strategy**接口的类来实现这种行为,正如在**Soft**里所做的。 -- **[1]** 在 **Strategize** 中,**Soft** 作为默认策略,在构造函数中赋值。 +- **[1]** 在 **Strategize** 中,你可以看到 **Soft** 作为默认策略,在构造函数中赋值。 -- **[2]** 一种略显简短且更自发的方法是创建一个**匿名内部类**。即使这样,仍有相当数量的冗余代码。你总是要仔细观察:“哦,原来这样,这里使用了匿名内部类。” +- **[2]** 一种较为简洁且更加自然的方法是创建一个**匿名内部类**。即便如此,仍有相当数量的冗余代码。你总需要仔细观察后才会发现:“哦,我明白了,原来这里使用了匿名内部类。” -- **[3]** Java 8 的 Lambda 表达式。由箭头 `->` 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。 +- **[3]** Java 8 的 Lambda 表达式,其参数和函数体被箭头 `->` 分隔开。箭头右侧是从 Lambda 返回的表达式。它与单独定义类和采用匿名内部类是等价的,但代码少得多。 -- **[4]** Java 8 的**方法引用**,由 `::` 区分。在 `::` 的左边是类或对象的名称,在 `::` 的右边是方法的名称,但没有参数列表。 +- **[4]** Java 8 的**方法引用**,它以 `::` 为特征。 `::` 的左边是类或对象的名称, `::` 的右边是方法的名称,但是没有参数列表。 -- **[5]** 在使用默认的 **Soft** **strategy** 之后,我们逐步遍历数组中的所有 **Strategy**,并使用 `changeStrategy()` 方法将每个 **Strategy** 放入 变量 `s` 中。 +- **[5]** 在使用默认的 **Soft** 策略之后,我们逐步遍历数组中的所有 **Strategy**,并通过调用 `changeStrategy()` 方法将每个 **Strategy** 传入变量 `s` 中。 -- **[6]** 现在,每次调用 `communicate()` 都会产生不同的行为,具体取决于此刻正在使用的策略**代码对象**。我们传递的是行为,而非仅数据。[^3] +- **[6]** 现在,每次调用 `communicate()` 都会产生不同的行为,具体取决于此刻正在使用的策略**代码对象**。我们传递的是行为,而并不仅仅是数据。[^3] -在 Java 8 之前,我们能够通过 **[1]** 和 **[2]** 的方式传递功能。然而,这种语法的读写非常笨拙,并且我们别无选择。方法引用和 Lambda 表达式的出现让我们可以在需要时**传递功能**,而不是仅在必要才这么做。 +在 Java 8 之前,我们能够通过 **[1]** 和 **[2]** 的方式传递功能。然而,这种语法的读写非常笨拙,并且我们别无选择。方法引用和 Lambda 表达式的出现让我们可以在需要时**传递功能**,而不是仅在必要时才这么做。 From 804b4eedcad9b62c45a49dd89809eb4d7c7857bd Mon Sep 17 00:00:00 2001 From: LikoLi Date: Mon, 20 Jul 2020 00:20:20 +0800 Subject: [PATCH 269/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9B=86=E5=90=88?= =?UTF-8?q?=E7=AB=A0=E8=8A=82=E9=83=A8=E5=88=86=E7=AC=94=E8=AF=AF=20(#509)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: liko --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index fd0e3c25..b1cc66dd 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -1794,7 +1794,7 @@ Serializable] 请注意,标记接口 **java.util.RandomAccess** 附加到了 **ArrayList** 上,但不附加到 **LinkedList** 上。这为根据特定 **List** 动态改变其行为的算法提供了信息。 -从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了 **java.util** 中更多的有关集合的内容后(特别是在[附录:集合主题]()中的内容),就会发现出了继承结构有点奇怪外,还有更多的问题。集合类库一直以来都是设计难题——解决这些问题涉及到要去满足经常彼此之间互为牵制的各方面需求。所以要做好准备,在各处做出妥协。 +从面向对象的继承层次结构来看,这种组织结构确实有些奇怪。但是,当了解了 **java.util** 中更多的有关集合的内容后(特别是在[附录:集合主题]()中的内容),就会发现除了继承结构有点奇怪外,还有更多的问题。集合类库一直以来都是设计难题——解决这些问题涉及到要去满足经常彼此之间互为牵制的各方面需求。所以要做好准备,在各处做出妥协。 尽管存在这些问题,但 Java 集合仍是在日常工作中使用的基本工具,它可以使程序更简洁、更强大、更有效。你可能需要一段时间才能熟悉集合类库的某些方面,但我想你很快就会找到自己的路子,来获得和使用这个类库中的类。 From d3f7333fb3fa60151f020935d90344899c644dd1 Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Mon, 20 Jul 2020 20:11:30 +0800 Subject: [PATCH 270/371] =?UTF-8?q?=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF=20?= =?UTF-8?q?(#511)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/15-Exceptions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index f114f4e8..82303e94 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -450,7 +450,7 @@ Throwable fillInStackTrace() 用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常(很快就会讲到)时很有用。 -此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass)也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName)方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。 +此外,也可以使用 Throwable 从其基类 Object(也是所有类的基类)继承的方法。对于异常来说,getClass() 也许是个很好用的方法,它将返回一个表示此对象类型的对象。然后可以使用 getName() 方法查询这个 Class 对象包含包信息的名称,或者使用只产生类名称的 getSimpleName() 方法。 下面的例子演示了如何使用 Exception 类型的方法: @@ -778,7 +778,7 @@ public class PreciseRethrow { 常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在 JDK1.4 以前,程序员必须自己编写代码来保存原始异常的信息。现在所有 Throwable 的子类在构造器中都可以接受一个 cause(因由)对象作为参数。这个 cause 就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。 -有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause0 方法而不是构造器。 +有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause() 方法而不是构造器。 下面的例子能让你在运行时动态地向 DymamicFields 对象添加字段: @@ -912,7 +912,7 @@ DynamicFields.setField(DynamicFields.java:67) 每个 DynamicFields 对象都含有一个数组,其元素是“成对的对象”。第一个对象表示字段标识符(一个字符串),第二个表示字段值,值的类型可以是除基本类型外的任意类型。当创建对象的时候,要合理估计一下需要多少字段。当调用 setField() 方法的时候,它将试图通过标识修改已有字段值,否则就建一个新的字段,并把值放入。如果空间不够了,将建立一个更长的数组,并把原来数组的元素复制进去。如果你试图为字段设置一个空值,将抛出一个 DynamicFieldsException 异常,它是通过使用 initCause() 方法把 NullPointerException 对象插入而建立的。 -至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField0 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。 +至于返回值,setField() 将用 getField() 方法把此位置的旧值取出,这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法,那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常,但如果异常是从 setField() 方法里抛出的,这种情况将被视为编程错误,所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。 你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 @@ -1380,7 +1380,7 @@ public class StormyInning extends Inning implements Storm { 在 Inning 类中,可以看到构造器和 event() 方法都声明将抛出异常,但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常,所以它很合理。这对于抽象方法同样成立,比如 atBat()。 -接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event(方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。 +接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut,如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口,那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event() 方法的异常接口。否则的话,在使用基类的时候就不能判断是否捕获了正确的异常,所以这也很合理。当然,如果接口里定义的方法不是来自于基类,比如 rainHard(),那么此方法抛出什么样的异常都没有问题。 异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常,而不必理会基类构造器所抛出的异常。然而,因为基类构造器必须以这样或那样的方式被调用(这里默认构造器将自动被调用),派生类构造器的异常说明必须包含基类构造器的异常说明。 From 4ddf2543f1b162efe4d21ebb5c145e42a92ea988 Mon Sep 17 00:00:00 2001 From: Yibin Yang Date: Mon, 20 Jul 2020 14:16:12 +0200 Subject: [PATCH 271/371] =?UTF-8?q?=E4=BF=AE=E6=94=B913=E7=AB=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=BC=8F=E7=BC=96=E7=A8=8B=E7=AB=A0=E8=8A=82=E4=B8=AD?= =?UTF-8?q?=E9=83=A8=E5=88=86=E4=B8=8D=E5=87=86=E7=A1=AE=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E8=BF=B0=20(#510)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/13-Functional-Programming.md | 75 +++++++++++++------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index beaf61db..db3dba56 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -202,7 +202,7 @@ from moreLines() - **[4]** 对于多个参数,将参数列表放在括号 `()` 中。 -到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 **return** 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。 +到目前为止,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 **return** 关键字是非法的。 这是 Lambda 表达式简化相应语法的另一种方式。 **[5]** 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 **return**。 @@ -255,7 +255,7 @@ public class RecursiveFactorial { 这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则将无限递归并产生异常。 -我们可以将 `Fibonacci` 序列改为使用递归 Lambda 表达式来实现,这次使用实例变量: +我们可以将 `Fibonacci` 序列用递归的 Lambda 表达式来实现,这次使用实例变量: ```java // functional/RecursiveFibonacci.java @@ -379,9 +379,9 @@ Help! **[9]** 这是 **[6]** 的另一个版本:对已实例化对象的方法的引用,有时称为*绑定方法引用*。 -**[10]** 最后,获取静态内部类的方法引用的操作与 **[8]** 中外部类方式一样。 +**[10]** 最后,获取静态内部类中静态方法的引用与 **[8]** 中通过外部类引用相似。 -上例只是简短的介绍,我们很快就能看到方法引用的全部变化。 +上例只是简短的介绍,我们很快就能看到方法引用的所有不同形式。 ### Runnable接口 @@ -431,7 +431,7 @@ Go::go() ### 未绑定的方法引用 -未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象: +未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用时,我们必须先提供对象: ```java // functional/UnboundMethodReference.java @@ -469,13 +469,13 @@ X::f() ``` -截止目前,我们已经知道了与接口方法同名的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果:即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。 +截止目前,我们看到了与对应接口签名相同的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。 要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,在 Java 中是允许的。我们必须做第二个心理调整——使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。 -**[2]** 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的`transform()`方法,将一个X类的对象传递给它,然后就以某种方式导致了对 `x.f()` 的调用。Java知道它必须拿到第一个参数,该参数实际就是`this`,并在其上调用方法。 +**[2]** 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的`transform()`方法,将一个X类的对象传递给它,最后使得 `x.f()` 以某种方式被调用。Java知道它必须拿到第一个参数,该参数实际就是`this`,然后调用方法作用在它之上。 -如果你的函数式接口中的方法有多个参数,就以第一个参数接受`this`的模式来处理。 +如果你的方法有更多个参数,就以第一个参数接受`this`的模式来处理。 ```java // functional/MultiUnbound.java @@ -514,7 +514,7 @@ public class MultiUnbound { } ``` -为了指明这一点,我将类命名为 **This**,将函数式方法的第一个参数命名为 **athis**,但你在生产代码中应该使用其他名字,以防止混淆。 +需要指出的是,我将类命名为 **This**,并将函数式方法的第一个参数命名为 **athis**,但你在生产级代码中应该使用其他名字,以防止混淆。 ### 构造函数引用 @@ -556,9 +556,9 @@ public class CtorReference { } ``` -**Dog** 有三个构造函数,函数接口内的 `make()` 方法反映了构造函数参数列表( `make()` 方法名称可以不同)。 +**Dog** 有三个构造函数,函数式接口内的 `make()` 方法反映了构造函数参数列表( `make()` 方法名称可以不同)。 -**注意**我们如何对 **[1]**,**[2]** 和 **[3]** 中的每一个使用 `Dog :: new`。 这 3 个构造函数只有一个相同名称:`:: new`,但在每种情况下都赋值给不同的接口。编译器可以检测并知道从哪个构造函数引用。 +**注意**我们如何对 **[1]**,**[2]** 和 **[3]** 中的每一个使用 `Dog :: new`。 这三个构造函数只有一个相同名称:`:: new`,但在每种情况下赋值给不同的接口,编译器可以从中知道具体使用哪个构造函数。 编译器知道调用函数式方法(本例中为 `make()`)就相当于调用构造函数。 @@ -566,7 +566,7 @@ public class CtorReference { ## 函数式接口 -方法引用和 Lambda 表达式必须被赋值,同时编译器需要识别类型信息以确保类型正确。 Lambda 表达式特别引入了新的要求。 代码示例: +方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性。尤其是Lambda 表达式,它引入了新的要求。 代码示例: ```java x -> x.toString() @@ -576,13 +576,13 @@ x -> x.toString() Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 `x` 的类型。 -下面是第 2 个代码示例: +下面是第二个代码示例: ```java (x, y) -> x + y ``` -现在 `x` 和 `y` 可以是任何支持 `+` 运算符连接的数据类型,可以是两个不同的数值类型或者是 1 个 **String** 加任意一种可自动转换为 **String** 的数据类型(这包括了大多数类型)。 但是,当 Lambda 表达式被赋值时,编译器必须确定 `x` 和 `y` 的确切类型以生成正确的代码。 +现在 `x` 和 `y` 可以是任何支持 `+` 运算符连接的数据类型,可以是两个不同的数值类型或者是 一个 **String** 加任意一种可自动转换为 **String** 的数据类型(这包括了大多数类型)。 但是,当 Lambda 表达式被赋值时,编译器必须确定 `x` 和 `y` 的确切类型以生成正确的代码。 该问题也适用于方法引用。 假设你要传递 `System.out :: println` 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型? @@ -632,17 +632,17 @@ public class FunctionalAnnotation { `@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 在 `NotFunctional` 的定义中可看到`@FunctionalInterface` 的作用:接口中如果有多个方法则会产生编译期错误。 -仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会自动包装方法引用或 Lambda 表达式到实现目标接口的类的实例中。 +仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中。 -尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java不允许我们像`fac`定义中的那样,将 `FunctionalAnnotation` 直接赋值给 `Functional`,因为 `FunctionalAnnotation` 没有明确说明实现 `Functional` 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。 +尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java不允许我们像`fac`定义中的那样,将 `FunctionalAnnotation` 直接赋值给 `Functional`,因为 `FunctionalAnnotation` 并没有显式地去实现 `Functional` 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。 -`java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。 +`java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。主要因为基本类型的存在,导致预定义的接口数量有少许增加。 如果你了解命名模式,顾名思义就能知道特定接口的作用。 以下是基本命名准则: 1. 如果只处理对象而非基本类型,名称则为 `Function`,`Consumer`,`Predicate` 等。参数类型通过泛型添加。 -2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但基本 `Supplier` 类型例外。 +2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 `LongConsumer`,`DoubleFunction`,`IntPredicate` 等,但返回基本类型的 `Supplier` 接口例外。 3. 如果返回值为基本类型,则用 `To` 表示,如 `ToLongFunction ` 和 `IntToLongFunction`。 @@ -671,13 +671,13 @@ public class FunctionalAnnotation { |2 参数类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**| -此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出更多行的函数式接口。 +此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出你所需要的函数式接口。 可以看出,在创建 `java.util.function` 时,设计者们做出了一些选择。 -例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我对他们放弃的原因表示同情)。这些选择是疏忽还是有人认为其他组合的使用情况出现得很少(他们是如何得出这个结论的)? +例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我理解他们为什么放弃这些接口)。这到底是疏忽还是有人认为其他组合使用得很少呢(他们是如何得出这个结论的)? -你还可以看到基本类型给 Java 添加了多少复杂性。为了缓和效率问题,该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然受到语言设计选择不佳的影响。 +你还可以看到基本类型给 Java 添加了多少复杂性。基于效率方面的考虑(问题之后有所缓解),该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然会受到语言设计选择不佳的影响。 下面枚举了基于 Lambda 表达式的所有不同 **Function** 变体的示例: @@ -842,11 +842,11 @@ public class ClassFunctionals { 请**注意**,每个方法名称都是随意的(如 `f1()`,`f2()`等)。正如你刚才看到的,一旦将方法引用赋值给函数接口,我们就可以调用与该接口关联的函数方法。 在此示例中为 `get()`、`compare()`、`accept()`、`apply()` 和 `test()`。 - + ### 多参数函数式接口 -`java.util.functional` 中的接口是有限的。比如有了 `BiFunction`,但它不能变化。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例: +`java.util.functional` 中的接口是有限的。比如有 `BiFunction`,但也仅此而已。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例: ```java // functional/TriFunction.java @@ -872,11 +872,11 @@ public class TriFunctionTest { } ``` -这里我们测试了方法引用和 Lambda 表达式。 +这里我们同时测试了方法引用和 Lambda 表达式。 ### 缺少基本类型的函数 -让我们重温一下 `BiConsumer`,看看我们如何创建缺少 **int**,**long** 和 **double** 的各种排列: +让我们重温一下 `BiConsumer`,看看我们如何创建缺少的针对 **int**,**long** 和 **double** 的各种排列: ```java // functional/BiConsumerPermutations.java @@ -908,7 +908,7 @@ public class BiConsumerPermutations { 这里使用 `System.out.format()` 来显示。它类似于 `System.out.println()` 但提供了更多的显示选项。 这里,`%f` 表示我将 `n` 作为浮点值给出,`%d` 表示 `n` 是一个整数值。 这其中可以包含空格,输入 `%n` 会换行 — 当然使用传统的 `\n` 也能换行,但 `%n` 是自动跨平台的,这是使用 `format()` 的另一个原因。 -上例简单使用了包装类型,装箱和拆箱用于在基本类型之间来回转换。 我们也可以使用包装类型,如 `Function`,而不是预定义的基本类型。代码示例: +上例简单使用了包装类型,装箱和拆箱负责它与基本类型之间的来回转换。 又比如,我们可以将包装类型和`Function`一起使用,而不去用各种针对基本类型的预定义接口。代码示例: ```java // functional/FunctionWithWrapped.java @@ -936,7 +936,7 @@ public interface IntToDoubleFunction { 似乎是考虑到使用频率,某些函数类型并没有预定义。 -当然,如果因缺少基本类型而造成的性能问题,你也可以轻松编写自己的接口( 参考 Java 源代码)——尽管这里出现性能瓶颈的可能性不大。 +当然,如果因为缺少针对基本类型的函数式接口造成了性能问题,你可以轻松编写自己的接口( 参考 Java 源代码)——尽管这里出现性能瓶颈的可能性不大。 ## 高阶函数 @@ -1050,7 +1050,7 @@ O **闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。 -考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 +考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决,那问题将变得非常棘手。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 首先,下列方法返回一个函数,该函数访问对象字段和方法参数: @@ -1067,7 +1067,7 @@ public class Closure1 { } ``` -但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,被现存函数以这种方式绑定的对象,垃圾收集器肯定会保留[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间: +但是,仔细考虑一下,`i` 的这种用法并非是个大难题,因为对象很可能在你调用 `makeFun()` 之后就存在了——实际上,垃圾收集器几乎肯定会保留以这种方式被绑定到现存函数的对象[^5]。当然,如果你对同一个对象多次调用 `makeFun()` ,你最终会得到多个函数,它们共享 `i` 的存储空间: ```java // functional/SharedStorage.java @@ -1250,7 +1250,7 @@ public class Closure8 { 请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。 -下面我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是**等同 final 效果**错误消息的触发点。 +下面我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是触发**等同 final 效果**错误消息的原因。 ```java // functional/Closure9.java @@ -1380,12 +1380,13 @@ foobaz 正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。 -从输出结果我们可以看到 `p4` 的工作流程:任何带有 `foo` 的东西都会留下,即使它的长度大于 5。 `fongopuckey` 因长度超出和不包含 `bar` 而被丢弃。 +从输出结果我们可以看到 `p4` 的工作流程:任何带有 `"foo"` 的字符串都得以保留,即使它的长度大于 5。 `"fongopuckey"` 因长度超出且不包含 `bar` 而被丢弃。 + ## 柯里化和部分求值 -[柯里化](https://en.wikipedia.org/wiki/Currying)(Currying)的名称来自于其发明者之一 *Haskell Curry*。他可能是计算机领域唯一名字被命名重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。 +[柯里化](https://en.wikipedia.org/wiki/Currying)(Currying)的名称来自于其发明者之一 *Haskell Curry*。他可能是计算机领域唯一姓氏和名字都命名过重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。 ```java // functional/CurryingAndPartials.java @@ -1459,9 +1460,9 @@ public class Curry3Args { Hi Ho Hup ``` -对于每个级别的箭头级联(Arrow-cascading),你在类型声明中包裹了另一个 **Function**。 +对于每个级别的箭头级联(Arrow-cascading),你都要在类型声明中包裹另一层 **Function**。 -处理基本类型和装箱时,请使用适当的 **Function** 接口: +处理基本类型和装箱时,请使用适当的函数式接口: ```java // functional/CurriedIntAdd.java @@ -1490,9 +1491,9 @@ public class CurriedIntAdd { ## 纯函数式编程 -即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。 +即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,所以编译器对我们犯的错误将无能为力。 -这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些规则) 或 Clojure (需要的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。 +这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要遵循一些规则) 或 Clojure (遵循的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。 ## 本章小结 @@ -1502,7 +1503,7 @@ Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而 这些特性满足了很多羡慕Clojure、Scala 这类更函数化语言的程序员,并且阻止了Java程序员转向那些更函数化的语言(就算不能阻止,起码提供了更好的选择)。 -但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它的使用还是会让人感觉沮丧和鸡肋。 +但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它终究还是会让人感觉沮丧和鸡肋。 当你遇到学习困难时,请记住通过 IDE(NetBeans、IntelliJ Idea 和 Eclipse)获得帮助,因为 IDE 可以智能提示你何时使用 Lambda 表达式或方法引用,甚至有时还能为你优化代码。 From 1076eafc445a74dbfdc42333f5762f29258057a2 Mon Sep 17 00:00:00 2001 From: yanxueer <1164313233@qq.com> Date: Mon, 20 Jul 2020 21:46:26 +0800 Subject: [PATCH 272/371] =?UTF-8?q?=E7=AC=AC10=E7=AB=A0=E6=8E=A5=E5=8F=A3-?= =?UTF-8?q?-=E6=8E=A5=E5=8F=A3=E9=80=82=E9=85=8D=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E5=8F=A5=E7=9A=84=E4=B9=A6=E5=86=99=E9=97=AE=E9=A2=98=20#508?= =?UTF-8?q?=20(#513)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第10章接口/接口适配 将“该接口的实现和传递对象给方法则交由你来做”。修正为:“该接口的实现和传递对象则取决于方法的使用者”。 --- docs/book/10-Interfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 493fb91f..d4b94063 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -1286,7 +1286,7 @@ interfacce I4 extends I1, I3 {} ## 接口适配 -接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象给方法则交由你来做。 +接口最吸引人的原因之一是相同的接口可以有多个实现。在简单情况下体现在一个方法接受接口作为参数,该接口的实现和传递对象则取决于方法的使用者。 因此,接口的一种常见用法是前面提到的*策略*设计模式。编写一个方法执行某些操作并接受一个指定的接口作为参数。可以说:“只要对象遵循接口,就可以调用方法” ,这使得方法更加灵活,通用,并更具可复用性。 From d0bc327d7079f45d1888b197ecaabb971dc983cb Mon Sep 17 00:00:00 2001 From: ChelinTsien Date: Tue, 21 Jul 2020 20:37:11 +0800 Subject: [PATCH 273/371] =?UTF-8?q?bugfix=EF=BC=9A=E7=AC=AC=2015=20?= =?UTF-8?q?=E7=AB=A0=E7=BF=BB=E8=AF=91=E6=9C=89=E7=82=B9=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#515)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 翻译有点问题 * Update 15-Exceptions.md 翻译问题 * Update 15-Exceptions.md 翻译不对意 --- docs/book/15-Exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 82303e94..c6d5f5ab 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -1890,7 +1890,7 @@ Caught: CloseException 从技术上讲,我们并没有被迫在这里提供一个 catch 子句;你可以通过 **main() throws CloseException** 的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。 -请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer 也是如此。 close() 抛出异常。当你想到它时,这就是你想要发生的事情,但是如果你必须自己编写所有这些逻辑,那么你可能会错过一些错误。想象一下所有代码都在那里,程序员没有考虑清理的所有含义,并且做错了。因此,应始终尽可能使用 try-with-resources。它有助于实现该功能,使得生成的代码更清晰,更易于理解。 +请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭 - 即使 Closer.close() 抛出异常也是如此。仔细想想,这就是你想要的结果。但如果你必须亲手编写所有的逻辑,或许会丢失一些东西并使得逻辑出错。想想那些程序员没有考虑 Clean up 的所有影响并且出错的代码。因此,如果可以,你应当始终使用 try-with-resources。这个特性有助于生成更简洁,更易于理解的代码。 From 808647112a7f63512bae1f943746d9aa363dfdd2 Mon Sep 17 00:00:00 2001 From: sampert-git <68496372+sampert-git@users.noreply.github.com> Date: Thu, 23 Jul 2020 20:50:40 +0800 Subject: [PATCH 274/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=8D=95=E8=AF=8D=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF=20(#518)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DymamicFields 改为 DynamicFields . close #517 --- docs/book/15-Exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index c6d5f5ab..dc18c427 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -780,7 +780,7 @@ public class PreciseRethrow { 有趣的是,在 Throwable 的子类中,只有三种基本的异常类提供了带 cause 参数的构造器。它们是 Error(用于 Java 虚拟机报告系统错误)、Exception 以及 RuntimeException。如果要把其他类型的异常链接起来,应该使用 initCause() 方法而不是构造器。 -下面的例子能让你在运行时动态地向 DymamicFields 对象添加字段: +下面的例子能让你在运行时动态地向 DynamicFields 对象添加字段: ```java // exceptions/DynamicFields.java From 086c9b9db3fc08d2d3cff2452ffc8e807977d1e1 Mon Sep 17 00:00:00 2001 From: Sylvester Yao Date: Sun, 26 Jul 2020 18:40:01 +0800 Subject: [PATCH 275/371] Fix some words of Appendix-Low-Level-Concurrency. (#520) --- docs/book/Appendix-Low-Level-Concurrency.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md index d85e110d..8fb91c08 100644 --- a/docs/book/Appendix-Low-Level-Concurrency.md +++ b/docs/book/Appendix-Low-Level-Concurrency.md @@ -637,7 +637,7 @@ No odd numbers discovered ### 可见性 -第二个问题属于 [Java 并发的四句格言](./24-Concurrent-Programming.md#四句格言)里第二句格言 “一切都重要” 的部分。你必须假设每个任务拥有自己的处理器,并且每个处理器都有自己的本地内存缓存。该缓存准许处理器允许的更快,因为处理器并不总是需要从比起使用缓存显著花费更多时间的主内存中获取数据。 +第二个问题属于 [Java 并发的四句格言](./24-Concurrent-Programming.md#四句格言)里第二句格言 “一切都重要” 的部分。你必须假设每个任务拥有自己的处理器,并且每个处理器都有自己的本地内存缓存。该缓存准许处理器运行的更快,因为处理器并不总是需要从比起使用缓存显著花费更多时间的主内存中获取数据。 出现这个问题是因为 Java 尝试尽可能地提高执行效率。缓存的主要目的是避免从主内存中读取数据。当并发时,有时不清楚 Java 什么时候应该将值从主内存刷新到本地缓存 — 而这个问题称为 *缓存一致性* ( *cache coherence* )。 @@ -649,7 +649,7 @@ No odd numbers discovered 2. 这些访问中至少有一个是写操作。 3. 你尝试避免同步 (在现代 Java 中,你可以使用高级工具来避免进行同步)。 -举个例字,如果你使用变量作为停止任务的标志值。那么该变量至少必须声明为 **volatile** (尽管这并不一定能保证这种标志的线程安全)。否则,当一个任务更改标志值时,这些更改可以存储在本地处理器缓存中,而不会刷新到主内存。当另一个任务查看标记值时,它不会看到更改。我更喜欢在 [并发编程](./24-Concurrent-Programming.md) 中 [终止耗时任务](./24-Concurrent-Programming.md#终止耗时任务) 章节中使用 **AtomicBoolean** 类型作为标志值的办法 +举个例子,如果你使用变量作为停止任务的标志值。那么该变量至少必须声明为 **volatile** (尽管这并不一定能保证这种标志的线程安全)。否则,当一个任务更改标志值时,这些更改可以存储在本地处理器缓存中,而不会刷新到主内存。当另一个任务查看标记值时,它不会看到更改。我更喜欢在 [并发编程](./24-Concurrent-Programming.md) 中 [终止耗时任务](./24-Concurrent-Programming.md#终止耗时任务) 章节中使用 **AtomicBoolean** 类型作为标志值的办法 任务对其自身变量所做的任何写操作都始终对该任务可见,因此,如果只在任务中使用变量,你不需要使其变量声明为 **volatile** 。 From 98678885a278f6a597d40adcd5bceb17201a9aef Mon Sep 17 00:00:00 2001 From: legendyql Date: Thu, 30 Jul 2020 11:18:51 +0800 Subject: [PATCH 276/371] =?UTF-8?q?=E7=AC=AC14=E7=AB=A0=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96=20(#524)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 第14章翻译优化 * Update 14-Streams.md --- docs/book/14-Streams.md | 77 +++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 7b3e5c30..8550e8b6 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -7,7 +7,7 @@ 流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。 -利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。 +使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。 在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。 @@ -40,11 +40,11 @@ public class Randoms { 19 ``` -首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了数值产生的边界。这将生成一个整数流。我们可以使用中间流操作(intermediate stream operation) `distinct()` 来获取它们的非重复值,然后使用 `limit()` 方法获取前 7 个元素。接下来,我们使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对每个流对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用。`System.out::println` 。 +首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了产生的数值的边界。这将生成一个随机整数流。我们用中间流操作(intermediate stream operation) `distinct()` 使流中的整数不重复,然后使用 `limit()` 方法获取前 7 个元素。接下来使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对流中的每个对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用:`System.out::println` 。 注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。 -声明式编程(Declarative programming)是一种:声明要做什么,而非怎么做的编程风格。正如我们在函数式编程中所看到的。**注意**,命令式编程的形式更难以理解。代码示例: +声明式编程(Declarative programming)是一种编程风格,它声明想要做什么,而非指明如何做。正如我们在函数式编程中所看到的。你会发现命令式编程的形式更难以理解。代码示例: ```java // streams/ImperativeRandoms.java @@ -71,9 +71,9 @@ public class ImperativeRandoms { 在 `Randoms.java` 中,我们无需定义任何变量,但在这里我们定义了 3 个变量: `rand`,`rints` 和 `r`。由于 `nextInt()` 方法没有下限的原因(其内置的下限永远为 0),这段代码实现起来更复杂。所以我们要生成额外的值来过滤小于 5 的结果。 -**注意**,你必须要研究程序的真正意图,而在 `Randoms.java` 中,代码只是告诉了你它正在做什么。这种语义清晰性也是 Java 8 的流式编程更受推崇的重要原因。 +注意,你必须用力的研究才能弄明白`ImperativeRandoms.java`程序在干什么。而在 `Randoms.java` 中,代码直接告诉了你它正在做什么。这种语义的清晰性是使用Java 8 流式编程的重要原因之一。 -在 `ImperativeRandoms.java` 中显式地编写迭代机制称为外部迭代。而在 `Randoms.java` 中,流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。 +像在 `ImperativeRandoms.java` 中那样显式地编写迭代过程的方式称为外部迭代。而在 `Randoms.java` 中,你看不到任何上述的迭代过程,所以它被称为内部迭代,这是流式编程的一个核心特征。内部迭代产生的代码可读性更强,而且能更简单的使用多核处理器。通过放弃对迭代过程的控制,可以把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。 另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。 @@ -85,7 +85,7 @@ Java 设计者面临着这样一个难题:现存的大量类库不仅为 Java 比如在 **Random** 中添加更多的方法。只要不改变原有的方法,现有代码就不会受到干扰。 -问题是,接口部分怎么改造呢?特别是涉及集合类接口的部分。如果你想把一个集合转换为流,直接向接口添加新方法会破坏所有老的接口实现类。 +一个大的挑战来自于使用接口的库。集合类是其中关键的一部分,因为你想把集合转为流。但是如果你将一个新方法添加到接口,那就破坏了每一个实现接口的类,因为这些类都没有实现你添加的新方法。 Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `default`(`默认`)修饰的方法。通过这种方案,设计者们可以将流式(*stream*)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。 @@ -321,13 +321,11 @@ public class RandomWords implements Supplier { it shop sir the much cheese by conclusion district is ``` -在这里你可以看到更为复杂的 `split()` 运用。在构造器中,每一行都被 `split()` 通过空格或者被方括号包裹的任意标点符号进行分割。在结束方括号后面的 `+` 代表 `+` 前面的东西可以出现一次或者多次。 +在这里可以看到 `split()` 更复杂的运用。在构造器里,每一行都被 `split()` 通过方括号内的空格或其它标点符号分割。在方括号后面的 `+` 表示 `+` 前面的东西可以出现一次或者多次。 -我们注意到在构造函数中循环体使用命令式编程(外部迭代)。在以后的例子中,你甚至会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。 +你会发现构造函数使用命令式编程(外部迭代)进行循环。在以后的例子中,你会看到我们是如何去除命令式编程的使用。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。 -在 `toString()` 和主方法中你看到了 `collect()` 收集操作,它根据参数来组合所有流中的元素。 - -当你使用 **Collectors.**`joining()`,你将会得到一个 `String` 类型的结果,每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于产生不同的结果。 +在`toString()` 和`main()`方法中你看到了 `collect()` 操作,它根据参数来结合所有的流元素。当你用 `Collectors.joining()`作为 `collect()` 的参数时,将得到一个`String` 类型的结果,该结果是流中的所有元素被`joining()`的参数隔开。还有很多不同的 `Collectors` 用于产生不同的结果。 在主方法中,我们提前看到了 **Stream.**`generate()` 的用法,它可以把任意 `Supplier` 用于生成 `T` 类型的流。 @@ -519,7 +517,7 @@ Bubble(4) ### iterate() -**Stream.**`iterate()` 以种子(第一个参数)开头,并将其传给方法(第二个参数)。方法的结果将添加到流,并存储作为第一个参数用于下次调用 `iterate()`,依次类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例: +`Stream.iterate()` 产生的流的第一个元素是种子(iterate方法的第一个参数),然后将种子传递给方法(iterate方法的第二个参数)。方法运行的结果被添加到流(作为流的第二个元素),并存储起来作为下次调用 `iterate()`时的第一个参数,以此类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例: ```java // streams/Fibonacci.java @@ -565,7 +563,7 @@ public class Fibonacci { ### 流的建造者模式 -在建造者设计模式(也称构造器模式)中,首先创建一个 `builder` 对象,传递给它多个构造器信息,最后执行“构造”。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例: +在建造者模式(Builder design pattern)中,首先创建一个 `builder` 对象,然后将创建流所需的多个信息传递给它,最后`builder` 对象执行”创建“流的操作。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例: ```java // streams/FileToWordsBuilder.java @@ -683,7 +681,7 @@ public class ArrayStreams { Java 的正则表达式将在[字符串](18-Strings.md)这一章节详细介绍。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制,输入只能是 **CharSequence**,因此不能将流作为 `splitAsStream()` 的参数。 -我们再一次查看将文件处理为单词流的过程。这一次,我们使用流将文件分割为单独的字符串,接着使用正则表达式将字符串转化为单词流。 +我们再一次查看将文件转换为单词的过程。这一次,我们使用流将文件转换为一个字符串,接着使用正则表达式将字符串转化为单词流。 ```java // streams/FileToWordsRegexp.java @@ -726,7 +724,7 @@ Not much of a cheese shop really is it 在构造器中我们读取了文件中的所有内容(跳过第一行注释,并将其转化成为单行字符串)。现在,当你调用 `stream()` 的时候,可以像往常一样获取一个流,但这次你可以多次调用 `stream()` 在已存储的字符串中创建一个新的流。这里有个限制,整个文件必须存储在内存中;在大多数情况下这并不是什么问题,但是这损失了流操作非常重要的优势: -1. 流“不需要存储”。当然它们需要一些内部存储,但是这只是序列的一小部分,和持有整个序列并不相同。 +1. “不需要把流存储起来。”当然,流确实需要一些内部存储,但存储的只是序列的一小部分,和存储整个序列不同。 2. 它们是懒加载计算的。 幸运的是,我们稍后就会知道如何解决这个问题。 @@ -799,7 +797,7 @@ you what to the that sir leads in district And * `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。 -* `filter(Predicate)`:过滤操作会保留与传递进去的过滤器函数计算结果为 `true` 元素。 +* `filter(Predicate)`:若元素传递给过滤函数产生的结果为`true` ,则过滤操作保留这些元素。 在下例中,`isPrime()` 作为过滤器函数,用于检测质数。 @@ -907,7 +905,7 @@ class FunctionMap { 5 ``` -在上面的自增示例中,我们使用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出 **NumberFormatException** 异常,我们只须回过头来将原始字符串放回到输出流中。 +在上面的自增示例中,我们用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能被转化成为整数就会抛出 `NumberFormatException` 异常,此时我们就回过头来把原始字符串放到输出流中。 在以上例子中,`map()` 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。下面代码示例: @@ -1097,7 +1095,7 @@ public class FileToWords { `stream()` 现在是一个静态方法,因为它可以自己完成整个流创建过程。 -**注意**:`\\W+` 是一个正则表达式。他表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\w` 表示“单词字符”。 +注意:`\\W+` 是一个正则表达式。表示“非单词字符”,`+` 表示“可以出现一次或者多次”。小写形式的 `\\w` 表示“单词字符”。 我们之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流: @@ -1105,7 +1103,7 @@ public class FileToWords { .flatMap(line -> Arrays.stream(line.split("\\W+")))) ``` -有了真正的、而非 `FileToWordsRegexp.java` 中基于集合存储的流,我们每次使用都必须从头创建,因为流并不能被复用: +因为有了真正的流(而不是`FileToWordsRegexp.java` 中基于集合存储的流),所以每次需要一个新的流时,我们都必须从头开始创建,因为流不能被复用: ```java // streams/FileToWordsTest.java @@ -1359,7 +1357,7 @@ Null 当我们的流管道生成了 **Optional** 对象,下面 3 个方法可使得 **Optional** 的后续能做更多的操作: -- `filter(Predicate)`:将 **Predicate** 应用于 **Optional** 中的内容并返回结果。当 **Optional** 不满足 **Predicate** 时返回空。如果 **Optional** 为空,则直接返回。 +- `filter(Predicate)`:对 **Optional** 中的内容应用**Predicate** 并将结果返回。如果 **Optional** 不满足 **Predicate** ,将 **Optional** 转化为空 **Optional** 。如果 **Optional** 已经为空,则直接返回空**Optional** 。 - `map(Function)`:如果 **Optional** 不为空,应用 **Function** 于 **Optional** 中的内容,并返回结果。否则直接返回 **Optional.empty**。 @@ -1441,11 +1439,11 @@ Optional[Bingo] Optional.empty ``` -即使输出看起来像流,特别是 `test()` 中的 for 循环。每一次的 for 循环时重新启动流,然后根据 for 循环的索引跳过指定个数的元素,这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素,结果会包装在 **Optional** 中。 +即使输出看起来像流,要特别注意 `test()` 中的 for 循环。每一次的for循环都重新启动流,然后跳过for循环索引指定的数量的元素,这就是流只剩后续元素的原因。然后调用`findFirst()` 获取剩余元素中的第一个元素,并包装在一个 `Optional`对象中。 **注意**,不同于普通 for 循环,这里的索引值范围并不是 `i < elements.length`, 而是 `i <= elements.length`。所以最后一个元素实际上超出了流。方便的是,这将自动成为 **Optional.empty**,你可以在每一个测试的结尾中看到。 -同 `map()` 一样 , `Optional.map()` 应用于函数。它仅在 **Optional** 不为空时才应用映射函数,并将 **Optional** 的内容提取到映射函数。代码示例: +同 `map()` 一样 , `Optional.map()` 执行一个函数。它仅在 **Optional** 不为空时才执行这个映射函数。并将 **Optional** 的内容提取出来,传递给映射函数。代码示例: ```java // streams/OptionalMap.java @@ -1751,7 +1749,7 @@ public class ForEach { 258 555 693 861 961 429 868 200 522 207 288 128 551 589 ``` -为了方便测试不同大小的数组,我们抽离出了 `SZ` 变量。结果很有趣:在第一个流中,未使用 `parallel()` ,所以 `rands()` 按照元素迭代出现的顺序显示结果;在第二个流中,引入`parallel()` ,即便流很小,输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试,结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。 +为了方便测试不同大小的流,我们抽离出了 `SZ` 变量。然而即使 `SZ` 值为14也产生了有趣的结果。在第一个流中,未使用 `parallel()` ,因此以元素从 `rands()`出来的顺序输出结果。在第二个流中,引入`parallel()` ,即便流很小,输出的结果的顺序也和前面不一样。这是由于多处理器并行操作的原因,如果你将程序多运行几次,你会发现输出都不相同,这是多处理器并行操作的不确定性造成的结果。 在最后一个流中,同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此,对非并行流使用 `forEachOrdered()` 是没有任何影响的。 @@ -1760,7 +1758,7 @@ public class ForEach { ### 集合 - `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。 -- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。 +- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新的结果集合,第二个参数 **BiConsumer** 将下一个元素收集到结果集合中,第三个参数 **BiConsumer** 用于将两个结果集合合并起来。 在这里我们只是简单介绍了几个 **Collectors** 的运用示例。实际上,它还有一些非常复杂的操作实现,可通过查看 `java.util.stream.Collectors` 的 API 文档了解。例如,我们可以将元素收集到任意一种特定的集合中。 @@ -1799,12 +1797,7 @@ stream, streams, throws, toCollection, trim, util, void, words2] ``` -**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果展平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,紧接着只获取100个单词,最后将其保存到 **TreeSet** 中。 - - +**Files.**`lines()` 打开 **Path** 并将其转换成为由行组成的流。下一行代码以一个或多个非单词字符(`\\W+`)为分界,对每一行进行分割,结果是产生一个数组,然后使用 **Arrays.**`stream()` 将数组转化成为流,最后`flatMap()`将各行形成的多个单词流,扁平映射为一个单词流。使用 `matches(\\d+)` 查找并移除全部是数字的字符串(注意,`words2` 是通过的)。然后用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词,并只获取前100个单词,最后将其保存到 **TreeSet** 中。 我们也可以在流中生成 **Map**。代码示例: @@ -1855,9 +1848,9 @@ public class MapCollector { {688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N} ``` -**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以这里创建了一个整数流,并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始,然后 `iterator()` 允许我们在 `stream()` 中使用它。就我所知,这是组合多个流以生成新的对象流的唯一方法。 +**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中,我们不能直接以某种方式组合两个流。所以我创建了一个整数流,并且使用 `mapToObj()` 将整数流转化成为 **Pair** 流。 **capChars**的随机大写字母迭代器创建了流,然后`next()`让我们可以在`stream()`中使用这个流。就我所知,这是将多个流组合成新的对象流的唯一方法。 -在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。 +在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法只需要两个从流中获取键和值的函数。还有其他重载形式,其中一种当是键发生冲突时,使用一个函数来处理冲突。 大多数情况下,`java.util.stream.Collectors` 中预设的 **Collector** 就能满足我们的要求。除此之外,你还可以使用第二种形式的 `collect()`。 我把它留作更高级的练习,下例给出基本用法: @@ -1890,7 +1883,7 @@ cheese cheese ``` -在这里, **ArrayList** 的方法已经执行了你所需要的操作,但是似乎更有可能的是,如果你必须使用这种形式的 `collect()`,则必须自己创建特殊的定义。 +在这里, **ArrayList** 的方法已经做了你所需要的操作,但更有可能的是,如果你必须使用这种形式的 `collect()`,就要自己创建特定的定义。 @@ -1947,18 +1940,18 @@ Frobnitz(7) Frobnitz(29) ``` -**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。 +**Frobnitz** 包含一个可生成自身的生成器 `supply()` ;因为 `supply()` 方法作为一个 `Supplier` 是签名兼容的,我们可以把 `supply()` 作为一个方法引用传递给 `Stream.generate()` (这种签名兼容性被称作结构一致性)。我们使用了没有“初始值”作为第一个参数的 `reduce()`方法,所以产生的结果是 **Optional** 类型。`Optional.ifPresent()` 方法只有在结果非空的时候才会调用 `Consumer` (`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。 -Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。 +Lambda 表达式中的第一个参数 `fr0` 是 `reduce()` 中上一次调用的结果。而第二个参数 `fr1` 是从流传递过来的值。 -`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。当取得第一个长度小于 50 的 `Frobnitz`,只要得到结果就会忽略其他。这是个非常奇怪的约束, 也确实让我们对 `reduce()` 有了更多的了解。 +`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当 `fr0` 的 `size` 值小于 50 的时候,将 `fr0` 作为结果,否则将序列中的下一个元素即 `fr1`作为结果。当取得第一个 `size` 值小于 50 的 `Frobnitz`,只要得到这个结果就会忽略流中其他元素。这是个非常奇怪的限制, 但也确实让我们对 `reduce()` 有了更多的了解。 ### 匹配 -- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。 -- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 true 时,则停止执行计算。 -- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时,停止执行计算。 +- `allMatch(Predicate)` :如果流的每个元素提供给 **Predicate** 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。 +- `anyMatch(Predicate)`:如果流的任意一个元素提供给 **Predicate** 返回 true ,结果返回为 true。在第一个 false 是停止执行计算。 +- `noneMatch(Predicate)`:如果流的每个元素提供给 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。 我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: @@ -2002,9 +1995,9 @@ public class Matching { 1 2 3 4 5 6 7 8 9 true ``` -**BiPredicate** 是一个二元谓词,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。 +**BiPredicate** 是一个二元谓词,它接受两个参数并返回 true 或者 false。第一个参数是我们要测试的流,第二个参数是一个谓词 **Predicate**。**Matcher** 可以匹配所有的 **Stream::\*Match** 方法,所以可以将每一个**Stream::\*Match**方法引用传递到 `show()` 中。对`match.test()` 的调用会被转换成 对方法引用**Stream::\*Match** 的调用。 -`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。 +`show()` 接受一个**Matcher**和一个 `val` 参数,`val` 在判断测试 `n < val`中指定了最大值。`show()` 方法生成了整数1-9组成的一个流。`peek()`用来展示在测试短路之前测试进行到了哪一步。从输出中可以看到每次都发生了短路。 ### 查找 @@ -2039,7 +2032,7 @@ public class SelectElement { 242 ``` -`findFirst()` 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。 +无论流是否为并行化,`findFirst()` 总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,用 `parallel()` 将流并行化,以展示 `findAny()` 不选择流的第一个元素的可能性。 如果必须选择流中最后一个元素,那就使用 `reduce()`。代码示例: From 988e61e6f0e2896337f5cbd52636485b5c4106bd Mon Sep 17 00:00:00 2001 From: nijixucai <825985100@qq.com> Date: Thu, 30 Jul 2020 11:44:14 +0800 Subject: [PATCH 277/371] =?UTF-8?q?Fix=20issue=20#523:=20=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E5=8D=95=E8=AF=8D=E6=8B=BC=E5=86=99=20(#525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: guozhe --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index d4076f05..bb91ba91 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -759,7 +759,7 @@ ESPRESSO ## 使用 EnumSet 替代 Flags -Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enumi 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。 +Set 是一种集合,只能向其中添加不重复的对象。当然,enum 也要求其成员都是唯一的,所以 enum 看起来也具有集合的行为。不过,由于不能从 enum 中删除或添加元素,所以它只能算是不太有用的集合。Java SE5 引入 EnumSet,是为了通过 enum 创建一种替代品,以替代传统的基于 int 的“位标志”。这种标志可以用来表示某种“开/关”信息,不过,使用这种标志,我们最终操作的只是一些 bit,而不是这些 bit 想要表达的概念,因此很容易写出令人难以理解的代码。 EnumSet 的设计充分考虑到了速度因素,因为它必须与非常高效的 bit 标志相竞争(其操作与 HashSet 相比,非常地快),就其内部而言,它(可能)就是将一个 long 值作为比特向量,所以 EnumSet 非常快速高效。使用 EnumSet 的优点是,它在说明一个二进制位是否存在时,具有更好的表达能力,并且无需担心性能。 From 0dc4d03dd070a59890a8a59172f9a634c657d667 Mon Sep 17 00:00:00 2001 From: legendyql Date: Thu, 30 Jul 2020 12:43:07 +0800 Subject: [PATCH 278/371] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=AC14=E7=AB=A0?= =?UTF-8?q?=E5=A4=9A=E5=87=BA=E7=9A=84=E4=B8=80=E8=A1=8C=20(#526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 8550e8b6..57d93071 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1854,10 +1854,6 @@ public class MapCollector { 大多数情况下,`java.util.stream.Collectors` 中预设的 **Collector** 就能满足我们的要求。除此之外,你还可以使用第二种形式的 `collect()`。 我把它留作更高级的练习,下例给出基本用法: - - ```java // streams/SpecialCollector.java import java.util.*; From df9b07d2c5b6261094c1141cb509e74fce96e8d6 Mon Sep 17 00:00:00 2001 From: zhenyiLiang <2665235383lzy@gmail.com> Date: Mon, 3 Aug 2020 10:18:14 +0800 Subject: [PATCH 279/371] Update 06-Housekeeping.md (#527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改语句不通顺的地方 --- docs/book/06-Housekeeping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 7cef1630..f0b805d1 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -95,7 +95,7 @@ Tree t = new Tree(12); // 12-foot 树 大多数编程语言(尤其是 C 语言)要求为每个方法(在这些语言中经常称为函数)提供一个独一无二的标识符。所以,你不能有一个 `print()` 函数既能打印整型,也能打印浮点型——每个函数名都必须不同。 -在 Java (C++) 中,还有一个因素也促使了必须使用方法重载:构造器。因为构造器方法名肯定是与类名相同,所以一个类中只会有一个构造器名。那么你怎么通过不同的方式创建一个对象呢?例如,你想创建一个类,这个类的初始化方式有两种:一种是标准化方式,另一种是从文件中读取信息的方式。你需要两个构造器:无参构造器和有一个 **String** 类型参数的构造器,该参数传入文件名。两个构造器具有相同的名字——与类名相同。因此,方法重载是必要的,它允许方法具有相同的方法名但接收的参数不同。尽管方法重载对于构造器是重要的,但是也可以对任何方法很方便地进行重载。 +在 Java (C++) 中,还有一个因素也促使了必须使用方法重载:构造器。因为构造器方法名肯定是与类名相同,所以一个类中只会有一个构造器名。那么你怎么通过不同的方式创建一个对象呢?例如,你想创建一个类,这个类的初始化方式有两种:一种是标准化方式,另一种是从文件中读取信息的方式。你需要两个构造器:无参构造器和有一个 **String** 类型参数的构造器,该参数传入文件名。两个构造器具有相同的名字——与类名相同。因此,方法重载是必要的,它允许方法具有相同的方法名但接收的参数不同。尽管方法重载对于构造器是重要的,但是也可以很方便地对其他任何方法进行重载。 下例展示了如何重载构造器和方法: @@ -440,7 +440,7 @@ Banana.peel(a, 1) Banana.peel(b, 2) ``` -这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,但能说明到底发生了什么。假设现在在方法内部,你想获得对当前对象的引用。但是,对象引用是被秘密地传达给编译器——并不在参数列表中。方便的是,有一个关键字: **this** 。**this** 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,**this** 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用其他该类中的方法,不要使用 **this**,直接调用即可,**this** 自动地应用于其他方法上了。因此你可以像这样: +这是在内部实现的,你不可以直接这么编写代码,编译器不会接受,但能说明到底发生了什么。假设现在在方法内部,你想获得对当前对象的引用。但是,对象引用是被秘密地传达给编译器——并不在参数列表中。方便的是,有一个关键字: **this** 。**this** 关键字只能在非静态方法内部使用。当你调用一个对象的方法时,**this** 生成了一个对象引用。你可以像对待其他引用一样对待这个引用。如果你在一个类的方法里调用该类的其他方法,不要使用 **this**,直接调用即可,**this** 自动地应用于其他方法上了。因此你可以像这样: ```java // housekeeping/Apricot.java From f950a6d4b42fba49baa453292f5db1e4f2ee2e46 Mon Sep 17 00:00:00 2001 From: flushmeteor Date: Tue, 4 Aug 2020 20:05:41 +0800 Subject: [PATCH 280/371] correct a word (#530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 输出流是System.out,应该是手误写错了 --- docs/book/Appendix-Standard-IO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Standard-IO.md b/docs/book/Appendix-Standard-IO.md index 0c61b233..bfc6d597 100644 --- a/docs/book/Appendix-Standard-IO.md +++ b/docs/book/Appendix-Standard-IO.md @@ -9,7 +9,7 @@ ## 从标准输入中读取 -遵循标准 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` 之前必须先对其进行包装。 +遵循标准 I/O 模型,Java 提供了标准输入流 `System.in`、标准输出流 `System.out` 和标准错误流 `System.err`。在本书中,你已经了解到如何使用 `System.out`将数据写到标准输出。 `System.out` 已经预先包装[^1]成了 `PrintStream` 对象。标准错误流 `System.err` 也预先包装为 `PrintStream` 对象,但是标准输入流 `System.in` 是原生的没有经过包装的 `InputStream`。这意味着尽管可以直接使用标准输出流 `System.out` 和标准错误流 `System.err`,但是在读取 `System.in` 之前必须先对其进行包装。 我们通常一次一行地读取输入。为了实现这个功能,将 `System.in` 包装成 `BufferedReader` 来使用,这要求我们用 `InputStreamReader` 把 `System.in` 转换[^2]成 `Reader` 。下面这个例子将键入的每一行显示出来: @@ -197,4 +197,4 @@ public class OSExecuteDemo { -
\ No newline at end of file +
From c9abc5c07ef502f6b0b83926993918a15febd576 Mon Sep 17 00:00:00 2001 From: gaodi16366 <44352537+gaodi16366@users.noreply.github.com> Date: Wed, 5 Aug 2020 11:40:31 +0900 Subject: [PATCH 281/371] =?UTF-8?q?=E7=AC=AC=E5=8D=81=E5=85=AB=E7=AB=A0&?= =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E5=8D=81=E7=AB=A0=20=E7=B1=BB=E5=90=8D?= =?UTF-8?q?=E9=94=99=E8=AF=AF=20(#531)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 第十八章 字符串 WitherStringBuilder -> WhitherStringBuilder * 第二十章 泛型 ComparableSet -> ComparablePet --- docs/book/18-Strings.md | 2 +- docs/book/20-Generics.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 90113eb3..fe902797 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -125,7 +125,7 @@ public class WhitherStringBuilder { } } ``` -现在运行 `javap -c WitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是 `implicit()` 方法: +现在运行 `javap -c WhitherStringBuilder`,可以看到两种不同方法(我已经去掉不相关的细节)对应的字节码。首先是 `implicit()` 方法: ```x86asm public java.lang.String implicit(java.lang.String[]); 0: ldc #2 // String diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 6f244bfa..00d25272 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3656,7 +3656,7 @@ class Gecko extends ComparablePet { } ``` -**Hamster** 显示了重新实现 **ComparableSet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。 +**Hamster** 显示了重新实现 **ComparablePet** 中相同的接口是可能的,只要接口完全相同,包括参数类型。然而正如 **Gecko** 中所示,这与直接覆写基类的方法完全相同。 From 5e6c267921bdc0afb5af906c26317c18f29ddcc1 Mon Sep 17 00:00:00 2001 From: AlanMeng Date: Thu, 6 Aug 2020 10:47:54 +0800 Subject: [PATCH 282/371] =?UTF-8?q?=E5=85=B3=E4=BA=8EanyMatch=E7=9A=84?= =?UTF-8?q?=E7=9F=AD=E8=B7=AF=E6=93=8D=E4=BD=9C=EF=BC=8C=E5=BA=94=E8=AF=A5?= =?UTF-8?q?=E5=8F=91=E7=94=9F=E5=9C=A8=E6=98=AF=E7=AC=AC=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=20true=EF=BC=8C=E8=80=8C=E4=B8=8D=E6=98=AF=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=20false=E3=80=82=20(#533)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 57d93071..f5611733 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1946,7 +1946,7 @@ Lambda 表达式中的第一个参数 `fr0` 是 `reduce()` 中上一次调用的 ### 匹配 - `allMatch(Predicate)` :如果流的每个元素提供给 **Predicate** 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。 -- `anyMatch(Predicate)`:如果流的任意一个元素提供给 **Predicate** 返回 true ,结果返回为 true。在第一个 false 是停止执行计算。 +- `anyMatch(Predicate)`:如果流的任意一个元素提供给 **Predicate** 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。 - `noneMatch(Predicate)`:如果流的每个元素提供给 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。 我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作,然后再将其转换为 **Matcher** 接口。代码示例: From ec563db627fe5855faa29629cf1f28ec15bff44f Mon Sep 17 00:00:00 2001 From: Decandy-star <59561865+Decandy-star@users.noreply.github.com> Date: Fri, 7 Aug 2020 10:08:03 +0800 Subject: [PATCH 283/371] =?UTF-8?q?issue535=20=E4=BF=AE=E6=94=B913?= =?UTF-8?q?=E7=AB=A0=E5=8D=95=E8=AF=8D=E9=94=99=E8=AF=AF=20(#536)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: huhaowen --- docs/book/13-Functional-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index db3dba56..01738eb1 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1380,7 +1380,7 @@ foobaz 正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。 -从输出结果我们可以看到 `p4` 的工作流程:任何带有 `"foo"` 的字符串都得以保留,即使它的长度大于 5。 `"fongopuckey"` 因长度超出且不包含 `bar` 而被丢弃。 +从输出结果我们可以看到 `p4` 的工作流程:任何带有 `"foo"` 的字符串都得以保留,即使它的长度大于 5。 `"fongopuckey"` 因长度超出且不包含 `foo` 而被丢弃。 From e6d9c4142fec0c47b92688ccab1ed0e3f6b375a9 Mon Sep 17 00:00:00 2001 From: Jianing Liang <2603054678@qq.com> Date: Fri, 7 Aug 2020 12:23:20 +0800 Subject: [PATCH 284/371] =?UTF-8?q?24=E7=AB=A0=E7=BF=BB=E8=AF=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20(#537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 24章翻译优化 * 修改并发部分的语序 --- docs/book/24-Concurrent-Programming.md | 28 ++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 18a07565..0a85cee3 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -53,27 +53,24 @@ slowdown occurs): 同时在多个位置完成多任务。这解决了所谓的 CPU 密集型问题:将程序分为多部分,在多个处理器上同时处理不同部分来加快程序执行效率。 -上面的定义告诉了我们术语令人困惑的原因:两者的核心是“同时完成多个任务”。并行增加了跨多个处理器的分布。更重要的是,这两种方法可以解决不同类型的问题:解决I / O绑定问题和并行化可能对您没有任何好处,因为该问题不是整体速度,而是阻塞。采取计算约束问题并尝试在单个处理器上使用并发性解决问题可能会浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式却有所不同,并且取决于问题所施加的约束。 +上面的定义说明了这两个术语令人困惑的原因:两者的核心都是“同时完成多个任务”,不过并行增加了跨多个处理器的分布。更重要的是,它们可以解决不同类型的问题:并行可能对解决I / O密集型问题没有任何好处,因为问题不在于程序的整体执行速度,而在于I/O阻塞。而尝试在单个处理器上使用并发来解决计算密集型问题也可能是浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式有所不同,这取决于问题施加的约束。 +这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制 - **线程**来实现并发和并行。 -术语混淆的原因在上面的定义中显示:其中核心是“在同一时间完成多个任务。”并行性通过多个处理器增加分布。更重要的是,两者解决了不同类型的问题:解决I/O密集型问题,并行化可能对你没有任何好处,因为问题不是整体速度,而是阻塞。并且考虑到计算力限制问题并试图在单个处理器上使用并发来解决它可能会浪费时间。两种方法都试图在更短的时间内完成更多,但它们实现加速的方式是不同的,并且取决于问题所带来的约束。 +我们甚至可以尝试以更细的粒度去进行定义(然而这并不是标准化的术语): -这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制**线程**来实现并发和并行。 - -我们甚至可以尝试添加细致的粒度去定义(但是,这不是标准化的术语): - -- **纯并发**:任务仍然在单个CPU上运行。纯并发系统产生的结果比顺序系统更快,但如果有更多的处理器,则运行速度不会更快 -- **并发-并行**:使用并发技术,结果程序利用更多处理器并更快地生成结果 +- **纯并发**:仍然在单个CPU上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 +- **并发-并行**:使用并发技术,结果程序可以利用更多处理器更快地产生结果。 - **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams**就是一个很好的例子)。 - **纯并行**:除非有多个处理器,否则不会运行。 在某些情况下,这可能是一个有用的分类法。 -对并发性的语言和库支持似乎[Leaky Abstraction](https://en.wikipedia.org/wiki/Leaky_abstraction)是完美候选者。抽象的目标是“抽象出”那些对于手头想法不重要的东西,从不必要的细节中汲取灵感。如果抽象是漏洞,那些碎片和细节会不断重新声明自己是重要的,无论你试图隐藏它们多少 +支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction)一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 -我开始怀疑是否真的有高度抽象。当编写这些类型的程序时,你永远不会被底层系统和工具屏蔽,甚至关于CPU缓存如何工作的细节。最后,如果你非常小心,你创作的东西在特定的情况下起作用,但它在其他情况下不起作用。有时,区别在于两台机器的配置方式,或者程序的估计负载。这不是Java特有的-它是并发和并行编程的本质。 +我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于CPU缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是Java特有的 - 这是并发和并行编程的本质。 -你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果它没有正确调整并且输入速率要么没有被正确估计或被限制(并且限制意味着,在不同情况下不同的东西具有不同的影响),该队列将填满并阻塞或溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式 +你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 ### 并发的新定义 @@ -81,14 +78,15 @@ slowdown occurs): 几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它: >**并发性是一系列性能技术,专注于减少等待** -这实际上是一个相当多的声明,所以我将其分解: +这实际上是一个相当复杂的表述,所以我将其分解: -- 这是一个技术类集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差别很大 +- 这是一个集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差异很大。 - 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 -- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待某个地方时产生结果。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且任何其他任务都没有等待,那么尝试提高吞吐量是没有意义的。并发的唯一形式是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 +- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 -值得强调的是,这个定义的有效性取决于等待这个词。如果没有什么可以等待,那就没有机会了。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 +值得强调的是,这个定义的有效性取决于“等待”这个词。如果没有什么可以等待,那就没有机会去加速。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 + ## 并发的超能力 想象一下,你置身于一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并沿着走廊向下移动。走廊分开了。 From 7ec2d2d775b5a7cbf27d2b18f98b7ac1afd81682 Mon Sep 17 00:00:00 2001 From: AlanMeng Date: Tue, 11 Aug 2020 10:53:34 +0800 Subject: [PATCH 285/371] Update 16-Validating-Your-Code.md (#539) --- docs/book/16-Validating-Your-Code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 527eef2e..a0a177d8 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -561,7 +561,7 @@ assert invariant(); 注意,**precondition()** 返回 **void** , 因为它不与断言一起使用。按照之前所说的,通常你会在代码中保留前置条件。通过将它们封装在 **precondition()** 方法调用中,如果你不得不做出关掉它们的可怕举动,你会有更好的选择。 -**postcondition()** 和 **constant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。 +**postcondition()** 和 **invariant()** 都返回一个布尔值,因此可以在 **assert** 语句中使用它们。此外,如果出于性能考虑禁用断言,则根本不存在方法调用。**invariant()** 对对象执行内部有效性检查,如果你在每个方法调用的开始和结束都这样做,这是一个花销巨大的操作,就像 **Meyer** 建议的那样。所以, 用代码清晰地表明是有帮助的,它帮助我调试了实现。此外,如果你对代码实现做任何更改,那么 **invariant()** 将确保你没有破坏代码,将不变性测试从方法调用移到单元测试代码中是相当简单的。如果你的单元测试是足够的,那么你应当对不变性保持一定的信心。 **dump()** 帮助方法返回一个包含所有数据的字符串,而不是直接打印数据。这允许我们用这部分信息做更多事。 From c16de5b28ae49c04fda923aaf23a1ed52710c23a Mon Sep 17 00:00:00 2001 From: legendyql Date: Tue, 11 Aug 2020 14:16:07 +0800 Subject: [PATCH 286/371] =?UTF-8?q?=E7=AC=AC15=E7=AB=A0Exceptions=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E4=BF=AE=E6=94=B9=20(#538)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 15-Exceptions.md * Update 15-Exceptions.md * Update 15-Exceptions.md * Update 15-Exceptions.md 直接把有翻译有争议的句子改回原翻译 * Update 15-Exceptions.md --- docs/book/15-Exceptions.md | 97 ++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index dc18c427..3d2895fb 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -15,7 +15,7 @@ Java 使用异常来提供一致的错误报告模型,使得构件能够与客 Java 中的异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更加确信:你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂,并且它属于那种可以使你的项目受益明显、立竿见影的特性之一。 -因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,书中也就只能写出那么些例子了。本章将向读者介绍如何编写正确的异常处理程序,并将展示当方法出问题的时候,如何产生自定义的异常。 +因为异常处理是 Java 中唯一官方的错误报告机制,并且通过编译器强制执行,所以不学习异常处理的话,你也就只能写出书中那么些例子了。本章将教你如何编写正确的异常处理程序,以及当方法出问题的时候,如何产生自定义的异常。 @@ -48,9 +48,7 @@ if(t == null) 这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。具体是哪个“地方”后面很快就会介绍。 -异常使得我们可以将每件事都当作一个事务来考虑,而异常可以看护着这些事务的底线“…事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法,如果出了什么问题,我们只需要放弃整个计算。”我们还可以将异常看作是一种内建的恢复(undo)系统,因为(在细心使用的情况下)我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将“恢复”到程序中某个已知的稳定点上。 - -异常最重要的方面之一就是如果发生问题,它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++ 这样的语言中,这可真是个问题,尤其是 C,它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去,因此我们有可能会较长时间地忽略问题,从而会陷入完全不恰当的状态中。异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。 +异常允许你将做的每件事都当作一个事务来考虑,而异常守护着这些事务:“...事务的基本保障是,我们需要的分布式计算的异常处理机制。事务相当于计算机中的合同法。如果任何事出现了错误,我们只需要丢弃整个计算。”你也可以将异常看作一种内建的“恢复”(undo)系统,因为(在细心使用时)你在程序中可以有各种恢复点。一旦程序的一个部分失败了,异常将“恢复”到一个已知的稳定点上。 @@ -121,7 +119,7 @@ try { ## 自定义异常 -不必拘泥于 Java 中已有的异常类型。Java 提供的异常体系不可能预见所有的希望加以报告的错误,所以可以自己定义异常类来表示程序中可能会遇到的特定问题。 +不必拘泥于 Java 已有的异常类型。Java异常体系不可能预见你将报告的所有错误,所以你可以创建自己的异常类,来表示你的程序中可能遇到的问题。 要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找)。建立新的异常类型最简单的方法就是让编译器为你产生无参构造器,所以这几乎不用写多少代码: @@ -157,7 +155,7 @@ Caught it! 编译器创建了无参构造器,它将自动调用基类的无参构造器。本例中不会得到像 SimpleException(String) 这样的构造器,这种构造器也不实用。你将看到,对异常来说,最重要的部分就是类名,所以本例中建立的异常类在大多数情况下已经够用了。 -本例的结果被打印到了控制台上,本书的输出显示系统正是在控制台上自动地捕获和测试这些结果的。但是,你也许想通过写入 System.err 而将错误发送给标准错误流。通常这比把错误信息输出到 System.out 要好,因为 System.out 也许会被重定向。如果把结果送到 System.err,它就不会随 System.out 一起被重定向,这样更容易被用户注意。 +本例的结果被显示在控制台。你也可以通过写入 System.err 而将错误发送给标准错误流。通常这比把错误信息输出到 System.out 要好,因为 System.out 也许会被重定向。如果把结果送到 System.err,它就不会随 System.out 一起被重定向,所以用户就更容易注意到它。 你也可以为异常类创建一个接受字符串参数的构造器: @@ -197,13 +195,11 @@ public class FullConstructors { Throwing MyException from f() MyException at FullConstructors.f(FullConstructors.java:11) - at -FullConstructors.main(FullConstructors.java:19) + at FullConstructors.main(FullConstructors.java:19) Throwing MyException from g() MyException: Originated in g() at FullConstructors.g(FullConstructors.java:15) - at -FullConstructors.main(FullConstructors.java:24) + at FullConstructors.main(FullConstructors.java:24) ``` 新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。 @@ -484,8 +480,7 @@ getLocalizedMessage():My Exception toString():java.lang.Exception: My Exception printStackTrace(): java.lang.Exception: My Exception -at -ExceptionMethods.main(ExceptionMethods.java:7) +at ExceptionMethods.main(ExceptionMethods.java:7) ``` 可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。 @@ -534,7 +529,6 @@ public class MultiCatch { void f() { try { x(); - } catch(Except1 | Except2 | Except3 | Except4 e) { process(); } @@ -624,7 +618,7 @@ catch(Exception e) { 重抛异常会把异常抛给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。 -如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 filInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样: +如果只是把当前异常对象重新抛出,那么 printStackTrace() 方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用 fillInStackTrace() 方法,这将返回一个 Throwable 对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的,就像这样: ```java // exceptions/Rethrowing.java @@ -700,7 +694,7 @@ at Rethrowing.main(Rethrowing.java:38) 调用 fillInStackTrace() 的那一行就成了异常的新发生地了。 -有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用 filInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息: +有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用 fillInStackTrace(),有关原来异常发生点的信息会丢失,剩下的是与新的抛出点有关的信息: ```java // exceptions/RethrowNew.java @@ -755,7 +749,7 @@ at RethrowNew.main(RethrowNew.java:26) ### 精准的重新抛出异常 -在 Java 7 之前,如果遇到异常,则只能重新抛出该类型的异常。这导致在 Java 7 中修复的代码不精确。所以在 Java 7 之前,这无法编译: +在 Java 7 之前,如果捕捉到一个异常,重新抛出的异常类型只能与原异常完全相同。这导致代码不精确,Java 7修复了这个问题。所以在 Java 7 之前,这无法编译: ```java class BaseException extends Exception {} @@ -899,14 +893,13 @@ df: d: A new value for d number: 47 number2: 48 number3: 11 + df.getField("d") : A new value for d DynamicFieldsException -at -DynamicFields.setField(DynamicFields.java:65) -at DynamicFields.main(DynamicFields.java:97) +at DynamicFields.setField(DynamicFields.java:65) +at DynamicFields. Caused by: java.lang.NullPointerException -at -DynamicFields.setField(DynamicFields.java:67) +at DynamicFields.setField(DynamicFields.java:67) ... 1 more ``` @@ -916,7 +909,7 @@ DynamicFields.setField(DynamicFields.java:67) 你会注意到,toString() 方法使用了一个 StringBuilder 来创建其结果。在 [字符串](./Strings.md) 这章中你将会了解到更多的关于 StringBuilder 的知识,但是只要你编写设计循环的 toString() 方法,通常都会想使用它,就像本例一样。 -主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,并结合“或(|)”符号。此 Java 7 功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。 +`main()` 方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常,这两种不同的异常通过“或(|)”符号结合起来。 Java 7 的这项功能有助于减少代码重复,并使你更容易指定要捕获的确切类型,而不是简单地捕获一个基类型。你可以通过这种方式组合多种异常类型。 @@ -924,7 +917,7 @@ DynamicFields.setField(DynamicFields.java:67) Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型(指从 Throwable 继承而得到的类型):Error 用来表示编译时和系统错误(除特殊情况外,一般不用你关心);Exception 是可以被抛出的基本类型,在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception。要想对异常有全面的了解,最好去浏览一下 HTML 格式的 Java 文档(可以从 java.sun.com 下载)。为了对不同的异常有个感性的认识,这么做是值得的。但很快你就会发现,这些异常除了名称外其实都差不多。同时,Java 中异常的数目在持续增加,所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说,关键是理解概念以及如何使用。 -异常的基本的概念是用名称代表发生的问题,并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从 java.io.IOException 继承而来的。 +基本理念是用异常的名称代表发生的问题。异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的;有些异常是用来支持其他像 util、net 和 io 这样的程序包,这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如,所有的输入/输出异常都是从 java.io.IOException 继承而来的。 ### 特例:RuntimeException @@ -937,7 +930,7 @@ if(t == null) 如果必须对传递给方法的每个引用都检查其是否为 null(因为无法确定调用者是否传入了非法引用),这听起来着实吓人。幸运的是,这不必由你亲自来做,它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用,Java 会自动抛出 NullPointerException 异常,所以上述代码是多余的,尽管你也许想要执行其他的检查以确保 NullPointerException 不会出现。 -属于运行时异常的类型有很多,它们会自动被 java 虚拟机抛出,所以不必在异常说明中把它们列出来。这些异常都是从 RuntimeException 类继承而来,所以既体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型。并且,也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常(或者任何从 RuntimeException 继承的异常),它们也被称为“不受检查异常”。这种异常属于错误,将被自动捕获,就不用你亲自动手了。要是自己去检查 RuntimeException 的话,代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常,但还是可以在代码中抛出 RuntimeException 类型的异常。 +属于运行时异常的类型有很多,它们被 java 自动抛出,所以不必在异常说明中把它们列出来。非常方便的是,通过将这些异常设置为 `RuntimeException`的子类而把它们归类起来,这是继承的一个绝佳例子:建立具有相同特征和行为的一组类型。 RuntimeException 代表的是编程错误: @@ -980,7 +973,7 @@ at NeverCaught.main(NeverCaught.java:13) 你会发现,RuntimeException(或任何从它继承的异常)是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了 System.err。 -请务必记住:只能在代码中忽略 RuntimeException(及其子类)类型的异常,因为所有受检查类型异常的处理都是由编译器强制实施的。 +请务必记住:代码中只有 RuntimeException(及其子类)类型的异常可以被忽略,因为编译器强制要求处理所有受检查类型的异常。 值得注意的是:不应把 Java 的异常处理机制当成是单一用途的工具。是的,它被设计用来处理一些烦人的运行时错误,这些错误往往是由代码控制能力之外的因素导致的;然而,它对于发现某些编译器无法检测到的编程错误,也是非常重要的。 @@ -1040,7 +1033,7 @@ No exception In finally clause ``` -可以从输出中发现,无论异常是否被抛出,finally 子句总能被执行。这个程序也给了我们一些思路,当 Java 中的异常不允许我们回到异常抛出的地点时,那么该如何应对呢?如果把 try 块放在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个 static 类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。 +从输出中发现,无论异常是否被抛出,finally 子句总能被执行。这也为解决 Java 不允许我们回到异常抛出点这一问题,提供了一个思路。如果将 try 块放在循环里,就可以设置一种在程序执行前一定会遇到的异常状况。还可以加入一个 static 类型的计数器或者别的装置,使循环在结束以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。 ### finally 用来做什么? @@ -1297,7 +1290,7 @@ public class ExceptionSilencer { ## 异常限制 -当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着,若当基类使用的代码应用到其派生类对象的时候,一样能够工作(当然,这是面向对象的基本概念),异常也不例外。 +当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着与基类一起工作的代码,也能和导出类一起正常工作(这是面向对象的基本概念),异常也不例外。 下面例子演示了这种(在编译时)施加在异常上面的限制: @@ -1386,11 +1379,11 @@ public class StormyInning extends Inning implements Storm { 派生类构造器不能捕获基类构造器抛出的异常。 -StormyInning.walk() 不能通过编译是因为它抛出了异常,而 Inning.walk() 并没有声明此异常。如果编译器允许这么做的话,就可以在调用 Inning.walk() 的时候不用做异常处理了,而且当把它替换成 Inning 的派生类的对象时,这个方法就有可能会抛出异常,于是程序就失灵了。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。 +StormyInning.walk() 不能通过编译是因为它抛出了一个 Inning.walk() 中没有声明的异常。如果编译器允许这么做的话,就可以编写调用Inning.walk()却不处理任何异常的代码。 但是当使用 `Inning`派生类的对象时,就会抛出异常,从而导致程序出现问题。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。 -覆盖后的 event() 方法表明,派生类方法可以不抛出任何异常,即使它是基类所定义的异常。同样这是因为,假使基类的方法会抛出异常,这样做也不会破坏已有的程序,所以也没有问题。类似的情况出现在 atBat() 身上,它抛出的是 PopFoul,这个异常是继承自“会被基类的 atBat() 抛出”的 Foul,这样,如果你写的代码是同 Inning 打交道,并且调用了它的 atBat() 的话,那么肯定能捕获 Foul,而 PopFoul 是由 Foul 派生出来的,因此异常处理程序也能捕获 PopFoul。 +覆盖后的 event() 方法表明,派生类版的方法可以不抛出任何异常,即使基类版的方法抛出了异常。因为这样做不会破坏那些假定基类版的方法会抛出异常的代码。类似的情况出现在 `atBat()`上,它抛出的异常`PopFoul`是由基类版`atBat()`抛出的`Foul` 异常派生而来。如果你写的代码同 `Inning` 一起工作,并且调用了 `atBat()`的话,那么肯定能捕获 `Foul` 。又因为 `PopFoul` 是由 `Foul`派生而来,因此异常处理程序也能捕获 `PopFoul`。 -最后一个值得注意的地方是 main()。这里可以看到,如果处理的刚好是 Stormylnning 对象的话,编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会(正确地)要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。 +最后一个有趣的地方在 `main()`。如果处理的刚好是 Stormylnning 对象的话,编译器只要求捕获这个类所抛出的异常。但是如果将它向上转型成基类型,那么编译器就会准确地要求捕获基类的异常。所有这些限制都是为了能产生更为健壮的异常处理代码。 尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。 @@ -1571,7 +1564,7 @@ NeedsCleanup 4 disposed - [2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。 - [3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的 try-finally 语句块中,并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。 -本例中的异常处理的棘手程度,对于应该创建不能失败的构造器是一个有力的论据,尽管这么做并非总是可行。 +本例中异常处理的混乱情形,有力的论证了应该创建不会抛出异常的构造器,尽管这并不总会实现。 注意,如果 dispose() 可以抛出异常,那么你可能需要额外的 try 语句块。基本上,你应该仔细考虑所有的可能性,并确保正确处理每一种情况。 @@ -1581,7 +1574,7 @@ NeedsCleanup 4 disposed 上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径,使系统远离不稳定状态,这非常具有挑战性。 -InputFile.java 是一个特别棘手的情况,因为文件被打开(包含所有可能的异常),然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常,因此可以调用 dispose() 方法。这是一个很好的例子,因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码(当然,你经常会遇到这种你无法选择的代码设计的情况,因此你必须仍然理解它)。 +`InputFile.java` 是一个特别棘手的情况,因为文件被打开(伴随所有可能因此产生的异常),然后它在对象的生命周期中保持打开状态。每次调用 `getLine()`都可能导致异常,而且 `dispose()`也是这种情况。这个例子只是好在它显示了事情可以混乱到什么地步。它还表明了你应该尽量不要那样设计代码(当然,你经常会遇到这种无法选择的代码设计的情况,因此你仍然必须要理解它)。 InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样,文件的打开,读取和关闭都发生在构造函数中。或者,如果读取和存储文件不切实际,你可以改为生成 Stream。理想情况下,你可以设计成如下的样子: @@ -1624,7 +1617,7 @@ main(String[] args) throws IOException { 1. 需要资源清理 2. 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理)。 -一个常见的例子是 jav.io.FileInputstream(将会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中提到)。要正确使用它,你必须编写一些棘手的样板代码: +一个常见的例子是 `java.io.FileInputStream` (将会在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中提到)。要正确使用它,你必须编写一些棘手的样板代码: ```java // exceptions/MessyExceptions.java @@ -1674,9 +1667,9 @@ public class TryWithResources { } ``` -在 Java 7 之前,try 总是后面跟着一个 {,但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在可用于整个 try 块的其余部分。更重要的是,无论你如何退出 try 块(正常或异常),都会执行前一个 finally 子句的等价物,但不会编写那些杂乱而棘手的代码。这是一项重要的改进。 +在 Java 7 之前,try 后面总是跟着一个 {,但是现在可以跟一个带括号的定义 ——这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头(resource specification header)。现在 `in` 在整个 try 块的其余部分都是可用的。更重要的是,无论你如何退出 try 块(正常或通过异常),和以前的 finally 子句等价的代码都会被执行,并且不用编写那些杂乱而棘手的代码。这是一项重要的改进。 -它是如何工作的?在 try-with-resources 定义子句中创建的对象(在括号内)必须实现 java.lang.AutoCloseable 接口,这个接口有一个方法:close()。当在 Java 7 中引入 AutoCloseable 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 Stream 对象: +它是如何工作的? try-with-resources 定义子句中创建的对象(在括号内)必须实现 `java.lang.AutoCloseable` 接口,这个接口只有一个方法:`close()`。当在 Java 7 中引入 `AutoCloseable` 时,许多接口和类被修改以实现它;查看 Javadocs 中的 AutoCloseable,可以找到所有实现该接口的类列表,其中包括 `Stream` 对象: ```java // exceptions/StreamsAreAutoCloseable.java @@ -1743,7 +1736,7 @@ Closing Second Closing First ``` -退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在此配置中,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。 +退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在这种情况下,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。 假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象 @@ -1802,7 +1795,7 @@ Caught: CE 正如预期的那样,First 创建时没有发生意外,SecondExcept 在创建期间抛出异常。请注意,不会为 SecondExcept 调用 close(),因为如果构造函数失败,则无法假设你可以安全地对该对象执行任何操作,包括关闭它。由于 SecondExcept 的异常,Second 对象实例 s2 不会被创建,因此也不会有清除事件发生。 -如果没有构造函数抛出异常,但你可能会在 try 的主体中获取它们,则再次强制你实现 catch 子句: +如果没有构造函数抛出异常,但在 try 的主体中可能抛出异常,那么你将再次被强制要求提供一个catch 子句: ```java // exceptions/BodyException.java @@ -1952,10 +1945,9 @@ try { ## 其他可选方式 -异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形” -发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。 +异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。 -异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。 +异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上,异常处理的一个重要目标就是把处理错误的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码中完成。这样一来,主要代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋于减少。 “被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句,这就导致了吞食则有害(harmful if swallowed)的问题: @@ -1969,7 +1961,8 @@ try { 当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第 2 版中,我在处理程序里通过打印栈轨迹的方法“修补”了这个问题(本章中的很多例子还是使用了这种方法,看起来还是比较合适的),虽然这样可以跟踪异常的行为,但是仍旧不知道该如何处理异常。这一节,我们来研究一下“被检查的异常”及其并发症,以及采用什么方法来解决这些问题。 -这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。 +这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANSI 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。 + ### 历史 @@ -1995,7 +1988,7 @@ C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的 ### 观点 -首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。 +首先,值得注意的是 Java 有效的发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及异常说明通常并不烦扰 C++ 程序员的事实),但是,这还只是一次尝试,目前还没有别的语言选择复制这种做法。 其次,仅从示意性的例子和小程序来看,“被检查的异常”的好处很明显。但是当程序开始变大的时候,就会带来一些微妙的问题。当然,程序不是一下就变大的,这有个过程。如果把不适用于大项目的语言用于小项目,当这些项目不断膨胀时,突然有一天你会发现,原来可以管理的东西,现在已经变得无法管理了。这就是我所说的过多的类型检查,特别是“被检查的异常"所造成的问题。 @@ -2018,6 +2011,8 @@ Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者) > “...总体来说,我觉得异常很不错,但是 Java 的”被检查的异常“带来的麻烦比好处要多。” +我现在认为 Java 的重要进步是统一了错误报告模式,所有错误都用异常来报告。这没有在 C++ 中发生,原因是为了向后兼容 C ,直接忽略错误的旧模式在 C++ 中依然是可用的。如果想使用异常,可以始终用异常来报告,如果不想这样做,异常可以抛到最高的级别(比如控制台)。当 Java 修改 C++ 的模式来让异常成为报告错误的唯一方式时,受检查的异常的额外限制可能就变得不那么必要了。 + 过去,我曾坚定地认为“被检查的异常”和强静态类型检查对开发健壮的程序是非常必要的。但是,我看到的以及我使用一些动态(类型检查)语言的亲身经历告诉我,这些好处实际上是来自于: 1. 不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型。 @@ -2033,7 +2028,7 @@ Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者) ### 把异常传递给控制台 -对于简单的程序,比如本书中的许多例子,最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从 main() 传递到控制台。例如,为了读取信息而打开一个文件(在第 12 章将详细介绍),必须对 FilelnputStream 进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做(本书中很多地方采用了这种方法): +在简单的程序中,不用写多少代码就能保留异常的最简单的方法,就是把它们从 `main()` 传递到控制台。例如,为了读取信息而打开一个文件(在[文件](17-Files.md) 章节中将详细介绍),必须对 `FilelnputStream` 进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做(本书中很多地方采用了这种方法): ```java // exceptions/MainException.java @@ -2054,9 +2049,7 @@ public class MainException { ### 把“被检查的异常”转换为“不检查的异常” -在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的,但这不是通用的方法。 - -问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或者打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样: +当编写自己使用的简单程序时,从 `main()` 中抛出异常是很方便的,但这并不总是有用。真正的问题是,当在一个普通方法里调用别的方法时发现:“我不知道该如何处理这个异常,但是不能把它'吞掉'或者打印一些无用的消息。”有了异常链,一个简单的解决办法就出现了。可以通过将一个“被检查的异常”传递给`RuntimeException` 的构造器,从而将它包装进 `RuntimeException` 里,就像这样: ```java try { @@ -2134,11 +2127,11 @@ Throwable: java.lang.RuntimeException: Where am I? SomeOtherException: SomeOtherException ``` -WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型的异常。这些异常被捕获并包装进了 RuntimeException 对象,所以它们成了这些运行时异常的"cause"了。 +`WrapCheckedException.throwRuntimeException()` 包含可生成不同类型异常的代码。这些异常被捕获并包装进`RuntimeException` 对象,所以它们成了这些运行时异常的原因("cause")。 在 TurnOfChecking 里,可以不用 try 块就调用 throwRuntimeException(),因为它没有抛出“被检查的异常”。但是,当你准备好去捕获异常的时候,还是可以用 try 块来捕获任何你想捕获的异常的。应该捕获 try 块肯定会抛出的异常,这里就是 SomeOtherException,RuntimeException 要放到最后去捕获。然后把 getCause() 的结果(也就是被包装的那个原始异常)抛出来。这样就把原先的那个异常给提取出来了,然后就可以用它们自己的 catch 子句进行处理。 -本书余下部分将会在合适的时候使用这种“用 RuntimeException 来包装,被检查的异常”的技术。另一种解决方案是创建自己的 RuntimeException 的子类。在这种方式中,不必捕获它,但是希望得到它的其他代码都可以捕获它。 +这种把被检查的异常用 `RuntimeException` 包装起来的技术,将在本书余下部分使用。另一种解决方案是创建自己的 `RuntimeException` 的子类。这样的话,异常捕获将不被强制要求,但是任何人都可以在需要的时候捕获这些异常。 @@ -2171,11 +2164,11 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型 (来自于 2011 年的一篇博文) -我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,并且不断被检查的异常所挫败。他向我指出 Howard Lewis Ship 的帖子“[被检查的例外的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”。特别是。James 对他必须跳过去做一些应该简单的事情的所有环感到沮丧。即使在 finally 块中,他也不得不放入更多的 try-catch 子句,因为关闭连接也会导致异常。它在哪里结束?为了简单起见,你必须在环之后跳过环(请注意,try-with-resources语句可以显著改善这种情况)。 +我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,但不断被受检查的异常所挫败。他把 Howard Lewis Ship 的帖子“[被检查的异常的悲剧](http://tapestryjava.blogspot.com/2011/05/tragedy-of-checked-exceptions.html)”指给我看。让 James 尤其沮丧的是,即使做一些本来很简单的事情,也必须在一个个环里跳来跳去。即使在 `finally` 块中,他也不得不放入更多的 `try-catch` 子句,因为关闭连接也会导致异常。这些麻烦事的终点在哪里?本来只是做一些简单的事,但却被强制要求在一个个环里跳来跳去(注意,try-with-resources语句可以显著改善这种情况)。 我们开始讨论 Go 编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。 -我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。 +我的印象是Go团队不做任何臆断,只有在明确一个特征是必须的时候才改进语言。他们似乎并不担心做出破坏旧代码的更改 ,因为他们创建了一个重写工具,当做出更改的时候,重写工具将为你重写代码。这使他们将语言变成一个前进的实验,以发现真正需要的东西,而不是做 Big Upfront Design。 他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。 @@ -2185,11 +2178,11 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型 result, err := functionCall() ``` -( := 告诉 Go 语言这里定义 result 和 err,并且推断他们的数据类型) +( `:=` 告诉 Go 语言在这里定义 `result` 和 `err`,并且推断它们的类型) 就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。 -起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题? +起初这看起来很原始,是向“古代”的回归。但到目前为止,我发现 Go 中的决定都经过了很好的考虑,值得深思。我的反应是因为我的大脑是异常的吗?这会如何影响 James 的问题? 它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句。正是这种替代执行路径的世界导致了 James 抱怨的问题。 From 6f761297f115d2c9c1538f07e646e0fca507e4b7 Mon Sep 17 00:00:00 2001 From: JyChen <2283290737@qq.com> Date: Tue, 11 Aug 2020 20:43:51 +0800 Subject: [PATCH 287/371] Update 22-Enumerations.md (#528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 22-Enumerations.md 枚举→values方法的神秘之处→去掉多余的0 * Update 22-Enumerations.md Co-authored-by: Joe <736777445@qq.com> --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index bb91ba91..299316bf 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -348,7 +348,7 @@ final class Explore extends java.lang.Enum { 由于擦除效应(在[泛型 ]() 章节中介绍过),反编译无法得到 Enum 的完整信息,所以它展示的 Explore 的父类只是一个原始的 Enum,而非事实上的 Enum\。 -由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants0 方法,所以即便 Enum 接口中没有 values0 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。 +由于 values() 方法是由编译器插入到 enum 定义中的 static 方法,所以,如果你将 enum 实例向上转型为 Enum,那么 values() 方法就不可访问了。不过,在 Class 中有一个 getEnumConstants() 方法,所以即便 Enum 接口中没有 values() 方法,我们仍然可以通过 Class 对象取得所有 enum 实例。 ```java // enums/UpcastEnum.java From 1bd0712b50331d404d52f42b1ef2b2fb654d6e7d Mon Sep 17 00:00:00 2001 From: AlanMeng Date: Thu, 13 Aug 2020 10:51:48 +0800 Subject: [PATCH 288/371] =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=AC=AC=E5=8D=81?= =?UTF-8?q?=E5=85=AD=E7=AB=A0=E4=BE=A7=E8=BE=B9=E6=A0=8F=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E6=9C=80=E5=90=8E=E5=A4=9A=E5=87=BA=E6=9D=A5=E7=9A=84=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20(#540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 16-Validating-Your-Code.md * 删除第十六章侧边栏目录最后多出来的链接 --- docs/book/16-Validating-Your-Code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index a0a177d8..940ba5a2 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -4,7 +4,7 @@ # 第十六章 代码校验 -### 你永远不能保证你的代码是正确的,你只能证明它是错的。 +>你永远不能保证你的代码是正确的,你只能证明它是错的。 让我们先暂停编程语言特性的学习,看看一些代码基础知识。特别是能让你的代码更加健壮的知识。 From 5dd8f5456ff41b1a2dd1eee799715153bed20485 Mon Sep 17 00:00:00 2001 From: AlanMeng Date: Fri, 14 Aug 2020 15:57:14 +0800 Subject: [PATCH 289/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E3=80=90=E7=AC=AC?= =?UTF-8?q?=E5=8D=81=E5=85=AB=E7=AB=A0=20=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E3=80=91-=E3=80=90=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E3=80=91-=E3=80=90=E5=88=9B=E5=BB=BA=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E3=80=91Markdown=E8=BD=AC?= =?UTF-8?q?=E4=B9=89=E9=94=99=E8=AF=AF=20(#542)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 16-Validating-Your-Code.md * 删除第十六章侧边栏目录最后多出来的链接 * 修改【第十八章 字符串】-【正则表达式】-【创建正则表达式】Markdown转义错误 --- docs/book/18-Strings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index fe902797..681bb38e 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -801,10 +801,10 @@ the mightiest banana in the forest...with... a banana! | 表达式 | 含义 | | :---- | :---- | | `.` | 任意字符 | -| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a|b|c`作用相同)| +| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a\|b\|c`作用相同)| | `[^abc]` | 除`a`、`b`和`c`之外的任何字符(否定) | | `[a-zA-Z]` | 从`a`到`z`或从`A`到`Z`的任何字符(范围) | -| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a|b|c|h|i|j`作用相同)(合并) | +| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a\|b\|c\|h\|i\|j`作用相同)(合并) | | `[a-z&&[hij]]` | 任意`h`、`i`或`j`(交) | | `\s` | 空白符(空格、tab、换行、换页、回车) | | `\S` | 非空白符(`[^\s]`) | From d2dd76994e13e58a51a21403b566b015f099389f Mon Sep 17 00:00:00 2001 From: AlanMeng Date: Sun, 16 Aug 2020 15:59:49 +0800 Subject: [PATCH 290/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E3=80=90=E7=AC=AC?= =?UTF-8?q?=E5=8D=81=E5=85=AB=E7=AB=A0=20=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E3=80=91-=E3=80=90=E6=AD=A3=E5=88=99=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=E3=80=91-=E3=80=90=E5=88=9B=E5=BB=BA=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E3=80=91-=E3=80=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=93=8D=E4=BD=9C=E7=AC=A6=E3=80=91Markdown?= =?UTF-8?q?=E8=BD=AC=E4=B9=89=E9=94=99=E8=AF=AF=20(#544)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 16-Validating-Your-Code.md * 删除第十六章侧边栏目录最后多出来的链接 * 修改【第十八章 字符串】-【正则表达式】-【创建正则表达式】Markdown转义错误 * 修改【第十八章 字符串】-【正则表达式】-【创建正则表达式】-【逻辑操作符】Markdown转义错误 --- docs/book/18-Strings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 681bb38e..be0bca0b 100644 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -818,7 +818,7 @@ the mightiest banana in the forest...with... a banana! | 逻辑操作符 | 含义 | | :----: | :---- | | `XY` | `Y`跟在`X`后面 | -| `X|Y` | `X`或`Y` | +| `X\|Y` | `X`或`Y` | | `(X)` | 捕获组(capturing group)。可以在表达式中用`\i`引用第i个捕获组 | 下面是不同的边界匹配符: From acc61a92c8060fba74e4ae415f0f2732f58cba90 Mon Sep 17 00:00:00 2001 From: jiangqs3 <34760596+jiangqs3@users.noreply.github.com> Date: Sun, 16 Aug 2020 17:03:52 +0800 Subject: [PATCH 291/371] =?UTF-8?q?=E5=B0=86=E5=9C=A8=20`NotFunctional`=20?= =?UTF-8?q?=E7=9A=84=E5=AE=9A=E4=B9=89=E4=B8=AD=E5=8F=AF=E7=9C=8B=E5=88=B0?= =?UTF-8?q?`@FunctionalInterface`=20=E7=9A=84=E4=BD=9C=E7=94=A8=EF=BC=9A?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=B8=AD=E5=A6=82=E6=9E=9C=E6=9C=89=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=96=B9=E6=B3=95=E5=88=99=E4=BC=9A=E4=BA=A7=E7=94=9F?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E6=9C=9F=E9=94=99=E8=AF=AF=E3=80=82=E4=B8=80?= =?UTF-8?q?=E5=8F=A5=E4=B8=AD=E7=9A=84=E6=96=B9=E6=B3=95=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E6=96=B9=E6=B3=95=20(#546)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #545 --- docs/book/13-Functional-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 01738eb1..0f8b5e83 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -630,7 +630,7 @@ public class FunctionalAnnotation { } ``` -`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 在 `NotFunctional` 的定义中可看到`@FunctionalInterface` 的作用:接口中如果有多个方法则会产生编译期错误。 +`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 在 `NotFunctional` 的定义中可看到`@FunctionalInterface` 的作用:接口中如果有多个抽象方法则会产生编译期错误。 仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中。 From db116b33a1b9b13e0776dc64243c034bf119b57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E4=B8=83?= Date: Wed, 19 Aug 2020 21:01:41 +0800 Subject: [PATCH 292/371] Update 04-Operators.md (#547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix 链接错误 --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index 60fc4fc3..626cc342 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -7,7 +7,7 @@ Java 是从 C++ 的基础上做了一些改进和简化发展而成的。对于 C/C++ 程序员来说,Java 的运算符并不陌生。如果你已了解 C 或 C++,大可以跳过本章和下一章,直接阅读 Java 与 C/C++ 不同的地方。 -如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com]) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。 +如果理解这两章的内容对你来说还有点困难,那么我推荐你先了解下 《Thinking in C》 再继续后面的学习。 这本书现在可以在 [www.OnJava8.com](http://www.OnJava8.com) 上免费下载。它的内容包含音频讲座、幻灯片、练习和解答,专门用于帮助你快速掌握学习 Java 所需的基础知识。 ## 开始使用 From 3f2331958bb5fe9856fc7fceee8d50452611ea04 Mon Sep 17 00:00:00 2001 From: Jian Meng Date: Fri, 21 Aug 2020 10:46:51 +0800 Subject: [PATCH 293/371] Update 09-Polymorphism.md (#548) --- docs/book/09-Polymorphism.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 95dabd2d..6a105c6a 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -640,7 +640,7 @@ Derived dynamicGet() ## 构造器和多态 -通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。这个理解可以帮助你避免一些不愉快的困扰。 +通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。理解这个可以帮助你避免一些不愉快的困扰。 ### 构造器调用顺序 From d68e68a006bac12d798d024fc569ffd39ef5c91a Mon Sep 17 00:00:00 2001 From: Chen Xuewei <316403398@qq.com> Date: Fri, 21 Aug 2020 10:50:45 +0800 Subject: [PATCH 294/371] AtomicInteger to replace synchronized (#543) With AtomicInteger, synchronized is no longer needed. --- docs/book/Appendix-Low-Level-Concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/Appendix-Low-Level-Concurrency.md b/docs/book/Appendix-Low-Level-Concurrency.md index 8fb91c08..2db5aa5a 100644 --- a/docs/book/Appendix-Low-Level-Concurrency.md +++ b/docs/book/Appendix-Low-Level-Concurrency.md @@ -1053,7 +1053,7 @@ public class AtomicSerialNumbers extends SerialNumbers { private AtomicInteger serialNumber = new AtomicInteger(); - public synchronized int nextSerialNumber() { + public int nextSerialNumber() { return serialNumber.getAndIncrement(); } public static void main(String[] args) { From 42b4b13f5d9b9044206b501ed62bdb7a4c0e0d22 Mon Sep 17 00:00:00 2001 From: xiantaner Date: Thu, 27 Aug 2020 14:48:53 +0800 Subject: [PATCH 295/371] Update 06-Housekeeping.md (#549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改翻译不准确的问题: “and putting the static method inside a class allows it access to other static methods and to static fields” --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index f0b805d1..0a7a5f1c 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -592,7 +592,7 @@ petalCount = 47 s = hi ### static 的含义 -记住了 **this** 关键字的内容,你会对 **static** 修饰的方法有更加深入的理解:**static** 方法中不会存在 **this**。你不能在静态方法中调用非静态方法(反之可以)。静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以被其他的静态方法和静态属性访问。一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 **this**,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 **static** 方法,就该重新考虑自己的设计了。然而,**static** 的概念很实用,许多时候都要用到它。至于它是否真的"面向对象",就留给理论家去讨论吧。 +记住了 **this** 关键字的内容,你会对 **static** 修饰的方法有更加深入的理解:**static** 方法中不会存在 **this**。你不能在静态方法中调用非静态方法(反之可以)。静态方法是为类而创建的,不需要任何对象。事实上,这就是静态方法的主要目的,静态方法看起来就像全局方法一样,但是 Java 中不允许全局方法,一个类中的静态方法可以访问其他静态方法和静态属性。一些人认为静态方法不是面向对象的,因为它们的确具有全局方法的语义。使用静态方法,因为不存在 **this**,所以你没有向一个对象发送消息。的确,如果你发现代码中出现了大量的 **static** 方法,就该重新考虑自己的设计了。然而,**static** 的概念很实用,许多时候都要用到它。至于它是否真的"面向对象",就留给理论家去讨论吧。 From 36c12d8b40a71f5431b4e8815242954773125d97 Mon Sep 17 00:00:00 2001 From: Maktub77 <44797222+Maktub77@users.noreply.github.com> Date: Mon, 31 Aug 2020 12:13:56 +0800 Subject: [PATCH 296/371] =?UTF-8?q?fix(doc):=E4=BF=AE=E6=94=B9=E9=94=99?= =?UTF-8?q?=E5=88=AB=E5=AD=97=20(#555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/22-Enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index 299316bf..5ad49993 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -551,7 +551,7 @@ public class TypeOfFood { 如果 enum 类型实现了 Food 接口,那么我们就可以将其实例向上转型为 Food,所以上例中的所有东西都是 Food。 -然而,当你需要与一大堆类型打交道时,接口就不如 enum 好用了。例如,如果你想创建一个“校举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类: +然而,当你需要与一大堆类型打交道时,接口就不如 enum 好用了。例如,如果你想创建一个“枚举的枚举”,那么可以创建一个新的 enum,然后用其实例包装 Food 中的每一个 enum 类: ```java // enums/menu/Course.java From 83662247d6008fbb0d860825d28191964a95c0d2 Mon Sep 17 00:00:00 2001 From: Maktub77 <44797222+Maktub77@users.noreply.github.com> Date: Tue, 1 Sep 2020 18:09:20 +0800 Subject: [PATCH 297/371] =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E5=8F=8A?= =?UTF-8?q?=E7=AC=A6=E5=8F=B7=E8=B0=83=E6=95=B4=20(#557)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(doc):修改错别字 * 错别字及符号调整 --- docs/book/22-Enumerations.md | 2 +- docs/book/23-Annotations.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/22-Enumerations.md b/docs/book/22-Enumerations.md index 5ad49993..818641a9 100644 --- a/docs/book/22-Enumerations.md +++ b/docs/book/22-Enumerations.md @@ -1799,7 +1799,7 @@ public interface Competitor> { } ``` -然后,我们定义两个 static 方法(static 可以避免显式地指明参数类型),第一个是 match() 方法,它会为一个 Competitor 对象调用 compete() 方法,并与另一个 Competitor 对象作比较。在这个例子中,我们看到,match())方法的参数需要是 Competitor\ 类型。但是在 play() 方法中,类型参数必须同时是 Enum\ 类型(因为它将在 Enums.random() 中使用)和 Competitor\ 类型因为它将被传递给 match() 方法): +然后,我们定义两个 static 方法(static 可以避免显式地指明参数类型),第一个是 match() 方法,它会为一个 Competitor 对象调用 compete() 方法,并与另一个 Competitor 对象作比较。在这个例子中,我们看到,match()方法的参数需要是 Competitor\ 类型。但是在 play() 方法中,类型参数必须同时是 Enum\ 类型(因为它将在 Enums.random() 中使用)和 Competitor\ 类型(因为它将被传递给 match() 方法): ```java // enums/RoShamBo.java diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 8448423f..95b79088 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -269,7 +269,7 @@ public @interface SQLInteger { **@Constraints** 注解允许处理器提供数据库表的元数据。**@Constraints** 代表了数据库通常提供的约束的一小部分,但是它所要表达的思想已经很清楚了。`primaryKey()`,`allowNull()` 和 `unique()` 元素明显的提供了默认值,从而使得在大多数情况下,该注解的使用者不需要输入太多东西。 -另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过为为示例,两个元素足够了。 +另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话,我们应该为每个 SQL 类型都定义相应的注解。不过作为示例,两个元素足够了。 这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能,将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值是 **@Constraints**。由于在 **@Constraints** 注解类型之后,没有在括号中指明 **@Constraints** 元素的值,因此,**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的 **@Constraints** 注解中的 `unique()` 元素为 true,并作为 `constraints()` 元素的默认值,你可以像如下定义: @@ -853,7 +853,7 @@ annotations.AtUnitExample1: anotherDisappointment 使用 **@Unit** 进行测试的类必须定义在某个包中(即必须包括 **package** 声明)。 -**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 a`notherDisappointment()` 方法之前,它们告诉 **@Unit** 方法作为单元测试来运行。同时 **@Test** 确保这些方法没有任何参数并且返回值为 **boolean** 或者 **void**。当你填写单元测试时,唯一需要做的就是决定测试是成功还是失败,(对于返回值为 **boolean** 的方法)应该返回 **ture** 还是 **false**。 +**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 `anotherDisappointment()` 方法之前,它们告诉 **@Unit** 方法作为单元测试来运行。同时 **@Test** 确保这些方法没有任何参数并且返回值为 **boolean** 或者 **void**。当你填写单元测试时,唯一需要做的就是决定测试是成功还是失败,(对于返回值为 **boolean** 的方法)应该返回 **ture** 还是 **false**。 如果你熟悉 **JUnit**,你还将注意到 **@Unit** 输出的信息更多。你会看到现在正在运行的测试的输出更有用,最后它会告诉你导致失败的类和测试。 From e7073d884ca11d12fd4458725ae037cb97b9b287 Mon Sep 17 00:00:00 2001 From: xiguazhiPrince <34903051+xiguazhiPrince@users.noreply.github.com> Date: Wed, 2 Sep 2020 12:04:32 +0800 Subject: [PATCH 298/371] =?UTF-8?q?=E5=B0=86=E2=80=9C=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E5=86=8DAbstractCollection=E2=80=9D=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=86=8D=E7=BB=A7=E6=89=BF=20(#558)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将“继承再AbstractCollection” 修改为再继承 --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index b1cc66dd..49326b79 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -1383,7 +1383,7 @@ extends AbstractCollection { - **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。 -这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能继承再 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多: +这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能再继承 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多: ```java // collections/NonCollectionSequence.java From 669c390eb13f515cb9ba99ea391ffcb80662fe29 Mon Sep 17 00:00:00 2001 From: Jian Meng Date: Wed, 2 Sep 2020 12:04:52 +0800 Subject: [PATCH 299/371] To correct run result, add empty space lines (#559) * Update 09-Polymorphism.md * To correct run result, add empty space lines --- docs/book/14-Streams.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index f5611733..f3d85cef 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -886,23 +886,27 @@ class FunctionMap { 输出结果: ``` ----( add brackets )--- + ---( add brackets )--- [12] [] [23] [45] ----( Increment )--- + ---( Increment )--- 13 + 24 46 ----( Replace )--- + ---( Replace )--- 19 + 93 45 ----( Take last digit )--- + ---( Take last digit )--- 2 + 3 5 + ``` 在上面的自增示例中,我们用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能被转化成为整数就会抛出 `NumberFormatException` 异常,此时我们就回过头来把原始字符串放到输出流中。 From 3ba9f9af4c27e9279f8dd7317148d343c2289d84 Mon Sep 17 00:00:00 2001 From: chong-xiaowu <54967084+chong-xiaowu@users.noreply.github.com> Date: Wed, 2 Sep 2020 16:04:38 +0800 Subject: [PATCH 300/371] =?UTF-8?q?10101111=E6=94=B9=E4=B8=BA01111111=20(#?= =?UTF-8?q?561)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: lyc --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index 626cc342..40ae0a9f 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -494,7 +494,7 @@ public class Literals { char c = 0xffff; // 最大 char 型16进制值 System.out.println( "c: " + Integer.toBinaryString(c)); - byte b = 0x7f; // 最大 byte 型16进制值 10101111; + byte b = 0x7f; // 最大 byte 型16进制值 01111111; System.out.println( "b: " + Integer.toBinaryString(b)); short s = 0x7fff; // 最大 short 型16进制值 From 41501060cc90572607f64278ef0ba4fa800c5000 Mon Sep 17 00:00:00 2001 From: jkleaf <43708298+jkleaf@users.noreply.github.com> Date: Thu, 3 Sep 2020 12:13:57 +0800 Subject: [PATCH 301/371] =?UTF-8?q?fix=20=E9=94=99=E5=88=AB=E5=AD=97=20(#5?= =?UTF-8?q?62)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/Appendix-Object-Serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/Appendix-Object-Serialization.md b/docs/book/Appendix-Object-Serialization.md index a5a09d14..722f0e51 100644 --- a/docs/book/Appendix-Object-Serialization.md +++ b/docs/book/Appendix-Object-Serialization.md @@ -283,7 +283,7 @@ Blip1 Constructor Blip1.readExternal ``` -没有恢复 Blip2 对象的原因是那样做会导致一个异常。你找出 Blip1 和 Blip2 之间的区别了吗?Blipl 的构造器是“公共的”(pablic),Blip2 的构造器却不是,这样就会在恢复时造成异常。试试将 Blip2 的构造器变成 public 的,然后删除//注释标记,看看是否能得到正确的结果。 +没有恢复 Blip2 对象的原因是那样做会导致一个异常。你找出 Blip1 和 Blip2 之间的区别了吗?Blipl 的构造器是“公共的”(public),Blip2 的构造器却不是,这样就会在恢复时造成异常。试试将 Blip2 的构造器变成 public 的,然后删除//注释标记,看看是否能得到正确的结果。 恢复 b1 后,会调用 Blip1 默认构造器。这与恢复一个 Serializable 对象不同。对于 Serializable 对象,对象完全以它存储的二进制位为基础来构造,而不调用构造器。而对于一个 Externalizable 对象,所有普通的默认构造器都会被调用(包括在字段定义时的初始化),然后调用 readExternal() 必须注意这一点--所有默认的构造器都会被调用,才能使 Externalizable 对象产生正确的行为。 From a7a723a3b31a7d91a9ab97a3baa4bd9a1d8a1426 Mon Sep 17 00:00:00 2001 From: amyxin <896123009@qq.com> Date: Mon, 7 Sep 2020 09:04:36 +0800 Subject: [PATCH 302/371] Update 24-Concurrent-Programming.md (#564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创建和运行并行任务 示例代码 final int中间增加了空格 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 0a85cee3..603eb9e9 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -771,7 +771,7 @@ Java并发的历史始于非常原始和有问题的机制,并且充满了各 // concurrent/NapTask.java import onjava.Nap; public class NapTask implements Runnable { - finalint id; + final int id; public NapTask(int id) { this.id = id; } From 8c2f581229f71ff50c9b79683f7f33789ed3efcf Mon Sep 17 00:00:00 2001 From: qurrer <40011809+qurrer@users.noreply.github.com> Date: Tue, 8 Sep 2020 15:53:09 +0800 Subject: [PATCH 303/371] Update 10-Interfaces.md (#566) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 完全解耦 修改Process为Processor --- docs/book/10-Interfaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index d4b94063..f2bc527e 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -780,7 +780,7 @@ Woodwind.play() MIDDLE_C 当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类。如果想把方法应用于那个继承层级结构之外的类,就会触霉头。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。 -例如有一个类 **Process** 有两个方法 `name()` 和 `process()`。`process()` 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 **Processor**。下例中,**Processor** 的各个子类修改 String 对象(注意,返回类型可能是协变类型而非参数类型): +例如有一个类 **Processor** 有两个方法 `name()` 和 `process()`。`process()` 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 **Processor**。下例中,**Processor** 的各个子类修改 String 对象(注意,返回类型可能是协变类型而非参数类型): ```java // interfaces/Applicator.java From edfebaf56c646d6aa4fcd1bbc97d2e9ee998da0c Mon Sep 17 00:00:00 2001 From: PEGASUS <32333727+PEGASUS1993@users.noreply.github.com> Date: Thu, 10 Sep 2020 17:13:00 +0800 Subject: [PATCH 304/371] =?UTF-8?q?Preface=20=E7=BF=BB=E8=AF=91=E4=BC=98?= =?UTF-8?q?=E5=8C=96=20(#565)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 00-Preface.md * Update 00-On-Java-8.md * Update 00-Preface.md * Update 00-Preface.md --- docs/book/00-On-Java-8.md | 11 ++++++----- docs/book/00-Preface.md | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/book/00-On-Java-8.md b/docs/book/00-On-Java-8.md index c11bde4d..a7d91f09 100644 --- a/docs/book/00-On-Java-8.md +++ b/docs/book/00-On-Java-8.md @@ -51,13 +51,14 @@ -本书出版自美国,版权所有,翻版必究。未经授权不得非法存储在检索系统中,或以电子,机械,影印,录制任何形式传输等。制造商和销售商使用商标用来区分其产品标识。如果这些名称出现在这本书中,并且出版商知道商标要求,则这些名称已经用大写字母或所有大写字母打印。 +本书出版自美国,版权所有,翻版必究。未经授权,不得非法存储在检索系统中,或以电子,机械,影印,录制等形式传播。制造商和销售商使用商标用来区分其产品标识。如果这些名称出现在这本书中,并且出版商知道商标要求,则这些名称已经用大写字母或所有大写字母打印。 Java 是甲骨文公司(Oracle. Inc.)的商标。Windows 95,Windows NT,Windows 2000,Windows XP,Windows 7,Windows 8 和 Windows 10 是微软公司(Microsoft Corporation)的商标。 -此处提及的所有其他产品名称和公司名称均为其各自所有者的财产。作者和出版商在编写本书时已经仔细校对过,但不作任何明示或暗示的保证,对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损失,我们不承担任何责任。 +此处提及的所有其他产品名称和公司名称均为其各自所有者的财产。 -这本书是以平板电脑和计算机为载体的电子书,非传统纸质版书籍。 -故所有布局和格式设计旨在优化您在各种电子书阅读平台和系统上的观看体验。 +作者和出版社在编写本书时已经仔细校对过,但不作出任何形式的保证,也不承担任何错误或遗漏的责任。对于使用本协议中包含的信息或程序而产生的附带或间接损失,本协议不承担任何责任。 + +本书是为平板电脑和计算机设备制作的的电子书,非传统纸质版书籍。 因此所有布局和格式设计旨在优化您在各种电子书阅读平台和系统上的观看体验。 封面由 Daniel Will-Harris 设计,[www.Will-Harris.com](http://www.Will-Harris.com)。 -
\ No newline at end of file +
diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index d614e816..903b92e5 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -5,13 +5,13 @@ 我之前的 Java 书籍 *Thinking in Java, 4th Edition*(《Java编程思想 (第4版)》 Prentice Hall 2006)依然适用于 Java 5 编程,在此版本 Java 语言开始用作 Android 编程。此后,这门语言的许多地方发生了翻天覆地的变化,特别是 Java 8 的转变,以至于新的 Java 代码读起来的感觉也不尽相同。这也促使我时隔多年,创作了这本新书。 -《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站补充必要的前置知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。与几年前我们依赖印刷媒体时相比,像 YouTube、博客和 StackOverFlow 这样的网站使得寻找答案变得非常简单。如果将本书作为编程入门书籍,请结合这些学习途径努力坚持下去。同时,本书也适合想要扩展知识的在职程序员。 +《On Java 8》旨在面向已具有编程基础的开发者们。对于初学者,可以先在 [Code.org](http://Code.org) 或者 [可汗学院Khan Academy](https://www.khanacademy.org/computing/computer-programming) 等网站补充必要的预备知识。同时,[OnJava8.com](http://www.OnJava8.com) 上也有免费的 Thinking in C(《C编程思想》)专题知识。与几年前我们依赖印刷媒体时相比,像 YouTube、博客和 StackOverFlow 这样的网站使得寻找答案变得非常简单。如果将本书作为编程入门书籍,请结合这些学习途径努力坚持下去。同时,本书也适合想要扩展知识的专业程序员。 得益于《*Thinking in Java*》,我得以到世界各地演讲,我对此由衷感激。它为我的 [Reinventing Business](http://www.reinventing-business.com) 项目在与人员及公司建立联系方面提供了宝贵的帮助。我最终撰写本书的原因之一就是想支持这一项目的研究,而下一个合乎逻辑的步骤似乎是实际创建一个所谓的蓝绿色组织(Teal Organization)。我希望本书可以成为该项目的一种众筹。 ## 教学目标 -每章教授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。 +每章讲授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。 本书的教学目标: @@ -51,9 +51,9 @@ 书中代码示例基于 Java 8 和 Gradle 编译构建,并且代码示例都保存在[这个自由访问的GitHub的仓库](https://github.com/BruceEckel/OnJava8-Examples) 中。我们需要内置的测试框架,以便于在每次构建系统时自动运行。否则,你将无法保证自己代码的可靠性。为了实现这一点,我创建了一个测试系统来显示和验证大多数示例的输出结果。这些输出结果我会附加在示例结尾的代码块中。有时仅显示必要的那几行或者首尾行。利用这种方式来改善读者的阅读和学习体验,同时也提供了一种验证示例正确性的方法。 -## 普及性 +## 受欢迎度 -Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你更容易找到工作。相关的培训材料,课程和其他可用的学习资源也很多。对于企业来说,招聘 Java 程序员相对容易。如果你不喜欢 Java 语言,那么最好不要拿他当作你谋生的工具,因为这种生活体验并不好。作为一家公司,在技术选型前一定不要单单只考虑 Java 程序员好招。每种语言都有其适用的范围,有可能你们的业务更适用于另一种编程语言来达到事半功倍的效果。如果你真的喜欢 Java,那么欢迎你。希望这本书能丰富你的编程经验! +Java的受欢迎程度具有重要意义。学习 Java 会让你更容易找到工作。相关的培训材料,课程和其他可用的学习资源也很多。对于使用Java的初创企业来说,招聘 Java 程序员相对容易。如果你真的不喜欢 Java 语言,那么最好不要使用它————仅仅为了找工作而使用它,并不是一个快乐的生活选择。作为一家公司,在技术选型前一定不要单单只考虑容易招聘Java 程序员。每种语言都有其适用的范围,有可能另一种编程语言更适用于你们的业务,来达到事半功倍的效果。如果你真的喜欢 Java,那么欢迎你。希望这本书能丰富你的编程经验! ## 关于安卓 @@ -93,8 +93,8 @@ Java 的普及性对于其受欢迎程度有重要意义。学习 Java 会让你 ## 献礼 -> 谨以此书献给我敬爱的父亲 E. Wayne Eckel。 -> 1924年4月1日至2016年11月23日 +> 谨以此书献给我敬爱的父亲 E. Wayne Eckel +> (1924.4.1 ~ 2016.11.23)。
From b677bca1fe303f4939f31df92f99e567d7aa0ac3 Mon Sep 17 00:00:00 2001 From: xiguazhiPrince <34903051+xiguazhiPrince@users.noreply.github.com> Date: Thu, 10 Sep 2020 20:43:34 +0800 Subject: [PATCH 305/371] =?UTF-8?q?=E5=B0=86=E2=80=9C=E7=BB=A7=E6=89=BF?= =?UTF-8?q?=E5=86=8DAbstractCollection=E2=80=9D=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=86=8D=E7=BB=A7=E6=89=BF=20(#569)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将“继承再AbstractCollection” 修改为再继承 From ccbe03fed1c8dfade10d33d6ff1ab9dffd56ee02 Mon Sep 17 00:00:00 2001 From: Lane Date: Thu, 10 Sep 2020 20:43:56 +0800 Subject: [PATCH 306/371] Update 11-Inner-Classes.md (#568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 让粗体显示正常 --- docs/book/11-Inner-Classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 3265c2fc..3a6ac3bd 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1419,9 +1419,9 @@ Anonymous inner 9 ## 内部类标识符 -由于编译后每个类都会产生一个**.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 **Class** 对象)。 +由于编译后每个类都会产生一个 **.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 **Class** 对象)。 -你可能猜到了,内部类也必须生成一个**.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“**$**",再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: +你可能猜到了,内部类也必须生成一个 **.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“**$**”,再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: ```java Counter.class From 706827c54cc99d5122439e164b061090fc2dcb18 Mon Sep 17 00:00:00 2001 From: Lane Date: Fri, 11 Sep 2020 15:32:28 +0800 Subject: [PATCH 307/371] Update 11-Inner-Classes.md (#570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 双引号挪到 `**` 内部 - 使用英文双引号(否则 gitbook 下显示 怪异) - 添加必要空格, - java => Java --- docs/book/11-Inner-Classes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 3a6ac3bd..ea329e45 100644 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1421,7 +1421,7 @@ Anonymous inner 9 由于编译后每个类都会产生一个 **.class** 文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个"meta-class",叫做 **Class** 对象)。 -你可能猜到了,内部类也必须生成一个 **.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上“**$**”,再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: +你可能猜到了,内部类也必须生成一个 **.class** 文件以包含它们的 **Class** 对象信息。这些类文件的命名有严格的规则:外部类的名字,加上 **"$"** ,再加上内部类的名字。例如,**LocalInnerClass.java** 生成的 **.class** 文件包括: ```java Counter.class @@ -1430,9 +1430,9 @@ LocalInnerClass$LocalCounter.class LocalInnerClass.class ``` -如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与“**$**”的后面。 +如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与 **"$"** 的后面。 -虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) +虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) From 0acae8e1324b25814ec427dadd01ba0a4e086689 Mon Sep 17 00:00:00 2001 From: Hongkuan Wang Date: Sun, 13 Sep 2020 18:12:21 +0300 Subject: [PATCH 308/371] =?UTF-8?q?=E8=BD=AC=E4=B9=89=E5=A4=A7=E4=BA=8E?= =?UTF-8?q?=E5=8F=B7=E5=92=8C=E5=B0=8F=E4=BA=8E=E5=8F=B7=20(#574)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/12-Collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 49326b79..269fc785 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -771,7 +771,7 @@ public class Stack { } ``` -这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。 +这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 **\** 告诉编译器这是一个参数化类型,而其中的类型参数 **T** 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 **T** 类型对象的 **Stack** 。” **Stack** 是使用 **ArrayDeque** 实现的,而 **ArrayDeque** 也被告知它将持有 **T** 类型对象。注意, `push()` 接受类型为 **T** 的对象,而 `peek()` 和 `pop()` 返回类型为 **T** 的对象。 `peek()` 方法将返回栈顶元素,但并不将其从栈顶删除,而 `pop()` 删除并返回顶部元素。 如果只需要栈的行为,那么使用继承是不合适的,因为这将产生一个具有 **ArrayDeque** 的其它所有方法的类(在[附录:集合主题]()中将会看到, **Java 1.0** 设计者在创建 **java.util.Stack** 时,就犯了这个错误)。使用组合,可以选择要公开的方法以及如何命名它们。 From 12b5813041df2d7bad7f206cae89d29065e05cec Mon Sep 17 00:00:00 2001 From: Lane Date: Sun, 13 Sep 2020 23:12:41 +0800 Subject: [PATCH 309/371] make sidebar appearance corrent (#573) --- docs/index.html | 45 ++++++++-------- docs/sidebar.md | 133 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 113 insertions(+), 65 deletions(-) diff --git a/docs/index.html b/docs/index.html index 7f54b8af..b7e41098 100644 --- a/docs/index.html +++ b/docs/index.html @@ -55,6 +55,11 @@ /*font-family: Microsoft YaHei, Source Sans Pro, Helvetica Neue, Arial, sans-serif !important;*/ } + .sidebar, .sidebar-nav { + padding-left: 10px; + padding-right: 10px; + } + .markdown-section>p { font-size: 16px !important; } @@ -66,7 +71,7 @@ /*.anchor span { color: rgb(66, 185, 131); - }*/ + }*/ section.cover h1 { margin: 0; @@ -345,29 +350,29 @@ window.$docsify = { name: '《On Java 8》中文版', repo: 'https://github.com/LingCoder/OnJava8', - // loadSidebar: true, - subMaxLevel: 3, - search: { - paths: 'auto', - placeholder: '🔍 点击搜索 ', - noData: '😞 没有结果! ', - // Headline depth, 1 - 6 - depth: 6 + loadSidebar: 'sidebar.md', + // subMaxLevel: 3, + search: { + paths: 'auto', + placeholder: '🔍 点击搜索 ', + noData: '😞 没有结果! ', + // Headline depth, 1 - 6 + depth: 6 + }, + copyCode: { + buttonText : '复制', + errorText : 'Error', + successText: 'OK!' + }, + pagination: { + previousText: '上一章节', + nextText: '下一章节', }, - copyCode: { - buttonText : '复制', - errorText : 'Error', - successText: 'OK!' - }, - pagination: { - previousText: '上一章节', - nextText: '下一章节', - }, - coverpage: true + coverpage: true, } - + diff --git a/docs/sidebar.md b/docs/sidebar.md index d1d99dcd..e79c07a0 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -1,9 +1,13 @@ -#### [译者的话](README.md) -#### [封面](book/00-On-Java-8.md) -#### [前言](book/00-Preface.md) -#### [简介](book/00-Introduction.md) -## [第一章 对象的概念](book/01-What-is-an-Object.md) +### [译者的话](README.md) + +### [封面](book/00-On-Java-8.md) + +### [前言](book/00-Preface.md) + +### [简介](book/00-Introduction.md) + +### [第一章 对象的概念](book/01-What-is-an-Object.md) * [抽象](book/01-What-is-an-Object.md#抽象) * [接口](book/01-What-is-an-Object.md#接口) * [服务提供](book/01-What-is-an-Object.md#服务提供) @@ -16,13 +20,15 @@ * [生命周期](book/01-What-is-an-Object.md#生命周期) * [异常处理](book/01-What-is-an-Object.md#异常处理) * [本章小结](book/01-What-is-an-Object.md#本章小结) -## [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) + +### [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器) * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell) * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装) * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装) * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) -## [第三章 万物皆对象](book/03-Objects-Everywhere.md) + +### [第三章 万物皆对象](book/03-Objects-Everywhere.md) * [对象操纵](book/03-Objects-Everywhere.md#对象操纵) * [对象创建](book/03-Objects-Everywhere.md#对象创建) * [代码注释](book/03-Objects-Everywhere.md#代码注释) @@ -32,7 +38,8 @@ * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀) * [编码风格](book/03-Objects-Everywhere.md#编码风格) * [本章小结](book/03-Objects-Everywhere.md#本章小结) -## [第四章 运算符](book/04-Operators.md) + +### [第四章 运算符](book/04-Operators.md) * [开始使用](book/04-Operators.md#开始使用) * [优先级](book/04-Operators.md#优先级) * [赋值](book/04-Operators.md#赋值) @@ -50,7 +57,8 @@ * [Java没有sizeof](book/04-Operators.md#Java没有sizeof) * [运算符总结](book/04-Operators.md#运算符总结) * [本章小结](book/04-Operators.md#本章小结) -## [第五章 控制流](book/05-Control-Flow.md) + +### [第五章 控制流](book/05-Control-Flow.md) * [true和flase](book/05-Control-Flow.md#true和flase) * [if-else](book/05-Control-Flow.md#if-else) * [迭代语句](book/05-Control-Flow.md#迭代语句) @@ -61,7 +69,8 @@ * [switch](book/05-Control-Flow.md#switch) * [switch字符串](book/05-Control-Flow.md#switch字符串) * [本章小结](book/05-Control-Flow.md#本章小结) -## [第六章 初始化和清理](book/06-Housekeeping.md) + +### [第六章 初始化和清理](book/06-Housekeeping.md) * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化) * [方法重载](book/06-Housekeeping.md#方法重载) * [无参构造器](book/06-Housekeeping.md#无参构造器) @@ -72,13 +81,15 @@ * [数组初始化](book/06-Housekeeping.md#数组初始化) * [枚举类型](book/06-Housekeeping.md#枚举类型) * [本章小结](book/06-Housekeeping.md#本章小结) -## [第七章 封装](book/07-Implementation-Hiding.md) + +### [第七章 封装](book/07-Implementation-Hiding.md) * [包的概念](book/07-Implementation-Hiding.md#包的概念) * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符) * [接口和实现](book/07-Implementation-Hiding.md#接口和实现) * [类访问权限](book/07-Implementation-Hiding.md#类访问权限) * [本章小结](book/07-Implementation-Hiding.md#本章小结) -## [第八章 复用](book/08-Reuse.md) + +### [第八章 复用](book/08-Reuse.md) * [组合语法](book/08-Reuse.md#组合语法) * [继承语法](book/08-Reuse.md#继承语法) * [委托](book/08-Reuse.md#委托) @@ -89,14 +100,16 @@ * [final关键字](book/08-Reuse.md#final关键字) * [类初始化和加载](book/08-Reuse.md#类初始化和加载) * [本章小结](book/08-Reuse.md#本章小结) -## [第九章 多态](book/09-Polymorphism.md) + +### [第九章 多态](book/09-Polymorphism.md) * [向上转型回溯](book/09-Polymorphism.md#向上转型回溯) * [深入理解](book/09-Polymorphism.md#深入理解) * [构造器和多态](book/09-Polymorphism.md#构造器和多态) * [返回类型协变](book/09-Polymorphism.md#返回类型协变) * [使用继承设计](book/09-Polymorphism.md#使用继承设计) * [本章小结](book/09-Polymorphism.md#本章小结) -## [第十章 接口](book/10-Interfaces.md) + +### [第十章 接口](book/10-Interfaces.md) * [抽象类和方法](book/10-Interfaces.md#抽象类和方法) * [接口创建](book/10-Interfaces.md#接口创建) * [抽象类和接口](book/10-Interfaces.md#抽象类和接口) @@ -108,7 +121,8 @@ * [接口嵌套](book/10-Interfaces.md#接口嵌套) * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式) * [本章小结](book/10-Interfaces.md#本章小结) -## [第十一章 内部类](book/11-Inner-Classes.md) + +### [第十一章 内部类](book/11-Inner-Classes.md) * [创建内部类](book/11-Inner-Classes.md#创建内部类) * [链接外部类](book/11-Inner-Classes.md#链接外部类) * [内部类this和new的使用](book/11-Inner-Classes.md#内部类this和new的使用) @@ -122,7 +136,8 @@ * [内部类局部变量](book/11-Inner-Classes.md#内部类局部变量) * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) * [本章小结](book/11-Inner-Classes.md#本章小结) -## [第十二章 集合](book/12-Collections.md) + +### [第十二章 集合](book/12-Collections.md) * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合) * [基本概念](book/12-Collections.md#基本概念) * [添加元素组](book/12-Collections.md#添加元素组) @@ -137,7 +152,8 @@ * [集合与迭代器](book/12-Collections.md#集合与迭代器) * [for-in和迭代器](book/12-Collections.md#for-in和迭代器) * [本章小结](book/12-Collections.md#本章小结) -## [第十三章 函数式编程](book/13-Functional-Programming.md) + +### [第十三章 函数式编程](book/13-Functional-Programming.md) * [新旧对比](book/13-Functional-Programming.md#新旧对比) * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式) * [方法引用](book/13-Functional-Programming.md#方法引用) @@ -148,14 +164,16 @@ * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值) * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程) * [本章小结](book/13-Functional-Programming.md#本章小结) -## [第十四章 流式编程](book/14-Streams.md) + +### [第十四章 流式编程](book/14-Streams.md) * [流支持](book/14-Streams.md#流支持) * [流创建](book/14-Streams.md#流创建) * [中级流操作](book/14-Streams.md#中级流操作) * [Optional类](book/14-Streams.md#Optional类) * [终端操作](book/14-Streams.md#终端操作) * [本章小结](book/14-Streams.md#本章小结) -## [第十五章 异常](book/15-Exceptions.md) + +### [第十五章 异常](book/15-Exceptions.md) * [异常概念](book/15-Exceptions.md#异常概念) * [基本异常](book/15-Exceptions.md#基本异常) * [异常捕获](book/15-Exceptions.md#异常捕获) @@ -171,7 +189,8 @@ * [异常准则](book/15-Exceptions.md#异常准则) * [异常指南](book/15-Exceptions.md#异常指南) * [本章小结](book/15-Exceptions.md#本章小结) -## [第十六章 代码校验](book/16-Validating-Your-Code.md) + +### [第十六章 代码校验](book/16-Validating-Your-Code.md) * [测试](book/16-Validating-Your-Code.md#测试) * [前提条件](book/16-Validating-Your-Code.md#前提条件) * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) @@ -186,7 +205,8 @@ * [重构](book/16-Validating-Your-Code.md#重构) * [持续集成](book/16-Validating-Your-Code.md#持续集成) * [本章小结](book/16-Validating-Your-Code.md#本章小结) -## [第十七章 文件](book/17-Files.md) + +### [第十七章 文件](book/17-Files.md) * [文件和目录路径](book/17-Files.md#文件和目录路径) * [目录](book/17-Files.md#目录) * [文件系统](book/17-Files.md#文件系统) @@ -194,7 +214,8 @@ * [文件查找](book/17-Files.md#文件查找) * [文件读写](book/17-Files.md#文件读写) * [本章小结](book/17-Files.md#本章小结) -## [第十八章 字符串](book/18-Strings.md) + +### [第十八章 字符串](book/18-Strings.md) * [字符串的不可变](book/18-Strings.md#字符串的不可变) * [重载和StringBuilder](book/18-Strings.md#重载和StringBuilder) * [意外递归](book/18-Strings.md#意外递归) @@ -204,7 +225,8 @@ * [扫描输入](book/18-Strings.md#扫描输入) * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) * [本章小结](book/18-Strings.md#本章小结) -## [第十九章 类型信息](book/19-Type-Information.md) + +### [第十九章 类型信息](book/19-Type-Information.md) * [运行时类型信息](book/19-Type-Information.md#运行时类型信息) * [类的对象](book/19-Type-Information.md#类的对象) * [类型转换检测](book/19-Type-Information.md#类型转换检测) @@ -215,7 +237,8 @@ * [Optional类](book/19-Type-Information.md#Optional类) * [接口和类型](book/19-Type-Information.md#接口和类型) * [本章小结](book/19-Type-Information.md#本章小结) -## [第二十章 泛型](book/20-Generics.md) + +### [第二十章 泛型](book/20-Generics.md) * [简单泛型](book/20-Generics.md#简单泛型) * [泛型接口](book/20-Generics.md#泛型接口) * [泛型方法](book/20-Generics.md#泛型方法) @@ -233,7 +256,8 @@ * [补偿不足](book/20-Generics.md#补偿不足) * [辅助潜在类型](book/20-Generics.md#辅助潜在类型) * [泛型的优劣](book/20-Generics.md#泛型的优劣) -## [第二十一章 数组](book/21-Arrays.md) + +### [第二十一章 数组](book/21-Arrays.md) * [数组特性](book/21-Arrays.md#数组特性) * [一等对象](book/21-Arrays.md#一等对象) * [返回数组](book/21-Arrays.md#返回数组) @@ -254,7 +278,8 @@ * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) * [本章小结](book/21-Arrays.md#本章小结) -## [第二十二章 枚举](book/22-Enumerations.md) + +### [第二十二章 枚举](book/22-Enumerations.md) * [基本功能](book/22-Enumerations.md#基本功能) * [方法添加](book/22-Enumerations.md#方法添加) * [switch语句](book/22-Enumerations.md#switch语句) @@ -267,13 +292,15 @@ * [常量特定方法](book/22-Enumerations.md#常量特定方法) * [多次调度](book/22-Enumerations.md#多次调度) * [本章小结](book/22-Enumerations.md#本章小结) -## [第二十三章 注解](book/23-Annotations.md) + +### [第二十三章 注解](book/23-Annotations.md) * [基本语法](book/23-Annotations.md#基本语法) * [编写注解处理器](book/23-Annotations.md#编写注解处理器) * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解) * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试) * [本章小结](book/23-Annotations.md#本章小结) -## [第二十四章 并发编程](book/24-Concurrent-Programming.md) + +### [第二十四章 并发编程](book/24-Concurrent-Programming.md) * [术语问题](book/24-Concurrent-Programming.md#术语问题) * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) * [针对速度](book/24-Concurrent-Programming.md#针对速度) @@ -288,7 +315,8 @@ * [构造函数非线程安全](book/24-Concurrent-Programming.md#构造函数非线程安全) * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) * [本章小结](book/24-Concurrent-Programming.md#本章小结) -## [第二十五章 设计模式](book/25-Patterns.md) + +### [第二十五章 设计模式](book/25-Patterns.md) * [概念](book/25-Patterns.md#概念) * [构建型](book/25-Patterns.md#构建型) * [面向实施](book/25-Patterns.md#面向实施) @@ -305,21 +333,25 @@ * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) * [本章小结](book/25-Patterns.md#本章小结) -## [附录:补充](book/Appendix-Supplements.md) +### [附录:补充](book/Appendix-Supplements.md) * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) * [动手实践](book/Appendix-Supplements.md#动手实践) -## [附录:编程指南](book/Appendix-Programming-Guidelines.md) + +### [附录:编程指南](book/Appendix-Programming-Guidelines.md) * [设计](book/Appendix-Programming-Guidelines.md#设计) * [实现](book/Appendix-Programming-Guidelines.md#实现) -## [附录:文档注释](book/Appendix-Javadoc.md) -## [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) + +### [附录:文档注释](book/Appendix-Javadoc.md) + +### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) -## [附录:流式IO](book/Appendix-IO-Streams.md) + +### [附录:流式IO](book/Appendix-IO-Streams.md) * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) @@ -327,9 +359,11 @@ * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) * [本章小结](book/Appendix-IO-Streams.md#本章小结) -## [附录:标准IO](book/Appendix-Standard-IO.md) + +### [附录:标准IO](book/Appendix-Standard-IO.md) * [执行控制](book/Appendix-Standard-IO.md#执行控制) -## [附录:新IO](book/Appendix-New-IO.md) + +### [附录:新IO](book/Appendix-New-IO.md) * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) * [转换数据](book/Appendix-New-IO.md#数据转换) * [获取原始类型](book/Appendix-New-IO.md#基本类型获取) @@ -337,11 +371,13 @@ * [使用缓冲区进行数据操作](book/Appendix-New-IO.md#缓冲区数据操作) * [内存映射文件](book/Appendix-New-IO.md#内存映射文件) * [文件锁定](book/Appendix-New-IO.md#文件锁定) -## [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) + +### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) * [equals典范](book/Appendix-Understanding-equals-and-hashCode.md#equals典范) * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) * [调整HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap) -## [附录:集合主题](book/Appendix-Collection-Topics.md) + +### [附录:集合主题](book/Appendix-Collection-Topics.md) * [示例数据](book/Appendix-Collection-Topics.md#示例数据) * [List表现](book/Appendix-Collection-Topics.md#List表现) * [Set表现](book/Appendix-Collection-Topics.md#Set表现) @@ -358,7 +394,8 @@ * [持有引用](book/Appendix-Collection-Topics.md#持有引用) * [避免旧式类库](book/Appendix-Collection-Topics.md#避免旧式类库) * [本章小结](book/Appendix-Collection-Topics.md#本章小结) -## [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) + +### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) * [线程](book/Appendix-Low-Level-Concurrency.md#线程) * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) @@ -367,26 +404,32 @@ * [关键部分](book/Appendix-Low-Level-Concurrency.md#关键部分) * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) -## [附录:数据压缩](book/Appendix-Data-Compression.md) + +### [附录:数据压缩](book/Appendix-Data-Compression.md) * [使用Gzip简单压缩](book/Appendix-Data-Compression.md#使用Gzip简单压缩) * [使用zip多文件存储](book/Appendix-Data-Compression.md#使用zip多文件存储) * [Java的jar](book/Appendix-Data-Compression.md#Java的jar) -## [附录:对象序列化](book/Appendix-Object-Serialization.md) + +### [附录:对象序列化](book/Appendix-Object-Serialization.md) * [查找类](book/Appendix-Object-Serialization.md#查找类) * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) -## [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) + +### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) -## [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -## [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) + +### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) + +### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) -## [词汇表](book/GLOSSARY.md) + +### [词汇表](book/GLOSSARY.md) From b393994e298146d85714762cb96d34c7e7b9a3bf Mon Sep 17 00:00:00 2001 From: Lane Date: Mon, 14 Sep 2020 16:06:40 +0800 Subject: [PATCH 310/371] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E5=92=8C=E6=AD=A3?= =?UTF-8?q?=E6=96=87=E7=9B=B8=E5=AF=B9=E5=BA=94=E4=BB=A5=E4=BE=BF=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=20(#575)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `中级流操作` => `中间操作` --- docs/sidebar.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sidebar.md b/docs/sidebar.md index e79c07a0..dbde9004 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -168,7 +168,7 @@ ### [第十四章 流式编程](book/14-Streams.md) * [流支持](book/14-Streams.md#流支持) * [流创建](book/14-Streams.md#流创建) - * [中级流操作](book/14-Streams.md#中级流操作) + * [中间操作](book/14-Streams.md#中间操作) * [Optional类](book/14-Streams.md#Optional类) * [终端操作](book/14-Streams.md#终端操作) * [本章小结](book/14-Streams.md#本章小结) From 897c0affba7d211378177a7e6dac6d53106c5737 Mon Sep 17 00:00:00 2001 From: FengBaoheng Date: Mon, 14 Sep 2020 17:17:48 +0800 Subject: [PATCH 311/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=83=A8=E5=88=86?= =?UTF-8?q?=E9=94=99=E5=88=AB=E5=AD=97=E5=92=8C=E8=AF=AD=E5=BA=8F=20(#576)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix typo 本章小节->本章小结 * fix typos on 20-Generics 修正20章的打字错误 * 添加示例代码链接 * 修改了部分错别字,调整语序 --- docs/book/23-Annotations.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 95b79088..1b585691 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -16,7 +16,7 @@ - **@Deprecated**:如果使用该注解的元素被调用,编译器就会发出警告信息。 - **@SuppressWarnings**:关闭不当的编译器警告信息。 - **@SafeVarargs**:在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。 -- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口 +- **@FunctionalInterface**:Java 8 中加入用于表示类型声明为函数式接口。 还有 5 种额外的注解类型用于创造新的注解。你将会在这一章学习它们。 @@ -45,7 +45,7 @@ public class Testable { } ``` -被注解标注的方法和其他的方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解的使用方式和修饰符的使用方式一致。 +被注解标注的方法和其他方法没有任何区别。在这个例子中,注解 `@Test` 可以和任何修饰符共同用于方法,诸如 **public**、**static** 或 **void**。从语法的角度上看,注解和修饰符的使用方式是一致的。 ### 定义注解 @@ -61,7 +61,7 @@ import java.lang.annotation.*; public @interface Test {} ``` -除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annoation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。 +除了 @ 符号之外, `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解(meta-annotation),比如 `@Target` 和 `@Retention`。`@Target` 定义你的注解可以应用在哪里(例如是方法还是字段)。`@Retention` 定义了注解在哪里可用,在源代码中(SOURCE),class文件(CLASS)中或者是在运行时(RUNTIME)。 注解通常会包含一些表示特定值的元素。当分析处理注解的时候,程序或工具可以利用这些值。注解的元素看起来就像接口的方法,但是可以为其指定默认值。 @@ -206,7 +206,7 @@ public @interface SimulatingNull { ### 生成外部文件 -当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。 +当有些框架需要一些额外的信息才能与你的源代码协同工作,这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术,每一个 Bean 都需要大量的接口和部署描述文件,而这些就是“样板”文件。Web Service,自定义标签库以及对象/关系映射工具(例如 Toplink 和 Hibernate)通常都需要 XML 描述文件,而这些文件脱离于代码之外。除了定义 Java 类,程序员还必须忍受沉闷,重复的提供某些信息,例如类名和包名等已经在原始类中提供过的信息。每当你使用外部描述文件时,他就拥有了一个类的两个独立信息源,这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时,也必须知道如何编辑描述文件。 假设你想提供一些基本的对象/关系映射功能,能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是,通过使用注解,你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。 @@ -306,7 +306,7 @@ public class Member { } ``` -类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: +类注解 **@DBTable** 注解给定了元素值 MEMBER,它将会作为表的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方:首先,他们都使用了嵌入的 **@Constraints** 注解的默认值;其次,它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素,并且在使用该注解时,**value** 为唯一一个需要赋值的元素,你就不需要使用名—值对的语法,你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**,不过在上面的例子中,这样的注解语句也更易于理解: ```java @SQLString(30) @@ -320,7 +320,7 @@ public class Member { 可以使用多种不同的方式来定义自己的注解用于上述任务。例如,你可以使用一个单一的注解类 **@TableColumn**,它拥有一个 **enum** 元素,元素值定义了 **STRING**,**INTEGER**,**FLOAT** 等类型。这消除了每个 SQL 类型都需要定义一个 **@interface** 的负担,不过也使得用额外信息修饰 SQL 类型变的不可能,这些额外的信息例如长度或精度等,都可能是非常有用的。 -你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解助力器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。 +你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型,比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型,但是这也将 Java 类型到 SQL 类型的映射绑在了一起,这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码;如果我们只需要告诉注解处理器,我们正在使用的是什么“口味(favor)”的 SQL,然后注解处理器来为我们处理 SQL 类型的细节,那将是一个优雅的设计。 第三种可行的方案是一起使用两个注解,**@Constraints** 和相应的 SQL 类型(例如,**@SQLInteger**)去注解同一个字段。这可能会让代码有些混乱,但是编译器允许你对同一个目标使用多个注解。在 Java 8,在使用多个注解的时候,你可以重复使用同一个注解。 @@ -459,7 +459,7 @@ CREATE TABLE MEMBER( 每一个你编写的注解都需要处理器,但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类,并且你可以添加监听器用于监听注解处理完成后接到通知。 -本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和StackOverflow 的准备。 +本节中的示例将帮助你开始学习,但如果你必须深入学习,请做好反复学习,大量访问 Google 和 StackOverflow 的准备。 ### 最简单的处理器 @@ -576,11 +576,11 @@ public class SimpleProcessor } ``` -(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的示例关于注解如何简化你的代码)。 +(旧的,失效的)**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过,你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解(这是一个很好的用注解简化代码的示例)。 -你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。 +你唯一需要实现的方法就是 `process()`,这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的,第二个参数保留了剩余信息。我们所做的事情只是打印了注解(这里只存在一个),可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作,我们循环所有被 **@Simple** 注解的元素,并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了自身的基本信息;例如,`getModifiers()` 告诉你它是否为 **public** 和 **static** 。 -**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 +**Element** 只能执行那些编译器解析的所有基本对象共有的操作,而类和方法之类的东西有额外的信息需要提取。所以(如果你阅读了正确的文档,但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索)你检查它是哪种 **ElementKind**,然后将其向下转换为更具体的元素类型,注入针对 CLASS 的 TypeElement 和针对 METHOD 的ExecutableElement。此时,可以为这些元素调用其他方法。 动态向下转型(在编译期不进行检查)并不像是 Java 的做事方式,这非常不直观这也是为什么我从未想过要这样做事。相反,我花了好几天的时间,试图发现你应该如何访问这些信息,而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式,但在我看来是。 From cd32cda6ae05d325051a4c3fa3dfff63014d4a19 Mon Sep 17 00:00:00 2001 From: Hongkuan Wang Date: Tue, 15 Sep 2020 05:42:14 +0300 Subject: [PATCH 312/371] Fix typo (#578) --- docs/book/Appendix-Understanding-equals-and-hashCode.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/book/Appendix-Understanding-equals-and-hashCode.md b/docs/book/Appendix-Understanding-equals-and-hashCode.md index 9cfeb1d5..01889642 100644 --- a/docs/book/Appendix-Understanding-equals-and-hashCode.md +++ b/docs/book/Appendix-Understanding-equals-and-hashCode.md @@ -69,7 +69,8 @@ import java.util.*; public class Equality { protected int i; protected String s; - protected double d;public Equality(int i, String s, double d) { + protected double d; + public Equality(int i, String s, double d) { this.i = i; this.s = s; this.d = d; @@ -141,7 +142,7 @@ Expected false, got false **testAll()** 执行了我们期望的所有不同类型对象的比较。它使用工厂创建了**Equality**对象。 -在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示**make()**函数。 +在 **main()** 里,请注意对 **testAll()** 的调用很简单。因为**EqualityFactory**有着单一的函数,它能够和lambda表达式一起使用来表示 **make()** 函数。 上述的 **equals()** 函数非常繁琐,并且我们能够将其简化成规范的形式,请注意: 1. **instanceof**检查减少了**null**检查的需要。 From 44a3892ec6397e5030bace80933eec9b516589a3 Mon Sep 17 00:00:00 2001 From: Lane Date: Tue, 15 Sep 2020 10:58:46 +0800 Subject: [PATCH 313/371] rebuild sidebar.md and SUMMARY.md (#580) --- SUMMARY.md | 860 +++++++++++++------------ docs/book/05-Control-Flow.md | 2 +- docs/book/11-Inner-Classes.md | 0 docs/book/18-Strings.md | 2 +- docs/book/19-Type-Information.md | 2 +- docs/book/21-Arrays.md | 2 +- docs/book/24-Concurrent-Programming.md | 2 +- docs/book/Appendix-Standard-IO.md | 2 +- docs/index.html | 32 +- docs/sidebar.md | 249 +++---- 10 files changed, 600 insertions(+), 553 deletions(-) mode change 100644 => 100755 docs/book/05-Control-Flow.md mode change 100644 => 100755 docs/book/11-Inner-Classes.md mode change 100644 => 100755 docs/book/18-Strings.md mode change 100644 => 100755 docs/book/19-Type-Information.md mode change 100644 => 100755 docs/book/21-Arrays.md mode change 100644 => 100755 docs/book/24-Concurrent-Programming.md mode change 100644 => 100755 docs/book/Appendix-Standard-IO.md mode change 100644 => 100755 docs/index.html diff --git a/SUMMARY.md b/SUMMARY.md index a15b48fc..5f2b8733 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,414 +1,448 @@ -# Summary - -* [Introduction](README.md) -* [译者的话](docs/README.md) -* [封面](docs/book/00-On-Java-8.md) -* [前言](docs/book/00-Preface.md) - * [教学目标](docs/book/00-Preface.md#教学目标) - * [语言设计错误](docs/book/00-Preface.md#语言设计错误) - * [测试用例](docs/book/00-Preface.md#测试用例) - * [普及性](docs/book/00-Preface.md#普及性) - * [关于安卓](docs/book/00-Preface.md#关于安卓) - * [电子版权声明](docs/book/00-Preface.md#电子版权声明) - * [版本说明](docs/book/00-Preface.md#版本说明) - * [封面设计](docs/book/00-Preface.md#封面设计) - * [感谢的人](docs/book/00-Preface.md#感谢的人) - * [献礼](docs/book/00-Preface.md#献礼) -* [简介](docs/book/00-Introduction.md) - * [前提条件](docs/book/00-Introduction.md#前提条件) - * [JDK文档](docs/book/00-Introduction.md#JDK文档) - * [C编程思想](docs/book/00-Introduction.md#C编程思想) - * [源码下载](docs/book/00-Introduction.md#源码下载) - * [编码样式](docs/book/00-Introduction.md#编码样式) - * [BUG提交](docs/book/00-Introduction.md#BUG提交) - * [邮箱订阅](docs/book/00-Introduction.md#邮箱订阅) - * [Java图形界面](docs/book/00-Introduction.md#Java图形界面) - -* [第一章 对象的概念](docs/book/01-What-is-an-Object.md) - * [抽象](docs/book/01-What-is-an-Object.md#抽象) - * [接口](docs/book/01-What-is-an-Object.md#接口) - * [服务提供](docs/book/01-What-is-an-Object.md#服务提供) - * [封装](docs/book/01-What-is-an-Object.md#封装) - * [复用](docs/book/01-What-is-an-Object.md#复用) - * [继承](docs/book/01-What-is-an-Object.md#继承) - * [多态](docs/book/01-What-is-an-Object.md#多态) - * [单继承](docs/book/01-What-is-an-Object.md#单继承) - * [集合](docs/book/01-What-is-an-Object.md#集合) - * [生命周期](docs/book/01-What-is-an-Object.md#生命周期) - * [异常处理](docs/book/01-What-is-an-Object.md#异常处理) - * [本章小结](docs/book/01-What-is-an-Object.md#本章小结) -* [第二章 安装Java和本书用例](docs/book/02-Installing-Java-and-the-Book-Examples.md) - * [编辑器](docs/book/02-Installing-Java-and-the-Book-Examples.md#编辑器) - * [Shell](docs/book/02-Installing-Java-and-the-Book-Examples.md#Shell) - * [Java安装](docs/book/02-Installing-Java-and-the-Book-Examples.md#Java安装) - * [校验安装](docs/book/02-Installing-Java-and-the-Book-Examples.md#校验安装) - * [安装和运行代码示例](docs/book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) -* [第三章 万物皆对象](docs/book/03-Objects-Everywhere.md) - * [对象操纵](docs/book/03-Objects-Everywhere.md#对象操纵) - * [对象创建](docs/book/03-Objects-Everywhere.md#对象创建) - * [代码注释](docs/book/03-Objects-Everywhere.md#代码注释) - * [对象清理](docs/book/03-Objects-Everywhere.md#对象清理) - * [类的创建](docs/book/03-Objects-Everywhere.md#类的创建) - * [程序编写](docs/book/03-Objects-Everywhere.md#程序编写) - * [小试牛刀](docs/book/03-Objects-Everywhere.md#小试牛刀) - * [编码风格](docs/book/03-Objects-Everywhere.md#编码风格) - * [本章小结](docs/book/03-Objects-Everywhere.md#本章小结) -* [第四章 运算符](docs/book/04-Operators.md) - * [使用说明](docs/book/04-Operators.md#使用说明) - * [优先级](docs/book/04-Operators.md#优先级) - * [赋值](docs/book/04-Operators.md#赋值) - * [算术运算符](docs/book/04-Operators.md#算术运算符) - * [递增和递减](docs/book/04-Operators.md#递增和递减) - * [关系运算符](docs/book/04-Operators.md#关系运算符) - * [逻辑运算符](docs/book/04-Operators.md#逻辑运算符) - * [字面值常量](docs/book/04-Operators.md#字面值常量) - * [按位运算符](docs/book/04-Operators.md#按位运算符) - * [移位运算符](docs/book/04-Operators.md#移位运算符) - * [三元运算符](docs/book/04-Operators.md#三元运算符) - * [字符串运算符](docs/book/04-Operators.md#字符串运算符) - * [常见陷阱](docs/book/04-Operators.md#常见陷阱) - * [类型转换](docs/book/04-Operators.md#类型转换) - * [Java没有sizeof](docs/book/04-Operators.md#Java没有sizeof) - * [运算符总结](docs/book/04-Operators.md#运算符总结) - * [本章小结](docs/book/04-Operators.md#本章小结) -* [第五章 控制流](docs/book/05-Control-Flow.md) - * [true和false](docs/book/05-Control-Flow.md#true和false) - * [if-else](docs/book/05-Control-Flow.md#if-else) - * [迭代语句](docs/book/05-Control-Flow.md#迭代语句) - * [for-in语法](docs/book/05-Control-Flow.md#for-in语法) - * [return](docs/book/05-Control-Flow.md#return) - * [break和continue](docs/book/05-Control-Flow.md#break和continue) - * [臭名昭著的goto](docs/book/05-Control-Flow.md#臭名昭著的goto) - * [switch](docs/book/05-Control-Flow.md#switch) - * [switch字符串](docs/book/05-Control-Flow.md#switch字符串) - * [本章小结](docs/book/05-Control-Flow.md#本章小结) -* [第六章 初始化和清理](docs/book/06-Housekeeping.md) - * [利用构造器保证初始化](docs/book/06-Housekeeping.md#利用构造器保证初始化) - * [方法重载](docs/book/06-Housekeeping.md#方法重载) - * [无参构造器](docs/book/06-Housekeeping.md#无参构造器) - * [this关键字](docs/book/06-Housekeeping.md#this关键字) - * [垃圾回收器](docs/book/06-Housekeeping.md#垃圾回收器) - * [成员初始化](docs/book/06-Housekeeping.md#成员初始化) - * [构造器初始化](docs/book/06-Housekeeping.md#构造器初始化) - * [数组初始化](docs/book/06-Housekeeping.md#数组初始化) - * [枚举类型](docs/book/06-Housekeeping.md#枚举类型) - * [本章小结](docs/book/06-Housekeeping.md#本章小结) -* [第七章 封装](docs/book/07-Implementation-Hiding.md) - * [包的概念](docs/book/07-Implementation-Hiding.md#包的概念) - * [访问权限修饰符](docs/book/07-Implementation-Hiding.md#访问权限修饰符) - * [接口和实现](docs/book/07-Implementation-Hiding.md#接口和实现) - * [类访问权限](docs/book/07-Implementation-Hiding.md#类访问权限) - * [本章小结](docs/book/07-Implementation-Hiding.md#本章小结) -* [第八章 复用](docs/book/08-Reuse.md) - * [组合语法](docs/book/08-Reuse.md#组合语法) - * [继承语法](docs/book/08-Reuse.md#继承语法) - * [委托](docs/book/08-Reuse.md#委托) - * [结合组合与继承](docs/book/08-Reuse.md#结合组合与继承) - * [组合与继承的选择](docs/book/08-Reuse.md#组合与继承的选择) - * [protected](docs/book/08-Reuse.md#protected) - * [向上转型](docs/book/08-Reuse.md#向上转型) - * [final关键字](docs/book/08-Reuse.md#final关键字) - * [类初始化和加载](docs/book/08-Reuse.md#类初始化和加载) - * [本章小结](docs/book/08-Reuse.md#本章小结) -* [第九章 多态](docs/book/09-Polymorphism.md) - * [向上转型回溯](docs/book/09-Polymorphism.md#向上转型回溯) - * [深入理解](docs/book/09-Polymorphism.md#深入理解) - * [构造器和多态](docs/book/09-Polymorphism.md#构造器和多态) - * [返回类型协变](docs/book/09-Polymorphism.md#返回类型协变) - * [使用继承设计](docs/book/09-Polymorphism.md#使用继承设计) - * [本章小结](docs/book/09-Polymorphism.md#本章小结) -* [第十章 接口](docs/book/10-Interfaces.md) - * [抽象类和方法](docs/book/10-Interfaces.md#抽象类和方法) - * [接口创建](docs/book/10-Interfaces.md#接口创建) - * [抽象类和接口](docs/book/10-Interfaces.md#抽象类和接口) - * [完全解耦](docs/book/10-Interfaces.md#完全解耦) - * [多接口结合](docs/book/10-Interfaces.md#多接口结合) - * [使用继承扩展接口](docs/book/10-Interfaces.md#使用继承扩展接口) - * [接口适配](docs/book/10-Interfaces.md#接口适配) - * [接口字段](docs/book/10-Interfaces.md#接口字段) - * [接口嵌套](docs/book/10-Interfaces.md#接口嵌套) - * [接口和工厂方法模式](docs/book/10-Interfaces.md#接口和工厂方法模式) - * [本章小结](docs/book/10-Interfaces.md#本章小结) -* [第十一章 内部类](docs/book/11-Inner-Classes.md) - * [创建内部类](docs/book/11-Inner-Classes.md#创建内部类) - * [链接外部类](docs/book/11-Inner-Classes.md#链接外部类) - * [内部类this和new的使用](docs/book/11-Inner-Classes.md#内部类this和new的使用) - * [内部类向上转型](docs/book/11-Inner-Classes.md#内部类向上转型) - * [内部类方法和作用域](docs/book/11-Inner-Classes.md#内部类方法和作用域) - * [匿名内部类](docs/book/11-Inner-Classes.md#匿名内部类) - * [嵌套类](docs/book/11-Inner-Classes.md#嵌套类) - * [为什么需要内部类](docs/book/11-Inner-Classes.md#为什么需要内部类) - * [继承内部类](docs/book/11-Inner-Classes.md#继承内部类) - * [重写内部类](docs/book/11-Inner-Classes.md#重写内部类) - * [内部类局部变量](docs/book/11-Inner-Classes.md#内部类局部变量) - * [内部类标识符](docs/book/11-Inner-Classes.md#内部类标识符) - * [本章小结](docs/book/11-Inner-Classes.md#本章小结) -* [第十二章 集合](docs/book/12-Collections.md) - * [泛型和类型安全的集合](docs/book/12-Collections.md#泛型和类型安全的集合) - * [基本概念](docs/book/12-Collections.md#基本概念) - * [添加元素组](docs/book/12-Collections.md#添加元素组) - * [集合的打印](docs/book/12-Collections.md#集合的打印) - * [列表List](docs/book/12-Collections.md#列表List) - * [迭代器Iterators](docs/book/12-Collections.md#迭代器Iterators) - * [链表LinkedList](docs/book/12-Collections.md#链表LinkedList) - * [堆栈Stack](docs/book/12-Collections.md#堆栈Stack) - * [集合Set](docs/book/12-Collections.md#集合Set) - * [映射Map](docs/book/12-Collections.md#映射Map) - * [队列Queue](docs/book/12-Collections.md#队列Queue) - * [集合与迭代器](docs/book/12-Collections.md#集合与迭代器) - * [for-in和迭代器](docs/book/12-Collections.md#for-in和迭代器) - * [本章小结](docs/book/12-Collections.md#本章小结) -* [第十三章 函数式编程](docs/book/13-Functional-Programming.md) - * [新旧对比](docs/book/13-Functional-Programming.md#新旧对比) - * [Lambda表达式](docs/book/13-Functional-Programming.md#Lambda表达式) - * [方法引用](docs/book/13-Functional-Programming.md#方法引用) - * [函数式接口](docs/book/13-Functional-Programming.md#函数式接口) - * [高阶函数](docs/book/13-Functional-Programming.md#高阶函数) - * [闭包](docs/book/13-Functional-Programming.md#闭包) - * [函数组合](docs/book/13-Functional-Programming.md#函数组合) - * [柯里化和部分求值](docs/book/13-Functional-Programming.md#柯里化和部分求值) - * [纯函数式编程](docs/book/13-Functional-Programming.md#纯函数式编程) - * [本章小结](docs/book/13-Functional-Programming.md#本章小结) -* [第十四章 流式编程](docs/book/14-Streams.md) - * [流支持](docs/book/14-Streams.md#流支持) - * [流创建](docs/book/14-Streams.md#流创建) - * [中级流操作](docs/book/14-Streams.md#中级流操作) - * [Optional类](docs/book/14-Streams.md#Optional类) - * [终端操作](docs/book/14-Streams.md#终端操作) - * [本章小结](docs/book/14-Streams.md#本章小结) -* [第十五章 异常](docs/book/15-Exceptions.md) - * [异常概念](docs/book/15-Exceptions.md#异常概念) - * [基本异常](docs/book/15-Exceptions.md#基本异常) - * [异常捕获](docs/book/15-Exceptions.md#异常捕获) - * [自定义异常](docs/book/15-Exceptions.md#自定义异常) - * [异常规范](docs/book/15-Exceptions.md#异常规范) - * [任意异常捕获](docs/book/15-Exceptions.md#任意异常捕获) - * [Java标准异常](docs/book/15-Exceptions.md#Java标准异常) - * [finally关键字](docs/book/15-Exceptions.md#finally关键字) - * [异常限制](docs/book/15-Exceptions.md#异常限制) - * [异常构造](docs/book/15-Exceptions.md#异常构造) - * [Try-With-Resources用法](docs/book/15-Exceptions.md#Try-With-Resources用法) - * [异常匹配](docs/book/15-Exceptions.md#异常匹配) - * [异常准则](docs/book/15-Exceptions.md#异常准则) - * [异常指南](docs/book/15-Exceptions.md#异常指南) - * [本章小结](docs/book/15-Exceptions.md#本章小结) -* [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) - * [测试](docs/book/16-Validating-Your-Code.md#测试) - * [前提条件](docs/book/16-Validating-Your-Code.md#前提条件) - * [测试驱动开发](docs/book/16-Validating-Your-Code.md#测试驱动开发) - * [日志](docs/book/16-Validating-Your-Code.md#日志) - * [调试](docs/book/16-Validating-Your-Code.md#调试) - * [基准测试](docs/book/16-Validating-Your-Code.md#基准测试) - * [分析和优化](docs/book/16-Validating-Your-Code.md#分析和优化) - * [风格检测](docs/book/16-Validating-Your-Code.md#风格检测) - * [静态错误分析](docs/book/16-Validating-Your-Code.md#静态错误分析) - * [代码重审](docs/book/16-Validating-Your-Code.md#代码重审) - * [结对编程](docs/book/16-Validating-Your-Code.md#结对编程) - * [重构](docs/book/16-Validating-Your-Code.md#重构) - * [持续集成](docs/book/16-Validating-Your-Code.md#持续集成) - * [本章小结](docs/book/16-Validating-Your-Code.md#本章小结) -* [第十七章 文件](docs/book/17-Files.md) - * [文件和目录路径](docs/book/17-Files.md#文件和目录路径) - * [目录](docs/book/17-Files.md#目录) - * [文件系统](docs/book/17-Files.md#文件系统) - * [路径监听](docs/book/17-Files.md#路径监听) - * [文件查找](docs/book/17-Files.md#文件查找) - * [文件读写](docs/book/17-Files.md#文件读写) - * [本章小结](docs/book/17-Files.md#本章小结) -* [第十八章 字符串](docs/book/18-Strings.md) - * [字符串的不可变](docs/book/18-Strings.md#字符串的不可变) - * [重载和StringBuilder](docs/book/18-Strings.md#重载和StringBuilder) - * [意外递归](docs/book/18-Strings.md#意外递归) - * [字符串操作](docs/book/18-Strings.md#字符串操作) - * [格式化输出](docs/book/18-Strings.md#格式化输出) - * [常规表达式](docs/book/18-Strings.md#常规表达式) - * [扫描输入](docs/book/18-Strings.md#扫描输入) - * [StringTokenizer类](docs/book/18-Strings.md#StringTokenizer类) - * [本章小结](docs/book/18-Strings.md#本章小结) -* [第十九章 类型信息](docs/book/19-Type-Information.md) - * [运行时类型信息](docs/book/19-Type-Information.md#运行时类型信息) - * [类的对象](docs/book/19-Type-Information.md#类的对象) - * [类型转换检测](docs/book/19-Type-Information.md#类型转换检测) - * [注册工厂](docs/book/19-Type-Information.md#注册工厂) - * [类的等价比较](docs/book/19-Type-Information.md#类的等价比较) - * [反射运行时类信息](docs/book/19-Type-Information.md#反射运行时类信息) - * [动态代理](docs/book/19-Type-Information.md#动态代理) - * [Optional类](docs/book/19-Type-Information.md#Optional类) - * [接口和类型](docs/book/19-Type-Information.md#接口和类型) - * [本章小结](docs/book/19-Type-Information.md#本章小结) -* [第二十章 泛型](docs/book/20-Generics.md) - * [简单泛型](docs/book/20-Generics.md#简单泛型) - * [泛型接口](docs/book/20-Generics.md#泛型接口) - * [泛型方法](docs/book/20-Generics.md#泛型方法) - * [复杂模型构建](docs/book/20-Generics.md#复杂模型构建) - * [泛型擦除](docs/book/20-Generics.md#泛型擦除) - * [补偿擦除](docs/book/20-Generics.md#补偿擦除) - * [边界](docs/book/20-Generics.md#边界) - * [通配符](docs/book/20-Generics.md#通配符) - * [问题](docs/book/20-Generics.md#问题) - * [自我约束类型](docs/book/20-Generics.md#自我约束类型) - * [动态类型安全](docs/book/20-Generics.md#动态类型安全) - * [泛型异常](docs/book/20-Generics.md#泛型异常) - * [混入](docs/book/20-Generics.md#混入) - * [潜在类型](docs/book/20-Generics.md#潜在类型) - * [补偿不足](docs/book/20-Generics.md#补偿不足) - * [辅助潜在类型](docs/book/20-Generics.md#辅助潜在类型) - * [泛型的优劣](docs/book/20-Generics.md#泛型的优劣) -* [第二十一章 数组](docs/book/21-Arrays.md) - * [数组特性](docs/book/21-Arrays.md#数组特性) - * [一等对象](docs/book/21-Arrays.md#一等对象) - * [返回数组](docs/book/21-Arrays.md#返回数组) - * [多维数组](docs/book/21-Arrays.md#多维数组) - * [泛型数组](docs/book/21-Arrays.md#泛型数组) - * [Arrays的fill方法](docs/book/21-Arrays.md#Arrays的fill方法) - * [Arrays的setAll方法](docs/book/21-Arrays.md#Arrays的setAll方法) - * [增量生成](docs/book/21-Arrays.md#增量生成) - * [随机生成](docs/book/21-Arrays.md#随机生成) - * [泛型和基本数组](docs/book/21-Arrays.md#泛型和基本数组) - * [数组元素修改](docs/book/21-Arrays.md#数组元素修改) - * [数组并行](docs/book/21-Arrays.md#数组并行) - * [Arrays工具类](docs/book/21-Arrays.md#Arrays工具类) - * [数组拷贝](docs/book/21-Arrays.md#数组拷贝) - * [数组比较](docs/book/21-Arrays.md#数组比较) - * [流和数组](docs/book/21-Arrays.md#流和数组) - * [数组排序](docs/book/21-Arrays.md#数组排序) - * [binarySearch二分查找](docs/book/21-Arrays.md#binarySearch二分查找) - * [parallelPrefix并行前缀](docs/book/21-Arrays.md#parallelPrefix并行前缀) - * [本章小结](docs/book/21-Arrays.md#本章小结) -* [第二十二章 枚举](docs/book/22-Enumerations.md) - * [基本功能](docs/book/22-Enumerations.md#基本功能) - * [方法添加](docs/book/22-Enumerations.md#方法添加) - * [switch语句](docs/book/22-Enumerations.md#switch语句) - * [values方法](docs/book/22-Enumerations.md#values方法) - * [实现而非继承](docs/book/22-Enumerations.md#实现而非继承) - * [随机选择](docs/book/22-Enumerations.md#随机选择) - * [使用接口组织](docs/book/22-Enumerations.md#使用接口组织) - * [使用EnumSet替代Flags](docs/book/22-Enumerations.md#使用EnumSet替代Flags) - * [使用EnumMap](docs/book/22-Enumerations.md#使用EnumMap) - * [常量特定方法](docs/book/22-Enumerations.md#常量特定方法) - * [多次调度](docs/book/22-Enumerations.md#多次调度) - * [本章小结](docs/book/22-Enumerations.md#本章小结) -* [第二十三章 注解](docs/book/23-Annotations.md) - * [基本语法](docs/book/23-Annotations.md#基本语法) - * [编写注解处理器](docs/book/23-Annotations.md#编写注解处理器) - * [使用javac处理注解](docs/book/23-Annotations.md#使用javac处理注解) - * [基于注解的单元测试](docs/book/23-Annotations.md#基于注解的单元测试) - * [本章小结](docs/book/23-Annotations.md#本章小结) -* [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) - * [术语问题](docs/book/24-Concurrent-Programming.md#术语问题) - * [并发的超能力](docs/book/24-Concurrent-Programming.md#并发的超能力) - * [针对速度](docs/book/24-Concurrent-Programming.md#针对速度) - * [四句格言](docs/book/24-Concurrent-Programming.md#四句格言) - * [残酷的真相](docs/book/24-Concurrent-Programming.md#残酷的真相) - * [本章其余部分](docs/book/24-Concurrent-Programming.md#本章其余部分) - * [并行流](docs/book/24-Concurrent-Programming.md#并行流) - * [创建和运行任务](docs/book/24-Concurrent-Programming.md#创建和运行任务) - * [终止耗时任务](docs/book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture类](docs/book/24-Concurrent-Programming.md#CompletableFuture类) - * [死锁](docs/book/24-Concurrent-Programming.md#死锁) - * [构造函数非线程安全](docs/book/24-Concurrent-Programming.md#构造函数非线程安全) - * [复杂性和代价](docs/book/24-Concurrent-Programming.md#复杂性和代价) - * [本章小结](docs/book/24-Concurrent-Programming.md#本章小结) -* [第二十五章 设计模式](docs/book/25-Patterns.md) - * [概念](docs/book/25-Patterns.md#概念) - * [构建型](docs/book/25-Patterns.md#构建型) - * [面向实施](docs/book/25-Patterns.md#面向实施) - * [工厂模式](docs/book/25-Patterns.md#工厂模式) - * [函数对象](docs/book/25-Patterns.md#函数对象) - * [接口改变](docs/book/25-Patterns.md#接口改变) - * [解释器](docs/book/25-Patterns.md#解释器) - * [回调](docs/book/25-Patterns.md#回调) - * [多次调度](docs/book/25-Patterns.md#多次调度) - * [模式重构](docs/book/25-Patterns.md#模式重构) - * [抽象用法](docs/book/25-Patterns.md#抽象用法) - * [多次派遣](docs/book/25-Patterns.md#多次派遣) - * [访问者模式](docs/book/25-Patterns.md#访问者模式) - * [RTTI的优劣](docs/book/25-Patterns.md#RTTI的优劣) - * [本章小结](docs/book/25-Patterns.md#本章小结) - -* [附录:补充](docs/book/Appendix-Supplements.md) - * [可下载的补充](docs/book/Appendix-Supplements.md#可下载的补充) - * [通过Thinking-in-C来巩固Java基础](docs/book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) - * [动手实践](docs/book/Appendix-Supplements.md#动手实践) -* [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) - * [设计](docs/book/Appendix-Programming-Guidelines.md#设计) - * [实现](docs/book/Appendix-Programming-Guidelines.md#实现) -* [附录:文档注释](docs/book/Appendix-Javadoc.md) -* [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) - * [传递引用](docs/book/Appendix-Passing-and-Returning-Objects.md#传递引用) - * [本地拷贝](docs/book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) - * [控制克隆](docs/book/Appendix-Passing-and-Returning-Objects.md#控制克隆) - * [不可变类](docs/book/Appendix-Passing-and-Returning-Objects.md#不可变类) - * [本章小结](docs/book/Appendix-Passing-and-Returning-Objects.md#本章小结) -* [附录:流式IO](docs/book/Appendix-IO-Streams.md) - * [输入流类型](docs/book/Appendix-IO-Streams.md#输入流类型) - * [输出流类型](docs/book/Appendix-IO-Streams.md#输出流类型) - * [添加属性和有用的接口](docs/book/Appendix-IO-Streams.md#添加属性和有用的接口) - * [Reader和Writer](docs/book/Appendix-IO-Streams.md#Reader和Writer) - * [RandomAccessFile类](docs/book/Appendix-IO-Streams.md#RandomAccessFile类) - * [IO流典型用途](docs/book/Appendix-IO-Streams.md#IO流典型用途) - * [本章小结](docs/book/Appendix-IO-Streams.md#本章小结) -* [附录:标准IO](docs/book/Appendix-Standard-IO.md) - * [执行控制](docs/book/Appendix-Standard-IO.md#执行控制) -* [附录:新IO](docs/book/Appendix-New-IO.md) - * [ByteBuffer](docs/book/Appendix-New-IO.md#ByteBuffer) - * [转换数据](docs/book/Appendix-New-IO.md#数据转换) - * [获取原始类型](docs/book/Appendix-New-IO.md#基本类型获取) - * [视图缓冲区](docs/book/Appendix-New-IO.md#视图缓冲区) - * [使用缓冲区进行数据操作](docs/book/Appendix-New-IO.md#缓冲区数据操作) - * [内存映射文件](docs/book/Appendix-New-IO.md#内存映射文件) - * [文件锁定](docs/book/Appendix-New-IO.md#文件锁定) -* [附录:理解equals和hashCode方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) - * [equals典范](docs/book/Appendix-Understanding-equals-and-hashCode.md#equals典范) - * [哈希和哈希码](docs/book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) - * [调整HashMap](docs/book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap) -* [附录:集合主题](docs/book/Appendix-Collection-Topics.md) - * [示例数据](docs/book/Appendix-Collection-Topics.md#示例数据) - * [List行为](docs/book/Appendix-Collection-Topics.md#List行为) - * [Set行为](docs/book/Appendix-Collection-Topics.md#Set行为) - * [在Map中使用函数式操作](docs/book/Appendix-Collection-Topics.md#在Map中使用函数式操作) - * [选择Map片段](docs/book/Appendix-Collection-Topics.md#选择Map片段) - * [填充集合](docs/book/Appendix-Collection-Topics.md#填充集合) - * [使用享元(Flyweight)自定义Collection和Map](docs/book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) - * [集合功能](docs/book/Appendix-Collection-Topics.md#集合功能) - * [可选操作](docs/book/Appendix-Collection-Topics.md#可选操作) - * [Set和存储顺序](docs/book/Appendix-Collection-Topics.md#Set和存储顺序) - * [队列](docs/book/Appendix-Collection-Topics.md#队列) - * [理解Map](docs/book/Appendix-Collection-Topics.md#理解Map) - * [集合工具类](docs/book/Appendix-Collection-Topics.md#集合工具类) - * [持有引用](docs/book/Appendix-Collection-Topics.md#持有引用) - * [Java 1.0 / 1.1 的集合类](docs/book/Appendix-Collection-Topics.md#避免旧式类库) - * [本章小结](docs/book/Appendix-Collection-Topics.md#本章小结) -* [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md) - * [线程](docs/book/Appendix-Low-Level-Concurrency.md#线程) - * [异常捕获](docs/book/Appendix-Low-Level-Concurrency.md#异常捕获) - * [资源共享](docs/book/Appendix-Low-Level-Concurrency.md#资源共享) - * [volatile关键字](docs/book/Appendix-Low-Level-Concurrency.md#volatile关键字) - * [原子性](docs/book/Appendix-Low-Level-Concurrency.md#原子性) - * [临界区](docs/book/Appendix-Low-Level-Concurrency.md#临界区) - * [库组件](docs/book/Appendix-Low-Level-Concurrency.md#库组件) - * [本章小结](docs/book/Appendix-Low-Level-Concurrency.md#本章小结) -* [附录:数据压缩](docs/book/Appendix-Data-Compression.md) - * [使用Gzip简单压缩](docs/book/Appendix-Data-Compression.md#使用Gzip简单压缩) - * [使用zip多文件存储](docs/book/Appendix-Data-Compression.md#使用zip多文件存储) - * [Java的jar](docs/book/Appendix-Data-Compression.md#Java的jar) -* [附录:对象序列化](docs/book/Appendix-Object-Serialization.md) - * [查找类](docs/book/Appendix-Object-Serialization.md#查找类) - * [控制序列化](docs/book/Appendix-Object-Serialization.md#控制序列化) - * [使用持久化](docs/book/Appendix-Object-Serialization.md#使用持久化) -* [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) - * [前言](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) - * [静态类型检查和测试](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) - * [如何提升打字](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) - * [生产力的成本](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) - * [静态和动态](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) -* [附录:C++和Java的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -* [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) - * [如何开始](docs/book/Appendix-Becoming-a-Programmer.md#如何开始) - * [码农生涯](docs/book/Appendix-Becoming-a-Programmer.md#码农生涯) - * [百分之五的神话](docs/book/Appendix-Becoming-a-Programmer.md#百分之五的神话) - * [重在动手](docs/book/Appendix-Becoming-a-Programmer.md#重在动手) - * [像打字般编程](docs/book/Appendix-Becoming-a-Programmer.md#像打字般编程) - * [做你喜欢的事](docs/book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) -* [词汇表](docs/book/GLOSSARY.md) + +### [译者的话](README.md) + +### [封面](book/00-On-Java-8.md) + +### [前言](book/00-Preface.md) + +### [简介](book/00-Introduction.md) + +### [第一章 对象的概念](book/01-What-is-an-Object.md) + * [抽象](book/01-What-is-an-Object.md#抽象) + * [接口](book/01-What-is-an-Object.md#接口) + * [服务提供](book/01-What-is-an-Object.md#服务提供) + * [封装](book/01-What-is-an-Object.md#封装) + * [复用](book/01-What-is-an-Object.md#复用) + * [继承](book/01-What-is-an-Object.md#继承) + * [多态](book/01-What-is-an-Object.md#多态) + * [单继承结构](book/01-What-is-an-Object.md#单继承结构) + * [集合](book/01-What-is-an-Object.md#集合) + * [对象创建与生命周期](book/01-What-is-an-Object.md#对象创建与生命周期) + * [异常处理](book/01-What-is-an-Object.md#异常处理) + * [本章小结](book/01-What-is-an-Object.md#本章小结) + +### [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) + * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器) + * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell) + * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装) + * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装) + * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) + +### [第三章 万物皆对象](book/03-Objects-Everywhere.md) + * [对象操纵](book/03-Objects-Everywhere.md#对象操纵) + * [对象创建](book/03-Objects-Everywhere.md#对象创建) + * [代码注释](book/03-Objects-Everywhere.md#代码注释) + * [对象清理](book/03-Objects-Everywhere.md#对象清理) + * [类的创建](book/03-Objects-Everywhere.md#类的创建) + * [程序编写](book/03-Objects-Everywhere.md#程序编写) + * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀) + * [编码风格](book/03-Objects-Everywhere.md#编码风格) + * [本章小结](book/03-Objects-Everywhere.md#本章小结) + +### [第四章 运算符](book/04-Operators.md) + * [开始使用](book/04-Operators.md#开始使用) + * [优先级](book/04-Operators.md#优先级) + * [赋值](book/04-Operators.md#赋值) + * [算术运算符](book/04-Operators.md#算术运算符) + * [递增和递减](book/04-Operators.md#递增和递减) + * [关系运算符](book/04-Operators.md#关系运算符) + * [逻辑运算符](book/04-Operators.md#逻辑运算符) + * [字面值常量](book/04-Operators.md#字面值常量) + * [位运算符](book/04-Operators.md#位运算符) + * [移位运算符](book/04-Operators.md#移位运算符) + * [三元运算符](book/04-Operators.md#三元运算符) + * [字符串运算符](book/04-Operators.md#字符串运算符) + * [常见陷阱](book/04-Operators.md#常见陷阱) + * [类型转换](book/04-Operators.md#类型转换) + * [Java没有sizeof](book/04-Operators.md#Java没有sizeof) + * [运算符总结](book/04-Operators.md#运算符总结) + * [本章小结](book/04-Operators.md#本章小结) + +### [第五章 控制流](book/05-Control-Flow.md) + * [true和false](book/05-Control-Flow.md#true和false) + * [if-else](book/05-Control-Flow.md#if-else) + * [迭代语句](book/05-Control-Flow.md#迭代语句) + * [for-in 语法](book/05-Control-Flow.md#for-in-语法) + * [return](book/05-Control-Flow.md#return) + * [break 和 continue](book/05-Control-Flow.md#break-和-continue) + * [臭名昭著的 goto](book/05-Control-Flow.md#臭名昭著的-goto) + * [switch](book/05-Control-Flow.md#switch) + * [switch 字符串](book/05-Control-Flow.md#switch-字符串) + * [本章小结](book/05-Control-Flow.md#本章小结) + +### [第六章 初始化和清理](book/06-Housekeeping.md) + * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化) + * [方法重载](book/06-Housekeeping.md#方法重载) + * [无参构造器](book/06-Housekeeping.md#无参构造器) + * [this关键字](book/06-Housekeeping.md#this关键字) + * [垃圾回收器](book/06-Housekeeping.md#垃圾回收器) + * [成员初始化](book/06-Housekeeping.md#成员初始化) + * [构造器初始化](book/06-Housekeeping.md#构造器初始化) + * [数组初始化](book/06-Housekeeping.md#数组初始化) + * [枚举类型](book/06-Housekeeping.md#枚举类型) + * [本章小结](book/06-Housekeeping.md#本章小结) + +### [第七章 封装](book/07-Implementation-Hiding.md) + * [包的概念](book/07-Implementation-Hiding.md#包的概念) + * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符) + * [接口和实现](book/07-Implementation-Hiding.md#接口和实现) + * [类访问权限](book/07-Implementation-Hiding.md#类访问权限) + * [本章小结](book/07-Implementation-Hiding.md#本章小结) + +### [第八章 复用](book/08-Reuse.md) + * [组合语法](book/08-Reuse.md#组合语法) + * [继承语法](book/08-Reuse.md#继承语法) + * [委托](book/08-Reuse.md#委托) + * [结合组合与继承](book/08-Reuse.md#结合组合与继承) + * [组合与继承的选择](book/08-Reuse.md#组合与继承的选择) + * [protected](book/08-Reuse.md#protected) + * [向上转型](book/08-Reuse.md#向上转型) + * [final关键字](book/08-Reuse.md#final关键字) + * [类初始化和加载](book/08-Reuse.md#类初始化和加载) + * [本章小结](book/08-Reuse.md#本章小结) + +### [第九章 多态](book/09-Polymorphism.md) + * [向上转型回顾](book/09-Polymorphism.md#向上转型回顾) + * [转机](book/09-Polymorphism.md#转机) + * [构造器和多态](book/09-Polymorphism.md#构造器和多态) + * [协变返回类型](book/09-Polymorphism.md#协变返回类型) + * [使用继承设计](book/09-Polymorphism.md#使用继承设计) + * [本章小结](book/09-Polymorphism.md#本章小结) + +### [第十章 接口](book/10-Interfaces.md) + * [抽象类和方法](book/10-Interfaces.md#抽象类和方法) + * [接口创建](book/10-Interfaces.md#接口创建) + * [抽象类和接口](book/10-Interfaces.md#抽象类和接口) + * [完全解耦](book/10-Interfaces.md#完全解耦) + * [多接口结合](book/10-Interfaces.md#多接口结合) + * [使用继承扩展接口](book/10-Interfaces.md#使用继承扩展接口) + * [接口适配](book/10-Interfaces.md#接口适配) + * [接口字段](book/10-Interfaces.md#接口字段) + * [接口嵌套](book/10-Interfaces.md#接口嵌套) + * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式) + * [本章小结](book/10-Interfaces.md#本章小结) + +### [第十一章 内部类](book/11-Inner-Classes.md) + * [创建内部类](book/11-Inner-Classes.md#创建内部类) + * [链接外部类](book/11-Inner-Classes.md#链接外部类) + * [使用 .this 和 .new](book/11-Inner-Classes.md#使用-this-和-new) + * [内部类与向上转型](book/11-Inner-Classes.md#内部类与向上转型) + * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域) + * [匿名内部类](book/11-Inner-Classes.md#匿名内部类) + * [嵌套类](book/11-Inner-Classes.md#嵌套类) + * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类) + * [继承内部类](book/11-Inner-Classes.md#继承内部类) + * [内部类可以被覆盖么?](book/11-Inner-Classes.md#内部类可以被覆盖么?) + * [局部内部类](book/11-Inner-Classes.md#局部内部类) + * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) + * [本章小结](book/11-Inner-Classes.md#本章小结) + +### [第十二章 集合](book/12-Collections.md) + * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合) + * [基本概念](book/12-Collections.md#基本概念) + * [添加元素组](book/12-Collections.md#添加元素组) + * [集合的打印](book/12-Collections.md#集合的打印) + * [列表List](book/12-Collections.md#列表List) + * [迭代器Iterators](book/12-Collections.md#迭代器Iterators) + * [链表LinkedList](book/12-Collections.md#链表LinkedList) + * [堆栈Stack](book/12-Collections.md#堆栈Stack) + * [集合Set](book/12-Collections.md#集合Set) + * [映射Map](book/12-Collections.md#映射Map) + * [队列Queue](book/12-Collections.md#队列Queue) + * [集合与迭代器](book/12-Collections.md#集合与迭代器) + * [for-in和迭代器](book/12-Collections.md#for-in和迭代器) + * [本章小结](book/12-Collections.md#本章小结) + +### [第十三章 函数式编程](book/13-Functional-Programming.md) + * [新旧对比](book/13-Functional-Programming.md#新旧对比) + * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式) + * [方法引用](book/13-Functional-Programming.md#方法引用) + * [函数式接口](book/13-Functional-Programming.md#函数式接口) + * [高阶函数](book/13-Functional-Programming.md#高阶函数) + * [闭包](book/13-Functional-Programming.md#闭包) + * [函数组合](book/13-Functional-Programming.md#函数组合) + * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值) + * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程) + * [本章小结](book/13-Functional-Programming.md#本章小结) + +### [第十四章 流式编程](book/14-Streams.md) + * [流支持](book/14-Streams.md#流支持) + * [流创建](book/14-Streams.md#流创建) + * [中间操作](book/14-Streams.md#中间操作) + * [Optional类](book/14-Streams.md#Optional类) + * [终端操作](book/14-Streams.md#终端操作) + * [本章小结](book/14-Streams.md#本章小结) + +### [第十五章 异常](book/15-Exceptions.md) + * [异常概念](book/15-Exceptions.md#异常概念) + * [基本异常](book/15-Exceptions.md#基本异常) + * [异常捕获](book/15-Exceptions.md#异常捕获) + * [自定义异常](book/15-Exceptions.md#自定义异常) + * [异常声明](book/15-Exceptions.md#异常声明) + * [捕获所有异常](book/15-Exceptions.md#捕获所有异常) + * [Java 标准异常](book/15-Exceptions.md#Java-标准异常) + * [使用 finally 进行清理](book/15-Exceptions.md#使用-finally-进行清理) + * [异常限制](book/15-Exceptions.md#异常限制) + * [构造器](book/15-Exceptions.md#构造器) + * [Try-With-Resources 用法](book/15-Exceptions.md#Try-With-Resources-用法) + * [异常匹配](book/15-Exceptions.md#异常匹配) + * [其他可选方式](book/15-Exceptions.md#其他可选方式) + * [异常指南](book/15-Exceptions.md#异常指南) + * [本章小结](book/15-Exceptions.md#本章小结) + * [后记:Exception Bizarro World](book/15-Exceptions.md#后记:Exception-Bizarro-World) + +### [第十六章 代码校验](book/16-Validating-Your-Code.md) + * [测试](book/16-Validating-Your-Code.md#测试) + * [前置条件](book/16-Validating-Your-Code.md#前置条件) + * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) + * [日志](book/16-Validating-Your-Code.md#日志) + * [调试](book/16-Validating-Your-Code.md#调试) + * [基准测试](book/16-Validating-Your-Code.md#基准测试) + * [剖析和优化](book/16-Validating-Your-Code.md#剖析和优化) + * [风格检测](book/16-Validating-Your-Code.md#风格检测) + * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析) + * [代码重审](book/16-Validating-Your-Code.md#代码重审) + * [结对编程](book/16-Validating-Your-Code.md#结对编程) + * [重构](book/16-Validating-Your-Code.md#重构) + * [持续集成](book/16-Validating-Your-Code.md#持续集成) + * [本章小结](book/16-Validating-Your-Code.md#本章小结) + +### [第十七章 文件](book/17-Files.md) + * [文件和目录路径](book/17-Files.md#文件和目录路径) + * [目录](book/17-Files.md#目录) + * [文件系统](book/17-Files.md#文件系统) + * [路径监听](book/17-Files.md#路径监听) + * [文件查找](book/17-Files.md#文件查找) + * [文件读写](book/17-Files.md#文件读写) + * [本章小结](book/17-Files.md#本章小结) + +### [第十八章 字符串](book/18-Strings.md) + * [字符串的不可变](book/18-Strings.md#字符串的不可变) + * [+ 的重载与 StringBuilder](book/18-Strings.md#+-的重载与-StringBuilder) + * [意外递归](book/18-Strings.md#意外递归) + * [字符串操作](book/18-Strings.md#字符串操作) + * [格式化输出](book/18-Strings.md#格式化输出) + * [正则表达式](book/18-Strings.md#正则表达式) + * [扫描输入](book/18-Strings.md#扫描输入) + * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) + * [本章小结](book/18-Strings.md#本章小结) + +### [第十九章 类型信息](book/19-Type-Information.md) + * [为什么需要 RTTI](book/19-Type-Information.md#为什么需要-RTTI) + * [Class 对象](book/19-Type-Information.md#Class-对象) + * [类型转换检测](book/19-Type-Information.md#类型转换检测) + * [注册工厂](book/19-Type-Information.md#注册工厂) + * [类的等价比较](book/19-Type-Information.md#类的等价比较) + * [反射:运行时类信息](book/19-Type-Information.md#反射:运行时类信息) + * [动态代理](book/19-Type-Information.md#动态代理) + * [Optional类](book/19-Type-Information.md#Optional类) + * [接口和类型](book/19-Type-Information.md#接口和类型) + * [本章小结](book/19-Type-Information.md#本章小结) + +### [第二十章 泛型](book/20-Generics.md) + * [简单泛型](book/20-Generics.md#简单泛型) + * [泛型接口](book/20-Generics.md#泛型接口) + * [泛型方法](book/20-Generics.md#泛型方法) + * [构建复杂模型](book/20-Generics.md#构建复杂模型) + * [泛型擦除](book/20-Generics.md#泛型擦除) + * [补偿擦除](book/20-Generics.md#补偿擦除) + * [边界](book/20-Generics.md#边界) + * [通配符](book/20-Generics.md#通配符) + * [问题](book/20-Generics.md#问题) + * [自限定的类型](book/20-Generics.md#自限定的类型) + * [动态类型安全](book/20-Generics.md#动态类型安全) + * [泛型异常](book/20-Generics.md#泛型异常) + * [混型](book/20-Generics.md#混型) + * [潜在类型机制](book/20-Generics.md#潜在类型机制) + * [对缺乏潜在类型机制的补偿](book/20-Generics.md#对缺乏潜在类型机制的补偿) + * [Java8 中的辅助潜在类型](book/20-Generics.md#Java8-中的辅助潜在类型) + * [总结:类型转换真的如此之糟吗?](book/20-Generics.md#总结:类型转换真的如此之糟吗?) + * [进阶阅读](book/20-Generics.md#进阶阅读) + +### [第二十一章 数组](book/21-Arrays.md) + * [数组特性](book/21-Arrays.md#数组特性) + * [一等对象](book/21-Arrays.md#一等对象) + * [返回数组](book/21-Arrays.md#返回数组) + * [多维数组](book/21-Arrays.md#多维数组) + * [泛型数组](book/21-Arrays.md#泛型数组) + * [Arrays的fill方法](book/21-Arrays.md#Arrays的fill方法) + * [Arrays的setAll方法](book/21-Arrays.md#Arrays的setAll方法) + * [增量生成](book/21-Arrays.md#增量生成) + * [随机生成](book/21-Arrays.md#随机生成) + * [泛型和基本数组](book/21-Arrays.md#泛型和基本数组) + * [数组元素修改](book/21-Arrays.md#数组元素修改) + * [数组并行](book/21-Arrays.md#数组并行) + * [Arrays工具类](book/21-Arrays.md#Arrays工具类) + * [数组拷贝](book/21-Arrays.md#数组拷贝) + * [数组比较](book/21-Arrays.md#数组比较) + * [流和数组](book/21-Arrays.md#流和数组) + * [数组排序](book/21-Arrays.md#数组排序) + * [Arrays.sort 的使用](book/21-Arrays.md#Arrayssort-的使用) + * [并行排序](book/21-Arrays.md#并行排序) + * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) + * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) + * [本章小结](book/21-Arrays.md#本章小结) + +### [第二十二章 枚举](book/22-Enumerations.md) + * [基本 enum 特性](book/22-Enumerations.md#基本-enum-特性) + * [方法添加](book/22-Enumerations.md#方法添加) + * [switch 语句中的 enum](book/22-Enumerations.md#switch-语句中的-enum) + * [values 方法的神秘之处](book/22-Enumerations.md#values-方法的神秘之处) + * [实现而非继承](book/22-Enumerations.md#实现而非继承) + * [随机选择](book/22-Enumerations.md#随机选择) + * [使用接口组织枚举](book/22-Enumerations.md#使用接口组织枚举) + * [使用 EnumSet 替代 Flags](book/22-Enumerations.md#使用-EnumSet-替代-Flags) + * [使用 EnumMap](book/22-Enumerations.md#使用-EnumMap) + * [常量特定方法](book/22-Enumerations.md#常量特定方法) + * [多路分发](book/22-Enumerations.md#多路分发) + * [本章小结](book/22-Enumerations.md#本章小结) + +### [第二十三章 注解](book/23-Annotations.md) + * [基本语法](book/23-Annotations.md#基本语法) + * [编写注解处理器](book/23-Annotations.md#编写注解处理器) + * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解) + * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试) + * [本章小结](book/23-Annotations.md#本章小结) + +### [第二十四章 并发编程](book/24-Concurrent-Programming.md) + * [术语问题](book/24-Concurrent-Programming.md#术语问题) + * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) + * [并发为速度而生](book/24-Concurrent-Programming.md#并发为速度而生) + * [四句格言](book/24-Concurrent-Programming.md#四句格言) + * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相) + * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分) + * [并行流](book/24-Concurrent-Programming.md#并行流) + * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) + * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) + * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类) + * [死锁](book/24-Concurrent-Programming.md#死锁) + * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) + * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) + * [本章小结](book/24-Concurrent-Programming.md#本章小结) + +### [第二十五章 设计模式](book/25-Patterns.md) + * [概念](book/25-Patterns.md#概念) + * [构建应用程序框架](book/25-Patterns.md#构建应用程序框架) + * [面向实现](book/25-Patterns.md#面向实现) + * [工厂模式](book/25-Patterns.md#工厂模式) + * [函数对象](book/25-Patterns.md#函数对象) + * [改变接口](book/25-Patterns.md#改变接口) + * [解释器:运行时的弹性](book/25-Patterns.md#解释器:运行时的弹性) + * [回调](book/25-Patterns.md#回调) + * [多次调度](book/25-Patterns.md#多次调度) + * [模式重构](book/25-Patterns.md#模式重构) + * [抽象用法](book/25-Patterns.md#抽象用法) + * [多次派遣](book/25-Patterns.md#多次派遣) + * [访问者模式](book/25-Patterns.md#访问者模式) + * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) + * [本章小结](book/25-Patterns.md#本章小结) + +### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) + * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) + * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) + * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) + * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) + * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) + * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) + +### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) + * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) + * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) + * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) + * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) + * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) + +### [附录:集合主题](book/Appendix-Collection-Topics.md) + * [示例数据](book/Appendix-Collection-Topics.md#示例数据) + * [List行为](book/Appendix-Collection-Topics.md#List行为) + * [Set行为](book/Appendix-Collection-Topics.md#Set行为) + * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作) + * [选择Map片段](book/Appendix-Collection-Topics.md#选择Map片段) + * [填充集合](book/Appendix-Collection-Topics.md#填充集合) + * [使用享元(Flyweight)自定义Collection和Map](book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) + * [集合功能](book/Appendix-Collection-Topics.md#集合功能) + * [可选操作](book/Appendix-Collection-Topics.md#可选操作) + * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序) + * [队列](book/Appendix-Collection-Topics.md#队列) + * [理解Map](book/Appendix-Collection-Topics.md#理解Map) + * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类) + * [持有引用](book/Appendix-Collection-Topics.md#持有引用) + * [Java 1.0 / 1.1 的集合类](book/Appendix-Collection-Topics.md#Java-10-11-的集合类) + * [本章小结](book/Appendix-Collection-Topics.md#本章小结) + +### [附录:数据压缩](book/Appendix-Data-Compression.md) + * [使用 Gzip 简单压缩](book/Appendix-Data-Compression.md#使用-Gzip-简单压缩) + * [使用 zip 多文件存储](book/Appendix-Data-Compression.md#使用-zip-多文件存储) + * [Java 的 jar](book/Appendix-Data-Compression.md#Java-的-jar) + +### [附录:流式IO](book/Appendix-IO-Streams.md) + * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) + * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) + * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) + * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) + * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) + * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) + * [本章小结](book/Appendix-IO-Streams.md#本章小结) + +### [附录:文档注释](book/Appendix-Javadoc.md) + * [句法规则](book/Appendix-Javadoc.md#句法规则) + * [内嵌 HTML](book/Appendix-Javadoc.md#内嵌-HTML) + * [示例标签](book/Appendix-Javadoc.md#示例标签) + * [文档示例](book/Appendix-Javadoc.md#文档示例) + +### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) + * [什么是线程?](book/Appendix-Low-Level-Concurrency.md#什么是线程?) + * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) + * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) + * [volatile 关键字](book/Appendix-Low-Level-Concurrency.md#volatile-关键字) + * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性) + * [临界区](book/Appendix-Low-Level-Concurrency.md#临界区) + * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) + * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) + +### [附录:新IO](book/Appendix-New-IO.md) + * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) + * [数据转换](book/Appendix-New-IO.md#数据转换) + * [基本类型获取](book/Appendix-New-IO.md#基本类型获取) + * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) + * [缓冲区数据操作](book/Appendix-New-IO.md#缓冲区数据操作) + * [ 内存映射文件](book/Appendix-New-IO.md#-内存映射文件) + * [文件锁定](book/Appendix-New-IO.md#文件锁定) + +### [附录:对象序列化](book/Appendix-Object-Serialization.md) + * [查找类](book/Appendix-Object-Serialization.md#查找类) + * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) + * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) + * [XML](book/Appendix-Object-Serialization.md#XML) + +### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) + * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) + * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) + * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) + * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) + * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) + +### [附录:编程指南](book/Appendix-Programming-Guidelines.md) + * [设计](book/Appendix-Programming-Guidelines.md#设计) + * [实现](book/Appendix-Programming-Guidelines.md#实现) + +### [附录:标准IO](book/Appendix-Standard-IO.md) + * [从标准输入中读取](book/Appendix-Standard-IO.md#从标准输入中读取) + * [将 System.out 转换成 PrintWriter](book/Appendix-Standard-IO.md#将-Systemout-转换成-PrintWriter) + * [重定向标准 I/O](book/Appendix-Standard-IO.md#重定向标准-IO) + * [执行控制](book/Appendix-Standard-IO.md#执行控制) + +### [附录:补充](book/Appendix-Supplements.md) + * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) + * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) + * [Hand-On Java 电子演示文稿](book/Appendix-Supplements.md#Hand-On-Java-电子演示文稿) + +### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) + +### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) + * [equals规范](book/Appendix-Understanding-equals-and-hashCode.md#equals规范) + * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) + * [调优 HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调优-HashMap) + +### [词汇表](book/GLOSSARY.md) \ No newline at end of file diff --git a/docs/book/05-Control-Flow.md b/docs/book/05-Control-Flow.md old mode 100644 new mode 100755 index 854d6dc5..c12ac689 --- a/docs/book/05-Control-Flow.md +++ b/docs/book/05-Control-Flow.md @@ -235,7 +235,7 @@ i = 4 j = 8 上例中 **int** 类型声明包含了 `i` 和 `j`。实际上,在初始化部分我们可以定义任意数量的同类型变量。**注意**:在 Java 中,仅允许 **for** 循环在控制表达式中定义变量。 我们不能将此方法与其他的循环语句和选择语句中一起使用。同时,我们可以看到:无论在初始化还是在步进部分,语句都是顺序执行的。 -## for-in 语法 +## for-in 语法 Java 5 引入了更为简洁的“增强版 **for** 循环”语法来操纵数组和集合。(更多细节,可参考 [数组](./21-Arrays.md) 和 [集合](./12-Collections.md) 章节内容)。大部分文档也称其为 **for-each** 语法,但因为了不与 Java 8 新添的 `forEach()` 产生混淆,因此我称之为 **for-in** 循环。 (Python 已有类似的先例,如:**for x in sequence**)。**注意**:你可能会在其他地方看到不同叫法。 diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md old mode 100644 new mode 100755 diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md old mode 100644 new mode 100755 index be0bca0b..0b7bc7e5 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -47,7 +47,7 @@ String x = Immutable.upcase(s); -## `+` 的重载与 `StringBuilder` +## + 的重载与 StringBuilder `String` 对象是不可变的,你可以给一个 `String` 对象添加任意多的别名。因为 `String` 是只读的,所以指向它的任何引用都不可能修改它的值,因此,也就不会影响到其他引用。 不可变性会带来一定的效率问题。为 `String` 对象重载的 `+` 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义(用于 `String` 的 `+` 与 `+=` 是 Java 中仅有的两个重载过的操作符,Java 不允许程序员重载任何其他的操作符 [^1])。 diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md old mode 100644 new mode 100755 index f013b51a..6c51d949 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -84,7 +84,7 @@ Triangle.draw() 但是,有时你会碰到一些编程问题,在这些问题中如果你能知道某个泛化引用的具体类型,就可以把问题轻松解决。例如,假设我们允许用户将某些几何形状高亮显示,现在希望找到屏幕上所有高亮显示的三角形;或者,我们现在需要旋转所有图形,但是想跳过圆形(因为圆形旋转没有意义)。这时我们就希望知道 `Stream` 里边的形状具体是什么类型,而 Java 实际上也满足了我们的这种需求。使用 RTTI,我们可以查询某个 `Shape` 引用所指向对象的确切类型,然后选择或者剔除特例。 -## `Class` 对象 +## Class 对象 要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为 **`Class`对象** 的特殊对象完成的,它包含了与类有关的信息。实际上,`Class` 对象就是用来创建该类所有"常规"对象的。Java 使用 `Class` 对象来实现 RTTI,即便是类型转换这样的操作都是用 `Class` 对象实现的。不仅如此,`Class` 类还提供了很多使用 RTTI 的其它方式。 diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md old mode 100644 new mode 100755 index 0ff7dad6..979170cf --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -2414,7 +2414,7 @@ After sorting: [[i = 21, j = 6], [i = 70, j = 7], [i = 41, j = 20] , ``` -## Arrays.sort()的使用 +## Arrays.sort 的使用 使用内置的排序方法,您可以对实现了 **Comparable** 接口或具有 **Comparator** 的任何对象数组 或 任何原生数组进行排序。这里我们生成一个随机字符串对象数组并对其排序: diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md old mode 100644 new mode 100755 index 603eb9e9..9046c59c --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -221,7 +221,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 Java是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 -## 残酷的真相 +## 残酷的真相 当人类开始烹饪他们的食物时,他们大大减少了他们的身体分解和消化食物所需的能量。烹饪创造了一个“外化的胃”,从而释放出能量去发展其他的能力。火的使用促成了文明。 diff --git a/docs/book/Appendix-Standard-IO.md b/docs/book/Appendix-Standard-IO.md old mode 100644 new mode 100755 index bfc6d597..842e2804 --- a/docs/book/Appendix-Standard-IO.md +++ b/docs/book/Appendix-Standard-IO.md @@ -35,7 +35,7 @@ public class Echo { `BufferedReader` 提供了 `lines()` 方法,返回类型是 `Stream` 。这显示出流模型的的灵活性:仅使用标准输入就能很好地工作。 `peek()` 方法重启 `TimeAbort`,只要保证至少每隔两秒有输入就能够使程序保持开启状态。 -## 将`System.out` 转换成 `PrintWriter` +## 将 System.out 转换成 PrintWriter `System.out` 是一个 `PrintStream`,而 `PrintStream` 是一个`OutputStream`。 `PrintWriter` 有一个把 `OutputStream` 作为参数的构造器。因此,如果你需要的话,可以使用这个构造器把 `System.out` 转换成 `PrintWriter` 。 diff --git a/docs/index.html b/docs/index.html old mode 100644 new mode 100755 index b7e41098..a19f3ccd --- a/docs/index.html +++ b/docs/index.html @@ -352,22 +352,22 @@ repo: 'https://github.com/LingCoder/OnJava8', loadSidebar: 'sidebar.md', // subMaxLevel: 3, - search: { - paths: 'auto', - placeholder: '🔍 点击搜索 ', - noData: '😞 没有结果! ', - // Headline depth, 1 - 6 - depth: 6 - }, - copyCode: { - buttonText : '复制', - errorText : 'Error', - successText: 'OK!' - }, - pagination: { - previousText: '上一章节', - nextText: '下一章节', - }, + search: { + paths: 'auto', + placeholder: '🔍 点击搜索 ', + noData: '😞 没有结果! ', + // Headline depth, 1 - 6 + depth: 6 + }, + copyCode: { + buttonText : '复制', + errorText : 'Error', + successText: 'OK!' + }, + pagination: { + previousText: '上一章节', + nextText: '下一章节', + }, coverpage: true, } diff --git a/docs/sidebar.md b/docs/sidebar.md index dbde9004..5f2b8733 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -1,4 +1,5 @@ + ### [译者的话](README.md) ### [封面](book/00-On-Java-8.md) @@ -15,9 +16,9 @@ * [复用](book/01-What-is-an-Object.md#复用) * [继承](book/01-What-is-an-Object.md#继承) * [多态](book/01-What-is-an-Object.md#多态) - * [单继承](book/01-What-is-an-Object.md#单继承) + * [单继承结构](book/01-What-is-an-Object.md#单继承结构) * [集合](book/01-What-is-an-Object.md#集合) - * [生命周期](book/01-What-is-an-Object.md#生命周期) + * [对象创建与生命周期](book/01-What-is-an-Object.md#对象创建与生命周期) * [异常处理](book/01-What-is-an-Object.md#异常处理) * [本章小结](book/01-What-is-an-Object.md#本章小结) @@ -48,7 +49,7 @@ * [关系运算符](book/04-Operators.md#关系运算符) * [逻辑运算符](book/04-Operators.md#逻辑运算符) * [字面值常量](book/04-Operators.md#字面值常量) - * [按位运算符](book/04-Operators.md#按位运算符) + * [位运算符](book/04-Operators.md#位运算符) * [移位运算符](book/04-Operators.md#移位运算符) * [三元运算符](book/04-Operators.md#三元运算符) * [字符串运算符](book/04-Operators.md#字符串运算符) @@ -59,15 +60,15 @@ * [本章小结](book/04-Operators.md#本章小结) ### [第五章 控制流](book/05-Control-Flow.md) - * [true和flase](book/05-Control-Flow.md#true和flase) + * [true和false](book/05-Control-Flow.md#true和false) * [if-else](book/05-Control-Flow.md#if-else) * [迭代语句](book/05-Control-Flow.md#迭代语句) - * [for-in语法](book/05-Control-Flow.md#for-in语法) + * [for-in 语法](book/05-Control-Flow.md#for-in-语法) * [return](book/05-Control-Flow.md#return) - * [break和continue](book/05-Control-Flow.md#break和continue) - * [臭名昭著的goto](book/05-Control-Flow.md#臭名昭著的goto) + * [break 和 continue](book/05-Control-Flow.md#break-和-continue) + * [臭名昭著的 goto](book/05-Control-Flow.md#臭名昭著的-goto) * [switch](book/05-Control-Flow.md#switch) - * [switch字符串](book/05-Control-Flow.md#switch字符串) + * [switch 字符串](book/05-Control-Flow.md#switch-字符串) * [本章小结](book/05-Control-Flow.md#本章小结) ### [第六章 初始化和清理](book/06-Housekeeping.md) @@ -102,10 +103,10 @@ * [本章小结](book/08-Reuse.md#本章小结) ### [第九章 多态](book/09-Polymorphism.md) - * [向上转型回溯](book/09-Polymorphism.md#向上转型回溯) - * [深入理解](book/09-Polymorphism.md#深入理解) + * [向上转型回顾](book/09-Polymorphism.md#向上转型回顾) + * [转机](book/09-Polymorphism.md#转机) * [构造器和多态](book/09-Polymorphism.md#构造器和多态) - * [返回类型协变](book/09-Polymorphism.md#返回类型协变) + * [协变返回类型](book/09-Polymorphism.md#协变返回类型) * [使用继承设计](book/09-Polymorphism.md#使用继承设计) * [本章小结](book/09-Polymorphism.md#本章小结) @@ -125,15 +126,15 @@ ### [第十一章 内部类](book/11-Inner-Classes.md) * [创建内部类](book/11-Inner-Classes.md#创建内部类) * [链接外部类](book/11-Inner-Classes.md#链接外部类) - * [内部类this和new的使用](book/11-Inner-Classes.md#内部类this和new的使用) - * [内部类向上转型](book/11-Inner-Classes.md#内部类向上转型) + * [使用 .this 和 .new](book/11-Inner-Classes.md#使用-this-和-new) + * [内部类与向上转型](book/11-Inner-Classes.md#内部类与向上转型) * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域) * [匿名内部类](book/11-Inner-Classes.md#匿名内部类) * [嵌套类](book/11-Inner-Classes.md#嵌套类) * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类) * [继承内部类](book/11-Inner-Classes.md#继承内部类) - * [重写内部类](book/11-Inner-Classes.md#重写内部类) - * [内部类局部变量](book/11-Inner-Classes.md#内部类局部变量) + * [内部类可以被覆盖么?](book/11-Inner-Classes.md#内部类可以被覆盖么?) + * [局部内部类](book/11-Inner-Classes.md#局部内部类) * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) * [本章小结](book/11-Inner-Classes.md#本章小结) @@ -178,26 +179,27 @@ * [基本异常](book/15-Exceptions.md#基本异常) * [异常捕获](book/15-Exceptions.md#异常捕获) * [自定义异常](book/15-Exceptions.md#自定义异常) - * [异常规范](book/15-Exceptions.md#异常规范) - * [任意异常捕获](book/15-Exceptions.md#任意异常捕获) - * [Java标准异常](book/15-Exceptions.md#Java标准异常) - * [finally关键字](book/15-Exceptions.md#finally关键字) + * [异常声明](book/15-Exceptions.md#异常声明) + * [捕获所有异常](book/15-Exceptions.md#捕获所有异常) + * [Java 标准异常](book/15-Exceptions.md#Java-标准异常) + * [使用 finally 进行清理](book/15-Exceptions.md#使用-finally-进行清理) * [异常限制](book/15-Exceptions.md#异常限制) - * [异常构造](book/15-Exceptions.md#异常构造) - * [Try-With-Resources用法](book/15-Exceptions.md#Try-With-Resources用法) + * [构造器](book/15-Exceptions.md#构造器) + * [Try-With-Resources 用法](book/15-Exceptions.md#Try-With-Resources-用法) * [异常匹配](book/15-Exceptions.md#异常匹配) - * [异常准则](book/15-Exceptions.md#异常准则) + * [其他可选方式](book/15-Exceptions.md#其他可选方式) * [异常指南](book/15-Exceptions.md#异常指南) * [本章小结](book/15-Exceptions.md#本章小结) + * [后记:Exception Bizarro World](book/15-Exceptions.md#后记:Exception-Bizarro-World) ### [第十六章 代码校验](book/16-Validating-Your-Code.md) * [测试](book/16-Validating-Your-Code.md#测试) - * [前提条件](book/16-Validating-Your-Code.md#前提条件) + * [前置条件](book/16-Validating-Your-Code.md#前置条件) * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) * [日志](book/16-Validating-Your-Code.md#日志) * [调试](book/16-Validating-Your-Code.md#调试) * [基准测试](book/16-Validating-Your-Code.md#基准测试) - * [分析和优化](book/16-Validating-Your-Code.md#分析和优化) + * [剖析和优化](book/16-Validating-Your-Code.md#剖析和优化) * [风格检测](book/16-Validating-Your-Code.md#风格检测) * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析) * [代码重审](book/16-Validating-Your-Code.md#代码重审) @@ -217,22 +219,22 @@ ### [第十八章 字符串](book/18-Strings.md) * [字符串的不可变](book/18-Strings.md#字符串的不可变) - * [重载和StringBuilder](book/18-Strings.md#重载和StringBuilder) + * [+ 的重载与 StringBuilder](book/18-Strings.md#+-的重载与-StringBuilder) * [意外递归](book/18-Strings.md#意外递归) * [字符串操作](book/18-Strings.md#字符串操作) * [格式化输出](book/18-Strings.md#格式化输出) - * [常规表达式](book/18-Strings.md#常规表达式) + * [正则表达式](book/18-Strings.md#正则表达式) * [扫描输入](book/18-Strings.md#扫描输入) * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) * [本章小结](book/18-Strings.md#本章小结) ### [第十九章 类型信息](book/19-Type-Information.md) - * [运行时类型信息](book/19-Type-Information.md#运行时类型信息) - * [类的对象](book/19-Type-Information.md#类的对象) + * [为什么需要 RTTI](book/19-Type-Information.md#为什么需要-RTTI) + * [Class 对象](book/19-Type-Information.md#Class-对象) * [类型转换检测](book/19-Type-Information.md#类型转换检测) * [注册工厂](book/19-Type-Information.md#注册工厂) * [类的等价比较](book/19-Type-Information.md#类的等价比较) - * [反射运行时类信息](book/19-Type-Information.md#反射运行时类信息) + * [反射:运行时类信息](book/19-Type-Information.md#反射:运行时类信息) * [动态代理](book/19-Type-Information.md#动态代理) * [Optional类](book/19-Type-Information.md#Optional类) * [接口和类型](book/19-Type-Information.md#接口和类型) @@ -242,20 +244,21 @@ * [简单泛型](book/20-Generics.md#简单泛型) * [泛型接口](book/20-Generics.md#泛型接口) * [泛型方法](book/20-Generics.md#泛型方法) - * [复杂模型构建](book/20-Generics.md#复杂模型构建) + * [构建复杂模型](book/20-Generics.md#构建复杂模型) * [泛型擦除](book/20-Generics.md#泛型擦除) * [补偿擦除](book/20-Generics.md#补偿擦除) * [边界](book/20-Generics.md#边界) * [通配符](book/20-Generics.md#通配符) * [问题](book/20-Generics.md#问题) - * [自我约束类型](book/20-Generics.md#自我约束类型) + * [自限定的类型](book/20-Generics.md#自限定的类型) * [动态类型安全](book/20-Generics.md#动态类型安全) * [泛型异常](book/20-Generics.md#泛型异常) - * [混入](book/20-Generics.md#混入) - * [潜在类型](book/20-Generics.md#潜在类型) - * [补偿不足](book/20-Generics.md#补偿不足) - * [辅助潜在类型](book/20-Generics.md#辅助潜在类型) - * [泛型的优劣](book/20-Generics.md#泛型的优劣) + * [混型](book/20-Generics.md#混型) + * [潜在类型机制](book/20-Generics.md#潜在类型机制) + * [对缺乏潜在类型机制的补偿](book/20-Generics.md#对缺乏潜在类型机制的补偿) + * [Java8 中的辅助潜在类型](book/20-Generics.md#Java8-中的辅助潜在类型) + * [总结:类型转换真的如此之糟吗?](book/20-Generics.md#总结:类型转换真的如此之糟吗?) + * [进阶阅读](book/20-Generics.md#进阶阅读) ### [第二十一章 数组](book/21-Arrays.md) * [数组特性](book/21-Arrays.md#数组特性) @@ -275,22 +278,24 @@ * [数组比较](book/21-Arrays.md#数组比较) * [流和数组](book/21-Arrays.md#流和数组) * [数组排序](book/21-Arrays.md#数组排序) + * [Arrays.sort 的使用](book/21-Arrays.md#Arrayssort-的使用) + * [并行排序](book/21-Arrays.md#并行排序) * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) * [本章小结](book/21-Arrays.md#本章小结) ### [第二十二章 枚举](book/22-Enumerations.md) - * [基本功能](book/22-Enumerations.md#基本功能) + * [基本 enum 特性](book/22-Enumerations.md#基本-enum-特性) * [方法添加](book/22-Enumerations.md#方法添加) - * [switch语句](book/22-Enumerations.md#switch语句) - * [values方法](book/22-Enumerations.md#values方法) + * [switch 语句中的 enum](book/22-Enumerations.md#switch-语句中的-enum) + * [values 方法的神秘之处](book/22-Enumerations.md#values-方法的神秘之处) * [实现而非继承](book/22-Enumerations.md#实现而非继承) * [随机选择](book/22-Enumerations.md#随机选择) - * [使用接口组织](book/22-Enumerations.md#使用接口组织) - * [使用EnumSet替代Flags](book/22-Enumerations.md#使用EnumSet替代Flags) - * [使用EnumMap](book/22-Enumerations.md#使用EnumMap) + * [使用接口组织枚举](book/22-Enumerations.md#使用接口组织枚举) + * [使用 EnumSet 替代 Flags](book/22-Enumerations.md#使用-EnumSet-替代-Flags) + * [使用 EnumMap](book/22-Enumerations.md#使用-EnumMap) * [常量特定方法](book/22-Enumerations.md#常量特定方法) - * [多次调度](book/22-Enumerations.md#多次调度) + * [多路分发](book/22-Enumerations.md#多路分发) * [本章小结](book/22-Enumerations.md#本章小结) ### [第二十三章 注解](book/23-Annotations.md) @@ -303,7 +308,7 @@ ### [第二十四章 并发编程](book/24-Concurrent-Programming.md) * [术语问题](book/24-Concurrent-Programming.md#术语问题) * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) - * [针对速度](book/24-Concurrent-Programming.md#针对速度) + * [并发为速度而生](book/24-Concurrent-Programming.md#并发为速度而生) * [四句格言](book/24-Concurrent-Programming.md#四句格言) * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相) * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分) @@ -312,18 +317,18 @@ * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类) * [死锁](book/24-Concurrent-Programming.md#死锁) - * [构造函数非线程安全](book/24-Concurrent-Programming.md#构造函数非线程安全) + * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) * [本章小结](book/24-Concurrent-Programming.md#本章小结) ### [第二十五章 设计模式](book/25-Patterns.md) * [概念](book/25-Patterns.md#概念) - * [构建型](book/25-Patterns.md#构建型) - * [面向实施](book/25-Patterns.md#面向实施) + * [构建应用程序框架](book/25-Patterns.md#构建应用程序框架) + * [面向实现](book/25-Patterns.md#面向实现) * [工厂模式](book/25-Patterns.md#工厂模式) * [函数对象](book/25-Patterns.md#函数对象) - * [接口改变](book/25-Patterns.md#接口改变) - * [解释器](book/25-Patterns.md#解释器) + * [改变接口](book/25-Patterns.md#改变接口) + * [解释器:运行时的弹性](book/25-Patterns.md#解释器:运行时的弹性) * [回调](book/25-Patterns.md#回调) * [多次调度](book/25-Patterns.md#多次调度) * [模式重构](book/25-Patterns.md#模式重构) @@ -333,58 +338,29 @@ * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) * [本章小结](book/25-Patterns.md#本章小结) -### [附录:补充](book/Appendix-Supplements.md) - * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) - * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) - * [动手实践](book/Appendix-Supplements.md#动手实践) - -### [附录:编程指南](book/Appendix-Programming-Guidelines.md) - * [设计](book/Appendix-Programming-Guidelines.md#设计) - * [实现](book/Appendix-Programming-Guidelines.md#实现) - -### [附录:文档注释](book/Appendix-Javadoc.md) - -### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) - * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) - * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) - * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) - * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) - * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) - -### [附录:流式IO](book/Appendix-IO-Streams.md) - * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) - * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) - * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) - * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) - * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) - * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) - * [本章小结](book/Appendix-IO-Streams.md#本章小结) - -### [附录:标准IO](book/Appendix-Standard-IO.md) - * [执行控制](book/Appendix-Standard-IO.md#执行控制) - -### [附录:新IO](book/Appendix-New-IO.md) - * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) - * [转换数据](book/Appendix-New-IO.md#数据转换) - * [获取原始类型](book/Appendix-New-IO.md#基本类型获取) - * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) - * [使用缓冲区进行数据操作](book/Appendix-New-IO.md#缓冲区数据操作) - * [内存映射文件](book/Appendix-New-IO.md#内存映射文件) - * [文件锁定](book/Appendix-New-IO.md#文件锁定) +### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) + * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) + * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) + * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) + * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) + * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) + * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) -### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) - * [equals典范](book/Appendix-Understanding-equals-and-hashCode.md#equals典范) - * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) - * [调整HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调整HashMap) +### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) + * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) + * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) + * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) + * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) + * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) ### [附录:集合主题](book/Appendix-Collection-Topics.md) * [示例数据](book/Appendix-Collection-Topics.md#示例数据) - * [List表现](book/Appendix-Collection-Topics.md#List表现) - * [Set表现](book/Appendix-Collection-Topics.md#Set表现) + * [List行为](book/Appendix-Collection-Topics.md#List行为) + * [Set行为](book/Appendix-Collection-Topics.md#Set行为) * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作) - * [选择Map的部分](book/Appendix-Collection-Topics.md#选择Map的部分) - * [集合的fill方法](book/Appendix-Collection-Topics.md#集合的fill方法) - * [使用Flyweight自定义集合和Map](book/Appendix-Collection-Topics.md#使用Flyweight自定义集合和Map) + * [选择Map片段](book/Appendix-Collection-Topics.md#选择Map片段) + * [填充集合](book/Appendix-Collection-Topics.md#填充集合) + * [使用享元(Flyweight)自定义Collection和Map](book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) * [集合功能](book/Appendix-Collection-Topics.md#集合功能) * [可选操作](book/Appendix-Collection-Topics.md#可选操作) * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序) @@ -392,44 +368,81 @@ * [理解Map](book/Appendix-Collection-Topics.md#理解Map) * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类) * [持有引用](book/Appendix-Collection-Topics.md#持有引用) - * [避免旧式类库](book/Appendix-Collection-Topics.md#避免旧式类库) + * [Java 1.0 / 1.1 的集合类](book/Appendix-Collection-Topics.md#Java-10-11-的集合类) * [本章小结](book/Appendix-Collection-Topics.md#本章小结) +### [附录:数据压缩](book/Appendix-Data-Compression.md) + * [使用 Gzip 简单压缩](book/Appendix-Data-Compression.md#使用-Gzip-简单压缩) + * [使用 zip 多文件存储](book/Appendix-Data-Compression.md#使用-zip-多文件存储) + * [Java 的 jar](book/Appendix-Data-Compression.md#Java-的-jar) + +### [附录:流式IO](book/Appendix-IO-Streams.md) + * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) + * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) + * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) + * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) + * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) + * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) + * [本章小结](book/Appendix-IO-Streams.md#本章小结) + +### [附录:文档注释](book/Appendix-Javadoc.md) + * [句法规则](book/Appendix-Javadoc.md#句法规则) + * [内嵌 HTML](book/Appendix-Javadoc.md#内嵌-HTML) + * [示例标签](book/Appendix-Javadoc.md#示例标签) + * [文档示例](book/Appendix-Javadoc.md#文档示例) + ### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) - * [线程](book/Appendix-Low-Level-Concurrency.md#线程) + * [什么是线程?](book/Appendix-Low-Level-Concurrency.md#什么是线程?) * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) - * [volatile关键字](book/Appendix-Low-Level-Concurrency.md#volatile关键字) + * [volatile 关键字](book/Appendix-Low-Level-Concurrency.md#volatile-关键字) * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性) - * [关键部分](book/Appendix-Low-Level-Concurrency.md#关键部分) + * [临界区](book/Appendix-Low-Level-Concurrency.md#临界区) * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) -### [附录:数据压缩](book/Appendix-Data-Compression.md) - * [使用Gzip简单压缩](book/Appendix-Data-Compression.md#使用Gzip简单压缩) - * [使用zip多文件存储](book/Appendix-Data-Compression.md#使用zip多文件存储) - * [Java的jar](book/Appendix-Data-Compression.md#Java的jar) +### [附录:新IO](book/Appendix-New-IO.md) + * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) + * [数据转换](book/Appendix-New-IO.md#数据转换) + * [基本类型获取](book/Appendix-New-IO.md#基本类型获取) + * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) + * [缓冲区数据操作](book/Appendix-New-IO.md#缓冲区数据操作) + * [ 内存映射文件](book/Appendix-New-IO.md#-内存映射文件) + * [文件锁定](book/Appendix-New-IO.md#文件锁定) ### [附录:对象序列化](book/Appendix-Object-Serialization.md) * [查找类](book/Appendix-Object-Serialization.md#查找类) * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) + * [XML](book/Appendix-Object-Serialization.md#XML) -### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) - * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) - * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) - * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) - * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) - * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) +### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) + * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) + * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) + * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) + * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) + * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) + +### [附录:编程指南](book/Appendix-Programming-Guidelines.md) + * [设计](book/Appendix-Programming-Guidelines.md#设计) + * [实现](book/Appendix-Programming-Guidelines.md#实现) + +### [附录:标准IO](book/Appendix-Standard-IO.md) + * [从标准输入中读取](book/Appendix-Standard-IO.md#从标准输入中读取) + * [将 System.out 转换成 PrintWriter](book/Appendix-Standard-IO.md#将-Systemout-转换成-PrintWriter) + * [重定向标准 I/O](book/Appendix-Standard-IO.md#重定向标准-IO) + * [执行控制](book/Appendix-Standard-IO.md#执行控制) + +### [附录:补充](book/Appendix-Supplements.md) + * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) + * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) + * [Hand-On Java 电子演示文稿](book/Appendix-Supplements.md#Hand-On-Java-电子演示文稿) ### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) - * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) - * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) - * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) - * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) - * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) - * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) +### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) + * [equals规范](book/Appendix-Understanding-equals-and-hashCode.md#equals规范) + * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) + * [调优 HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调优-HashMap) -### [词汇表](book/GLOSSARY.md) +### [词汇表](book/GLOSSARY.md) \ No newline at end of file From 46d067656aa8957780f599e9efa6b3973f4fbabb Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Wed, 16 Sep 2020 11:02:52 +0800 Subject: [PATCH 314/371] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9d501d37..e6c333a9 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,6 @@ 如不熟悉 md 排版,可不必纠结,我会在合并 pr 时代为排版 如还有其它问题,欢迎发送 issue,谢谢~ -## 友情链接 - -[Effective.Java.3rd.Edition 中文版](https://sjsdfg.github.io/effective-java-3rd-chinese/#/) - ## 开源协议 本项目基于 MIT 协议开源。 From 177735c7c065c08a3cef9037b28b10b73d872d9b Mon Sep 17 00:00:00 2001 From: arobot Date: Wed, 16 Sep 2020 16:07:31 +0800 Subject: [PATCH 315/371] Update 24-Concurrent-Programming.md (#581) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原文是”Combining parallel() and limit() is for experts only“,强调这两个方法应该是组合操作,而不是这两个方法是留给专家使用 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 9046c59c..127b6827 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -747,7 +747,7 @@ public class ParallelStreamPuzzle3 { 现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 -它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”**parallel()**和**limit()**仅供专家使用(把话说在前面,我不认为自己是这里的专家)。 +它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将**parallel()**和**limit()**结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 - 并行流只看起来很容易 From 7ff61d1ce86d81eca81cf6ed2ebf0f029494bacd Mon Sep 17 00:00:00 2001 From: arobot Date: Wed, 16 Sep 2020 17:09:33 +0800 Subject: [PATCH 316/371] Update 24-Concurrent-Programming.md (#582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 原文”First note there is no SingleThreadExecutor class. newSingleThreadExecutor() is a factory in Executors that creates that particular kind of ExecutorService. “ 2. 顺手修复markdown的一些语法显示。加粗前后尽量跟上空格,不然很多加粗显示异常。 --- docs/book/24-Concurrent-Programming.md | 38 ++++++++++++++------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 127b6827..b8dc1871 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -396,7 +396,7 @@ Sum Iterated: 284ms **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使SZ为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 -如果使用**iterate()**来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,当**SZ**超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()**,但如果你生成的东西不是简单的序列,你必须使用**iterate()**。应用**parallel()**是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: +如果使用**iterate()** 来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,当**SZ**超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()** ,但如果你生成的东西不是简单的序列,你必须使用**iterate()** 。应用**parallel()** 是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: - 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。 - 数组分割成本低,分割均匀且对分割的大小有着完美的掌控。 @@ -554,9 +554,9 @@ Long Parallel: 1008ms** - parallel()/limit()交点 -使用**parallel()**时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 +使用 **parallel()** 时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 -Java 8将两者合并起来。例如,**Collections**没有内置的**map()**操作。在**Collection**和**Map**中唯一类似流的批处理操作是**forEach()**。如果要执行**map()**和**reduce()**等操作,必须首先将**Collection**转换为存在这些操作的**Stream**: +Java 8将两者合并起来。例如,**Collections**没有内置的**map()** 操作。在**Collection**和**Map**中唯一类似流的批处理操作是**forEach()** 。如果要执行**map()** 和**reduce()** 等操作,必须首先将**Collection**转换为存在这些操作的**Stream**: ```java // concurrent/CollectionIntoStream.java @@ -595,9 +595,9 @@ bynxt :PENCUXGVGINNLOZVEWPPCPOALJLNXT ``` -**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()**和**retainAll()**,但这些都是破坏性的操作。**ConcurrentHashMap**对**forEach**和**reduce**操作有特别广泛的支持。 +**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()** 和**retainAll()** ,但这些都是破坏性的操作。**ConcurrentHashMap**对**forEach**和**reduce**操作有特别广泛的支持。 -在许多情况下,只在集合上调用**stream()**或者**parallelStream()**没有问题。但是,有时将**Stream**与**Collection**混合会产生意想不到的结果。这是一个有趣的难题: +在许多情况下,只在集合上调用**stream()** 或者**parallelStream()** 没有问题。但是,有时将**Stream**与**Collection**混合会产生意想不到的结果。这是一个有趣的难题: ```java // concurrent/ParallelStreamPuzzle.java @@ -672,11 +672,12 @@ public class ParallelStreamPuzzle2 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()**允许多个线程调用**get()**。 +current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()** 允许多个线程调用**get()**。 -在查看 **PSP2.txt**.**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。 +在查看 **PSP2.txt** .**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。 -**0: main +``` +0: main 1: ForkJoinPool.commonPool-worker-1 2: ForkJoinPool.commonPool-worker-2 3: ForkJoinPool.commonPool-worker-2 @@ -698,7 +699,8 @@ current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞 20: ForkJoinPool.commonPool-worker-110 21: ForkJoinPool.commonPool-worker-110 22: ForkJoinPool.commonPool-worker-110 -23: ForkJoinPool.commonPool-worker-1** +23: ForkJoinPool.commonPool-worker-1 +``` 这个块大小似乎是内部实现的一部分(尝试使用`limit()` 的不同参数来查看不同的块大小)。将`parallel()`与`limit()`结合使用可以预取一串值,作为流输出。 @@ -741,17 +743,17 @@ public class ParallelStreamPuzzle3 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -为了表明**parallel()**确实有效,我添加了一个对**peek()**的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 +为了表明**parallel()**确实有效,我添加了一个对**peek()** 的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -你还可以看到**boxed()**的添加,它接受**int**流并将其转换为**Integer**流。 +你还可以看到**boxed()** 的添加,它接受**int**流并将其转换为**Integer**流。 现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 -它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将**parallel()**和**limit()**结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 +它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将**parallel()** 和**limit()** 结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 - 并行流只看起来很容易 -实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将**parallel()**加到你的Stream操作上并不一定是安全的事情。在使用**parallel()**之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 +实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将**parallel()** 加到你的Stream操作上并不一定是安全的事情。在使用**parallel()** 之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 ## 创建和运行任务 @@ -788,7 +790,7 @@ public class NapTask implements Runnable { } ``` -这只是一个**Runnable**:一个包含**run()**方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”: +这只是一个**Runnable**:一个包含**run()** 方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”: ```java // onjava/Nap.java @@ -810,9 +812,9 @@ public class Nap { ``` 为了消除异常处理的视觉干扰,这被定义为实用程序。第二个构造函数在超时时显示一条消息 -对**TimeUnit.MILLISECONDS.sleep()**的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()**是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 +对**TimeUnit.MILLISECONDS.sleep()** 的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()** 是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 -你可以看到**sleep()**抛出一个受检的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 +你可以看到**sleep()** 抛出一个受检的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 要执行任务,我们将从最简单的方法--SingleThreadExecutor开始: @@ -867,7 +869,7 @@ main awaiting termination NapTask[9] pool-1-thread-1 ``` -首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()**是**Executors**中的工厂,它创建特定类型的[^4] +首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()** 是 **Executors** 中的一个工厂方法,它创建特定类型的**ExecutorService**[^4] 我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。 @@ -932,7 +934,7 @@ public class MoreTasksAfterShutdown { java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1 ``` -**exec.shutdown()**的替代方法是**exec.shutdownNow()**,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 +**exec.shutdown()** 的替代方法是**exec.shutdownNow()** ,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 - 使用更多线程 From 004ebc98aadc607f45f6b444debd7375f050135a Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Wed, 16 Sep 2020 17:12:05 +0800 Subject: [PATCH 317/371] Update 24-Concurrent-Programming.md (#583) --- docs/book/24-Concurrent-Programming.md | 440 ++++++++++++------------- 1 file changed, 220 insertions(+), 220 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index b8dc1871..ab5d177a 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -3,16 +3,16 @@ # 第二十四章 并发编程 ->爱丽丝:“我可不想到疯子中间去” +> 爱丽丝:“我可不想到疯子中间去” > ->猫咪:“啊,那没辙了,我们这都是疯子。我疯了,你也疯了” +> 猫咪:“啊,那没辙了,我们这都是疯子。我疯了,你也疯了” > ->爱丽丝:“你怎么知道我疯了”。 +> 爱丽丝:“你怎么知道我疯了”。 > ->猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第6章。 +> 猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第 6 章。 -在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为5,再看时它已变成47的话,结果就很匪夷所思了。 +在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为 5,再看时它已变成 47 的话,结果就很匪夷所思了。 现在,我们来到了陌生的并发世界。这样的结果一点都不奇怪,因为你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 @@ -20,11 +20,11 @@ 假设我们处在多条故事线并行的间谍小说里,非单一意识流地叙事:第一个间谍在岩石底留下了微缩胶片。当第二个间谍来取时,胶片可能已被第三个间谍拿走。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 -构建并发程序好比玩[搭积木](https://en.wikipedia.org/wiki/Jenga)游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 +构建并发程序好比玩[搭积木 ](https://en.wikipedia.org/wiki/Jenga) 游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 本章是对并发概念最基本的介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但还远未及全面论述并发。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 -更多繁琐和底层的细节,请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 +更多繁琐和底层的细节,请参阅附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 @@ -42,7 +42,7 @@ “并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 -还有另一种方法,在减速发生的地方写下定义(原文Here’s another approach, writing the definitions around where the +还有另一种方法,在减速发生的地方写下定义(原文 Here’s another approach, writing the definitions around where the slowdown occurs): **并发** @@ -53,36 +53,36 @@ slowdown occurs): 同时在多个位置完成多任务。这解决了所谓的 CPU 密集型问题:将程序分为多部分,在多个处理器上同时处理不同部分来加快程序执行效率。 -上面的定义说明了这两个术语令人困惑的原因:两者的核心都是“同时完成多个任务”,不过并行增加了跨多个处理器的分布。更重要的是,它们可以解决不同类型的问题:并行可能对解决I / O密集型问题没有任何好处,因为问题不在于程序的整体执行速度,而在于I/O阻塞。而尝试在单个处理器上使用并发来解决计算密集型问题也可能是浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式有所不同,这取决于问题施加的约束。 +上面的定义说明了这两个术语令人困惑的原因:两者的核心都是“同时完成多个任务”,不过并行增加了跨多个处理器的分布。更重要的是,它们可以解决不同类型的问题:并行可能对解决 I / O 密集型问题没有任何好处,因为问题不在于程序的整体执行速度,而在于 I/O 阻塞。而尝试在单个处理器上使用并发来解决计算密集型问题也可能是浪费时间。两种方法都试图在更短的时间内完成更多工作,但是它们实现加速的方式有所不同,这取决于问题施加的约束。 -这两个概念混合在一起的一个主要原因是包括Java在内的许多编程语言使用相同的机制 - **线程**来实现并发和并行。 +这两个概念混合在一起的一个主要原因是包括 Java 在内的许多编程语言使用相同的机制 - **线程**来实现并发和并行。 我们甚至可以尝试以更细的粒度去进行定义(然而这并不是标准化的术语): -- **纯并发**:仍然在单个CPU上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 +- **纯并发**:仍然在单个 CPU 上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 - **并发-并行**:使用并发技术,结果程序可以利用更多处理器更快地产生结果。 -- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams**就是一个很好的例子)。 +- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams** 就是一个很好的例子)。 - **纯并行**:除非有多个处理器,否则不会运行。 在某些情况下,这可能是一个有用的分类法。 -支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction)一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 +支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction) 一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 -我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于CPU缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是Java特有的 - 这是并发和并行编程的本质。 +我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于 CPU 缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是 Java 特有的 - 这是并发和并行编程的本质。 -你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 +你可能会认为[纯函数式 ](https://en.wikipedia.org/wiki/Purely_functional) 语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 ### 并发的新定义 几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它: ->**并发性是一系列性能技术,专注于减少等待** +>** 并发性是一系列性能技术,专注于减少等待** 这实际上是一个相当复杂的表述,所以我将其分解: - 这是一个集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差异很大。 -- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在Java中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 -- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起I/O请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 +- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在 Java 中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 +- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起 I/O 请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 值得强调的是,这个定义的有效性取决于“等待”这个词。如果没有什么可以等待,那就没有机会去加速。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 @@ -95,7 +95,7 @@ slowdown occurs): 现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。 -每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出50个自己来搜索这间房间。 +每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出 50 个自己来搜索这间房间。 一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。 @@ -103,7 +103,7 @@ slowdown occurs): 以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 -让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有8个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 +让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有 8 个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 许多模型能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 @@ -126,7 +126,7 @@ slowdown occurs): 当“同时”执行的任务相互干扰时,会出现问题。它可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,我们编写的并发程序似乎都能正常工作,但是在适当的条件下,将会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 -因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管Java 8在并发性方面做出了很大改进,但仍然没有像编译时验证(compile-time verification)或受检查的异常(checked exceptions)那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用Java编写可靠的并发代码。 +因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管 Java 8 在并发性方面做出了很大改进,但仍然没有像编译时验证 (compile-time verification) 或受检查的异常 (checked exceptions) 那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用 Java 编写可靠的并发代码。 @@ -136,28 +136,28 @@ slowdown occurs): 速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学会如何利用那些额外的处理器,这是并发性给你的一个建议。 -使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器Web服务器通常就是这种情况,它可以在程序中为CPU分配大量用户请求,每个请求分配一个线程。 +使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器 Web 服务器通常就是这种情况,它可以在程序中为 CPU 分配大量用户请求,每个请求分配一个线程。 但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。 -可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 +可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是 I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些长时间运行操作,从而最终忽略用户输入和无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中轮询它。这会产生笨拙的代码,无法保证程序员不会忘记执行检查。没有并发性,生成响应式用户界面的唯一方法是让所有任务定期检查用户输入。通过创建单独的执行线程来响应用户输入,该程序保证了一定程度的响应。 -实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和I/O等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 +实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和 I/O 等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,从而阻止了在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”) -一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 +一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 -Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 +Java 采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;除此以外,你必须特别关注那些使用了并发操作的代码。 ## 四句格言 -在经历了多年的Java并发之后,我总结了以下四个格言: +在经历了多年的 Java 并发之后,我总结了以下四个格言: >1.不要这样做 > >2.没有什么是真的,一切可能都有问题 @@ -166,7 +166,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 > >4.你仍然必须理解它 -这些格言专门针对Java的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 +这些格言专门针对 Java 的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 ### 1.不要这样做 @@ -186,7 +186,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 在非并发程序中你可以忽略的各种事情在并发程序中突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须深入了解对象构造的复杂性,以便你的构造器不会意外地将数据暴露给其他线程进行更改。问题还有很多。 -因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见Java Concurrency in Practice),但你必须了解它们。 +因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见 Java Concurrency in Practice),但你必须了解它们。 ### 3.它起作用,并不意味着它没有问题 @@ -197,28 +197,28 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 - 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。 - 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。 -在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 +在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect) 效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码前了解所有并发问题。 -在Java的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 +在 Java 的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 ### 4.你必须仍然理解 -在格言1-3之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 +在格言 1-3 之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 -这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在JVM上运行的程序(从而提供与Java的轻松通信),例如Clojure或Scala。为什么不用这些语言编写并发部分并将Java用于其他所有部分呢? +这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在 JVM 上运行的程序(从而提供与 Java 的轻松通信),例如 Clojure 或 Scala。为什么不用这些语言编写并发部分并将 Java 用于其他所有部分呢? 唉,你不能轻易逃脱: -- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing图形用户界面(GUI)库,或者像**Timer** class那样简单的东西。 +- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing 图形用户界面(GUI)库,或者像 **Timer** class 那样简单的东西。 - 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? 人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。 -唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在Java程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web系统是最常见的Java应用程序之一,本质上是多线程的Web服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 +唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在 Java 程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web 系统是最常见的 Java 应用程序之一,本质上是多线程的 Web 服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 -Java是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多Java程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 +Java 是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多 Java 程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 ## 残酷的真相 @@ -229,51 +229,51 @@ Java是一种多线程语言,不管你有没有意识到并发问题,它就 有了这种根本性的人类变化,看到许多破坏和失败的实验并不令人惊讶。实际上,进化依赖于无数的实验,其中大多数都失败了。这些实验是向前发展的必要条件。 -Java是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易感觉语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气态阶段,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 +Java 是在充满自信,热情和睿智的氛围中创建的。在发明一种编程语言时,很容易感觉语言的初始可塑性会持续存在一样,你可以把某些东西拿出来,如果不能解决问题,那么就修复它。编程语言以这种方式是独一无二的 - 它们经历了类似水的改变:气态,液态和最终的固态。在气态阶段,灵活性似乎是无限的,并且很容易认为它总是那样。一旦人们开始使用你的语言,变化就会变得更加严重,环境变得更加粘稠。语言设计的过程本身就是一门艺术。 -紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript和PHP等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 +紧迫感来自互联网的最初兴起。它似乎是一场比赛,第一个通过起跑线的人将“获胜”(事实上,Java,JavaScript 和 PHP 等语言的流行程度可以证明这一点)。唉,通过匆忙设计语言而产生的认知负荷和技术债务最终会赶上我们。 -[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness)是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 +[Turing completeness](https://en.wikipedia.org/wiki/Turing_completeness) 是不足够的;语言需要更多的东西:它们必须能够创造性地表达,而不是用不必要的东西来衡量我们。解放我们的心理能力只是为了扭转并再次陷入困境,这是毫无意义的。我承认,尽管存在这些问题,我们已经完成了令人惊奇的事情,但我也知道如果没有这些问题我们能做得更多。 -热情使原始Java设计师加入了一些似乎有必要的特性。信心(以及气态的初始语言)让他们认为任何问题随后都可以解决。在时间轴的某个地方,有人认为任何加入Java的东西是固定的和永久性的 -他们非常有信心,并相信第一个决定永远是正确的,因此我们看到Java的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用Vector,但只能在语言中继续保留它以便对之前版本的支持。 +热情使原始 Java 设计师加入了一些似乎有必要的特性。信心(以及气态的初始语言)让他们认为任何问题随后都可以解决。在时间轴的某个地方,有人认为任何加入 Java 的东西是固定的和永久性的 -他们非常有信心,并相信第一个决定永远是正确的,因此我们看到 Java 的体系中充斥着糟糕的决策。其中一些决定最终没有什么后果;例如,你可以告诉人们不要使用 Vector,但只能在语言中继续保留它以便对之前版本的支持。 -线程包含在Java 1.0中。当然,对java来说支持并发是一个很基本的设计决定,该特性影响了这个语言的各个角落,我们很难想象以后在以后的版本添加它。公平地说,当时并不清楚基本的并发性是多少。像C这样的其他语言能够将线程视为一个附加功能,因此Java设计师也纷纷效仿,包括一个Thread类和必要的JVM支持(这比你想象的要复杂得多)。 +线程包含在 Java 1.0 中。当然,对 java 来说支持并发是一个很基本的设计决定,该特性影响了这个语言的各个角落,我们很难想象以后在以后的版本添加它。公平地说,当时并不清楚基本的并发性是多少。像 C 这样的其他语言能够将线程视为一个附加功能,因此 Java 设计师也纷纷效仿,包括一个 Thread 类和必要的 JVM 支持(这比你想象的要复杂得多)。 -C语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java的大规模扩展迅速暴露了基本问题。在Thread类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。 +C 语言是面向过程语言,这限制了它的野心。这些限制使附加线程库合理。当采用原始模型并将其粘贴到复杂语言中时,Java 的大规模扩展迅速暴露了基本问题。在 Thread 类中的许多方法的弃用以及后续的高级库浪潮中,这种情况变得明显,这些库试图提供更好的并发抽象。 -不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的Java设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 +不幸的是,为了在更高级别的语言中获得并发性,所有语言功能都会受到影响,包括最基本的功能,例如标识符代表可变值。在简化并发编程中,所有函数和方法中为了保持事物不变和防止副作用都要做出巨大的改变(这些是纯函数式编程语言的基础),但当时对于主流语言的创建者来说似乎是奇怪的想法。最初的 Java 设计师要么没有意识到这些选择,要么认为它们太不同了,并且会劝退许多潜在的语言使用者。我们可以慷慨地说,语言设计社区当时根本没有足够的经验来理解调整在线程库中的影响。 -Java实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为Java 线程并不那么困难的陷阱。表面上看起来正常工作的程序实际上充满了微妙的并发bug。 +Java 实验告诉我们,结果是悄然灾难性的。程序员很容易陷入认为 Java 线程并不那么困难的陷阱。表面上看起来正常工作的程序实际上充满了微妙的并发 bug。 为了获得正确的并发性,语言功能必须从头开始设计并考虑并发性。木已成舟;Java 将不再是为并发而设计的语言,而只是一种允许并发的语言。 -尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在Java 8中进行改进:并行流和**CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 +尽管有这些基本的不可修复的缺陷,但令人印象深刻的是它已经走了这么远。Java 的后续版本添加了库,以便在使用并发时提升抽象级别。事实上,我根本不会想到有可能在 Java 8 中进行改进:并行流和 **CompletableFutures** - 这是惊人的史诗般的变化,我会惊奇地重复的查看它[^3]。 -这些改进非常有用,我们将在本章重点介绍并行流和**CompletableFutures**。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于Java的原始设计,代码的所有部分仍然很脆弱,你仍然必须理解这些复杂和微妙的问题。Java中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 +这些改进非常有用,我们将在本章重点介绍并行流和 **CompletableFutures** 。虽然它们可以大大简化你对并发和后续代码的思考方式,但基本问题仍然存在:由于 Java 的原始设计,代码的所有部分仍然很脆弱,你仍然必须理解这些复杂和微妙的问题。Java 中的线程绝不是简单或安全的;那种经历必须降级为另一种更新的语言。 ## 本章其余部分 -这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级Java并发结构。相比于旧的替代品,使用这些会使你的生活更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)包含一些更原始的Java并发元素的介绍。 +这是我们将在本章的其余部分介绍的内容。请记住,本章的重点是使用最新的高级 Java 并发结构。相比于旧的替代品,使用这些会使你的生活更加轻松。但是,你仍会在遗留代码中遇到一些低级工具。有时,你可能会被迫自己使用其中的一些。附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md) 包含一些更原始的 Java 并发元素的介绍。 - Parallel Streams(并行流) -到目前为止,我已经强调了Java 8 Streams提供的改进语法。现在该语法(作为一个粉丝,我希望)会使你感到舒适,你可以获得额外的好处:你可以通过简单地将parallel()添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 +到目前为止,我已经强调了 Java 8 Streams 提供的改进语法。现在该语法(作为一个粉丝,我希望)会使你感到舒适,你可以获得额外的好处:你可以通过简单地将 parallel() 添加到表达式来并行化流。这是一种简单,强大,坦率地说是利用多处理器的惊人方式 -添加parallel()来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相](#The-Brutal-Truth)中学到的那样简单。我将演示并解释一些盲目添加parallel()到Stream表达式的缺陷。 +添加 parallel() 来提高速度似乎是微不足道的,但是,唉,它就像你刚刚在[残酷的真相 ](#The-Brutal-Truth) 中学到的那样简单。我将演示并解释一些盲目添加 parallel() 到 Stream 表达式的缺陷。 - 创建和运行任务 -任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或CompletableFutures更简单的机制:Executor。执行者管理一些低级Thread对象(Java中最原始的并发形式)。你创建一个任务,然后将其交给Executor去运行。 +任务是一段可以独立运行的代码。为了解释创建和运行任务的一些基础知识,本节介绍了一种比并行流或 CompletableFutures 更简单的机制:Executor。执行者管理一些低级 Thread 对象(Java 中最原始的并发形式)。你创建一个任务,然后将其交给 Executor 去运行。 -有多种类型的Executor用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 +有多种类型的 Executor 用于不同的目的。在这里,我们将展示规范形式,代表创建和运行任务的最简单和最佳方法。 - 终止长时间运行的任务 -任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用Java的“Atomic”库来回避它。 +任务独立运行,因此需要一种机制来关闭它们。典型的方法使用了一个标志,这引入了共享内存的问题,我们将使用 Java 的“Atomic”库来回避它。 - Completable Futures -当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,当你的衣服洗干净时你可以把它取走。收据是你与干洗店在后台执行的任务的连接。这是Java 5中引入的Future的方法。 +当你将衣服带到干洗店时,他们会给你一张收据。你继续完成其他任务,当你的衣服洗干净时你可以把它取走。收据是你与干洗店在后台执行的任务的连接。这是 Java 5 中引入的 Future 的方法。 -Future比以前的方法更方便,但你仍然必须出现并用收据取出干洗,如果任务没有完成你还需要等待。对于一系列操作,Futures并没有真正帮助那么多。 +Future 比以前的方法更方便,但你仍然必须出现并用收据取出干洗,如果任务没有完成你还需要等待。对于一系列操作,Futures 并没有真正帮助那么多。 -Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了CompletableFuture完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,收拾餐具,储存餐具”等一系列链式操作。 +Java 8 CompletableFuture 是一个更好的解决方案:它允许你将操作链接在一起,因此你不必将代码写入接口排序操作。有了 CompletableFuture 完美的结合,就可以更容易地做出“采购原料,组合成分,烹饪食物,提供食物,收拾餐具,储存餐具”等一系列链式操作。 - 死锁 某些任务必须去**等待 - 阻塞**来获得其他任务的结果。被阻止的任务有可能等待另一个被阻止的任务,另一个被阻止的任务也在等待其他任务,等等。如果被阻止的任务链循环到第一个,没有人可以取得任何进展,你就会陷入死锁。 @@ -284,14 +284,14 @@ Java 8 CompletableFuture是一个更好的解决方案:它允许你将操作 * 努力,复杂,成本 -我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是CompletableFutures。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来使你的程序变得更快。 +我们将通过模拟创建披萨的过程完成本章,首先使用并行流实现它,然后是 CompletableFutures。这不仅仅是两种方法的比较,更重要的是探索你应该投入多少工作来使你的程序变得更快。 ## 并行流 -Java 8流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用Streams编写的,那么并行化以提高速度似乎是一种琐事 +Java 8 流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为 Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用 Streams 编写的,那么并行化以提高速度似乎是一种琐事 -例如,考虑来自Streams的Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: +例如,考虑来自 Streams 的 Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: ```java // concurrent/ParallelPrime.java @@ -330,19 +330,19 @@ public class ParallelPrime { 1224 ``` -请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止编译器过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且终止了计算(这不太可能,但并非不可能)。请注意使用nio2库编写文件的简单性(在[文件](./17-Files.md)一章中有描述)。 +请注意,这不是微基准测试,因为我们计时整个程序。我们将数据保存在磁盘上以防止编译器过激的优化;如果我们没有对结果做任何事情,那么一个高级的编译器可能会观察到程序没有意义并且终止了计算(这不太可能,但并非不可能)。请注意使用 nio2 库编写文件的简单性(在[文件 ](./17-Files.md) 一章中有描述)。 -当我注释掉[1] parallel()行时,我的结果用时大约是parallel()的三倍。 +当我注释掉[1] parallel() 行时,我的结果用时大约是 parallel() 的三倍。 -并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入parallel()以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 +并行流似乎是一个甜蜜的交易。你所需要做的就是将编程问题转换为流,然后插入 parallel() 以加快速度。实际上,有时候这很容易。但遗憾的是,有许多陷阱。 -- parallel()不是灵丹妙药 +- parallel() 不是灵丹妙药 -作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:对增长的数字序列进行求和。事实证明有大量的方式去实现它,并且我将冒险用计时器将它们进行比较 - 我会尽量小心,但我承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如JVM没有“热身”),但我认为它仍然提供了一些有用的指示。 +作为对流和并行流的不确定性的探索,让我们看一个看似简单的问题:对增长的数字序列进行求和。事实证明有大量的方式去实现它,并且我将冒险用计时器将它们进行比较 - 我会尽量小心,但我承认我可能会在计时代码执行时遇到许多基本陷阱之一。结果可能有一些缺陷(例如 JVM 没有“热身”),但我认为它仍然提供了一些有用的指示。 -我将从一个计时方法**timeTest()**开始,它采用**LongSupplier**,测量**getAsLong()**调用的长度,将结果与**checkValue**进行比较并显示结果。 +我将从一个计时方法 **timeTest()** 开始,它采用 **LongSupplier** ,测量 **getAsLong()** 调用的长度,将结果与 **checkValue** 进行比较并显示结果。 -请注意,一切都必须严格使用**long**;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了**long**。 +请注意,一切都必须严格使用 **long** ;我花了一些时间发现隐蔽的溢出,然后才意识到在重要的地方错过了 **long** 。 所有关于时间和内存的数字和讨论都是指“我的机器”。你的经历可能会有所不同。 @@ -392,21 +392,21 @@ Sum Stream Parallel: 46ms Sum Iterated: 284ms ``` -**CHECK**值是使用Carl Friedrich Gauss(高斯)在1700年代后期还在上小学的时候创建的公式计算出来的. +**CHECK** 值是使用 Carl Friedrich Gauss(高斯)在 1700 年代后期还在上小学的时候创建的公式计算出来的. - **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使SZ为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 + **main()** 的第一个版本使用直接生成 **Stream** 并调用 **sum()** 的方法。我们看到流的好处在于即使 SZ 为十亿(1_000_000_000)程序也可以很好地处理而没有溢出(为了让程序运行得快一点,我使用了较小的数字)。使用 **parallel()** 的基本范围操作明显更快。 -如果使用**iterate()** 来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用lambda。但是如果我们尝试并行化,当**SZ**超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用**range()**时,你不会使用**iterate()** ,但如果你生成的东西不是简单的序列,你必须使用**iterate()** 。应用**parallel()** 是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: +如果使用 **iterate()** 来生成序列,则减速是相当明显的,可能是因为每次生成数字时都必须调用 lambda。但是如果我们尝试并行化,当 **SZ** 超过一百万时,结果不仅比非并行版本花费的时间更长,而且也会耗尽内存(在某些机器上)。当然,当你可以使用 **range()** 时,你不会使用 **iterate()** ,但如果你生成的东西不是简单的序列,你必须使用 **iterate()** 。应用 **parallel()** 是一个合理的尝试,但会产生令人惊讶的结果。我们将在后面的部分中探讨内存限制的原因,但我们可以对流并行算法进行初步观察: - 流并行性将输入数据分成多个部分,因此算法可以应用于那些单独的部分。 - 数组分割成本低,分割均匀且对分割的大小有着完美的掌控。 - 链表没有这些属性;“拆分”一个链表仅仅意味着把它分成“第一元素”和“其余元素”,这相对无用。 -- 无状态生成器的行为类似于数组;上面使用的 **range()** 就是无状态的。 -- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。 +- 无状态生成器的行为类似于数组;上面使用的 **range()** 就是无状态的。 +- 迭代生成器的行为类似于链表; **iterate()** 是一个迭代生成器。 现在让我们尝试通过在数组中填充值并对数组求和来解决问题。因为数组只分配了一次,所以我们不太可能遇到垃圾收集时序问题。 -首先我们将尝试一个充满原始**long**的数组: +首先我们将尝试一个充满原始 **long** 的数组: ```java // concurrent/Summing2.java @@ -451,9 +451,9 @@ Basic Sum: 106ms parallelPrefix: 265ms ``` -第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。 +第一个限制是内存大小;因为数组是预先分配的,所以我们不能创建几乎与以前版本一样大的任何东西。并行化可以加快速度,甚至比使用 **basicSum()** 循环更快。有趣的是, **Arrays.parallelPrefix()** 似乎实际上减慢了速度。但是,这些技术中的任何一种在其他条件下都可能更有用 - 这就是为什么你不能做出任何确定性的声明,除了“你必须尝试一下”。 -最后,考虑使用包装类**Long**的效果: +最后,考虑使用包装类 **Long** 的效果: ```java // concurrent/Summing3.java @@ -499,9 +499,9 @@ Sum: 21ms Long parallelPrefix: 3616ms ``` -现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了**basicSum()**,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。 +现在可用的内存量大约减半,并且所有情况下所需的时间都会很长,除了 **basicSum()** ,它只是循环遍历数组。令人惊讶的是, **Arrays.parallelPrefix()** 比任何其他方法都要花费更长的时间。 -我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果: +我将 **parallel()** 版本分开了,因为在上面的程序中运行它导致了一个冗长的垃圾收集,扭曲了结果: ```java // concurrent/Summing4.java @@ -528,15 +528,15 @@ public class Summing4 { Long Parallel: 1014ms ``` -它比非parallel()版本略快,但并不显着。 +它比非 parallel() 版本略快,但并不显着。 -导致时间增加的一个重要原因是处理器内存缓存。使用**Summing2.java**中的原始**long**,数组**la**是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为**Long**生成一个超出缓存的引用。 +导致时间增加的一个重要原因是处理器内存缓存。使用 **Summing2.java** 中的原始 **long** ,数组 **la** 是连续的内存。处理器可以更容易地预测该阵列的使用,并使缓存充满下一个需要的阵列元素。访问缓存比访问主内存快得多。似乎 **Long parallelPrefix** 计算受到影响,因为它为每个计算读取两个数组元素,并将结果写回到数组中,并且每个都为 **Long** 生成一个超出缓存的引用。 -使用**Summing3.java**和**Summing4.java**,**aL**是一个**Long**数组,它不是一个连续的数据数组,而是一个连续的**Long**对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是不在缓存中。 +使用 **Summing3.java** 和 **Summing4.java** ,**aL** 是一个 **Long** 数组,它不是一个连续的数据数组,而是一个连续的 **Long** 对象引用数组。尽管该数组可能会在缓存中出现,但指向的对象几乎总是不在缓存中。 -这些示例使用不同的SZ值来显示内存限制。 +这些示例使用不同的 SZ 值来显示内存限制。 -为了进行时间比较,以下是SZ设置为最小值1000万的结果: +为了进行时间比较,以下是 SZ 设置为最小值 1000 万的结果: **Sum Stream: 69msSum Stream Parallel: 18msSum @@ -548,15 +548,15 @@ parallelPrefix: 28ms Long Array Stream Reduce: 1046ms Long Basic Sum: 21ms Long parallelPrefix: 3287ms -Long Parallel: 1008ms** +Long Parallel: 1008ms** -虽然Java 8的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加parallel()并且它会更快!” 我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 +虽然 Java 8 的各种内置“并行”工具非常棒,但我认为它们被视为神奇的灵丹妙药:“只需添加 parallel() 并且它会更快!” 我希望我已经开始表明情况并非所有都是如此,并且盲目地应用内置的“并行”操作有时甚至会使运行速度明显变慢。 -- parallel()/limit()交点 +- parallel()/limit() 交点 -使用 **parallel()** 时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 +使用 **parallel()** 时会有更复杂的问题。从其他语言中吸取的流机制被设计为大约是一个无限的流模型。如果你拥有有限数量的元素,则可以使用集合以及为有限大小的集合设计的关联算法。如果你使用无限流,则使用针对流优化的算法。 -Java 8将两者合并起来。例如,**Collections**没有内置的**map()** 操作。在**Collection**和**Map**中唯一类似流的批处理操作是**forEach()** 。如果要执行**map()** 和**reduce()** 等操作,必须首先将**Collection**转换为存在这些操作的**Stream**: +Java 8 将两者合并起来。例如,**Collections** 没有内置的 **map()** 操作。在 **Collection** 和 **Map** 中唯一类似流的批处理操作是 **forEach()** 。如果要执行 **map()** 和 **reduce()** 等操作,必须首先将 **Collection** 转换为存在这些操作的 **Stream** : ```java // concurrent/CollectionIntoStream.java @@ -595,9 +595,9 @@ bynxt :PENCUXGVGINNLOZVEWPPCPOALJLNXT ``` -**Collection**确实有一些批处理操作,如**removeAll()**,**removeIf()** 和**retainAll()** ,但这些都是破坏性的操作。**ConcurrentHashMap**对**forEach**和**reduce**操作有特别广泛的支持。 +**Collection** 确实有一些批处理操作,如 **removeAll()** ,**removeIf()** 和 **retainAll()** ,但这些都是破坏性的操作。**ConcurrentHashMap** 对 **forEach** 和 **reduce** 操作有特别广泛的支持。 -在许多情况下,只在集合上调用**stream()** 或者**parallelStream()** 没有问题。但是,有时将**Stream**与**Collection**混合会产生意想不到的结果。这是一个有趣的难题: +在许多情况下,只在集合上调用 **stream()** 或者 **parallelStream()** 没有问题。但是,有时将 **Stream** 与 **Collection** 混合会产生意想不到的结果。这是一个有趣的难题: ```java // concurrent/ParallelStreamPuzzle.java @@ -626,13 +626,13 @@ public class ParallelStreamPuzzle { */ ``` -如果[1]注释运行它,它会产生预期的: -**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]** -每次。但是包含了parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如: -**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]** +如果[1] 注释运行它,它会产生预期的: +**[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]** +每次。但是包含了 parallel(),它看起来像一个随机数生成器,带有输出(从一次运行到下一次运行不同),如: +**[0, 3, 6, 8, 11, 14, 17, 20, 23, 26]** 这样一个简单的程序怎么会如此糟糕呢?让我们考虑一下我们在这里要实现的目标:“并行生成。”那意味着什么?一堆线程都在从一个生成器取值,然后以某种方式选择有限的结果集?代码看起来很简单,但它变成了一个特别棘手的问题。 -为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用**ConcurrentLinkedDeque**: +为了看到它,我们将添加一些仪器。由于我们正在处理线程,因此我们必须将任何跟踪信息捕获到并发数据结构中。在这里我使用 **ConcurrentLinkedDeque** : ```java // concurrent/ParallelStreamPuzzle2.java @@ -672,9 +672,9 @@ public class ParallelStreamPuzzle2 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()** 允许多个线程调用**get()**。 +current 是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞争条件;**parallel()** 允许多个线程调用 **get()** 。 -在查看 **PSP2.txt** .**IntGenerator.get()** 被调用1024次时,你可能会感到惊讶。 +在查看 **PSP2.txt** .**IntGenerator.get()** 被调用 1024 次时,你可能会感到惊讶。 ``` 0: main @@ -704,9 +704,9 @@ current是使用线程安全的 **AtomicInteger** 类定义的,可以防止竞 这个块大小似乎是内部实现的一部分(尝试使用`limit()` 的不同参数来查看不同的块大小)。将`parallel()`与`limit()`结合使用可以预取一串值,作为流输出。 -试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用`get()`。添加`limit()`,你说“只需要这些。”基本上,当你为了随机输出而选择将`parallel()`与`limit()`结合使用时,这种方法可能对你正在解决的问题有效。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java弄错了”。 +试着想象一下这里发生了什么:一个流抽象出无限序列,按需生成。当你要求它并行产生流时,你要求所有这些线程尽可能地调用`get()`。添加`limit()`,你说“只需要这些。”基本上,当你为了随机输出而选择将`parallel()`与`limit()`结合使用时,这种方法可能对你正在解决的问题有效。但是当你这样做时,你必须明白。这是一个仅限专家的功能,而不是要争辩说“Java 弄错了”。 -什么是更合理的方法来解决问题?好吧,如果你想生成一个int流,你可以使用IntStream.range(),如下所示: +什么是更合理的方法来解决问题?好吧,如果你想生成一个 int 流,你可以使用 IntStream.range(),如下所示: ```java // concurrent/ParallelStreamPuzzle3.java @@ -743,31 +743,31 @@ public class ParallelStreamPuzzle3 { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ``` -为了表明**parallel()**确实有效,我添加了一个对**peek()** 的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 +为了表明 **parallel()** 确实有效,我添加了一个对 **peek()** 的调用,这是一个主要用于调试的流函数:它从流中提取一个值并执行某些操作但不影响从流向下传递的元素。注意这会干扰线程行为,但我只是尝试在这里做一些事情,而不是实际调试任何东西。 -你还可以看到**boxed()** 的添加,它接受**int**流并将其转换为**Integer**流。 +你还可以看到 **boxed()** 的添加,它接受 **int** 流并将其转换为 **Integer** 流。 -现在我们得到多个线程产生不同的值,但它只产生10个请求的值,而不是1024个产生10个值。 +现在我们得到多个线程产生不同的值,但它只产生 10 个请求的值,而不是 1024 个产生 10 个值。 -它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将**parallel()** 和**limit()** 结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 +它更快吗?一个更好的问题是:什么时候开始有意义?当然不是这么小的一套;上下文切换的代价远远超过并行性的任何加速。很难想象什么时候用并行生成一个简单的数字序列会有意义。如果你要生成的东西需要很高的成本,它可能有意义 - 但这都是猜测。只有通过测试我们才能知道用并行是否有效。记住这句格言:“首先使它工作,然后使它更快地工作 - 只有当你必须这样做时。”将 **parallel()** 和 **limit()** 结合使用仅供专家操作(把话说在前面,我不认为自己是这里的专家)。 - 并行流只看起来很容易 -实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将**parallel()** 加到你的Stream操作上并不一定是安全的事情。在使用**parallel()** 之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 +实际上,在许多情况下,并行流确实可以毫不费力地更快地产生结果。但正如你所见,仅仅将 **parallel()** 加到你的 Stream 操作上并不一定是安全的事情。在使用 **parallel()** 之前,你必须了解并行性如何帮助或损害你的操作。一个基本认知错误就是认为使用并行性总是一个好主意。事实上并不是。Stream 意味着你不需要重写所有代码便可以并行运行它。但是流的出现并不意味着你可以不用理解并行的原理以及不用考虑并行是否真的有助于实现你的目标。 ## 创建和运行任务 -如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想Java 8方法是CompletableFuture,但我们将使用更基本的工具介绍概念。 +如果无法通过并行流实现并发,则必须创建并运行自己的任务。稍后你将看到运行任务的理想 Java 8 方法是 CompletableFuture,但我们将使用更基本的工具介绍概念。 -Java并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发(Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。 +Java 并发的历史始于非常原始和有问题的机制,并且充满了各种尝试的改进。这些主要归入附录:[低级并发 (Appendix: Low-Level Concurrency)](./Appendix-Low-Level-Concurrency.md)。在这里,我们将展示一个规范形式,表示创建和运行任务的最简单,最好的方法。与并发中的所有内容一样,存在各种变体,但这些变体要么降级到该附录,要么超出本书的范围。 - Tasks and Executors -在Java的早期版本中,你通过直接创建自己的Thread对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 +在 Java 的早期版本中,你通过直接创建自己的 Thread 对象来使用线程,甚至将它们子类化以创建你自己的特定“任务线程”对象。你手动调用了构造函数并自己启动了线程。 -创建所有这些线程的开销变得非常重要,现在不鼓励采用手动操作方法。在Java 5中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给ExecutorService以运行该任务,而不是为每种不同类型的任务创建新的Thread子类型。ExecutorService为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 +创建所有这些线程的开销变得非常重要,现在不鼓励采用手动操作方法。在 Java 5 中,添加了类来为你处理线程池。你可以将任务创建为单独的类型,然后将其交给 ExecutorService 以运行该任务,而不是为每种不同类型的任务创建新的 Thread 子类型。ExecutorService 为你管理线程,并且在运行任务后重新循环线程而不是丢弃线程。 -首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: +首先,我们将创建一个几乎不执行任务的任务。它“sleep”(暂停执行)100 毫秒,显示其标识符和正在执行任务的线程的名称,然后完成: ```java // concurrent/NapTask.java @@ -790,7 +790,7 @@ public class NapTask implements Runnable { } ``` -这只是一个**Runnable**:一个包含**run()** 方法的类。它没有包含实际运行任务的机制。我们使用**Nap**类中的“sleep”: +这只是一个 **Runnable** :一个包含 **run()** 方法的类。它没有包含实际运行任务的机制。我们使用 **Nap** 类中的“sleep”: ```java // onjava/Nap.java @@ -812,11 +812,11 @@ public class Nap { ``` 为了消除异常处理的视觉干扰,这被定义为实用程序。第二个构造函数在超时时显示一条消息 -对**TimeUnit.MILLISECONDS.sleep()** 的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS任务管理器定期检查**sleep()** 是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 +对 **TimeUnit.MILLISECONDS.sleep()** 的调用获取“当前线程”并在参数中将其置于休眠状态,这意味着该线程被挂起。这并不意味着底层处理器停止。操作系统将其切换到其他任务,例如在你的计算机上运行另一个窗口。OS 任务管理器定期检查 **sleep()** 是否超时。当它执行时,线程被“唤醒”并给予更多处理时间。 -你可以看到**sleep()** 抛出一个受检的**InterruptedException**;这是原始Java设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 +你可以看到 **sleep()** 抛出一个受检的 **InterruptedException** ;这是原始 Java 设计中的一个工件,它通过突然断开它们来终止任务。因为它往往会产生不稳定的状态,所以后来不鼓励终止。但是,我们必须在需要或仍然发生终止的情况下捕获异常。 -要执行任务,我们将从最简单的方法--SingleThreadExecutor开始: +要执行任务,我们将从最简单的方法--SingleThreadExecutor 开始: ```java //concurrent/SingleThreadExecutor.java @@ -869,13 +869,13 @@ main awaiting termination NapTask[9] pool-1-thread-1 ``` -首先请注意,没有**SingleThreadExecutor**类。**newSingleThreadExecutor()** 是 **Executors** 中的一个工厂方法,它创建特定类型的**ExecutorService**[^4] +首先请注意,没有 **SingleThreadExecutor** 类。**newSingleThreadExecutor()** 是 **Executors** 中的一个工厂方法,它创建特定类型的 **ExecutorService** [^4] -我创建了十个NapTasks并将它们提交给ExecutorService,这意味着它们开始自己运行。然而,在此期间,main()继续做事。当我运行callexec.shutdown()时,它告诉ExecutorService完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出main()之前完成。这是通过检查exec.isTerminated()来实现的,这在所有任务完成后变为true。 +我创建了十个 NapTasks 并将它们提交给 ExecutorService,这意味着它们开始自己运行。然而,在此期间,main() 继续做事。当我运行 callexec.shutdown() 时,它告诉 ExecutorService 完成已经提交的任务,但不接受任何新任务。此时,这些任务仍然在运行,因此我们必须等到它们在退出 main() 之前完成。这是通过检查 exec.isTerminated() 来实现的,这在所有任务完成后变为 true。 -请注意,main()中线程的名称是main,并且只有一个其他线程pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。 +请注意,main() 中线程的名称是 main,并且只有一个其他线程 pool-1-thread-1。此外,交错输出显示两个线程确实同时运行。 -如果你只是调用exec.shutdown(),程序将完成所有任务。也就是说,不需要**while(!exec.isTerminated())**。 +如果你只是调用 exec.shutdown(),程序将完成所有任务。也就是说,不需要 **while(!exec.isTerminated())** 。 ```java // concurrent/SingleThreadExecutor2.java @@ -908,7 +908,7 @@ NapTask[8] pool-1-thread-1 NapTask[9] pool-1-thread-1 ``` -一旦你callexec.shutdown(),尝试提交新任务将抛出RejectedExecutionException。 +一旦你 callexec.shutdown(),尝试提交新任务将抛出 RejectedExecutionException。 ```java // concurrent/MoreTasksAfterShutdown.java @@ -934,11 +934,11 @@ public class MoreTasksAfterShutdown { java.util.concurrent.RejectedExecutionException: TaskNapTask[99] rejected from java.util.concurrent.ThreadPoolExecutor@4e25154f[Shutting down, pool size = 1,active threads = 1, queued tasks = 0, completed tasks =0]NapTask[1] pool-1-thread-1 ``` -**exec.shutdown()** 的替代方法是**exec.shutdownNow()** ,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 +**exec.shutdown()** 的替代方法是 **exec.shutdownNow()** ,它除了不接受新任务外,还会尝试通过中断任务来停止任何当前正在运行的任务。同样,中断是错误的,容易出错并且不鼓励。 - 使用更多线程 -使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用SingleThreadExecutor呢?查看执行**Executors**的Javadoc,你将看到更多选项。例如CachedThreadPool: +使用线程的重点是(几乎总是)更快地完成任务,那么我们为什么要限制自己使用 SingleThreadExecutor 呢?查看执行 **Executors** 的 Javadoc,你将看到更多选项。例如 CachedThreadPool: ```java // concurrent/CachedThreadPool.java @@ -971,7 +971,7 @@ NapTask[6] pool-1-thread-7 NapTask[5] pool-1-thread-6 ``` -当你运行这个程序时,你会发现它完成得更快。这是有道理的,每个任务都有自己的线程,所以它们都并行运行,而不是使用相同的线程来顺序运行每个任务。这似乎没毛病,很难理解为什么有人会使用SingleThreadExecutor。 +当你运行这个程序时,你会发现它完成得更快。这是有道理的,每个任务都有自己的线程,所以它们都并行运行,而不是使用相同的线程来顺序运行每个任务。这似乎没毛病,很难理解为什么有人会使用 SingleThreadExecutor。 要理解这个问题,我们需要一个更复杂的任务: @@ -994,7 +994,7 @@ public class InterferingTask implements Runnable { ``` -每个任务增加val一百次。这似乎很简单。让我们用CachedThreadPool尝试一下: +每个任务增加 val 一百次。这似乎很简单。让我们用 CachedThreadPool 尝试一下: ```java // concurrent/CachedThreadPool2.java @@ -1027,7 +1027,7 @@ public class CachedThreadPool2 { 6 pool-1-thread-7 1000 ``` -输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入val的单个实例,并且他们正在踩着彼此的脚趾。我们称这样的类是线程不安全的。让我们看看SingleThreadExecutor会发生什么: +输出不是我们所期望的,并且从一次运行到下一次运行会有所不同。问题是所有的任务都试图写入 val 的单个实例,并且他们正在踩着彼此的脚趾。我们称这样的类是线程不安全的。让我们看看 SingleThreadExecutor 会发生什么: ```java // concurrent/SingleThreadExecutor3.java @@ -1060,15 +1060,15 @@ public class SingleThreadExecutor3 { 9 pool-1-thread-1 1000 ``` -现在我们每次都得到一致的结果,尽管**InterferingTask**缺乏线程安全性。这是SingleThreadExecutor的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程封闭,因为在单线程上运行任务限制了它们的影响。线程封闭限制了加速,但可以节省很多困难的调试和重写。 +现在我们每次都得到一致的结果,尽管 **InterferingTask** 缺乏线程安全性。这是 SingleThreadExecutor 的主要好处 - 因为它一次运行一个任务,这些任务不会相互干扰,因此强加了线程安全性。这种现象称为线程封闭,因为在单线程上运行任务限制了它们的影响。线程封闭限制了加速,但可以节省很多困难的调试和重写。 - 产生结果 -因为**InterferingTask**是一个**Runnable**,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了**CachedThreadPool2.java**。**InterferingTask**中的**val**被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。 +因为 **InterferingTask** 是一个 **Runnable** ,它没有返回值,因此只能使用副作用产生结果 - 操纵缓冲值而不是返回结果。副作用是并发编程中的主要问题之一,因为我们看到了 **CachedThreadPool2.java** 。**InterferingTask** 中的 **val** 被称为可变共享状态,这就是问题所在:多个任务同时修改同一个变量会产生竞争。结果取决于首先在终点线上执行哪个任务,并修改变量(以及其他可能性的各种变化)。 避免竞争条件的最好方法是避免可变的共享状态。我们可以称之为自私的孩子原则:什么都不分享。 -使用**InterferingTask**,最好删除副作用并返回任务结果。为此,我们创建**Callable**而不是**Runnable**: +使用 **InterferingTask** ,最好删除副作用并返回任务结果。为此,我们创建 **Callable** 而不是 **Runnable** : ```java // concurrent/CountingTask.java @@ -1089,9 +1089,9 @@ public class CountingTask implements Callable { ``` -**call()完全独立于所有其他CountingTasks生成其结果**,这意味着没有可变的共享状态 +**call() 完全独立于所有其他 CountingTasks 生成其结果**,这意味着没有可变的共享状态 -**ExecutorService**允许你使用**invokeAll()**启动集合中的每个Callable: +**ExecutorService** 允许你使用 **invokeAll()** 启动集合中的每个 Callable: ```java // concurrent/CachedThreadPool3.java @@ -1141,7 +1141,7 @@ sum = 1000 ``` -只有在所有任务完成后,**invokeAll()**才会返回一个**Future**列表,每个任务一个**Future**。**Future**是Java 5中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用**ExecutorService.submit()**: +只有在所有任务完成后,**invokeAll()** 才会返回一个 **Future** 列表,每个任务一个 **Future** 。**Future** 是 Java 5 中引入的机制,允许你提交任务而无需等待它完成。在这里,我们使用 **ExecutorService.submit()** : ```java // concurrent/Futures.java @@ -1167,15 +1167,15 @@ public class Futures { 100 ``` -- [1] 当你的任务在尚未完成的**Future**上调用**get()**时,调用会阻塞(等待)直到结果可用。 +- [1] 当你的任务在尚未完成的 **Future** 上调用 **get()** 时,调用会阻塞(等待)直到结果可用。 -但这意味着,在**CachedThreadPool3.java**中,**Future**似乎是多余的,因为**invokeAll()**甚至在所有任务完成之前都不会返回。但是,这里的Future并不用于延迟结果,而是用于捕获任何可能发生的异常。 +但这意味着,在 **CachedThreadPool3.java** 中,**Future** 似乎是多余的,因为 **invokeAll()** 甚至在所有任务完成之前都不会返回。但是,这里的 Future 并不用于延迟结果,而是用于捕获任何可能发生的异常。 -还要注意在**CachedThreadPool3.java.get()**中抛出异常,因此**extractResult()**在Stream中执行此提取。 +还要注意在 **CachedThreadPool3.java.get()** 中抛出异常,因此 **extractResult()** 在 Stream 中执行此提取。 -因为当你调用**get()**时,**Future**会阻塞,所以它只能解决等待任务完成才暴露问题。最终,**Futures**被认为是一种无效的解决方案,现在不鼓励,我们推荐Java 8的**CompletableFuture**,这将在本章后面探讨。当然,你仍会在遗留库中遇到Futures。 +因为当你调用 **get()** 时,**Future** 会阻塞,所以它只能解决等待任务完成才暴露问题。最终,**Futures** 被认为是一种无效的解决方案,现在不鼓励,我们推荐 Java 8 的 **CompletableFuture** ,这将在本章后面探讨。当然,你仍会在遗留库中遇到 Futures。 -我们可以使用并行Stream以更简单,更优雅的方式解决这个问题: +我们可以使用并行 Stream 以更简单,更优雅的方式解决这个问题: ```java // concurrent/CountingStream.java @@ -1213,9 +1213,9 @@ public class CountingStream { 这不仅更容易理解,而且我们需要做的就是将 `parallel()` 插入到其他顺序操作中,然后一切都在同时运行。 -- Lambda和方法引用作为任务 +- Lambda 和方法引用作为任务 -在 **java8** 有了 **lambdas** 和方法引用,你不需要受限于只能使用 **Runnable** 和 **Callable** 。因为 java8 的**lambdas** 和方法引用可以通过匹配方法签名来使用(即,它支持结构一致性),所以我们可以将非 **Runnable** 或 **Callable** 的参数传递给 `ExecutorService` : +在 **java8** 有了 **lambdas** 和方法引用,你不需要受限于只能使用 **Runnable** 和 **Callable** 。因为 java8 的 **lambdas** 和方法引用可以通过匹配方法签名来使用(即,它支持结构一致性),所以我们可以将非 **Runnable** 或 **Callable** 的参数传递给 `ExecutorService` : ```java // concurrent/LambdasAndMethodReferences.java @@ -1257,24 +1257,24 @@ Lambda2 ``` -这里,前两个**submit()**调用可以改为调用**execute()**。所有**submit()**调用都返回**Futures**,你可以在后两次调用的情况下提取结果。 +这里,前两个 **submit()** 调用可以改为调用 **execute()** 。所有 **submit()** 调用都返回 **Futures** ,你可以在后两次调用的情况下提取结果。 ## 终止耗时任务 -并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止**Runnable**和**Callable**任务,例如当你关闭程序时。 +并发程序通常使用长时间运行的任务。可调用任务在完成时返回值;虽然这给它一个有限的寿命,但仍然可能很长。可运行的任务有时被设置为永远运行的后台进程。你经常需要一种方法在正常完成之前停止 **Runnable** 和 **Callable** 任务,例如当你关闭程序时。 -最初的Java设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 +最初的 Java 设计提供了中断运行任务的机制(为了向后兼容,仍然存在);中断机制包括阻塞问题。中断任务既乱又复杂,因为你必须了解可能发生中断的所有可能状态,以及可能导致的数据丢失。使用中断被视为反对模式,但我们仍然被迫接受。 InterruptedException,因为设计的向后兼容性残留。 -任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的shutdown进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。 +任务终止的最佳方法是设置任务周期性检查的标志。然后任务可以通过自己的 shutdown 进程并正常终止。不是在任务中随机关闭线程,而是要求任务在到达了一个较好时自行终止。这总是产生比中断更好的结果,以及更容易理解的更合理的代码。 -以这种方式终止任务听起来很简单:设置任务可以看到的**boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。 +以这种方式终止任务听起来很简单:设置任务可以看到的 **boolean** flag。编写任务,以便定期检查标志并执行正常终止。这实际上就是你所做的,但是有一个复杂的问题:我们的旧克星,共同的可变状态。如果该标志可以被另一个任务操纵,则存在碰撞可能性。 -在研究Java文献时,你会发现很多解决这个问题的方法,经常使用**volatile**关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发](./Appendix-Low-Level-Concurrency.md)中有所涉及。 +在研究 Java 文献时,你会发现很多解决这个问题的方法,经常使用 **volatile** 关键字。我们将使用更简单的技术并避免所有易变的参数,这些都在[附录:低级并发 ](./Appendix-Low-Level-Concurrency.md) 中有所涉及。 -Java 5引入了**Atomic**类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加**AtomicBoolean**标志,告诉任务清理自己并退出。 +Java 5 引入了 **Atomic** 类,它提供了一组可以使用的类型,而不必担心并发问题。我们将添加 **AtomicBoolean** 标志,告诉任务清理自己并退出。 ```java // concurrent/QuittableTask.java @@ -1299,14 +1299,14 @@ public class QuittableTask implements Runnable { ``` -虽然多个任务可以在同一个实例上成功调用**quit()**,但是**AtomicBoolean**可以防止多个任务同时实际修改**running**,从而使**quit()**方法成为线程安全的。 +虽然多个任务可以在同一个实例上成功调用 **quit()** ,但是 **AtomicBoolean** 可以防止多个任务同时实际修改 **running** ,从而使 **quit()** 方法成为线程安全的。 -- [1]:只要运行标志为true,此任务的run()方法将继续。 +- [1]:只要运行标志为 true,此任务的 run() 方法将继续。 - [2]: 显示仅在任务退出时发生。 -需要**running AtomicBoolean**证明编写Java program并发时最基本的困难之一是,如果**running**是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 +需要 **running AtomicBoolean** 证明编写 Java program 并发时最基本的困难之一是,如果 **running** 是一个普通的布尔值,你可能无法在执行程序中看到问题。实际上,在这个例子中,你可能永远不会有任何问题 - 但是代码仍然是不安全的。编写表明该问题的测试可能很困难或不可能。因此,你没有任何反馈来告诉你已经做错了。通常,你编写线程安全代码的唯一方法就是通过了解事情可能出错的所有细微之处。 -作为测试,我们将启动很多QuittableTasks然后关闭它们。尝试使用较大的COUNT值 +作为测试,我们将启动很多 QuittableTasks 然后关闭它们。尝试使用较大的 COUNT 值 ```java // concurrent/QuittingTasks.java @@ -1337,11 +1337,11 @@ public class QuittingTasks { 136 131 135 139 148 140 2 126 6 5 1 18 129 17 14 13 2122 9 10 30 33 58 37 125 26 34 133 145 78 137 141 138 6274 142 86 65 73 146 70 42 149 121 110 134 105 82 117106 113 122 45 114 118 38 50 29 90 101 89 57 53 94 4161 66 130 69 77 81 85 93 25 102 54 109 98 49 46 97 ``` -我使用**peek()**将**QuittableTasks**传递给**ExecutorService**,然后将这些任务收集到**List.main()**中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用quit()方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 +我使用 **peek()** 将 **QuittableTasks** 传递给 **ExecutorService** ,然后将这些任务收集到 **List.main()** 中,只要任何任务仍在运行,就会阻止程序退出。即使为每个任务按顺序调用 quit() 方法,任务也不会按照它们创建的顺序关闭。独立运行的任务不会确定性地响应信号。 -## CompletableFuture类 +## CompletableFuture 类 -作为介绍,这里是使用CompletableFutures在QuittingTasks.java中: +作为介绍,这里是使用 CompletableFutures 在 QuittingTasks.java 中: ```java // concurrent/QuittingCompletable.java @@ -1380,7 +1380,7 @@ public class QuittingCompletable { ### 基本用法 -这是一个带有静态方法**work()**的类,它对该类的对象执行某些工作: +这是一个带有静态方法 **work()** 的类,它对该类的对象执行某些工作: ```java // concurrent/Machina.java @@ -1415,9 +1415,9 @@ public class Machina { ``` -这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()**方法将机器从一个状态移动到下一个状态,并且需要100毫秒才能完成“工作”。 +这是一个有限状态机,一个微不足道的机器,因为它没有分支......它只是从头到尾遍历一条路径。**work()** 方法将机器从一个状态移动到下一个状态,并且需要 100 毫秒才能完成“工作”。 -**CompletableFuture**可以被用来做的一件事是, 使用**completedFuture()**将它感兴趣的对象进行包装。 +**CompletableFuture** 可以被用来做的一件事是, 使用 **completedFuture()** 将它感兴趣的对象进行包装。 ```java // concurrent/CompletedMachina.java @@ -1437,11 +1437,11 @@ public class CompletedMachina { } ``` -**completedFuture()**创建一个“已经完成”的**CompletableFuture**。对这样一个未来做的唯一有用的事情是**get()**里面的对象,所以这看起来似乎没有用。注意**CompletableFuture**被输入到它包含的对象。这个很重要。 +**completedFuture()** 创建一个“已经完成”的 **CompletableFuture** 。对这样一个未来做的唯一有用的事情是 **get()** 里面的对象,所以这看起来似乎没有用。注意 **CompletableFuture** 被输入到它包含的对象。这个很重要。 -通常,**get()**在等待结果时阻塞调用线程。此块可以通过**InterruptedException**或**ExecutionException**中断。在这种情况下,阻止永远不会发生,因为CompletableFutureis已经完成,所以答案立即可用。 +通常,**get()** 在等待结果时阻塞调用线程。此块可以通过 **InterruptedException** 或 **ExecutionException** 中断。在这种情况下,阻止永远不会发生,因为 CompletableFutureis 已经完成,所以答案立即可用。 -当我们将**handle()**包装在**CompletableFuture**中时,发现我们可以在**CompletableFuture**上添加操作来处理所包含的对象,使得事情变得更加有趣: +当我们将 **handle()** 包装在 **CompletableFuture** 中时,发现我们可以在 **CompletableFuture** 上添加操作来处理所包含的对象,使得事情变得更加有趣: ```java // concurrent/CompletableApply.java @@ -1472,11 +1472,11 @@ Machina0: THREE Machina0: complete ``` -`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是(类似于流中的 `map()` )函数也可以返回不同的类型,这将体现在返回类型上。 +`thenApply()` 应用一个接收输入并产生输出的函数。在本例中,`work()` 函数产生的类型与它所接收的类型相同 (`Machina`),因此每个 `CompletableFuture`添加的操作的返回类型都为 `Machina`,但是 (类似于流中的 `map()` ) 函数也可以返回不同的类型,这将体现在返回类型上。 -你可以在此处看到有关**CompletableFutures**的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。 +你可以在此处看到有关 **CompletableFutures** 的重要信息:它们会在你执行操作时自动解包并重新包装它们所携带的对象。这使得编写和理解代码变得更加简单, 而不会在陷入在麻烦的细节中。 -我们可以消除中间变量并将操作链接在一起,就像我们使用Streams一样: +我们可以消除中间变量并将操作链接在一起,就像我们使用 Streams 一样: ```java // concurrent/CompletableApplyChained.javaimport java.util.concurrent.*; @@ -1507,9 +1507,9 @@ Machina0: complete ``` 这里我们还添加了一个 `Timer`,它的功能在每一步都显性地增加 100ms 等待时间之外,还将 `CompletableFuture` 内部 `thenApply` 带来的额外开销给体现出来了。 -**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。 +**CompletableFutures** 的一个重要好处是它们鼓励使用私有子类原则(不共享任何东西)。默认情况下,使用 **thenApply()** 来应用一个不对外通信的函数 - 它只需要一个参数并返回一个结果。这是函数式编程的基础,并且它在并发特性方面非常有效[^5]。并行流和 `ComplempleFutures` 旨在支持这些原则。只要你不决定共享数据(共享非常容易导致意外发生)你就可以编写出相对安全的并发程序。 -回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的: +回调 `thenApply()` 一旦开始一个操作,在完成所有任务之前,不会完成 **CompletableFuture** 的构建。虽然这有时很有用,但是开始所有任务通常更有价值,这样就可以运行继续前进并执行其他操作。我们可通过`thenApplyAsync()` 来实现此目的: ```java // concurrent/CompletableApplyAsync.java @@ -1544,9 +1544,9 @@ Machina0: complete 552 ``` -同步调用(我们通常使用的那种)意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。 +同步调用 (我们通常使用的那种) 意味着:“当你完成工作时,才返回”,而异步调用以意味着: “立刻返回并继续后续工作”。 正如你所看到的,`cf` 的创建现在发生的更快。每次调用 `thenApplyAsync()` 都会立刻返回,因此可以进行下一次调用,整个调用链路完成速度比以前快得多。 -事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到cf操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。 +事实上,如果没有回调 `cf.join()` 方法,程序会在完成其工作之前退出。而 `cf.join()` 直到 cf 操作完成之前,阻止 `main()` 进程结束。我们还可以看出本示例大部分时间消耗在 `cf.join()` 这。 这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。 @@ -1554,9 +1554,9 @@ Machina0: complete - 其他操作 -当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务(在本书中,我们忽略了 `Executor` 选项)。 +当你查看`CompletableFuture`的 `Javadoc` 时,你会看到它有很多方法,但这个方法的大部分来自不同操作的变体。例如,有 `thenApply()`,`thenApplyAsync()` 和第二种形式的 `thenApplyAsync()`,它们使用 `Executor` 来运行任务 (在本书中,我们忽略了 `Executor` 选项)。 -下面的示例展示了所有"基本"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常(我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序: +下面的示例展示了所有"基本"操作,这些操作既不涉及组合两个 `CompletableFuture`,也不涉及异常 (我们将在后面介绍)。首先,为了提供简洁性和方便性,我们应该重用以下两个实用程序: ```java package onjava; @@ -1683,8 +1683,8 @@ dependents: 2 - `cfi(8)` 使用 `toCompletableFuture()` 从 `CompletionStage` 生成一个`CompletableFuture`。 - `c.complete(9)` 显示了如何通过给它一个结果来完成一个`task`(`future`)(与 `obtrudeValue()` 相对,后者可能会迫使其结果替换该结果)。 - 如果你调用 `CompletableFuture`中的 `cancel()`方法,如果已经完成此任务,则正常结束。 如果尚未完成,则使用 `CancellationException` 完成此 `CompletableFuture`。 - - 如果任务(`future`)完成,则**getNow()**方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。 - - 最后,我们看一下依赖(`dependents`)的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。 + - 如果任务(`future`)完成,则 **getNow()** 方法返回`CompletableFuture`的完成值,否则返回`getNow()`的替换参数。 + - 最后,我们看一下依赖 (`dependents`) 的概念。如果我们将两个`thenApplyAsync()`调用链路到`CompletableFuture`上,则依赖项的数量不会增加,保持为 1。但是,如果我们另外将另一个`thenApplyAsync()`直接附加到`c`,则现在有两个依赖项:两个一起的链路和另一个单独附加的链路。 - 这表明你可以使用一个`CompletionStage`,当它完成时,可以根据其结果派生多个新任务。 @@ -1730,7 +1730,7 @@ public class Workable { } ``` -在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母W附加到id上,表示工作已经完成。 +在 `make()`中,`work()`方法应用于`CompletableFuture`。`work()`需要一定的时间才能完成,然后它将字母 W 附加到 id 上,表示工作已经完成。 现在我们可以创建多个竞争的 `CompletableFuture`,并使用 `CompletableFuture` 库中的各种方法来进行操作: @@ -1859,7 +1859,7 @@ thenAcceptBoth: Workable[AW], Workable[BW] - `init()`方法用于 `A`, `B` 初始化这两个变量,因为 `B` 总是给出比`A`较短的延迟,所以总是 `win` 的一方。 - `join()` 是在两个方法上调用 `join()` 并显示边框的另一个便利方法。 - 所有这些 “`dual`” 方法都以一个 `CompletableFuture` 作为调用该方法的对象,第二个 `CompletableFuture` 作为第一个参数,然后是要执行的操作。 -- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载)的 `CompletableFuture`。 +- 通过使用 `showr()` 和 `voidr()` 可以看到,“`run`”和“`accept`”是终端操作,而“`apply`”和“`combine`”则生成新的 `payload-bearing` (承载负载) 的 `CompletableFuture`。 - 方法的名称不言自明,你可以通过查看输出来验证这一点。一个特别有趣的方法是 `combineAsync()`,它等待两个 `CompletableFuture` 完成,然后将它们都交给一个 `BiFunction`,这个 `BiFunction` 可以将结果加入到最终的 `CompletableFuture` 的有效负载中。 @@ -2114,7 +2114,7 @@ done? false java.lang.RuntimeException: forced ``` -测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试F中调用 `get()` 时,我们才会看到抛出的异常。 +测试 `A` 到 `E` 运行到抛出异常,然后…并没有将抛出的异常暴露给调用方。只有在测试 F 中调用 `get()` 时,我们才会看到抛出的异常。 测试 `G` 表明,你可以首先检查在处理期间是否抛出异常,而不抛出该异常。然而,test `H` 告诉我们,不管异常是否成功,它仍然被视为已“完成”。 代码的最后一部分展示了如何将异常插入到 `CompletableFuture` 中,而不管是否存在任何失败。 在连接或获取结果时,我们使用 `CompletableFuture` 提供的更复杂的机制来自动响应异常,而不是使用粗糙的 `try-catch`。 @@ -2208,16 +2208,16 @@ result: Breakable_whenComplete [-4] - `exceptionally()` 通过将一个好的对象插入到流中来恢复到一个可行的状态。 -- `handle()` 一致被调用来查看是否发生异常(必须检查fail是否为true)。 +- `handle()` 一致被调用来查看是否发生异常(必须检查 fail 是否为 true)。 - 但是 `handle()` 可以生成任何新类型,所以它允许执行处理,而不是像使用 `exceptionally()`那样简单地恢复。 - - `whenComplete()` 类似于handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。 + - `whenComplete()` 类似于 handle(),同样必须测试它是否失败,但是参数是一个消费者,并且不修改传递给它的结果对象。 ### 流异常(Stream Exception) -通过修改**CompletableExceptions.java**,看看 **CompletableFuture**异常与流异常有何不同: +通过修改 **CompletableExceptions.java** ,看看 **CompletableFuture** 异常与流异常有何不同: ```java // concurrent/StreamExceptions.java @@ -2261,7 +2261,7 @@ Throwing Exception for C Breakable_C failed ``` -使用 `CompletableFuture`,我们可以看到测试A到E的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client +使用 `CompletableFuture`,我们可以看到测试 A 到 E 的进展,但是使用流,在你应用一个终端操作之前(e.g. `forEach()`),什么都不会暴露给 Client `CompletableFuture` 执行工作并捕获任何异常供以后检索。比较这两者并不容易,因为 `Stream` 在没有终端操作的情况下根本不做任何事情——但是流绝对不会存储它的异常。 @@ -2316,7 +2316,7 @@ public class ThrowsChecked { } ``` -如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出lambda表达式(或者编写一个不会抛出异常的包装器方法)。 +如果你试图像使用 `nochecked()` 那样使用` withchecked()` 的方法引用,编译器会在 `[1]` 和 `[2]` 中报错。相反,你必须写出 lambda 表达式 (或者编写一个不会抛出异常的包装器方法)。 ## 死锁 @@ -2325,7 +2325,7 @@ public class ThrowsChecked { 埃德斯·迪克斯特拉(`Essger Dijkstra`)发明的“哲学家进餐"问题是经典的死锁例证。基本描述指定了五位哲学家(此处显示的示例允许任何数目)。这些哲学家将花部分时间思考,花部分时间就餐。他们在思考的时候并不需要任何共享资源;但是他们使用的餐具数量有限。在最初的问题描述中,餐具是叉子,需要两个叉子才能从桌子中间的碗里取出意大利面。常见的版本是使用筷子, 显然,每个哲学家都需要两根筷子才能吃饭。 引入了一个困难:作为哲学家,他们的钱很少,所以他们只能买五根筷子(更一般地讲,筷子的数量与哲学家相同)。他们围在桌子周围,每人之间放一根筷子。 当一个哲学家要就餐时,该哲学家必须同时持有左边和右边的筷子。如果任一侧的哲学家都在使用所需的筷子,则我们的哲学家必须等待,直到可得到必须的筷子。 -**StickHolder** 类通过将单根筷子保持在大小为1的**BlockingQueue**中来管理它。**BlockingQueue**是一个设计用于在并发程序中安全使用的集合,如果你调用take()并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: +**StickHolder** 类通过将单根筷子保持在大小为 1 的 **BlockingQueue** 中来管理它。**BlockingQueue** 是一个设计用于在并发程序中安全使用的集合,如果你调用 take() 并且队列为空,则它将阻塞(等待)。将新元素放入队列后,将释放该块并返回该值: ```java // concurrent/StickHolder.java @@ -2399,7 +2399,7 @@ public class Philosopher implements Runnable { } ``` -没有两个哲学家可以同时成功调用take()同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 +没有两个哲学家可以同时成功调用 take() 同一只筷子。另外,如果一个哲学家已经拿过筷子,那么下一个试图拿起同一根筷子的哲学家将阻塞,等待其被释放。 结果是一个看似无辜的程序陷入了死锁。我在这里使用数组而不是集合,只是因为这种语法更简洁: @@ -2439,19 +2439,19 @@ public class DiningPhilosophers { ``` - 当你停止查看输出时,该程序将死锁。但是,根据你的计算机配置,你可能不会看到死锁。看来这取决于计算机上的内核数[^7]。两个核心不会产生死锁,但两核以上却很容易产生死锁。 -- 此行为使该示例更好地说明了死锁,因为你可能正在具有2核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在2核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。 +- 此行为使该示例更好地说明了死锁,因为你可能正在具有 2 核的计算机上编写程序(如果确实是导致问题的原因),并且确信该程序可以正常工作,只能启动它将其安装在另一台计算机上时出现死锁。请注意,不能因为你没或不容易看到死锁,这并不意味着此程序不会在 2 核机器上发生死锁。 该程序仍然有死锁倾向,只是很少发生——可以说是最糟糕的情况,因为问题不容易出现。 - 在 `DiningPhilosophers` 的构造方法中,每个哲学家都获得一个左右筷子的引用。除最后一个哲学家外,都是通过把哲学家放在下一双空闲筷子之间来初始化: - - 最后一位哲学家得到了第0根筷子作为他的右筷子,所以圆桌就完成。 - - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1]显示了以n为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。 + - 最后一位哲学家得到了第 0 根筷子作为他的右筷子,所以圆桌就完成。 + - 那是因为最后一位哲学家正坐在第一个哲学家的旁边,而且他们俩都共用零筷子。[1] 显示了以 n 为模数选择的右筷子,将最后一个哲学家绕到第一个哲学家的旁边。 - 现在,所有哲学家都可以尝试吃饭,每个哲学家都在旁边等待哲学家放下筷子。 - - 为了让每个哲学家在[3]上运行,调用 `runAsync()`,这意味着DiningPhilosophers的构造函数立即返回到[4]。 + - 为了让每个哲学家在[3] 上运行,调用 `runAsync()`,这意味着 DiningPhilosophers 的构造函数立即返回到[4]。 - 如果没有任何东西阻止 `main()` 完成,程序就会退出,不会做太多事情。 - - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出(假设/可能是)死锁程序。 + - `Nap` 对象阻止 `main()` 退出,然后在三秒后强制退出 (假设/可能是) 死锁程序。 - 在给定的配置中,哲学家几乎不花时间思考。因此,他们在吃东西的时候都争着用筷子,而且往往很快就会陷入僵局。你可以改变这个: -1. 通过增加[4]的值来添加更多哲学家。 +1. 通过增加[4] 的值来添加更多哲学家。 -2. 在Philosopher.java中取消注释行[1]。 +2. 在 Philosopher.java 中取消注释行[1]。 任一种方法都会减少死锁的可能性,这表明编写并发程序并认为它是安全的危险,因为它似乎“在我的机器上运行正常”。你可以轻松地说服自己该程序没有死锁,即使它不是。这个示例相当有趣,因为它演示了看起来可以正确运行,但实际上会可能发生死锁的程序。 @@ -2464,7 +2464,7 @@ public class DiningPhilosophers { 因为必须满足所有条件才能导致死锁,所以要阻止死锁的话,只需要破坏其中一个即可。在此程序中,防止死锁的一种简单方法是打破第四个条件。之所以会发生这种情况,是因为每个哲学家都尝试按照特定的顺序拾起自己的筷子:先右后左。因此,每个哲学家都有可能在等待左手的同时握住右手的筷子,从而导致循环等待状态。但是,如果其中一位哲学家尝试首先拿起左筷子,则该哲学家决不会阻止紧邻右方的哲学家拿起筷子,从而排除了循环等待。 -在**DiningPhilosophers.java**中,取消注释[1]和其后的一行。这将原来的哲学家[1]替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 +在 **DiningPhilosophers.java** 中,取消注释[1] 和其后的一行。这将原来的哲学家[1] 替换为筷子颠倒的哲学家。通过确保第二位哲学家拾起并在右手之前放下左筷子,我们消除了死锁的可能性。 这只是解决问题的一种方法。你也可以通过防止其他情况之一来解决它。 没有语言支持可以帮助防止死锁;你有责任通过精心设计来避免这种情况。对于试图调试死锁程序的人来说,这些都不是安慰。当然,避免并发问题的最简单,最好的方法是永远不要共享资源-不幸的是,这并不总是可能的。 @@ -2472,11 +2472,11 @@ public class DiningPhilosophers { ## 构造方法非线程安全 -当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS)自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” +当你在脑子里想象一个对象构造的过程,你会很容易认为这个过程是线程安全的。毕竟,在对象初始化完成前对外不可见,所以又怎会对此产生争议呢?确实,[Java 语言规范 ](https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.3) (JLS) 自信满满地陈述道:“*没必要使构造器的线程同步,因为它会锁定正在构造的对象,直到构造器完成初始化后才对其他线程可见。*” 不幸的是,对象的构造过程如其他操作一样,也会受到共享内存并发问题的影响,只是作用机制可能更微妙罢了。 -设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: +设想下使用一个 **static** 字段为每个对象自动创建唯一标识符的过程。为了测试其不同的实现过程,我们从一个接口开始。代码示例: ```java //concurrent/HasID.java @@ -2485,7 +2485,7 @@ public interface HasID { } ``` -然后 **StaticIDField** 类显式地实现该接口。代码示例: +然后 **StaticIDField** 类显式地实现该接口。代码示例: ```java // concurrent/StaticIDField.java @@ -2541,11 +2541,11 @@ public class IDChecker { } ``` -**MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 +**MakeObjects** 类是一个生产者类,包含一个能够产生 List\ 类型的列表对象的 `get()` 方法。通过从每个 `HasID` 对象提取 `ID` 并放入列表中来生成这个列表对象,而 `test()` 方法则创建了两个并行的 **CompletableFuture** 对象,用于运行 **MakeObjects** 生产者类,然后获取运行结果。 使用 Guava 库中的 **Sets.`intersection()` 方法,计算出这两个返回的 List\ 对象中有多少相同的 `ID`(使用谷歌 Guava 库里的方法比使用官方的 `retainAll()` 方法速度快得多)。 -现在我们可以测试上面的 **StaticIDField** 类了。代码示例: +现在我们可以测试上面的 **StaticIDField** 类了。代码示例: ```java // concurrent/TestStaticIDField.java @@ -2563,7 +2563,7 @@ public class TestStaticIDField { 13287 ``` -结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例: +结果中出现了很多重复项。很显然,纯静态 `int` 用于构造过程并不是线程安全的。让我们使用 **AtomicInteger** 来使其变为线程安全的。代码示例: ```java // concurrent/GuardedIDField.java @@ -2643,9 +2643,9 @@ public class SharedConstructorArgument{ 0 ``` -在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 +在这里,**SharedUser** 构造器实际上共享了相同的参数。即使 **SharedUser** 以完全无害且合理的方式使用其自己的参数,其构造器的调用方式也会引起冲突。**SharedUser** 甚至不知道它是以这种方式调用的,更不必说控制它了。 -同步构造器并不被java语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管JLS(java语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的Class对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: +同步构造器并不被 java 语言所支持,但是通过使用同步语块来创建你自己的同步构造器是可能的(请参阅附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md),来进一步了解同步关键字—— `synchronized`)。尽管 JLS(java 语言规范)这样陈述道:“……它会锁定正在构造的对象”,但这并不是真的——构造器实际上只是一个静态方法,因此同步构造器实际上会锁定该类的 Class 对象。我们可以通过创建自己的静态对象并锁定它,来达到与同步构造器相同的效果: ```java // concurrent/SynchronizedConstructor.java @@ -2683,7 +2683,7 @@ public class SynchronizedConstructor{ 0 ``` -**Unsafe**类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态Factory方法来生成新对象: +**Unsafe** 类的共享使用现在就变得安全了。另一种方法是将构造器设为私有(因此可以防止继承),并提供一个静态 Factory 方法来生成新对象: ```java // concurrent/SynchronizedFactory.java @@ -2720,9 +2720,9 @@ public class SynchronizedFactory{ 0 ``` -通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 +通过同步静态工厂方法,可以在构造过程中锁定 **Class** 对象。 -这些示例充分表明了在并发Java程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 +这些示例充分表明了在并发 Java 程序中检测和管理共享状态有多困难。即使你采取“不共享任何内容”的策略,也很容易产生意外的共享事件。 ## 复杂性和代价 @@ -2808,7 +2808,7 @@ public class Pizza{ } ``` -这只算得上是一个平凡的状态机,就像**Machina**类一样。 +这只算得上是一个平凡的状态机,就像 **Machina** 类一样。 制作一个披萨,当披萨饼最终被放在盒子中时,就算完成最终任务了。 如果一个人在做一个披萨饼,那么所有步骤都是线性进行的,即一个接一个地进行: @@ -2902,9 +2902,9 @@ Pizza 3:BOXED 1739 ``` -现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1]的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将**QUANTITY**更改为4、8、10、16和17,看看会有什么不同,并猜猜看为什么会这样。 +现在,我们制作五个披萨的时间与制作单个披萨的时间就差不多了。 尝试删除标记为[1] 的行后,你会发现它花费的时间是原来的五倍。 你还可以尝试将 **QUANTITY** 更改为 4、8、10、16 和 17,看看会有什么不同,并猜猜看为什么会这样。 -**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? +**PizzaStreams** 类产生的每个并行流在它的`forEach()`内完成所有工作,如果我们将其各个步骤用映射的方式一步一步处理,情况会有所不同吗? ```java // concurrent/PizzaParallelSteps.java @@ -2982,7 +2982,7 @@ Pizza3: complete 答案是“否”,事后看来这并不奇怪,因为每个披萨都需要按顺序执行步骤。因此,没法通过分步执行操作来进一步提高速度,就像上文的 `PizzaParallelSteps.java` 里面展示的一样。 -我们可以使用 **CompletableFutures** 重写这个例子: +我们可以使用 **CompletableFutures** 重写这个例子: ```java // concurrent/CompletablePizza.java @@ -3076,19 +3076,19 @@ Pizza4: complete 1797 ``` -并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究**Spliterator**的文档)。 +并行流和 **CompletableFutures** 是 Java 并发工具箱中最先进发达的技术。 你应该始终首先选择其中之一。 当一个问题很容易并行处理时,或者说,很容易把数据分解成相同的、易于处理的各个部分时,使用并行流方法处理最为合适(而如果你决定不借助它而由自己完成,你就必须撸起袖子,深入研究 **Spliterator** 的文档)。 -而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。 +而当工作的各个部分内容各不相同时,使用 **CompletableFutures** 是最好的选择。比起面向数据,**CompletableFutures** 更像是面向任务的。 对于披萨问题,结果似乎也没有什么不同。实际上,并行流方法看起来更简洁,仅出于这个原因,我认为并行流作为解决问题的首次尝试方法更具吸引力。 -由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作n个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。 +由于制作披萨总需要一定的时间,无论你使用哪种并发方法,你能做到的最好情况,是在制作一个披萨的相同时间内制作 n 个披萨。 在这里当然很容易看出来,但是当你处理更复杂的问题时,你就可能忘记这一点。 通常,在项目开始时进行粗略的计算,就能很快弄清楚最大可能的并行吞吐量,这可以防止你因为采取无用的加快运行速度的举措而忙得团团转。 -使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 +使用 **CompletableFutures** 或许可以轻易地带来重大收益,但是在尝试更进一步时需要倍加小心,因为额外增加的成本和工作量会非常容易远远超出你之前拼命挤出的那一点点收益。 ## 本章小结 -需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于Java用于构建用户界面时并不高效,因此[^8]这仅仅意味着“你的程序运行速度还不够快”。 +需要并发的唯一理由是“等待太多”。这也可以包括用户界面的响应速度,但是由于 Java 用于构建用户界面时并不高效,因此[^8] 这仅仅意味着“你的程序运行速度还不够快”。 如果并发很容易,则没有理由拒绝并发。 正因为并发实际上很难,所以你应该仔细考虑是否值得为此付出努力,并考虑你能否以其他方式提升速度。 @@ -3104,7 +3104,7 @@ Pizza4: complete 1. 在线程等待共享资源时会降低速度。 -2. 线程管理产生额外CPU开销。 +2. 线程管理产生额外 CPU 开销。 3. 糟糕的设计决策带来无法弥补的复杂性。 @@ -3112,7 +3112,7 @@ Pizza4: complete 5. 跨平台的不一致。 通过一些示例,我发现了某些计算机上很快出现的竞争状况,而在其他计算机上却没有。 如果你在后者上开发程序,则在分发程序时可能会感到非常惊讶。 -另外,并发的应用是一门艺术。 Java旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9]但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个**Thread**对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。 +另外,并发的应用是一门艺术。 Java 旨在允许你创建尽可能多的所需要的对象来解决问题——至少在理论上是这样。[^9] 但是,线程不是典型的对象:每个线程都有其自己的执行环境,包括堆栈和其他必要的元素,使其比普通对象大得多。 在大多数环境中,只能在内存用光之前创建数千个 **Thread** 对象。通常,你只需要几个线程即可解决问题,因此一般来说创建线程没有什么限制,但是对于某些设计而言,它会成为一种约束,可能迫使你使用完全不同的方案。 ### 共享内存陷阱 @@ -3120,49 +3120,49 @@ Pizza4: complete 我花了多年的时间研究并发。 我了解到你永远无法相信使用共享内存并发的程序可以正常工作。 你可以轻易发现它是错误的,但永远无法证明它是正确的。 这是众所周知的并发原则之一。[^10] -我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个CPU的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对Java工具很了解。 而且在我的单CPU计算机上也没有失败。而到了具有多个CPU的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是Java的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单CPU机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多CPU机器上,就会出现一些其他问题。 +我遇到了许多人,他们对编写正确的线程程序的能力充满信心。 我偶尔开始认为我也可以做好。 对于一个特定的程序,我最初是在只有单个 CPU 的机器上编写的。 那时我能够说服自己该程序是正确的,因为我以为我对 Java 工具很了解。 而且在我的单 CPU 计算机上也没有失败。而到了具有多个 CPU 的计算机,程序出现问题不能运行后,我感到很惊讶,但这还只是众多问题中的一个而已。 这不是 Java 的错; “写一次,到处运行”,在单核与多核计算机间无法扩展到并发编程领域。这是并发编程的基本问题。 实际上你可以在单 CPU 机器上发现一些并发问题,但是在多线程实际上真的在并行运行的多 CPU 机器上,就会出现一些其他问题。 再举一个例子,哲学家就餐的问题可以很容易地进行调整,因此几乎不会产生死锁,这会给你一种一切都棒极了的印象。当涉及到共享内存并发编程时,你永远不应该对自己的编程能力变得过于自信。 ### This Albatross is Big -如果你对Java并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问**Thread**类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)页面, 看一下哪些方法现在是**Deprecated**(废弃的)。这些是Java语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 +如果你对 Java 并发感到不知所措,那说明你身处在一家出色的公司里。你可以访问 **Thread** 类的[Javadoc](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html) 页面, 看一下哪些方法现在是 **Deprecated** (废弃的)。这些是 Java 语言设计者犯过错的地方,因为他们在设计语言时对并发性了解不足。 -事实证明,在Java的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8中的并行**Streams**和**CompletableFutures**都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。 +事实证明,在 Java 的后续版本中添加的许多库解决方案都是无效的,甚至是无用的。 幸运的是,Java 8 中的并行 **Streams** 和 **CompletableFutures** 都非常有价值。但是当你使用旧代码时,仍然会遇到旧的解决方案。 -在本书的其他地方,我谈到了Java的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。 +在本书的其他地方,我谈到了 Java 的一个基本问题:每个失败的实验都永远嵌入在语言或库中。 Java 并发强调了这个问题。尽管有不少错误,但错误并不是那么多,因为有很多不同的尝试方法来解决问题。 好的方面是,这些尝试产生了更好,更简单的设计。 不利之处在于,在找到好的方法之前,你很容易迷失于旧的设计中。 ### 其他类库 -本章重点介绍了相对安全易用的并行工具流和**CompletableFutures**,并且仅涉及Java标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个**Atomic**(原子)类,**ConcurrentLinkedDeque**,**ExecutorService**和**ArrayBlockingQueue**。附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md)涵盖了其他一些内容,但是你还想探索**java.util.concurrent**的Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 +本章重点介绍了相对安全易用的并行工具流和 **CompletableFutures** ,并且仅涉及 Java 标准库中一些更细粒度的工具。 为避免你不知所措,我没有介绍你可能实际在实践中使用的某些库。我们使用了几个 **Atomic** (原子)类,**ConcurrentLinkedDeque** ,**ExecutorService** 和 **ArrayBlockingQueue** 。附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md) 涵盖了其他一些内容,但是你还想探索 **java.util.concurrent** 的 Javadocs。 但是要小心,因为某些库组件已被新的更好的组件所取代。 ### 考虑为并发设计的语言 -通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或**CompletableFutures**。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在Java的世界范围内)。 +通常,请谨慎地使用并发。 如果需要使用它,请尝试使用最现代的方法:并行流或 **CompletableFutures** 。 这些功能旨在(假设你不尝试共享内存)使你摆脱麻烦(在 Java 的世界范围内)。 -如果你的并发问题变得比高级Java构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM上最纯粹的功能语言是Clojure(Lisp的一种版本)和Frege(Haskell的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过JVM轻松地与你的主要Java代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将JVM之外的语言与另一种为并发设计的语言进行通信。[^11] +如果你的并发问题变得比高级 Java 构造所支持的问题更大且更复杂,请考虑使用专为并发设计的语言,仅在需要并发的程序部分中使用这种语言是有可能的。 在撰写本文时,JVM 上最纯粹的功能语言是 Clojure(Lisp 的一种版本)和 Frege(Haskell 的一种实现)。这些使你可以在其中编写应用程序的并发部分语言,并通过 JVM 轻松地与你的主要 Java 代码进行交互。 或者,你可以选择更复杂的方法,即通过外部功能接口(FFI)将 JVM 之外的语言与另一种为并发设计的语言进行通信。[^11] -你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建HTML / JavaScript用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js**允许你在Scala中完成代码)。 +你很容易被一种语言绑定,迫使自己尝试使用该语言来做所有事情。 一个常见的示例是构建 HTML / JavaScript 用户界面。 这些工具确实很难使用,令人讨厌,并且有许多库允许你通过使用自己喜欢的语言编写代码来生成这些工具(例如,**Scala.js** 允许你在 Scala 中完成代码)。 -心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理](./Appendix-Low-Level-Concurrency.md))中已经表明Java并发是一个你可能无法逃离很深的洞。 与Java语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 +心理上的便利是一个合理的考虑因素。 但是,我希望我在本章(以及附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md))中已经表明 Java 并发是一个你可能无法逃离很深的洞。 与 Java 语言的任何其他部分相比,在视觉上检查代码同时记住所有陷阱所需要的的知识要困难得多。 无论使用特定的语言、库使得并发看起来多么简单,都要将其视为一种妖术,因为总是有东西会在你最不期望出现的时候咬你。 ### 拓展阅读 -《Java Concurrency in Practice》,出自Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes和 Doug Lea (Addison Wesley,2006年)——这些基本上就是Java并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000年)。尽管这本书出版时间远远早于Java 5发布,但Doug的大部分工作都写入了**java.util.concurrent**库。因此,这本书对于全面理解并发问题至关重要。 它超越了Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 +《Java Concurrency in Practice》,出自 Brian Goetz,Tim Peierls, Joshua Bloch,Joseph Bowbeer,David Holmes 和 Doug Lea (Addison Wesley,2006 年)——这些基本上就是 Java 并发世界中的名人名单了《Java Concurrency in Practice》第二版,出自 Doug Lea (Addison-Wesley,2000 年)。尽管这本书出版时间远远早于 Java 5 发布,但 Doug 的大部分工作都写入了 **java.util.concurrent** 库。因此,这本书对于全面理解并发问题至关重要。 它超越了 Java,讨论了跨语言和技术的并发编程。 尽管它在某些地方可能很钝,但值得多次重读(最好是在两个月之间进行消化)。 道格(Doug)是世界上为数不多的真正了解并发编程的人之一,因此这是值得的。 -[^1]:例如,Eric-Raymond在“Unix编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 +[^1]:例如,Eric-Raymond 在“Unix 编程艺术”(Addison-Wesley,2004)中提出了一个很好的案例。 [^2]:可以说,试图将并发性用于后续语言是一种注定要失败的方法,但你必须得出自己的结论 -[^3]:有人谈论在Java——10中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 +[^3]:有人谈论在 Java——10 中围绕泛型做一些类似的基本改进,这将是非常令人难以置信的。 [^4]:这是一种有趣的,虽然不一致的方法。通常,我们期望在公共接口上使用显式类表示不同的行为 -[^5]:不,永远不会有纯粹的功能性Java。我们所能期望的最好的是一种在JVM上运行的全新语言。 +[^5]:不,永远不会有纯粹的功能性 Java。我们所能期望的最好的是一种在 JVM 上运行的全新语言。 [^6]:当两个任务能够更改其状态以使它们不会被阻止但它们从未取得任何有用的进展时,你也可以使用活动锁。 -[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的Java版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩ +[^7]: 而不是超线程;通常每个内核有两个超线程,并且在询问内核数量时,本书所使用的 Java 版本会报告超线程的数量。超线程产生了更快的上下文切换,但是只有实际的内核才真的工作,而不是超线程。 ↩ [^8]: 库就在那里用于调用,而语言本身就被设计用于此目的,但实际上它很少发生,以至于可以说”没有“。↩ -[^9]: 举例来说,如果没有Flyweight设计模式,在工程中创建数百万个对象用于有限元分析可能在Java中不可行。↩ +[^9]: 举例来说,如果没有 Flyweight 设计模式,在工程中创建数百万个对象用于有限元分析可能在 Java 中不可行。↩ [^10]: 在科学中,虽然从来没有一种理论被证实过,但是一种理论必须是可证伪的才有意义。而对于并发性,我们大部分时间甚至都无法得到这种可证伪性。↩ -[^11]: 尽管**Go**语言显示了FFI的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。 +[^11]: 尽管 **Go** 语言显示了 FFI 的前景,但在撰写本文时,它并未提供跨所有平台的解决方案。 From 63a31fab8460c17ee0e299d75ebe8e7a6c24a999 Mon Sep 17 00:00:00 2001 From: arobot Date: Thu, 17 Sep 2020 10:53:39 +0800 Subject: [PATCH 318/371] Update 24-Concurrent-Programming.md (#584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > ”the blocking never happens because the CompletableFuture is already complete, so the answer is instantly available.“ 1. 笔误 2. answer这里应该是指结果 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index ab5d177a..052cd722 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -1439,7 +1439,7 @@ public class CompletedMachina { **completedFuture()** 创建一个“已经完成”的 **CompletableFuture** 。对这样一个未来做的唯一有用的事情是 **get()** 里面的对象,所以这看起来似乎没有用。注意 **CompletableFuture** 被输入到它包含的对象。这个很重要。 -通常,**get()** 在等待结果时阻塞调用线程。此块可以通过 **InterruptedException** 或 **ExecutionException** 中断。在这种情况下,阻止永远不会发生,因为 CompletableFutureis 已经完成,所以答案立即可用。 +通常,**get()** 在等待结果时阻塞调用线程。此块可以通过 **InterruptedException** 或 **ExecutionException** 中断。在这种情况下,阻止永远不会发生,因为 **CompletableFuture** 已经完成,所以结果立即可用。 当我们将 **handle()** 包装在 **CompletableFuture** 中时,发现我们可以在 **CompletableFuture** 上添加操作来处理所包含的对象,使得事情变得更加有趣: From c6a1207aa741d51e787c3447d2b80867a55235a7 Mon Sep 17 00:00:00 2001 From: Lane Date: Thu, 17 Sep 2020 11:50:34 +0800 Subject: [PATCH 319/371] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=88=86=E5=8F=B7=20&&=20=E9=87=8D=E6=96=B0=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=B4=A2=E5=BC=95=20(#586)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add semicolon * rebuild index --- SUMMARY.md | 4 ++-- docs/book/18-Strings.md | 2 +- docs/sidebar.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SUMMARY.md b/SUMMARY.md index 5f2b8733..c7d1abb2 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -219,7 +219,7 @@ ### [第十八章 字符串](book/18-Strings.md) * [字符串的不可变](book/18-Strings.md#字符串的不可变) - * [+ 的重载与 StringBuilder](book/18-Strings.md#+-的重载与-StringBuilder) + * [+ 的重载与 StringBuilder](book/18-Strings.md#-的重载与-StringBuilder) * [意外递归](book/18-Strings.md#意外递归) * [字符串操作](book/18-Strings.md#字符串操作) * [格式化输出](book/18-Strings.md#格式化输出) @@ -315,7 +315,7 @@ * [并行流](book/24-Concurrent-Programming.md#并行流) * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类) + * [CompletableFuture 类](book/24-Concurrent-Programming.md#CompletableFuture-类) * [死锁](book/24-Concurrent-Programming.md#死锁) * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 0b7bc7e5..151eed10 100755 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -273,7 +273,7 @@ import java.util.stream.*; public class InfiniteRecursion { @Override public String toString() { - return " InfiniteRecursion address: " + this + "\n" + return " InfiniteRecursion address: " + this + "\n"; } public static void main(String[] args) { Stream.generate(InfiniteRecursion::new) diff --git a/docs/sidebar.md b/docs/sidebar.md index 5f2b8733..c7d1abb2 100644 --- a/docs/sidebar.md +++ b/docs/sidebar.md @@ -219,7 +219,7 @@ ### [第十八章 字符串](book/18-Strings.md) * [字符串的不可变](book/18-Strings.md#字符串的不可变) - * [+ 的重载与 StringBuilder](book/18-Strings.md#+-的重载与-StringBuilder) + * [+ 的重载与 StringBuilder](book/18-Strings.md#-的重载与-StringBuilder) * [意外递归](book/18-Strings.md#意外递归) * [字符串操作](book/18-Strings.md#字符串操作) * [格式化输出](book/18-Strings.md#格式化输出) @@ -315,7 +315,7 @@ * [并行流](book/24-Concurrent-Programming.md#并行流) * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture类](book/24-Concurrent-Programming.md#CompletableFuture类) + * [CompletableFuture 类](book/24-Concurrent-Programming.md#CompletableFuture-类) * [死锁](book/24-Concurrent-Programming.md#死锁) * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) From 46083fb5757f719bf968cc0a05863eb5b27b04f5 Mon Sep 17 00:00:00 2001 From: arobot Date: Thu, 17 Sep 2020 15:59:29 +0800 Subject: [PATCH 320/371] Update 24-Concurrent-Programming.md (#585) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 24-Concurrent-Programming.md > And unless you know something specific about your system that changes things, you’ll probably want to use Async calls. 主谓宾 something specific changes things。作者应该是想说明逻辑中的一些具体实现可能会导致过程中的特定变化,如果使用异步实现会产生异常。而你是明白会产生这样的结果的,否则会更愿意使用异步调用。 原句中没有体现changes这个动作,读起来让人费解。 * Update 24-Concurrent-Programming.md 合并修改意见 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 052cd722..668db583 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -1550,7 +1550,7 @@ Machina0: complete 这种“立即返回”的异步能力需要 `CompletableFuture` 库进行一些秘密(`client` 无感)工作。特别是,它将你需要的操作链存储为一组回调。当操作的第一个链路(后台操作)完成并返回时,第二个链路(后台操作)必须获取生成的 `Machina` 并开始工作,以此类推! 但这种异步机制没有我们可以通过程序调用栈控制的普通函数调用序列,它的调用链路顺序会丢失,因此它使用一个函数地址来存储的回调来解决这个问题。 -幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道系统的一些具体的变化,否则你更想使用异步调用来实现程序。 +幸运的是,这就是你需要了解的有关回调的全部信息。程序员将这种人为制造的混乱称为 callback hell(回调地狱)。通过异步调用,`CompletableFuture` 帮你管理所有回调。 除非你知道你系统中的一些特定逻辑会导致某些改变,或许你更想使用异步调用来实现程序。 - 其他操作 From cb4f1abd553f0b01dc55f7376395b6c334f292b1 Mon Sep 17 00:00:00 2001 From: Knn120 <19728079+Knn120@users.noreply.github.com> Date: Thu, 17 Sep 2020 20:37:12 +0800 Subject: [PATCH 321/371] =?UTF-8?q?=E8=A7=A3=E5=86=B3=20issue#587=20?= =?UTF-8?q?=E6=9C=89=E4=B8=A4=E4=B8=AA=E7=AB=A0=E8=8A=82=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=97=AE=E9=A2=98=20(#588)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 20-Generics.md * Update Appendix-Understanding-equals-and-hashCode.md 增加脚注 --- docs/book/20-Generics.md | 6 +++--- docs/book/Appendix-Understanding-equals-and-hashCode.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 00d25272..43bb33ba 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -4298,7 +4298,7 @@ test string 2 1494331663027 2 */ ``` -**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。 +**Mixin** 类基本上是在使用*委托*,因此每个混入类型都要求在 **Mixin** 中有一个相应的域,而你必须在 **Mixin** 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。[^4] ### 使用装饰器模式 @@ -4539,7 +4539,7 @@ Clank! ``` 在 Python 和 C++ 中,**Dog** 和 **Robot** 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,`perform()` 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。 -C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。 +C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。[^5]潜在类型机制没有损害强类型机制。 ### Go 中的潜在类型 @@ -5064,7 +5064,7 @@ public class Suppliers { } ``` -`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。 +`create()` 为你创建一个新的 **Collection** 子类型,而 `fill()` 的第一个版本将元素放入 **Collection** 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。[^6] 前两种方法一般都受约束,只能与 **Collection** 子类型一起使用。`fill()` 的第二个版本适用于任何类型的 **holder** 。 它需要一个附加参数:未绑定方法引用 `adder. fill()` ,使用辅助潜在类型来使其与任何具有添加元素方法的 **holder** 类型一起使用。因为此未绑定方法 **adder** 必须带有一个参数(要添加到 **holder** 的元素),所以 **adder** 必须是 `BiConsumer ` ,其中 **H** 是要绑定到的 **holder** 对象的类型,而 **A** 是要被添加的绑定元素类型。 对 `accept()` 的调用将使用参数 a 调用对象 **holder** 上的未绑定方法 **holder**。 diff --git a/docs/book/Appendix-Understanding-equals-and-hashCode.md b/docs/book/Appendix-Understanding-equals-and-hashCode.md index 01889642..a5c3dc25 100644 --- a/docs/book/Appendix-Understanding-equals-and-hashCode.md +++ b/docs/book/Appendix-Understanding-equals-and-hashCode.md @@ -675,7 +675,7 @@ SlowMap.java 说明了创建一种新的Map并不困难。但是正如它的名 答案就是:数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中的、且可能由你的类覆盖的hashCode()方法(在计算机科学的术语中称为散列函数)生成。 -于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。 +于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够保证没有冲突(如果值的数量是固定的,那么就有可能),那可就有了一个完美的散列函数,但是这种情况只是特例。[^1]。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的 list。然后对 list中的值使用equals()方法进行线性的查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不是查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。 理解了散列的原理,我们就能够实现一个简单的散列Map了: From 68fc4fdf1d7b085878353aea0e180c082ecf4735 Mon Sep 17 00:00:00 2001 From: rainbow Date: Mon, 21 Sep 2020 10:53:02 +0800 Subject: [PATCH 322/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D25=E7=AB=A0-=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E6=96=B9=E6=B3=95=E6=A8=A1=E5=BC=8F=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F=E9=97=AE=E9=A2=98=20(#590)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改25章-模板方法模式中的代码格式问题 * fix more code format error --- docs/book/25-Patterns.md | 58 +++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/docs/book/25-Patterns.md b/docs/book/25-Patterns.md index f3d37647..43c4f929 100644 --- a/docs/book/25-Patterns.md +++ b/docs/book/25-Patterns.md @@ -122,8 +122,26 @@ abstract class ApplicationFramework { abstract void customize1(); - abstract void customize2(); // "private" means automatically "final": private void templateMethod() { IntStream.range(0, 5).forEach( n -> { customize1(); customize2(); }); }}// Create a new "application": class MyApp extends ApplicationFramework { @Override void customize1() { System.out.print("Hello "); }@Override + abstract void customize2(); + + // "private" means automatically "final": + private void templateMethod() { + IntStream.range(0, 5).forEach( + n -> { + customize1(); + customize2(); + }); + } +} +// Create a new "application": +class MyApp extends ApplicationFramework { + @Override + void customize1() { + System.out.print("Hello "); + } + + @Override void customize2() { System.out.println("World!"); } @@ -134,8 +152,7 @@ public class TemplateMethod { new MyApp(); } } -/* -Output: +/* Output: Hello World! Hello World! Hello World! @@ -245,8 +262,20 @@ class State implements StateBase { @Override public void changeImp(StateBase newImp) { implementation = newImp; - }// Pass method calls to the implementation: @Override public void f() { implementation.f(); } @Override public void g() { implementation.g(); } @Override + } + // Pass method calls to the implementation: + @Override + public void f() { + implementation.f(); + } + + @Override + public void g() { + implementation.g(); + } + + @Override public void h() { implementation.h(); } @@ -302,7 +331,8 @@ public class StateDemo { } public static void main(String[] args) { - StateBase b = new State(new Implementation1()); + StateBase b = + new State(new Implementation1()); test(b); b.changeImp(new Implementation2()); test(b); @@ -349,9 +379,6 @@ interface State { abstract class StateMachine { protected State currentState; - Nap(0.5); -System.out.println("Washing"); new - protected abstract boolean changeState(); // Template method: @@ -362,9 +389,12 @@ System.out.println("Washing"); new } // A different subclass for each state: + class Wash implements State { @Override public void run() { + System.out.println("Washing"); + new Nap(0.5); } } @@ -386,9 +416,11 @@ class Rinse implements State { class Washer extends StateMachine { private int i = 0; - // The state table: - private State[] states = {new Wash(), new Spin(), new Rinse(), new Spin(),}; + private State[] states = { + new Wash(), new Spin(), + new Rinse(), new Spin(), + }; Washer() { runAll(); @@ -401,7 +433,8 @@ class Washer extends StateMachine { // surrogate reference to a new object: currentState = states[i++]; return true; - } else return false; + } else + return false; } } @@ -410,8 +443,7 @@ public class StateMachineDemo { new Washer(); } } -/* -Output: +/* Output: Washing Spinning Rinsing From 4cfa753144315083ecbf22074cbc35da256eb6c2 Mon Sep 17 00:00:00 2001 From: PEGASUS <32333727+PEGASUS1993@users.noreply.github.com> Date: Wed, 23 Sep 2020 16:08:06 +0800 Subject: [PATCH 323/371] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E5=B0=8F=E5=B0=8F=E5=B0=8F=E5=B0=8F=E6=AD=A7=E4=B9=89=20(#593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 00-Preface.md * Update 00-On-Java-8.md * Update 00-Preface.md * Update 00-Preface.md * Update Appendix-Becoming-a-Programmer.md --- docs/book/Appendix-Becoming-a-Programmer.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/book/Appendix-Becoming-a-Programmer.md b/docs/book/Appendix-Becoming-a-Programmer.md index edbf4c8b..437fbcc3 100644 --- a/docs/book/Appendix-Becoming-a-Programmer.md +++ b/docs/book/Appendix-Becoming-a-Programmer.md @@ -12,25 +12,25 @@ 我的朋友丹尼尔(就是设计我的书封面的人)有一个兄弟,他有段时间通过向酒吧和餐馆提供弹球机来赚钱。他有一台投币式街机(老虎机),最早的《乓》游戏之一,我对此全然不知,到现在我还忍受不了这东西(现在我几乎不玩电脑游戏,这样看来我可能是个没有幽默的人,但似乎编程比玩电脑游戏更有趣、更具挑战性。) -后来我在高中参与了摄影和新闻工作,在大学的第一年就主修新闻学。我觉得自己已经从学校学到了足够多的东西,又转修了物理学。后来我在加州大学欧文分校完成了物理学位,如果我当时选择了一个特定的工程领域,修了足够的工程课就能拿到双专业,但我试图走得更远一些,所以最后我获得的本科学位是 "应用物理"。作为一名本科生,我多多少少学习了一些可以自娱自乐,但又没有任何深度的计算机编程课程。我个人认为在这些课程细细熏陶下,帮我打下了一定的基础,但事实我理解的这些东西没有任何深度。我不知道计算机、编译器或解释器有什么区别(只是对编译器和解释器一点点的理解)。对我来说计算机是绝对可靠的,而且我从来没有想过在程序语言和操作系统中会有出现错误的可能。 +后来我在高中参与了摄影和新闻工作,在大学的第一年就主修新闻学。我觉得自己已经从学校学到了足够多的东西,又转修了物理学。后来我在加州大学尔湾分校完成了物理学位,如果我当时选择了一个特定的工程领域,修了足够的工程课就能拿到双专业,但我试图走得更远一些,所以最后我获得的本科学位是 "应用物理"。作为一名本科生,我多多少少学习了一些可以自娱自乐,但又没有任何深度的计算机编程课程。我个人认为在这些课程细细熏陶下,帮我打下了一定的基础,但事实我理解的这些东西没有任何深度。我不知道计算机、编译器或解释器有什么区别(只是对编译器和解释器一点点的理解)。对我来说计算机是绝对可靠的,而且我从来没有想过在程序语言和操作系统中会有出现错误的可能。 -后来我去了在加州州立理工大学攻读研究生,主要有三点原因 +后来我去了加州州立理工大学攻读研究生,主要有三点原因: -1. 我真的非常喜欢物理学这个领域 +1. 我真的非常喜欢物理学这个领域; -2. 他们接受了我,甚至给了我一份教学工作和奖学金 +2. 他们接收了我,甚至给了我一份教学工作和奖学金 ; -3. 出乎意料的是他们给我的工作时间不止一个夏天 +3. 出乎意料的是他们给我的工作时间不止一个夏天。 而我完全没做好上班的准备。 -作为一名物理专业的学生,我学习的是太阳能发电系统,当时太阳能发电系统很大 (如果你的房子上装了太阳能或生意上是关于太阳能系统,加州就会给予税收抵免,因此也兴起很多生意),加州理工大学也承诺会在工程系开设相应的课程。然而因为学校没有提供必要的课程,要想获得在太阳能工程的学位得花好几年时间。所以我学习了研究生其他的工程课,包括介绍机械,太阳能,电气和电子工程。我上的课是非电气工程专业的电气工程导论。最常见的研究生工程课程是计算机工程专业,所以最后我拿了那个学位。我还上了艺术课,几门舞蹈课,还有一些计算机科学课程 (Pascal和数据结构),在计算机工程中,我终于弄清楚了处理器的工作流程,从那以后我一直带着一个处理器在身上。这些就是我学的计算机基础知识。 +作为一名物理专业的学生,我学习的是太阳能发电系统,当时太阳能发电系统很大 (如果你的房子上装了太阳能或生意上是关于太阳能系统,加州就会给予税收抵免,因此也兴起很多生意),加州州立理工大学也承诺会在工程系开设相应的课程。然而因为学校没有提供必要的课程,要想获得在太阳能工程的学位得花好几年时间。所以我学习了研究生其他的工程课,包括介绍机械,太阳能,电气和电子工程。我上的课是非电气工程专业的电气工程导论。最常见的研究生工程课程是计算机工程专业,所以最后我拿了那个学位。我还上了艺术课,几门舞蹈课,还有一些计算机科学课程 (Pascal和数据结构),在计算机工程中,我终于弄清楚了处理器的工作流程,从那以后我一直带着一个处理器在身上。这些就是我学的计算机基础知识。 刚开始工作的时候,凭借着一堆硬件和相对简单低水平的编程,做了一名计算机工程师。因为C语言似乎是理想的嵌入式系统语言,于是我开始自学,并慢慢开始了解更多关于编程语言的东西。我们在这家公司从源代码构建编译器,这让我大开眼界。 (想象一下一个编译器只是另一个软件的一部分!) 当我去华盛顿大学海洋学院为Tom Keffer后来创建了“疯狗浪”)工作时,我们决定使用C++。我只有一本Stroustrup写的非初学者书可以参考,最终不得不通过检查C++预处理器生成的中间C代码来了解语言的功能。这个过程非常痛苦,但学习的效果很好。从那以后我就用相同的方式学习,因为它让我学习了如何剖析一种语言,并看到它本质的能力,与此同时开始有了批判性思维。 -我并没有理解清楚所有的概念。只是在之后的日子里不断反复,我所知道的一切需要时间才能消化吸收。如果我现在能很容易地理解一个新概念,那只是因为它是我已经知道的积累概念的一个变种。在加州理工大学招收非计算机本科学历的计算机科学研究生项目中,学生们曾经说他们花了一年的时间才弄清楚他们对计算机的困惑(他们正在沉浸程序之中)。当人们学习计算机时,他们往往会对自己抱有不切实际的期望,通常是他们听说学计算机编程的好处,就希望在几周内找到一份高薪的工作。但是,最好的学习过程是先对计算机感兴趣,随着时间的推移,学习的越来越多,自然的就开始自学。 +我并没有理解清楚所有的概念。只是在之后的日子里不断反复,我所知道的一切需要时间才能消化吸收。如果我现在能很容易地理解一个新概念,那只是因为它是我已经知道的积累概念的一个变种。在加州州立理工大学招收非计算机本科学历的计算机科学研究生项目中,学生们曾经说他们花了一年的时间才弄清楚他们对计算机的困惑(他们正在沉浸程序之中)。当人们学习计算机时,他们往往会对自己抱有不切实际的期望,通常是他们听说学计算机编程的好处,就希望在几周内找到一份高薪的工作。但是,最好的学习过程是先对计算机感兴趣,随着时间的推移,学习的越来越多,自然的就开始自学。 这些就是我主要做的事,尽管我通过学计算机工程有还算扎实的基础,但我没上过编程课,而是通过自学。在此期间我也在不断地学习新事物,在这个行业里,不断学习是非常重要的一部分。 From d0076930a80ecfe13998f61f7dc37d113bd56c95 Mon Sep 17 00:00:00 2001 From: PolygonT <56947591+PolygonT@users.noreply.github.com> Date: Wed, 23 Sep 2020 03:08:43 -0500 Subject: [PATCH 324/371] Update 24-Concurrent-Programming.md (#592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit final和int间添加空格 From 6eab4fb16d9ae542ef90bd1da819316a553dfb69 Mon Sep 17 00:00:00 2001 From: TENCHIANG Date: Fri, 25 Sep 2020 14:16:34 +0800 Subject: [PATCH 325/371] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=EF=BC=9A24-Concurrent-Programming.md#=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E7=9A=84=E8=B6=85=E8=83=BD=E5=8A=9B=20(#594)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 优化翻译:24-Concurrent-Programming.md#并发的超能力 * 优化翻译:24-Concurrent-Programming.md#并发的超能力 fix翻译错误 --- docs/book/24-Concurrent-Programming.md | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 668db583..f0eaa0d2 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -1,6 +1,7 @@ [TOC] + # 第二十四章 并发编程 > 爱丽丝:“我可不想到疯子中间去” @@ -89,47 +90,47 @@ slowdown occurs): ## 并发的超能力 -想象一下,你置身于一部科幻电影。你必须在高层建筑中搜索一个精心巧妙地隐藏在建筑物的一千万个房间之一中的单个物品。你进入建筑物并沿着走廊向下移动。走廊分开了。 +想象一下,你置身于一部科幻电影。你必须在一栋大楼中找到一个东西,它被小心而巧妙地隐藏在大楼一千万个房间中的一间。你进入大楼,沿着走廊走下去。走廊是分开的。 -你自己完成这项任务需要一百个生命周期。 +一个人完成这项任务要花上一百辈子的时间。 现在假设你有一个奇怪的超能力。你可以将自己一分为二,然后在继续前进的同时将另一半送到另一个走廊。每当你在走廊或楼梯上遇到分隔到下一层时,你都会重复这个分裂的技巧。最终,整个建筑中的每个走廊的终点都有一个你。 -每个走廊都有一千个房间。你的超能力变得有点弱,所以你只能分裂出 50 个自己来搜索这间房间。 +每个走廊都有一千个房间。此时你的超能力变得弱了一点,你只能克隆 50 个自己来并发搜索走廊里面的房间。 -一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,你获得了寻找的物品是否在房间内的消息。 +一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,不知怎么的,你就知道这间房间里有没有那个东西。 我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是泄漏抽象的(leaky abstraction)。 -以下是其中一个漏洞:在理想的世界中,每次克隆自己时,你还会复制硬件处理器来运行该克隆。但当然不会发生这种情况 - 你的机器上可能有四个或八个处理器(通常在写入时)。你可能还有更多,并且仍有许多情况只有一个处理器。在抽象的讨论中,物理处理器的分配方式不仅可以泄漏,甚至可以支配你的决策 +以下是其中一个漏洞:在理想的世界中,每次克隆自己时,还需要复制一个物理处理器来运行该克隆。这当然是不现实的——实际上,你的机器上一般只有 4 个或 8 个处理器核心(编写本文时的典型情况)。你也可能更多,但仍有很多情况下只有一个单核处理器。在关于抽象的讨论中,分配物理处理器核心这本身就是抽象的泄露,甚至也可以支配你的决策。 -让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人回答。如果我们每个搜索者有一个处理器,这没有问题 - 处理器只是空闲,直到门被回答。但是如果我们只有 8 个处理器和数千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 +让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人开门。如果每个搜索者都有一个处理器核心,这没有问题——只是空闲等待直到有人开门。但是如果我们只有 8 个处理器核心却有几千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 -许多模型能够有效地隐藏处理器的数量,并允许你假装你的数量非常大。但是有些情况会发生故障的时候,你必须知道处理器的数量,以便你可以解决这个问题。 +许多模型能够有效地隐藏处理器的数量,允许你假装有很多个处理器。但在某些情况下,这种方法会失效,这时你必须知道处理器核心的真实数量,以便处理这个问题。 -其中一个最大的影响取决于你是单个处理器还是多个处理器。如果你只有一个处理器,那么任务切换的成本也由该处理器承担,将并发技术应用于你的系统会使它运行得更慢。 +最大的影响之一取决于您是使用单核处理器还是多核处理器。如果你只有单核处理器,那么任务切换的成本也由该核心承担,将并发技术应用于你的系统会使它运行得更慢。 -这可能会让你认为,在单个处理器的情况下,编写并发代码时没有意义。然而,有些情况下,并发模型会产生更简单的代码,实际上为了这个目的值得让它运行得更慢。 +这可能会让你以为,在单核处理器的情况下,编写并发代码是没有意义的。然而,有些情况下,并发模型会产生更简单的代码,光是为了这个目的就值得舍弃一些性能。 -在克隆体敲门等待的情况下,即使单处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备好的任务。但是如果所有任务都可以一直运行那么切换的成本反而会使任务变慢,在这种情况下,如果你有多个进程,并发通常只会有意义。 +在克隆体敲门等待的情况下,即使单核处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备运行的任务。但是如果所有任务都可以一直运行那么切换的成本反而会使任务变慢,在这种情况下,如果你有多个进程,并发通常只会有意义。 -假设你正在尝试破解某种密码,在同一时间内参与破解的线程越多,你越快得到答案的可能性就越大。每个线程都能持续使用你所分配的处理器时间,在这种情况下(一个计算约束问题),你的代码中分配的线程数应该和你拥有的处理器的数量保持一致。 +如果你正在尝试破解某种密码,在同一时间内参与破解的线程越多,你越快得到答案的可能性就越大。每个线程都能持续使用你所分配的处理器时间,在这种情况下(CPU 密集型问题),你代码中的线程数应该和你拥有的处理器的核心数保持一致。 -在接听电话的客户服务部门,你只有一定数量的员工,但是你的部门可能会收到很多电话。这些员工(处理器)一次只能处理一个电话,直到完成,与此同时,额外的电话必须排队。 +在接听电话的客户服务部门,你只有一定数量的员工,但是你的部门可能会打来很多电话。这些员工(处理器)一次只能接听一个电话直到打完,此时其它打来的电话必须排队等待。 -在“鞋匠和精灵”的童话故事中,鞋匠有很多工作要做,当他睡着时,出现了一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时也会产生限制 - 例如,鞋底需要大量的时间去制作,这会限制制鞋的速度并改变你设计解决方案的方式。 +在“鞋匠和精灵”的童话故事中,鞋匠有很多工作要做,当他睡着时,出现了一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时也会产生限制——例如,如果鞋底的制作时间最长,这就限制了鞋子的制作速度,这也会改变你设计解决方案的方式。 -因此,你尝试解决的问题驱动解决方案的设计。有一个迷人的抽象那就是将一个问题分解为子问题并且让它们独立运行,然后就是赤裸裸的现实。物理现实一次又一次地打了这种抽象的脸。 +因此,你要解决的问题推动了解决方案的设计。将一个问题分解成“独立运行”的子任务,这是一种美好的抽象,然后就是残酷的现实:物理现实不断地侵入和动摇这个抽象。 -这只是问题的一部分。考虑一个制作蛋糕的工厂。我们不知何故在工人中分发了蛋糕制作任务,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备存放蛋糕。但是,在工人将蛋糕放入盒子之前,另一名工人投入并将蛋糕放入盒子中!我们的工人已经把蛋糕放进去了,然后就开始了!这两个蛋糕被砸碎并毁了。这是常见的“共享内存”问题,产生我们称之为竞争条件的问题,其结果取决于哪个工作人员可以首先将蛋糕放入盒子中(通常使用锁机制来解决问题,因此一个工作人员可以先拿到盒子并防止蛋糕被砸烂)。 +这只是问题的一部分:考虑一个制作蛋糕的工厂。我们以某种方式把制作蛋糕的任务分给了工人们,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备存放蛋糕。但是在工人把蛋糕放进盒子之前,另一个工人就冲过去,把蛋糕放进盒子里,砰!这两个蛋糕撞到一起砸坏了。这是常见的“共享内存”问题,会产生所谓的竞态条件(race condition),其结果取决于哪个工人能先把蛋糕放进盒子里(通常使用锁机制来解决问题,因此一个工作人员可以先抓住一个盒子并防止蛋糕被砸烂)。 -当“同时”执行的任务相互干扰时,会出现问题。它可以以如此微妙和偶然的方式发生,可能公平地说,并发性“可以说是确定性的,但实际上是非确定性的。”也就是说,你可以假设编写通过维护和代码检查正常工作的并发程序。然而,在实践中,我们编写的并发程序似乎都能正常工作,但是在适当的条件下,将会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。 -这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 +当“同时”执行的任务相互干扰时,就会出现问题。这可能以一种微妙而偶然的方式发生,因此可以说并发是“可以论证的确定性,但实际上是不确定性的”。也就是说,假设你很小心地编写并发程序,而且通过了代码检查可以正确运行。然而实际上,我们编写的并发程序大部分情况下都能正常运行,但是在一些情况下会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 -因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管 Java 8 在并发性方面做出了很大改进,但仍然没有像编译时验证 (compile-time verification) 或受检查的异常 (checked exceptions) 那样的安全网来告诉你何时出现错误。通过并发,你只能依靠自己,只有知识渊博,保持怀疑和积极进取的人,才能用 Java 编写可靠的并发代码。 +因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管 Java 8 在并发性方面做出了很大改进,但仍然没有像编译时验证 (compile-time verification) 或受检查的异常 (checked exceptions) 那样的安全网来告诉你何时出现错误。关于并发,你只能依靠自己,只有知识渊博、保持怀疑和积极进取的人,才能用 Java 编写可靠的并发代码。 + ## 并发为速度而生 在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定用它之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置替换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在必要的地方去使用它。 From 7285c0229f016255e0afb15fc57303c81e7666f3 Mon Sep 17 00:00:00 2001 From: KIDlty Date: Sun, 27 Sep 2020 10:20:27 +0800 Subject: [PATCH 326/371] Update 00-Preface.md (#598) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For this I might receive criticism for using “toy examples,” but I’m willing to accept that in favor of producing something pedagogically useful. 翻译修正 --- docs/book/00-Preface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index 903b92e5..0e2ccbd0 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -17,7 +17,7 @@ 1. 循序渐进地呈现学习内容,以便于你在不依赖后置知识框架的情况下轻松完成现有的学习任务,同时尽量保证前面章节的内容在后面的学习中得到运用。如果确有必要引入我们还没学习到的知识概念,我会做个简短地介绍。 -2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意看到我的读者们因此能保持饶有兴趣地学习。 +2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意相信这样的教学方式更加有效。 3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。 From 8fcffc3cdb15cf12ff0d75af529797bc10a78c24 Mon Sep 17 00:00:00 2001 From: jangbeyond <70657747+jangbeyond@users.noreply.github.com> Date: Sun, 27 Sep 2020 15:55:20 +0800 Subject: [PATCH 327/371] =?UTF-8?q?=E7=AC=AC=E5=8D=81=E5=9B=9B=E7=AB=A0=20?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3Optional=E7=B1=BB=E6=A0=87=E9=A2=98=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E7=AC=AC=E4=B8=80=E6=AE=B5=20(#595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修正Optional类标题下的第一段 这段的译文确实不太好懂,所以我尝试着修改了一下。 不过,Happy Path 这个词没似乎没有正式的术语。 有道词典显示,有36个网页翻译成了「快乐路径」,有4个网页翻译成了「愉悦路径」,因此我采取了「快乐路径」这个译法。 * Update 14-Streams.md 空流应该还是更准确一点吧。。。囧 * Change the position of the footnote.... --- docs/book/14-Streams.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index f3d85cef..ba2fd7c6 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1138,7 +1138,7 @@ is it ## Optional类 -在我们学习终端操作(Terminal Operations)之前,我们必须考虑如果你在一个空流中获取元素会发生什么。我们喜欢为了“happy path”而将流连接起来,并假设流不会被中断。在流中放置 `null` 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)? +在我们学习终端操作(Terminal Operations)之前,我们必须考虑在一个空流中获取元素会发生什么。我们喜欢沿着“快乐路径”[^1]把流连接起来,同时假设流不会中断。然而,在流中放置 `null` 却会轻易令其中断。那么是否存在某种对象,可以在持有流元素的同时,即使在我们查找的元素不存在时,也能友好地对我们进行提示(也就是说,不会产生异常)? **Optional** 可以实现这样的功能。一些标准流操作返回 **Optional** 对象,因为它们并不能保证预期结果一定存在。包括: @@ -2144,6 +2144,8 @@ IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998} 流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。在本书的剩余部分,我们将尽可能地使用流。 +[^1]: 在软件或信息建模的上下文中,快乐路径(有时称为快乐流)是没有异常或错误条件的默认场景。例如,验证信用卡号的函数的快乐路径应该是任何验证规则都不会出现错误的地方,从而让执行成功地继续到最后,生成一个积极的响应。[见 wikipedia: happy path](https://en.wikipedia.org/wiki/Happy_path) +
From f70058c853556e474aa7b7e6c7e16086f62379fd Mon Sep 17 00:00:00 2001 From: Xingkai Yu <38156925+GeekEmperor@users.noreply.github.com> Date: Mon, 28 Sep 2020 16:21:40 +0800 Subject: [PATCH 328/371] =?UTF-8?q?Fix=20issue=20#596:=20=E7=AC=AC?= =?UTF-8?q?=E5=85=AB=E7=AB=A0=E5=92=8C=E7=AC=AC=E4=B9=9D=E7=AB=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=94=99=E4=BD=8D=20(#600)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/08-Reuse.md | 2 +- docs/book/09-Polymorphism.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index baad9ac2..2e37dabf 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -869,7 +869,7 @@ public class Wind extends Instrument { 该术语是基于传统的类继承图:图最上面是根,然后向下铺展。(当然你可以以任意方式画你认为有帮助的类图。)于是,**Wind.java** 的类图是: -![Wind 类图](../images/1561774164644.png) +![Wind 类图](../images/1562204648023.png) 继承图中派生类转型为基类是向上的,所以通常称作*向上转型*。因为是从一个更具体的类转化为一个更一般的类,所以向上转型永远是安全的。也就是说,派生类是基类的一个超集。它可能比基类包含更多的方法,但它必须至少具有与基类一样的方法。在向上转型期间,类接口只可能失去方法,不会增加方法。这就是为什么编译器在没有任何明确转型或其他特殊标记的情况下,仍然允许向上转型的原因。 diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 6a105c6a..63075630 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -184,7 +184,7 @@ Java 中除了 **static** 和 **final** 方法(**private** 方法也是隐式 形状的例子中,有一个基类称为 **Shape** ,多个不同的派生类型分别是:**Circle**,**Square**,**Triangle** 等等。这个例子之所以好用,是因为我们可以直接说“圆(Circle)是一种形状(Shape)”,这很容易理解。继承图展示了它们之间的关系: -![形状继承图](../images/1562204648023.png) +![形状继承图](../images/1561774164644.png) 向上转型就像下面这么简单: From 7dc616e6d7e878269883db18b6784e43135b9ece Mon Sep 17 00:00:00 2001 From: TENCHIANG Date: Wed, 30 Sep 2020 10:49:34 +0800 Subject: [PATCH 329/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E4=BC=98=E5=8C=96=20?= =?UTF-8?q?24-Concurrent-Programming.md#=E5=B9=B6=E5=8F=91=E4=B8=BA?= =?UTF-8?q?=E9=80=9F=E5=BA=A6=E8=80=8C=E7=94=9F=20#Java=20=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E7=9A=84=E5=9B=9B=E5=8F=A5=E6=A0=BC=E8=A8=80=20(#597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 翻译优化 24-Concurrent-Programming.md#并发为速度而生 #Java 并发的四句格言 * 翻译优化 24-Concurrent-Programming.md#并发为速度而生 #Java 并发的四句格言 fix翻译错误 --- docs/book/24-Concurrent-Programming.md | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index f0eaa0d2..e8ed661e 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -135,91 +135,91 @@ slowdown occurs): 在听说并发编程的问题之后,你可能会想知道它是否值得这么麻烦。答案是“不,除非你的程序运行速度不够快。”并且在决定用它之前你会想要仔细思考。不要随便跳进并发编程的悲痛之中。如果有一种方法可以在更快的机器上运行你的程序,或者如果你可以对其进行分析并发现瓶颈并在该位置替换更快的算法,那么请执行此操作。只有在显然没有其他选择时才开始使用并发,然后仅在必要的地方去使用它。 -速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解成碎片并在一个单独的处理器上运行每个部分。由于我们能够提高时钟速度流(至少对于传统芯片),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使你的程序运行得更快,你必须学会如何利用那些额外的处理器,这是并发性给你的一个建议。 +速度问题一开始听起来很简单:如果你想要一个程序运行得更快,将其分解为多个部分,并在单独的处理器上运行每个部分。随着我们提高时钟速度的能力耗尽(至少对传统芯片而言),速度的提高是出现在多核处理器的形式而不是更快的芯片。为了使程序运行得更快,你必须学会利用那些额外的处理器(译者注:处理器一般代表 CPU 的一个逻辑核心),这是并发所带来的好处之一。 -使用多处理器机器,可以在这些处理器之间分配多个任务,这可以显着提高吞吐量。强大的多处理器 Web 服务器通常就是这种情况,它可以在程序中为 CPU 分配大量用户请求,每个请求分配一个线程。 +对于多处理器机器,可以在这些处理器之间分配多个任务,这可以显著提高吞吐量。强大的多处理器 Web 服务器通常就是这种情况,它可以在程序中为 CPU 分配大量用户请求,每个请求分配一个线程。 -但是,并发性通常可以提高在单个处理器上运行的程序的性能。这听起来有点违反直觉。如果考虑一下,由于上下文切换的成本增加(从一个任务更改为另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。在表面上,将程序的所有部分作为单个任务运行并节省上下文切换的成本似乎更便宜。 +但是,并发通常可以提高在单处理器上运行的程序的性能。这听起来有点违反直觉。如果你仔细想想,由于上下文切换的成本增加(从一个任务切换到另一个任务),在单个处理器上运行的并发程序实际上应该比程序的所有部分顺序运行具有更多的开销。从表面上看,将程序的所有部分作为单个任务运行,并且节省上下文切换的成本,这样看似乎更划算。 -可以产生影响的问题是阻塞。如果你的程序中的一个任务由于程序控制之外的某些条件(通常是 I/O)而无法继续,我们会说任务或线程阻塞(在我们的科幻故事中,克隆体已敲门而且是等待它打开)。如果没有并发性,整个程序就会停止,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻止时,程序中的其他任务可以继续执行,因此程序继续向前移动。实际上,从性能的角度来看,在单处理器机器上使用并发是没有意义的,除非其中一个任务可能阻塞。 +使这个问题变得有些不同的是阻塞。如果程序中的某个任务由于程序控制之外的某种情况而无法继续(通常是 I/O),我们就称该任务或线程已阻塞(在我们的科幻故事中,就是克隆人已经敲门并等待它打开)。如果没有并发,整个程序就会停下来,直到外部条件发生变化。但是,如果使用并发编写程序,则当一个任务被阻塞时,程序中的其他任务可以继续执行,因此整个程序得以继续运行。事实上,从性能的角度来看,如果没有任务会阻塞,那么在单处理器机器上使用并发是没有意义的。 -单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些长时间运行操作,从而最终忽略用户输入和无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中轮询它。这会产生笨拙的代码,无法保证程序员不会忘记执行检查。没有并发性,生成响应式用户界面的唯一方法是让所有任务定期检查用户输入。通过创建单独的执行线程来响应用户输入,该程序保证了一定程度的响应。 +单处理器系统中性能改进的一个常见例子是事件驱动编程,特别是用户界面编程。考虑一个程序执行一些耗时操作,最终忽略用户输入导致无响应。如果你有一个“退出”按钮,你不想在你编写的每段代码中都检查它的状态(轮询)。这会产生笨拙的代码,也无法保证程序员不会忘了检查。没有并发,生成可响应用户界面的唯一方法是让所有任务都定期检查用户输入。通过创建单独的线程以执行用户输入的响应,能够让程序保证一定程度的响应能力。 -实现并发的直接方法是在操作系统级别,使用与线程不同的进程。进程是一个在自己的地址空间内运行的自包含程序。进程很有吸引力,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程共享内存和 I/O 等资源,因此编写多线程程序时遇到的困难是在不同的线程驱动的任务之间协调这些资源,一次不能通过多个任务访问它们。 +实现并发的一种简单方式是使用操作系统级别的进程。与线程不同,进程是在其自己的地址空间中运行的独立程序。进程的优势在于,因为操作系统通常将一个进程与另一个进程隔离,因此它们不会相互干扰,这使得进程编程相对容易。相比之下,线程之间会共享内存和 I/O 等资源,因此编写多线程程序最基本的困难,在于协调不同线程驱动的任务之间对这些资源的使用,以免这些资源同时被多个任务访问。 -有些人甚至提倡将进程作为并发的唯一合理方法[^1],但不幸的是,通常存在数量和开销限制,从而阻止了在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”) +有些人甚至提倡将进程作为唯一合理的并发实现方式[^1],但遗憾的是,通常存在数量和开销方面的限制,从而阻止了进程在并发范围内的适用性(最终你会习惯标准的并发限制,“这种方法适用于一些情况但不适用于其他情况”) -一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生其他影响(因此不能与其他函数干涉),因此可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果你发现程序的某一部分必须大量使用并发性并且你在尝试构建该部分时遇到了过多的问题,那么你可能会考虑使用专用并发语言创建程序的那一部分。 +一些编程语言旨在将并发任务彼此隔离。这些通常被称为_函数式语言_,其中每个函数调用不产生副作用(不会干扰到其它函数),所以可以作为独立的任务来驱动。Erlang 就是这样一种语言,它包括一个任务与另一个任务进行通信的安全机制。如果发现程序的某一部分必须大量使用并发,并且在尝试构建该部分时遇到了过多的问题,那么可以考虑使用这些专用的并发语言创建程序的这个部分。 -Java 采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分配外部进程,线程在执行程序所代表的单个进程中创建任务交换。 +Java 采用了更传统的方法[^2],即在顺序语言之上添加对线程的支持而不是在多任务操作系统中分叉外部进程,线程是在表示执行程序的单个进程内创建任务。 -并发性会带来成本,包括复杂性成本,但可以通过程序设计,资源平衡和用户便利性的改进来抵消。通常,并发性使你能够创建更加松散耦合的设计;除此以外,你必须特别关注那些使用了并发操作的代码。 +并发会带来各种成本,包括复杂性成本,但可以被程序设计、资源平衡和用户便利性方面的改进所抵消。通常,并发性使你能够创建更低耦合的设计;另一方面,你必须特别关注那些使用了并发操作的代码。 -## 四句格言 +## Java 并发的四句格言 -在经历了多年的 Java 并发之后,我总结了以下四个格言: ->1.不要这样做 +在经历了多年 Java 并发的实践之后,我总结了以下四个格言: +>1.不要用它(避免使用并发) > >2.没有什么是真的,一切可能都有问题 > ->3.它起作用,并不意味着它没有问题 +>3.仅仅是它能运行,并不意味着它没有问题 > ->4.你仍然必须理解它 +>4.你必须理解它(逃不掉并发) 这些格言专门针对 Java 的并发设计问题,尽管它们也可以适用于其他一些语言。但是,确实存在旨在防止这些问题的语言。 -### 1.不要这样做 +### 1.不要用它 -(不要自己动手) +(而且不要自己去实现它) -避免纠缠于并发产生的深层问题的最简单方法就是不要这样做。虽然它是诱人的,并且在做一些简单的事情时似乎足够安全,但它存在无数、微妙的陷阱。如果你可以避免它,你的生活会更容易。 +避免陷入并发所带来的玄奥问题的最简单方法就是不要用它。尽管尝试一些简单的东西可能很诱人,也似乎足够安全,但是陷阱却是无穷且微妙的。如果你能避免使用它,你的生活将会轻松得多。 -证明并发性的唯一因素是速度。如果你的程序运行速度不够快 - 在这里要小心,因为只是希望它运行得更快是不合理的 - 应该首先用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。 +使用并发唯一的正当理由是速度。如果你的程序运行速度不够快——这里要小心,因为仅仅想让它运行得更快不是正当理由——应该首先用一个分析器(参见代码校验章中分析和优化)来发现你是否可以执行其他一些优化。 -如果你被迫进行并发,请采取最简单,最安全的方法来解决问题。使用众所周知的库并尽可能少地编写自己的代码。有了并发性,就没有“太简单了”。自负才是你的敌人。 +如果你被迫使用并发,请采取最简单,最安全的方法来解决问题。使用知名的库并尽可能少地自己编写代码。对于并发,就没有“太简单了”——自作聪明是你的敌人。 ### 2.没有什么是真的,一切可能都有问题 -没有并发性的编程,你会发现你的世界有一定的顺序和一致性。通过简单地将变量赋值给某个值,很明显它应该始终正常工作。 +不使用并发编程,你已经预料到你的世界具有确定的顺序和一致性。对于变量赋值这样简单的操作,很明显它应该总是能够正常工作。 -在并发领域,有些事情可能是真的而有些事情却不是,你必须认为没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能或者可能不会按预期的方式工作,并且从那里开始走下坡路。对于在并发中遇到那些看起来有效但实际上无效的东西,我已经很习惯了。 +在并发领域,有些事情可能是真的而有些事情却不是,以至于你必须假设没有什么是真的。你必须质疑一切。即使将变量设置为某个值也可能不会按预期的方式工作,事情从这里开始迅速恶化。我已经熟悉了这样一种感觉:我认为应该明显奏效的东西,实际上却行不通。 -在非并发程序中你可以忽略的各种事情在并发程序中突然变得非常重要。例如,你必须知道处理器缓存以及保持本地缓存与主内存一致的问题。你必须深入了解对象构造的复杂性,以便你的构造器不会意外地将数据暴露给其他线程进行更改。问题还有很多。 +在非并发编程中你可以忽略的各种事情,在并发下突然变得很重要。例如,你必须了解处理器缓存以及保持本地缓存与主内存一致的问题,你必须理解对象构造的深层复杂性,这样你的构造函数就不会意外地暴露数据,以致于被其它线程更改。这样的例子不胜枚举。 -因为这些主题太复杂,本章无法为你提供更专业的知识(再次参见 Java Concurrency in Practice),但你必须了解它们。 +虽然这些主题过于复杂,无法在本章中给你提供专业知识(同样,请参见 Java Concurrency in Practice),但你必须了解它们。 -### 3.它起作用,并不意味着它没有问题 +### 3.仅仅是它能运行,并不意味着它没有问题 -我们很容易编写出一个看似完美实则有问题的并发程序,并且往往问题直在极端情况下才暴露出来 - 在程序部署后不可避免地会出现用户问题。 +我们很容易编写出一个看似正常实则有问题的并发程序,而且问题只有在极少的情况下才会显现出来——在程序部署后不可避免地会成为用户问题(投诉)。 -- 你不能证明并发程序是正确的,你只能(有时)证明它是不正确的。 -- 大多数情况下你甚至不能这样做:如果它有问题,你可能无法检测到它。 -- 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。 +- 你不能验证出并发程序是正确的,你只能(有时)验证出它是不正确的。 +- 大多数情况下你甚至没办法验证:如果它出问题了,你可能无法检测到它。 +- 你通常无法编写有用的测试,因此你必须依靠代码检查和对并发的深入了解来发现错误。 - 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。 -在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect) 效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 +在其他 Java 主题中,我们养成了决定论的观念。一切都按照语言的承诺的(或暗示的)发生,这是令人欣慰的也是人们所期待的——毕竟,编程语言的意义就是让机器做我们想要它做的事情。从确定性编程的世界进入并发编程领域,我们遇到了一种称为 [邓宁-克鲁格效应](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect) 的认知偏差,可以概括为“无知者无畏”,意思是:“相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 -我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码前了解所有并发问题。 +我自己的经验是,无论你是多么确定你的代码是_线程安全_的,它都可能是有问题的。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念,让你意识到你编写的大多数代码实际上都容易受到并发 bug 的影响。当某些代码不正确时,编译器不会告诉你。为了使它正确,在研究代码时,必须将并发性的所有问题都放在前脑中。 -在 Java 的所有非并发领域,“没有明显的错误和没有明显的编译错误”似乎意味着一切都好。对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 +在 Java 的所有非并发领域,“没有明显的 bug 而且没有编译报错“似乎意味着一切都好。但对于并发,它没有任何意义。在这种情况你最糟糕的表现就是“自信”。 -### 4.你必须仍然理解 +### 4.你必须理解它 在格言 1-3 之后,你可能会对并发性感到害怕,并且认为,“到目前为止,我已经避免了它,也许我可以继续避免它。 -这是一种理性的反应。你可能知道其他编程语言更好地设计用于构建并发程序 - 甚至是在 JVM 上运行的程序(从而提供与 Java 的轻松通信),例如 Clojure 或 Scala。为什么不用这些语言编写并发部分并将 Java 用于其他所有部分呢? +这是一种理性的反应。你可能知道其他更好地被设计用于构建并发程序的编程语言——甚至是在 JVM 上运行的语言(从而提供与 Java 的轻松通信),例如 Clojure 或 Scala。为什么不用这些语言来编写并发部分,然后用Java来做其他的事情呢? 唉,你不能轻易逃脱: -- 即使你从未明确地创建一个线程,你可能使用的框架 - 例如,Swing 图形用户界面(GUI)库,或者像 **Timer** class 那样简单的东西。 -- 这是最糟糕的事情:当你创建组件时,你必须假设这些组件可能在多线程环境中重用。即使你的解决方案是放弃并声明你的组件“不是线程安全的”,你仍然必须知道这样的声明是重要的,它是什么意思? +- 即使你从未显示地创建一个线程,你使用的框架也可能——例如,Swing 图形用户界面(GUI)库,或者像 **Timer** 类(计时器)那样简单的东西。 +- 最糟糕的是:当你创建组件时,必须假设这些组件可能会在多线程环境中重用。即使你的解决方案是放弃并声明你的组件是“非线程安全的”,你仍然必须充分了解这样一个语句的重要性及其含义。 -人们有时会认为并发性太难,不能包含在介绍该语言的书中。他们认为并发是一个可以独立对待的独立主题,并且它在日常编程中出现的少数情况(例如图形用户界面)可以用特殊的习语来处理。如果你可以避免它,为什么要介绍这样的复杂的主题。 +人们有时会认为并发对于介绍语言的书来说太高级了,因此不适合放在其中。他们认为并发是一个独立的主题,并且对于少数出现在日常的程序设计中的情况(例如图形用户界面),可以用特殊的惯用法来处理。如果你可以回避,为什么还要介绍这么复杂的主题呢? -唉,如果只是这样的话,那就太好了。但不幸的是,你无法选择何时在 Java 程序中出现线程。仅仅你从未写过自己的线程,并不意味着你可以避免编写线程代码。例如,Web 系统是最常见的 Java 应用程序之一,本质上是多线程的 Web 服务器通常包含多个处理器,而并行性是利用这些处理器的理想方式。就像这样的系统看起来那么简单,你必须理解并发才能正确地编写它。 +唉,如果是这样就好了。遗憾的是,对于线程何时出现在 Java 程序中,这不是你能决定的。仅仅是你自己没有启动线程,并不代表你就可以回避编写使用线程的代码。例如,Web 系统是最常见的 Java 应用之一,本质上是多线程的 Web 服务器,通常包含多个处理器,而并行是利用这些处理器的理想方式。尽管这样的系统看起来很简单,但你必须理解并发才能正确地编写它。 -Java 是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有许多 Java 程序正在使用中,或者只是偶然工作,或者大部分时间工作并且不时地发生问题,因为。有时这种问题是相对良性的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题放在其他地方而不是你的代码中。如果将程序移动到多处理器系统,则可以暴露或放大这些类型的问题。基本上,了解并发性使你意识到正确的程序可能会表现出错误的行为。 +Java 是一种多线程语言,不管你有没有意识到并发问题,它就在那里。因此,有很多使用并发的 Java 程序,要么只是偶然运行,要么大部分时间都在运行,并且会因为未被发现的并发缺陷而时不时地神秘崩溃。有时这种崩溃是相对温和的,但有时它意味着丢失有价值的数据,如果你没有意识到并发问题,你最终可能会把问题归咎于其他地方而不是你的代码中。如果将程序移动到多处理器系统中,这些类型的问题还会被暴露或放大。基本上,了解并发可以使你意识到明显正确的程序也可能会表现出错误的行为。 ## 残酷的真相 From 0434fa9c4906d4eae82e5808ecc2e5943a625865 Mon Sep 17 00:00:00 2001 From: Hongkuan Wang Date: Fri, 9 Oct 2020 10:37:18 +0800 Subject: [PATCH 330/371] fix typo (#603) --- docs/book/Appendix-Object-Serialization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/Appendix-Object-Serialization.md b/docs/book/Appendix-Object-Serialization.md index 722f0e51..648373bd 100644 --- a/docs/book/Appendix-Object-Serialization.md +++ b/docs/book/Appendix-Object-Serialization.md @@ -199,7 +199,7 @@ class Alien 正如大家所看到的,默认的序列化机制并不难操纵。然而,如果有特殊的需要那又该怎么办呢?例如,也许要考虑特殊的安全问题,而且你不希望对象的某一部分被序列化;或者一个对象被还原以后,某子对象需要重新创建,从而不必将该子对象序列化。 -在这些特殊情况下,可通过实现 Externalizable 接口——代替实现 Serializable 接口-来对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal0 和 readExternal0。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。 +在这些特殊情况下,可通过实现 Externalizable 接口——代替实现 Serializable 接口-来对序列化过程进行控制。这个 Externalizable 接口继承了 Serializable 接口,同时增添了两个方法:writeExternal() 和 readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊操作。 下面这个例子展示了 Externalizable 接口方法的简单实现。注意 Blip1 和 Blip2 除了细微的差别之外,几乎完全一致(研究一下代码,看看你能否发现): @@ -366,7 +366,7 @@ Blip3.readExternal A String 47 ``` -其中,字段 s 和只在第二个构造器中初始化,而不是在默认的构造器中初始化。这意味着假如不在 readExternal0 中初始化 s 和 i,s 就会为 null,而就会为零(因为在创建对象的第一步中将对象的存储空间清理为 0)。如果注释掉跟随于"You must do this”后面的两行代码,然后运行程序,就会发现当对象被还原后,s 是 null,而 i 是零。 +其中,字段 s 和 i 只在第二个构造器中初始化,而不是在默认的构造器中初始化。这意味着假如不在 readExternal() 中初始化 s 和 i,s 就会为 null,而 i 就会为零(因为在创建对象的第一步中将对象的存储空间清理为 0)。如果注释掉跟随于"You must do this”后面的两行代码,然后运行程序,就会发现当对象被还原后,s 是 null,而 i 是零。 我们如果从一个 Externalizable 对象继承,通常需要调用基类版本的 writeExternal() 和 readExternal() 来为基类组件提供恰当的存储和恢复功能。 @@ -552,7 +552,7 @@ writeObject() 方法必须检查 sc,判断它是否拥有自己的 writeObject ## 使用持久化 -个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象-它们都具有指向第三个对象的引用-进行序列化,会发生什么情况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢? +一个比较诱人的使用序列化技术的想法是:存储程序的一些状态,以便我们随后可以很容易地将程序恢复到当前状态。但是在我们能够这样做之前,必须回答几个问题。如果我们将两个对象-它们都具有指向第三个对象的引用-进行序列化,会发生什么情况?当我们从它们的序列化状态恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢? 下面这个例子说明了上述问题: From 71ff462b5df21cbe49437f4443ee976ae481b371 Mon Sep 17 00:00:00 2001 From: Hongkuan Wang Date: Fri, 9 Oct 2020 10:37:39 +0800 Subject: [PATCH 331/371] fix typo (#602) --- docs/book/Appendix-New-IO.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/Appendix-New-IO.md b/docs/book/Appendix-New-IO.md index 94e47660..2f49f3e5 100644 --- a/docs/book/Appendix-New-IO.md +++ b/docs/book/Appendix-New-IO.md @@ -4,7 +4,7 @@ # 附录:新IO -> Java 新I/O 库是在 1.4 版本引入到 `Java .nio.* package` 中的,旨在更快速。 +> Java 新I/O 库是在 1.4 版本引入到 `java.nio.*` 包中的,旨在更快速。 实际上,新 I/O 使用 **NIO**(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 **NIO** 的种种优点。即使我们不显式地使用 **NIO** 方式来编写代码,也能带来性能和速度的提高。这种提升不仅仅体现在文件读写(File I/O),同时也体现在网络读写(Network I/O)中。例如,网络编程。 @@ -88,7 +88,7 @@ Some text Some more 将字节放入 **ByteBuffer** 的一种方法是直接调用 `put()` 方法将一个或多个字节放入 **ByteBuffer**;当然也可以是其它基本类型的数据。此外,参考上例,我们还可以调用 `wrap()` 方法包装现有字节数组到 **ByteBuffer**。执行此操作时,不会复制底层数组,而是将其用作生成的 **ByteBuffer** 存储。这样产生的 **ByteBuffer** 是数组“支持”的。 -data.txt 文件被 **RandomAccessFile** 重新打开。**注意**,你可以在文件中移动 **FileChanne**。 在这里,它被移动到末尾,以便添加额外的写操作。 +data.txt 文件被 **RandomAccessFile** 重新打开。**注意**,你可以在文件中移动 **FileChannel**。 在这里,它被移动到末尾,以便添加额外的写操作。 对于只读访问,必须使用静态 `allocate()` 方法显式地分配 **ByteBuffer**。**NIO** 的目标是快速移动大量数据,因此 **ByteBuffer** 的大小应该很重要 —— 实际上,这里设置的 1K 都可能偏小了(我们在工作中应该反复测试以找到最佳大小)。 @@ -175,7 +175,7 @@ public class TransferTo { ## 数据转换 -为了将 **GetChannel.java** 文件中的信息打印出来。在 Java 中,我们每次提取一个字节的数据并将其转换为字符。看起来很简单 —— 如果你有看过 `ava.nio.`**CharBuffer** 类,你会发现一个 `toString()` 方法。该方法的作用是“返回一个包含此缓冲区字符的字符串”。 +为了将 **GetChannel.java** 文件中的信息打印出来。在 Java 中,我们每次提取一个字节的数据并将其转换为字符。看起来很简单 —— 如果你有看过 `java.nio.`**CharBuffer** 类,你会发现一个 `toString()` 方法。该方法的作用是“返回一个包含此缓冲区字符的字符串”。 既然 **ByteBuffer** 可以通过 **CharBuffer** 类的 `asCharBuffer()` 方法查看,那我们就来尝试一样。从下面输出语句的第一行可以看出,这并不正确: From 35a32719aa89d20b2b6f197c32678cda13c9e85a Mon Sep 17 00:00:00 2001 From: Hongkuan Wang Date: Fri, 9 Oct 2020 16:11:51 +0800 Subject: [PATCH 332/371] Remove redundant \ (#604) --- docs/book/18-Strings.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/18-Strings.md b/docs/book/18-Strings.md index 151eed10..b75429c5 100755 --- a/docs/book/18-Strings.md +++ b/docs/book/18-Strings.md @@ -801,10 +801,10 @@ the mightiest banana in the forest...with... a banana! | 表达式 | 含义 | | :---- | :---- | | `.` | 任意字符 | -| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a\|b\|c`作用相同)| +| `[abc]` |包含`a`、`b`或`c`的任何字符(和`a|b|c`作用相同)| | `[^abc]` | 除`a`、`b`和`c`之外的任何字符(否定) | | `[a-zA-Z]` | 从`a`到`z`或从`A`到`Z`的任何字符(范围) | -| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a\|b\|c\|h\|i\|j`作用相同)(合并) | +| `[abc[hij]]` | `a`、`b`、`c`、`h`、`i`、`j`中的任意字符(与`a|b|c|h|i|j`作用相同)(合并) | | `[a-z&&[hij]]` | 任意`h`、`i`或`j`(交) | | `\s` | 空白符(空格、tab、换行、换页、回车) | | `\S` | 非空白符(`[^\s]`) | @@ -818,7 +818,7 @@ the mightiest banana in the forest...with... a banana! | 逻辑操作符 | 含义 | | :----: | :---- | | `XY` | `Y`跟在`X`后面 | -| `X\|Y` | `X`或`Y` | +| `X|Y` | `X`或`Y` | | `(X)` | 捕获组(capturing group)。可以在表达式中用`\i`引用第i个捕获组 | 下面是不同的边界匹配符: From 88af6850893d288b02227e35af8c70b2312e9f1a Mon Sep 17 00:00:00 2001 From: DING-Weibing <57584830+DING-Weibing@users.noreply.github.com> Date: Mon, 12 Oct 2020 00:29:36 +0900 Subject: [PATCH 333/371] =?UTF-8?q?"=E5=8F=97=E4=BF=9D=E6=8A=A4"=EF=BC=8C?= =?UTF-8?q?=E6=9C=89=E6=AD=A7=E4=B9=89=EF=BC=8C=E5=B9=B6=E4=B8=94=E4=B8=8D?= =?UTF-8?q?=E6=98=93=E4=BA=8E=E7=90=86=E8=A7=A3=E3=80=82=E5=8E=9F=E6=96=87?= =?UTF-8?q?=E5=AF=B9protected=E4=BD=BF=E7=94=A8=E4=BA=86=E5=8A=A0=E7=B2=97?= =?UTF-8?q?=E5=AD=97=E4=BD=93=EF=BC=8C=E8=A1=A8=E6=98=8E=E8=BF=99=E6=98=AF?= =?UTF-8?q?=E4=B8=AA=E4=B8=80=E4=B8=AA=E5=85=B3=E9=94=AE=E5=AD=97=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E9=9D=9E=E5=AD=97=E9=9D=A2=E6=84=8F=E6=80=9D=E5=8F=97?= =?UTF-8?q?=E4=BF=9D=E6=8A=A4=E3=80=82=20(#605)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * "受保护",有歧义,并且不易于理解。原文对protected使用了加粗字体,表明这是个一个关键字,并非字面意思受保护。 * Markdown语法错误 * Markdown语法错误 * Markdown语法错误 * 没读懂原来的翻译是什么意思。 * 错句 * 没读懂原来的翻译是什么意思,这样改或许更好。 * 语义错误 --- docs/book/08-Reuse.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index 2e37dabf..824d59b9 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -214,7 +214,7 @@ Cleanser dilute() apply() scrub() 在这里,`Detergent.main()` 显式地调用 `Cleanser.main()`,从命令行传递相同的参数(当然,你可以传递任何字符串数组)。 -**Cleanser** 中的所有方法都是公开的。请记住,如果不使用任何访问修饰符,则成员默认为包访问权限,这只允许包内成员访问。因此,如果没有访问修饰符,那么包内的任何人都可以使用这些方法。例如,**Detergent** 就没有问题。但是,如果其他包中的类继承 **Cleanser**,则该类只能访问 **Cleanser** 的公共成员。因此,为了允许继承,一般规则是所有字段为私有,所有方法为公共。(受保护成员也允许派生类访问;你以后会知道的。)在特定的情况下,你必须进行调整,但这是一个有用的指南。 +**Cleanser** 中的所有方法都是公开的。请记住,如果不使用任何访问修饰符,则成员默认为包访问权限,这只允许包内成员访问。因此,如果没有访问修饰符,那么包内的任何人都可以使用这些方法。例如,**Detergent** 就没有问题。但是,如果其他包中的类继承 **Cleanser**,则该类只能访问 **Cleanser** 的公共成员。因此,为了允许继承,一般规则是所有字段为私有,所有方法为公共。(**protected**成员也允许派生类访问;你以后会知道的。)在特定的情况下,你必须进行调整,但这是一个有用的指南。 **Cleanser** 的接口中有一组方法: `append()`、`dilute()`、`apply()`、`scrub()` 和 `toString()`。因为 **Detergent** 是从 **Cleanser** 派生的(通过 **extends** 关键字),所以它会在其接口中自动获取所有这些方法,即使你没有在 **Detergent** 中看到所有这些方法的显式定义。那么,可以把继承看作是复用类。如在 `scrub()` 中所见,可以使用基类中定义的方法并修改它。在这里,你可以在新类中调用基类的该方法。但是在 `scrub()` 内部,不能简单地调用 `scrub()`,因为这会产生递归调用。为了解决这个问题,Java的 **super** 关键字引用了当前类继承的“超类”(基类)。因此表达式 `super.scrub()` 调用方法 `scrub()` 的基类版本。 @@ -359,7 +359,7 @@ DerivedSpaceShip extends SpaceShipControls { ``` -然而, **DerivedSpaceShip** 并不是真正的“一种” **SpaceShipControls** ,即使你“告诉” **DerivedSpaceShip** 调用 `forward()`。更准确地说,一艘宇宙飞船包含了 **SpaceShipControls **,同时 **SpaceShipControls** 中的所有方法都暴露在宇宙飞船中。委托解决了这个难题: +然而, **DerivedSpaceShip** 并不是真正的“一种” **SpaceShipControls** ,即使你“告诉” **DerivedSpaceShip** 调用 `forward()`。更准确地说,一艘宇宙飞船包含了 **SpaceShipControls**,同时 **SpaceShipControls** 中的所有方法都暴露在宇宙飞船中。委托解决了这个难题: ```java // reuse/SpaceShipDelegation.java @@ -507,7 +507,7 @@ PlaceSetting constructor ### 保证适当的清理 -Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在"异常"章节中描述的——你必须通过在 **finally **子句中放置此类清理来防止异常。 +Java 没有 C++ 中析构函数的概念,析构函数是在对象被销毁时自动调用的方法。原因可能是,在Java中,通常是忘掉而不是销毁对象,从而允许垃圾收集器根据需要回收内存。通常这是可以的,但是有时你的类可能在其生命周期中执行一些需要清理的活动。初始化和清理章节提到,你无法知道垃圾收集器何时会被调用,甚至它是否会被调用。因此,如果你想为类清理一些东西,必须显式地编写一个特殊的方法来完成它,并确保客户端程序员知道他们必须调用这个方法。最重要的是——正如在"异常"章节中描述的——你必须通过在 **finally**子句中放置此类清理来防止异常。 请考虑一个在屏幕上绘制图片的计算机辅助设计系统的例子: @@ -692,7 +692,7 @@ doh(Milhouse) **Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。在下一章中你将看到,使用与基类中完全相同的签名和返回类型覆盖相同名称的方法要常见得多。否则就会令人困惑。 -你已经看到了Java 5 **@Override **注释,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法时,你可以选择添加这个注释,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: +你已经看到了Java 5 **@Override**注释,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法时,你可以选择添加这个注释,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: ```java // reuse/Lisa.java @@ -1161,7 +1161,7 @@ public class Jurassic { } ``` -**final** 类的属性可以根据个人选择是或不是 **final**。这同样适用于不管类是否是 **final** 的内部 **final** 属性。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。 +**final** 类的属性可以根据个人选择是或不是 **final**。同样,非 **final** 类的属性也可以根据个人选择是或不是 **final**。然而,由于 **final** 类禁止继承,类中所有的方法都被隐式地指定为 **final**,所以没有办法覆写它们。你可以在 final 类中的方法加上 **final** 修饰符,但不会增加任何意义。 ### final 忠告 @@ -1169,9 +1169,9 @@ public class Jurassic { 但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。 -Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,而且从效率考虑(这近乎是个幻想),如果它的所有方法没有被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 +Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,如果它的所有方法没有因为从效率考虑(这近乎是个幻想),而被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 -第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。 +第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看到同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。 Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 @@ -1242,7 +1242,7 @@ j = 39 如果基类还存在自身的基类,那么第二个基类也将被加载,以此类推。接下来,根基类(例子中根基类是 **Insect**)的 **static** 的初始化开始执行,接着是派生类,以此类推。这点很重要,因为派生类中 **static** 的初始化可能依赖基类成员是否被正确地初始化。 -至此,必要的类都加载完毕,可以创建对象了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。 +至此,必要的类都加载完毕,对象可以被创建了。首先,对象中的所有基本类型变量都被置为默认值,对象引用被设为 **null** —— 这是通过将对象内存设为二进制零值一举生成的。接着会调用基类的构造器。本例中是自动调用的,但是你也可以使用 **super** 调用指定的基类构造器(在 **Beetle** 构造器中的第一步操作)。基类构造器和派生类构造器一样以相同的顺序经历相同的过程。当基类构造器完成后,实例变量按文本顺序初始化。最终,构造器的剩余部分被执行。 From d344d0d6bd73044506251931b40175634d6fcd07 Mon Sep 17 00:00:00 2001 From: salithfish <31979864+salithfish@users.noreply.github.com> Date: Mon, 12 Oct 2020 11:46:02 +0800 Subject: [PATCH 334/371] =?UTF-8?q?=E4=BF=AE=E6=94=B924=E7=AB=A0=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E7=BC=96=E7=A8=8B=E4=BB=A3=E7=A0=81=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#608)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/24-Concurrent-Programming.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index e8ed661e..201b9a3d 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -290,7 +290,7 @@ Java 8 CompletableFuture 是一个更好的解决方案:它允许你将操作 ## 并行流 -Java 8 流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自仔细的库设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为 Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用 Streams 编写的,那么并行化以提高速度似乎是一种琐事 +Java 8 流的一个显著优点是,在某些情况下,它们可以很容易地并行化。这来自库的仔细设计,特别是流使用内部迭代的方式 - 也就是说,它们控制着自己的迭代器。特别是,他们使用一种特殊的迭代器,称为 Spliterator,它被限制为易于自动分割。我们只需要念 `.parallel()` 就会产生魔法般的结果,流中的所有内容都作为一组并行任务运行。如果你的代码是使用 Streams 编写的,那么并行化以提高速度似乎是一种琐事 例如,考虑来自 Streams 的 Prime.java。查找质数可能是一个耗时的过程,我们可以看到该程序的计时: @@ -615,7 +615,7 @@ public class ParallelStreamPuzzle { } } public static void main(String[] args) { - List x = Stream.generate(newIntGenerator()) + List x = Stream.generate(new IntGenerator()) .limit(10) .parallel() // [1] .collect(Collectors.toList()); From 643b2a8d2a40baa16935a8379bebeb119212a309 Mon Sep 17 00:00:00 2001 From: Hao Lin <1184264181@qq.com> Date: Tue, 20 Oct 2020 11:35:48 +0800 Subject: [PATCH 335/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86finaly?= =?UTF-8?q?=E7=9A=84=E9=94=99=E5=88=AB=E5=AD=97=20(#609)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/15-Exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/15-Exceptions.md b/docs/book/15-Exceptions.md index 3d2895fb..ecb6ff8f 100644 --- a/docs/book/15-Exceptions.md +++ b/docs/book/15-Exceptions.md @@ -1393,7 +1393,7 @@ StormyInning.walk() 不能通过编译是因为它抛出了一个 Inning.walk() 有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗?"尽管大多数情况下是非常安全的,但涉及构造器时,问题就出现了。构造器会把对象设置成安全的初始状态,但还会有别的动作,比如打开一个文件,这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常,这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。 -你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finaly 子句中却是要被清理的。 +你也许会认为使用 finally 就可以解决问题。但问题并非如此简单,因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废,也许该对象的某些部分还没有被成功创建,而这些部分在 finally 子句中却是要被清理的。 在下面的例子中,建立了一个 InputFile 类,它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类(将在 [附录:I/O 流 ](./Appendix-IO-Streams.md) 中讨论),这些类的基本用法很简单,你应该很容易明白: From f4586b506004319bc5dded191e4b86c2a4504fd9 Mon Sep 17 00:00:00 2001 From: sleepingraven <32897701+sleepingraven@users.noreply.github.com> Date: Tue, 27 Oct 2020 10:55:18 +0800 Subject: [PATCH 336/371] =?UTF-8?q?=E6=95=99=E5=AD=A6=E7=9B=AE=E6=A0=87=20?= =?UTF-8?q?(LingCoder#496)=20(#612)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 半角括号、删除多余空格 (#494) * 前言 (#496) * 上传旧文档 * Revert "上传旧文档" This reverts commit f053e7d36126ce530def85a61d4ab09ea0bc5210. * 新文档 --- docs/book/00-Preface.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/book/00-Preface.md b/docs/book/00-Preface.md index 0e2ccbd0..94523a82 100644 --- a/docs/book/00-Preface.md +++ b/docs/book/00-Preface.md @@ -11,15 +11,15 @@ ## 教学目标 -每章讲授一个或一组相关的概念,并且这些知识不依赖于尚未学习到的章节。如此,学习者可以在当前知识的背景框架下循序渐进地掌握 Java。 +每章讲授一个或一组相关的概念,并且不依赖于尚未介绍的特性。这样你就可以在现有的知识背景下,在继续向前学习之前,消化吸收每一部分内容。 本书的教学目标: -1. 循序渐进地呈现学习内容,以便于你在不依赖后置知识框架的情况下轻松完成现有的学习任务,同时尽量保证前面章节的内容在后面的学习中得到运用。如果确有必要引入我们还没学习到的知识概念,我会做个简短地介绍。 +1. 循序渐进地呈现学习内容,使读者可以很容易地将每个知识点融会贯通;同时仔细地对特性的讲解进行排序,以使得你在看到对某个特性的运用之前,会先了解它。如果确有必要引入还未学习的知识概念,我会做个简短的介绍。 -2. 尽可能地使用简单和简短的示例,方便读者理解。而不强求引入解决实际问题的例子。因为我发现,相比解决某个实际问题,读者更乐于看到自己真正理解了示例的每个细节。或许我会因为这些“玩具示例”而被一些人所诟病,但我更愿意相信这样的教学方式更加有效。 +2. 使用尽可能简单和简短的示例,方便读者理解。而不强求引入解决“现实世界”中问题的例子。因为我发现,通常初学者更乐于看到自己通晓示例的每个细节,而非明晰所解决的问题范畴。或许我会因这些“玩具示例”而被一些人所诟病,但是我乐意接受那些有利于为教育带来益处的种种事物,更希望读者们能因此保持饶有兴趣地学习。 -3. 把我知道以及我认为对于你学习语言很重要的东西都告诉你。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。 +3. 向读者提供“我认为对理解这种程序设计语言来说很重要”的部分,而不是提供我所知道的所有事情。我认为信息的重要性是分层次结构的。绝大多数情况下,我们没必要弄清问题的所有本质。好比编程语言中的某些特性和实现细节,95% 的程序员都不需要去知道。这些细节除了会加重你的学习成本,还让你更觉得这门语言好复杂。如果你非要考虑这些细节,那么它还会迷惑该代码的阅读者/维护者,所以我主张选择简单的方法解决问题。 4. 希望本书能为你打下坚实的基础,方便你将来学习更难的课程和书籍。 From 4d21726c4a93e104b410d06668c73975ba4069b5 Mon Sep 17 00:00:00 2001 From: Hao Lin <1184264181@qq.com> Date: Wed, 4 Nov 2020 12:08:43 +0800 Subject: [PATCH 337/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E9=94=99?= =?UTF-8?q?=E5=88=AB=E5=AD=97=20(#614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复了finaly的错别字 * 修复Mao的错别字,实际应为Map * 修复‘既’的错别字 --- docs/book/20-Generics.md | 2 +- docs/book/Appendix-Collection-Topics.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 43bb33ba..72263fcc 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -3537,7 +3537,7 @@ NeedCasting.java:10: warning: [unchecked] unchecked cast 1 warning ``` -你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,既通过泛型类来转型: +你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用 Java 5 引入的新的转型形式,即通过泛型类来转型: ```java // generics/ClassCasting.java diff --git a/docs/book/Appendix-Collection-Topics.md b/docs/book/Appendix-Collection-Topics.md index 511de489..083462a7 100644 --- a/docs/book/Appendix-Collection-Topics.md +++ b/docs/book/Appendix-Collection-Topics.md @@ -2324,7 +2324,7 @@ dancing | **LinkedHashMap** | 与 **HashMap** 类似,但是当遍历时,可以按插入顺序或最近最少使用(LRU)顺序获取键值对。只比 **HashMap** 略慢,一个例外是在迭代时,由于其使用链表维护内部顺序,所以会更快些。 | | **TreeMap** | 基于红黑树的实现。当查看键或键值对时,它们按排序顺序(由 **Comparable** 或 **Comparator** 确定)。 **TreeMap** 的侧重点是按排序顺序获得结果。 **TreeMap** 是唯一使用 `subMap()` 方法的 **Map** ,它返回红黑树的一部分。 | | **WeakHashMap** | 一种具有 *弱键*(weak keys) 的 **Map** ,为了解决某些类型的问题,它允许释放 **Map** 所引用的对象。如果在 **Map** 外没有对特定键的引用,则可以对该键进行垃圾回收。 | -| **ConcurrentHashMap** | 不使用同步锁定的线程安全 **Mao** 。这在[第二十四章 并发编程]() 一章中讨论。 | +| **ConcurrentHashMap** | 不使用同步锁定的线程安全 **Map** 。这在[第二十四章 并发编程]() 一章中讨论。 | | **IdentityHashMap** | 使用 `==` 而不是 `equals()` 来比较键。仅用于解决特殊问题,不适用于一般用途。 | 散列是在 **Map** 中存储元素的最常用方法。 From 0a4365eed12605e63679d960bea38b61133f1728 Mon Sep 17 00:00:00 2001 From: Hao Lin <1184264181@qq.com> Date: Fri, 13 Nov 2020 15:03:06 +0800 Subject: [PATCH 338/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E4=B8=AD=E5=88=86=E8=A1=8C=E6=97=B6=E7=9A=84=E6=AF=8F=E8=A1=8C?= =?UTF-8?q?=E5=88=86=E5=8F=B7=E6=97=B6=E6=9C=89=E6=97=B6=E6=97=A0=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=20(#617)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复了finaly的错别字 * 修复Mao的错别字,实际应为Map * 修复‘既’的错别字 * 统一风格 --- docs/book/23-Annotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/23-Annotations.md b/docs/book/23-Annotations.md index 1b585691..af5fb8dc 100644 --- a/docs/book/23-Annotations.md +++ b/docs/book/23-Annotations.md @@ -117,8 +117,8 @@ Java 语言中目前有 5 种标准注解(前面介绍过),以及 5 种元 | 注解 | 解释 | | ----------- | ------------------------------------------------------------ | -| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明
**FIELD**:字段声明(包括 enum 实例)
**LOCAL_VARIABLE**:局部变量声明
**METHOD**:方法声明
**PACKAGE**:包声明
**PARAMETER**:参数声明
**TYPE**:类、接口(包括注解类型)或者 enum 声明 | -| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃。
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 | +| @Target | 表示注解可以用于哪些地方。可能的 **ElementType** 参数包括:
**CONSTRUCTOR**:构造器的声明;
**FIELD**:字段声明(包括 enum 实例);
**LOCAL_VARIABLE**:局部变量声明;
**METHOD**:方法声明;
**PACKAGE**:包声明;
**PARAMETER**:参数声明;
**TYPE**:类、接口(包括注解类型)或者 enum 声明。 | +| @Retention | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括:
**SOURCE**:注解将被编译器丢弃;
**CLASS**:注解在 class 文件中可用,但是会被 VM 丢弃;
**RUNTIME**:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息。 | | @Documented | 将此注解保存在 Javadoc 中 | | @Inherited | 允许子类继承父类的注解 | | @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 | From ad9de0d3871852b0466c427201258cb1411e9f69 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Fri, 20 Nov 2020 10:57:22 +0800 Subject: [PATCH 339/371] Update README.md --- docs/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 328b1fe4..fca2f067 100644 --- a/docs/README.md +++ b/docs/README.md @@ -38,10 +38,6 @@ 本项目基于 MIT 协议开源。 -## 友情链接 - -Effective Java 第 3 版: https://github.com/sjsdfg/effective-java-3rd-chinese - ## 联系方式 - E-mail : From 11a6ac3969c265a8648588da62da1a4bf0c462c3 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Tue, 24 Nov 2020 18:30:16 +0800 Subject: [PATCH 340/371] =?UTF-8?q?=E7=BA=A0=E9=94=99=20(#618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? --- docs/book/06-Housekeeping.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 0a7a5f1c..0248808c 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1379,7 +1379,7 @@ public class ArrayInit { 在这两种形式中,初始化列表的最后一个逗号是可选的(这一特性使维护长列表变得更容易)。 -尽管第一种形式很有用,但是它更加受限,因为它只能用于数组定义处。第二种和第三种形式可以用在任何地方,甚至用在方法的内部。例如,你创建了一个 **String** 数组,将其传递给另一个类的 `main()` 方法,如下: +尽管第一种形式很有用,但是它更加受限,因为它只能用于数组定义处。第二种形式可以用在任何地方,甚至用在方法的内部。例如,你创建了一个 **String** 数组,将其传递给另一个类的 `main()` 方法,如下: ```java // housekeeping/DynamicArray.java From 2dfca3c1da67af80179a7a64a4f848ee8461a4c5 Mon Sep 17 00:00:00 2001 From: witcxc Date: Wed, 25 Nov 2020 11:08:21 +0800 Subject: [PATCH 341/371] =?UTF-8?q?=E5=B0=86=E9=98=9F=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E8=BF=B0=E5=BE=97=E6=9B=B4=E6=B8=85=E6=99=B0=20(#619)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: happyleaf.cx --- docs/book/16-Validating-Your-Code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/16-Validating-Your-Code.md b/docs/book/16-Validating-Your-Code.md index 940ba5a2..26917eab 100644 --- a/docs/book/16-Validating-Your-Code.md +++ b/docs/book/16-Validating-Your-Code.md @@ -555,7 +555,7 @@ assert invariant(); } ``` -**in** 计数器指示数组中下一个对象所在的位置。**out** 计数器指示下一个对象来自何处。**wrapped** 的flag表示 **in** 已经“绕着圆圈”走了,现在从后面出来了。当**in**和 **out** 重合时,队列为空(如果包装为 **false** )或满(如果 **wrapped** 为 **true** )。 +**in** 计数器指示数组中下一个入队对象所在的位置。**out** 计数器指示下一个出队对象来自何处。**wrapped** 的flag表示入队和出队指针顺序是否变换, 为**false** 表示**in**在**out**之前,为**true**则顺序相反。当**in**和 **out** 重合时,队列为空(如果**wrapped**为 **false** )或满(如果 **wrapped** 为 **true** )。 **put()** 和 **get()** 方法调用 **precondition()** ,**postcondition()**, 和 **invariant**(),这些都是在类中定义的私有方法。前置**precondition()** 和 **postcondition()** 是用来阐明代码的辅助方法。 From 9dacdce2b9edfe58b1b149b02e7adac2b2d8cb7d Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Thu, 26 Nov 2020 14:19:35 +0800 Subject: [PATCH 342/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#622)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 --- docs/book/08-Reuse.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index 824d59b9..729d4ca3 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -68,7 +68,7 @@ i = 0 f = 0.0 source = Constructed ``` -这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注释来告诉编译器,以确保正确地覆盖。**@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。 +这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注解来告诉编译器,以确保正确地覆盖。**@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。 编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。初始化引用有四种方法: @@ -690,9 +690,10 @@ doh(Milhouse) ```` -**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。在下一章中你将看到,使用与基类中完全相同的签名和返回类型覆盖相同名称的方法要常见得多。否则就会令人困惑。 +**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。正如你将在下一章中看到的那样,比起重载,更常见的是覆盖同名方法,使用与基类中完全相同的方法签名[^1]和返回类型。否则会让人感到困惑。 + +你已经看到了Java 5 **@Override**注解,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法[^2]时,你可以选择添加这个注解,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: -你已经看到了Java 5 **@Override**注释,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法时,你可以选择添加这个注释,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: ```java // reuse/Lisa.java @@ -709,8 +710,14 @@ class Lisa extends Homer { ``` -**{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:方法不会覆盖超类中的方法, **@Override** 注释防止你意外地重载。 +**{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:method does not override a method from its superclass.方法不会重写超类中的方法, **@Override** 注解能防止你意外地重载。 + +- **[1]** 方法签名——方法名和参数类型的合称 + +- **[2]** 重写——覆盖同名方法,使用与基类中完全相同的方法签名和返回类型[^3] +- **[3]** 在java 1.4版本以前,重写方法的返回值类型被要求必须与被重写方法一致,但是在java 5.0中放宽了这一个限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类。 + ## 组合与继承的选择 From df95e50ae7668e79ece0a9be4e9d661841b679e8 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Thu, 26 Nov 2020 21:09:37 +0800 Subject: [PATCH 343/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#623)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 --- docs/book/08-Reuse.md | 4 +++- docs/book/10-Interfaces.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index 729d4ca3..e10f8751 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -1176,12 +1176,14 @@ public class Jurassic { 但请留意你的假设。通常来说,预见一个类如何被复用是很困难的,特别是通用类。如果将一个方法指定为 **final**,可能会防止其他程序员的项目中通过继承来复用你的类,而这仅仅是因为你没有想到它被以那种方式使用。 -Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,如果它的所有方法没有因为从效率考虑(这近乎是个幻想),而被指定为 **final**,可能会更加有用。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 +Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vector** 类被广泛地使用,然而它的所有方法出于"效率"考虑(然而并没有提升效率,只是幻觉)全被指定为 **final** ,如果不指定 **final** 的话,可能会更加有用[^1]。很容易想到,你可能会继承并覆写这么一个基础类,但是设计者们认为这么做不合适。有两个讽刺的原因。第一,**Stack** 继承自 **Vector**,就是说 **Stack** 是个 **Vector**,但从逻辑上来说不对。尽管如此,Java 设计者们仍然这么做,在用这种方式创建 **Stack** 时,他们应该意识到了 **final** 方法过于约束。 第二,**Vector** 中的很多重要方法,比如 `addElement()` 和 `elementAt()` 方法都是同步的。在“并发编程”一章中会看到同步会导致很大的执行开销,可能会抹煞 **final** 带来的好处。这加强了程序员永远无法正确猜到优化应该发生在何处的观点。如此笨拙的设计却出现在每个人都要使用的标准库中,太糟糕了。庆幸的是,现代 Java 容器用 **ArrayList** 代替了 **Vector**,它的行为要合理得多。不幸的是,仍然有很多新代码使用旧的集合类库,其中就包括 **Vector**。 Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 +- **[1]** Java 1.4 开始已将 **Vector** 类大多数方法的 **final** 去掉 + ## 类初始化和加载 diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index f2bc527e..7eb5cf49 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -104,7 +104,7 @@ public class Instantiable extends Uninstantiable { } ``` -留意 `@Override` 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 `@Override` 是多余的。但是,`@Override` 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 `@Override`,即使在没有这个注解,编译器告诉我错误的时候。 +留意 `@Override` 的使用。没有这个注解的话,如果你没有定义相同的方法名或签名,抽象机制会认为你没有实现抽象方法从而产生编译时错误。因此,你可能认为这里的 `@Override` 是多余的。但是,`@Override` 还提示了这个方法被覆写——我认为这是有用的,所以我会使用 `@Override`,不仅仅是因为当没有这个注解时,编译器会告诉我出错。 记住,事实上的访问权限是“friendly”。你很快会看到接口自动将其方法指明为 **public**。事实上,接口只允许 **public** 方法,如果不加访问修饰符的话,接口的方法不是 **friendly** 而是 **public**。然而,抽象类允许每件事: From a9bda4acfd44456bccbe938166688caa9be21a0b Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Fri, 27 Nov 2020 22:46:08 +0800 Subject: [PATCH 344/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#624)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 * 修改语句拗口问题 6 * 修改语句拗口问题 7 * 修改语句拗口问题 8 * 修改语句拗口问题 9 --- docs/book/09-Polymorphism.md | 4 ++-- docs/book/10-Interfaces.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index 63075630..ddca482d 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -964,7 +964,7 @@ Disposing Shared 0 **static long counter** 跟踪所创建的 **Shared** 实例数量,还提供了 **id** 的值。**counter** 的类型是 **long** 而不是 **int**,以防溢出(这只是个良好实践,对于本书的所有示例,**counter** 不会溢出)。**id** 是 **final** 的,因为它的值在初始化时确定后不应该变化。 -在将一个 **shared** 对象附着在类上时,必须记住调用 `addRef()`,而 `dispose()` 方法会跟踪引用数,以确定在何时真正地执行清理工作。使用这种技巧需要加倍细心,但是如果正在共享需要被清理的对象,就没有太多选择了。 +在将一个 **shared** 对象附着在类上时,必须记住调用 `addRef()`,而 `dispose()` 方法会跟踪引用数,以确定在何时真正地执行清理工作。使用这种技巧需要加倍细心,但是如果需要清理正在共享的对象,你没有太多选择。 ### 构造器内部多态方法的行为 @@ -1154,7 +1154,7 @@ HappyActor SadActor ``` -**Stage** 对象中包含了 **Actor** 引用,该引用被初始化为指向一个 **HappyActor** 对象,这意味着 `performPlay()` 会产生一个特殊行为。但是既然引用可以在运行时与其他不同的对象绑定,那么它就可以被替换成对 **SadActor** 的引用,`performPlay()` 的行为随之改变。这样你就获得了运行时的动态灵活性(这被称为状态模式)。与之相反,我们不能在运行时决定继承不同的对象,那在编译时就完全确定下来了。 +**Stage** 对象中包含了 **Actor** 引用,该引用被初始化为指向一个 **HappyActor** 对象,这意味着 `performPlay()` 会产生一个特殊行为。但是既然引用可以在运行时与其他不同的对象绑定,那么它就可以被替换成对 **SadActor** 的引用,`performPlay()` 的行为随之改变。这样你就获得了运行时的动态灵活性(这被称为状态模式)。与之相反,我们无法在运行时才决定继承不同的对象;那在编译时就完全决定好了。 有一条通用准则:使用继承表达行为的差异,使用属性表达状态的变化。在上个例子中,两者都用到了。通过继承得到的两个不同类在 `act()` 方法中表达了不同的行为,**Stage** 通过组合使自己的状态发生变化。这里状态的改变产生了行为的改变。 diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index 7eb5cf49..b7cf5cb5 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -602,7 +602,7 @@ public interface Operations { } ``` -这是模版方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们: +这是*模版*方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们: ```java // interface/Machine.java @@ -768,17 +768,17 @@ Woodwind.play() MIDDLE_C | 状态 | 不能包含属性(除了静态属性,不支持对象状态) | 可以包含属性,非抽象方法可能引用这些属性 | | 默认方法 和 抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 | | 构造器 | 没有构造器 | 可以有构造器 | -| 可见性 | 隐式 **public** | 可以是 **protected** 或友元 | +| 可见性 | 隐式 **public** | 可以是 **protected** 或 "friendly" | 抽象类仍然是一个类,在创建新类时只能继承它一个。而创建类的过程中可以实现多个接口。 -有一条实际经验:尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。 +有一条实际经验:在合理的范围内尽可能地抽象。因此,更倾向使用接口而不是抽象类。只有当必要时才使用抽象类。除非必须使用,否则不要用接口和抽象类。大多数时候,普通类已经做得很好,如果不行的话,再移动到接口或抽象类中。 ## 完全解耦 -当方法操纵的是一个类而非接口时,它就只能作用于那个类或其子类。如果想把方法应用于那个继承层级结构之外的类,就会触霉头。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。 +每当一个方法与一个类而不是接口一起工作时(当方法的参数是类而不是接口),你只能应用那个类或它的子类。如果你想把这方法应用到一个继承层次之外的类,是做不到的。接口在很大程度上放宽了这个限制,因而使用接口可以编写复用性更好的代码。 例如有一个类 **Processor** 有两个方法 `name()` 和 `process()`。`process()` 方法接受输入,修改并输出。把这个类作为基类用来创建各种不同类型的 **Processor**。下例中,**Processor** 的各个子类修改 String 对象(注意,返回类型可能是协变类型而非参数类型): From 3708ab32f5c9b985a00e44c4f5e0c2518f98d2b5 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Tue, 1 Dec 2020 12:55:39 +0800 Subject: [PATCH 345/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#625)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 * 修改语句拗口问题 6 * 修改语句拗口问题 7 * 修改语句拗口问题 8 * 修改语句拗口问题 9 * 拗口问题 * 修改语句拗口问题 10 * 修改语句拗口问题 11 --- docs/book/10-Interfaces.md | 4 +++- docs/book/11-Inner-Classes.md | 41 ++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/book/10-Interfaces.md b/docs/book/10-Interfaces.md index b7cf5cb5..f2257f52 100644 --- a/docs/book/10-Interfaces.md +++ b/docs/book/10-Interfaces.md @@ -602,7 +602,9 @@ public interface Operations { } ``` -这是*模版*方法设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模版方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们: + +这是*模板方法*设计模式的一个版本(在“设计模式”一章中详细描述),`runOps()` 是一个模板方法。`runOps()` 使用可变参数列表,因而我们可以传入任意多的 **Operation** 参数并按顺序运行它们: + ```java // interface/Machine.java diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index ea329e45..83376eb8 100755 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -8,7 +8,7 @@ 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外部类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样(而且 Java 8 的 Lambda 表达式和方法引用减少了编写内部类的需求)。 -最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,"Why inner classes?"就应该使得内部类的益处明确显现了。 +最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法与语义之后,就能明白使用内部类的好处了。 本章剩余部分包含了对内部类语法更加详尽的探索,这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始不需要。因此,本章最初的部分也许就是你现在所需的全部,你可以将更详尽的探索当作参考资料。 @@ -170,7 +170,7 @@ public class Sequence { 0 1 2 3 4 5 6 7 8 9 ``` -**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“迭代器”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。 +**Sequence** 类只是一个固定大小的 **Object** 的数组,以类的形式包装了起来。可以调用 `add()` 在序列末尾增加新的 **Object**(只要还有空间),要获取 **Sequence** 中的每一个对象,可以使用 **Selector** 接口。这是“*迭代器*”设计模式的一个例子,在本书稍后的部分将更多地学习它。**Selector** 允许你检查序列是否到末尾了(`end()`),访问当前对象(`current()`),以及移到序列中的下一个对象(`next()`)。因为 **Selector** 是一个接口,所以别的类可以按它们自己的方式来实现这个接口,并且其他方法能以此接口为参数,来生成更加通用的代码。 这里,**SequenceSelector** 是提供 **Selector** 功能的 **private** 类。可以看到,在 `main()` 中创建了一个 **Sequence**,并向其中添加了一些 **String** 对象。然后通过调用 `selector()` 获取一个 **Selector**,并用它在 **Sequence** 中移动和选择每一个元素。 最初看到 **SequenceSelector**,可能会觉得它只不过是另一个内部类罢了。但请仔细观察它,注意方法 `end()`,`current()` 和 `next()` 都用到了 **items**,这是一个引用,它并不是 **SequenceSelector** 的一部分,而是外部类中的一个 **private** 字段。然而内部类可以访问其外部类的方法和字段,就像自己拥有它们似的,这带来了很大的方便,就如前面的例子所示。 @@ -325,7 +325,7 @@ public class TestParcel { ## 内部类方法和作用域 -到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。 +到目前为止,读者所看到的只是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么它们都是“平凡的”内部类,简单并且容易理解。然而,内部类的语法重写了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者在任意的作用域内定义内部类。 这么做有两个理由: @@ -513,7 +513,7 @@ public class Parcel9 { } ``` -如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是 **final** 的(也就是说,它在初始化后不会改变,所以可以被当作 **final**),就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但是通常最好加上 **final** 作为一种暗示。 +如果在定义一个匿名内部类时,它要使用一个外部环境(在本匿名内部类之外定义)对象,那么编译器会要求其(该对象)参数引用是 **final** 或者是 “effectively final”(也就是说,该参数在初始化后不能被重新赋值,所以可以当作 **final**)的,就像你在 `destination()` 的参数中看到的那样。这里省略掉 **final** 也没问题,但通常加上 **final** 作为提醒比较好。 如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样: @@ -552,7 +552,7 @@ Inside instance initializer In anonymous f() ``` -在此例中,不要求变量一定是 **final** 的。因为被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。 +在此例中,不要求变量 **i** 一定是 **final** 的。因为 **i** 被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用。 下例是带实例初始化的"parcel"形式。注意 `destination()` 的参数必须是 **final** 的,因为它们是在匿名类内部使用的(译者注:即使不加 **final**, Java 8 的编译器也会为我们自动加上 **final**,以保证数据的一致性)。 @@ -591,15 +591,15 @@ Over budget! 在实例初始化操作的内部,可以看到有一段代码,它们不能作为字段初始化动作的一部分来执行(就是 **if** 语句)。所以对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。 -匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。 +匿名内部类与正规的继承相比有些受限,因为匿名内部类要么继承类,要么实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。 ## 嵌套类 -如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为嵌套类。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: +如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 **static**,这通常称为*嵌套类*。想要理解 **static** 应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 **static** 的时,就不是这样了。嵌套类意味着: -1. 要创建嵌套类的对象,并不需要其外部类的对象。 +1. 创建嵌套类的对象时,不需要其外部类的对象。 2. 不能从嵌套类的对象中访问非静态的外部类对象。 嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 **static** 数据和 **static** 字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西: @@ -910,7 +910,7 @@ Other operation 3 ``` -这个例子进一步展示了外部类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而覆盖 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外部类的接口中添加东西,也没有修改外部类的接口。 +这个例子进一步展示了外部类实现一个接口与内部类实现此接口之间的区别。就代码而言,**Callee1** 是更简单的解决方式。**Callee2** 继承自 **MyIncrement**,后者已经有了一个不同的 `increment()` 方法,并且与 **Incrementable** 接口期望的 `increment()` 方法完全不相关。所以如果 **Callee2** 继承了 **MyIncrement**,就不能为了 **Incrementable** 的用途而重写 `increment()` 方法,于是只能使用内部类独立地实现 **Incrementable**,还要注意,当创建了一个内部类时,并没有在外部类的接口中添加东西,也没有修改外部类的接口。 注意,在 **Callee2** 中除了 `getCallbackReference()` 以外,其他成员都是 **private** 的。要想建立与外部世界的任何连接,接口 **Incrementable** 都是必需的。在这里可以看到,**interface** 是如何允许接口与接口的实现完全独立的。 内部类 **Closure** 实现了 **Incrementable**,以提供一个返回 **Callee2** 的“钩子”(hook)-而且是一个安全的钩子。无论谁获得此 **Incrementable** 的引用,都只能调用 `increment()`,除此之外没有其他功能(不像指针那样,允许你做很多事情)。 @@ -923,13 +923,13 @@ Other operation 在将要介绍的控制框架(control framework)中,可以看到更多使用内部类的具体例子。 -应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案,以解决你的特定问题。这是设计模式中模板方法的一个例子,模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可覆盖的方法就是变化的事物。 +应用程序框架(application framework)就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并重写某些方法。你在重写的方法中写的代码定制了该应用程序框架提供的通用解决方案,来解决你的具体问题。这是设计模式中*模板方法*的一个例子,模板方法包含算法的基本结构,而且会调用一个或多个可重写的方法来完成该算法的运算。设计模式总是将变化的事物与保持不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可重写的方法就是变化的事物。 控制框架是一类特殊的应用程序框架,它用来解决响应事件的需求。主要用来响应事件的系统被称作*事件驱动*系统。应用程序设计中常见的问题之一是图形用户接口(GUI),它几乎完全是事件驱动的系统。 -要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。接下来的问题就是,对于要控制什么,控制框架并不包含任何具体的信息。那些信息是在实现算法的 `action()` 部分时,通过继承来提供的。 +要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪`ready()`”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。下面是一个控制框架,它不包含具体的控制信息。那些信息是通过继承(当算法的 `action()` 部分被实现时)来提供的。 -首先,接口描述了要控制的事件。因为其默认的行为是基于时间去执行控制,所以使用抽象类代替实际的接口。下面的例子包含了某些实现: +这里是描述了所有控制事件的接口。之所以用抽象类代替了真正的接口,是因为默认行为都是根据时间来执行控制的。也因此包含了一些具体实现: ```java // innerclasses/controller/Event.java @@ -955,7 +955,7 @@ public abstract class Event { 当希望运行 **Event** 并随后调用 `start()` 时,那么构造器就会捕获(从对象创建的时刻开始的)时间,此时间是这样得来的:`start()` 获取当前时间,然后加上一个延迟时间,这样生成触发事件的时间。`start()` 是一个独立的方法,而没有包含在构造器内,因为这样就可以在事件运行以后重新启动计时器,也就是能够重复使用 **Event** 对象。例如,如果想要重复一个事件,只需简单地在 `action()` 中调用 `start()` 方法。 -`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中覆盖 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。 +`ready()` 告诉你何时可以运行 `action()` 方法了。当然,可以在派生类中重写 `ready()` 方法,使得 **Event** 能够基于时间以外的其他因素而触发。 下面的文件包含了一个用来管理并触发事件的实际控制框架。**Event** 对象被保存在 **List**\<**Event**\> 类型(读作“Event 的列表”)的容器对象中,容器会在 [集合 ]() 中详细介绍。目前读者只需要知道 `add()` 方法用来将一个 **Event** 添加到 **List** 的尾端,`size()` 方法用来得到 **List** 中元素的个数,foreach 语法用来连续获取 **List** 中的 **Event**,`remove()` 方法用来从 **List** 中移除指定的 **Event**。 @@ -1150,7 +1150,7 @@ public class GreenhouseControls extends Controller { 一个由 **Event** 对象组成的数组被递交给 **Restart**,该数组要加到控制器上。由于 `Restart()` 也是一个 **Event** 对象,所以同样可以将 **Restart** 对象添加到 `Restart.action()` 中,以使系统能够有规律地重新启动自己。 -下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是命令设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求: +下面的类通过创建一个 **GreenhouseControls** 对象,并添加各种不同的 **Event** 对象来配置该系统,这是 *命令* 设计模式的一个例子—**eventList** 中的每个对象都被封装成对象的请求: ```java // innerclasses/GreenhouseController.java @@ -1248,9 +1248,9 @@ enclosingClassReference.super(); -## 内部类可以被覆盖么? +## 内部类可以被重写么? -如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是“覆盖”内部类就好像它是外部类的一个方法,其实并不起什么作用: +如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被重写吗?这看起来似乎是个很有用的思想,但是“重写”内部类就好像它是外部类的一个方法,其实并不起什么作用: ```java // innerclasses/BigEgg.java @@ -1286,7 +1286,7 @@ New Egg() Egg.Yolk() ``` -默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“覆盖后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。 +默认的无参构造器是编译器自动生成的,这里是调用基类的默认构造器。你可能认为既然创建了 **BigEgg** 的对象,那么所使用的应该是“重写后”的 **Yolk** 版本,但从输出中可以看到实际情况并不是这样的。 这个例子说明,当继承了某个外部类的时候,内部类并没有发生什么特别神奇的变化。这两个内部类是完全独立的两个实体,各自在自己的命名空间内。当然,明确地继承某个内部类也是可以的: @@ -1335,7 +1335,7 @@ BigEgg2.Yolk() BigEgg2.Yolk.f() ``` -现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且覆盖了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,覆盖后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。 +现在 **BigEgg2.Yolk** 通过 **extends Egg2.Yolk** 明确地继承了此内部类,并且重写了其中的方法。`insertYolk()` 方法允许 **BigEgg2** 将它自己的 **Yolk** 对象向上转型为 **Egg2** 中的引用 **y**。所以当 `g()` 调用 `y.f()` 时,重写后的新版的 `f()` 被执行。第二次调用 `Egg2.Yolk()`,结果是 **BigEgg2.Yolk** 的构造器调用了其基类的构造器。可以看到在调用 `g()` 的时候,新版的 `f()` 被调用了。 @@ -1426,14 +1426,15 @@ Anonymous inner 9 ```java Counter.class LocalInnerClass$1.class -LocalInnerClass$LocalCounter.class +LocalInnerClass$1LocalCounter.class LocalInnerClass.class ``` 如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外部类标识符与 **"$"** 的后面。 -虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) +虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况[^1]。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) +- **[1]** 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 ## 本章小结 From a6fb15b05d276e64cca5934282fdac94e457ed18 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Thu, 3 Dec 2020 10:58:22 +0800 Subject: [PATCH 346/371] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=AF=AD=E5=8F=A5?= =?UTF-8?q?=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#626)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 * 修改语句拗口问题 6 * 修改语句拗口问题 7 * 修改语句拗口问题 8 * 修改语句拗口问题 9 * 拗口问题 * 修改语句拗口问题 10 * 修改语句拗口问题 11 * 修改语句拗口问题 12 * 修改语句拗口问题 13 * 修改语句拗口问题 14 * 修改语句拗口问题 14 * 修改语句拗口问题 15 --- docs/book/08-Reuse.md | 22 ++++++++------- docs/book/11-Inner-Classes.md | 2 +- docs/book/12-Collections.md | 52 +++++++++++++++++------------------ 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index e10f8751..cf27c665 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -690,9 +690,9 @@ doh(Milhouse) ```` -**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。正如你将在下一章中看到的那样,比起重载,更常见的是覆盖同名方法,使用与基类中完全相同的方法签名[^1]和返回类型。否则会让人感到困惑。 +**Homer** 的所有重载方法在 **Bart** 中都是可用的,尽管 **Bart** 引入了一种新的重载方法。正如你将在下一章中看到的那样,比起重载,更常见的是覆盖同名方法,使用与基类中完全相同的方法签名[^2]和返回类型。否则会让人感到困惑。 -你已经看到了Java 5 **@Override**注解,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法[^2]时,你可以选择添加这个注解,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: +你已经看到了Java 5 **@Override**注解,它不是关键字,但是可以像使用关键字一样使用它。当你打算重写一个方法[^3]时,你可以选择添加这个注解,如果你不小心用了重载而不是重写,编译器会产生一个错误消息: ```java @@ -712,12 +712,7 @@ class Lisa extends Homer { **{WillNotCompile}** 标记将该文件排除在本书的 **Gradle** 构建之外,但是如果你手工编译它,你将看到:method does not override a method from its superclass.方法不会重写超类中的方法, **@Override** 注解能防止你意外地重载。 -- **[1]** 方法签名——方法名和参数类型的合称 - -- **[2]** 重写——覆盖同名方法,使用与基类中完全相同的方法签名和返回类型[^3] - -- **[3]** 在java 1.4版本以前,重写方法的返回值类型被要求必须与被重写方法一致,但是在java 5.0中放宽了这一个限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类。 - + ## 组合与继承的选择 @@ -1182,8 +1177,6 @@ Java 标准类库就是一个很好的例子。尤其是 Java 1.0/1.1 的 **Vect Java 1.0/1.1 标准类库中另一个重要的类是 **Hashtable**(后来被 **HashMap** 取代),它不含任何 **final** 方法。本书中其他地方也提到,很明显不同的类是由不同的人设计的。**Hashtable** 就比 **Vector** 中的方法名简洁得多,这又是一条证据。对于类库的使用者来说,这是一个本不应该如此草率的事情。这种不规则的情况造成用户需要做更多的工作——这是对粗糙的设计和代码的又一讽刺。 -- **[1]** Java 1.4 开始已将 **Vector** 类大多数方法的 **final** 去掉 - ## 类初始化和加载 @@ -1267,6 +1260,15 @@ j = 39 当开始设计一个系统时,记住程序开发是一个增量过程,正如人类学习。它依赖实验,你可以尽可能多做分析,然而在项目开始时仍然无法知道所有的答案。如果把项目视作一个有机的,进化着的生命去培养,而不是视为像摩天大楼一样快速见效,就能获得更多的成功和更迅速的反馈。继承和组合正是可以让你执行如此实验的面向对象编程中最基本的两个工具。 +[^1]: Java 1.4 开始已将 **Vector** 类大多数方法的 **final** 去掉 + +[^2]: 方法签名——方法名和参数类型的合称 + +[^3]: 重写——覆盖同名方法,使用与基类中完全相同的方法签名和返回类型[^4] + +[^4]: 在java 1.4版本以前,重写方法的返回值类型被要求必须与被重写方法一致,但是在java 5.0中放宽了这一个限制,添加了对协变返回类型的支持,在重写的时候,重写方法的返回值类型可以是被重写方法返回值类型的子类。 + +
diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 83376eb8..caf557e8 100755 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1434,7 +1434,7 @@ LocalInnerClass.class 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况[^1]。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) -- **[1]** 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 +[^1]: 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 ## 本章小结 diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 269fc785..131d1771 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -17,12 +17,12 @@ MyType aReference; 集合还有一些其它特性。例如, **Set** 对于每个值都只保存一个对象, **Map** 是一个关联数组,允许将某些对象与其他对象关联起来。Java集合类都可以自动地调整自己的大小。因此,与数组不同,在编程时,可以将任意数量的对象放置在集合中,而不用关心集合应该有多大。 -尽管在 Java 中没有直接的关键字支持,[^1]但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。 +尽管在 Java 中没有直接的关键字支持[^1],但集合类仍然是可以显著增强编程能力的基本工具。在本章中,将介绍 Java 集合类库的基本知识,并重点介绍一些典型用法。这里将专注于在日常编程中使用的集合。稍后,在[附录:集合主题]()中,还将学习到其余的那些集合和相关功能,以及如何使用它们的更多详细信息。 ## 泛型和类型安全的集合 -使用 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”类型的警告([注解]()章节将介绍更多有关注解的信息): @@ -70,7 +70,7 @@ ndOrangesWithoutGenerics.java:23) 在[泛型]()章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存 **Apple** 对象的 **ArrayList** ,只需要使用 **ArrayList\** 来代替 **ArrayList** 。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。 -通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3]下面还是这个示例,但是使用了泛型: +通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中[^3]。下面还是这个示例,但是使用了泛型: ```java // collections/ApplesAndOrangesWithGenerics.java import java.util.*; @@ -310,7 +310,7 @@ public class PrintingCollections { */ ``` -这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键*和与之关联的*值*。 +这显示了Java集合库中的两个主要类型。它们的区别在于集合中的每个“槽”(slot)保存的元素个数。 **Collection** 类型在每个槽中只能保存一个元素。此类集合包括: **List** ,它以特定的顺序保存一组元素; **Set** ,其中元素不允许重复; **Queue** ,只能在集合一端插入对象,并从另一端移除对象(就本例而言,这只是查看序列的另一种方式,因此并没有显示它)。 **Map** 在每个槽中存放了两个元素,即*键* (key)和与之关联的*值* (value)。 默认的打印行为,使用集合提供的 `toString()` 方法即可生成可读性很好的结果。 **Collection** 打印出的内容用方括号括住,每个元素由逗号分隔。 **Map** 则由大括号括住,每个键和值用等号连接(键在左侧,值在右侧)。 @@ -318,7 +318,7 @@ public class PrintingCollections { **ArrayList** 和 **LinkedList** 都是 **List** 的类型,从输出中可以看出,它们都按插入顺序保存元素。两者之间的区别不仅在于执行某些类型的操作时的性能,而且 **LinkedList** 包含的操作多于 **ArrayList** 。本章后面将对这些内容进行更全面的探讨。 -**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它将按比较结果的升序保存对象)或 **LinkedHashSet** ,它按照被添加的先后顺序保存对象。 +**HashSet** , **TreeSet** 和 **LinkedHashSet** 是 **Set** 的类型。从输出中可以看到, **Set** 仅保存每个相同项中的一个,并且不同的 **Set** 实现存储元素的方式也不同。 **HashSet** 使用相当复杂的方法存储元素,这在[附录:集合主题]()中进行了探讨。现在只需要知道,这种技术是检索元素的最快方法,因此,存储顺序看上去没有什么意义(通常只关心某事物是否是 **Set** 的成员,而存储顺序并不重要)。如果存储顺序很重要,则可以使用 **TreeSet** ,它把对象按照比较规则来排序;还有 **LinkedHashSet** ,它把对象按照被添加的先后顺序来排序。 **Map** (也称为*关联数组*)使用*键*来查找对象,就像一个简单的数据库。所关联的对象称为*值*。 假设有一个 **Map** 将美国州名与它们的首府联系在一起,如果想要俄亥俄州(Ohio)的首府,可以用“Ohio”作为键来查找,几乎就像使用数组下标一样。正是由于这种行为,对于每个键, **Map** 只存储一次。 @@ -328,7 +328,7 @@ public class PrintingCollections { 本例使用了 **Map** 的三种基本风格: **HashMap** , **TreeMap** 和 **LinkedHashMap** 。 -键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 通过比较结果的升序来保存键, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按键的插入顺序保存键。 +键和值保存在 **HashMap** 中的顺序不是插入顺序,因为 **HashMap** 实现使用了非常快速的算法来控制顺序。 **TreeMap** 把所有的键按照比较规则来排序, **LinkedHashMap** 在保持 **HashMap** 查找速度的同时按照键的插入顺序来排序。 @@ -729,7 +729,7 @@ pets.removeLast(): Hamster 堆栈是“后进先出”(LIFO)集合。它有时被称为*叠加栈*(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。 -Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们永远坚持 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法: +Java 1.0 中附带了一个 **Stack** 类,结果设计得很糟糕(为了向后兼容,我们被迫一直忍受 Java 中的旧设计错误)。Java 6 添加了 **ArrayDeque** ,其中包含直接实现堆栈功能的方法: ```java // collections/StackTest.java @@ -861,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** 也使用散列来提高查询速度,但是似乎使用了链表来维护元素的插入顺序。显然,散列算法有改动,以至于现在(上述示例中的HashSet ) **Integer** 是有序的。但是,您不应该依赖此行为(下面例子就没有排序): ```java // collections/SetOfString.java @@ -990,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 @@ -1088,7 +1088,7 @@ true */ ``` -**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度,只需要创建一个值为 **Map** 的 **Map**(这些 **Map** 的值可以是其他集合,甚至是其他 **Map**)。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\>** 即可: +**Map** 与数组和其他的 **Collection** 一样,可以轻松地扩展到多个维度:只需要创建一个 **Map** ,其值也是 **Map** (这些 **Map** 的值可以是其他集合,甚至是其他的 **Map** )。因此,能够很容易地将集合组合起来以快速生成强大的数据结构。例如,假设你正在追踪有多个宠物的人,只需要一个 **Map\>** 即可: ```java @@ -1164,7 +1164,7 @@ Person Luke has: 队列是一个典型的“先进先出”(FIFO)集合。 即从集合的一端放入事物,再从另一端去获取它们,事物放入集合的顺序和被取出的顺序是相同的。队列通常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在[并发编程]()中尤为重要,因为它们可以安全地将对象从一个任务传输到另一个任务。 -**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中与 **Queue** 相关(Queue-specific)的方法: +**LinkedList** 实现了 **Queue** 接口,并且提供了一些方法以支持队列行为,因此 **LinkedList** 可以用作 **Queue** 的一种实现。 通过将 **LinkedList** 向上转换为 **Queue** ,下面的示例使用了在 **Queue** 接口中的 **Queue** 特有(Queue-specific)方法: ```java // collections/QueueDemo.java @@ -1195,18 +1195,18 @@ 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()` 都返回队头元素而不删除它,但如果队列为空,则 `peek()` 返回 **null** , 而 `element()` 抛出 **NoSuchElementException** 。 `poll()` 和 `remove()` 都删除并返回队头元素,但如果队列为空,则 `poll()` 返回 **null** ,而 `remove()` 抛出 **NoSuchElementException** 。 自动包装机制会自动将 `nextInt()` 的 **int** 结果转换为 **queue** 所需的 **Integer** 对象,并将 **char c** 转换为 **qc** 所需的 **Character** 对象。 **Queue** 接口窄化了对 **LinkedList** 方法的访问权限,因此只有适当的方法才能使用,因此能够访问到的 **LinkedList** 的方法会变少(这里实际上可以将 **Queue** 强制转换回 **LinkedList** ,但至少我们不鼓励这样做)。 -与 **Queue** 相关的方法提供了完整而独立的功能。 也就是说,对于 **Queue** 所继承的 **Collection** ,在不需要使用它的任何方法的情况下,就可以拥有一个可用的 **Queue** 。 +**Queue** 的特有方法提供了独立而完整的功能。 换句话说, **Queue** 无需调用继承自 **Collection** 的方法,(只依靠 **Queue** 的特有方法)就有队列的功能。 ### 优先级队列PriorityQueue -先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出声明的是下一个弹出的元素应该是等待时间最长的元素。 +先进先出(FIFO)描述了最典型的*队列规则*(queuing discipline)。队列规则是指在给定队列中的一组元素的情况下,确定下一个弹出队列的元素的规则。先进先出:下一个弹出的元素应该是等待时间最长的那一个。 -优先级队列声明下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,当飞机临近起飞时,这架飞机的乘客可以在办理登机手续时排到队头。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们何时到达。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。 +*优先级队列* :下一个弹出的元素是最需要的元素(具有最高的优先级)。例如,在机场,飞机即将起飞的、尚未登机的乘客可能会被拉出队伍(作最优先的处理)。如果构建了一个消息传递系统,某些消息比其他消息更重要,应该尽快处理,而不管它们到达时间先后。在Java 5 中添加了 **PriorityQueue** ,以便自动实现这种行为。 当在 **PriorityQueue** 上调用 `offer()` 方法来插入一个对象时,该对象会在队列中被排序。[^5]默认的排序使用队列中对象的*自然顺序*(natural order),但是可以通过提供自己的 **Comparator** 来修改这个顺序。 **PriorityQueue** 确保在调用 `peek()` , `poll()` 或 `remove()` 方法时,获得的元素将是队列中优先级最高的元素。 @@ -1274,9 +1274,9 @@ C B A A ## 集合与迭代器 -**Collection** 是所有序列集合共有的根接口。它可能会被认为是一种“附属接口”(incidental interface),即因为要表示其他若干个接口的共性而出现的接口。此外,**java.util.AbstractCollection** 类提供了 **Collection** 的默认实现,使得你可以创建 **AbstractCollection** 的子类型,而其中没有不必要的代码重复。 +**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 @@ -1337,7 +1337,7 @@ Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx} 在本例中,这两种方式都可以奏效。事实上, **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** 会容易些,但是 **AbstractCollection** 还有 `iterator()` 和 `size()`没有实现(抽象方法),而 **AbstractCollection** 中的其它方法会用到它们,因此必须以自己的方式实现这两个方法: ```java // collections/CollectionSequence.java @@ -1383,7 +1383,7 @@ extends AbstractCollection { - **[1]** 你可能会认为,因为 `iterator()` 返回 **Iterator\** ,匿名内部类定义可以使用菱形语法,Java可以推断出类型。但这不起作用,类型推断仍然非常有限。 -这个例子表明,如果实现了 **Collection** ,就必须实现 `iterator()` ,并且只拿实现 `iterator()` 与继承 **AbstractCollection** 相比,花费的代价只有略微减少。但是,如果类已经继承了其他的类,那么就不能再继承 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力要容易得多: +这个例子表明,如果实现了 **Collection** ,就必须也实现 `iterator()` ,而单独只实现 `iterator()` 和继承 **AbstractCollection** 相比,并没有容易多少。但是,如果类已经继承了其他的类,那么就没法再继承 **AbstractCollection** 了。在这种情况下,要实现 **Collection** ,就必须实现该接口中的所有方法。此时,继承并提供创建迭代器的能力(单独只实现 `iterator()`)要容易得多: ```java // collections/NonCollectionSequence.java @@ -1422,12 +1422,12 @@ public class NonCollectionSequence extends PetSequence { */ ``` -生成 **Iterator** 是将序列与消费该序列的方法连接在一起耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。 +生成 **Iterator** 是将序列与消费该序列的方法连接在一起的耦合度最小的方式,并且与实现 **Collection** 相比,它在序列类上所施加的约束也少得多。 ## for-in和迭代器 -到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是一个更通用的证明: +到目前为止,*for-in* 语法主要用于数组,但它也适用于任何 **Collection** 对象。实际上在使用 **ArrayList** 时,已经看到了一些使用它的示例,下面是它的通用性的证明: ```java // collections/ForInCollections.java @@ -1508,7 +1508,7 @@ public class EnvironmentVariables { `System.getenv()` [^7]返回一个 **Map** , `entrySet()` 产生一个由 **Map.Entry** 的元素构成的 **Set** ,并且这个 **Set** 是一个 **Iterable** ,因此它可以用于 *for-in* 循环。 -*for-in* 语句适用于数组或其它任何 **Iterable** ,但这并不意味着数组肯定也是个 **Iterable** ,也不会发生任何自动装箱: +*for-in* 语句适用于数组或者其它任何 **Iterable** ,但这并不代表数组一定是 **Iterable** ,也不会发生任何自动装箱: ```java // collections/ArrayIsNotIterable.java @@ -1684,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** 对象做了任何修改,又不想让原始数组被修改,那么就应该在另一个集合中创建一个副本。 ## 本章小结 @@ -1804,11 +1804,11 @@ Serializable] [^3]: 在[泛型]()章节的末尾,有个关于这个问题是否很严重的讨论。但是,[泛型]()章节还将展示Java泛型远不止是类型安全的集合这么简单。 -[^4]: `remove()` 是一个所谓的“可选”方法(还有一些其它的这种方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。 +[^4]: `remove()` 是一个所谓的“可选”方法(还有其它这样的方法),这意味着并非所有的 **Iterator** 实现都必须实现该方法。这个问题将在[附录:集合主题]()中介绍。但是,标准 Java 库集合实现了 `remove()` ,因此在[附录:集合主题]()章节之前,都不必担心这个问题。 -[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果对象的优先级在它在队列中等待时可以修改,那么算法的选择就显得很重要了。 +[^5]: 这实际上依赖于具体实现。优先级队列算法通常会按插入顺序排序(维护一个*堆*),但它们也可以在删除时选择最重要的元素。 如果一个对象在队列中等待时,它的优先级会发生变化,那么算法的选择就很重要。 -[^6]: 有些人提倡这样一种自动创建机制,即对一个类中所有可能的方法组合都自动创建一个接口,有时候对于单个的类都是如此。 我相信接口的意义不应该仅限于方法组合的机械地复制,因此我在创建接口之前,总是要先看到增加接口带来的价值。 +[^6]: 有些人鼓吹不加思索地创建接口对应一个类甚至每个类中的所有可能的方法组合。 但我相信接口的意义不应该仅限于机械地重复方法组合,因此我倾向于看到增加接口的价值才创建它。 [^7]: 这在 Java 5 之前是不可用的,因为该方法被认为与操作系统的耦合度过紧,因此违反“一次编写,处处运行”的原则。现在却提供它,这一事实表明, Java 的设计者们更加务实了。 From e2c50c2c750c175478074debf19a894a28919587 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Mon, 7 Dec 2020 11:07:49 +0800 Subject: [PATCH 347/371] =?UTF-8?q?=E5=8D=81=E4=B8=89=E7=AB=A0=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=AF=AD=E5=8F=A5=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20=20(#630)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 * 修改语句拗口问题 6 * 修改语句拗口问题 7 * 修改语句拗口问题 8 * 修改语句拗口问题 9 * 拗口问题 * 修改语句拗口问题 10 * 修改语句拗口问题 11 * 修改语句拗口问题 12 * 修改语句拗口问题 13 * 修改语句拗口问题 14 * 修改语句拗口问题 14 * 修改语句拗口问题 15 * 修改语句拗口问题 16 * 修改语句拗口问题 17 * 十三章修改语句拗口问题 --- docs/book/12-Collections.md | 4 +- docs/book/13-Functional-Programming.md | 74 +++++++++++++------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index 131d1771..e34688fa 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -1538,10 +1538,10 @@ public class ArrayIsNotIterable { ### 适配器方法惯用法 -如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并覆盖 `iterator()` 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。 +如果现在有一个 **Iterable** 类,你想要添加一种或多种在 *for-in* 语句中使用这个类的方法,应该怎么做呢?例如,你希望可以选择正向还是反向遍历一个单词列表。如果直接继承这个类,并重写 `iterator()` 方法,则只能替换现有的方法,而不能实现遍历顺序的选择。 一种解决方案是所谓*适配器方法*(Adapter Method)的惯用法。“适配器”部分来自于设计模式,因为必须要提供特定的接口来满足 *for-in* 语句。如果已经有一个接口并且需要另一个接口时,则编写适配器就可以解决这个问题。 -在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用覆盖,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式: +在这里,若希望在默认的正向迭代器的基础上,添加产生反向迭代器的能力,因此不能使用重写,相反,而是添加了一个能够生成 **Iterable** 对象的方法,该对象可以用于 *for-in* 语句。这使得我们可以提供多种使用 *for-in* 语句的方式: ```java // collections/AdapterMethodIdiom.java diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 0f8b5e83..9cef7164 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -127,7 +127,7 @@ Hello there Hello there Lambda 表达式是使用**最小可能**语法编写的函数定义: -1. Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。 +1. Lambda 表达式产生函数,而不是类。 虽然在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是类,但是幕后有各种操作执行让 Lambda 看起来像函数 —— 作为程序员,你可以高兴地假装它们“就是函数”。 2. Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。 @@ -424,7 +424,7 @@ lambda Go::go() ``` -**Thread** 对象将 **Runnable** 作为其构造函数参数,并具有会调用 `run()` 的方法 `start()`。 **注意**,只有**匿名内部类**才需要具有名为 `run()` 的方法。 +**Thread** 对象将 **Runnable** 作为其构造函数参数,并具有会调用 `run()` 的方法 `start()`。 注意这里只有**匿名内部类**才要求显式声明 `run()` 方法。 @@ -469,11 +469,11 @@ X::f() ``` -截止目前,我们看到了与对应接口签名相同的方法引用。 在 **[1]**,我们尝试把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。 +到目前为止,我们已经见过了方法引用和对应接口的签名(参数类型和返回类型)一致的几个赋值例子。 在 **[1]** 中,我们尝试同样的做法,把 `X` 的 `f()` 方法引用赋值给 **MakeString**。结果即使 `make()` 与 `f()` 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 问题在于,这里其实还需要另一个隐藏参数参与:我们的老朋友 `this`。 你不能在没有 `X` 对象的前提下调用 `f()`。 因此,`X :: f` 表示未绑定的方法引用,因为它尚未“绑定”到对象。 -要解决这个问题,我们需要一个 `X` 对象,所以我们的接口实际上需要一个额外的参数,如上例中的 **TransformX**。 如果将 `X :: f` 赋值给 **TransformX**,在 Java 中是允许的。我们必须做第二个心理调整——使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。 +要解决这个问题,我们需要一个 `X` 对象,因此我们的接口实际上需要一个额外的参数,正如在 **TransformX** 中看到的那样。 如果将 `X :: f` 赋值给 **TransformX**,在 Java 中是允许的。我们必须做第二个心理调整——使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。 -**[2]** 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的`transform()`方法,将一个X类的对象传递给它,最后使得 `x.f()` 以某种方式被调用。Java知道它必须拿到第一个参数,该参数实际就是`this`,然后调用方法作用在它之上。 +**[2]** 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的`transform()`方法,将一个X类的对象传递给它,最后使得 `x.f()` 以某种方式被调用。Java知道它必须拿第一个参数,该参数实际就是`this` 对象,然后对此调用方法。 如果你的方法有更多个参数,就以第一个参数接受`this`的模式来处理。 @@ -574,7 +574,7 @@ x -> x.toString() 我们清楚这里返回类型必须是 **String**,但 `x` 是什么类型呢? -Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 `x` 的类型。 +Lambda 表达式包含 *类型推导* (编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 `x` 的类型。 下面是第二个代码示例: @@ -586,7 +586,7 @@ Lambda 表达式包含类型推导(编译器会自动推导出类型信息, 该问题也适用于方法引用。 假设你要传递 `System.out :: println` 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型? -为了解决这个问题,Java 8 引入了 `java.util.function` 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为函数式方法。 +为了解决这个问题,Java 8 引入了 `java.util.function` 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为 *函数式方法* 。 在编写接口时,可以使用 `@FunctionalInterface` 注解强制执行此“函数式方法”模式: @@ -630,11 +630,11 @@ public class FunctionalAnnotation { } ``` -`@FunctionalInterface` 注解是可选的; Java 在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口。 在 `NotFunctional` 的定义中可看到`@FunctionalInterface` 的作用:接口中如果有多个抽象方法则会产生编译期错误。 +`@FunctionalInterface` 注解是可选的; Java 会在 `main()` 中把 **Functional** 和 **FunctionalNoAnn** 都当作函数式接口来看待。 在 `NotFunctional` 的定义中可看出`@FunctionalInterface` 的作用:当接口中抽象方法多于一个时产生编译期错误。 -仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 定义接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中。 +仔细观察在定义 `f` 和 `fna` 时发生了什么。 `Functional` 和 `FunctionalNoAnn` 声明了是接口,然而被赋值的只是方法 `goodbye()`。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中。 -尽管 `FunctionalAnnotation` 确实适合 `Functional` 模型,但 Java不允许我们像`fac`定义中的那样,将 `FunctionalAnnotation` 直接赋值给 `Functional`,因为 `FunctionalAnnotation` 并没有显式地去实现 `Functional` 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。 +虽然 `FunctionalAnnotation` 确实符合 `Functional` 模型,但是 Java不允许我们像`fac`定义的那样,将 `FunctionalAnnotation` 直接赋值给 `Functional`,因为 `FunctionalAnnotation` 并没有显式地去实现 `Functional` 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。 `java.util.function` 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。主要因为基本类型的存在,导致预定义的接口数量有少许增加。 如果你了解命名模式,顾名思义就能知道特定接口的作用。 @@ -646,9 +646,9 @@ public class FunctionalAnnotation { 3. 如果返回值为基本类型,则用 `To` 表示,如 `ToLongFunction ` 和 `IntToLongFunction`。 -4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。 +4. 如果返回值类型与参数类型相同,则是一个 `Operator` :单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`。 -5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。 +5. 如果接收参数并返回一个布尔值,则是一个 **谓词** (`Predicate`)。 6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`。 @@ -661,14 +661,14 @@ public class FunctionalAnnotation { |无参数;
返回类型任意|**Callable**
(java.util.concurrent)
`call()`|**Callable``**| |1 参数;
无返回值|**Consumer**
`accept()`|**`Consumer`
IntConsumer
LongConsumer
DoubleConsumer**| |2 参数 **Consumer**|**BiConsumer**
`accept()`|**`BiConsumer`**| -|2 参数 **Consumer**;
1 引用;
1 基本类型|**Obj类型Consumer**
`accept()`|**`ObjIntConsumer`
`ObjLongConsumer`
`ObjDoubleConsumer`**| +|2 参数 **Consumer**;
第一个参数是 引用;
第二个参数是 基本类型|**Obj类型Consumer**
`accept()`|**`ObjIntConsumer`
`ObjLongConsumer`
`ObjDoubleConsumer`**| |1 参数;
返回类型不同|**Function**
`apply()`
**To类型** 和 **类型To类型**
`applyAs类型()`|**Function``
IntFunction``
`LongFunction`
DoubleFunction``
ToIntFunction``
`ToLongFunction`
`ToDoubleFunction`
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**| |1 参数;
返回类型相同|**UnaryOperator**
`apply()`|**`UnaryOperator`
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator**| -|2 参数类型相同;
返回类型相同|**BinaryOperator**
`apply()`|**`BinaryOperator`
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator**| -|2 参数类型相同;
返回整型|Comparator
(java.util)
`compare()`|**`Comparator`**| +|2 参数,类型相同;
返回类型相同|**BinaryOperator**
`apply()`|**`BinaryOperator`
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator**| +|2 参数,类型相同;
返回整型|Comparator
(java.util)
`compare()`|**`Comparator`**| |2 参数;
返回布尔型|**Predicate**
`test()`|**`Predicate`
`BiPredicate`
IntPredicate
LongPredicate
DoublePredicate**| |参数基本类型;
返回基本类型|**类型To类型Function**
`applyAs类型()`|**IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**| -|2 参数类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**| +|2 参数;
类型不同|**Bi操作**
(不同方法名)|**`BiFunction`
`BiConsumer`
`BiPredicate`
`ToIntBiFunction`
`ToLongBiFunction`
`ToDoubleBiFunction`**| 此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出你所需要的函数式接口。 @@ -677,7 +677,7 @@ public class FunctionalAnnotation { 例如,为什么没有 `IntComparator`,`LongComparator` 和 `DoubleComparator` 呢?有 `BooleanSupplier` 却没有其他表示 **Boolean** 的接口;有通用的 `BiConsumer` 却没有用于 **int**,**long** 和 **double** 的 `BiConsumers` 变体(我理解他们为什么放弃这些接口)。这到底是疏忽还是有人认为其他组合使用得很少呢(他们是如何得出这个结论的)? -你还可以看到基本类型给 Java 添加了多少复杂性。基于效率方面的考虑(问题之后有所缓解),该语言的第一版中就包含了基本类型。现在,在语言的生命周期中,我们仍然会受到语言设计选择不佳的影响。 +你还可以看到基本类型给 Java 添加了多少复杂性。该语言的第一版中就包含了基本类型,原因是考虑效率问题(该问题很快就缓解了)。现在,在语言的生命周期里,我们一直忍受语言设计的糟糕选择所带来的影响。 下面枚举了基于 Lambda 表达式的所有不同 **Function** 变体的示例: @@ -747,9 +747,9 @@ public class FunctionVariants { } ``` -这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。 +这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下有必要进行强制类型转换,否则编译器会报截断错误。 -主方法中的每个测试都显示了 `Function` 接口中不同类型的 `apply()` 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。 +`main()`中的每个测试都显示了 `Function` 接口中不同类型的 `apply()` 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。 方法引用有自己的小魔法: @@ -788,7 +788,7 @@ accept() someOtherName() ``` -查看 `BiConsumer` 的文档,你会看到 `accept()` 方法。 实际上,如果我们将方法命名为 `accept()`,它就可以作为方法引用。 但是我们也可用不同的名称,比如 `someOtherName()`。只要参数类型、返回类型与 `BiConsumer` 的 `accept()` 相同即可。 +查看 `BiConsumer` 的文档,你会看到它的函数式方法为 `accept()` 。 的确,如果我们将方法命名为 `accept()`,它就可以作为方法引用。 但是我们也可用不同的名称,比如 `someOtherName()`。只要参数类型、返回类型与 `BiConsumer` 的 `accept()` 相同即可。 因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 `accept()`),而不是你的方法名。 @@ -876,7 +876,7 @@ public class TriFunctionTest { ### 缺少基本类型的函数 -让我们重温一下 `BiConsumer`,看看我们如何创建缺少的针对 **int**,**long** 和 **double** 的各种排列: +让我们重温一下 `BiConsumer`,看看我们将如何创建各种缺失的预定义组合,涉及 **int**,**long** 和 **double** (基本类型): ```java // functional/BiConsumerPermutations.java @@ -908,7 +908,7 @@ public class BiConsumerPermutations { 这里使用 `System.out.format()` 来显示。它类似于 `System.out.println()` 但提供了更多的显示选项。 这里,`%f` 表示我将 `n` 作为浮点值给出,`%d` 表示 `n` 是一个整数值。 这其中可以包含空格,输入 `%n` 会换行 — 当然使用传统的 `\n` 也能换行,但 `%n` 是自动跨平台的,这是使用 `format()` 的另一个原因。 -上例简单使用了包装类型,装箱和拆箱负责它与基本类型之间的来回转换。 又比如,我们可以将包装类型和`Function`一起使用,而不去用各种针对基本类型的预定义接口。代码示例: +上例只是简单使用了合适的包装类型,而装箱和拆箱负责它与基本类型之间的来回转换。 又比如,我们可以将包装类型和`Function`一起使用,而不去用各种针对基本类型的预定义接口。代码示例: ```java // functional/FunctionWithWrapped.java @@ -932,7 +932,7 @@ public interface IntToDoubleFunction { } ``` -因为我们可以简单地写 `Function ` 并产生正常的结果,所以用基本类型的唯一原因是可以避免传递参数和返回结果过程中的自动装箱和自动拆箱,进而提升性能。 +因为我们可以简单地写 `Function ` 并产生正常的结果,所以用基本类型(`IntToDoubleFunction`)的唯一理由是可以避免传递参数和返回结果过程中的自动拆装箱,进而提升性能。 似乎是考虑到使用频率,某些函数类型并没有预定义。 @@ -1050,7 +1050,7 @@ O **闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。 -考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决,那问题将变得非常棘手。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 +考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决,那问题将变得非常棘手。 能够解决这个问题的语言被称作 *支持闭包*,或者称作 *词法定界*(*lexically scoped* ,基于词法作用域的)( 也有用术语 *变量捕获* *variable capture* 称呼的)。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 首先,下列方法返回一个函数,该函数访问对象字段和方法参数: @@ -1169,7 +1169,7 @@ public class Closure5 { **等同 final 效果**意味着可以在变量声明前加上 **final** 关键字而不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。 -通过在闭包中使用 `final` 关键字提前修饰变量 `x` 和 `i` , 我们解决了 `Closure5.java` 中的问题。代码示例: +在闭包中,在使用 `x` 和 `i` 之前,通过将它们赋值给 `final` 修饰的变量,我们解决了 `Closure5.java` 中遇到的问题。代码示例: ```java @@ -1191,7 +1191,7 @@ public class Closure6 { 上例中 `iFinal` 和 `xFinal` 的值在赋值后并没有改变过,因此在这里使用 `final` 是多余的。 -如果函数式方法中使用的外部局部变量是引用,而不是基本类型的话,会是什么情况呢?我们可以把`int`类型改为`Integer`类型研究一下: +如果改用包装类型会是什么情况呢?我们可以把`int`类型改为`Integer`类型研究一下: ```java // functional/Closure7.java @@ -1208,7 +1208,7 @@ public class Closure7 { } ``` -编译器非常聪明地识别到变量 `i` 的值被更改过。 因为包装类型可能被特殊处理过了,所以我们尝试下 **List**: +编译器非常聪明地识别到变量 `i` 的值被更改过。 包装类型可能是被特殊处理了,我们再尝试下 **List**: ```java // functional/Closure8.java @@ -1250,7 +1250,7 @@ public class Closure8 { 请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。 -下面我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是触发**等同 final 效果**错误消息的原因。 +我们来看看 `Closure7.java` 和 `Closure8.java` 之间的区别。我们看到:在 `Closure7.java` 中变量 `i` 有过重新赋值。 也许这就是触发**等同 final 效果**错误消息的原因。 ```java // functional/Closure9.java @@ -1270,7 +1270,7 @@ public class Closure9 { 上例,重新赋值引用会触发错误消息。如果只修改指向的对象则没问题,只要没有其他人获得对该对象的引用(这意味着你有多个实体可以修改对象,此时事情会变得非常混乱),基本上就是安全的[^6]。 -让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是“在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。 +让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外部类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是 “在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**” 那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段(实例变量),那么它有独立的生命周期,不需要任何特殊的捕获以便稍后在调用 Lambda 时存在。(注:结论是——Lambda 可以没有限制地引用 实例变量和静态变量。但 局部变量必须显式声明为final,或事实上是final 。) @@ -1302,15 +1302,15 @@ public class AnonymousClosure { ## 函数组合 -函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,有一个使用 `andThen()` 的函数组合示例。一些 `java.util.function` 接口中包含支持函数组合的方法 [^7]。 +函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 `TransformFunction.java` 类中,就有一个使用 `andThen()` 的函数组合示例。一些 `java.util.function` 接口中包含支持函数组合的方法 [^7]。 | 组合方法 | 支持接口 | | :----- | :----- | -| `andThen(argument)`
根据参数执行原始操作 | **Function
BiFunction
Consumer
BiConsumer
IntConsumer
LongConsumer
DoubleConsumer
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator** | -| `compose(argument)`
根据参数执行原始操作 | **Function
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator** | -| `and(argument)`
短路**逻辑与**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | -| `or(argument)`
短路**逻辑或**原始谓词和参数谓词 | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | -| `negate()`
该谓词的**逻辑否**谓词| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `andThen(argument)`
执行原操作,再执行参数操作 | **Function
BiFunction
Consumer
BiConsumer
IntConsumer
LongConsumer
DoubleConsumer
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator** | +| `compose(argument)`
执行参数操作,再执行原操作 | **Function
UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator** | +| `and(argument)`
原谓词(Predicate)和参数谓词的短路**逻辑与** | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `or(argument)`
原谓词和参数谓词的短路**逻辑或** | **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | +| `negate()`
该谓词的**逻辑非**| **Predicate
BiPredicate
IntPredicate
LongPredicate
DoublePredicate** | 下例使用了 `Function` 里的 `compose()`和 `andThen()`。代码示例: @@ -1347,7 +1347,7 @@ _fter _ll _mbul_nces 当 `f1` 获得字符串时,它已经被`f2` 剥离了前三个字符。这是因为 `compose(f2)` 表示 `f2` 的调用发生在 `f1` 之前。 -下例是 `Predicate` 的逻辑运算演示.代码示例: +下例是 谓词(`Predicate`) 的逻辑运算演示.代码示例: ```java // functional/PredicateComposition.java @@ -1376,7 +1376,7 @@ foobar foobaz ``` -`p4` 获取到了所有谓词并组合成一个更复杂的谓词。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。 +`p4` 获取到了所有谓词(`Predicate`)并组合成一个更复杂的谓词。解读:如果字符串中不包含 `bar` 且长度小于 5,或者它包含 `foo` ,则结果为 `true`。 正因它产生如此清晰的语法,我在主方法中采用了一些小技巧,并借用了下一章的内容。首先,我创建了一个字符串对象的流,然后将每个对象传递给 `filter()` 操作。 `filter()` 使用 `p4` 的谓词来确定对象的去留。最后我们使用 `forEach()` 将 `println` 方法引用应用在每个留存的对象上。 From 4cf57da5fca73fac66a0a4895747323ff1b72799 Mon Sep 17 00:00:00 2001 From: andyphone <792998301@qq.com> Date: Tue, 8 Dec 2020 15:46:27 +0800 Subject: [PATCH 348/371] =?UTF-8?q?13-14=20=E4=BF=AE=E6=94=B9=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E6=8B=97=E5=8F=A3=E9=97=AE=E9=A2=98=20(#631)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1 * 2 * 没有第三种形式吧? * 修改语句拗口问题 * 修改语句拗口问题 2 * 修改语句拗口问题 3 * 修改语句拗口问题 4 * 修改语句拗口问题 5 * 修改语句拗口问题 6 * 修改语句拗口问题 7 * 修改语句拗口问题 8 * 修改语句拗口问题 9 * 拗口问题 * 修改语句拗口问题 10 * 修改语句拗口问题 11 * 修改语句拗口问题 12 * 修改语句拗口问题 13 * 修改语句拗口问题 14 * 修改语句拗口问题 14 * 修改语句拗口问题 15 * 修改语句拗口问题 16 * 修改语句拗口问题 17 * 十三章修改语句拗口问题 * 13-14 修改语句拗口问题 * 13-14 修改语句拗口问题 2 * 13-14 修改语句拗口问题 3 * 13-14 修改语句拗口问题 4 * Update 13-Functional-Programming.md Co-authored-by: Joe <736777445@qq.com> --- docs/book/11-Inner-Classes.md | 2 +- docs/book/13-Functional-Programming.md | 16 ++++----- docs/book/14-Streams.md | 50 +++++++++++++------------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index caf557e8..66d3d5a1 100755 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -1411,7 +1411,7 @@ Anonymous inner 8 Anonymous inner 9 ``` -**Counter** 返回的是序列中的下一个值。我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能用于实例初始化。 +**Counter** 返回的是序列中的下一个值。我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只能使用实例初始化。 所以使用局部内部类而不使用匿名内部类的另一个理由就是,需要不止一个该内部类的对象。 diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index 9cef7164..65f3ece4 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1429,9 +1429,9 @@ Hup Hey **[1]** 这一连串的箭头很巧妙。*注意*,在函数接口声明中,第二个参数是另一个函数。 -**[2]** 柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “自由函数”(free argumnet) 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。 +**[2]** 柯里化的目的是能够通过提供单个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “自由函数”(free argument) 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。 -我们可以通过添加级别来柯里化一个三参数函数: +我们可以通过继续添加层级来柯里化一个三参数函数: ```java // functional/Curry3Args.java @@ -1460,7 +1460,7 @@ public class Curry3Args { Hi Ho Hup ``` -对于每个级别的箭头级联(Arrow-cascading),你都要在类型声明中包裹另一层 **Function**。 +对于每一级的箭头级联(Arrow-cascading),你都会在类型声明周围包裹另一个 **Function** 。 处理基本类型和装箱时,请使用适当的函数式接口: @@ -1491,17 +1491,17 @@ public class CurriedIntAdd { ## 纯函数式编程 -即使没有函数式支持,像 C 这样的基础语言,也可以按照一定的原则编写纯函数式程序。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,所以编译器对我们犯的错误将无能为力。 +只要多加练习,用没有函数式支持的语言也可以写出纯函数式程序(即使是 C 这样的原始语言)。Java 8 让函数式编程更简单,不过我们要确保一切是 `final` 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,所以编译器对我们犯的错误将无能为力。 -这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要遵循一些规则) 或 Clojure (遵循的规则更少)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。 +这种情况下,我们可以借助第三方工具[^9],但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些练习) 或 Clojure (仅需更少的练习)。虽然 Java 支持[并发编程](./24-Concurrent-Programming.md),但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 `Scala` 或 `Clojure` 之类的语言。 ## 本章小结 -Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。在下一章中,你会看到它们在流式编程中的应用。相信你会像我一样,喜欢上流式编程。 +Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进。因为这允许你编写更简洁明了,易于理解的代码。在下一章中,你会看到它们在 *流式编程(streams)* 中的应用。相信你会像我一样,喜欢上流式编程。 -这些特性满足了很多羡慕Clojure、Scala 这类更函数化语言的程序员,并且阻止了Java程序员转向那些更函数化的语言(就算不能阻止,起码提供了更好的选择)。 +这些特性满足了很大一部分的、羡慕Clojure 和 Scala 这类更函数化语言的Java程序员。阻止了他们投奔向那些语言(或者至少让他们在投奔之前做好准备)。 但是,Lambdas 和方法引用远非完美,我们永远要为 Java 设计者早期的草率决定付出代价。特别是没有泛型 Lambda,所以 Lambda 在 Java 中并非一等公民。虽然我不否认 Java 8 的巨大改进,但这意味着和许多 Java 特性一样,它终究还是会让人感觉沮丧和鸡肋。 @@ -1516,7 +1516,7 @@ Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而 [^5]: 我还没有验证过这种说法。 [^6]: 当你理解了[并发编程](./24-Concurrent-Programming.md)章节的内容,你就能明白为什么更改共享变量 “不是线程安全的” 的了。 [^7]: 接口能够支持方法的原因是它们是 Java 8 默认方法,你将在下一章中了解到。 -[^8]: 一些语言,如 Python,允许像调用其他函数一样调用组合函数。但这是 Java,所以我们做做可为之事。 +[^8]: 一些语言,如 Python,允许像调用其他函数一样调用组合函数。但这是 Java,所以我们要量力而行。 [^9]: 例如,[Immutables](https://immutables.github.io/) 和 [Mutability Detector](https://mutabilitydetector.github.io/MutabilityDetector/)。 diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index ba2fd7c6..253968be 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -3,15 +3,17 @@ # 第十四章 流式编程 -> 集合优化了对象的存储,而流和对象的处理有关。 +> 集合优化了对象的存储,而流(Streams)则是关于一组组对象的处理。 -流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。 +流(Streams)是与任何特定存储机制无关的元素序列——实际上,我们说流是 "没有存储 "的。 -使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。 +取代了在集合中迭代元素的做法,使用流即可从管道中提取元素并对其操作。这些管道通常被串联在一起形成一整套的管线,来对流进行操作。 -在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。 +在大多数情况下,将对象存储在集合中就是为了处理它们,因此你会发现你把编程的主要焦点从集合转移到了流上。 -举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。实际上,你的关注点首先是创建一个有序集合。围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你想做什么: +流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。 + +举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。你要对它们进行排序的事实,会使你首先关注选用哪个有序集合,然后围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你要做什么: ```java // streams/Randoms.java @@ -40,11 +42,11 @@ public class Randoms { 19 ``` -首先,我们给 **Random** 对象一个种子(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了产生的数值的边界。这将生成一个随机整数流。我们用中间流操作(intermediate stream operation) `distinct()` 使流中的整数不重复,然后使用 `limit()` 方法获取前 7 个元素。接下来使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对流中的每个对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用:`System.out::println` 。 +首先,我们给 **Random** 对象一个种子值47(以便程序再次运行时产生相同的输出)。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 —— 两个参数限定了产生的数值的边界。这将生成一个随机整数流。我们用 *流的中间操作*(intermediate stream operation) `distinct()` 使流中的整数不重复,然后使用 `limit()` 方法获取前 7 个元素。接下来使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出,它根据传递给它的函数对流中的每个对象执行操作。在这里,我们传递了一个可以在控制台显示每个元素的方法引用:`System.out::println` 。 -注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。 +注意 `Randoms.java` 中没有声明任何变量。流可以在不曾使用赋值或可变数据的情况下,对有状态的系统建模,这非常有用。 -声明式编程(Declarative programming)是一种编程风格,它声明想要做什么,而非指明如何做。正如我们在函数式编程中所看到的。你会发现命令式编程的形式更难以理解。代码示例: +*声明式编程*(Declarative programming)是一种编程风格——它声明了要做什么,而不是指明(每一步)如何做。而这正是我们在函数式编程中所看到的(编程风格)。你会注意到,命令式(Imperative)编程的形式(指明每一步如何做)会更难理解: ```java // streams/ImperativeRandoms.java @@ -71,9 +73,9 @@ public class ImperativeRandoms { 在 `Randoms.java` 中,我们无需定义任何变量,但在这里我们定义了 3 个变量: `rand`,`rints` 和 `r`。由于 `nextInt()` 方法没有下限的原因(其内置的下限永远为 0),这段代码实现起来更复杂。所以我们要生成额外的值来过滤小于 5 的结果。 -注意,你必须用力的研究才能弄明白`ImperativeRandoms.java`程序在干什么。而在 `Randoms.java` 中,代码直接告诉了你它正在做什么。这种语义的清晰性是使用Java 8 流式编程的重要原因之一。 +注意,你必须研究代码才能搞清楚`ImperativeRandoms.java`程序在做什么。而在 `Randoms.java` 中,代码会直接告诉你它在做什么。这种语义的清晰性是使用Java 8 流式编程的重要原因之一。 -像在 `ImperativeRandoms.java` 中那样显式地编写迭代过程的方式称为外部迭代。而在 `Randoms.java` 中,你看不到任何上述的迭代过程,所以它被称为内部迭代,这是流式编程的一个核心特征。内部迭代产生的代码可读性更强,而且能更简单的使用多核处理器。通过放弃对迭代过程的控制,可以把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。 +像在 `ImperativeRandoms.java` 中那样显式地编写迭代过程的方式称为*外部迭代(external iteration)*。而在 `Randoms.java` 中,你看不到任何上述的迭代过程,所以它被称为*内部迭代(internal iteration)*,这是流式编程的一个核心特征。内部迭代产生的代码可读性更强,而且能更简单的使用多核处理器。通过放弃对迭代过程的控制,可以把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。 另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。 @@ -87,7 +89,7 @@ Java 设计者面临着这样一个难题:现存的大量类库不仅为 Java 一个大的挑战来自于使用接口的库。集合类是其中关键的一部分,因为你想把集合转为流。但是如果你将一个新方法添加到接口,那就破坏了每一个实现接口的类,因为这些类都没有实现你添加的新方法。 -Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `default`(`默认`)修饰的方法。通过这种方案,设计者们可以将流式(*stream*)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是到集合中)。 +Java 8 采用的解决方案是:在[接口](10-Interfaces.md)中添加被 `default`(`默认`)修饰的方法。通过这种方案,设计者们可以将流式(*stream*)方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是汇入一个集合)。 下面我们来看下每种类型的流操作。 @@ -265,7 +267,7 @@ public class RandomGenerators { 为了消除冗余代码,我创建了一个泛型方法 `show(Stream stream)` (在讲解泛型之前就使用这个特性,确实有点作弊,但是回报是值得的)。类型参数 `T` 可以是任何类型,所以这个方法对 **Integer**、**Long** 和 **Double** 类型都生效。但是 **Random** 类只能生成基本类型 **int**, **long**, **double** 的流。幸运的是, `boxed()` 流操作将会自动地把基本类型包装成为对应的装箱类型,从而使得 `show()` 能够接受流。 -我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个文本文件提供字符串对象的例子。 +我们可以使用 **Random** 为任意对象集合创建 **Supplier**。从文本文件提供字符串对象的例子如下。 Cheese.dat 文件内容: @@ -321,9 +323,9 @@ public class RandomWords implements Supplier { it shop sir the much cheese by conclusion district is ``` -在这里可以看到 `split()` 更复杂的运用。在构造器里,每一行都被 `split()` 通过方括号内的空格或其它标点符号分割。在方括号后面的 `+` 表示 `+` 前面的东西可以出现一次或者多次。 +在这里可以看到 `split()` 更复杂的运用。在构造器里,每一行都被 `split()` 通过方括号内的空格或其它标点符号分割。在方括号后面的 `+` 表示 `+` 前面的东西可以出现一次或者多次(正则表达式)。 -你会发现构造函数使用命令式编程(外部迭代)进行循环。在以后的例子中,你会看到我们是如何去除命令式编程的使用。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。 +你会发现构造函数使用命令式编程(外部迭代)进行循环。在以后的例子中,你会看到我们是如何去除命令式编程。这种旧的形式虽不是特别糟糕,但使用流会让人感觉更好。 在`toString()` 和`main()`方法中你看到了 `collect()` 操作,它根据参数来结合所有的流元素。当你用 `Collectors.joining()`作为 `collect()` 的参数时,将得到一个`String` 类型的结果,该结果是流中的所有元素被`joining()`的参数隔开。还有很多不同的 `Collectors` 用于产生不同的结果。 @@ -365,7 +367,7 @@ public class Ranges { 在主方法中的第一种方式是我们传统编写 `for` 循环的方式;第二种方式,我们使用 `range()` 创建了流并将其转化为数组,然后在 `for-in` 代码块中使用。但是,如果你能像第三种方法那样全程使用流是更好的。我们对范围中的数字进行求和。在流中可以很方便的使用 `sum()` 操作求和。 -注意 **IntStream.**`range()` 相比 `onjava.Range.range()` 拥有更多的限制。这是由于其可选的第三个参数,后者允许步长大于 1,并且可以从大到小来生成。 +注意 **IntStream.**`range()` 相比 `onjava.Range.range()` 受更多限制。这是由于其可选的第三个参数,后者允许步长大于 1,并且可以从大到小来生成。 实用小功能 `repeat()` 可以用来替换简单的 `for` 循环。代码示例: @@ -406,7 +408,7 @@ Hi! Hi! ``` -原则上,在代码中包含并解释 `repeat()` 并不值得。诚然它是一个相当透明的工具,但结果取决于你的团队和公司的运作方式。 +原则上,在代码中包含和解释 `repeat()` 并不值得。诚然它是一个相当透明的工具,但这取决于你的团队和公司的运作方式。 ### generate() @@ -441,7 +443,7 @@ public class Generator implements Supplier { YNZBRNYGCFOWZNTCQRGSEGZMMJMROE ``` -使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围,所以使用数组边界是经过深思熟虑的。 +使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围,所以使用数组边界是经过慎重考虑的。 如果要创建包含相同对象的流,只需要传递一个生成那些对象的 `lambda` 到 `generate()` 中: @@ -513,11 +515,11 @@ Bubble(3) Bubble(4) ``` -这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这是一个对于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。 +这是创建单独工厂类(Separate Factory class)的另一种方式。在很多方面它更加整洁,但是这是一个关于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。 ### iterate() -`Stream.iterate()` 产生的流的第一个元素是种子(iterate方法的第一个参数),然后将种子传递给方法(iterate方法的第二个参数)。方法运行的结果被添加到流(作为流的第二个元素),并存储起来作为下次调用 `iterate()`时的第一个参数,以此类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例: +`Stream.iterate()` 产生的流的第一个元素是种子(iterate方法的第一个参数),然后将种子传递给方法(iterate方法的第二个参数)。方法运行的结果被添加到流(作为流的下一个元素),并被存储起来,作为下次调用 `iterate()`方法时的第一个参数,以此类推。我们可以利用 `iterate()` 生成一个斐波那契数列(上一章已经遇到过Fibonacci)。代码示例: ```java // streams/Fibonacci.java @@ -563,7 +565,7 @@ public class Fibonacci { ### 流的建造者模式 -在建造者模式(Builder design pattern)中,首先创建一个 `builder` 对象,然后将创建流所需的多个信息传递给它,最后`builder` 对象执行”创建“流的操作。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例: +在*建造者模式*(Builder design pattern)中,首先创建一个 `builder` 对象,然后将创建流所需的多个信息传递给它,最后`builder` 对象执行”创建“流的操作。**Stream** 库提供了这样的 `Builder`。在这里,我们重新审视文件读取并将其转换成为单词流的过程。代码示例: ```java // streams/FileToWordsBuilder.java @@ -722,7 +724,7 @@ public class FileToWordsRegexp { Not much of a cheese shop really is it ``` -在构造器中我们读取了文件中的所有内容(跳过第一行注释,并将其转化成为单行字符串)。现在,当你调用 `stream()` 的时候,可以像往常一样获取一个流,但这次你可以多次调用 `stream()` 在已存储的字符串中创建一个新的流。这里有个限制,整个文件必须存储在内存中;在大多数情况下这并不是什么问题,但是这损失了流操作非常重要的优势: +在构造器中我们读取了文件中的所有内容(跳过第一行注释,并将其转化成为单行字符串)。现在,当你调用 `stream()` 的时候,可以像往常一样获取一个流,但这回你可以多次调用 `stream()` ,每次从已存储的字符串中创建一个新的流。这里有个限制,整个文件必须存储在内存中;在大多数情况下这并不是什么问题,但是这丢掉了流操作非常重要的优势: 1. “不需要把流存储起来。”当然,流确实需要一些内部存储,但存储的只是序列的一小部分,和存储整个序列不同。 2. 它们是懒加载计算的。 @@ -795,11 +797,11 @@ you what to the that sir leads in district And ### 移除元素 -* `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合,该方法的工作量要少得多。 +* `distinct()`:在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合来消除重复,该方法的工作量要少得多。 -* `filter(Predicate)`:若元素传递给过滤函数产生的结果为`true` ,则过滤操作保留这些元素。 +* `filter(Predicate)`:过滤操作,保留如下元素:若元素传递给过滤函数产生的结果为`true` 。 -在下例中,`isPrime()` 作为过滤器函数,用于检测质数。 +在下例中,`isPrime()` 作为过滤函数,用于检测质数。 ```java // streams/Prime.java From d05cb54eaac23a04184e3f106424339981a43b7f Mon Sep 17 00:00:00 2001 From: LingCoder <34231795+LingCoder@users.noreply.github.com> Date: Fri, 11 Dec 2020 12:19:14 +0800 Subject: [PATCH 349/371] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6c333a9..9e9e83bd 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ ## 书籍简介 - 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 -- 本书是事实上的 《Java 编程思想》第五版。 -- 《Java 编程思想》第四版基于 JAVA **5** 版本;《On Java 8》 基于 JAVA **8** 版本。 + ## 传送门 @@ -99,7 +98,7 @@ 1. 本书排版布局和翻译风格上参考**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) 2. 采用第一人称叙述。 3. 由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。所以本人在翻译过程中,去除了部分主题无关内容、重复描写。 -4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文以及《Java 编程思想》第四版中文版的部分内容(对其翻译死板,生造名词,语言精炼度差问题进行规避和改正)。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 +4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 5. 由于译者个人能力、时间有限,如有翻译错误和笔误的地方,还请大家批评指正! ## 如何参与 From 4b782bedf76ea58f3359505e0a0354c6dd0b5f81 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Wed, 9 Sep 2020 23:14:24 +0800 Subject: [PATCH 350/371] =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/00-Introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/00-Introduction.md b/docs/book/00-Introduction.md index bcd233c0..a9ec9f27 100644 --- a/docs/book/00-Introduction.md +++ b/docs/book/00-Introduction.md @@ -1,7 +1,7 @@ # 简介 -> “我的语言极限,即是我的世界的极限。” ——路德维希·维特根斯坦(*Wittgenstein*) +> “语言观决定世界观。” ——路德维希·维特根斯坦(*Wittgenstein*) 这句话无论对于自然语言还是编程语言来说都是一样的。你所使用的编程语言会将你的思维模式固化并逐渐远离其他语言,而且往往发生在潜移默化中。Java 作为一门傲娇的语言尤其如此。 From a5e6ce4b8961164f9b9d4a2e91b4ebd62f735bde Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 11 Dec 2020 12:50:07 +0800 Subject: [PATCH 351/371] =?UTF-8?q?=E5=9B=A0=E4=B8=BA=E4=BE=B5=E6=9D=83?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=88=A0=E9=99=A4=E4=BB=8B=E7=BB=8D?= =?UTF-8?q?=E9=A1=B5=E4=B8=8E=E3=80=8AJava=E7=BC=96=E7=A8=8B=E6=80=9D?= =?UTF-8?q?=E6=83=B3=E3=80=8B=E6=9C=89=E5=85=B3=E7=9A=84=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E9=80=A0=E6=88=90=E9=94=99=E8=AF=AF=E5=BC=95=E5=AF=BC=E7=9A=84?= =?UTF-8?q?=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 4 +--- docs/_coverpage.md | 2 +- docs/book/24-Concurrent-Programming.md | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index fca2f067..d51ce131 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,8 +9,6 @@ ## 书籍简介 * 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 -* 本书是事实上的 《Java 编程思想》第五版。 -* 《Java 编程思想》第四版基于 JAVA **5** 版本;《On Java 8》 基于 JAVA **8** 版本。 ## 翻译说明 @@ -18,7 +16,7 @@ 1. 本书排版布局和翻译风格上参考了**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) 2. 采用第一人称叙述。 3. 由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。所以本人在翻译过程中,去除了部分主题无关内容、重复描写。 -4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文以及《Java编程思想》第四版中文版的部分内容(对其翻译死板,生造名词,语言精炼度差问题进行规避和改正)。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 +4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 5. 由于译者个人能力、时间有限,如有翻译错误和笔误的地方,还请大家批评指正! ## 如何参与 diff --git a/docs/_coverpage.md b/docs/_coverpage.md index 09ca8f43..68c3bf2f 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -2,7 +2,7 @@ # On Java 8 -- 《On Java 8》中文版,是事实上的《Java 编程思想》第5版。 +- 《On Java 8》中文版。 [![stars](https://badgen.net/github/stars/lingcoder/OnJava8?icon=github&color=4ab8a1)](https://github.com/lingcoder/OnJava8) [![forks](https://badgen.net/github/forks/lingcoder/OnJava8?icon=github&color=4ab8a1)](https://github.com/lingcoder/OnJava8) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 201b9a3d..52e7aacd 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -65,13 +65,13 @@ slowdown occurs): - **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams** 就是一个很好的例子)。 - **纯并行**:除非有多个处理器,否则不会运行。 -在某些情况下,这可能是一个有用的分类法。 +在某些情况下,这是一个有效的分类法。 -支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction) 一词的完美候选。抽象的目标是“抽象出”那些对于手头想法不重要的东西,以屏蔽不必要的细节。如果抽象是有漏洞的,那些碎片和细节就会不断重新声明自己是重要的,无论你废了多少功夫来隐藏它们。 +支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction) 一词的完美候选。抽象的目标是“抽象”掉那些对手头的想法不重要的部分,以屏蔽不必要的细节所带来的影响。如果抽象是有漏洞的,那么即使废很大功夫去隐藏它们,这些细枝末节也总会不断凸显出自己是重要的,。 -我开始怀疑是否真的有高度抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于 CPU 缓存如何工作的细节,都永远不会被屏蔽。最后,如果你非常小心,你创作的东西在特定的情况下工作,但在其他情况下不工作。有时是两台机器的配置方式不同,有时是程序的估计负载不同。这不是 Java 特有的 - 这是并发和并行编程的本质。 +于是我开始怀疑是否真的有高度地抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于 CPU 缓存如何工作的细节,都永远不会被屏蔽。最后,即使你已非常谨慎,你开发的程序也不一定在所有情况下运行正常。有时是因为两台机器的配置不同,有时是程序的预计负载不同。这不是 Java 特有的 - 这是并发和并行编程的本质。 -你可能会认为[纯函数式 ](https://en.wikipedia.org/wiki/Purely_functional) 语言没有这些限制。实际上,纯函数式语言解决了大量并发问题,所以如果你正在解决一个困难的并发问题,你可以考虑用纯函数语言编写这个部分。但最终,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 +你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional) 语言没有这些限制。实际上,纯函数式语言的确解决了大量并发问题。如果你正在解决一个困难的并发问题,可以考虑用纯函数语言编写这个部分。但是,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 ### 并发的新定义 From 963abc9397503a9ff5763e0687196f5f399f783e Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 11 Dec 2020 12:53:16 +0800 Subject: [PATCH 352/371] =?UTF-8?q?=E5=9B=A0=E4=B8=BA=E4=BE=B5=E6=9D=83?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=88=A0=E9=99=A4=E4=BB=8B=E7=BB=8D?= =?UTF-8?q?=E9=A1=B5=E4=B8=8E=E3=80=8AJava=E7=BC=96=E7=A8=8B=E6=80=9D?= =?UTF-8?q?=E6=83=B3=E3=80=8B=E6=9C=89=E5=85=B3=E7=9A=84=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E9=80=A0=E6=88=90=E9=94=99=E8=AF=AF=E5=BC=95=E5=AF=BC=E7=9A=84?= =?UTF-8?q?=E6=96=87=E5=AD=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book.json b/book.json index dd18be46..36f77afe 100644 --- a/book.json +++ b/book.json @@ -1,7 +1,7 @@ { "title": "《On Java 8》中文版", "author": "LingCoder", - "description": "根据 Bruce Eckel 大神的新书 On Java 8 翻译,可以说是事实上的 Thinking in Java 5th", + "description": "", "language": "zh-hans", "gitbook": "3.2.3", "styles": { From 4e513bb43d49fcdc5e46211c4d0442a29351925b Mon Sep 17 00:00:00 2001 From: huakaimay <39322023+nengquqiaoxiaoyun@users.noreply.github.com> Date: Mon, 14 Dec 2020 15:36:11 +0800 Subject: [PATCH 353/371] =?UTF-8?q?=E5=B0=86Fruit=E6=94=B9=E4=B8=BAflist?= =?UTF-8?q?=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原文:but to upcast to flist, that type is a “don’t actually care --- docs/book/20-Generics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/20-Generics.md b/docs/book/20-Generics.md index 72263fcc..d93b82ed 100644 --- a/docs/book/20-Generics.md +++ b/docs/book/20-Generics.md @@ -2666,7 +2666,7 @@ public class GenericsAndCovariance { } ``` -**flist** 的类型现在是 `List`,你可以读作“一个具有任何从 **Fruit** 继承的类型的列表”。然而,这实际上并不意味着这个 **List** 将持有任何类型的 **Fruit**。通配符引用的是明确的类型,因此它意味着“某种 **flist** 引用没有指定的具体类型”。因此这个被赋值的 **List** 必须持有诸如 **Fruit** 或 **Apple** 这样的指定类型,但是为了向上转型为 **Fruit**,这个类型是什么没人在意。 +**flist** 的类型现在是 `List`,你可以读作“一个具有任何从 **Fruit** 继承的类型的列表”。然而,这实际上并不意味着这个 **List** 将持有任何类型的 **Fruit**。通配符引用的是明确的类型,因此它意味着“某种 **flist** 引用没有指定的具体类型”。因此这个被赋值的 **List** 必须持有诸如 **Fruit** 或 **Apple** 这样的指定类型,但是为了向上转型为 **flist**,这个类型是什么没人在意。 **List** 必须持有一种具体的 **Fruit** 或 **Fruit** 的子类型,但是如果你不关心具体的类型是什么,那么你能对这样的 **List** 做什么呢?如果不知道 **List** 中持有的对象是什么类型,你怎能保证安全地向其中添加对象呢?就像在 **CovariantArrays.java** 中向上转型一样,你不能,除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。 From 91c862c8fe45b6fdf80c06531d714ddae9b890fa Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Fri, 8 Jan 2021 11:03:25 +0800 Subject: [PATCH 354/371] Update 24-Concurrent-Programming.md --- docs/book/24-Concurrent-Programming.md | 73 ++++++++++++-------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 52e7aacd..67de4a97 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -12,39 +12,32 @@ > > 猫咪:“你一定是疯了,否则你就不会来这儿” ——爱丽丝梦游仙境 第 6 章。 +在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果我们将一个值设置为 5,再看时它已变成 47 的话,这就令人匪夷所思了。 -在本章之前,我们惯用一种简单顺序的叙事方式来编程,有点类似文学中的意识流:第一件事发生了,然后是第二件,第三件……总之,我们完全掌握着事情发生的进展和顺序。如果将值设置为 5,再看时它已变成 47 的话,结果就很匪夷所思了。 +现在,我们来到了陌生的并发世界,在这里这样的结果一点都不奇怪。你原来相信的一切都不再可靠。原有的规则可能生效也可能失效。更可能的是原有的规则只会在某些情况下生效。我们只有完全了解这些情况,才能决定我们处理事情的方式。 -现在,我们来到了陌生的并发世界。这样的结果一点都不奇怪,因为你原来信赖的一切都不再可靠。它可能有效,也可能无效。更可能得是,它在某些情况下会起作用,而在另一些情况下则不会。只有了解了这些情况,我们才能正确地行事。 - -作为类比,我们正常生活是发生在经典牛顿力学中的。物体具有质量:会坠落并转移动量。电线有电阻,光直线传播。假如我们进入极小、极热、极冷、或是极大的世界(我们不能生存),这些现象就会发生变化。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质还会变为超导体。 +比如,我们正常的生活的世界是遵循经典牛顿力学的。物体具有质量:会坠落并且转移动能。电线有电阻,光沿直线传播。假如我们进入到极小、极大、极冷或者极热(那些我们无法生存的世界),这些现象就会发生改变。我们无法判断某物体是粒子还是波,光是否受到重力影响,一些物质还会变为超导体。 假设我们处在多条故事线并行的间谍小说里,非单一意识流地叙事:第一个间谍在岩石底留下了微缩胶片。当第二个间谍来取时,胶片可能已被第三个间谍拿走。小说并没有交代此处的细节。所以直到故事结尾,我们都没搞清楚到底发生了什么。 -构建并发程序好比玩[搭积木 ](https://en.wikipedia.org/wiki/Jenga) 游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 +构建并发程序好比玩[搭积木](https://en.wikipedia.org/wiki/Jenga)游戏。每拉出一块放在塔顶时都有崩塌的可能。每个积木塔和应用程序都是独一无二的,有着自己的作用。你在某个系统构建中学到的知识并不一定适用于下一个系统。 本章是对并发概念最基本的介绍。虽然我们用到了现代的 Java 8 工具来演示原理,但还远未及全面论述并发。我的目标是为你提供足够的基础知识,使你能够把握问题的复杂性和危险性,从而安全地渡过这片鲨鱼肆虐的困难水域。 -更多繁琐和底层的细节,请参阅附录:[并发底层原理 ](./Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了 JVM 的内部工作方式,包括线程。 - - - +更多繁琐和底层的细节,请参阅附录:[并发底层原理](https://github.com/LingCoder/OnJava8/blob/master/docs/book/Appendix-Low-Level-Concurrency.md)。要进一步深入这个领域,你还必须阅读 *Brian Goetz* 等人的 《Java Concurrency in Practice》。在撰写本文时,该书已有十多年的历史了,但它仍包含我们必须要了解和明白的知识要点。理想情况下,本章和上述附录是阅读该书的良好前提。另外,*Bill Venner* 的 《Inside the Java Virtual Machine》也很值得一看。它详细描述了包括线程在内的 JVM 的内部工作方式。 ## 术语问题 -术语“并发”,“并行”,“多任务”,“多处理”,“多线程”,分布式系统(可能还有其他)在整个编程文献中都以多种相互冲突的方式使用,并且经常被混为一谈。 -*Brian Goetz* 在他 2016 年《从并发到并行》的演讲中指出了这一点,之后提出了合理的二分法: +术语“并发”,“并行”,“多任务”,“多处理”,“多线程”,分布式系统(可能还有其他)在整个编程文献中都以多种相互冲突的方式使用,并且经常被混为一谈。 *Brian Goetz* 在他 2016 年《从并发到并行》的演讲中指出了这一点,之后提出了合理的区分: - 并发是关于正确有效地控制对共享资源的访问。 - - 并行是使用额外的资源来更快地产生结果。 这些定义很好,但是我们已有几十年混乱使用和抗拒解决此问题的历史了。一般来说,当人们使用“并发”这个词时,他们的意思是“所有的一切”。事实上,我自己也经常陷入这样的想法。在大多数书籍中,包括 *Brian Goetz* 的 《Java Concurrency in Practice》,都在标题中使用这个词。 -“并发”通常表示:不止一个任务正在执行。而“并行”几乎总是代表:不止一个任务同时执行。现在你能看到问题所在了吗?“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外,还有一些场景重叠:为并行编写的程序有时在单处理器上运行,而一些并发编程系统可以利用多处理器。 +“并发”通常表示:”不止一个任务正在执行“。而“并行”几乎总是代表:”不止一个任务同时执行“。现在我们能立即看出这些定义中的问题所在:“并行”也有不止一个任务正在执行的语义在里面。区别就在于细节:究竟是怎么“执行”的。此外还有一些重叠:为并行编写的程序依旧可以在单处理器上运行,而并发编写的系统也可以利用多个处理器。 -还有另一种方法,在减速发生的地方写下定义(原文 Here’s another approach, writing the definitions around where the -slowdown occurs): +还有另一种方式,围绕”缓慢“出现的情况写下定义: **并发** @@ -60,37 +53,37 @@ slowdown occurs): 我们甚至可以尝试以更细的粒度去进行定义(然而这并不是标准化的术语): -- **纯并发**:仍然在单个 CPU 上运行任务。纯并发系统比顺序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 -- **并发-并行**:使用并发技术,结果程序可以利用更多处理器更快地产生结果。 -- **并行-并发**:使用并行编程技术编写,如果只有一个处理器,结果程序仍然可以运行(Java 8 **Streams** 就是一个很好的例子)。 -- **纯并行**:除非有多个处理器,否则不会运行。 +- **纯并发**:仍然在单个 CPU 上运行任务。纯并发系统比时序系统更快地产生结果,但是它的运行速度不会因为处理器的增加而变得更快。 + +- **并发-并行**:使用并发技术,结果程序可以利用多处理器更快地产生结果。 +- **并行-并发**:使用并行编程技术编写,即使只有一个处理器,结果程序仍然可以运行(Java 8 **Streams** 就是一个很好的例子)。 +- **纯并行**:只有多个处理器的情况下才能运行。 在某些情况下,这是一个有效的分类法。 -支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction)](https://en.wikipedia.org/wiki/Leaky_abstraction) 一词的完美候选。抽象的目标是“抽象”掉那些对手头的想法不重要的部分,以屏蔽不必要的细节所带来的影响。如果抽象是有漏洞的,那么即使废很大功夫去隐藏它们,这些细枝末节也总会不断凸显出自己是重要的,。 +支持并发性的语言和库似乎是[抽象泄露(Leaky Abstraction](https://en.wikipedia.org/wiki/Leaky_abstraction)一词的完美候选。抽象的目标是“抽象”掉那些对手头的想法不重要的部分,以屏蔽不必要的细节所带来的影响。如果抽象发生泄露,那么即使费很大功夫去隐藏它们,这些细枝末节也总会不断凸显出自己是重要的。 于是我开始怀疑是否真的有高度地抽象。因为当编写这类程序时,底层的系统、工具,甚至是关于 CPU 缓存如何工作的细节,都永远不会被屏蔽。最后,即使你已非常谨慎,你开发的程序也不一定在所有情况下运行正常。有时是因为两台机器的配置不同,有时是程序的预计负载不同。这不是 Java 特有的 - 这是并发和并行编程的本质。 -你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional) 语言没有这些限制。实际上,纯函数式语言的确解决了大量并发问题。如果你正在解决一个困难的并发问题,可以考虑用纯函数语言编写这个部分。但是,如果你编写一个使用队列的系统,例如,如果该系统没有被正确地调整,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有细节,任何问题都可能会破坏你的系统。这是一种非常不同的编程方式。 +你可能会认为[纯函数式](https://en.wikipedia.org/wiki/Purely_functional)语言没有这些限制。实际上,纯函数式语言的确解决了大量并发问题。如果你正在解决一个困难的并发问题,可以考虑用纯函数语言编写这个部分。但是,如果你编写一个使用队列的系统,例如,如果该系统没有被合理地调优,并且输入速率也没有被正确地估计或限制(在不同的情况下,限制意味着具有不同的影响的不同东西),该队列要么被填满并阻塞,要么溢出。最后,你必须了解所有可能会破坏你的系统的细节和问题。这是一种非常不同的编程方式。 + +## 并发的新定义 - -### 并发的新定义 +几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战是简洁的定义它。在撰写本章的过程中,我终于有了这样的洞察力,我将其定义为: -几十年来,我一直在努力解决各种形式的并发问题,其中一个最大的挑战一直是简单地定义它。在撰写本章的过程中,我终于有了这样的洞察力,我认为可以定义它: ->** 并发性是一系列性能技术,专注于减少等待** +> 并发性是一系列专注于减少等待的性能技术 这实际上是一个相当复杂的表述,所以我将其分解: -- 这是一个集合:包含许多不同的方法来解决这个问题。这是使定义并发性如此具有挑战性的问题之一,因为技术差异很大。 -- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在 Java 中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得无法管理。 -- “减少等待”部分很重要而且微妙。无论(例如)你运行多少个处理器,你只能在等待发生时产生效益。如果你发起 I/O 请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是如果程序的某些部分被迫等待。等待可以以多种形式出现 - 这解释了为什么存在如此不同的并发方法。 +- 这是一个集合:包含许多不同的方法来解决这个问题。因为技术差异很大,这是使定义并发性如此具有挑战性的问题之一。 +- 这些是性能技术:就是这样。并发的关键点在于让你的程序运行得更快。在 Java 中,并发是非常棘手和困难的,所以绝对不要使用它,除非你有一个重大的性能问题 - 即使这样,使用最简单的方法产生你需要的性能,因为并发很快变得难以管理。 +- “减少等待”部分很重要而且微妙。无论(例如)你的程序运行在多少个处理器上,你只能在等待发生时产生效益。如果你发起 I/O 请求并立即获得结果,没有延迟,因此无需改进。如果你在多个处理器上运行多个任务,并且每个处理器都以满容量运行,并且没有任务需要等待其他任务,那么尝试提高吞吐量是没有意义的。并发的唯一机会是程序的某些部分被迫等待。等待会以多种形式出现 - 这解释了为什么存在多种不同的并发方法。 值得强调的是,这个定义的有效性取决于“等待”这个词。如果没有什么可以等待,那就没有机会去加速。如果有什么东西在等待,那么就会有很多方法可以加快速度,这取决于多种因素,包括系统运行的配置,你要解决的问题类型以及其他许多问题。 - ## 并发的超能力 -想象一下,你置身于一部科幻电影。你必须在一栋大楼中找到一个东西,它被小心而巧妙地隐藏在大楼一千万个房间中的一间。你进入大楼,沿着走廊走下去。走廊是分开的。 +想象一下,你置身于一部科幻电影。你必须在一栋大楼中找到一个东西,它被小心而巧妙地隐藏在大楼千万个房间中的一间。你进入大楼,沿着走廊走下去。走廊是分开的。 一个人完成这项任务要花上一百辈子的时间。 @@ -100,31 +93,31 @@ slowdown occurs): 一旦克隆体进入房间,它必须搜索房间的每个角落。这时它切换到了第二种超能力。它分裂成了一百万个纳米机器人,每个机器人都会飞到或爬到房间里一些看不见的地方。你不需要了解这种功能 - 一旦你开启它就会自动工作。在他们自己的控制下,纳米机器人开始行动,搜索房间然后回来重新组装成你,突然间,不知怎么的,你就知道这间房间里有没有那个东西。 -我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是泄漏抽象的(leaky abstraction)。 +我很想说,“并发就是刚才描述的置身于科幻电影中的超能力“。就像你自己可以一分为二然后解决更多的问题一样简单。但是问题在于,我们来描述这种现象的任何模型最终都是抽象泄露的(leaky abstraction)。 -以下是其中一个漏洞:在理想的世界中,每次克隆自己时,还需要复制一个物理处理器来运行该克隆。这当然是不现实的——实际上,你的机器上一般只有 4 个或 8 个处理器核心(编写本文时的典型情况)。你也可能更多,但仍有很多情况下只有一个单核处理器。在关于抽象的讨论中,分配物理处理器核心这本身就是抽象的泄露,甚至也可以支配你的决策。 +以下是其中一个泄露:在理想的世界中,每次克隆自己时,也会复制一个物理处理器来运行克隆搜索者。这当然是不现实的——实际上,你的机器上一般只有 4 个或 8 个处理器核心(编写本文时的典型情况)。或许你拥有更多的处理器,但仍有很多情况下只有一个单核处理器。在关于抽象的讨论中,分配物理处理器核心这本身就是抽象的泄露,甚至也可以支配你的决策。 让我们在科幻电影中改变一些东西。现在当每个克隆搜索者最终到达一扇门时,他们必须敲门并等到有人开门。如果每个搜索者都有一个处理器核心,这没有问题——只是空闲等待直到有人开门。但是如果我们只有 8 个处理器核心却有几千个搜索者,我们不希望处理器仅仅因为某个搜索者恰好在等待回答中被锁住而闲置下来。相反,我们希望将处理器应用于可以真正执行工作的搜索者身上,因此需要将处理器从一个任务切换到另一个任务的机制。 -许多模型能够有效地隐藏处理器的数量,允许你假装有很多个处理器。但在某些情况下,这种方法会失效,这时你必须知道处理器核心的真实数量,以便处理这个问题。 +许多模型能够有效地隐藏处理器的数量,允许你假装有很多个处理器。但在某些情况下,当你必须明确知道处理器数量以便于工作的时候,这些模型就会失效。 -最大的影响之一取决于您是使用单核处理器还是多核处理器。如果你只有单核处理器,那么任务切换的成本也由该核心承担,将并发技术应用于你的系统会使它运行得更慢。 +最大的影响之一取决于是使用单核处理器还是多核处理器。如果你只有单核处理器,那么任务切换的成本也由该核心承担,将并发技术应用于你的系统会使它运行得更慢。 这可能会让你以为,在单核处理器的情况下,编写并发代码是没有意义的。然而,有些情况下,并发模型会产生更简单的代码,光是为了这个目的就值得舍弃一些性能。 -在克隆体敲门等待的情况下,即使单核处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备运行的任务。但是如果所有任务都可以一直运行那么切换的成本反而会使任务变慢,在这种情况下,如果你有多个进程,并发通常只会有意义。 +在克隆体敲门等待的情况下,即使单核处理器系统也能从并发中受益,因为它可以从等待(阻塞)的任务切换到准备运行的任务。但是如果所有任务都可以一直运行那么切换的成本反而会使任务变慢,在这种情况下,并发只在如果你有多个处理器的情况下有意义。 -如果你正在尝试破解某种密码,在同一时间内参与破解的线程越多,你越快得到答案的可能性就越大。每个线程都能持续使用你所分配的处理器时间,在这种情况下(CPU 密集型问题),你代码中的线程数应该和你拥有的处理器的核心数保持一致。 +假设你正在尝试破解某种密码,在同一时间内参与破解的线程越多,你越快得到答案的可能性就越大。每个线程都能持续使用你所分配的处理器时间,在这种情况下(CPU 密集型问题),你代码中的线程数应该和你拥有的处理器的核心数保持一致。 -在接听电话的客户服务部门,你只有一定数量的员工,但是你的部门可能会打来很多电话。这些员工(处理器)一次只能接听一个电话直到打完,此时其它打来的电话必须排队等待。 +在接听电话的客户服务部门,你只有一定数量的员工,但是你的部门可能会收到很多电话。这些员工(处理器)一次只能接听一个电话直到打完,此时其它打来的电话必须排队等待。 在“鞋匠和精灵”的童话故事中,鞋匠有很多工作要做,当他睡着时,出现了一群精灵来为他制作鞋子。这里的工作是分布式的,但即使使用大量的物理处理器,在制造鞋子的某些部件时也会产生限制——例如,如果鞋底的制作时间最长,这就限制了鞋子的制作速度,这也会改变你设计解决方案的方式。 -因此,你要解决的问题推动了解决方案的设计。将一个问题分解成“独立运行”的子任务,这是一种美好的抽象,然后就是残酷的现实:物理现实不断地侵入和动摇这个抽象。 +因此,你要解决的问题驱动了方案的设计。将一个问题分解成“独立运行”的子任务,这是一种美好的抽象,然后就是实际发生的现实:物理现实不断干扰和动摇这个抽象。 -这只是问题的一部分:考虑一个制作蛋糕的工厂。我们以某种方式把制作蛋糕的任务分给了工人们,但是现在是时候让工人把蛋糕放在盒子里了。那里有一个盒子,准备存放蛋糕。但是在工人把蛋糕放进盒子之前,另一个工人就冲过去,把蛋糕放进盒子里,砰!这两个蛋糕撞到一起砸坏了。这是常见的“共享内存”问题,会产生所谓的竞态条件(race condition),其结果取决于哪个工人能先把蛋糕放进盒子里(通常使用锁机制来解决问题,因此一个工作人员可以先抓住一个盒子并防止蛋糕被砸烂)。 +这只是问题的一部分:考虑一个制作蛋糕的工厂。我们以某种方式把制作蛋糕的任务分给了工人们,现在是时候让工人把蛋糕放在盒子里了。那里有一个准备存放蛋糕的盒子。但是在一个工人把蛋糕放进盒子之前,另一个工人就冲过去,把蛋糕放进盒子里,砰!这两个蛋糕撞到一起砸坏了。这是常见的“共享内存”问题,会产生所谓的竞态条件(race condition),其结果取决于哪个工人能先把蛋糕放进盒子里(通常使用锁机制来解决问题,因此一个工作人员可以先抓住一个盒子并防止蛋糕被砸烂)。 -当“同时”执行的任务相互干扰时,就会出现问题。这可能以一种微妙而偶然的方式发生,因此可以说并发是“可以论证的确定性,但实际上是不确定性的”。也就是说,假设你很小心地编写并发程序,而且通过了代码检查可以正确运行。然而实际上,我们编写的并发程序大部分情况下都能正常运行,但是在一些情况下会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 +当“同时”执行的任务相互干扰时,就会出现问题。这可能以一种微妙而偶然的方式发生,因此可以说并发是“可以论证的确定性,但实际上是不确定性的”。也就是说,假设你很小心地编写并发程序,而且通过了代码检查可以正确运行。然而实际上,我们编写的并发程序大部分情况下都能正常运行,但是在一些特定情况下会失败。这些情况可能永远不会发生,或者在你在测试期间几乎很难发现它们。实际上,编写测试代码通常无法为并发程序生成故障条件。由此产生的失败只会偶尔发生,因此它们以客户投诉的形式出现。这是学习并发中最强有力的论点之一:如果你忽略它,你可能会受伤。 因此,并发似乎充满了危险,如果这让你有点害怕,这可能是一件好事。尽管 Java 8 在并发性方面做出了很大改进,但仍然没有像编译时验证 (compile-time verification) 或受检查的异常 (checked exceptions) 那样的安全网来告诉你何时出现错误。关于并发,你只能依靠自己,只有知识渊博、保持怀疑和积极进取的人,才能用 Java 编写可靠的并发代码。 From 21413cb09665f87e566e9e0840b0804ab7a817d4 Mon Sep 17 00:00:00 2001 From: iao113 Date: Mon, 11 Jan 2021 17:14:45 +0800 Subject: [PATCH 355/371] Update 04-Operators.md (#638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “内存溢出”通常指“堆栈溢出”,算术导致的溢出通常会用“数据溢出”或者“算术溢出”。 --- docs/book/04-Operators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/04-Operators.md b/docs/book/04-Operators.md index 40ae0a9f..14f8fcf7 100644 --- a/docs/book/04-Operators.md +++ b/docs/book/04-Operators.md @@ -1436,7 +1436,7 @@ public class AllOps { ```java // operators/Overflow.java -// 厉害了!内存溢出 +// 厉害了!数据溢出了! public class Overflow { public static void main(String[] args) { int big = Integer.MAX_VALUE; From a7244b24d20afe78f23d2c932f7279dd8eb0ee81 Mon Sep 17 00:00:00 2001 From: iao113 Date: Wed, 13 Jan 2021 14:21:56 +0800 Subject: [PATCH 356/371] Update 06-Housekeeping.md (#639) typo --- docs/book/06-Housekeeping.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/06-Housekeeping.md b/docs/book/06-Housekeeping.md index 0248808c..db3510bf 100644 --- a/docs/book/06-Housekeeping.md +++ b/docs/book/06-Housekeeping.md @@ -1130,7 +1130,7 @@ public class ExplicitStatic { 输出: ``` -Inside main +Inside main() Cup(1) Cup(2) f(99) @@ -1182,7 +1182,7 @@ public class Mugs { 输出: ``` -Inside main +Inside main() Mug(1) Mug(2) mug1 & mug2 initialized From 4cef49d2a28ae6c5a1b64ccfb969c47d8be28495 Mon Sep 17 00:00:00 2001 From: iao113 Date: Fri, 29 Jan 2021 10:45:07 +0800 Subject: [PATCH 357/371] Update 07-Implementation-Hiding.md (#641) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一些加重左右缺少空格导致显示不正确。 --- docs/book/07-Implementation-Hiding.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/book/07-Implementation-Hiding.md b/docs/book/07-Implementation-Hiding.md index 874ba9ce..946b77f7 100644 --- a/docs/book/07-Implementation-Hiding.md +++ b/docs/book/07-Implementation-Hiding.md @@ -61,7 +61,7 @@ import java.util.* ### 代码组织 -当编译一个 **.java** 文件时,**.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为“obj”文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。 +当编译一个 **.java** 文件时, **.java** 文件的每个类都会有一个输出文件。每个输出的文件名和 **.java** 文件中每个类的类名相同,只是后缀名是 **.class**。因此,在编译少量的 **.java** 文件后,会得到大量的 **.class** 文件。如果你使用过编译型语言,那么你可能习惯编译后产生一个中间文件(通常称为“obj”文件),然后与使用链接器(创建可执行文件)或类库生成器(创建类库)产生的其他同类文件打包到一起的情况。这不是 Java 工作的方式。在 Java 中,可运行程序是一组 **.class** 文件,它们可以打包压缩成一个 Java 文档文件(JAR,使用 **jar** 文档生成器)。Java 解释器负责查找、加载和解释这些文件。 类库是一组类文件。每个源文件通常都含有一个 **public** 类和任意数量的非 **public** 类,因此每个文件都有一个 **public** 组件。如果把这些组件集中在一起,就需要使用关键字 **package**。 @@ -327,7 +327,7 @@ public class Cookie { } ``` -记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.**,Java 就不会查找当前目录。 +记住,**Cookie.java** 文件产生的类文件必须位于名为 **dessert** 的子目录中,该子目录在 **hiding** (表明本书的"封装"章节)下,它必须在 CLASSPATH 的几个目录之下。不要错误地认为 Java 总是会将当前目录视作查找行为的起点之一。如果你的 CLASSPATH 中没有 **.** ,Java 就不会查找当前目录。 现在,使用 **Cookie** 创建一个程序: ```java @@ -384,7 +384,7 @@ class Pie { } ``` -最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 **.**,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。 +最初看上去这两个文件毫不相关,但在 **Cake** 中可以创建一个 **Pie** 对象并调用它的 `f()` 方法。(注意,你的 CLASSPATH 中一定得有 **.** ,这样文件才能编译)通常会认为 **Pie** 和 `f()` 具有包访问权限,因此不能被 **Cake** 访问。它们的确具有包访问权限,这是部分正确。**Cake.java** 可以访问它们是因为它们在相同的目录中且没有给自己设定明确的包名。Java 把这样的文件看作是隶属于该目录的默认包中,因此它们为该目录中所有的其他文件都提供了包访问权限。 ### private: 你无法访问 From cf914264c6f46ba90448d399b496e17f551ab9c6 Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 30 Jan 2021 19:24:21 +0800 Subject: [PATCH 358/371] Update 02-Installing-Java-and-the-Book-Examples.md (#643) #642 --- docs/book/02-Installing-Java-and-the-Book-Examples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/02-Installing-Java-and-the-Book-Examples.md b/docs/book/02-Installing-Java-and-the-Book-Examples.md index 86f76082..e3350b02 100644 --- a/docs/book/02-Installing-Java-and-the-Book-Examples.md +++ b/docs/book/02-Installing-Java-and-the-Book-Examples.md @@ -111,7 +111,7 @@ Mac 系统自带的 Java 版本太老,为了确保本书的代码示例能被 2. 在命令行下执行下面的命令来安装 Java。 ```bash - brew cask install java + brew install java --cask ``` 当以上安装都完成后,如果你有需要,可以使用游客账户来运行本书中的代码示例。 From 20f73ef6ade215fb7b1e15159b79f9fa924dafdc Mon Sep 17 00:00:00 2001 From: Joe <736777445@qq.com> Date: Sat, 30 Jan 2021 19:31:18 +0800 Subject: [PATCH 359/371] Update 02-Installing-Java-and-the-Book-Examples.md --- docs/book/02-Installing-Java-and-the-Book-Examples.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/book/02-Installing-Java-and-the-Book-Examples.md b/docs/book/02-Installing-Java-and-the-Book-Examples.md index e3350b02..8de3ce6b 100644 --- a/docs/book/02-Installing-Java-and-the-Book-Examples.md +++ b/docs/book/02-Installing-Java-and-the-Book-Examples.md @@ -111,9 +111,11 @@ Mac 系统自带的 Java 版本太老,为了确保本书的代码示例能被 2. 在命令行下执行下面的命令来安装 Java。 ```bash - brew install java --cask + brew install --cask java ``` +> 译者注:原有命令为 `brew cask install java`。原有命令已经被禁用。具体内容可看 https://github.com/LingCoder/OnJava8/issues/642 + 当以上安装都完成后,如果你有需要,可以使用游客账户来运行本书中的代码示例。 **Linux** From 482bb3b0856cd6ea1e67e2da9e50048889830237 Mon Sep 17 00:00:00 2001 From: iao113 Date: Sat, 30 Jan 2021 22:00:57 +0800 Subject: [PATCH 360/371] Update 08-Reuse.md (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Markdown格式有误,程序输出typo --- docs/book/08-Reuse.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/book/08-Reuse.md b/docs/book/08-Reuse.md index cf27c665..a2e6bc4b 100644 --- a/docs/book/08-Reuse.md +++ b/docs/book/08-Reuse.md @@ -68,7 +68,7 @@ i = 0 f = 0.0 source = Constructed ``` -这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注解来告诉编译器,以确保正确地覆盖。**@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。 +这两个类中定义的一个方法是特殊的: `toString()`。每个非基本类型对象都有一个 `toString()` 方法,在编译器需要字符串但它有对象的特殊情况下调用该方法。因此,在 [1] 中,编译器看到你试图“添加”一个 **WaterSource** 类型的字符串对象 。因为字符串只能拼接另一个字符串,所以它就先会调用 `toString()` 将 **source** 转换成一个字符串。然后,它可以拼接这两个字符串并将结果字符串传递给 `System.out.println()`。要对创建的任何类允许这种行为,只需要编写一个 **toString()** 方法。在 `toString()` 上使用 **@Override** 注解来告诉编译器,以确保正确地覆盖。 **@Override** 是可选的,但它有助于验证你没有拼写错误 (或者更微妙地说,大小写字母输入错误)。类中的基本类型字段自动初始化为零,正如 **object Everywhere** 一章中所述。但是对象引用被初始化为 **null**,如果你尝试调用其任何一个方法,你将得到一个异常(一个运行时错误)。方便的是,打印 **null** 引用却不会得到异常。 编译器不会为每个引用创建一个默认对象,这是有意义的,因为在许多情况下,这会导致不必要的开销。初始化引用有四种方法: @@ -200,8 +200,7 @@ public class Detergent extends Cleanser { } } /* Output: -Cleanser dilute() apply() Detergent.scrub() scrub() -foam() +Cleanser dilute() apply() Detergent.scrub() scrub() foam() Testing base class: Cleanser dilute() apply() scrub() */ From f47dcab808918c4a81ab199ac8295bcd46c32df2 Mon Sep 17 00:00:00 2001 From: iao113 Date: Mon, 1 Feb 2021 18:00:33 +0800 Subject: [PATCH 361/371] Update 09-Polymorphism.md (#645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Markdown语法有误 --- docs/book/09-Polymorphism.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/09-Polymorphism.md b/docs/book/09-Polymorphism.md index ddca482d..67f50ed0 100644 --- a/docs/book/09-Polymorphism.md +++ b/docs/book/09-Polymorphism.md @@ -503,7 +503,7 @@ class Derived extends PrivateOverride { private f() ``` -你可能期望输出是 **public f()**,然而 **private** 方法可以当作是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。 +你可能期望输出是 **public f()** ,然而 **private** 方法可以当作是 **final** 的,对于派生类来说是隐蔽的。因此,这里 **Derived** 的 `f()` 是一个全新的方法;因为基类版本的 `f()` 屏蔽了 **Derived** ,因此它都不算是重写方法。 结论是只有非 **private** 方法才能被重写,但是得小心重写 **private** 方法的现象,编译器不报错,但不会按我们所预期的执行。为了清晰起见,派生类中的方法名采用与基类中 **private** 方法名不同的命名。 @@ -1236,7 +1236,7 @@ at RTTI.main 正如前面类图所示,**MoreUseful** 扩展了 **Useful** 的接口。而 **MoreUseful** 也继承了 **Useful**,所以它可以向上转型为 **Useful**。在 `main()` 方法中可以看到这种情况的发生。因为两个对象都是 **Useful** 类型,所以对它们都可以调用 `f()` 和 `g()` 方法。如果试图调用 `u()` 方法(只存在于 **MoreUseful** 中),就会得到编译时错误信息。 -为了访问 **MoreUseful** 对象的扩展接口,就得尝试向下转型。如果转型为正确的类型,就转型成功。否则,就会得到 ClassCastException 异常。你不必为这个异常编写任何特殊代码,因为它指出了程序员在程序的任何地方都可能犯的错误。**{ThrowsException}** 注释标签告知本书的构建系统:在运行程序时,预期抛出一个异常。 +为了访问 **MoreUseful** 对象的扩展接口,就得尝试向下转型。如果转型为正确的类型,就转型成功。否则,就会得到 ClassCastException 异常。你不必为这个异常编写任何特殊代码,因为它指出了程序员在程序的任何地方都可能犯的错误。 **{ThrowsException}** 注释标签告知本书的构建系统:在运行程序时,预期抛出一个异常。 RTTI 不仅仅包括简单的转型。例如,它还提供了一种方法,使你可以在试图向下转型前检查所要处理的类型。“类型信息”一章中会详细阐述运行时类型信息的方方面面。 From c0cc40224023627a51d9d03f7e8988fe8fee9f85 Mon Sep 17 00:00:00 2001 From: iao113 Date: Fri, 5 Feb 2021 11:57:06 +0800 Subject: [PATCH 362/371] Update 11-Inner-Classes.md (#647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Markdown格式修复 --- docs/book/11-Inner-Classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/book/11-Inner-Classes.md b/docs/book/11-Inner-Classes.md index 66d3d5a1..91f363a7 100755 --- a/docs/book/11-Inner-Classes.md +++ b/docs/book/11-Inner-Classes.md @@ -698,7 +698,7 @@ public class TestBed { f() ``` -这生成了一个独立的类 **TestBed$Tester**(要运行这个程序,执行 **java TestBed$Tester**,在 Unix/Linux 系统中需要转义 **$**)。你可以使用这个类测试,但是不必在发布的产品中包含它,可以在打包产品前删除 **TestBed$Tester.class**。 +这生成了一个独立的类 **TestBed$Tester**(要运行这个程序,执行 **java TestBed$Tester**,在 Unix/Linux 系统中需要转义 **$** )。你可以使用这个类测试,但是不必在发布的产品中包含它,可以在打包产品前删除 **TestBed$Tester.class**。 ### 从多层嵌套类中访问外部类的成员 @@ -1434,7 +1434,7 @@ LocalInnerClass.class 虽然这种命名格式简单而直接,但它还是很健壮的,足以应对绝大多数情况[^1]。因为这是 Java 的标准命名方式,所以产生的文件自动都是平台无关的。(注意,为了保证你的内部类能起作用,Java 编译器会尽可能地转换它们。) -[^1]: 另一方面,**$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 +[^1]: 另一方面, **$** 对Unix shell来说是一个元字符,所以当你列出.class文件时,有时会遇到麻烦。这对基于Unix的Sun公司来说有点奇怪。我的猜测是,他们没有考虑这个问题,而是认为你会很自然地关注源代码文件。 ## 本章小结 From fc86061ccf7e39dd56a960c5cad3a5b29b4d90e7 Mon Sep 17 00:00:00 2001 From: iao113 Date: Sun, 7 Feb 2021 11:52:39 +0800 Subject: [PATCH 363/371] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E8=BE=93=E5=87=BAtypo=20(#648)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update 12-Collections.md 程序输出typo * Update 12-Collections.md 程序输出typo --- docs/book/12-Collections.md | 150 ++++++++++-------------------------- 1 file changed, 42 insertions(+), 108 deletions(-) diff --git a/docs/book/12-Collections.md b/docs/book/12-Collections.md index e34688fa..a8198174 100644 --- a/docs/book/12-Collections.md +++ b/docs/book/12-Collections.md @@ -57,10 +57,8 @@ public class ApplesAndOrangesWithoutGenerics { /* Output: ___[ Error Output ]___ Exception in thread "main" -java.lang.ClassCastException: Orange cannot be cast to -Apple - at ApplesAndOrangesWithoutGenerics.main(ApplesA -ndOrangesWithoutGenerics.java:23) +java.lang.ClassCastException: Orange cannot be cast to Apple + at ApplesAndOrangesWithoutGenerics.main(ApplesAndOrangesWithoutGenerics.java:23) */ ``` @@ -178,7 +176,7 @@ public class SimpleCollection { } } /* Output: -0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +0, 1, 2, 3, 4, 5, 6, 7, 8, 9, */ ``` @@ -553,14 +551,10 @@ public class CrossCollectionIteration { } } /* Output: -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug -0:Rat +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat */ ``` @@ -594,14 +588,10 @@ public class CrossCollectionIteration2 { } } /* Output: -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug -0:Rat +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +5:Cymric 2:Cymric 7:Manx 1:Manx 3:Mutt 6:Pug 4:Pug 0:Rat */ ``` @@ -640,12 +630,10 @@ public class ListIteration { } } /* Output: -Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, -5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7; +Rat, 1, 0; Manx, 2, 1; Cymric, 3, 2; Mutt, 4, 3; Pug, 5, 4; Cymric, 6, 5; Pug, 7, 6; Manx, 8, 7; 7 6 5 4 3 2 1 0 [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx] -[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, -EgyptianMau] +[Rat, Manx, Cymric, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau] */ ``` @@ -854,8 +842,7 @@ public class SetOfInteger { } } /* Output: -[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] */ ``` @@ -952,8 +939,7 @@ set2 in set1: true set1: [A, B, C, D, E, F, G, I, J, K, L, M] set2 in set1: false set2 removed from set1: [A, B, C, D, E, F, G, M] -'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y, -Z] +'X Y Z' added to set1: [A, B, C, D, E, F, G, M, X, Y, Z] */ ``` @@ -980,13 +966,7 @@ public class UniqueWords { } } /* Output: -[A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K, -L, M, N, Output, Set, SetOperations, String, System, X, -Y, Z, add, addAll, added, args, class, collections, -contains, containsAll, false, from, import, in, java, -main, new, out, println, public, remove, removeAll, -removed, set1, set2, split, static, to, true, util, -void] +[A, B, C, Collections, D, E, F, G, H, HashSet, I, J, K, L, M, N, Output, Set, SetOperations, String, System, X, Y, Z, add, addAll, added, args, class, collections, contains, containsAll, false, from, import, in, java, main, new, out, println, public, remove, removeAll, removed, set1, set2, split, static, to, true, util, void] */ ``` @@ -1013,12 +993,7 @@ public class UniqueWordsAlphabetic { } } /* Output: -[A, add, addAll, added, args, B, C, class, collections, -contains, containsAll, D, E, F, false, from, G, H, -HashSet, I, import, in, J, java, K, L, M, main, N, new, -out, Output, println, public, remove, removeAll, -removed, Set, set1, set2, SetOperations, split, static, -String, System, to, true, util, void, X, Y, Z] +[A, add, addAll, added, args, B, C, class, collections, contains, containsAll, D, E, F, false, from, G, H, HashSet, I, import, in, J, java, K, L, M, main, N, new, out, Output, println, public, remove, removeAll, removed, Set, set1, set2, SetOperations, split, static, String, System, to, true, util, void, X, Y, Z] */ ``` @@ -1051,9 +1026,7 @@ public class Statistics { } } /* Output: -{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, -7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, -14=477, 15=497, 16=533, 17=509, 18=478, 19=464} +{0=481, 1=502, 2=489, 3=508, 4=481, 5=503, 6=519, 7=471, 8=468, 9=549, 10=513, 11=531, 12=521, 13=506, 14=477, 15=497, 16=533, 17=509, 18=478, 19=464} */ ``` @@ -1315,21 +1288,14 @@ public class InterfaceVsIterator { } } /* Output: -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt, -Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx} +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +{Ralph=Rat, Eric=Manx, Robin=Cymric, Lacey=Mutt, Britney=Pug, Sam=Cymric, Spot=Pug, Fluffy=Manx} [Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffy] -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx */ ``` @@ -1372,10 +1338,8 @@ extends AbstractCollection { } } /* Output: -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx */ ``` @@ -1417,8 +1381,7 @@ public class NonCollectionSequence extends PetSequence { } } /* Output: -0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug -7:Manx +0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx */ ``` @@ -1729,64 +1692,35 @@ public class CollectionDifferences { } } /* Output: -Collection: [add, addAll, clear, contains, containsAll, -equals, forEach, hashCode, isEmpty, iterator, -parallelStream, remove, removeAll, removeIf, retainAll, -size, spliterator, stream, toArray] +Collection: [add, addAll, clear, contains, containsAll, equals, forEach, hashCode, isEmpty, iterator, parallelStream, remove, removeAll, removeIf, retainAll, size, spliterator, stream, toArray] Interfaces in Collection: [Iterable] Set extends Collection, adds: [] Interfaces in Set: [Collection] HashSet extends Set, adds: [] Interfaces in HashSet: [Set, Cloneable, Serializable] LinkedHashSet extends HashSet, adds: [] -Interfaces in LinkedHashSet: [Set, Cloneable, -Serializable] -TreeSet extends Set, adds: [headSet, -descendingIterator, descendingSet, pollLast, subSet, -floor, tailSet, ceiling, last, lower, comparator, -pollFirst, first, higher] -Interfaces in TreeSet: [NavigableSet, Cloneable, -Serializable] -List extends Collection, adds: [replaceAll, get, -indexOf, subList, set, sort, lastIndexOf, listIterator] +Interfaces in LinkedHashSet: [Set, Cloneable, Serializable] +TreeSet extends Set, adds: [headSet, descendingIterator, descendingSet, pollLast, subSet, floor, tailSet, ceiling, last, lower, comparator, pollFirst, first, higher] +Interfaces in TreeSet: [NavigableSet, Cloneable, Serializable] +List extends Collection, adds: [replaceAll, get, indexOf, subList, set, sort, lastIndexOf, listIterator] Interfaces in List: [Collection] -ArrayList extends List, adds: [trimToSize, -ensureCapacity] -Interfaces in ArrayList: [List, RandomAccess, -Cloneable, Serializable] -LinkedList extends List, adds: [offerFirst, poll, -getLast, offer, getFirst, removeFirst, element, -removeLastOccurrence, peekFirst, peekLast, push, -pollFirst, removeFirstOccurrence, descendingIterator, -pollLast, removeLast, pop, addLast, peek, offerLast, -addFirst] -Interfaces in LinkedList: [List, Deque, Cloneable, -Serializable] -Queue extends Collection, adds: [poll, peek, offer, -element] +ArrayList extends List, adds: [trimToSize, ensureCapacity] +Interfaces in ArrayList: [List, RandomAccess, Cloneable, Serializable] +LinkedList extends List, adds: [offerFirst, poll, getLast, offer, getFirst, removeFirst, element, removeLastOccurrence, peekFirst, peekLast, push, pollFirst, removeFirstOccurrence, descendingIterator, pollLast, removeLast, pop, addLast, peek, offerLast, addFirst] +Interfaces in LinkedList: [List, Deque, Cloneable, Serializable] +Queue extends Collection, adds: [poll, peek, offer, element] Interfaces in Queue: [Collection] PriorityQueue extends Queue, adds: [comparator] Interfaces in PriorityQueue: [Serializable] -Map: [clear, compute, computeIfAbsent, -computeIfPresent, containsKey, containsValue, entrySet, -equals, forEach, get, getOrDefault, hashCode, isEmpty, -keySet, merge, put, putAll, putIfAbsent, remove, -replace, replaceAll, size, values] +Map: [clear, compute, computeIfAbsent, computeIfPresent, containsKey, containsValue, entrySet, equals, forEach, get, getOrDefault, hashCode, isEmpty, keySet, merge, put, putAll, putIfAbsent, remove, replace, replaceAll, size, values] HashMap extends Map, adds: [] Interfaces in HashMap: [Map, Cloneable, Serializable] LinkedHashMap extends HashMap, adds: [] Interfaces in LinkedHashMap: [Map] -SortedMap extends Map, adds: [lastKey, subMap, -comparator, firstKey, headMap, tailMap] +SortedMap extends Map, adds: [lastKey, subMap, comparator, firstKey, headMap, tailMap] Interfaces in SortedMap: [Map] -TreeMap extends Map, adds: [descendingKeySet, -navigableKeySet, higherEntry, higherKey, floorKey, -subMap, ceilingKey, pollLastEntry, firstKey, lowerKey, -headMap, tailMap, lowerEntry, ceilingEntry, -descendingMap, pollFirstEntry, lastKey, firstEntry, -floorEntry, comparator, lastEntry] -Interfaces in TreeMap: [NavigableMap, Cloneable, -Serializable] +TreeMap extends Map, adds: [descendingKeySet, navigableKeySet, higherEntry, higherKey, floorKey, subMap, ceilingKey, pollLastEntry, firstKey, lowerKey, headMap, tailMap, lowerEntry, ceilingEntry, descendingMap, pollFirstEntry, lastKey, firstEntry, floorEntry, comparator, lastEntry] +Interfaces in TreeMap: [NavigableMap, Cloneable, Serializable] */ ``` From 13038c489acdf8eda29e100f614028865cca4ee5 Mon Sep 17 00:00:00 2001 From: Guan Date: Thu, 25 Feb 2021 12:30:00 +0800 Subject: [PATCH 364/371] =?UTF-8?q?fix:=20=E7=AC=AC=E4=B8=89=E7=AB=A0=20ma?= =?UTF-8?q?rkdown=20=E8=AF=AD=E6=B3=95=20(#649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/03-Objects-Everywhere.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/03-Objects-Everywhere.md b/docs/book/03-Objects-Everywhere.md index 1c91e4b1..7c143f81 100644 --- a/docs/book/03-Objects-Everywhere.md +++ b/docs/book/03-Objects-Everywhere.md @@ -487,7 +487,7 @@ public class HelloDate { 选择 `java.lang`,你会看到该库中所有类的列表。由于 `java.lang` 隐式包含在每个 Java 代码文件中,因此这些类是自动可用的。`java.lang` 类库中没有 **Date** 类,所以我们必须导入其他的类库(即 Date 所在的类库)。如果你不清楚某个类所在的类库或者想查看类库中所有的类,那么可以在 Java 文档中选择 “Tree” 查看。 -现在,我们可以找到 Java 附带的每个类。使用浏览器的“查找”功能查找 **Date**,搜索结果中将会列出 **java.util.Date**,我们就知道了 **Date** 在 **util** 库中,所以必须导入 **java.util.*** 才能使用 **Date**。 +现在,我们可以找到 Java 附带的每个类。使用浏览器的“查找”功能查找 **Date**,搜索结果中将会列出 **java.util.Date**,我们就知道了 **Date** 在 **util** 库中,所以必须导入 **java.util.\*** 才能使用 **Date**。 如果你在文档中选择 **java.lang**,然后选择 **System**,你会看到 **System** 类中有几个字段,如果你选择了 **out**,你会发现它是一个静态的 **PrintStream** 对象。 所以,即使我们不使用 **new** 创建, **out** 对象就已经存在并可以使用。 **out** 对象可以执行的操作取决于它的类型: **PrintStream** ,其在文档中是一个超链接,如果单击该链接,我们将可以看到 **PrintStream** 对应的方法列表(更多详情,将在本书后面介绍)。 现在我们重点说的是 **println()** 这个方法。 它的作用是 “将信息输出到控制台,并以换行符结束”。既然如此,我们可以这样编码来输出信息到控制台。 代码示例: From 1ef7ec48e492862300e667e24c245e9b3a5ccd98 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Fri, 26 Feb 2021 15:46:06 +0800 Subject: [PATCH 365/371] Update 24-Concurrent-Programming.md (#651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更正翻译错误,"call" -> "调用了" --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 67de4a97..0703d281 100755 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -902,7 +902,7 @@ NapTask[8] pool-1-thread-1 NapTask[9] pool-1-thread-1 ``` -一旦你 callexec.shutdown(),尝试提交新任务将抛出 RejectedExecutionException。 +一旦你调用了 exec.shutdown(),尝试提交新任务将抛出 RejectedExecutionException。 ```java // concurrent/MoreTasksAfterShutdown.java From 3b0acf3d84d25774de79638a9c11ac904da3d6e9 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 26 Feb 2021 23:38:17 +0800 Subject: [PATCH 366/371] =?UTF-8?q?=E6=B8=85=E7=A9=BA=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 45 - README.md | 108 +- SUMMARY.md | 448 -- book.json | 31 - cover.jpg | Bin 117331 -> 0 bytes cover_small.jpg | Bin 73325 -> 0 bytes docs/.nojekyll | 0 docs/README.md | 43 - docs/_coverpage.md | 25 - docs/_style/prism-master/.editorconfig | 14 - docs/_style/prism-master/.gitattributes | 4 - docs/_style/prism-master/.gitignore | 4 - docs/_style/prism-master/.npmignore | 27 - docs/_style/prism-master/.travis.yml | 23 - docs/_style/prism-master/CHANGELOG.md | 1334 ----- docs/_style/prism-master/CNAME | 1 - docs/_style/prism-master/LICENSE | 21 - docs/_style/prism-master/README.md | 30 - docs/_style/prism-master/bower.json | 30 - docs/_style/prism-master/code.js | 213 - docs/_style/prism-master/components.js | 2 - docs/_style/prism-master/components.json | 913 --- docs/_style/prism-master/components/index.js | 82 - .../prism-master/components/prism-abap.js | 48 - .../prism-master/components/prism-abap.min.js | 1 - .../components/prism-actionscript.js | 17 - .../components/prism-actionscript.min.js | 1 - .../prism-master/components/prism-ada.js | 19 - .../prism-master/components/prism-ada.min.js | 1 - .../components/prism-apacheconf.js | 47 - .../components/prism-apacheconf.min.js | 1 - .../prism-master/components/prism-apl.js | 32 - .../prism-master/components/prism-apl.min.js | 1 - .../components/prism-applescript.js | 20 - .../components/prism-applescript.min.js | 1 - .../prism-master/components/prism-arduino.js | 5 - .../components/prism-arduino.min.js | 1 - .../prism-master/components/prism-arff.js | 10 - .../prism-master/components/prism-arff.min.js | 1 - .../prism-master/components/prism-asciidoc.js | 271 - .../components/prism-asciidoc.min.js | 1 - .../prism-master/components/prism-asm6502.js | 28 - .../components/prism-asm6502.min.js | 1 - .../prism-master/components/prism-aspnet.js | 36 - .../components/prism-aspnet.min.js | 1 - .../components/prism-autohotkey.js | 27 - .../components/prism-autohotkey.min.js | 1 - .../prism-master/components/prism-autoit.js | 34 - .../components/prism-autoit.min.js | 1 - .../prism-master/components/prism-bash.js | 84 - .../prism-master/components/prism-bash.min.js | 1 - .../prism-master/components/prism-basic.js | 17 - .../components/prism-basic.min.js | 1 - .../prism-master/components/prism-batch.js | 99 - .../components/prism-batch.min.js | 1 - .../prism-master/components/prism-bison.js | 39 - .../components/prism-bison.min.js | 1 - .../components/prism-brainfuck.js | 20 - .../components/prism-brainfuck.min.js | 1 - .../prism-master/components/prism-bro.js | 48 - .../prism-master/components/prism-bro.min.js | 1 - .../_style/prism-master/components/prism-c.js | 33 - .../prism-master/components/prism-c.min.js | 1 - .../prism-master/components/prism-cil.js | 27 - .../prism-master/components/prism-cil.min.js | 1 - .../prism-master/components/prism-clike.js | 30 - .../components/prism-clike.min.js | 1 - .../prism-master/components/prism-clojure.js | 13 - .../components/prism-clojure.min.js | 1 - .../components/prism-coffeescript.js | 92 - .../components/prism-coffeescript.min.js | 1 - .../prism-master/components/prism-core.js | 548 -- .../prism-master/components/prism-core.min.js | 1 - .../prism-master/components/prism-cpp.js | 20 - .../prism-master/components/prism-cpp.min.js | 1 - .../prism-master/components/prism-crystal.js | 51 - .../components/prism-crystal.min.js | 1 - .../prism-master/components/prism-csharp.js | 81 - .../components/prism-csharp.min.js | 1 - .../prism-master/components/prism-csp.js | 25 - .../prism-master/components/prism-csp.min.js | 1 - .../components/prism-css-extras.js | 31 - .../components/prism-css-extras.min.js | 1 - .../prism-master/components/prism-css.js | 52 - .../prism-master/components/prism-css.min.js | 1 - .../_style/prism-master/components/prism-d.js | 64 - .../prism-master/components/prism-d.min.js | 1 - .../prism-master/components/prism-dart.js | 24 - .../prism-master/components/prism-dart.min.js | 1 - .../prism-master/components/prism-diff.js | 20 - .../prism-master/components/prism-diff.min.js | 1 - .../prism-master/components/prism-django.js | 41 - .../components/prism-django.min.js | 1 - .../prism-master/components/prism-docker.js | 11 - .../components/prism-docker.min.js | 1 - .../prism-master/components/prism-eiffel.js | 37 - .../components/prism-eiffel.min.js | 1 - .../prism-master/components/prism-elixir.js | 93 - .../components/prism-elixir.min.js | 1 - .../prism-master/components/prism-elm.js | 44 - .../prism-master/components/prism-elm.min.js | 1 - .../prism-master/components/prism-erb.js | 20 - .../prism-master/components/prism-erb.min.js | 1 - .../prism-master/components/prism-erlang.js | 44 - .../components/prism-erlang.min.js | 1 - .../prism-master/components/prism-flow.js | 35 - .../prism-master/components/prism-flow.min.js | 1 - .../prism-master/components/prism-fortran.js | 40 - .../components/prism-fortran.min.js | 1 - .../prism-master/components/prism-fsharp.js | 67 - .../components/prism-fsharp.min.js | 1 - .../prism-master/components/prism-gcode.js | 15 - .../components/prism-gcode.min.js | 1 - .../prism-master/components/prism-gedcom.js | 28 - .../components/prism-gedcom.min.js | 1 - .../prism-master/components/prism-gherkin.js | 79 - .../components/prism-gherkin.min.js | 1 - .../prism-master/components/prism-git.js | 68 - .../prism-master/components/prism-git.min.js | 1 - .../prism-master/components/prism-glsl.js | 16 - .../prism-master/components/prism-glsl.min.js | 1 - .../prism-master/components/prism-gml.js | 7 - .../prism-master/components/prism-gml.min.js | 1 - .../prism-master/components/prism-go.js | 12 - .../prism-master/components/prism-go.min.js | 1 - .../prism-master/components/prism-graphql.js | 24 - .../components/prism-graphql.min.js | 1 - .../prism-master/components/prism-groovy.js | 65 - .../components/prism-groovy.min.js | 1 - .../prism-master/components/prism-haml.js | 154 - .../prism-master/components/prism-haml.min.js | 1 - .../components/prism-handlebars.js | 37 - .../components/prism-handlebars.min.js | 1 - .../prism-master/components/prism-haskell.js | 36 - .../components/prism-haskell.min.js | 1 - .../prism-master/components/prism-haxe.js | 45 - .../prism-master/components/prism-haxe.min.js | 1 - .../prism-master/components/prism-hpkp.js | 20 - .../prism-master/components/prism-hpkp.min.js | 1 - .../prism-master/components/prism-hsts.js | 20 - .../prism-master/components/prism-hsts.min.js | 1 - .../prism-master/components/prism-http.js | 79 - .../prism-master/components/prism-http.min.js | 1 - .../components/prism-ichigojam.js | 15 - .../components/prism-ichigojam.min.js | 1 - .../prism-master/components/prism-icon.js | 20 - .../prism-master/components/prism-icon.min.js | 1 - .../prism-master/components/prism-inform7.js | 61 - .../components/prism-inform7.min.js | 1 - .../prism-master/components/prism-ini.js | 11 - .../prism-master/components/prism-ini.min.js | 1 - .../prism-master/components/prism-io.js | 31 - .../prism-master/components/prism-io.min.js | 1 - .../_style/prism-master/components/prism-j.js | 25 - .../prism-master/components/prism-j.min.js | 1 - .../prism-master/components/prism-java.js | 54 - .../prism-master/components/prism-java.min.js | 1 - .../components/prism-javascript.js | 91 - .../components/prism-javascript.min.js | 1 - .../components/prism-javastacktrace.js | 93 - .../components/prism-javastacktrace.min.js | 1 - .../prism-master/components/prism-jolie.js | 55 - .../components/prism-jolie.min.js | 1 - .../prism-master/components/prism-json.js | 18 - .../prism-master/components/prism-json.min.js | 1 - .../prism-master/components/prism-jsx.js | 126 - .../prism-master/components/prism-jsx.min.js | 1 - .../prism-master/components/prism-julia.js | 12 - .../components/prism-julia.min.js | 1 - .../prism-master/components/prism-keyman.js | 14 - .../components/prism-keyman.min.js | 1 - .../prism-master/components/prism-kotlin.js | 62 - .../components/prism-kotlin.min.js | 1 - .../prism-master/components/prism-latex.js | 61 - .../components/prism-latex.min.js | 1 - .../prism-master/components/prism-less.js | 54 - .../prism-master/components/prism-less.min.js | 1 - .../prism-master/components/prism-liquid.js | 12 - .../components/prism-liquid.min.js | 1 - .../prism-master/components/prism-lisp.js | 197 - .../prism-master/components/prism-lisp.min.js | 1 - .../components/prism-livescript.js | 119 - .../components/prism-livescript.min.js | 1 - .../prism-master/components/prism-lolcode.js | 55 - .../components/prism-lolcode.min.js | 1 - .../prism-master/components/prism-lua.js | 20 - .../prism-master/components/prism-lua.min.js | 1 - .../prism-master/components/prism-makefile.js | 34 - .../components/prism-makefile.min.js | 1 - .../prism-master/components/prism-markdown.js | 229 - .../components/prism-markdown.min.js | 1 - .../components/prism-markup-templating.js | 89 - .../components/prism-markup-templating.min.js | 1 - .../prism-master/components/prism-markup.js | 56 - .../components/prism-markup.min.js | 1 - .../prism-master/components/prism-matlab.js | 16 - .../components/prism-matlab.min.js | 1 - .../prism-master/components/prism-mel.js | 43 - .../prism-master/components/prism-mel.min.js | 1 - .../prism-master/components/prism-mizar.js | 12 - .../components/prism-mizar.min.js | 1 - .../prism-master/components/prism-monkey.js | 31 - .../components/prism-monkey.min.js | 1 - .../prism-master/components/prism-n4js.js | 14 - .../prism-master/components/prism-n4js.min.js | 1 - .../prism-master/components/prism-nasm.js | 24 - .../prism-master/components/prism-nasm.min.js | 1 - .../prism-master/components/prism-nginx.js | 11 - .../components/prism-nginx.min.js | 1 - .../prism-master/components/prism-nim.js | 33 - .../prism-master/components/prism-nim.min.js | 1 - .../prism-master/components/prism-nix.js | 40 - .../prism-master/components/prism-nix.min.js | 1 - .../prism-master/components/prism-nsis.js | 29 - .../prism-master/components/prism-nsis.min.js | 1 - .../components/prism-objectivec.js | 5 - .../components/prism-objectivec.min.js | 1 - .../prism-master/components/prism-ocaml.js | 27 - .../components/prism-ocaml.min.js | 1 - .../prism-master/components/prism-opencl.js | 49 - .../components/prism-opencl.min.js | 1 - .../prism-master/components/prism-oz.js | 25 - .../prism-master/components/prism-oz.min.js | 1 - .../prism-master/components/prism-parigp.js | 30 - .../components/prism-parigp.min.js | 1 - .../prism-master/components/prism-parser.js | 67 - .../components/prism-parser.min.js | 1 - .../prism-master/components/prism-pascal.js | 55 - .../components/prism-pascal.min.js | 1 - .../prism-master/components/prism-perl.js | 191 - .../prism-master/components/prism-perl.min.js | 1 - .../components/prism-php-extras.js | 11 - .../components/prism-php-extras.min.js | 1 - .../prism-master/components/prism-php.js | 122 - .../prism-master/components/prism-php.min.js | 1 - .../prism-master/components/prism-plsql.js | 20 - .../components/prism-plsql.min.js | 1 - .../components/prism-powershell.js | 55 - .../components/prism-powershell.min.js | 1 - .../components/prism-processing.js | 18 - .../components/prism-processing.min.js | 1 - .../prism-master/components/prism-prolog.js | 20 - .../components/prism-prolog.min.js | 1 - .../components/prism-properties.js | 9 - .../components/prism-properties.min.js | 1 - .../prism-master/components/prism-protobuf.js | 8 - .../components/prism-protobuf.min.js | 1 - .../prism-master/components/prism-pug.js | 198 - .../prism-master/components/prism-pug.min.js | 1 - .../prism-master/components/prism-puppet.js | 136 - .../components/prism-puppet.min.js | 1 - .../prism-master/components/prism-pure.js | 81 - .../prism-master/components/prism-pure.min.js | 1 - .../prism-master/components/prism-python.js | 64 - .../components/prism-python.min.js | 1 - .../_style/prism-master/components/prism-q.js | 51 - .../prism-master/components/prism-q.min.js | 1 - .../prism-master/components/prism-qore.js | 20 - .../prism-master/components/prism-qore.min.js | 1 - .../_style/prism-master/components/prism-r.js | 22 - .../prism-master/components/prism-r.min.js | 1 - .../prism-master/components/prism-reason.js | 32 - .../components/prism-reason.min.js | 1 - .../prism-master/components/prism-renpy.js | 29 - .../components/prism-renpy.min.js | 1 - .../prism-master/components/prism-rest.js | 205 - .../prism-master/components/prism-rest.min.js | 1 - .../prism-master/components/prism-rip.js | 32 - .../prism-master/components/prism-rip.min.js | 1 - .../prism-master/components/prism-roboconf.js | 27 - .../components/prism-roboconf.min.js | 1 - .../prism-master/components/prism-ruby.js | 143 - .../prism-master/components/prism-ruby.min.js | 1 - .../prism-master/components/prism-rust.js | 68 - .../prism-master/components/prism-rust.min.js | 1 - .../prism-master/components/prism-sas.js | 34 - .../prism-master/components/prism-sas.min.js | 1 - .../prism-master/components/prism-sass.js | 72 - .../prism-master/components/prism-sass.min.js | 1 - .../prism-master/components/prism-scala.js | 18 - .../components/prism-scala.min.js | 1 - .../prism-master/components/prism-scheme.js | 33 - .../components/prism-scheme.min.js | 1 - .../prism-master/components/prism-scss.js | 74 - .../prism-master/components/prism-scss.min.js | 1 - .../components/prism-smalltalk.js | 31 - .../components/prism-smalltalk.min.js | 1 - .../prism-master/components/prism-smarty.js | 96 - .../components/prism-smarty.min.js | 1 - .../prism-master/components/prism-soy.js | 96 - .../prism-master/components/prism-soy.min.js | 1 - .../prism-master/components/prism-sql.js | 24 - .../prism-master/components/prism-sql.min.js | 1 - .../prism-master/components/prism-stylus.js | 111 - .../components/prism-stylus.min.js | 1 - .../prism-master/components/prism-swift.js | 25 - .../components/prism-swift.min.js | 1 - .../prism-master/components/prism-tap.js | 20 - .../prism-master/components/prism-tap.min.js | 1 - .../prism-master/components/prism-tcl.js | 46 - .../prism-master/components/prism-tcl.min.js | 1 - .../prism-master/components/prism-textile.js | 257 - .../components/prism-textile.min.js | 1 - .../prism-master/components/prism-toml.js | 43 - .../prism-master/components/prism-toml.min.js | 1 - .../prism-master/components/prism-tsx.js | 2 - .../prism-master/components/prism-tsx.min.js | 1 - .../prism-master/components/prism-tt2.js | 56 - .../prism-master/components/prism-tt2.min.js | 1 - .../prism-master/components/prism-twig.js | 46 - .../prism-master/components/prism-twig.min.js | 1 - .../components/prism-typescript.js | 7 - .../components/prism-typescript.min.js | 1 - .../prism-master/components/prism-vala.js | 74 - .../prism-master/components/prism-vala.min.js | 1 - .../prism-master/components/prism-vbnet.js | 15 - .../components/prism-vbnet.min.js | 1 - .../prism-master/components/prism-velocity.js | 72 - .../components/prism-velocity.min.js | 1 - .../prism-master/components/prism-verilog.js | 20 - .../components/prism-verilog.min.js | 1 - .../prism-master/components/prism-vhdl.js | 23 - .../prism-master/components/prism-vhdl.min.js | 1 - .../prism-master/components/prism-vim.js | 10 - .../prism-master/components/prism-vim.min.js | 1 - .../components/prism-visual-basic.js | 34 - .../components/prism-visual-basic.min.js | 1 - .../prism-master/components/prism-wasm.js | 31 - .../prism-master/components/prism-wasm.min.js | 1 - .../prism-master/components/prism-wiki.js | 81 - .../prism-master/components/prism-wiki.min.js | 1 - .../prism-master/components/prism-xeora.js | 114 - .../components/prism-xeora.min.js | 1 - .../prism-master/components/prism-xojo.js | 20 - .../prism-master/components/prism-xojo.min.js | 1 - .../prism-master/components/prism-xquery.js | 164 - .../components/prism-xquery.min.js | 1 - .../prism-master/components/prism-yaml.js | 47 - .../prism-master/components/prism-yaml.min.js | 1 - docs/_style/prism-master/composer.json | 20 - docs/_style/prism-master/download.html | 193 - docs/_style/prism-master/download.js | 598 -- docs/_style/prism-master/examples.html | 110 - docs/_style/prism-master/examples.js | 217 - .../prism-master/examples/prism-abap.html | 65 - .../examples/prism-actionscript.html | 133 - .../prism-master/examples/prism-ada.html | 35 - .../examples/prism-apacheconf.html | 54 - .../prism-master/examples/prism-apl.html | 26 - .../examples/prism-applescript.html | 41 - .../prism-master/examples/prism-arduino.html | 63 - .../prism-master/examples/prism-arff.html | 46 - .../prism-master/examples/prism-asciidoc.html | 104 - .../prism-master/examples/prism-asm6502.html | 39 - .../prism-master/examples/prism-aspnet.html | 36 - .../examples/prism-autohotkey.html | 68 - .../prism-master/examples/prism-autoit.html | 52 - .../prism-master/examples/prism-bash.html | 49 - .../prism-master/examples/prism-basic.html | 69 - .../prism-master/examples/prism-batch.html | 17 - .../prism-master/examples/prism-bison.html | 104 - .../examples/prism-brainfuck.html | 37 - .../prism-master/examples/prism-bro.html | 645 -- .../_style/prism-master/examples/prism-c.html | 22 - .../prism-master/examples/prism-clike.html | 28 - .../prism-master/examples/prism-clojure.html | 386 -- .../examples/prism-coffeescript.html | 61 - .../prism-master/examples/prism-cpp.html | 61 - .../prism-master/examples/prism-crystal.html | 16 - .../prism-master/examples/prism-csharp.html | 60 - .../prism-master/examples/prism-csp.html | 13 - .../prism-master/examples/prism-css.html | 34 - .../_style/prism-master/examples/prism-d.html | 267 - .../prism-master/examples/prism-dart.html | 59 - .../prism-master/examples/prism-diff.html | 33 - .../prism-master/examples/prism-django.html | 31 - .../prism-master/examples/prism-docker.html | 49 - .../prism-master/examples/prism-eiffel.html | 72 - .../prism-master/examples/prism-elixir.html | 462 -- .../prism-master/examples/prism-elm.html | 91 - .../prism-master/examples/prism-erb.html | 22 - .../prism-master/examples/prism-erlang.html | 47 - .../prism-master/examples/prism-flow.html | 18 - .../prism-master/examples/prism-fortran.html | 71 - .../prism-master/examples/prism-fsharp.html | 89 - .../prism-master/examples/prism-gcode.html | 22 - .../prism-master/examples/prism-gedcom.html | 50 - .../prism-master/examples/prism-gherkin.html | 74 - .../prism-master/examples/prism-git.html | 39 - .../prism-master/examples/prism-glsl.html | 65 - .../prism-master/examples/prism-gml.html | 29 - .../prism-master/examples/prism-go.html | 68 - .../prism-master/examples/prism-graphql.html | 31 - .../prism-master/examples/prism-groovy.html | 93 - .../prism-master/examples/prism-haml.html | 79 - .../examples/prism-handlebars.html | 41 - .../prism-master/examples/prism-haskell.html | 80 - .../prism-master/examples/prism-haxe.html | 37 - .../prism-master/examples/prism-hpkp.html | 11 - .../prism-master/examples/prism-hsts.html | 8 - .../prism-master/examples/prism-http.html | 33 - .../examples/prism-ichigojam.html | 29 - .../prism-master/examples/prism-icon.html | 172 - .../prism-master/examples/prism-inform7.html | 171 - .../prism-master/examples/prism-ini.html | 10 - .../prism-master/examples/prism-io.html | 31 - .../_style/prism-master/examples/prism-j.html | 59 - .../prism-master/examples/prism-java.html | 65 - .../examples/prism-javascript.html | 77 - .../examples/prism-javastacktrace.html | 63 - .../prism-master/examples/prism-jolie.html | 162 - .../prism-master/examples/prism-jsx.html | 18 - .../prism-master/examples/prism-julia.html | 29 - .../prism-master/examples/prism-keyman.html | 107 - .../prism-master/examples/prism-kotlin.html | 134 - .../prism-master/examples/prism-latex.html | 12 - .../prism-master/examples/prism-less.html | 70 - .../prism-master/examples/prism-liquid.html | 75 - .../prism-master/examples/prism-lisp.html | 46 - .../examples/prism-livescript.html | 84 - .../prism-master/examples/prism-lolcode.html | 62 - .../prism-master/examples/prism-lua.html | 89 - .../prism-master/examples/prism-makefile.html | 263 - .../prism-master/examples/prism-markdown.html | 86 - .../prism-master/examples/prism-markup.html | 77 - .../prism-master/examples/prism-matlab.html | 52 - .../prism-master/examples/prism-mel.html | 137 - .../prism-master/examples/prism-mizar.html | 45 - .../prism-master/examples/prism-monkey.html | 74 - .../prism-master/examples/prism-n4js.html | 114 - .../prism-master/examples/prism-nasm.html | 74 - .../prism-master/examples/prism-nginx.html | 25 - .../prism-master/examples/prism-nim.html | 222 - .../prism-master/examples/prism-nix.html | 46 - .../prism-master/examples/prism-nsis.html | 18 - .../examples/prism-objectivec.html | 44 - .../prism-master/examples/prism-ocaml.html | 59 - .../prism-master/examples/prism-opencl.html | 83 - .../prism-master/examples/prism-oz.html | 89 - .../prism-master/examples/prism-parigp.html | 20 - .../prism-master/examples/prism-parser.html | 88 - .../prism-master/examples/prism-pascal.html | 65 - .../prism-master/examples/prism-perl.html | 71 - .../prism-master/examples/prism-php.html | 67 - .../prism-master/examples/prism-plsql.html | 40 - .../examples/prism-powershell.html | 19 - .../examples/prism-processing.html | 173 - .../prism-master/examples/prism-prolog.html | 44 - .../examples/prism-properties.html | 9 - .../prism-master/examples/prism-pug.html | 85 - .../prism-master/examples/prism-puppet.html | 152 - .../prism-master/examples/prism-pure.html | 115 - .../prism-master/examples/prism-python.html | 61 - .../_style/prism-master/examples/prism-q.html | 112 - .../prism-master/examples/prism-qore.html | 962 --- .../_style/prism-master/examples/prism-r.html | 38 - .../prism-master/examples/prism-reason.html | 35 - .../prism-master/examples/prism-renpy.html | 123 - .../prism-master/examples/prism-rest.html | 329 -- .../prism-master/examples/prism-rip.html | 12 - .../prism-master/examples/prism-roboconf.html | 49 - .../prism-master/examples/prism-ruby.html | 30 - .../prism-master/examples/prism-rust.html | 68 - .../prism-master/examples/prism-sas.html | 158 - .../prism-master/examples/prism-sass.html | 47 - .../prism-master/examples/prism-scala.html | 100 - .../prism-master/examples/prism-scheme.html | 35 - .../prism-master/examples/prism-scss.html | 31 - .../examples/prism-smalltalk.html | 92 - .../prism-master/examples/prism-smarty.html | 81 - .../prism-master/examples/prism-soy.html | 36 - .../prism-master/examples/prism-sql.html | 34 - .../prism-master/examples/prism-stylus.html | 72 - .../prism-master/examples/prism-swift.html | 80 - .../prism-master/examples/prism-tcl.html | 26 - .../prism-master/examples/prism-textile.html | 178 - .../prism-master/examples/prism-tsx.html | 31 - .../prism-master/examples/prism-tt2.html | 61 - .../prism-master/examples/prism-twig.html | 35 - .../examples/prism-typescript.html | 28 - .../prism-master/examples/prism-vala.html | 33 - .../prism-master/examples/prism-vbnet.html | 16 - .../prism-master/examples/prism-velocity.html | 47 - .../prism-master/examples/prism-verilog.html | 103 - .../prism-master/examples/prism-vhdl.html | 92 - .../prism-master/examples/prism-vim.html | 25 - .../examples/prism-visual-basic.html | 36 - .../prism-master/examples/prism-wasm.html | 43 - .../prism-master/examples/prism-wiki.html | 165 - .../prism-master/examples/prism-xeora.html | 111 - .../prism-master/examples/prism-xojo.html | 63 - .../prism-master/examples/prism-xquery.html | 47 - .../prism-master/examples/prism-yaml.html | 107 - docs/_style/prism-master/extending.html | 247 - docs/_style/prism-master/faq.html | 182 - docs/_style/prism-master/favicon.png | Bin 209 -> 0 bytes docs/_style/prism-master/gulpfile.js | 143 - docs/_style/prism-master/img/logo-ala.png | Bin 1745 -> 0 bytes .../prism-master/img/logo-css-tricks.png | Bin 1735 -> 0 bytes docs/_style/prism-master/img/logo-drupal.png | Bin 2371 -> 0 bytes docs/_style/prism-master/img/logo-mdn.png | Bin 6677 -> 0 bytes docs/_style/prism-master/img/logo-react.png | Bin 4705 -> 0 bytes .../prism-master/img/logo-sitepoint.png | Bin 2703 -> 0 bytes .../_style/prism-master/img/logo-smashing.png | Bin 14160 -> 0 bytes docs/_style/prism-master/img/logo-stripe.png | Bin 3377 -> 0 bytes docs/_style/prism-master/img/spectrum.png | Bin 359393 -> 0 bytes docs/_style/prism-master/index.html | 321 - docs/_style/prism-master/logo.svg | 22 - docs/_style/prism-master/package.json | 47 - .../plugins/autolinker/index.html | 70 - .../plugins/autolinker/prism-autolinker.css | 3 - .../plugins/autolinker/prism-autolinker.js | 81 - .../autolinker/prism-autolinker.min.js | 1 - .../plugins/autoloader/index.html | 204 - .../plugins/autoloader/prism-autoloader.js | 209 - .../autoloader/prism-autoloader.min.js | 1 - .../plugins/command-line/index.html | 111 - .../command-line/prism-command-line.css | 33 - .../command-line/prism-command-line.js | 139 - .../command-line/prism-command-line.min.js | 1 - .../plugins/copy-to-clipboard/index.html | 48 - .../prism-copy-to-clipboard.js | 75 - .../prism-copy-to-clipboard.min.js | 1 - .../plugins/custom-class/index.html | 133 - .../custom-class/prism-custom-class.js | 31 - .../custom-class/prism-custom-class.min.js | 1 - .../plugins/data-uri-highlight/index.html | 60 - .../prism-data-uri-highlight.js | 98 - .../prism-data-uri-highlight.min.js | 1 - .../plugins/file-highlight/index.html | 75 - .../file-highlight/prism-file-highlight.js | 105 - .../prism-file-highlight.min.js | 1 - .../plugins/highlight-keywords/index.html | 51 - .../prism-highlight-keywords.js | 17 - .../prism-highlight-keywords.min.js | 1 - docs/_style/prism-master/plugins/index.html | 42 - .../plugins/jsonp-highlight/index.html | 174 - .../jsonp-highlight/prism-jsonp-highlight.js | 151 - .../prism-jsonp-highlight.min.js | 1 - .../plugins/keep-markup/index.html | 80 - .../plugins/keep-markup/prism-keep-markup.js | 99 - .../keep-markup/prism-keep-markup.min.js | 1 - .../plugins/line-highlight/index.html | 88 - .../line-highlight/prism-line-highlight.css | 49 - .../line-highlight/prism-line-highlight.js | 181 - .../prism-line-highlight.min.js | 1 - .../plugins/line-numbers/index.html | 69 - .../line-numbers/prism-line-numbers.css | 41 - .../line-numbers/prism-line-numbers.js | 159 - .../line-numbers/prism-line-numbers.min.js | 1 - .../plugins/normalize-whitespace/demo.html | 33 - .../plugins/normalize-whitespace/index.html | 180 - .../prism-normalize-whitespace.js | 190 - .../prism-normalize-whitespace.min.js | 1 - .../plugins/previewers/index.html | 233 - .../plugins/previewers/prism-previewers.css | 242 - .../plugins/previewers/prism-previewers.js | 708 --- .../previewers/prism-previewers.min.js | 1 - .../remove-initial-line-feed/index.html | 59 - .../prism-remove-initial-line-feed.js | 21 - .../prism-remove-initial-line-feed.min.js | 1 - .../plugins/show-invisibles/index.html | 46 - .../show-invisibles/prism-show-invisibles.css | 34 - .../show-invisibles/prism-show-invisibles.js | 21 - .../prism-show-invisibles.min.js | 1 - .../plugins/show-language/index.html | 54 - .../show-language/prism-show-language.js | 31 - .../show-language/prism-show-language.min.js | 1 - .../prism-master/plugins/toolbar/index.html | 134 - .../plugins/toolbar/prism-toolbar.css | 58 - .../plugins/toolbar/prism-toolbar.js | 137 - .../plugins/toolbar/prism-toolbar.min.js | 1 - .../plugins/unescaped-markup/index.html | 195 - .../prism-unescaped-markup.css | 10 - .../prism-unescaped-markup.js | 44 - .../prism-unescaped-markup.min.js | 1 - .../prism-master/plugins/wpd/index.html | 68 - .../prism-master/plugins/wpd/prism-wpd.css | 11 - .../prism-master/plugins/wpd/prism-wpd.js | 169 - .../prism-master/plugins/wpd/prism-wpd.min.js | 1 - docs/_style/prism-master/prefixfree.min.js | 5 - docs/_style/prism-master/prism.js | 917 --- docs/_style/prism-master/style.css | 407 -- .../_style/prism-master/templates/footer.html | 15 - .../templates/header-download.html | 2 - .../prism-master/templates/header-main.html | 12 - .../templates/header-plugins.html | 8 - docs/_style/prism-master/test-suite.html | 167 - docs/_style/prism-master/test.html | 203 - .../prism-master/tests/helper/prism-loader.js | 131 - .../prism-master/tests/helper/test-case.js | 196 - .../tests/helper/test-discovery.js | 115 - .../tests/helper/token-stream-transformer.js | 32 - .../tests/languages/abap/comment_feature.test | 13 - .../languages/abap/eol-comment_feature.test | 13 - .../tests/languages/abap/keyword_feature.test | 1801 ------ .../tests/languages/abap/number_feature.test | 15 - .../languages/abap/operator_feature.test | 38 - .../abap/string-template_feature.test | 17 - .../tests/languages/abap/string_feature.test | 21 - .../actionscript/keyword_feature.test | 71 - .../actionscript/operator_feature.test | 29 - .../languages/ada/attr-name_feature.test | 13 - .../tests/languages/ada/boolean_feature.test | 13 - .../tests/languages/ada/char_feature.test | 13 - .../tests/languages/ada/comment_feature.test | 13 - .../tests/languages/ada/keyword_feature.test | 153 - .../tests/languages/ada/number_feature.test | 21 - .../tests/languages/ada/operator_feature.test | 23 - .../tests/languages/ada/string_feature.test | 13 - .../tests/languages/ada/variable_feature.test | 13 - .../languages/apacheconf/comment_feature.test | 15 - .../apacheconf/directive-block_feature.test | 469 -- .../apacheconf/directive-flags_feature.test | 13 - .../apacheconf/directive-inline_feature.test | 1163 ---- .../languages/apacheconf/regex_feature.test | 15 - .../languages/apacheconf/string_feature.test | 24 - .../apacheconf/variable_feature.test | 15 - .../languages/apl/assignment_feature.test | 13 - .../tests/languages/apl/comment_feature.test | 15 - .../tests/languages/apl/constant_feature.test | 19 - .../tests/languages/apl/dfn_feature.test | 23 - .../apl/dyadic-operator_feature.test | 15 - .../tests/languages/apl/function_feature.test | 43 - .../apl/monadic-operator_feature.test | 15 - .../tests/languages/apl/number_feature.test | 27 - .../languages/apl/statement_feature.test | 13 - .../tests/languages/apl/string_feature.test | 15 - .../apl/system-function_feature.test | 17 - .../languages/applescript/class_feature.test | 39 - .../applescript/comment_feature.test | 21 - .../applescript/keyword_feature.test | 59 - .../languages/applescript/number_feature.test | 17 - .../applescript/operator_feature.test | 48 - .../languages/applescript/string_feature.test | 13 - .../tests/languages/arff/comment_feature.test | 15 - .../tests/languages/arff/keyword_feature.test | 17 - .../tests/languages/arff/number_feature.test | 13 - .../tests/languages/arff/string_feature.test | 13 - .../asciidoc/admonition_feature.test | 19 - .../asciidoc/attribute-entry_feature.test | 58 - .../asciidoc/attributes_feature.test | 403 -- .../languages/asciidoc/callout_feature.test | 34 - .../asciidoc/comment-block_feature.test | 19 - .../languages/asciidoc/comment_feature.test | 41 - .../languages/asciidoc/entity_feature.js | 4 - .../languages/asciidoc/entity_feature.test | 48 - .../tests/languages/asciidoc/hr_feature.test | 14 - .../asciidoc/indented-block_feature.test | 28 - .../languages/asciidoc/inline_feature.test | 521 -- .../asciidoc/line-continuation_feature.test | 18 - .../asciidoc/list-label_feature.test | 73 - .../asciidoc/list-punctuation_feature.test | 77 - .../asciidoc/literal-block_feature.test | 46 - .../languages/asciidoc/macro_feature.test | 250 - .../asciidoc/other-block_feature.test | 45 - .../asciidoc/page-break_feature.test | 14 - .../asciidoc/passthrough-block_feature.test | 29 - .../asciidoc/replacement_feature.test | 48 - .../languages/asciidoc/table_feature.test | 61 - .../languages/asciidoc/title_feature.test | 80 - .../languages/asm6502/comment_feature.test | 13 - .../languages/asm6502/directive_feature.test | 12 - .../languages/asm6502/number_feature.test | 18 - .../languages/asm6502/opcode_feature.test | 17 - .../languages/asm6502/register_feature.test | 17 - .../languages/asm6502/string_feature.test | 12 - .../languages/aspnet/comment_feature.test | 16 - .../aspnet/page-directive_feature.test | 92 - .../languages/autohotkey/boolean_feature.test | 13 - .../languages/autohotkey/builtin_feature.test | 147 - .../languages/autohotkey/comment_feature.test | 13 - .../autohotkey/constant_feature.test | 275 - .../autohotkey/function_feature.test | 15 - .../autohotkey/important_feature.test | 67 - .../languages/autohotkey/keyword_feature.test | 537 -- .../languages/autohotkey/number_feature.test | 21 - .../autohotkey/operator_feature.test | 33 - .../autohotkey/selector_feature.test | 381 -- .../languages/autohotkey/string_feature.test | 15 - .../languages/autohotkey/symbol_feature.test | 347 -- .../languages/autohotkey/tag_feature.test | 15 - .../autohotkey/variable_feature.test | 13 - .../languages/autoit/boolean_feature.test | 13 - .../languages/autoit/comment_feature.test | 33 - .../languages/autoit/directive_feature.test | 13 - .../languages/autoit/function_feature.test | 15 - .../languages/autoit/keyword_feature.test | 83 - .../languages/autoit/number_feature.test | 21 - .../languages/autoit/operator_feature.test | 23 - .../languages/autoit/string_feature.test | 37 - .../tests/languages/autoit/url_feature.test | 15 - .../languages/autoit/variable_feature.test | 19 - .../bash/arithmetic_environment_feature.test | 53 - .../bash/command_substitution_feature.test | 45 - .../tests/languages/bash/comment_feature.test | 13 - .../languages/bash/function_feature.test | 101 - .../tests/languages/bash/keyword_feature.test | 20 - .../tests/languages/bash/shebang_feature.test | 11 - .../tests/languages/bash/string_feature.test | 67 - .../languages/bash/variable_feature.test | 15 - .../languages/basic/comment_feature.test | 13 - .../languages/basic/function_feature.test | 309 - .../languages/basic/keyword_feature.test | 213 - .../tests/languages/basic/number_feature.test | 19 - .../languages/basic/operator_feature.test | 21 - .../tests/languages/basic/string_feature.test | 13 - .../languages/batch/command_feature.test | 103 - .../languages/batch/comment_feature.test | 18 - .../tests/languages/batch/label_feature.test | 13 - .../tests/languages/bison/c_feature.test | 56 - .../languages/bison/comment_feature.test | 25 - .../languages/bison/keyword_feature.test | 22 - .../tests/languages/bison/number_feature.test | 15 - .../languages/bison/property_feature.test | 21 - .../tests/languages/bison/string_feature.test | 21 - .../languages/brainfuck/all_feature.test | 19 - .../tests/languages/bro/builtin_feature.test | 29 - .../tests/languages/bro/comment_feature.test | 17 - .../tests/languages/bro/function_feature.test | 21 - .../tests/languages/bro/keyword_feature.test | 87 - .../tests/languages/bro/string_feature.test | 23 - .../tests/languages/bro/variable_feature.test | 25 - .../tests/languages/c+pure/c_inclusion.test | 28 - .../tests/languages/c/constant_feature.test | 37 - .../tests/languages/c/keyword_feature.test | 29 - .../tests/languages/c/macro_feature.test | 45 - .../tests/languages/c/number_feature.test | 35 - .../tests/languages/c/operator_feature.test | 61 - .../tests/languages/cil/asm_reference.test | 11 - .../tests/languages/cil/boolean.test | 14 - .../tests/languages/cil/comment.test | 11 - .../tests/languages/cil/instructions.test | 457 -- .../tests/languages/cil/keywords.test | 161 - .../tests/languages/cil/strings.test | 11 - .../languages/clike/boolean_feature.test | 12 - .../languages/clike/class-name_feature.test | 53 - .../languages/clike/comment_feature.test | 16 - .../languages/clike/function_feature.test | 23 - .../tests/languages/clike/issue1340.test | 13 - .../languages/clike/keyword_feature.test | 30 - .../tests/languages/clike/number_feature.test | 23 - .../languages/clike/operator_feature.test | 21 - .../tests/languages/clike/string_feature.test | 31 - .../languages/clojure/boolean_feature.test | 15 - .../languages/clojure/comment_feature.test | 13 - .../languages/clojure/keyword_feature.test | 175 - .../clojure/operator_and_punctuation.test | 20 - .../languages/clojure/string_feature.test | 13 - .../coffeescript_inclusion.test | 24 - .../coffeescript_inclusion.test | 19 - .../coffeescript/block-regex_feature.test | 33 - .../coffeescript/class-member_feature.test | 13 - .../coffeescript/comment_feature.test | 16 - .../inline-javascript_feature.test | 22 - .../coffeescript/keyword_feature.test | 41 - .../coffeescript/property_feature.test | 15 - .../coffeescript/string_feature.test | 64 - .../languages/cpp+pure/cpp_inclusion.test | 18 - .../tests/languages/cpp/boolean_feature.test | 13 - .../languages/cpp/class-name_feature.test | 13 - .../tests/languages/cpp/keyword_feature.test | 49 - .../tests/languages/cpp/operator_feature.test | 73 - .../languages/cpp/raw_string_feature.test | 18 - .../languages/crystal/attribute_feature.test | 21 - .../languages/crystal/expansion_feature.test | 37 - .../languages/crystal/keyword_feature.test | 37 - .../languages/crystal/number_feature.test | 23 - .../csharp+aspnet/directive_feature.test | 70 - .../languages/csharp/class-name_feature.test | 34 - .../languages/csharp/generic_feature.test | 34 - .../tests/languages/csharp/issue1091.test | 12 - .../tests/languages/csharp/issue1365.test | 39 - .../tests/languages/csharp/issue1371.test | 144 - .../tests/languages/csharp/issue806.test | 12 - .../languages/csharp/keyword_feature.test | 209 - .../languages/csharp/number_feature.test | 17 - .../languages/csharp/operator_feature.test | 60 - .../csharp/preprocessor_feature.test | 35 - .../languages/csharp/punctuation_feature.test | 27 - .../languages/csharp/string_feature.test | 32 - .../csp/directive_no_value_feature.test | 11 - ...ective_with_source_expression_feature.test | 12 - .../tests/languages/csp/safe_feature.test | 19 - .../tests/languages/csp/unsafe_feature.test | 15 - .../css!+css-extras/entity_feature.test | 13 - .../css!+css-extras/hexcode_feature.test | 17 - .../css!+css-extras/number_feature.test | 17 - .../css!+css-extras/operator_feature.test | 71 - .../css!+css-extras/selector_feature.test | 58 - .../css!+css-extras/unit_feature.test | 21 - .../css!+css-extras/variable_feature.test | 54 - .../languages/css+haml/css+haml_usage.test | 28 - .../languages/css+http/css_inclusion.test | 25 - .../languages/css+textile/css_inclusion.test | 117 - .../tests/languages/css/atrule_feature.test | 47 - .../tests/languages/css/comment_feature.test | 16 - .../tests/languages/css/function_feature.test | 41 - .../languages/css/important_feature.test | 27 - .../tests/languages/css/property_feature.test | 29 - .../tests/languages/css/selector_feature.test | 25 - .../tests/languages/css/string_feature.test | 22 - .../tests/languages/css/url_feature.test | 21 - .../tests/languages/d/comment_feature.test | 27 - .../tests/languages/d/keyword_feature.test | 251 - .../tests/languages/d/number_feature.test | 55 - .../tests/languages/d/operator_feature.test | 63 - .../tests/languages/d/property_feature.test | 17 - .../tests/languages/d/register_feature.test | 75 - .../tests/languages/d/string_feature.test | 56 - .../languages/d/token-string_feature.test | 13 - .../tests/languages/dart/keyword_feature.test | 49 - .../languages/dart/metadata_feature.test | 20 - .../languages/dart/operator_feature.test | 33 - .../tests/languages/dart/string_feature.test | 25 - .../tests/languages/diff/coord_feature.test | 21 - .../tests/languages/diff/diff_feature.test | 21 - .../languages/django/comment_feature.test | 16 - .../languages/django/property_feature.test | 64 - .../languages/docker/comment_feature.test | 13 - .../languages/docker/keyword_feature.test | 45 - .../languages/docker/string_feature.test | 23 - .../languages/eiffel/boolean_feature.test | 13 - .../tests/languages/eiffel/char_feature.test | 15 - .../languages/eiffel/class-name_feature.test | 15 - .../languages/eiffel/comment_feature.test | 15 - .../languages/eiffel/keyword_feature.test | 39 - .../languages/eiffel/number_feature.test | 29 - .../languages/eiffel/operator_feature.test | 19 - .../languages/eiffel/string_feature.test | 34 - .../tests/languages/elixir/atom_feature.test | 15 - .../languages/elixir/attr-name_feature.test | 24 - .../languages/elixir/attribute_feature.test | 19 - .../languages/elixir/boolean_feature.test | 15 - .../languages/elixir/capture_feature.test | 28 - .../languages/elixir/comment_feature.test | 15 - .../tests/languages/elixir/issue1392.test | 16 - .../tests/languages/elixir/issue775.test | 17 - .../languages/elixir/keyword_feature.test | 31 - .../languages/elixir/number_feature.test | 27 - .../languages/elixir/operator_feature.test | 41 - .../tests/languages/elixir/regex_feature.test | 29 - .../languages/elixir/string_feature.test | 115 - .../tests/languages/elm/builtin_feature.test | 25 - .../tests/languages/elm/char_feature.test | 19 - .../tests/languages/elm/comment_feature.test | 14 - .../tests/languages/elm/constant_feature.test | 15 - .../languages/elm/hvariable_feature.test | 15 - .../elm/import_statement_feature.test | 48 - .../tests/languages/elm/keyword_feature.test | 19 - .../tests/languages/elm/number_feature.test | 21 - .../tests/languages/elm/operator_feature.test | 33 - .../tests/languages/elm/string_feature.test | 21 - .../tests/languages/erb/erb_feature.test | 36 - .../languages/erb/erb_in_markup_feature.test | 48 - .../tests/languages/erlang/atom_feature.test | 17 - .../languages/erlang/boolean_feature.test | 13 - .../languages/erlang/comment_feature.test | 11 - .../languages/erlang/function_feature.test | 17 - .../languages/erlang/keyword_feature.test | 15 - .../languages/erlang/number_feature.test | 25 - .../languages/erlang/operator_feature.test | 27 - .../languages/erlang/string_feature.test | 13 - .../languages/erlang/variable_feature.test | 17 - .../flow/flow-punctuation_feature.test | 12 - .../flow/function-variable_feature.test | 20 - .../tests/languages/flow/keyword_feature.test | 39 - .../tests/languages/flow/type_feature.test | 31 - .../fortran+pure/fortran_inclusion.test | 18 - .../languages/fortran/boolean_feature.test | 13 - .../languages/fortran/comment_feature.test | 15 - .../languages/fortran/keyword_feature.test | 199 - .../languages/fortran/number_feature.test | 37 - .../languages/fortran/operator_feature.test | 25 - .../languages/fortran/string_feature.test | 31 - .../languages/fsharp/annotation_feature.test | 35 - .../languages/fsharp/class-name_feature.test | 86 - .../languages/fsharp/comment_feature.test | 16 - .../computation-expression_feature.test | 17 - .../tests/languages/fsharp/issue1480.test | 36 - .../languages/fsharp/keyword_feature.test | 73 - .../languages/fsharp/number_feature.test | 65 - .../languages/fsharp/operator_feature.test | 53 - .../fsharp/preprocessor_feature.test | 22 - .../languages/fsharp/string_feature.test | 47 - .../languages/gcode/checksum_feature.test | 12 - .../languages/gcode/comment_feature.test | 20 - .../languages/gcode/keyword_feature.test | 23 - .../languages/gcode/property_feature.test | 17 - .../tests/languages/gcode/string_feature.test | 17 - .../tests/languages/gedcom/level_feature.test | 20 - .../languages/gedcom/line-value_feature.test | 29 - .../languages/gedcom/pointer_feature.test | 23 - .../tests/languages/gedcom/tag_feature.test | 25 - .../languages/gherkin/atrule_feature.test | 1211 ---- .../languages/gherkin/comment_feature.test | 15 - .../languages/gherkin/feature_feature.test | 196 - .../languages/gherkin/outline_feature.test | 11 - .../languages/gherkin/pystring_feature.test | 20 - .../languages/gherkin/scenario_feature.test | 581 -- .../languages/gherkin/string_feature.test | 29 - .../languages/gherkin/table_feature.test | 40 - .../tests/languages/gherkin/tag_feature.test | 17 - .../tests/languages/git/command_feature.test | 15 - .../tests/languages/git/comment_feature.test | 17 - .../languages/git/commit_sha1_feature.test | 15 - .../tests/languages/git/coord_feature.test | 13 - .../tests/languages/git/diff_feature.test | 31 - .../tests/languages/git/string_feature.test | 17 - .../tests/languages/glsl/comment_feature.test | 21 - .../tests/languages/glsl/keyword_feature.test | 263 - .../tests/languages/glsl/number_feature.test | 31 - .../languages/glsl/preprocessor_feature.test | 35 - .../tests/languages/go/boolean_feature.test | 19 - .../tests/languages/go/builtin_feature.test | 79 - .../tests/languages/go/keyword_feature.test | 59 - .../tests/languages/go/number_feature.test | 43 - .../tests/languages/go/operator_feature.test | 27 - .../tests/languages/go/string_feature.test | 37 - .../languages/graphql/attr-name_feature.test | 27 - .../languages/graphql/boolean_feature.test | 13 - .../languages/graphql/comment_feature.test | 13 - .../languages/graphql/directive_feature.test | 13 - .../languages/graphql/keyword_feature.test | 24 - .../languages/graphql/number_feature.test | 23 - .../languages/graphql/string_feature.test | 15 - .../languages/graphql/variable_feature.test | 13 - .../languages/groovy/annotation_feature.test | 17 - .../tests/languages/groovy/issue1049.js | 8 - .../languages/groovy/keyword_feature.test | 61 - .../languages/groovy/number_feature.test | 43 - .../languages/groovy/operator_feature.test | 45 - .../languages/groovy/shebang_feature.test | 13 - .../languages/groovy/spock-block_feature.test | 25 - .../groovy/string-interpolation_feature.js | 28 - .../languages/groovy/string_feature.test | 60 - .../tests/languages/haml/code_feature.test | 19 - .../tests/languages/haml/doctype_feature.test | 15 - .../languages/haml/interpolation_feature.test | 21 - .../haml/multiline-code_feature.test | 58 - .../haml/multiline-comment_feature.test | 46 - .../tests/languages/haml/tag_feature.test | 161 - .../handlebars+pug/handlebars_inclusion.test | 15 - .../languages/handlebars/block_feature.test | 23 - .../languages/handlebars/boolean_feature.test | 17 - .../languages/handlebars/comment_feature.test | 19 - .../handlebars_in_markup_feature.test | 59 - .../languages/handlebars/number_feature.test | 29 - .../languages/handlebars/string_feature.test | 25 - .../languages/haskell/builtin_feature.test | 137 - .../tests/languages/haskell/char_feature.test | 17 - .../languages/haskell/comment_feature.test | 14 - .../languages/haskell/constant_feature.test | 15 - .../languages/haskell/hvariable_feature.test | 15 - .../haskell/import_statement_feature.test | 35 - .../languages/haskell/keyword_feature.test | 19 - .../languages/haskell/number_feature.test | 23 - .../languages/haskell/operator_feature.test | 37 - .../languages/haskell/string_feature.test | 19 - .../tests/languages/haxe/keyword_feature.test | 93 - .../languages/haxe/metadata_feature.test | 15 - .../languages/haxe/operator_feature.test | 29 - .../languages/haxe/preprocessor_feature.test | 17 - .../tests/languages/haxe/regex_feature.test | 15 - .../languages/haxe/reification_feature.test | 16 - .../tests/languages/haxe/string_feature.test | 37 - .../languages/hpkp/safe_maxage_feature.test | 12 - .../languages/hpkp/sha256_pin_feature.test | 11 - .../languages/hpkp/unsafe_maxage_feature.test | 12 - .../hsts/include_subdomains_feature.test | 11 - .../tests/languages/hsts/preload_feature.test | 11 - .../languages/hsts/safe_maxage_feature.test | 12 - .../languages/hsts/unsafe_maxage_feature.test | 12 - .../languages/http/header-name_feature.test | 24 - .../languages/http/request-line_feature.test | 56 - .../http/response-status_feature.test | 29 - .../languages/ichigojam/comment_feature.test | 17 - .../languages/ichigojam/function_feature.test | 59 - .../languages/ichigojam/keyword_feature.test | 119 - .../languages/ichigojam/label_feature.test | 13 - .../languages/ichigojam/number_feature.test | 23 - .../languages/ichigojam/operator_feature.test | 36 - .../languages/ichigojam/string_feature.test | 13 - .../icon/builtin-keyword_feature.test | 91 - .../tests/languages/icon/comment_feature.test | 13 - .../languages/icon/directive_feature.test | 21 - .../languages/icon/function_feature.test | 15 - .../tests/languages/icon/keyword_feature.test | 67 - .../tests/languages/icon/number_feature.test | 33 - .../languages/icon/operator_feature.test | 69 - .../tests/languages/icon/string_feature.test | 22 - .../languages/inform7/comment_feature.test | 15 - .../languages/inform7/keyword_feature.test | 85 - .../languages/inform7/number_feature.test | 25 - .../languages/inform7/position_feature.test | 73 - .../languages/inform7/property_feature.test | 157 - .../languages/inform7/string_feature.test | 49 - .../languages/inform7/title_feature.test | 21 - .../languages/inform7/variable_feature.test | 141 - .../tests/languages/inform7/verb_feature.test | 213 - .../tests/languages/ini/comment_feature.test | 13 - .../languages/ini/key_value_feature.test | 21 - .../tests/languages/ini/selector_feature.test | 13 - .../tests/languages/io/comment_feature.test | 19 - .../tests/languages/io/number_feature.test | 23 - .../tests/languages/io/operator_feature.test | 26 - .../tests/languages/io/string_feature.test | 18 - .../tests/languages/j/adverb_feature.test | 19 - .../tests/languages/j/comment_feature.test | 13 - .../languages/j/conjunction_feature.test | 41 - .../tests/languages/j/keyword_feature.test | 77 - .../tests/languages/j/number_feature.test | 41 - .../tests/languages/j/string_feature.test | 13 - .../tests/languages/j/verb_feature.test | 93 - .../languages/java/function_featrue.test | 32 - .../languages/java/generics_feature.test | 67 - .../tests/languages/java/issue1351.test | 27 - .../tests/languages/java/keyword_feature.test | 59 - .../tests/languages/java/module_feature.test | 158 - .../tests/languages/java/number_feature.test | 60 - .../languages/java/operator_feature.test | 37 - .../tests/languages/java/package_feature.test | 80 - .../javascript+haml/javascript_inclusion.test | 24 - .../javascript+http/javascript_inclusion.test | 21 - .../languages/javascript/boolean_feature.test | 13 - .../javascript/class-method_feature.test | 59 - .../javascript/constant_feature.test | 21 - .../javascript/function-variable_feature.test | 104 - .../javascript/function_feature.test | 29 - .../tests/languages/javascript/issue1337.test | 11 - .../tests/languages/javascript/issue1340.test | 15 - .../tests/languages/javascript/issue1397.test | 21 - .../tests/languages/javascript/issue1526.test | 35 - .../languages/javascript/keyword_feature.test | 73 - .../languages/javascript/number_feature.test | 36 - .../javascript/operator_feature.test | 33 - .../languages/javascript/regex_feature.test | 29 - .../javascript/supposed-classes_feature.test | 30 - .../javascript/supposed-function_feature.test | 38 - .../javascript/template-string_feature.test | 49 - .../javascript/try-catch_feature.test | 22 - .../javastacktrace/more_feature.test | 22 - .../javastacktrace/stack-frame_feature.test | 71 - .../javastacktrace/summary_feature.test | 155 - .../languages/jolie/deployment_features.test | 43 - .../languages/jolie/keyword_feature.test | 129 - .../tests/languages/jolie/number_feature.test | 19 - .../languages/jolie/operator_feature.test | 42 - .../json+http/json-suffix_inclusion.test | 21 - .../languages/json+http/json_inclusion.test | 21 - .../tests/languages/json/boolean_feature.test | 13 - .../tests/languages/json/comment_feature.test | 27 - .../tests/languages/json/null_feature.test | 11 - .../tests/languages/json/number_feature.test | 27 - .../languages/json/operator_feature.test | 11 - .../languages/json/property_feature.test | 33 - .../languages/json/punctuation_feature.test | 20 - .../tests/languages/json/string_feature.test | 27 - .../tests/languages/jsx/issue1103.test | 29 - .../tests/languages/jsx/issue1235.test | 29 - .../tests/languages/jsx/issue1236.test | 26 - .../tests/languages/jsx/issue1294.test | 71 - .../tests/languages/jsx/issue1335.test | 126 - .../tests/languages/jsx/issue1342.test | 53 - .../tests/languages/jsx/issue1356.test | 32 - .../tests/languages/jsx/issue1364.test | 25 - .../tests/languages/jsx/issue1408.test | 36 - .../tests/languages/jsx/issue1421.test | 58 - .../languages/jsx/plain-text_feature.test | 57 - .../tests/languages/jsx/tag_feature.test | 83 - .../languages/julia/boolean_feature.test | 13 - .../languages/julia/comment_feature.test | 13 - .../languages/julia/keyword_feature.test | 31 - .../tests/languages/julia/number_feature.test | 33 - .../languages/julia/operator_feature.test | 41 - .../tests/languages/julia/string_feature.test | 29 - .../languages/keyman/atrule_feature.test | 15 - .../tests/languages/keyman/bold_feature.test | 59 - .../languages/keyman/comment_feature.test | 13 - .../languages/keyman/function_feature.test | 39 - .../languages/keyman/keyword_feature.test | 21 - .../languages/keyman/number_feature.test | 21 - .../languages/keyman/operator_feature.test | 15 - .../languages/keyman/string_feature.test | 17 - .../tests/languages/keyman/tag_feature.test | 19 - .../languages/kotlin/annotation_feature.test | 21 - .../languages/kotlin/function_feature.test | 16 - .../kotlin/interpolation_feature.test | 46 - .../languages/kotlin/keyword_feature.test | 137 - .../tests/languages/kotlin/label_feature.test | 15 - .../languages/kotlin/number_feature.test | 39 - .../languages/kotlin/operator_feature.test | 31 - .../languages/kotlin/raw-string_feature.test | 18 - .../tests/languages/latex/cdata_feature.test | 28 - .../languages/latex/comment_feature.test | 13 - .../languages/latex/equation_feature.test | 121 - .../languages/latex/headline_feature.test | 39 - .../languages/latex/keyword_feature.test | 32 - .../tests/languages/latex/url_feature.test | 12 - .../languages/less+haml/less_inclusion.test | 32 - .../languages/less+pug/less_inclusion.test | 20 - .../tests/languages/less/atrule_feature.test | 25 - .../tests/languages/less/comment_feature.test | 18 - .../languages/less/operator_feature.test | 14 - .../languages/less/property_feature.test | 19 - .../languages/less/selector_feature.test | 24 - .../languages/liquid/function_feature.test | 39 - .../languages/liquid/keyword_feature.test | 29 - .../languages/liquid/number_feature.test | 27 - .../languages/liquid/operator_feature.test | 33 - .../tests/languages/lisp/boolean_feature.test | 17 - .../tests/languages/lisp/car_feature.test | 13 - .../tests/languages/lisp/comment_feature.test | 11 - .../tests/languages/lisp/declare_feature.test | 16 - .../tests/languages/lisp/defun_feature.test | 27 - .../tests/languages/lisp/defvar_feature.test | 25 - .../tests/languages/lisp/heading_feature.test | 11 - .../languages/lisp/interactive_feature.test | 16 - .../tests/languages/lisp/keyword_feature.test | 73 - .../tests/languages/lisp/lambda-feature.test | 13 - .../languages/lisp/lisp-property_feature.test | 15 - .../tests/languages/lisp/number_boolean.test | 27 - .../languages/lisp/punctuation_feature.test | 16 - .../languages/lisp/quoted-symbol_feature.test | 17 - .../tests/languages/lisp/splice_feature.test | 17 - .../tests/languages/lisp/string_feature.test | 27 - .../livescript/argument_feature.test | 17 - .../languages/livescript/boolean_feature.test | 21 - .../languages/livescript/comment_feature.test | 18 - .../livescript/identifier_feature.test | 25 - .../livescript/interpolated-string.test | 57 - .../livescript/keyword-operator_feature.test | 57 - .../languages/livescript/keyword_feature.test | 87 - .../languages/livescript/number_feature.test | 21 - .../livescript/operator_feature.test | 55 - .../languages/livescript/regex_feature.test | 27 - .../languages/livescript/string_feature.test | 32 - .../languages/lolcode/boolean_feature.test | 13 - .../languages/lolcode/comment_feature.test | 14 - .../languages/lolcode/function_feature.test | 18 - .../languages/lolcode/keyword_feature.test | 93 - .../languages/lolcode/label_feature.test | 15 - .../languages/lolcode/number_feature.test | 13 - .../languages/lolcode/operator_feature.test | 39 - .../languages/lolcode/string_feature.test | 36 - .../languages/lolcode/symbol_feature.test | 27 - .../languages/lolcode/variable_feature.test | 11 - .../tests/languages/lua/comment_feature.test | 22 - .../tests/languages/lua/function_feature.test | 17 - .../tests/languages/lua/keyword_feature.test | 53 - .../tests/languages/lua/number_feature.test | 35 - .../tests/languages/lua/operator_feature.test | 25 - .../tests/languages/lua/string_feature.test | 36 - .../languages/makefile/builtin_feature.test | 15 - .../languages/makefile/comment_feature.test | 16 - .../languages/makefile/keyword_feature.test | 73 - .../languages/makefile/operator_feature.test | 15 - .../languages/makefile/string_feature.test | 23 - .../languages/makefile/symbol_feature.test | 18 - .../languages/makefile/variable_feature.test | 19 - .../markdown+haml/markdown_inclusion.test | 32 - .../markdown+pug/markdown_inclusion.test | 18 - .../markdown/blockquote_feature.test | 15 - .../languages/markdown/bold_feature.test | 82 - .../languages/markdown/code_feature.test | 33 - .../tests/languages/markdown/hr_feature.test | 17 - .../languages/markdown/italic_feature.test | 46 - .../languages/markdown/list_feature.test | 22 - .../languages/markdown/strike_feature.test | 70 - .../languages/markdown/title_feature.test | 42 - .../markdown/url-reference_feature.test | 56 - .../tests/languages/markdown/url_feature.test | 25 - .../markup!+css+javascript/issue1240.test | 38 - .../languages/markup!+css/css_inclusion.test | 71 - .../javascript_inclusion.test | 60 - .../markup+actionscript/xml_feature.test | 64 - .../markup+css+wiki/table-tag_feature.test | 147 - .../languages/markup+haml/markup_feature.test | 11 - .../languages/markup+http/html_inclusion.test | 30 - .../markup+http/xml-suffix_inclusion.test | 30 - .../languages/markup+http/xml_inclusion.test | 30 - .../script_feature.test | 57 - .../markup+php/php_in_markup_feature.test | 129 - .../languages/markup+pug/markup_feature.test | 29 - .../markup+tt2/tt2_in_markup_feature.test | 89 - .../tests/languages/markup/cdata_feature.test | 15 - .../languages/markup/comment_feature.test | 16 - .../languages/markup/doctype_feature.test | 16 - .../tests/languages/markup/entity_feature.js | 4 - .../languages/markup/entity_feature.test | 14 - .../tests/languages/markup/issue585.test | 63 - .../tests/languages/markup/issue888.test | 18 - .../languages/markup/prolog_feature.test | 16 - .../markup/tag_attribute_feature.test | 110 - .../tests/languages/markup/tag_feature.test | 81 - .../languages/matlab/comment_feature.test | 20 - .../languages/matlab/function_feature.test | 13 - .../languages/matlab/keyword_feature.test | 21 - .../languages/matlab/number_feature.test | 27 - .../languages/matlab/operator_feature.test | 29 - .../languages/matlab/string_feature.test | 15 - .../tests/languages/mel/code_feature.test | 16 - .../tests/languages/mel/comment_feature.test | 13 - .../tests/languages/mel/flag_feature.test | 15 - .../tests/languages/mel/function_feature.test | 2453 -------- .../tests/languages/mel/keyword_feature.test | 47 - .../tests/languages/mel/number_feature.test | 15 - .../tests/languages/mel/operator_feature.test | 33 - .../tests/languages/mel/string_feature.test | 13 - .../tests/languages/mel/variable_feature.test | 15 - .../languages/mizar/comment_feature.test | 11 - .../languages/mizar/keyword_feature.test | 245 - .../tests/languages/mizar/number_feature.test | 15 - .../languages/mizar/operator_feature.test | 17 - .../languages/mizar/parameter_feature.test | 17 - .../languages/mizar/variable_feature.test | 17 - .../languages/monkey/comment_feature.test | 21 - .../languages/monkey/function_feature.test | 13 - .../languages/monkey/keyword_feature.test | 125 - .../languages/monkey/number_feature.test | 19 - .../languages/monkey/operator_feature.test | 41 - .../monkey/preprocessor_feature.test | 15 - .../languages/monkey/string_feature.test | 13 - .../languages/monkey/type-char_feature.test | 17 - .../languages/n4js/annotation_feature.test | 53 - .../tests/languages/n4js/keyword_feature.test | 121 - .../tests/languages/nasm/comment_feature.test | 13 - .../tests/languages/nasm/keyword_feature.test | 30 - .../tests/languages/nasm/label_feature.test | 15 - .../tests/languages/nasm/number_feature.test | 97 - .../languages/nasm/operator_feature.test | 17 - .../languages/nasm/register_feature.test | 43 - .../tests/languages/nasm/string_feature.test | 21 - .../languages/nginx/comment_feature.test | 13 - .../languages/nginx/keyword_feature.test | 719 --- .../languages/nginx/variable_feature.test | 13 - .../tests/languages/nim/comment_feature.test | 13 - .../tests/languages/nim/function_feature.test | 17 - .../tests/languages/nim/keyword_feature.test | 123 - .../tests/languages/nim/number_feature.test | 31 - .../tests/languages/nim/operator_feature.test | 39 - .../tests/languages/nim/string_feature.test | 38 - .../languages/nix/antiquotation_feature.test | 14 - .../tests/languages/nix/boolean_feature.test | 13 - .../tests/languages/nix/comment_feature.test | 18 - .../tests/languages/nix/function_feature.test | 133 - .../tests/languages/nix/keyword_feature.test | 15 - .../tests/languages/nix/number_feature.test | 15 - .../tests/languages/nix/operator_feature.test | 25 - .../tests/languages/nix/string_feature.test | 56 - .../tests/languages/nix/url_feature.test | 21 - .../tests/languages/nsis/comment_feature.test | 18 - .../languages/nsis/constant_feature.test | 23 - .../languages/nsis/important_feature.test | 75 - .../tests/languages/nsis/keyword_feature.test | 455 -- .../tests/languages/nsis/number_feature.test | 19 - .../languages/nsis/operator_feature.test | 21 - .../languages/nsis/property_feature.test | 115 - .../tests/languages/nsis/string_feature.test | 17 - .../languages/nsis/variable_feature.test | 13 - .../languages/objectivec/keyword_feature.test | 59 - .../objectivec/operator_feature.test | 25 - .../languages/objectivec/string_feature.test | 34 - .../languages/ocaml/boolean_feature.test | 13 - .../languages/ocaml/comment_feature.test | 14 - .../languages/ocaml/directive_feature.test | 15 - .../languages/ocaml/keyword_feature.test | 101 - .../tests/languages/ocaml/number_feature.test | 25 - .../languages/ocaml/operator_feature.test | 31 - .../tests/languages/ocaml/string_feature.test | 25 - .../tests/languages/ocaml/type_feature.test | 17 - .../languages/opencl+c/boolean_feature.test | 13 - .../languages/opencl+c/constant_feature.test | 859 --- .../languages/opencl+c/function_feature.test | 227 - .../languages/opencl+c/type_feature.test | 219 - .../languages/opencl+cpp/type_feature.test | 83 - .../languages/opencl/constant_feature.test | 185 - .../languages/opencl/function_feature.test | 601 -- .../languages/opencl/keyword_feature.test | 419 -- .../tests/languages/oz/atom_feature.test | 16 - .../tests/languages/oz/attr-name_feature.test | 14 - .../tests/languages/oz/comment_feature.test | 18 - .../tests/languages/oz/function_feature.test | 13 - .../tests/languages/oz/keyword_feature.test | 103 - .../tests/languages/oz/number_feature.test | 35 - .../tests/languages/oz/operator_feature.test | 35 - .../tests/languages/oz/string_feature.test | 16 - .../tests/languages/oz/variable_feature.test | 15 - .../languages/parigp/comment_feature.test | 18 - .../languages/parigp/function_feature.test | 13 - .../languages/parigp/keyword_feature.test | 103 - .../languages/parigp/number_feature.test | 39 - .../languages/parigp/operator_feature.test | 155 - .../languages/parigp/string_feature.test | 13 - .../languages/parser/boolean_feature.test | 21 - .../languages/parser/escape_feature.test | 68 - .../languages/parser/expression_feature.test | 58 - .../languages/parser/function_feature.test | 48 - .../languages/parser/keyword_feature.test | 70 - .../languages/parser/number_feature.test | 51 - .../languages/parser/operator_feature.test | 257 - .../parser/parser-comment_feature.test | 17 - .../languages/parser/string_feature.test | 47 - .../languages/parser/variable_feature.test | 55 - .../languages/pascal/comment_feature.test | 23 - .../languages/pascal/keyword_feature.test | 143 - .../languages/pascal/number_feature.test | 25 - .../languages/pascal/operator_feature.test | 31 - .../languages/pascal/string_feature.test | 21 - .../tests/languages/perl/comment_feature.test | 17 - .../languages/perl/filehandle_feature.test | 17 - .../languages/perl/function_feature.test | 13 - .../tests/languages/perl/keyword_feature.test | 29 - .../tests/languages/perl/number_feature.test | 33 - .../languages/perl/operator_feature.test | 71 - .../tests/languages/perl/regex_feature.test | 129 - .../tests/languages/perl/string_feature.test | 130 - .../languages/perl/variable_feature.test | 47 - .../tests/languages/perl/vstring_feature.test | 13 - .../php!+php-extras/global_feature.test | 37 - .../php!+php-extras/scope_feature.test | 27 - .../php!+php-extras/this_feature.test | 11 - .../tests/languages/php/comment_feature.test | 20 - .../tests/languages/php/constant_feature.test | 15 - .../languages/php/delimiter_feature.test | 24 - .../tests/languages/php/keyword_feature.test | 76 - .../tests/languages/php/package_feature.test | 29 - .../tests/languages/php/property_feature.test | 19 - .../languages/php/shell-comment_feature.test | 13 - .../php/string-interpolation_feature.test | 140 - .../tests/languages/php/string_feature.test | 56 - .../tests/languages/php/variable_feature.test | 19 - .../languages/plsql/comment_feature.test | 18 - .../languages/plsql/keyword_feature.test | 415 -- .../languages/plsql/operator_feature.test | 11 - .../languages/powershell/boolean_feature.test | 18 - .../languages/powershell/comment_feature.test | 21 - .../powershell/function_feature.test | 398 -- .../tests/languages/powershell/issue1407.test | 43 - .../languages/powershell/keyword_feature.test | 46 - .../powershell/namespace_feature.test | 18 - .../powershell/operator_feature.test | 55 - .../languages/powershell/string_feature.test | 78 - .../powershell/variable_feature.test | 15 - .../processing/constant_feature.test | 13 - .../processing/function_feature.test | 13 - .../languages/processing/keyword_feature.test | 59 - .../processing/operator_feature.test | 31 - .../languages/processing/type_feature.test | 31 - .../languages/prolog/builtin_feature.test | 17 - .../languages/prolog/comment_feature.test | 16 - .../languages/prolog/function_feature.test | 17 - .../languages/prolog/number_feature.test | 15 - .../languages/prolog/operator_feature.test | 29 - .../languages/prolog/string_feature.test | 27 - .../languages/prolog/variable_feature.test | 15 - .../languages/properties/comment_feature.test | 17 - .../properties/key_value_feature.test | 36 - .../languages/protobuf/keyword_feature.test | 40 - .../languages/protobuf/string_feature.test | 23 - .../tests/languages/pug/code_feature.test | 36 - .../tests/languages/pug/comment_feature.test | 22 - .../tests/languages/pug/doctype_feature.test | 15 - .../languages/pug/flow-control_feature.test | 75 - .../tests/languages/pug/keyword_feature.test | 27 - .../tests/languages/pug/mixin_feature.test | 35 - .../pug/multiline-plain-text_feature.test | 30 - .../pug/multiline-script_feature.test | 58 - .../languages/pug/plain-text_feature.test | 19 - .../tests/languages/pug/script_feature.test | 38 - .../tests/languages/pug/tag_feature.test | 97 - .../languages/puppet/attr-name_feature.test | 19 - .../languages/puppet/boolean_feature.test | 13 - .../languages/puppet/comment_feature.test | 22 - .../languages/puppet/datatype_feature.test | 63 - .../languages/puppet/function_feature.test | 37 - .../languages/puppet/heredoc_feature.test | 51 - .../puppet/interpolation_feature.test | 54 - .../languages/puppet/keyword_feature.test | 47 - .../languages/puppet/number_feature.test | 25 - .../languages/puppet/operator_feature.test | 29 - .../tests/languages/puppet/regex_feature.test | 32 - .../languages/puppet/string_feature.test | 36 - .../languages/puppet/variable_feature.test | 15 - .../tests/languages/pure/comment_feature.test | 20 - .../languages/pure/function_feature.test | 619 -- .../tests/languages/pure/keyword_feature.test | 113 - .../tests/languages/pure/number_feature.test | 31 - .../languages/pure/operator_feature.test | 29 - .../tests/languages/pure/special_feature.test | 15 - .../tests/languages/pure/string_feature.test | 13 - .../languages/python/boolean_feature.test | 15 - .../languages/python/builtin_feature.test | 56 - .../languages/python/class-name_feature.test | 15 - .../languages/python/comment_feature.test | 13 - .../languages/python/decorator_feature.test | 28 - .../languages/python/function_feature.test | 17 - .../tests/languages/python/issue1355.test | 24 - .../languages/python/keyword_feature.test | 31 - .../languages/python/number_feature.test | 27 - .../languages/python/operator_feature.test | 29 - .../python/string-interpolation_feature.test | 147 - .../languages/python/string_feature.test | 29 - .../python/triple-quoted-string_feature.test | 21 - .../tests/languages/q/adverb_feature.test | 17 - .../tests/languages/q/comment_feature.test | 24 - .../tests/languages/q/datetime_feature.test | 39 - .../tests/languages/q/keyword_feature.test | 371 -- .../tests/languages/q/number_feature.test | 33 - .../tests/languages/q/string_feature.test | 13 - .../tests/languages/q/symbol_feature.test | 17 - .../tests/languages/q/verb_feature.test | 35 - .../tests/languages/qore/boolean_feature.test | 13 - .../tests/languages/qore/comment_feature.test | 22 - .../languages/qore/function_feature.test | 15 - .../tests/languages/qore/keyword_feature.test | 63 - .../tests/languages/qore/number_feature.test | 21 - .../languages/qore/operator_feature.test | 37 - .../tests/languages/qore/string_feature.test | 19 - .../languages/qore/variable_feature.test | 15 - .../tests/languages/r/boolean_feature.test | 13 - .../tests/languages/r/comment_feature.test | 13 - .../tests/languages/r/ellipsis_feature.test | 15 - .../tests/languages/r/keyword_feature.test | 25 - .../tests/languages/r/number_feature.test | 39 - .../tests/languages/r/operator_feature.test | 29 - .../languages/r/percent-operator_feature.test | 15 - .../tests/languages/r/string_feature.test | 17 - .../languages/reason/character_feature.test | 19 - .../languages/reason/class-name_feature.test | 15 - .../languages/reason/comment_feature.test | 20 - .../languages/reason/constructor_feature.test | 15 - .../languages/reason/keyword_feature.test | 103 - .../tests/languages/reason/label_feature.test | 13 - .../languages/reason/operator_feature.test | 31 - .../languages/reason/string_feature.test | 13 - .../rest/command-line-option_feature.test | 37 - .../tests/languages/rest/comment_feature.test | 24 - .../languages/rest/directive_feature.test | 33 - .../languages/rest/doctest-block_feature.test | 22 - .../tests/languages/rest/field_feature.test | 19 - .../tests/languages/rest/hr_feature.test | 112 - .../tests/languages/rest/inline_feature.test | 55 - .../languages/rest/link-target_feature.test | 90 - .../tests/languages/rest/link_feature.test | 33 - .../languages/rest/list-bullet_feature.test | 45 - .../languages/rest/literal-block_feature.test | 27 - .../rest/quoted-literal-block_feature.test | 333 -- .../rest/substitution-def_feature.test | 69 - .../tests/languages/rest/table_feature.test | 64 - .../tests/languages/rest/title_feature.test | 585 -- .../tests/languages/rip/boolean_feature.test | 13 - .../tests/languages/rip/builtin_feature.test | 13 - .../languages/rip/character_feature.test | 14 - .../tests/languages/rip/comment_feature.test | 13 - .../languages/rip/date_time_feature.test | 23 - .../tests/languages/rip/keyword_feature.test | 21 - .../tests/languages/rip/number_feature.test | 21 - .../languages/rip/reference_feature.test | 13 - .../tests/languages/rip/regex_feature.test | 17 - .../tests/languages/rip/string_feature.test | 17 - .../tests/languages/rip/symbol_feature.test | 15 - .../languages/roboconf/comment_feature.test | 13 - .../languages/roboconf/component_feature.test | 13 - .../languages/roboconf/keyword_feature.test | 19 - .../languages/roboconf/optional_feature.test | 11 - .../languages/roboconf/property_feature.test | 17 - .../languages/roboconf/value_feature.test | 20 - .../languages/roboconf/wildcard_feature.test | 12 - .../tests/languages/ruby/builtin_feature.test | 29 - .../tests/languages/ruby/comment_feature.test | 22 - .../languages/ruby/constant_feature.test | 21 - .../tests/languages/ruby/issue1336.test | 15 - .../tests/languages/ruby/keyword_feature.test | 103 - .../ruby/method_definition_feature.test | 82 - .../tests/languages/ruby/regex_feature.test | 45 - .../tests/languages/ruby/string_feature.test | 287 - .../tests/languages/ruby/symbol_feature.test | 17 - .../languages/ruby/variable_feature.test | 27 - .../languages/rust/attribute_feature.test | 13 - .../tests/languages/rust/char_feature.test | 13 - .../rust/closure-params_feature.test | 30 - .../tests/languages/rust/comment_feature.test | 18 - .../languages/rust/function_feature.test | 23 - .../tests/languages/rust/issue1339.test | 49 - .../tests/languages/rust/issue1353.test | 16 - .../tests/languages/rust/keyword_feature.test | 39 - .../rust/lifetime-annotation_feature.test | 15 - .../languages/rust/macro-rules_feature.test | 15 - .../tests/languages/rust/number_feature.test | 47 - .../languages/rust/operator_feature.test | 35 - .../tests/languages/rust/string_feature.test | 35 - .../tests/languages/sas/comment_feature.test | 17 - .../languages/sas/datalines_feature.test | 38 - .../tests/languages/sas/datetime_feature.test | 15 - .../tests/languages/sas/keyword_feature.test | 13 - .../tests/languages/sas/number_feature.test | 21 - .../tests/languages/sas/operator_feature.test | 37 - .../tests/languages/sas/string_feature.test | 23 - .../languages/sass/atrule-line_feature.test | 35 - .../tests/languages/sass/comment_feature.test | 23 - .../languages/sass/property-line_feature.test | 47 - .../languages/sass/selector_feature.test | 24 - .../languages/sass/variable-line_feature.test | 29 - .../languages/scala/builtin_feature.test | 17 - .../languages/scala/keyword_feature.test | 35 - .../tests/languages/scala/number_feature.test | 27 - .../tests/languages/scala/string_feature.test | 34 - .../tests/languages/scala/symbol_feature.test | 15 - .../languages/scheme/boolean_feature.test | 13 - .../languages/scheme/builtin_feature.test | 53 - .../languages/scheme/character_feature.test | 19 - .../languages/scheme/comment_feature.test | 13 - .../languages/scheme/function_feature.test | 21 - .../tests/languages/scheme/issue1331.test | 12 - .../languages/scheme/keyword_feature.test | 57 - .../languages/scheme/number_feature.test | 19 - .../languages/scheme/operator_feature.test | 31 - .../languages/scheme/string_feature.test | 17 - .../languages/scss+haml/scss_inclusion.test | 40 - .../languages/scss+pug/scss_inclusion.test | 17 - .../tests/languages/scss/atrule_feature.test | 20 - .../tests/languages/scss/boolean_feature.test | 13 - .../tests/languages/scss/comment_feature.test | 19 - .../tests/languages/scss/keyword_feature.test | 28 - .../tests/languages/scss/null_feature.test | 11 - .../languages/scss/operator_feature.test | 38 - .../languages/scss/placeholder_feature.test | 15 - .../languages/scss/property_feature.test | 23 - .../languages/scss/selector_feature.test | 23 - .../languages/scss/statement_feature.test | 21 - .../tests/languages/scss/url_feature.test | 19 - .../languages/scss/variable_feature.test | 21 - .../smalltalk/block-arguments_feature.test | 26 - .../smalltalk/character_feature.test | 17 - .../languages/smalltalk/comment_feature.test | 14 - .../languages/smalltalk/keyword_feature.test | 13 - .../languages/smalltalk/number_feature.test | 23 - .../languages/smalltalk/operator_feature.test | 29 - .../languages/smalltalk/string_feature.test | 14 - .../languages/smalltalk/symbol_feature.test | 21 - .../temporary-variables_feature.test | 25 - .../languages/smarty/attr-name_feature.test | 36 - .../languages/smarty/comment_feature.test | 14 - .../languages/smarty/function_feature.test | 41 - .../languages/smarty/keyword_feature.test | 51 - .../languages/smarty/number_feature.test | 45 - .../languages/smarty/operator_feature.test | 187 - .../smarty/smarty_in_markup_feature.test | 55 - .../languages/smarty/string_feature.test | 33 - .../languages/smarty/variable_feature.test | 56 - .../tests/languages/soy/boolean_feature.test | 27 - .../tests/languages/soy/command-arg.test | 84 - .../tests/languages/soy/comment_feature.test | 25 - .../tests/languages/soy/function_feature.test | 86 - .../tests/languages/soy/keyword_feature.test | 338 -- .../tests/languages/soy/literal_feature.test | 73 - .../tests/languages/soy/number_feature.test | 63 - .../tests/languages/soy/operator_feature.test | 121 - .../languages/soy/parameter_feature.test | 72 - .../tests/languages/soy/property_feature.test | 54 - .../languages/soy/soy_in_markup_feature.test | 68 - .../tests/languages/soy/string_feature.test | 47 - .../tests/languages/soy/variable_feature.test | 86 - .../tests/languages/sql/boolean_feature.test | 15 - .../tests/languages/sql/comment_feature.test | 26 - .../tests/languages/sql/function_feature.test | 39 - .../tests/languages/sql/keyword_feature.test | 717 --- .../tests/languages/sql/number_feature.test | 15 - .../tests/languages/sql/operator_feature.test | 49 - .../tests/languages/sql/string_feature.test | 27 - .../tests/languages/sql/variable_feature.test | 26 - .../stylus+pug/stylus_inclusion.test | 20 - .../stylus/atrule-declaration_feature.test | 31 - .../languages/stylus/boolean_feature.test | 21 - .../languages/stylus/comment_feature.test | 18 - .../tests/languages/stylus/func_feature.test | 51 - .../languages/stylus/hexcode_feature.test | 29 - .../languages/stylus/important_feature.test | 22 - .../languages/stylus/keyword_feature.test | 44 - .../languages/stylus/number_feature.test | 32 - .../languages/stylus/operator_feature.test | 273 - .../stylus/property-declaration_feature.test | 52 - .../languages/stylus/selector_feature.test | 49 - .../languages/stylus/string_feature.test | 33 - .../tests/languages/stylus/url_feature.test | 21 - .../stylus/variable-declaration_feature.test | 33 - .../tests/languages/swift/atrule_feature.test | 33 - .../languages/swift/builtin_feature.test | 53 - .../languages/swift/constant_feature.test | 19 - .../languages/swift/keyword_feature.test | 165 - .../tests/languages/swift/number_feature.test | 25 - .../tests/languages/swift/string_feature.test | 47 - .../tests/languages/tap/bail_out_feature.test | 13 - .../languages/tap/directive_feature.test | 15 - .../languages/tap/pass_fail_feature.test | 17 - .../tests/languages/tap/plan_feature.test | 13 - .../tests/languages/tap/pragma_feature.test | 13 - .../tests/languages/tap/version_feature.test | 11 - .../tests/languages/tap/yamlish_feature.test | 50 - .../tests/languages/tcl/builtin_feature.test | 35 - .../tests/languages/tcl/comment_feature.test | 13 - .../tests/languages/tcl/function_feature.test | 15 - .../tests/languages/tcl/keyword_feature.test | 213 - .../tests/languages/tcl/operator_feature.test | 45 - .../tests/languages/tcl/scope_feature.test | 15 - .../tests/languages/tcl/string_feature.test | 16 - .../tests/languages/tcl/variable_feature.test | 31 - .../languages/textile/acronym_feature.test | 15 - .../languages/textile/block-tag_feature.test | 131 - .../languages/textile/footnote_feature.test | 15 - .../languages/textile/image_feature.test | 71 - .../languages/textile/inline_feature.test | 166 - .../languages/textile/link-ref_feature.test | 25 - .../tests/languages/textile/link_feature.test | 44 - .../tests/languages/textile/list_feature.test | 36 - .../tests/languages/textile/mark_feature.test | 17 - .../languages/textile/table_feature.test | 177 - .../tests/languages/toml/boolean_feature.test | 13 - .../tests/languages/toml/comment_feature.test | 11 - .../tests/languages/toml/date_feature.test | 33 - .../tests/languages/toml/key_feature.test | 42 - .../tests/languages/toml/number_feature.test | 79 - .../tests/languages/toml/string_feature.test | 29 - .../tests/languages/toml/table_feature.test | 20 - .../tests/languages/tsx/tag_feature.test | 105 - .../tests/languages/tt2/comment_feature.test | 36 - .../languages/tt2/delimiter_feature.test | 18 - .../tests/languages/tt2/keyword_feature.test | 130 - .../tests/languages/tt2/operator_feature.test | 69 - .../tt2/string-interpolation_feature.test | 21 - .../tests/languages/tt2/string_feature.test | 35 - .../tests/languages/tt2/variable_feature.test | 36 - .../languages/twig+pug/twig_inclusion.test | 19 - .../tests/languages/twig/boolean_feature.test | 27 - .../tests/languages/twig/comment_feature.test | 16 - .../tests/languages/twig/keyword_feature.test | 53 - .../tests/languages/twig/number_feature.test | 45 - .../languages/twig/operator_feature.test | 179 - .../tests/languages/twig/string_feature.test | 33 - .../languages/typescript/builtin_feature.test | 31 - .../languages/typescript/keyword_feature.test | 129 - .../languages/vala/class-name_feature.test | 37 - .../tests/languages/vala/keyword_feature.test | 189 - .../tests/languages/vala/number_feature.test | 27 - .../languages/vala/operator_feature.test | 63 - .../languages/vala/punctuation_feature.test | 22 - .../tests/languages/vala/string_feature.test | 64 - .../languages/vbnet/comment_feature.test | 18 - .../languages/vbnet/keyword_feature.test | 443 -- .../languages/velocity/directive_feature.test | 417 -- .../languages/velocity/unparsed_feature.test | 29 - .../languages/velocity/variable_feature.test | 147 - .../velocity/velocity-comment_feature.test | 18 - .../languages/verilog/comment_feature.test | 18 - .../languages/verilog/constant_feature.test | 13 - .../languages/verilog/function_feature.test | 15 - .../languages/verilog/important_feature.test | 27 - .../languages/verilog/keyword_feature.test | 443 -- .../languages/verilog/number_feature.test | 39 - .../languages/verilog/operator_feature.test | 39 - .../languages/verilog/property_feature.test | 11 - .../languages/verilog/string_feature.test | 16 - .../tests/languages/vhdl/boolean_feature.test | 13 - .../tests/languages/vhdl/comment_feature.test | 13 - .../languages/vhdl/constant_feature.test | 13 - .../languages/vhdl/function_feature.test | 29 - .../tests/languages/vhdl/keyword_feature.test | 229 - .../tests/languages/vhdl/number_feature.test | 37 - .../languages/vhdl/operator_feature.test | 29 - .../tests/languages/vhdl/string_feature.test | 16 - .../languages/vhdl/vhdl-vectors_feature.test | 21 - .../tests/languages/vim/builtin_feature.test | 2289 -------- .../tests/languages/vim/comment_feature.test | 13 - .../tests/languages/vim/function_feature.test | 13 - .../tests/languages/vim/keyword_feature.test | 1661 ------ .../tests/languages/vim/number_feature.test | 15 - .../tests/languages/vim/operator_feature.test | 37 - .../tests/languages/vim/string_feature.test | 19 - .../visual-basic/boolean_feature.test | 15 - .../visual-basic/comment_feature.test | 25 - .../languages/visual-basic/date_feature.test | 23 - .../visual-basic/directive_feature.test | 23 - .../visual-basic/keyword_feature.test | 307 - .../visual-basic/number_feature.test | 37 - .../visual-basic/operator_feature.test | 37 - .../visual-basic/string_feature.test | 35 - .../tests/languages/wasm/comment_feature.test | 22 - .../tests/languages/wasm/keyword_feature.test | 407 -- .../tests/languages/wasm/number_feature.test | 61 - .../tests/languages/wasm/string_feature.test | 22 - .../languages/wasm/variable_feature.test | 17 - .../languages/wiki/block-comment_feature.test | 16 - .../languages/wiki/emphasis_feature.test | 27 - .../tests/languages/wiki/heading_feature.test | 33 - .../tests/languages/wiki/hr_feature.test | 13 - .../tests/languages/wiki/nowiki_feature.test | 63 - .../tests/languages/wiki/symbol_feature.test | 17 - .../tests/languages/wiki/url_feature.test | 29 - .../languages/wiki/variable_feature.test | 19 - .../languages/xeora/constant_feature.test | 15 - .../xeora/directive-block-close_feature.test | 12 - .../xeora/directive-block-open_feature.test | 30 - .../directive-block-separator_feature.test | 17 - .../xeora/directive-inline_feature.test | 21 - .../xeora/function-block_feature.test | 43 - .../xeora/function-inline_feature.test | 31 - .../languages/xeora/variable_feature.test | 36 - .../tests/languages/xojo/comment_feature.test | 15 - .../tests/languages/xojo/keyword_feature.test | 125 - .../tests/languages/xojo/number_feature.test | 27 - .../languages/xojo/operator_feature.test | 27 - .../tests/languages/xojo/string_feature.test | 15 - .../tests/languages/xojo/symbol_feature.test | 19 - .../tests/languages/xquery/axis_feature.test | 33 - .../languages/xquery/builtin_feature.test | 122 - .../languages/xquery/extension_feature.test | 11 - .../languages/xquery/function_feature.test | 43 - .../xquery/keyword-operator_feature.test | 43 - .../languages/xquery/keyword_feature.test | 119 - .../languages/xquery/number_feature.test | 17 - .../languages/xquery/operator_feature.test | 21 - .../languages/xquery/plain-text_feature.test | 71 - .../languages/xquery/string_feature.test | 25 - .../tests/languages/xquery/tag_feature.test | 133 - .../languages/xquery/variable_feature.test | 15 - .../xquery/xquery-attribute_feature.test | 18 - .../xquery/xquery-comment_feature.test | 19 - .../xquery/xquery-element_feature.test | 18 - .../tests/languages/yaml/boolean_feature.test | 17 - .../tests/languages/yaml/comment_feature.test | 13 - .../languages/yaml/datetime_feature.test | 31 - .../languages/yaml/directive_feature.test | 13 - .../languages/yaml/important_feature.test | 19 - .../tests/languages/yaml/key_feature.test | 15 - .../tests/languages/yaml/null_feature.test | 17 - .../tests/languages/yaml/number_feature.test | 38 - .../tests/languages/yaml/scalar_feature.test | 23 - .../tests/languages/yaml/string_feature.test | 29 - .../tests/languages/yaml/tag_feature.test | 15 - docs/_style/prism-master/tests/run.js | 45 - .../prism-master/tests/testrunner-tests.js | 166 - docs/_style/prism-master/themes/prism-coy.css | 225 - .../_style/prism-master/themes/prism-dark.css | 128 - .../prism-master/themes/prism-funky.css | 116 - .../prism-master/themes/prism-okaidia.css | 122 - .../themes/prism-solarizedlight.css | 149 - .../prism-master/themes/prism-tomorrow.css | 121 - .../prism-master/themes/prism-twilight.css | 198 - docs/_style/prism-master/themes/prism.css | 138 - docs/_style/prism-master/utopia.js | 463 -- .../prism-master/vendor/FileSaver.min.js | 2 - docs/_style/prism-master/vendor/jszip.min.js | 15 - docs/_style/prism-master/vendor/promise.js | 5 - docs/_style/style.css | 81 - docs/book/00-Introduction.md | 99 - docs/book/00-On-Java-8.md | 64 - docs/book/00-Preface.md | 100 - docs/book/01-What-is-an-Object.md | 294 - ...2-Installing-Java-and-the-Book-Examples.md | 196 - docs/book/03-Objects-Everywhere.md | 629 -- docs/book/04-Operators.md | 1471 ----- docs/book/05-Control-Flow.md | 827 --- docs/book/06-Housekeeping.md | 1824 ------ docs/book/07-Implementation-Hiding.md | 694 --- docs/book/08-Reuse.md | 1274 ---- docs/book/09-Polymorphism.md | 1257 ---- docs/book/10-Interfaces.md | 1820 ------ docs/book/11-Inner-Classes.md | 1450 ----- docs/book/12-Collections.md | 1755 ------ docs/book/13-Functional-Programming.md | 1524 ----- docs/book/14-Streams.md | 2153 ------- docs/book/15-Exceptions.md | 2199 ------- docs/book/16-Validating-Your-Code.md | 1961 ------- docs/book/17-Files.md | 854 --- docs/book/18-Strings.md | 1544 ----- docs/book/19-Type-Information.md | 2494 -------- docs/book/20-Generics.md | 5192 ----------------- docs/book/21-Arrays.md | 2743 --------- docs/book/22-Enumerations.md | 2111 ------- docs/book/23-Annotations.md | 1809 ------ docs/book/24-Concurrent-Programming.md | 3163 ---------- docs/book/25-Patterns.md | 1261 ---- docs/book/Appendix-Becoming-a-Programmer.md | 119 - ...efits-and-Costs-of-Static-Type-Checking.md | 29 - docs/book/Appendix-Collection-Topics.md | 3236 ---------- docs/book/Appendix-Data-Compression.md | 252 - docs/book/Appendix-IO-Streams.md | 547 -- docs/book/Appendix-Javadoc.md | 198 - docs/book/Appendix-Low-Level-Concurrency.md | 1690 ------ docs/book/Appendix-New-IO.md | 1081 ---- docs/book/Appendix-Object-Serialization.md | 981 ---- .../Appendix-Passing-and-Returning-Objects.md | 58 - docs/book/Appendix-Programming-Guidelines.md | 162 - docs/book/Appendix-Standard-IO.md | 200 - docs/book/Appendix-Supplements.md | 26 - ...Positive-Legacy-of-C-plus-plus-and-Java.md | 31 - ...endix-Understanding-equals-and-hashCode.md | 1015 ---- docs/book/GLOSSARY.md | 11 - docs/favicon.ico | Bin 16958 -> 0 bytes docs/images/1545758268350.png | Bin 20345 -> 0 bytes docs/images/1545763399825.png | Bin 49793 -> 0 bytes docs/images/1545764724202.png | Bin 50824 -> 0 bytes docs/images/1545764780795.png | Bin 60425 -> 0 bytes docs/images/1545764820176.png | Bin 53163 -> 0 bytes docs/images/1545839316314.png | Bin 50495 -> 0 bytes docs/images/1545841270997.png | Bin 41945 -> 0 bytes docs/images/1554546258113.png | Bin 29644 -> 0 bytes docs/images/1554546378822.png | Bin 13303 -> 0 bytes docs/images/1554546452861.png | Bin 259226 -> 0 bytes docs/images/1554546627710.png | Bin 6981 -> 0 bytes docs/images/1554546666685.png | Bin 7891 -> 0 bytes docs/images/1554546693664.png | Bin 7892 -> 0 bytes docs/images/1554546847181.png | Bin 7891 -> 0 bytes docs/images/1554546861836.png | Bin 7891 -> 0 bytes docs/images/1554546881189.png | Bin 7884 -> 0 bytes docs/images/1554546890132.png | Bin 7884 -> 0 bytes docs/images/1561774164644.png | Bin 57171 -> 0 bytes docs/images/1562204648023.png | Bin 56116 -> 0 bytes docs/images/1562252767216.png | Bin 99346 -> 0 bytes docs/images/1562406479787.png | Bin 49227 -> 0 bytes docs/images/1562409366637.png | Bin 85411 -> 0 bytes docs/images/1562409366638.png | Bin 30168 -> 0 bytes docs/images/1562409926765.png | Bin 21837 -> 0 bytes docs/images/1562653648586.png | Bin 33219 -> 0 bytes docs/images/1562737974623.png | Bin 31295 -> 0 bytes docs/images/1562999314238.png | Bin 164564 -> 0 bytes docs/images/QQGroupQRCode.png | Bin 15366 -> 0 bytes docs/images/collection.png | Bin 51285 -> 0 bytes docs/images/cover.jpg | Bin 117331 -> 0 bytes docs/images/cover_small.jpg | Bin 73325 -> 0 bytes docs/images/designproxy.png | Bin 58606 -> 0 bytes .../image-20190409114913825-4781754.png | Bin 38151 -> 0 bytes docs/images/level_1_title.png | Bin 2530 -> 0 bytes docs/images/level_2_title.png | Bin 14688 -> 0 bytes docs/images/map.png | Bin 20386 -> 0 bytes docs/images/qqgroup.png | Bin 2018 -> 0 bytes docs/images/reader.png | Bin 45866 -> 0 bytes docs/images/simple-collection-taxonomy.png | Bin 168908 -> 0 bytes docs/index.html | 389 -- docs/sidebar.md | 448 -- 1841 files changed, 5 insertions(+), 154409 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 SUMMARY.md delete mode 100644 book.json delete mode 100644 cover.jpg delete mode 100644 cover_small.jpg delete mode 100644 docs/.nojekyll delete mode 100644 docs/README.md delete mode 100644 docs/_coverpage.md delete mode 100644 docs/_style/prism-master/.editorconfig delete mode 100644 docs/_style/prism-master/.gitattributes delete mode 100644 docs/_style/prism-master/.gitignore delete mode 100644 docs/_style/prism-master/.npmignore delete mode 100644 docs/_style/prism-master/.travis.yml delete mode 100644 docs/_style/prism-master/CHANGELOG.md delete mode 100644 docs/_style/prism-master/CNAME delete mode 100644 docs/_style/prism-master/LICENSE delete mode 100644 docs/_style/prism-master/README.md delete mode 100644 docs/_style/prism-master/bower.json delete mode 100644 docs/_style/prism-master/code.js delete mode 100644 docs/_style/prism-master/components.js delete mode 100644 docs/_style/prism-master/components.json delete mode 100644 docs/_style/prism-master/components/index.js delete mode 100644 docs/_style/prism-master/components/prism-abap.js delete mode 100644 docs/_style/prism-master/components/prism-abap.min.js delete mode 100644 docs/_style/prism-master/components/prism-actionscript.js delete mode 100644 docs/_style/prism-master/components/prism-actionscript.min.js delete mode 100644 docs/_style/prism-master/components/prism-ada.js delete mode 100644 docs/_style/prism-master/components/prism-ada.min.js delete mode 100644 docs/_style/prism-master/components/prism-apacheconf.js delete mode 100644 docs/_style/prism-master/components/prism-apacheconf.min.js delete mode 100644 docs/_style/prism-master/components/prism-apl.js delete mode 100644 docs/_style/prism-master/components/prism-apl.min.js delete mode 100644 docs/_style/prism-master/components/prism-applescript.js delete mode 100644 docs/_style/prism-master/components/prism-applescript.min.js delete mode 100644 docs/_style/prism-master/components/prism-arduino.js delete mode 100644 docs/_style/prism-master/components/prism-arduino.min.js delete mode 100644 docs/_style/prism-master/components/prism-arff.js delete mode 100644 docs/_style/prism-master/components/prism-arff.min.js delete mode 100644 docs/_style/prism-master/components/prism-asciidoc.js delete mode 100644 docs/_style/prism-master/components/prism-asciidoc.min.js delete mode 100644 docs/_style/prism-master/components/prism-asm6502.js delete mode 100644 docs/_style/prism-master/components/prism-asm6502.min.js delete mode 100644 docs/_style/prism-master/components/prism-aspnet.js delete mode 100644 docs/_style/prism-master/components/prism-aspnet.min.js delete mode 100644 docs/_style/prism-master/components/prism-autohotkey.js delete mode 100644 docs/_style/prism-master/components/prism-autohotkey.min.js delete mode 100644 docs/_style/prism-master/components/prism-autoit.js delete mode 100644 docs/_style/prism-master/components/prism-autoit.min.js delete mode 100644 docs/_style/prism-master/components/prism-bash.js delete mode 100644 docs/_style/prism-master/components/prism-bash.min.js delete mode 100644 docs/_style/prism-master/components/prism-basic.js delete mode 100644 docs/_style/prism-master/components/prism-basic.min.js delete mode 100644 docs/_style/prism-master/components/prism-batch.js delete mode 100644 docs/_style/prism-master/components/prism-batch.min.js delete mode 100644 docs/_style/prism-master/components/prism-bison.js delete mode 100644 docs/_style/prism-master/components/prism-bison.min.js delete mode 100644 docs/_style/prism-master/components/prism-brainfuck.js delete mode 100644 docs/_style/prism-master/components/prism-brainfuck.min.js delete mode 100644 docs/_style/prism-master/components/prism-bro.js delete mode 100644 docs/_style/prism-master/components/prism-bro.min.js delete mode 100644 docs/_style/prism-master/components/prism-c.js delete mode 100644 docs/_style/prism-master/components/prism-c.min.js delete mode 100644 docs/_style/prism-master/components/prism-cil.js delete mode 100644 docs/_style/prism-master/components/prism-cil.min.js delete mode 100644 docs/_style/prism-master/components/prism-clike.js delete mode 100644 docs/_style/prism-master/components/prism-clike.min.js delete mode 100644 docs/_style/prism-master/components/prism-clojure.js delete mode 100644 docs/_style/prism-master/components/prism-clojure.min.js delete mode 100644 docs/_style/prism-master/components/prism-coffeescript.js delete mode 100644 docs/_style/prism-master/components/prism-coffeescript.min.js delete mode 100644 docs/_style/prism-master/components/prism-core.js delete mode 100644 docs/_style/prism-master/components/prism-core.min.js delete mode 100644 docs/_style/prism-master/components/prism-cpp.js delete mode 100644 docs/_style/prism-master/components/prism-cpp.min.js delete mode 100644 docs/_style/prism-master/components/prism-crystal.js delete mode 100644 docs/_style/prism-master/components/prism-crystal.min.js delete mode 100644 docs/_style/prism-master/components/prism-csharp.js delete mode 100644 docs/_style/prism-master/components/prism-csharp.min.js delete mode 100644 docs/_style/prism-master/components/prism-csp.js delete mode 100644 docs/_style/prism-master/components/prism-csp.min.js delete mode 100644 docs/_style/prism-master/components/prism-css-extras.js delete mode 100644 docs/_style/prism-master/components/prism-css-extras.min.js delete mode 100644 docs/_style/prism-master/components/prism-css.js delete mode 100644 docs/_style/prism-master/components/prism-css.min.js delete mode 100644 docs/_style/prism-master/components/prism-d.js delete mode 100644 docs/_style/prism-master/components/prism-d.min.js delete mode 100644 docs/_style/prism-master/components/prism-dart.js delete mode 100644 docs/_style/prism-master/components/prism-dart.min.js delete mode 100644 docs/_style/prism-master/components/prism-diff.js delete mode 100644 docs/_style/prism-master/components/prism-diff.min.js delete mode 100644 docs/_style/prism-master/components/prism-django.js delete mode 100644 docs/_style/prism-master/components/prism-django.min.js delete mode 100644 docs/_style/prism-master/components/prism-docker.js delete mode 100644 docs/_style/prism-master/components/prism-docker.min.js delete mode 100644 docs/_style/prism-master/components/prism-eiffel.js delete mode 100644 docs/_style/prism-master/components/prism-eiffel.min.js delete mode 100644 docs/_style/prism-master/components/prism-elixir.js delete mode 100644 docs/_style/prism-master/components/prism-elixir.min.js delete mode 100644 docs/_style/prism-master/components/prism-elm.js delete mode 100644 docs/_style/prism-master/components/prism-elm.min.js delete mode 100644 docs/_style/prism-master/components/prism-erb.js delete mode 100644 docs/_style/prism-master/components/prism-erb.min.js delete mode 100644 docs/_style/prism-master/components/prism-erlang.js delete mode 100644 docs/_style/prism-master/components/prism-erlang.min.js delete mode 100644 docs/_style/prism-master/components/prism-flow.js delete mode 100644 docs/_style/prism-master/components/prism-flow.min.js delete mode 100644 docs/_style/prism-master/components/prism-fortran.js delete mode 100644 docs/_style/prism-master/components/prism-fortran.min.js delete mode 100644 docs/_style/prism-master/components/prism-fsharp.js delete mode 100644 docs/_style/prism-master/components/prism-fsharp.min.js delete mode 100644 docs/_style/prism-master/components/prism-gcode.js delete mode 100644 docs/_style/prism-master/components/prism-gcode.min.js delete mode 100644 docs/_style/prism-master/components/prism-gedcom.js delete mode 100644 docs/_style/prism-master/components/prism-gedcom.min.js delete mode 100644 docs/_style/prism-master/components/prism-gherkin.js delete mode 100644 docs/_style/prism-master/components/prism-gherkin.min.js delete mode 100644 docs/_style/prism-master/components/prism-git.js delete mode 100644 docs/_style/prism-master/components/prism-git.min.js delete mode 100644 docs/_style/prism-master/components/prism-glsl.js delete mode 100644 docs/_style/prism-master/components/prism-glsl.min.js delete mode 100644 docs/_style/prism-master/components/prism-gml.js delete mode 100644 docs/_style/prism-master/components/prism-gml.min.js delete mode 100644 docs/_style/prism-master/components/prism-go.js delete mode 100644 docs/_style/prism-master/components/prism-go.min.js delete mode 100644 docs/_style/prism-master/components/prism-graphql.js delete mode 100644 docs/_style/prism-master/components/prism-graphql.min.js delete mode 100644 docs/_style/prism-master/components/prism-groovy.js delete mode 100644 docs/_style/prism-master/components/prism-groovy.min.js delete mode 100644 docs/_style/prism-master/components/prism-haml.js delete mode 100644 docs/_style/prism-master/components/prism-haml.min.js delete mode 100644 docs/_style/prism-master/components/prism-handlebars.js delete mode 100644 docs/_style/prism-master/components/prism-handlebars.min.js delete mode 100644 docs/_style/prism-master/components/prism-haskell.js delete mode 100644 docs/_style/prism-master/components/prism-haskell.min.js delete mode 100644 docs/_style/prism-master/components/prism-haxe.js delete mode 100644 docs/_style/prism-master/components/prism-haxe.min.js delete mode 100644 docs/_style/prism-master/components/prism-hpkp.js delete mode 100644 docs/_style/prism-master/components/prism-hpkp.min.js delete mode 100644 docs/_style/prism-master/components/prism-hsts.js delete mode 100644 docs/_style/prism-master/components/prism-hsts.min.js delete mode 100644 docs/_style/prism-master/components/prism-http.js delete mode 100644 docs/_style/prism-master/components/prism-http.min.js delete mode 100644 docs/_style/prism-master/components/prism-ichigojam.js delete mode 100644 docs/_style/prism-master/components/prism-ichigojam.min.js delete mode 100644 docs/_style/prism-master/components/prism-icon.js delete mode 100644 docs/_style/prism-master/components/prism-icon.min.js delete mode 100644 docs/_style/prism-master/components/prism-inform7.js delete mode 100644 docs/_style/prism-master/components/prism-inform7.min.js delete mode 100644 docs/_style/prism-master/components/prism-ini.js delete mode 100644 docs/_style/prism-master/components/prism-ini.min.js delete mode 100644 docs/_style/prism-master/components/prism-io.js delete mode 100644 docs/_style/prism-master/components/prism-io.min.js delete mode 100644 docs/_style/prism-master/components/prism-j.js delete mode 100644 docs/_style/prism-master/components/prism-j.min.js delete mode 100644 docs/_style/prism-master/components/prism-java.js delete mode 100644 docs/_style/prism-master/components/prism-java.min.js delete mode 100644 docs/_style/prism-master/components/prism-javascript.js delete mode 100644 docs/_style/prism-master/components/prism-javascript.min.js delete mode 100644 docs/_style/prism-master/components/prism-javastacktrace.js delete mode 100644 docs/_style/prism-master/components/prism-javastacktrace.min.js delete mode 100644 docs/_style/prism-master/components/prism-jolie.js delete mode 100644 docs/_style/prism-master/components/prism-jolie.min.js delete mode 100644 docs/_style/prism-master/components/prism-json.js delete mode 100644 docs/_style/prism-master/components/prism-json.min.js delete mode 100644 docs/_style/prism-master/components/prism-jsx.js delete mode 100644 docs/_style/prism-master/components/prism-jsx.min.js delete mode 100644 docs/_style/prism-master/components/prism-julia.js delete mode 100644 docs/_style/prism-master/components/prism-julia.min.js delete mode 100644 docs/_style/prism-master/components/prism-keyman.js delete mode 100644 docs/_style/prism-master/components/prism-keyman.min.js delete mode 100644 docs/_style/prism-master/components/prism-kotlin.js delete mode 100644 docs/_style/prism-master/components/prism-kotlin.min.js delete mode 100644 docs/_style/prism-master/components/prism-latex.js delete mode 100644 docs/_style/prism-master/components/prism-latex.min.js delete mode 100644 docs/_style/prism-master/components/prism-less.js delete mode 100644 docs/_style/prism-master/components/prism-less.min.js delete mode 100644 docs/_style/prism-master/components/prism-liquid.js delete mode 100644 docs/_style/prism-master/components/prism-liquid.min.js delete mode 100644 docs/_style/prism-master/components/prism-lisp.js delete mode 100644 docs/_style/prism-master/components/prism-lisp.min.js delete mode 100644 docs/_style/prism-master/components/prism-livescript.js delete mode 100644 docs/_style/prism-master/components/prism-livescript.min.js delete mode 100644 docs/_style/prism-master/components/prism-lolcode.js delete mode 100644 docs/_style/prism-master/components/prism-lolcode.min.js delete mode 100644 docs/_style/prism-master/components/prism-lua.js delete mode 100644 docs/_style/prism-master/components/prism-lua.min.js delete mode 100644 docs/_style/prism-master/components/prism-makefile.js delete mode 100644 docs/_style/prism-master/components/prism-makefile.min.js delete mode 100644 docs/_style/prism-master/components/prism-markdown.js delete mode 100644 docs/_style/prism-master/components/prism-markdown.min.js delete mode 100644 docs/_style/prism-master/components/prism-markup-templating.js delete mode 100644 docs/_style/prism-master/components/prism-markup-templating.min.js delete mode 100644 docs/_style/prism-master/components/prism-markup.js delete mode 100644 docs/_style/prism-master/components/prism-markup.min.js delete mode 100644 docs/_style/prism-master/components/prism-matlab.js delete mode 100644 docs/_style/prism-master/components/prism-matlab.min.js delete mode 100644 docs/_style/prism-master/components/prism-mel.js delete mode 100644 docs/_style/prism-master/components/prism-mel.min.js delete mode 100644 docs/_style/prism-master/components/prism-mizar.js delete mode 100644 docs/_style/prism-master/components/prism-mizar.min.js delete mode 100644 docs/_style/prism-master/components/prism-monkey.js delete mode 100644 docs/_style/prism-master/components/prism-monkey.min.js delete mode 100644 docs/_style/prism-master/components/prism-n4js.js delete mode 100644 docs/_style/prism-master/components/prism-n4js.min.js delete mode 100644 docs/_style/prism-master/components/prism-nasm.js delete mode 100644 docs/_style/prism-master/components/prism-nasm.min.js delete mode 100644 docs/_style/prism-master/components/prism-nginx.js delete mode 100644 docs/_style/prism-master/components/prism-nginx.min.js delete mode 100644 docs/_style/prism-master/components/prism-nim.js delete mode 100644 docs/_style/prism-master/components/prism-nim.min.js delete mode 100644 docs/_style/prism-master/components/prism-nix.js delete mode 100644 docs/_style/prism-master/components/prism-nix.min.js delete mode 100644 docs/_style/prism-master/components/prism-nsis.js delete mode 100644 docs/_style/prism-master/components/prism-nsis.min.js delete mode 100644 docs/_style/prism-master/components/prism-objectivec.js delete mode 100644 docs/_style/prism-master/components/prism-objectivec.min.js delete mode 100644 docs/_style/prism-master/components/prism-ocaml.js delete mode 100644 docs/_style/prism-master/components/prism-ocaml.min.js delete mode 100644 docs/_style/prism-master/components/prism-opencl.js delete mode 100644 docs/_style/prism-master/components/prism-opencl.min.js delete mode 100644 docs/_style/prism-master/components/prism-oz.js delete mode 100644 docs/_style/prism-master/components/prism-oz.min.js delete mode 100644 docs/_style/prism-master/components/prism-parigp.js delete mode 100644 docs/_style/prism-master/components/prism-parigp.min.js delete mode 100644 docs/_style/prism-master/components/prism-parser.js delete mode 100644 docs/_style/prism-master/components/prism-parser.min.js delete mode 100644 docs/_style/prism-master/components/prism-pascal.js delete mode 100644 docs/_style/prism-master/components/prism-pascal.min.js delete mode 100644 docs/_style/prism-master/components/prism-perl.js delete mode 100644 docs/_style/prism-master/components/prism-perl.min.js delete mode 100644 docs/_style/prism-master/components/prism-php-extras.js delete mode 100644 docs/_style/prism-master/components/prism-php-extras.min.js delete mode 100644 docs/_style/prism-master/components/prism-php.js delete mode 100644 docs/_style/prism-master/components/prism-php.min.js delete mode 100644 docs/_style/prism-master/components/prism-plsql.js delete mode 100644 docs/_style/prism-master/components/prism-plsql.min.js delete mode 100644 docs/_style/prism-master/components/prism-powershell.js delete mode 100644 docs/_style/prism-master/components/prism-powershell.min.js delete mode 100644 docs/_style/prism-master/components/prism-processing.js delete mode 100644 docs/_style/prism-master/components/prism-processing.min.js delete mode 100644 docs/_style/prism-master/components/prism-prolog.js delete mode 100644 docs/_style/prism-master/components/prism-prolog.min.js delete mode 100644 docs/_style/prism-master/components/prism-properties.js delete mode 100644 docs/_style/prism-master/components/prism-properties.min.js delete mode 100644 docs/_style/prism-master/components/prism-protobuf.js delete mode 100644 docs/_style/prism-master/components/prism-protobuf.min.js delete mode 100644 docs/_style/prism-master/components/prism-pug.js delete mode 100644 docs/_style/prism-master/components/prism-pug.min.js delete mode 100644 docs/_style/prism-master/components/prism-puppet.js delete mode 100644 docs/_style/prism-master/components/prism-puppet.min.js delete mode 100644 docs/_style/prism-master/components/prism-pure.js delete mode 100644 docs/_style/prism-master/components/prism-pure.min.js delete mode 100644 docs/_style/prism-master/components/prism-python.js delete mode 100644 docs/_style/prism-master/components/prism-python.min.js delete mode 100644 docs/_style/prism-master/components/prism-q.js delete mode 100644 docs/_style/prism-master/components/prism-q.min.js delete mode 100644 docs/_style/prism-master/components/prism-qore.js delete mode 100644 docs/_style/prism-master/components/prism-qore.min.js delete mode 100644 docs/_style/prism-master/components/prism-r.js delete mode 100644 docs/_style/prism-master/components/prism-r.min.js delete mode 100644 docs/_style/prism-master/components/prism-reason.js delete mode 100644 docs/_style/prism-master/components/prism-reason.min.js delete mode 100644 docs/_style/prism-master/components/prism-renpy.js delete mode 100644 docs/_style/prism-master/components/prism-renpy.min.js delete mode 100644 docs/_style/prism-master/components/prism-rest.js delete mode 100644 docs/_style/prism-master/components/prism-rest.min.js delete mode 100644 docs/_style/prism-master/components/prism-rip.js delete mode 100644 docs/_style/prism-master/components/prism-rip.min.js delete mode 100644 docs/_style/prism-master/components/prism-roboconf.js delete mode 100644 docs/_style/prism-master/components/prism-roboconf.min.js delete mode 100644 docs/_style/prism-master/components/prism-ruby.js delete mode 100644 docs/_style/prism-master/components/prism-ruby.min.js delete mode 100644 docs/_style/prism-master/components/prism-rust.js delete mode 100644 docs/_style/prism-master/components/prism-rust.min.js delete mode 100644 docs/_style/prism-master/components/prism-sas.js delete mode 100644 docs/_style/prism-master/components/prism-sas.min.js delete mode 100644 docs/_style/prism-master/components/prism-sass.js delete mode 100644 docs/_style/prism-master/components/prism-sass.min.js delete mode 100644 docs/_style/prism-master/components/prism-scala.js delete mode 100644 docs/_style/prism-master/components/prism-scala.min.js delete mode 100644 docs/_style/prism-master/components/prism-scheme.js delete mode 100644 docs/_style/prism-master/components/prism-scheme.min.js delete mode 100644 docs/_style/prism-master/components/prism-scss.js delete mode 100644 docs/_style/prism-master/components/prism-scss.min.js delete mode 100644 docs/_style/prism-master/components/prism-smalltalk.js delete mode 100644 docs/_style/prism-master/components/prism-smalltalk.min.js delete mode 100644 docs/_style/prism-master/components/prism-smarty.js delete mode 100644 docs/_style/prism-master/components/prism-smarty.min.js delete mode 100644 docs/_style/prism-master/components/prism-soy.js delete mode 100644 docs/_style/prism-master/components/prism-soy.min.js delete mode 100644 docs/_style/prism-master/components/prism-sql.js delete mode 100644 docs/_style/prism-master/components/prism-sql.min.js delete mode 100644 docs/_style/prism-master/components/prism-stylus.js delete mode 100644 docs/_style/prism-master/components/prism-stylus.min.js delete mode 100644 docs/_style/prism-master/components/prism-swift.js delete mode 100644 docs/_style/prism-master/components/prism-swift.min.js delete mode 100644 docs/_style/prism-master/components/prism-tap.js delete mode 100644 docs/_style/prism-master/components/prism-tap.min.js delete mode 100644 docs/_style/prism-master/components/prism-tcl.js delete mode 100644 docs/_style/prism-master/components/prism-tcl.min.js delete mode 100644 docs/_style/prism-master/components/prism-textile.js delete mode 100644 docs/_style/prism-master/components/prism-textile.min.js delete mode 100644 docs/_style/prism-master/components/prism-toml.js delete mode 100644 docs/_style/prism-master/components/prism-toml.min.js delete mode 100644 docs/_style/prism-master/components/prism-tsx.js delete mode 100644 docs/_style/prism-master/components/prism-tsx.min.js delete mode 100644 docs/_style/prism-master/components/prism-tt2.js delete mode 100644 docs/_style/prism-master/components/prism-tt2.min.js delete mode 100644 docs/_style/prism-master/components/prism-twig.js delete mode 100644 docs/_style/prism-master/components/prism-twig.min.js delete mode 100644 docs/_style/prism-master/components/prism-typescript.js delete mode 100644 docs/_style/prism-master/components/prism-typescript.min.js delete mode 100644 docs/_style/prism-master/components/prism-vala.js delete mode 100644 docs/_style/prism-master/components/prism-vala.min.js delete mode 100644 docs/_style/prism-master/components/prism-vbnet.js delete mode 100644 docs/_style/prism-master/components/prism-vbnet.min.js delete mode 100644 docs/_style/prism-master/components/prism-velocity.js delete mode 100644 docs/_style/prism-master/components/prism-velocity.min.js delete mode 100644 docs/_style/prism-master/components/prism-verilog.js delete mode 100644 docs/_style/prism-master/components/prism-verilog.min.js delete mode 100644 docs/_style/prism-master/components/prism-vhdl.js delete mode 100644 docs/_style/prism-master/components/prism-vhdl.min.js delete mode 100644 docs/_style/prism-master/components/prism-vim.js delete mode 100644 docs/_style/prism-master/components/prism-vim.min.js delete mode 100644 docs/_style/prism-master/components/prism-visual-basic.js delete mode 100644 docs/_style/prism-master/components/prism-visual-basic.min.js delete mode 100644 docs/_style/prism-master/components/prism-wasm.js delete mode 100644 docs/_style/prism-master/components/prism-wasm.min.js delete mode 100644 docs/_style/prism-master/components/prism-wiki.js delete mode 100644 docs/_style/prism-master/components/prism-wiki.min.js delete mode 100644 docs/_style/prism-master/components/prism-xeora.js delete mode 100644 docs/_style/prism-master/components/prism-xeora.min.js delete mode 100644 docs/_style/prism-master/components/prism-xojo.js delete mode 100644 docs/_style/prism-master/components/prism-xojo.min.js delete mode 100644 docs/_style/prism-master/components/prism-xquery.js delete mode 100644 docs/_style/prism-master/components/prism-xquery.min.js delete mode 100644 docs/_style/prism-master/components/prism-yaml.js delete mode 100644 docs/_style/prism-master/components/prism-yaml.min.js delete mode 100644 docs/_style/prism-master/composer.json delete mode 100644 docs/_style/prism-master/download.html delete mode 100644 docs/_style/prism-master/download.js delete mode 100644 docs/_style/prism-master/examples.html delete mode 100644 docs/_style/prism-master/examples.js delete mode 100644 docs/_style/prism-master/examples/prism-abap.html delete mode 100644 docs/_style/prism-master/examples/prism-actionscript.html delete mode 100644 docs/_style/prism-master/examples/prism-ada.html delete mode 100644 docs/_style/prism-master/examples/prism-apacheconf.html delete mode 100644 docs/_style/prism-master/examples/prism-apl.html delete mode 100644 docs/_style/prism-master/examples/prism-applescript.html delete mode 100644 docs/_style/prism-master/examples/prism-arduino.html delete mode 100644 docs/_style/prism-master/examples/prism-arff.html delete mode 100644 docs/_style/prism-master/examples/prism-asciidoc.html delete mode 100644 docs/_style/prism-master/examples/prism-asm6502.html delete mode 100644 docs/_style/prism-master/examples/prism-aspnet.html delete mode 100644 docs/_style/prism-master/examples/prism-autohotkey.html delete mode 100644 docs/_style/prism-master/examples/prism-autoit.html delete mode 100644 docs/_style/prism-master/examples/prism-bash.html delete mode 100644 docs/_style/prism-master/examples/prism-basic.html delete mode 100644 docs/_style/prism-master/examples/prism-batch.html delete mode 100644 docs/_style/prism-master/examples/prism-bison.html delete mode 100644 docs/_style/prism-master/examples/prism-brainfuck.html delete mode 100644 docs/_style/prism-master/examples/prism-bro.html delete mode 100644 docs/_style/prism-master/examples/prism-c.html delete mode 100644 docs/_style/prism-master/examples/prism-clike.html delete mode 100644 docs/_style/prism-master/examples/prism-clojure.html delete mode 100644 docs/_style/prism-master/examples/prism-coffeescript.html delete mode 100644 docs/_style/prism-master/examples/prism-cpp.html delete mode 100644 docs/_style/prism-master/examples/prism-crystal.html delete mode 100644 docs/_style/prism-master/examples/prism-csharp.html delete mode 100644 docs/_style/prism-master/examples/prism-csp.html delete mode 100644 docs/_style/prism-master/examples/prism-css.html delete mode 100644 docs/_style/prism-master/examples/prism-d.html delete mode 100644 docs/_style/prism-master/examples/prism-dart.html delete mode 100644 docs/_style/prism-master/examples/prism-diff.html delete mode 100644 docs/_style/prism-master/examples/prism-django.html delete mode 100644 docs/_style/prism-master/examples/prism-docker.html delete mode 100644 docs/_style/prism-master/examples/prism-eiffel.html delete mode 100644 docs/_style/prism-master/examples/prism-elixir.html delete mode 100644 docs/_style/prism-master/examples/prism-elm.html delete mode 100644 docs/_style/prism-master/examples/prism-erb.html delete mode 100644 docs/_style/prism-master/examples/prism-erlang.html delete mode 100644 docs/_style/prism-master/examples/prism-flow.html delete mode 100644 docs/_style/prism-master/examples/prism-fortran.html delete mode 100644 docs/_style/prism-master/examples/prism-fsharp.html delete mode 100644 docs/_style/prism-master/examples/prism-gcode.html delete mode 100644 docs/_style/prism-master/examples/prism-gedcom.html delete mode 100644 docs/_style/prism-master/examples/prism-gherkin.html delete mode 100644 docs/_style/prism-master/examples/prism-git.html delete mode 100644 docs/_style/prism-master/examples/prism-glsl.html delete mode 100644 docs/_style/prism-master/examples/prism-gml.html delete mode 100644 docs/_style/prism-master/examples/prism-go.html delete mode 100644 docs/_style/prism-master/examples/prism-graphql.html delete mode 100644 docs/_style/prism-master/examples/prism-groovy.html delete mode 100644 docs/_style/prism-master/examples/prism-haml.html delete mode 100644 docs/_style/prism-master/examples/prism-handlebars.html delete mode 100644 docs/_style/prism-master/examples/prism-haskell.html delete mode 100644 docs/_style/prism-master/examples/prism-haxe.html delete mode 100644 docs/_style/prism-master/examples/prism-hpkp.html delete mode 100644 docs/_style/prism-master/examples/prism-hsts.html delete mode 100644 docs/_style/prism-master/examples/prism-http.html delete mode 100644 docs/_style/prism-master/examples/prism-ichigojam.html delete mode 100644 docs/_style/prism-master/examples/prism-icon.html delete mode 100644 docs/_style/prism-master/examples/prism-inform7.html delete mode 100644 docs/_style/prism-master/examples/prism-ini.html delete mode 100644 docs/_style/prism-master/examples/prism-io.html delete mode 100644 docs/_style/prism-master/examples/prism-j.html delete mode 100644 docs/_style/prism-master/examples/prism-java.html delete mode 100644 docs/_style/prism-master/examples/prism-javascript.html delete mode 100644 docs/_style/prism-master/examples/prism-javastacktrace.html delete mode 100644 docs/_style/prism-master/examples/prism-jolie.html delete mode 100644 docs/_style/prism-master/examples/prism-jsx.html delete mode 100644 docs/_style/prism-master/examples/prism-julia.html delete mode 100644 docs/_style/prism-master/examples/prism-keyman.html delete mode 100644 docs/_style/prism-master/examples/prism-kotlin.html delete mode 100644 docs/_style/prism-master/examples/prism-latex.html delete mode 100644 docs/_style/prism-master/examples/prism-less.html delete mode 100644 docs/_style/prism-master/examples/prism-liquid.html delete mode 100644 docs/_style/prism-master/examples/prism-lisp.html delete mode 100644 docs/_style/prism-master/examples/prism-livescript.html delete mode 100644 docs/_style/prism-master/examples/prism-lolcode.html delete mode 100644 docs/_style/prism-master/examples/prism-lua.html delete mode 100644 docs/_style/prism-master/examples/prism-makefile.html delete mode 100644 docs/_style/prism-master/examples/prism-markdown.html delete mode 100644 docs/_style/prism-master/examples/prism-markup.html delete mode 100644 docs/_style/prism-master/examples/prism-matlab.html delete mode 100644 docs/_style/prism-master/examples/prism-mel.html delete mode 100644 docs/_style/prism-master/examples/prism-mizar.html delete mode 100644 docs/_style/prism-master/examples/prism-monkey.html delete mode 100644 docs/_style/prism-master/examples/prism-n4js.html delete mode 100644 docs/_style/prism-master/examples/prism-nasm.html delete mode 100644 docs/_style/prism-master/examples/prism-nginx.html delete mode 100644 docs/_style/prism-master/examples/prism-nim.html delete mode 100644 docs/_style/prism-master/examples/prism-nix.html delete mode 100644 docs/_style/prism-master/examples/prism-nsis.html delete mode 100644 docs/_style/prism-master/examples/prism-objectivec.html delete mode 100644 docs/_style/prism-master/examples/prism-ocaml.html delete mode 100644 docs/_style/prism-master/examples/prism-opencl.html delete mode 100644 docs/_style/prism-master/examples/prism-oz.html delete mode 100644 docs/_style/prism-master/examples/prism-parigp.html delete mode 100644 docs/_style/prism-master/examples/prism-parser.html delete mode 100644 docs/_style/prism-master/examples/prism-pascal.html delete mode 100644 docs/_style/prism-master/examples/prism-perl.html delete mode 100644 docs/_style/prism-master/examples/prism-php.html delete mode 100644 docs/_style/prism-master/examples/prism-plsql.html delete mode 100644 docs/_style/prism-master/examples/prism-powershell.html delete mode 100644 docs/_style/prism-master/examples/prism-processing.html delete mode 100644 docs/_style/prism-master/examples/prism-prolog.html delete mode 100644 docs/_style/prism-master/examples/prism-properties.html delete mode 100644 docs/_style/prism-master/examples/prism-pug.html delete mode 100644 docs/_style/prism-master/examples/prism-puppet.html delete mode 100644 docs/_style/prism-master/examples/prism-pure.html delete mode 100644 docs/_style/prism-master/examples/prism-python.html delete mode 100644 docs/_style/prism-master/examples/prism-q.html delete mode 100644 docs/_style/prism-master/examples/prism-qore.html delete mode 100644 docs/_style/prism-master/examples/prism-r.html delete mode 100644 docs/_style/prism-master/examples/prism-reason.html delete mode 100644 docs/_style/prism-master/examples/prism-renpy.html delete mode 100644 docs/_style/prism-master/examples/prism-rest.html delete mode 100644 docs/_style/prism-master/examples/prism-rip.html delete mode 100644 docs/_style/prism-master/examples/prism-roboconf.html delete mode 100644 docs/_style/prism-master/examples/prism-ruby.html delete mode 100644 docs/_style/prism-master/examples/prism-rust.html delete mode 100644 docs/_style/prism-master/examples/prism-sas.html delete mode 100644 docs/_style/prism-master/examples/prism-sass.html delete mode 100644 docs/_style/prism-master/examples/prism-scala.html delete mode 100644 docs/_style/prism-master/examples/prism-scheme.html delete mode 100644 docs/_style/prism-master/examples/prism-scss.html delete mode 100644 docs/_style/prism-master/examples/prism-smalltalk.html delete mode 100644 docs/_style/prism-master/examples/prism-smarty.html delete mode 100644 docs/_style/prism-master/examples/prism-soy.html delete mode 100644 docs/_style/prism-master/examples/prism-sql.html delete mode 100644 docs/_style/prism-master/examples/prism-stylus.html delete mode 100644 docs/_style/prism-master/examples/prism-swift.html delete mode 100644 docs/_style/prism-master/examples/prism-tcl.html delete mode 100644 docs/_style/prism-master/examples/prism-textile.html delete mode 100644 docs/_style/prism-master/examples/prism-tsx.html delete mode 100644 docs/_style/prism-master/examples/prism-tt2.html delete mode 100644 docs/_style/prism-master/examples/prism-twig.html delete mode 100644 docs/_style/prism-master/examples/prism-typescript.html delete mode 100644 docs/_style/prism-master/examples/prism-vala.html delete mode 100644 docs/_style/prism-master/examples/prism-vbnet.html delete mode 100644 docs/_style/prism-master/examples/prism-velocity.html delete mode 100644 docs/_style/prism-master/examples/prism-verilog.html delete mode 100644 docs/_style/prism-master/examples/prism-vhdl.html delete mode 100644 docs/_style/prism-master/examples/prism-vim.html delete mode 100644 docs/_style/prism-master/examples/prism-visual-basic.html delete mode 100644 docs/_style/prism-master/examples/prism-wasm.html delete mode 100644 docs/_style/prism-master/examples/prism-wiki.html delete mode 100644 docs/_style/prism-master/examples/prism-xeora.html delete mode 100644 docs/_style/prism-master/examples/prism-xojo.html delete mode 100644 docs/_style/prism-master/examples/prism-xquery.html delete mode 100644 docs/_style/prism-master/examples/prism-yaml.html delete mode 100644 docs/_style/prism-master/extending.html delete mode 100644 docs/_style/prism-master/faq.html delete mode 100644 docs/_style/prism-master/favicon.png delete mode 100644 docs/_style/prism-master/gulpfile.js delete mode 100644 docs/_style/prism-master/img/logo-ala.png delete mode 100644 docs/_style/prism-master/img/logo-css-tricks.png delete mode 100644 docs/_style/prism-master/img/logo-drupal.png delete mode 100644 docs/_style/prism-master/img/logo-mdn.png delete mode 100644 docs/_style/prism-master/img/logo-react.png delete mode 100644 docs/_style/prism-master/img/logo-sitepoint.png delete mode 100644 docs/_style/prism-master/img/logo-smashing.png delete mode 100644 docs/_style/prism-master/img/logo-stripe.png delete mode 100644 docs/_style/prism-master/img/spectrum.png delete mode 100644 docs/_style/prism-master/index.html delete mode 100644 docs/_style/prism-master/logo.svg delete mode 100644 docs/_style/prism-master/package.json delete mode 100644 docs/_style/prism-master/plugins/autolinker/index.html delete mode 100644 docs/_style/prism-master/plugins/autolinker/prism-autolinker.css delete mode 100644 docs/_style/prism-master/plugins/autolinker/prism-autolinker.js delete mode 100644 docs/_style/prism-master/plugins/autolinker/prism-autolinker.min.js delete mode 100644 docs/_style/prism-master/plugins/autoloader/index.html delete mode 100644 docs/_style/prism-master/plugins/autoloader/prism-autoloader.js delete mode 100644 docs/_style/prism-master/plugins/autoloader/prism-autoloader.min.js delete mode 100644 docs/_style/prism-master/plugins/command-line/index.html delete mode 100644 docs/_style/prism-master/plugins/command-line/prism-command-line.css delete mode 100644 docs/_style/prism-master/plugins/command-line/prism-command-line.js delete mode 100644 docs/_style/prism-master/plugins/command-line/prism-command-line.min.js delete mode 100644 docs/_style/prism-master/plugins/copy-to-clipboard/index.html delete mode 100644 docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.js delete mode 100644 docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js delete mode 100644 docs/_style/prism-master/plugins/custom-class/index.html delete mode 100644 docs/_style/prism-master/plugins/custom-class/prism-custom-class.js delete mode 100644 docs/_style/prism-master/plugins/custom-class/prism-custom-class.min.js delete mode 100644 docs/_style/prism-master/plugins/data-uri-highlight/index.html delete mode 100644 docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.js delete mode 100644 docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.min.js delete mode 100644 docs/_style/prism-master/plugins/file-highlight/index.html delete mode 100644 docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.js delete mode 100644 docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.min.js delete mode 100644 docs/_style/prism-master/plugins/highlight-keywords/index.html delete mode 100644 docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.js delete mode 100644 docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.min.js delete mode 100644 docs/_style/prism-master/plugins/index.html delete mode 100644 docs/_style/prism-master/plugins/jsonp-highlight/index.html delete mode 100644 docs/_style/prism-master/plugins/jsonp-highlight/prism-jsonp-highlight.js delete mode 100644 docs/_style/prism-master/plugins/jsonp-highlight/prism-jsonp-highlight.min.js delete mode 100644 docs/_style/prism-master/plugins/keep-markup/index.html delete mode 100644 docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.js delete mode 100644 docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.min.js delete mode 100644 docs/_style/prism-master/plugins/line-highlight/index.html delete mode 100644 docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.css delete mode 100644 docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.js delete mode 100644 docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.min.js delete mode 100644 docs/_style/prism-master/plugins/line-numbers/index.html delete mode 100644 docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.css delete mode 100644 docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.js delete mode 100644 docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.min.js delete mode 100644 docs/_style/prism-master/plugins/normalize-whitespace/demo.html delete mode 100644 docs/_style/prism-master/plugins/normalize-whitespace/index.html delete mode 100644 docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.js delete mode 100644 docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.min.js delete mode 100644 docs/_style/prism-master/plugins/previewers/index.html delete mode 100644 docs/_style/prism-master/plugins/previewers/prism-previewers.css delete mode 100644 docs/_style/prism-master/plugins/previewers/prism-previewers.js delete mode 100644 docs/_style/prism-master/plugins/previewers/prism-previewers.min.js delete mode 100644 docs/_style/prism-master/plugins/remove-initial-line-feed/index.html delete mode 100644 docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.js delete mode 100644 docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.min.js delete mode 100644 docs/_style/prism-master/plugins/show-invisibles/index.html delete mode 100644 docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.css delete mode 100644 docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.js delete mode 100644 docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.min.js delete mode 100644 docs/_style/prism-master/plugins/show-language/index.html delete mode 100644 docs/_style/prism-master/plugins/show-language/prism-show-language.js delete mode 100644 docs/_style/prism-master/plugins/show-language/prism-show-language.min.js delete mode 100644 docs/_style/prism-master/plugins/toolbar/index.html delete mode 100644 docs/_style/prism-master/plugins/toolbar/prism-toolbar.css delete mode 100644 docs/_style/prism-master/plugins/toolbar/prism-toolbar.js delete mode 100644 docs/_style/prism-master/plugins/toolbar/prism-toolbar.min.js delete mode 100644 docs/_style/prism-master/plugins/unescaped-markup/index.html delete mode 100644 docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.css delete mode 100644 docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.js delete mode 100644 docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.min.js delete mode 100644 docs/_style/prism-master/plugins/wpd/index.html delete mode 100644 docs/_style/prism-master/plugins/wpd/prism-wpd.css delete mode 100644 docs/_style/prism-master/plugins/wpd/prism-wpd.js delete mode 100644 docs/_style/prism-master/plugins/wpd/prism-wpd.min.js delete mode 100644 docs/_style/prism-master/prefixfree.min.js delete mode 100644 docs/_style/prism-master/prism.js delete mode 100644 docs/_style/prism-master/style.css delete mode 100644 docs/_style/prism-master/templates/footer.html delete mode 100644 docs/_style/prism-master/templates/header-download.html delete mode 100644 docs/_style/prism-master/templates/header-main.html delete mode 100644 docs/_style/prism-master/templates/header-plugins.html delete mode 100644 docs/_style/prism-master/test-suite.html delete mode 100644 docs/_style/prism-master/test.html delete mode 100644 docs/_style/prism-master/tests/helper/prism-loader.js delete mode 100644 docs/_style/prism-master/tests/helper/test-case.js delete mode 100644 docs/_style/prism-master/tests/helper/test-discovery.js delete mode 100644 docs/_style/prism-master/tests/helper/token-stream-transformer.js delete mode 100644 docs/_style/prism-master/tests/languages/abap/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/eol-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/string-template_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/abap/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/actionscript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/actionscript/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ada/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/directive-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/directive-flags_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/directive-inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apacheconf/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/assignment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/dfn_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/dyadic-operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/monadic-operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/statement_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/apl/system-function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/class_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/applescript/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/arff/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/arff/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/arff/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/arff/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/admonition_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/attribute-entry_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/attributes_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/callout_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/comment-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/entity_feature.js delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/entity_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/hr_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/indented-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/line-continuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/list-label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/list-punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/literal-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/macro_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/other-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/page-break_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/passthrough-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/replacement_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/table_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asciidoc/title_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/opcode_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/register_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/asm6502/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/aspnet/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/aspnet/page-directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autohotkey/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/autoit/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/arithmetic_environment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/command_substitution_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/shebang_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bash/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/basic/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/batch/command_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/batch/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/batch/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/c_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bison/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/brainfuck/all_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/bro/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/c+pure/c_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/c/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/c/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/c/macro_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/c/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/c/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/asm_reference.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/boolean.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/comment.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/instructions.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/keywords.test delete mode 100644 docs/_style/prism-master/tests/languages/cil/strings.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/issue1340.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clike/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clojure/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clojure/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clojure/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/clojure/operator_and_punctuation.test delete mode 100644 docs/_style/prism-master/tests/languages/clojure/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript+haml/coffeescript_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript+pug/coffeescript_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/block-regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/class-member_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/inline-javascript_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/coffeescript/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp+pure/cpp_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/cpp/raw_string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/crystal/attribute_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/crystal/expansion_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/crystal/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/crystal/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp+aspnet/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/generic_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/issue1091.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/issue1365.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/issue1371.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/issue806.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/preprocessor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csharp/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csp/directive_no_value_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csp/directive_with_source_expression_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csp/safe_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/csp/unsafe_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/entity_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/hexcode_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/unit_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css!+css-extras/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css+haml/css+haml_usage.test delete mode 100644 docs/_style/prism-master/tests/languages/css+http/css_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/css+textile/css_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/css/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/css/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/register_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/d/token-string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/dart/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/dart/metadata_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/dart/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/dart/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/diff/coord_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/diff/diff_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/django/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/django/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/docker/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/docker/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/docker/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/eiffel/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/atom_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/attribute_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/capture_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/issue1392.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/issue775.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elixir/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/hvariable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/import_statement_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/elm/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erb/erb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erb/erb_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/atom_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/erlang/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/flow/flow-punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/flow/function-variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/flow/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/flow/type_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran+pure/fortran_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fortran/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/annotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/computation-expression_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/issue1480.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/preprocessor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/fsharp/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gcode/checksum_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gcode/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gcode/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gcode/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gcode/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gedcom/level_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gedcom/line-value_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gedcom/pointer_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gedcom/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/feature_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/outline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/pystring_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/scenario_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/table_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/gherkin/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/command_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/commit_sha1_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/coord_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/diff_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/git/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/glsl/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/glsl/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/glsl/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/glsl/preprocessor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/go/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/graphql/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/annotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/issue1049.js delete mode 100644 docs/_style/prism-master/tests/languages/groovy/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/shebang_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/spock-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/groovy/string-interpolation_feature.js delete mode 100644 docs/_style/prism-master/tests/languages/groovy/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/code_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/doctype_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/multiline-code_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/multiline-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haml/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars+pug/handlebars_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/handlebars_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/handlebars/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/hvariable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/import_statement_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haskell/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/metadata_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/preprocessor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/reification_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/haxe/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hpkp/safe_maxage_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hpkp/sha256_pin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hpkp/unsafe_maxage_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hsts/include_subdomains_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hsts/preload_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hsts/safe_maxage_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/hsts/unsafe_maxage_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/http/header-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/http/request-line_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/http/response-status_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ichigojam/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/builtin-keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/icon/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/position_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/title_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/inform7/verb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ini/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ini/key_value_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ini/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/io/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/io/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/io/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/io/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/adverb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/conjunction_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/j/verb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/function_featrue.test delete mode 100644 docs/_style/prism-master/tests/languages/java/generics_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/issue1351.test delete mode 100644 docs/_style/prism-master/tests/languages/java/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/module_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/java/package_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript+haml/javascript_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript+http/javascript_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/class-method_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/function-variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/issue1337.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/issue1340.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/issue1397.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/issue1526.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/supposed-classes_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/supposed-function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/template-string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javascript/try-catch_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javastacktrace/more_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javastacktrace/stack-frame_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/javastacktrace/summary_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/jolie/deployment_features.test delete mode 100644 docs/_style/prism-master/tests/languages/jolie/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/jolie/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/jolie/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json+http/json-suffix_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/json+http/json_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/json/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/null_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/json/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1103.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1235.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1236.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1294.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1335.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1342.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1356.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1364.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1408.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/issue1421.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/plain-text_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/jsx/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/julia/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/bold_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/keyman/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/annotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/kotlin/raw-string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/cdata_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/equation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/headline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/latex/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/less+haml/less_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/less+pug/less_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/less/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/less/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/less/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/less/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/less/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/liquid/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/liquid/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/liquid/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/liquid/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/car_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/declare_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/defun_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/defvar_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/heading_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/interactive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/lambda-feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/lisp-property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/number_boolean.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/quoted-symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/splice_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lisp/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/argument_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/identifier_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/interpolated-string.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/keyword-operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/livescript/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lolcode/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/lua/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/makefile/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown+haml/markdown_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown+pug/markdown_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/blockquote_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/bold_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/code_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/hr_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/italic_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/list_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/strike_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/title_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/url-reference_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markdown/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup!+css+javascript/issue1240.test delete mode 100644 docs/_style/prism-master/tests/languages/markup!+css/css_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markup!+javascript/javascript_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+actionscript/xml_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+css+wiki/table-tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+haml/markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+http/html_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+http/xml-suffix_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+http/xml_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+javascript+csharp+aspnet/script_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+php/php_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+pug/markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup+tt2/tt2_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/cdata_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/doctype_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/entity_feature.js delete mode 100644 docs/_style/prism-master/tests/languages/markup/entity_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/issue585.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/issue888.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/prolog_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/tag_attribute_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/markup/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/matlab/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/code_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/flag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mel/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/parameter_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/mizar/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/preprocessor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/monkey/type-char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/n4js/annotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/n4js/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/register_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nasm/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nginx/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nginx/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nginx/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nim/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/antiquotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nix/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/nsis/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/objectivec/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/objectivec/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/objectivec/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ocaml/type_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl+c/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl+c/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl+c/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl+c/type_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl+cpp/type_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/opencl/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/atom_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/oz/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parigp/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/escape_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/expression_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/parser-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/parser/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pascal/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pascal/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pascal/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pascal/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pascal/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/filehandle_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/perl/vstring_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php!+php-extras/global_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php!+php-extras/scope_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php!+php-extras/this_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/delimiter_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/package_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/shell-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/string-interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/php/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/plsql/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/plsql/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/plsql/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/issue1407.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/namespace_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/powershell/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/processing/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/processing/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/processing/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/processing/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/processing/type_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/prolog/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/properties/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/properties/key_value_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/protobuf/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/protobuf/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/code_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/doctype_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/flow-control_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/mixin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/multiline-plain-text_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/multiline-script_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/plain-text_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/script_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pug/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/datatype_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/heredoc_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/puppet/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/special_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/pure/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/decorator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/issue1355.test delete mode 100644 docs/_style/prism-master/tests/languages/python/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/string-interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/python/triple-quoted-string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/adverb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/datetime_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/q/verb_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/qore/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/ellipsis_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/percent-operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/r/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/character_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/constructor_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/label_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/reason/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/command-line-option_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/doctest-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/field_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/hr_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/link-target_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/link_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/list-bullet_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/literal-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/quoted-literal-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/substitution-def_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/table_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rest/title_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/character_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/date_time_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/reference_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rip/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/component_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/optional_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/value_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/roboconf/wildcard_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/issue1336.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/method_definition_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/regex_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/ruby/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/attribute_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/char_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/closure-params_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/issue1339.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/issue1353.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/lifetime-annotation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/macro-rules_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/rust/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/datalines_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/datetime_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sas/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sass/atrule-line_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sass/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sass/property-line_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sass/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sass/variable-line_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scala/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scala/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scala/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scala/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scala/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/character_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/issue1331.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scheme/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss+haml/scss_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/scss+pug/scss_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/null_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/placeholder_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/statement_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/scss/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/block-arguments_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/character_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smalltalk/temporary-variables_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/attr-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/smarty_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/smarty/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/command-arg.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/literal_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/parameter_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/soy_in_markup_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/soy/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/sql/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus+pug/stylus_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/atrule-declaration_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/func_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/hexcode_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/property-declaration_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/selector_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/stylus/variable-declaration_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/atrule_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/swift/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/bail_out_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/pass_fail_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/plan_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/pragma_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/version_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tap/yamlish_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/scope_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tcl/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/acronym_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/block-tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/footnote_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/image_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/link-ref_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/link_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/list_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/mark_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/textile/table_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/date_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/key_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/toml/table_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tsx/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/delimiter_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/string-interpolation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/tt2/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig+pug/twig_inclusion.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/twig/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/typescript/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/typescript/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/class-name_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/punctuation_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vala/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vbnet/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vbnet/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/velocity/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/velocity/unparsed_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/velocity/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/velocity/velocity-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/property_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/verilog/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vhdl/vhdl-vectors_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/vim/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/date_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/visual-basic/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wasm/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wasm/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wasm/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wasm/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wasm/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/block-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/emphasis_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/heading_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/hr_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/nowiki_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/url_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/wiki/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/constant_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/directive-block-close_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/directive-block-open_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/directive-block-separator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/directive-inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/function-block_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/function-inline_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xeora/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xojo/symbol_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/axis_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/builtin_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/extension_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/function_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/keyword-operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/keyword_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/operator_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/plain-text_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/variable_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/xquery-attribute_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/xquery-comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/xquery/xquery-element_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/boolean_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/comment_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/datetime_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/directive_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/important_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/key_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/null_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/number_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/scalar_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/string_feature.test delete mode 100644 docs/_style/prism-master/tests/languages/yaml/tag_feature.test delete mode 100644 docs/_style/prism-master/tests/run.js delete mode 100644 docs/_style/prism-master/tests/testrunner-tests.js delete mode 100644 docs/_style/prism-master/themes/prism-coy.css delete mode 100644 docs/_style/prism-master/themes/prism-dark.css delete mode 100644 docs/_style/prism-master/themes/prism-funky.css delete mode 100644 docs/_style/prism-master/themes/prism-okaidia.css delete mode 100644 docs/_style/prism-master/themes/prism-solarizedlight.css delete mode 100644 docs/_style/prism-master/themes/prism-tomorrow.css delete mode 100644 docs/_style/prism-master/themes/prism-twilight.css delete mode 100644 docs/_style/prism-master/themes/prism.css delete mode 100644 docs/_style/prism-master/utopia.js delete mode 100644 docs/_style/prism-master/vendor/FileSaver.min.js delete mode 100644 docs/_style/prism-master/vendor/jszip.min.js delete mode 100644 docs/_style/prism-master/vendor/promise.js delete mode 100644 docs/_style/style.css delete mode 100644 docs/book/00-Introduction.md delete mode 100644 docs/book/00-On-Java-8.md delete mode 100644 docs/book/00-Preface.md delete mode 100644 docs/book/01-What-is-an-Object.md delete mode 100644 docs/book/02-Installing-Java-and-the-Book-Examples.md delete mode 100644 docs/book/03-Objects-Everywhere.md delete mode 100644 docs/book/04-Operators.md delete mode 100755 docs/book/05-Control-Flow.md delete mode 100644 docs/book/06-Housekeeping.md delete mode 100644 docs/book/07-Implementation-Hiding.md delete mode 100644 docs/book/08-Reuse.md delete mode 100644 docs/book/09-Polymorphism.md delete mode 100644 docs/book/10-Interfaces.md delete mode 100755 docs/book/11-Inner-Classes.md delete mode 100644 docs/book/12-Collections.md delete mode 100644 docs/book/13-Functional-Programming.md delete mode 100644 docs/book/14-Streams.md delete mode 100644 docs/book/15-Exceptions.md delete mode 100644 docs/book/16-Validating-Your-Code.md delete mode 100644 docs/book/17-Files.md delete mode 100755 docs/book/18-Strings.md delete mode 100755 docs/book/19-Type-Information.md delete mode 100644 docs/book/20-Generics.md delete mode 100755 docs/book/21-Arrays.md delete mode 100644 docs/book/22-Enumerations.md delete mode 100644 docs/book/23-Annotations.md delete mode 100755 docs/book/24-Concurrent-Programming.md delete mode 100644 docs/book/25-Patterns.md delete mode 100644 docs/book/Appendix-Becoming-a-Programmer.md delete mode 100644 docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md delete mode 100644 docs/book/Appendix-Collection-Topics.md delete mode 100644 docs/book/Appendix-Data-Compression.md delete mode 100644 docs/book/Appendix-IO-Streams.md delete mode 100644 docs/book/Appendix-Javadoc.md delete mode 100644 docs/book/Appendix-Low-Level-Concurrency.md delete mode 100644 docs/book/Appendix-New-IO.md delete mode 100644 docs/book/Appendix-Object-Serialization.md delete mode 100644 docs/book/Appendix-Passing-and-Returning-Objects.md delete mode 100644 docs/book/Appendix-Programming-Guidelines.md delete mode 100755 docs/book/Appendix-Standard-IO.md delete mode 100644 docs/book/Appendix-Supplements.md delete mode 100644 docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md delete mode 100644 docs/book/Appendix-Understanding-equals-and-hashCode.md delete mode 100644 docs/book/GLOSSARY.md delete mode 100644 docs/favicon.ico delete mode 100644 docs/images/1545758268350.png delete mode 100644 docs/images/1545763399825.png delete mode 100644 docs/images/1545764724202.png delete mode 100644 docs/images/1545764780795.png delete mode 100644 docs/images/1545764820176.png delete mode 100644 docs/images/1545839316314.png delete mode 100644 docs/images/1545841270997.png delete mode 100644 docs/images/1554546258113.png delete mode 100644 docs/images/1554546378822.png delete mode 100644 docs/images/1554546452861.png delete mode 100644 docs/images/1554546627710.png delete mode 100644 docs/images/1554546666685.png delete mode 100644 docs/images/1554546693664.png delete mode 100644 docs/images/1554546847181.png delete mode 100644 docs/images/1554546861836.png delete mode 100644 docs/images/1554546881189.png delete mode 100644 docs/images/1554546890132.png delete mode 100644 docs/images/1561774164644.png delete mode 100644 docs/images/1562204648023.png delete mode 100644 docs/images/1562252767216.png delete mode 100644 docs/images/1562406479787.png delete mode 100644 docs/images/1562409366637.png delete mode 100644 docs/images/1562409366638.png delete mode 100644 docs/images/1562409926765.png delete mode 100644 docs/images/1562653648586.png delete mode 100644 docs/images/1562737974623.png delete mode 100644 docs/images/1562999314238.png delete mode 100644 docs/images/QQGroupQRCode.png delete mode 100644 docs/images/collection.png delete mode 100644 docs/images/cover.jpg delete mode 100644 docs/images/cover_small.jpg delete mode 100644 docs/images/designproxy.png delete mode 100644 docs/images/image-20190409114913825-4781754.png delete mode 100644 docs/images/level_1_title.png delete mode 100644 docs/images/level_2_title.png delete mode 100644 docs/images/map.png delete mode 100644 docs/images/qqgroup.png delete mode 100644 docs/images/reader.png delete mode 100644 docs/images/simple-collection-taxonomy.png delete mode 100755 docs/index.html delete mode 100644 docs/sidebar.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 907a705a..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,45 +0,0 @@ -## 如何贡献项目 - -领取或创建新的 [Issue](https://github.com/lingcoder/OnJava8/issues),如 [issue 14](https://github.com/lingcoder/OnJava8/issues/14) ,在编辑栏的右侧添加自己为 `Assignee`。 - -在 [GitHub](https://github.com/lingcoder/OnJava8/fork) 上 `fork` 项目到自己的仓库,你的如 `user_name/OnJava8`,然后 `clone` 到本地,并设置用户信息。 - -```bash -$ git clone git@github.com:user_name/OnJava8.git - -$ cd OnJava8 -``` - -修改代码后提交,并推送到自己的仓库,注意修改提交消息为对应 Issue 号和描述。 - -```bash -# 更新内容 - -$ git commit -a -s - -# 在提交对话框里添加类似如下内容 "Fix issue #14: 描述你的修改内容" - -$ git push -``` - -在 [GitHub](https://github.com/lingcoder/OnJava8/pulls) 上提交 `Pull Request`,添加标签,并邀请维护者进行 `Review`。 - -定期使用源项目仓库内容同步更新自己仓库的内容。 - -```bash -$ git remote add upstream https://github.com/lingcoder/OnJava8 - -$ git fetch upstream - -$ git rebase upstream/master - -$ git push -f origin master -``` - -## 视频演示教程 - -- https://www.bilibili.com/video/av58040840 - -## 排版规范 - -本开源书籍排版布局和翻译风格上参考**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) diff --git a/README.md b/README.md index 9e9e83bd..576a7651 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,12 @@ # 《On Java 8》中文版 -## 书籍简介 +## 通知公告 -- 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 +图灵要出On Java 8的中文版了! +非常感谢大家长久以来对本项目的支持和贡献,出于对原作者的敬意和对版权尊重,本项目将于2021年2月26日起闭源。 +之后,我将作为On Java 8的特邀审读嘉宾,继续贡献自己的一份力量! - -## 传送门 - -- 目录阅读:[进入](https://github.com/LingCoder/OnJava8/blob/master/SUMMARY.md) - -- GitHub Pages 完整阅读:[进入](https://lingcoder.github.io/OnJava8/) - -- Gitee Pages 完整阅读:[进入](https://lingcoder.gitee.io/onjava8/) - -## 翻译进度 - -- [x] [前言](docs/book/00-Preface.md) -- [x] [简介](docs/book/00-Introduction.md) -- [x] [第一章 对象的概念](docs/book/01-What-is-an-Object.md) -- [x] [第二章 安装 Java 和本书用例](docs/book/02-Installing-Java-and-the-Book-Examples.md) -- [x] [第三章 万物皆对象](docs/book/03-Objects-Everywhere.md) -- [x] [第四章 运算符](docs/book/04-Operators.md) -- [x] [第五章 控制流](docs/book/05-Control-Flow.md) -- [x] [第六章 初始化和清理](docs/book/06-Housekeeping.md) -- [x] [第七章 封装](docs/book/07-Implementation-Hiding.md) -- [x] [第八章 复用](docs/book/08-Reuse.md) -- [x] [第九章 多态](docs/book/09-Polymorphism.md) -- [x] [第十章 接口](docs/book/10-Interfaces.md) -- [x] [第十一章 内部类](docs/book/11-Inner-Classes.md) -- [x] [第十二章 集合](docs/book/12-Collections.md) -- [x] [第十三章 函数式编程](docs/book/13-Functional-Programming.md) -- [x] [第十四章 流式编程](docs/book/14-Streams.md) -- [x] [第十五章 异常](docs/book/15-Exceptions.md) -- [x] [第十六章 代码校验](docs/book/16-Validating-Your-Code.md) -- [x] [第十七章 文件](docs/book/17-Files.md) -- [x] [第十八章 字符串](docs/book/18-Strings.md) -- [x] [第十九章 类型信息](docs/book/19-Type-Information.md) -- [x] [第二十章 泛型](docs/book/20-Generics.md) -- [x] [第二十一章 数组](docs/book/21-Arrays.md) -- [x] [第二十二章 枚举](docs/book/22-Enumerations.md) -- [x] [第二十三章 注解](docs/book/23-Annotations.md) -- [x] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md) -- [ ] [第二十五章 设计模式](docs/book/25-Patterns.md) -- [x] [附录:补充](docs/book/Appendix-Supplements.md) -- [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md) -- [x] [附录:文档注释](docs/book/Appendix-Javadoc.md) -- [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md) -- [x] [附录:流式 IO](docs/book/Appendix-IO-Streams.md) -- [x] [附录:标准 IO](docs/book/Appendix-Standard-IO.md) -- [x] [附录:新 IO](docs/book/Appendix-New-IO.md) -- [x] [附录:理解 equals 和 hashCode 方法](docs/book/Appendix-Understanding-equals-and-hashCode.md) -- [x] [附录:集合主题](docs/book/Appendix-Collection-Topics.md) -- [x] [附录:并发底层原理](docs/book/Appendix-Low-Level-Concurrency.md) -- [x] [附录:数据压缩](docs/book/Appendix-Data-Compression.md) -- [x] [附录:对象序列化](docs/book/Appendix-Object-Serialization.md) -- [ ] [附录:静态语言类型检查](docs/book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) -- [x] [附录:C++ 和 Java 的优良传统](docs/book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) -- [ ] [附录:成为一名程序员](docs/book/Appendix-Becoming-a-Programmer.md) +想要继续关注本书出版进度,请访问图灵社区:https://www.ituring.com.cn/book/2935 ## 一起交流 @@ -66,54 +16,6 @@
QQGroupQRCode
- -## 大事记 - -- 2018-11-20 初始化项目 - -## 原书资料 - -
-cover_small -
- -- 作者: Bruce Eckel -- ISBN: 9780981872520 -- 页数:2038 -- 发行:仅电子版 - -## 示例代码 - -- [gradle: OnJava8-Examples](https://github.com/BruceEckel/OnJava8-Examples) -- [maven: OnJava8-Examples-Maven](https://github.com/sjsdfg/OnJava8-Examples-Maven) - -## 贡献者 - -- 主译:[LingCoder](https://github.com/LingCoder),[sjsdfg](https://github.com/sjsdfg),[xiangflight](https://github.com/xiangflight) -- 参译:[Langdon-Chen](https://github.com/Langdon-Chen),[1326670425](https://github.com/1326670425),[LortSir](https://github.com/LortSir) -- 校对:[LingCoder](https://github.com/LingCoder),[jason31520](https://github.com/jason31520),[xiangflight](https://github.com/xiangflight),[nickChenyx](https://github.com/nickChenyx) - -## 翻译说明 - -1. 本书排版布局和翻译风格上参考**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) -2. 采用第一人称叙述。 -3. 由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。所以本人在翻译过程中,去除了部分主题无关内容、重复描写。 -4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 -5. 由于译者个人能力、时间有限,如有翻译错误和笔误的地方,还请大家批评指正! - -## 如何参与 - -如果你想对本书做出一些贡献的话 -可以在阅读本书过程中帮忙校对,找 bug 错别字等等 -可以提出专业方面的修改建议 -可以把一些不尽人意的语句翻译的更好更有趣 -对于以上各类建议,请以 issue 或 pr 的形式发送,我看到之后会尽快处理 -使用 MarkDown 编辑器,md 语法格式进行文档翻译及排版工作 -完成之后 PullRequest -如没问题的话,我会合并到主分支 -如不熟悉 md 排版,可不必纠结,我会在合并 pr 时代为排版 -如还有其它问题,欢迎发送 issue,谢谢~ - ## 开源协议 本项目基于 MIT 协议开源。 diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index c7d1abb2..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,448 +0,0 @@ - - -### [译者的话](README.md) - -### [封面](book/00-On-Java-8.md) - -### [前言](book/00-Preface.md) - -### [简介](book/00-Introduction.md) - -### [第一章 对象的概念](book/01-What-is-an-Object.md) - * [抽象](book/01-What-is-an-Object.md#抽象) - * [接口](book/01-What-is-an-Object.md#接口) - * [服务提供](book/01-What-is-an-Object.md#服务提供) - * [封装](book/01-What-is-an-Object.md#封装) - * [复用](book/01-What-is-an-Object.md#复用) - * [继承](book/01-What-is-an-Object.md#继承) - * [多态](book/01-What-is-an-Object.md#多态) - * [单继承结构](book/01-What-is-an-Object.md#单继承结构) - * [集合](book/01-What-is-an-Object.md#集合) - * [对象创建与生命周期](book/01-What-is-an-Object.md#对象创建与生命周期) - * [异常处理](book/01-What-is-an-Object.md#异常处理) - * [本章小结](book/01-What-is-an-Object.md#本章小结) - -### [第二章 安装Java和本书用例](book/02-Installing-Java-and-the-Book-Examples.md) - * [编辑器](book/02-Installing-Java-and-the-Book-Examples.md#编辑器) - * [Shell](book/02-Installing-Java-and-the-Book-Examples.md#Shell) - * [Java安装](book/02-Installing-Java-and-the-Book-Examples.md#Java安装) - * [校验安装](book/02-Installing-Java-and-the-Book-Examples.md#校验安装) - * [安装和运行代码示例](book/02-Installing-Java-and-the-Book-Examples.md#安装和运行代码示例) - -### [第三章 万物皆对象](book/03-Objects-Everywhere.md) - * [对象操纵](book/03-Objects-Everywhere.md#对象操纵) - * [对象创建](book/03-Objects-Everywhere.md#对象创建) - * [代码注释](book/03-Objects-Everywhere.md#代码注释) - * [对象清理](book/03-Objects-Everywhere.md#对象清理) - * [类的创建](book/03-Objects-Everywhere.md#类的创建) - * [程序编写](book/03-Objects-Everywhere.md#程序编写) - * [小试牛刀](book/03-Objects-Everywhere.md#小试牛刀) - * [编码风格](book/03-Objects-Everywhere.md#编码风格) - * [本章小结](book/03-Objects-Everywhere.md#本章小结) - -### [第四章 运算符](book/04-Operators.md) - * [开始使用](book/04-Operators.md#开始使用) - * [优先级](book/04-Operators.md#优先级) - * [赋值](book/04-Operators.md#赋值) - * [算术运算符](book/04-Operators.md#算术运算符) - * [递增和递减](book/04-Operators.md#递增和递减) - * [关系运算符](book/04-Operators.md#关系运算符) - * [逻辑运算符](book/04-Operators.md#逻辑运算符) - * [字面值常量](book/04-Operators.md#字面值常量) - * [位运算符](book/04-Operators.md#位运算符) - * [移位运算符](book/04-Operators.md#移位运算符) - * [三元运算符](book/04-Operators.md#三元运算符) - * [字符串运算符](book/04-Operators.md#字符串运算符) - * [常见陷阱](book/04-Operators.md#常见陷阱) - * [类型转换](book/04-Operators.md#类型转换) - * [Java没有sizeof](book/04-Operators.md#Java没有sizeof) - * [运算符总结](book/04-Operators.md#运算符总结) - * [本章小结](book/04-Operators.md#本章小结) - -### [第五章 控制流](book/05-Control-Flow.md) - * [true和false](book/05-Control-Flow.md#true和false) - * [if-else](book/05-Control-Flow.md#if-else) - * [迭代语句](book/05-Control-Flow.md#迭代语句) - * [for-in 语法](book/05-Control-Flow.md#for-in-语法) - * [return](book/05-Control-Flow.md#return) - * [break 和 continue](book/05-Control-Flow.md#break-和-continue) - * [臭名昭著的 goto](book/05-Control-Flow.md#臭名昭著的-goto) - * [switch](book/05-Control-Flow.md#switch) - * [switch 字符串](book/05-Control-Flow.md#switch-字符串) - * [本章小结](book/05-Control-Flow.md#本章小结) - -### [第六章 初始化和清理](book/06-Housekeeping.md) - * [利用构造器保证初始化](book/06-Housekeeping.md#利用构造器保证初始化) - * [方法重载](book/06-Housekeeping.md#方法重载) - * [无参构造器](book/06-Housekeeping.md#无参构造器) - * [this关键字](book/06-Housekeeping.md#this关键字) - * [垃圾回收器](book/06-Housekeeping.md#垃圾回收器) - * [成员初始化](book/06-Housekeeping.md#成员初始化) - * [构造器初始化](book/06-Housekeeping.md#构造器初始化) - * [数组初始化](book/06-Housekeeping.md#数组初始化) - * [枚举类型](book/06-Housekeeping.md#枚举类型) - * [本章小结](book/06-Housekeeping.md#本章小结) - -### [第七章 封装](book/07-Implementation-Hiding.md) - * [包的概念](book/07-Implementation-Hiding.md#包的概念) - * [访问权限修饰符](book/07-Implementation-Hiding.md#访问权限修饰符) - * [接口和实现](book/07-Implementation-Hiding.md#接口和实现) - * [类访问权限](book/07-Implementation-Hiding.md#类访问权限) - * [本章小结](book/07-Implementation-Hiding.md#本章小结) - -### [第八章 复用](book/08-Reuse.md) - * [组合语法](book/08-Reuse.md#组合语法) - * [继承语法](book/08-Reuse.md#继承语法) - * [委托](book/08-Reuse.md#委托) - * [结合组合与继承](book/08-Reuse.md#结合组合与继承) - * [组合与继承的选择](book/08-Reuse.md#组合与继承的选择) - * [protected](book/08-Reuse.md#protected) - * [向上转型](book/08-Reuse.md#向上转型) - * [final关键字](book/08-Reuse.md#final关键字) - * [类初始化和加载](book/08-Reuse.md#类初始化和加载) - * [本章小结](book/08-Reuse.md#本章小结) - -### [第九章 多态](book/09-Polymorphism.md) - * [向上转型回顾](book/09-Polymorphism.md#向上转型回顾) - * [转机](book/09-Polymorphism.md#转机) - * [构造器和多态](book/09-Polymorphism.md#构造器和多态) - * [协变返回类型](book/09-Polymorphism.md#协变返回类型) - * [使用继承设计](book/09-Polymorphism.md#使用继承设计) - * [本章小结](book/09-Polymorphism.md#本章小结) - -### [第十章 接口](book/10-Interfaces.md) - * [抽象类和方法](book/10-Interfaces.md#抽象类和方法) - * [接口创建](book/10-Interfaces.md#接口创建) - * [抽象类和接口](book/10-Interfaces.md#抽象类和接口) - * [完全解耦](book/10-Interfaces.md#完全解耦) - * [多接口结合](book/10-Interfaces.md#多接口结合) - * [使用继承扩展接口](book/10-Interfaces.md#使用继承扩展接口) - * [接口适配](book/10-Interfaces.md#接口适配) - * [接口字段](book/10-Interfaces.md#接口字段) - * [接口嵌套](book/10-Interfaces.md#接口嵌套) - * [接口和工厂方法模式](book/10-Interfaces.md#接口和工厂方法模式) - * [本章小结](book/10-Interfaces.md#本章小结) - -### [第十一章 内部类](book/11-Inner-Classes.md) - * [创建内部类](book/11-Inner-Classes.md#创建内部类) - * [链接外部类](book/11-Inner-Classes.md#链接外部类) - * [使用 .this 和 .new](book/11-Inner-Classes.md#使用-this-和-new) - * [内部类与向上转型](book/11-Inner-Classes.md#内部类与向上转型) - * [内部类方法和作用域](book/11-Inner-Classes.md#内部类方法和作用域) - * [匿名内部类](book/11-Inner-Classes.md#匿名内部类) - * [嵌套类](book/11-Inner-Classes.md#嵌套类) - * [为什么需要内部类](book/11-Inner-Classes.md#为什么需要内部类) - * [继承内部类](book/11-Inner-Classes.md#继承内部类) - * [内部类可以被覆盖么?](book/11-Inner-Classes.md#内部类可以被覆盖么?) - * [局部内部类](book/11-Inner-Classes.md#局部内部类) - * [内部类标识符](book/11-Inner-Classes.md#内部类标识符) - * [本章小结](book/11-Inner-Classes.md#本章小结) - -### [第十二章 集合](book/12-Collections.md) - * [泛型和类型安全的集合](book/12-Collections.md#泛型和类型安全的集合) - * [基本概念](book/12-Collections.md#基本概念) - * [添加元素组](book/12-Collections.md#添加元素组) - * [集合的打印](book/12-Collections.md#集合的打印) - * [列表List](book/12-Collections.md#列表List) - * [迭代器Iterators](book/12-Collections.md#迭代器Iterators) - * [链表LinkedList](book/12-Collections.md#链表LinkedList) - * [堆栈Stack](book/12-Collections.md#堆栈Stack) - * [集合Set](book/12-Collections.md#集合Set) - * [映射Map](book/12-Collections.md#映射Map) - * [队列Queue](book/12-Collections.md#队列Queue) - * [集合与迭代器](book/12-Collections.md#集合与迭代器) - * [for-in和迭代器](book/12-Collections.md#for-in和迭代器) - * [本章小结](book/12-Collections.md#本章小结) - -### [第十三章 函数式编程](book/13-Functional-Programming.md) - * [新旧对比](book/13-Functional-Programming.md#新旧对比) - * [Lambda表达式](book/13-Functional-Programming.md#Lambda表达式) - * [方法引用](book/13-Functional-Programming.md#方法引用) - * [函数式接口](book/13-Functional-Programming.md#函数式接口) - * [高阶函数](book/13-Functional-Programming.md#高阶函数) - * [闭包](book/13-Functional-Programming.md#闭包) - * [函数组合](book/13-Functional-Programming.md#函数组合) - * [柯里化和部分求值](book/13-Functional-Programming.md#柯里化和部分求值) - * [纯函数式编程](book/13-Functional-Programming.md#纯函数式编程) - * [本章小结](book/13-Functional-Programming.md#本章小结) - -### [第十四章 流式编程](book/14-Streams.md) - * [流支持](book/14-Streams.md#流支持) - * [流创建](book/14-Streams.md#流创建) - * [中间操作](book/14-Streams.md#中间操作) - * [Optional类](book/14-Streams.md#Optional类) - * [终端操作](book/14-Streams.md#终端操作) - * [本章小结](book/14-Streams.md#本章小结) - -### [第十五章 异常](book/15-Exceptions.md) - * [异常概念](book/15-Exceptions.md#异常概念) - * [基本异常](book/15-Exceptions.md#基本异常) - * [异常捕获](book/15-Exceptions.md#异常捕获) - * [自定义异常](book/15-Exceptions.md#自定义异常) - * [异常声明](book/15-Exceptions.md#异常声明) - * [捕获所有异常](book/15-Exceptions.md#捕获所有异常) - * [Java 标准异常](book/15-Exceptions.md#Java-标准异常) - * [使用 finally 进行清理](book/15-Exceptions.md#使用-finally-进行清理) - * [异常限制](book/15-Exceptions.md#异常限制) - * [构造器](book/15-Exceptions.md#构造器) - * [Try-With-Resources 用法](book/15-Exceptions.md#Try-With-Resources-用法) - * [异常匹配](book/15-Exceptions.md#异常匹配) - * [其他可选方式](book/15-Exceptions.md#其他可选方式) - * [异常指南](book/15-Exceptions.md#异常指南) - * [本章小结](book/15-Exceptions.md#本章小结) - * [后记:Exception Bizarro World](book/15-Exceptions.md#后记:Exception-Bizarro-World) - -### [第十六章 代码校验](book/16-Validating-Your-Code.md) - * [测试](book/16-Validating-Your-Code.md#测试) - * [前置条件](book/16-Validating-Your-Code.md#前置条件) - * [测试驱动开发](book/16-Validating-Your-Code.md#测试驱动开发) - * [日志](book/16-Validating-Your-Code.md#日志) - * [调试](book/16-Validating-Your-Code.md#调试) - * [基准测试](book/16-Validating-Your-Code.md#基准测试) - * [剖析和优化](book/16-Validating-Your-Code.md#剖析和优化) - * [风格检测](book/16-Validating-Your-Code.md#风格检测) - * [静态错误分析](book/16-Validating-Your-Code.md#静态错误分析) - * [代码重审](book/16-Validating-Your-Code.md#代码重审) - * [结对编程](book/16-Validating-Your-Code.md#结对编程) - * [重构](book/16-Validating-Your-Code.md#重构) - * [持续集成](book/16-Validating-Your-Code.md#持续集成) - * [本章小结](book/16-Validating-Your-Code.md#本章小结) - -### [第十七章 文件](book/17-Files.md) - * [文件和目录路径](book/17-Files.md#文件和目录路径) - * [目录](book/17-Files.md#目录) - * [文件系统](book/17-Files.md#文件系统) - * [路径监听](book/17-Files.md#路径监听) - * [文件查找](book/17-Files.md#文件查找) - * [文件读写](book/17-Files.md#文件读写) - * [本章小结](book/17-Files.md#本章小结) - -### [第十八章 字符串](book/18-Strings.md) - * [字符串的不可变](book/18-Strings.md#字符串的不可变) - * [+ 的重载与 StringBuilder](book/18-Strings.md#-的重载与-StringBuilder) - * [意外递归](book/18-Strings.md#意外递归) - * [字符串操作](book/18-Strings.md#字符串操作) - * [格式化输出](book/18-Strings.md#格式化输出) - * [正则表达式](book/18-Strings.md#正则表达式) - * [扫描输入](book/18-Strings.md#扫描输入) - * [StringTokenizer类](book/18-Strings.md#StringTokenizer类) - * [本章小结](book/18-Strings.md#本章小结) - -### [第十九章 类型信息](book/19-Type-Information.md) - * [为什么需要 RTTI](book/19-Type-Information.md#为什么需要-RTTI) - * [Class 对象](book/19-Type-Information.md#Class-对象) - * [类型转换检测](book/19-Type-Information.md#类型转换检测) - * [注册工厂](book/19-Type-Information.md#注册工厂) - * [类的等价比较](book/19-Type-Information.md#类的等价比较) - * [反射:运行时类信息](book/19-Type-Information.md#反射:运行时类信息) - * [动态代理](book/19-Type-Information.md#动态代理) - * [Optional类](book/19-Type-Information.md#Optional类) - * [接口和类型](book/19-Type-Information.md#接口和类型) - * [本章小结](book/19-Type-Information.md#本章小结) - -### [第二十章 泛型](book/20-Generics.md) - * [简单泛型](book/20-Generics.md#简单泛型) - * [泛型接口](book/20-Generics.md#泛型接口) - * [泛型方法](book/20-Generics.md#泛型方法) - * [构建复杂模型](book/20-Generics.md#构建复杂模型) - * [泛型擦除](book/20-Generics.md#泛型擦除) - * [补偿擦除](book/20-Generics.md#补偿擦除) - * [边界](book/20-Generics.md#边界) - * [通配符](book/20-Generics.md#通配符) - * [问题](book/20-Generics.md#问题) - * [自限定的类型](book/20-Generics.md#自限定的类型) - * [动态类型安全](book/20-Generics.md#动态类型安全) - * [泛型异常](book/20-Generics.md#泛型异常) - * [混型](book/20-Generics.md#混型) - * [潜在类型机制](book/20-Generics.md#潜在类型机制) - * [对缺乏潜在类型机制的补偿](book/20-Generics.md#对缺乏潜在类型机制的补偿) - * [Java8 中的辅助潜在类型](book/20-Generics.md#Java8-中的辅助潜在类型) - * [总结:类型转换真的如此之糟吗?](book/20-Generics.md#总结:类型转换真的如此之糟吗?) - * [进阶阅读](book/20-Generics.md#进阶阅读) - -### [第二十一章 数组](book/21-Arrays.md) - * [数组特性](book/21-Arrays.md#数组特性) - * [一等对象](book/21-Arrays.md#一等对象) - * [返回数组](book/21-Arrays.md#返回数组) - * [多维数组](book/21-Arrays.md#多维数组) - * [泛型数组](book/21-Arrays.md#泛型数组) - * [Arrays的fill方法](book/21-Arrays.md#Arrays的fill方法) - * [Arrays的setAll方法](book/21-Arrays.md#Arrays的setAll方法) - * [增量生成](book/21-Arrays.md#增量生成) - * [随机生成](book/21-Arrays.md#随机生成) - * [泛型和基本数组](book/21-Arrays.md#泛型和基本数组) - * [数组元素修改](book/21-Arrays.md#数组元素修改) - * [数组并行](book/21-Arrays.md#数组并行) - * [Arrays工具类](book/21-Arrays.md#Arrays工具类) - * [数组拷贝](book/21-Arrays.md#数组拷贝) - * [数组比较](book/21-Arrays.md#数组比较) - * [流和数组](book/21-Arrays.md#流和数组) - * [数组排序](book/21-Arrays.md#数组排序) - * [Arrays.sort 的使用](book/21-Arrays.md#Arrayssort-的使用) - * [并行排序](book/21-Arrays.md#并行排序) - * [binarySearch二分查找](book/21-Arrays.md#binarySearch二分查找) - * [parallelPrefix并行前缀](book/21-Arrays.md#parallelPrefix并行前缀) - * [本章小结](book/21-Arrays.md#本章小结) - -### [第二十二章 枚举](book/22-Enumerations.md) - * [基本 enum 特性](book/22-Enumerations.md#基本-enum-特性) - * [方法添加](book/22-Enumerations.md#方法添加) - * [switch 语句中的 enum](book/22-Enumerations.md#switch-语句中的-enum) - * [values 方法的神秘之处](book/22-Enumerations.md#values-方法的神秘之处) - * [实现而非继承](book/22-Enumerations.md#实现而非继承) - * [随机选择](book/22-Enumerations.md#随机选择) - * [使用接口组织枚举](book/22-Enumerations.md#使用接口组织枚举) - * [使用 EnumSet 替代 Flags](book/22-Enumerations.md#使用-EnumSet-替代-Flags) - * [使用 EnumMap](book/22-Enumerations.md#使用-EnumMap) - * [常量特定方法](book/22-Enumerations.md#常量特定方法) - * [多路分发](book/22-Enumerations.md#多路分发) - * [本章小结](book/22-Enumerations.md#本章小结) - -### [第二十三章 注解](book/23-Annotations.md) - * [基本语法](book/23-Annotations.md#基本语法) - * [编写注解处理器](book/23-Annotations.md#编写注解处理器) - * [使用javac处理注解](book/23-Annotations.md#使用javac处理注解) - * [基于注解的单元测试](book/23-Annotations.md#基于注解的单元测试) - * [本章小结](book/23-Annotations.md#本章小结) - -### [第二十四章 并发编程](book/24-Concurrent-Programming.md) - * [术语问题](book/24-Concurrent-Programming.md#术语问题) - * [并发的超能力](book/24-Concurrent-Programming.md#并发的超能力) - * [并发为速度而生](book/24-Concurrent-Programming.md#并发为速度而生) - * [四句格言](book/24-Concurrent-Programming.md#四句格言) - * [残酷的真相](book/24-Concurrent-Programming.md#残酷的真相) - * [本章其余部分](book/24-Concurrent-Programming.md#本章其余部分) - * [并行流](book/24-Concurrent-Programming.md#并行流) - * [创建和运行任务](book/24-Concurrent-Programming.md#创建和运行任务) - * [终止耗时任务](book/24-Concurrent-Programming.md#终止耗时任务) - * [CompletableFuture 类](book/24-Concurrent-Programming.md#CompletableFuture-类) - * [死锁](book/24-Concurrent-Programming.md#死锁) - * [构造方法非线程安全](book/24-Concurrent-Programming.md#构造方法非线程安全) - * [复杂性和代价](book/24-Concurrent-Programming.md#复杂性和代价) - * [本章小结](book/24-Concurrent-Programming.md#本章小结) - -### [第二十五章 设计模式](book/25-Patterns.md) - * [概念](book/25-Patterns.md#概念) - * [构建应用程序框架](book/25-Patterns.md#构建应用程序框架) - * [面向实现](book/25-Patterns.md#面向实现) - * [工厂模式](book/25-Patterns.md#工厂模式) - * [函数对象](book/25-Patterns.md#函数对象) - * [改变接口](book/25-Patterns.md#改变接口) - * [解释器:运行时的弹性](book/25-Patterns.md#解释器:运行时的弹性) - * [回调](book/25-Patterns.md#回调) - * [多次调度](book/25-Patterns.md#多次调度) - * [模式重构](book/25-Patterns.md#模式重构) - * [抽象用法](book/25-Patterns.md#抽象用法) - * [多次派遣](book/25-Patterns.md#多次派遣) - * [访问者模式](book/25-Patterns.md#访问者模式) - * [RTTI的优劣](book/25-Patterns.md#RTTI的优劣) - * [本章小结](book/25-Patterns.md#本章小结) - -### [附录:成为一名程序员](book/Appendix-Becoming-a-Programmer.md) - * [如何开始](book/Appendix-Becoming-a-Programmer.md#如何开始) - * [码农生涯](book/Appendix-Becoming-a-Programmer.md#码农生涯) - * [百分之五的神话](book/Appendix-Becoming-a-Programmer.md#百分之五的神话) - * [重在动手](book/Appendix-Becoming-a-Programmer.md#重在动手) - * [像打字般编程](book/Appendix-Becoming-a-Programmer.md#像打字般编程) - * [做你喜欢的事](book/Appendix-Becoming-a-Programmer.md#做你喜欢的事) - -### [附录:静态语言类型检查](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md) - * [前言](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#前言) - * [静态类型检查和测试](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态类型检查和测试) - * [如何提升打字](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#如何提升打字) - * [生产力的成本](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#生产力的成本) - * [静态和动态](book/Appendix-Benefits-and-Costs-of-Static-Type-Checking.md#静态和动态) - -### [附录:集合主题](book/Appendix-Collection-Topics.md) - * [示例数据](book/Appendix-Collection-Topics.md#示例数据) - * [List行为](book/Appendix-Collection-Topics.md#List行为) - * [Set行为](book/Appendix-Collection-Topics.md#Set行为) - * [在Map中使用函数式操作](book/Appendix-Collection-Topics.md#在Map中使用函数式操作) - * [选择Map片段](book/Appendix-Collection-Topics.md#选择Map片段) - * [填充集合](book/Appendix-Collection-Topics.md#填充集合) - * [使用享元(Flyweight)自定义Collection和Map](book/Appendix-Collection-Topics.md#使用享元(Flyweight)自定义Collection和Map) - * [集合功能](book/Appendix-Collection-Topics.md#集合功能) - * [可选操作](book/Appendix-Collection-Topics.md#可选操作) - * [Set和存储顺序](book/Appendix-Collection-Topics.md#Set和存储顺序) - * [队列](book/Appendix-Collection-Topics.md#队列) - * [理解Map](book/Appendix-Collection-Topics.md#理解Map) - * [集合工具类](book/Appendix-Collection-Topics.md#集合工具类) - * [持有引用](book/Appendix-Collection-Topics.md#持有引用) - * [Java 1.0 / 1.1 的集合类](book/Appendix-Collection-Topics.md#Java-10-11-的集合类) - * [本章小结](book/Appendix-Collection-Topics.md#本章小结) - -### [附录:数据压缩](book/Appendix-Data-Compression.md) - * [使用 Gzip 简单压缩](book/Appendix-Data-Compression.md#使用-Gzip-简单压缩) - * [使用 zip 多文件存储](book/Appendix-Data-Compression.md#使用-zip-多文件存储) - * [Java 的 jar](book/Appendix-Data-Compression.md#Java-的-jar) - -### [附录:流式IO](book/Appendix-IO-Streams.md) - * [输入流类型](book/Appendix-IO-Streams.md#输入流类型) - * [输出流类型](book/Appendix-IO-Streams.md#输出流类型) - * [添加属性和有用的接口](book/Appendix-IO-Streams.md#添加属性和有用的接口) - * [Reader和Writer](book/Appendix-IO-Streams.md#Reader和Writer) - * [RandomAccessFile类](book/Appendix-IO-Streams.md#RandomAccessFile类) - * [IO流典型用途](book/Appendix-IO-Streams.md#IO流典型用途) - * [本章小结](book/Appendix-IO-Streams.md#本章小结) - -### [附录:文档注释](book/Appendix-Javadoc.md) - * [句法规则](book/Appendix-Javadoc.md#句法规则) - * [内嵌 HTML](book/Appendix-Javadoc.md#内嵌-HTML) - * [示例标签](book/Appendix-Javadoc.md#示例标签) - * [文档示例](book/Appendix-Javadoc.md#文档示例) - -### [附录:并发底层原理](book/Appendix-Low-Level-Concurrency.md) - * [什么是线程?](book/Appendix-Low-Level-Concurrency.md#什么是线程?) - * [异常捕获](book/Appendix-Low-Level-Concurrency.md#异常捕获) - * [资源共享](book/Appendix-Low-Level-Concurrency.md#资源共享) - * [volatile 关键字](book/Appendix-Low-Level-Concurrency.md#volatile-关键字) - * [原子性](book/Appendix-Low-Level-Concurrency.md#原子性) - * [临界区](book/Appendix-Low-Level-Concurrency.md#临界区) - * [库组件](book/Appendix-Low-Level-Concurrency.md#库组件) - * [本章小结](book/Appendix-Low-Level-Concurrency.md#本章小结) - -### [附录:新IO](book/Appendix-New-IO.md) - * [ByteBuffer](book/Appendix-New-IO.md#ByteBuffer) - * [数据转换](book/Appendix-New-IO.md#数据转换) - * [基本类型获取](book/Appendix-New-IO.md#基本类型获取) - * [视图缓冲区](book/Appendix-New-IO.md#视图缓冲区) - * [缓冲区数据操作](book/Appendix-New-IO.md#缓冲区数据操作) - * [ 内存映射文件](book/Appendix-New-IO.md#-内存映射文件) - * [文件锁定](book/Appendix-New-IO.md#文件锁定) - -### [附录:对象序列化](book/Appendix-Object-Serialization.md) - * [查找类](book/Appendix-Object-Serialization.md#查找类) - * [控制序列化](book/Appendix-Object-Serialization.md#控制序列化) - * [使用持久化](book/Appendix-Object-Serialization.md#使用持久化) - * [XML](book/Appendix-Object-Serialization.md#XML) - -### [附录:对象传递和返回](book/Appendix-Passing-and-Returning-Objects.md) - * [传递引用](book/Appendix-Passing-and-Returning-Objects.md#传递引用) - * [本地拷贝](book/Appendix-Passing-and-Returning-Objects.md#本地拷贝) - * [控制克隆](book/Appendix-Passing-and-Returning-Objects.md#控制克隆) - * [不可变类](book/Appendix-Passing-and-Returning-Objects.md#不可变类) - * [本章小结](book/Appendix-Passing-and-Returning-Objects.md#本章小结) - -### [附录:编程指南](book/Appendix-Programming-Guidelines.md) - * [设计](book/Appendix-Programming-Guidelines.md#设计) - * [实现](book/Appendix-Programming-Guidelines.md#实现) - -### [附录:标准IO](book/Appendix-Standard-IO.md) - * [从标准输入中读取](book/Appendix-Standard-IO.md#从标准输入中读取) - * [将 System.out 转换成 PrintWriter](book/Appendix-Standard-IO.md#将-Systemout-转换成-PrintWriter) - * [重定向标准 I/O](book/Appendix-Standard-IO.md#重定向标准-IO) - * [执行控制](book/Appendix-Standard-IO.md#执行控制) - -### [附录:补充](book/Appendix-Supplements.md) - * [可下载的补充](book/Appendix-Supplements.md#可下载的补充) - * [通过Thinking-in-C来巩固Java基础](book/Appendix-Supplements.md#通过Thinking-in-C来巩固Java基础) - * [Hand-On Java 电子演示文稿](book/Appendix-Supplements.md#Hand-On-Java-电子演示文稿) - -### [附录:C++和Java的优良传统](book/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.md) - -### [附录:理解equals和hashCode方法](book/Appendix-Understanding-equals-and-hashCode.md) - * [equals规范](book/Appendix-Understanding-equals-and-hashCode.md#equals规范) - * [哈希和哈希码](book/Appendix-Understanding-equals-and-hashCode.md#哈希和哈希码) - * [调优 HashMap](book/Appendix-Understanding-equals-and-hashCode.md#调优-HashMap) - -### [词汇表](book/GLOSSARY.md) \ No newline at end of file diff --git a/book.json b/book.json deleted file mode 100644 index 36f77afe..00000000 --- a/book.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "title": "《On Java 8》中文版", - "author": "LingCoder", - "description": "", - "language": "zh-hans", - "gitbook": "3.2.3", - "styles": { - "website": "styles/website.css", - "ebook": "styles/ebook.css", - "pdf": "styles/pdf.css", - "mobi": "styles/mobi.css", - "epub": "styles/epub.css" - }, - "plugins": [ - "splitter", - "edit-link", - "search-pro", - "emphasize", - "toggle-chapters", - "katex", - "mermaid-gb3", - "advanced-emoji", - "include-codeblock" - ], - "pluginsConfig": { - "edit-link": { - "base": "https://github.com/lingcoder/OnJava8/edit/master", - "label": "Edit This Page" - } - } -} diff --git a/cover.jpg b/cover.jpg deleted file mode 100644 index 1819c2744826178e730b4927c9c7b24ebd06d111..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 117331 zcmbTdWl$YY_%(Rp;_iBJcXx*$?(XjHc5w&<2<|~cfZ!I~g1ZGLNN@;p;o=U<|9!W1 ztM=3G_SE#3>6+@(^HlfqoO9-V^?e(Fr6i{)2Y`VA0AM~Y!23Et27rWsfQSH(goucU zjEsbWhKG)Zii$>zgNunrMnXYOMgjs+(y}v9QnS*4K#T%RtejlDyu1_)LSlm4qU=1p z-2d|u7-VE*G*mPqbaWzaDi9U-|L6AJ2f#)GlENAQVWQg8DI_1q%QR0|dgt0pa1{;66r&d^`uhVZ-B4aY-TIYFZ*vd*X3N zq!uI5NH_N5Yt2Dvd91u5kx>W;iHJ$)=ouK9n0fj51q6kJW&V?ulUGnw($>+{(>E|Q zvbM3cvv+WG^7ird^A89FM}3Np`5YS;pO&7HnU$UMHTQc-Y1xnRipr{=P0cN>ZS5VM z1A{}uBco&E6Y~p;OUo;(f7W((_x2ACkNzE>TwYz@+}_#<~@AizA~Dru8R&9?DTA%ZNsu>U!B(U1n=cuAJBpwT!SMXfJKdZ%>|onShJuFULq z`_Z)UvT!mG3-ym`t4Q`~322VM;BA-tf$i&C=od*lWtBax@Y zGUM+gqWOMZo$mn`YGEunjm0BVsxk9Od%PUw3;~)KYAAg{H!QgTNAt|a<3+zxs&G-v zk@{m{vA=CqWidr0-B&E)B$CxFa>n&7I{RF5ef^XPm#O`wYes0&h%#O42&rSgLFg+RHUK> zKR&~gt9PP?R7T@e`49Op4{HMpX|Eh_yR=@^voNFgDBj~hPH~zlkr+lDbkEzqzGwqZ ziVTMtqf8MRgQ#nFavLV0yl&inL$FEM2Ebe>H8x446LGLaYA|Q9h5$f?OzUdmm#AA} zRd3NXn*shq1mNK~`NBUr(WzcFD5;TQ6tx6;ofOisS_zsNk*G}Dk)`B0kcWiNNa)}E)Qyy_AhL1)OG@lNo$FCDeiNoLEon2{QH0#_gc;ggHYk&=dNbqDP~ zmbKh%BOxj&f3bmfy+ug#_EZ@Mtcb>oxVGA0ofFj z-{c=EGC8j|<~uCH-_s4D_xjer%`|o*C|Ekp_~LMNNt!Vc4M4LOtUBcWMj-3f9kp_* zZ^S=7qLei|IbY+DvMSz)yi7db$A26kdf6wqk6C8PR_E*3kyiCbcSKD93xi9|*0tYI z`jw^fvLCv&RwJ=Pjs#m0_@=g<^9DNVH98!?Y!1v*lauqe(N@Vguaj-4fV-JRtbEc= zaG_?!*WnsFO4Wu>o(!%g_Rh2wWxN>vZ>t;VTy=I-PrJ}QwNpJFDn4V4UB#vK=ZGd@ z@q}=aXcATsp?1?jV9tT_A=RyzKzkzxXJhp97~&!M21x~R;6I{!iHT^R7AowFM?W_a zg(J|l3h12FIJtB6RGr2_Piqi*;)uW4p#NOD-^%r%7e$yD@6W&MA^1b3HnV^H;zDU2 zB7`0-bUc2#xR_7_#HVHd?N4zmX%O9Je`o>_zjOS-R(^1luqWmiVEuab4hUGF)w7

_cD9`DBtZ*nYxHzpADp_d65L{Tm8Yqt^MqxsBF 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≀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 b&X_nnP%&4g@0kfa2L6;##IWS%iU9xF2U%r| diff --git a/cover_small.jpg b/cover_small.jpg deleted file mode 100644 index d896e8a123dd2fca1e7d5b128829d27d6cc18ca0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73325 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 diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index d51ce131..00000000 --- a/docs/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# 译者的话 - -[![GitHub stars](https://img.shields.io/github/stars/lingcoder/OnJava8.svg?style=social&label=Star&)](https://github.com/lingcoder/OnJava8/stargazers)[![GitHub forks](https://img.shields.io/github/forks/lingcoder/OnJava8.svg?style=social&label=Fork&)](https://github.com/lingcoder/OnJava8/fork) - -本翻译项目的 GITHUB 开源地址:[https://github.com/LingCoder/OnJava8](https://github.com/LingCoder/OnJava8) - -如果你在阅读本书的过程中有发现不明白或者错误的地方,请随时到项目地址发布 issue 或者 fork 项目后发布 pr 帮助译者改善!不胜感激! - -## 书籍简介 - -* 本书原作者为 [美] Bruce Eckel,即《Java 编程思想》的作者。 - - -## 翻译说明 - -1. 本书排版布局和翻译风格上参考了**阮一峰**老师的 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide) -2. 采用第一人称叙述。 -3. 由于中英行文差异,完全的逐字逐句翻译会很冗余啰嗦。所以本人在翻译过程中,去除了部分主题无关内容、重复描写。 -4. 译者在翻译中同时参考了谷歌、百度、有道翻译的译文。最后结合译者自己的理解进行本地化,尽量做到专业和言简意赅,方便大家更好的理解学习。 -5. 由于译者个人能力、时间有限,如有翻译错误和笔误的地方,还请大家批评指正! - -## 如何参与 - -如果你想对本书做出一些贡献的话 -可以在阅读本书过程中帮忙校对,找 bug 错别字等等 -可以提出专业方面的修改建议 -可以把一些不尽人意的语句翻译的更好更有趣 -对于以上各类建议,请以 issue 或 pr 的形式发送,我看到之后会尽快处理 -使用 MarkDown 编辑器,md 语法格式进行文档翻译及排版工作 -完成之后 PullRequest -如没问题的话,我会合并到主分支 -如不熟悉 md 排版,可不必纠结,我会在合并 pr 时代为排版 -如还有其它问题,欢迎发送 issue,谢谢~ - -## 开源协议 - -本项目基于 MIT 协议开源。 - -## 联系方式 - -- E-mail : - -

diff --git a/docs/_coverpage.md b/docs/_coverpage.md deleted file mode 100644 index 68c3bf2f..00000000 --- a/docs/_coverpage.md +++ /dev/null @@ -1,25 +0,0 @@ - - -# On Java 8 - -- 《On Java 8》中文版。 - - -[![stars](https://badgen.net/github/stars/lingcoder/OnJava8?icon=github&color=4ab8a1)](https://github.com/lingcoder/OnJava8) [![forks](https://badgen.net/github/forks/lingcoder/OnJava8?icon=github&color=4ab8a1)](https://github.com/lingcoder/OnJava8) - - - 👁️本页总访问次数: - - - | 🧑总访客数: - - -[GitHub](https://github.com/lingcoder/onJava8/) -[Get Started](sidebar.md) - - - - - - - diff --git a/docs/_style/prism-master/.editorconfig b/docs/_style/prism-master/.editorconfig deleted file mode 100644 index b2e4603b..00000000 --- a/docs/_style/prism-master/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -insert_final_newline = false -charset = utf-8 -indent_style = tab -indent_size = 4 - -[tests/languages/**.test] -end_of_line = crlf - -[{package.json,.travis.yml}] -indent_style = space -indent_size = 2 \ No newline at end of file diff --git a/docs/_style/prism-master/.gitattributes b/docs/_style/prism-master/.gitattributes deleted file mode 100644 index 1d598bdc..00000000 --- a/docs/_style/prism-master/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -* text=auto - -# Test files should not have their line endings modified by git -/tests/languages/**/*.test binary \ No newline at end of file diff --git a/docs/_style/prism-master/.gitignore b/docs/_style/prism-master/.gitignore deleted file mode 100644 index 3b16b7d3..00000000 --- a/docs/_style/prism-master/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -hide-*.js -node_modules -.idea/ -.DS_Store diff --git a/docs/_style/prism-master/.npmignore b/docs/_style/prism-master/.npmignore deleted file mode 100644 index 2ff075c9..00000000 --- a/docs/_style/prism-master/.npmignore +++ /dev/null @@ -1,27 +0,0 @@ -.idea -*.iml - -hide-*.js - -CNAME -examples/ -img/ -templates/ -tests/ -vendor/ -*.tgz -*.html -style.css -favicon.png -logo.svg -bower.json -composer.json -download.js -examples.js -gulpfile.js -prefixfree.min.js -utopia.js -code.js -.editorconfig -.gitattributes -.travis.yml \ No newline at end of file diff --git a/docs/_style/prism-master/.travis.yml b/docs/_style/prism-master/.travis.yml deleted file mode 100644 index 3d0c7d16..00000000 --- a/docs/_style/prism-master/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: node_js -node_js: -- '4' -- '6' -- '8' -- '9' -# Build all branches -branches: - only: - - gh-pages - - /.*/ -before_script: -- npm install -g gulp -- gulp -script: npm test -deploy: - provider: npm - email: lea@verou.me - api_key: - secure: TjRcXEr7Y/9KRJ4EOEQbd2Ij8hxKj8c/yOpEROy2lTYv6QH9x46nFDgZEE3VHfp/nnBUYpC47dRaSxiUj8H5rtkMNCZrREZu1n1zahmzP6dI6kCj+H3GiY7yw/Jhdx3uvQZHwknW2TJ/YRsLeQsmMSG2HnJobY9Zn4REX5ccP2E= - on: - tags: true - repo: PrismJS/prism diff --git a/docs/_style/prism-master/CHANGELOG.md b/docs/_style/prism-master/CHANGELOG.md deleted file mode 100644 index 14c64052..00000000 --- a/docs/_style/prism-master/CHANGELOG.md +++ /dev/null @@ -1,1334 +0,0 @@ -# Prism Changelog - -## 1.15.0 (2018-06-16) - -### New components - -* __Template Tookit 2__ ([#1418](https://github.com/PrismJS/prism/issues/1418)) [[`e063992`](https://github.com/PrismJS/prism/commit/e063992)] -* __XQuery__ ([#1411](https://github.com/PrismJS/prism/issues/1411)) [[`e326cb0`](https://github.com/PrismJS/prism/commit/e326cb0)] -* __TAP__ ([#1430](https://github.com/PrismJS/prism/issues/1430)) [[`8c2b71f`](https://github.com/PrismJS/prism/commit/8c2b71f)] - -### Updated components - -* __HTTP__ - * Absolute path is a valid request uri ([#1388](https://github.com/PrismJS/prism/issues/1388)) [[`f6e81cb`](https://github.com/PrismJS/prism/commit/f6e81cb)] -* __Kotlin__ - * Add keywords of Kotlin and modify it's number pattern. ([#1389](https://github.com/PrismJS/prism/issues/1389)) [[`1bf73b0`](https://github.com/PrismJS/prism/commit/1bf73b0)] - * Add `typealias` keyword ([#1437](https://github.com/PrismJS/prism/issues/1437)) [[`a21fdee`](https://github.com/PrismJS/prism/commit/a21fdee)] -* __JavaScript - * Improve Regexp pattern [[`5b043cf`](https://github.com/PrismJS/prism/commit/5b043cf)] - * Add support for one level of nesting inside template strings. Fix [#1397](https://github.com/PrismJS/prism/issues/1397) [[`db2d0eb`](https://github.com/PrismJS/prism/commit/db2d0eb)] -* __Elixir__ - * Elixir: Fix attributes consuming punctuation. Fix [#1392](https://github.com/PrismJS/prism/issues/1392) [[`dac0485`](https://github.com/PrismJS/prism/commit/dac0485)] -* __Bash__ - * Change reserved keyword reference ([#1396](https://github.com/PrismJS/prism/issues/1396)) [[`b94f01f`](https://github.com/PrismJS/prism/commit/b94f01f)] -* __PowerShell__ - * Allow for one level of nesting in expressions inside strings. Fix [#1407](https://github.com/PrismJS/prism/issues/1407) [[`9272d6f`](https://github.com/PrismJS/prism/commit/9272d6f)] -* __JSX__ - * Allow for two levels of nesting inside JSX tags. Fix [#1408](https://github.com/PrismJS/prism/issues/1408) [[`f1cd7c5`](https://github.com/PrismJS/prism/commit/f1cd7c5)] - * Add support for fragments short syntax. Fix [#1421](https://github.com/PrismJS/prism/issues/1421) [[`38ce121`](https://github.com/PrismJS/prism/commit/38ce121)] -* __Pascal__ - * Add `objectpascal` as an alias to `pascal` ([#1426](https://github.com/PrismJS/prism/issues/1426)) [[`a0bfc84`](https://github.com/PrismJS/prism/commit/a0bfc84)] -* __Swift__ - * Fix Swift 'protocol' keyword ([#1440](https://github.com/PrismJS/prism/issues/1440)) [[`081e318`](https://github.com/PrismJS/prism/commit/081e318)] - -### Updated plugins - -* __File Highlight__ - * Fix issue causing the Download button to show up on every code blocks. [[`cd22499`](https://github.com/PrismJS/prism/commit/cd22499)] - * Simplify lang regex on File Highlight plugin ([#1399](https://github.com/PrismJS/prism/issues/1399)) [[`7bc9a4a`](https://github.com/PrismJS/prism/commit/7bc9a4a)] -* __Show Language__ - * Don't process language if block language not set ([#1410](https://github.com/PrismJS/prism/issues/1410)) [[`c111869`](https://github.com/PrismJS/prism/commit/c111869)] -* __Autoloader__ - * ASP.NET should require C# [[`fa328bb`](https://github.com/PrismJS/prism/commit/fa328bb)] -* __Line Numbers__ - * Make line-numbers styles more specific ([#1434](https://github.com/PrismJS/prism/issues/1434), [#1435](https://github.com/PrismJS/prism/issues/1435)) [[`9ee4f54`](https://github.com/PrismJS/prism/commit/9ee4f54)] - -### Updated themes - -* Add .token.class-name to rest of themes ([#1360](https://github.com/PrismJS/prism/issues/1360)) [[`f356dfe`](https://github.com/PrismJS/prism/commit/f356dfe)] - -### Other changes - -* __Website__ - * Site now loads over HTTPS! - * Use HTTPS / canonical URLs ([#1390](https://github.com/PrismJS/prism/issues/1390)) [[`95146c8`](https://github.com/PrismJS/prism/commit/95146c8)] - * Added Angular tutorial link [[`c436a7c`](https://github.com/PrismJS/prism/commit/c436a7c)] - * Use rel="icon" instead of rel="shortcut icon" ([#1398](https://github.com/PrismJS/prism/issues/1398)) [[`d95f8fb`](https://github.com/PrismJS/prism/commit/d95f8fb)] - * Fix Download page not handling multiple dependencies when from Redownload URL [[`c2ff248`](https://github.com/PrismJS/prism/commit/c2ff248)] - * Update documentation for node & webpack usage [[`1e99e96`](https://github.com/PrismJS/prism/commit/1e99e96)] -* Handle optional dependencies in `loadLanguages()` ([#1417](https://github.com/PrismJS/prism/issues/1417)) [[`84935ac`](https://github.com/PrismJS/prism/commit/84935ac)] -* Add Chinese translation [[`f2b1964`](https://github.com/PrismJS/prism/commit/f2b1964)] - -## 1.14.0 (2018-04-11) - -### New components -* __GEDCOM__ ([#1385](https://github.com/PrismJS/prism/issues/1385)) [[`6e0b20a`](https://github.com/PrismJS/prism/commit/6e0b20a)] -* __Lisp__ ([#1297](https://github.com/PrismJS/prism/issues/1297)) [[`46468f8`](https://github.com/PrismJS/prism/commit/46468f8)] -* __Markup Templating__ ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] -* __Soy__ ([#1387](https://github.com/PrismJS/prism/issues/1387)) [[`b4509bf`](https://github.com/PrismJS/prism/commit/b4509bf)] -* __Velocity__ ([#1378](https://github.com/PrismJS/prism/issues/1378)) [[`5a524f7`](https://github.com/PrismJS/prism/commit/5a524f7)] -* __Visual Basic__ ([#1382](https://github.com/PrismJS/prism/issues/1382)) [[`c673ec2`](https://github.com/PrismJS/prism/commit/c673ec2)] -* __WebAssembly__ ([#1386](https://github.com/PrismJS/prism/issues/1386)) [[`c28d8c5`](https://github.com/PrismJS/prism/commit/c28d8c5)] - -### Updated components -* __Bash__: - * Add curl to the list of common functions. Close [#1160](https://github.com/PrismJS/prism/issues/1160) [[`1bfc084`](https://github.com/PrismJS/prism/commit/1bfc084)] -* __C-like__: - * Make single-line comments greedy. Fix [#1337](https://github.com/PrismJS/prism/issues/1337). Make sure [#1340](https://github.com/PrismJS/prism/issues/1340) stays fixed. [[`571f2c5`](https://github.com/PrismJS/prism/commit/571f2c5)] -* __C#__: - * More generic class-name highlighting. Fix [#1365](https://github.com/PrismJS/prism/issues/1365) [[`a6837d2`](https://github.com/PrismJS/prism/commit/a6837d2)] - * More specific class-name highlighting. Fix [#1371](https://github.com/PrismJS/prism/issues/1371) [[`0a95f69`](https://github.com/PrismJS/prism/commit/0a95f69)] -* __Eiffel__: - * Fix verbatim strings. Fix [#1379](https://github.com/PrismJS/prism/issues/1379) [[`04df41b`](https://github.com/PrismJS/prism/commit/04df41b)] -* __Elixir__ - * Make regexps greedy, remove comment hacks. Update known failures and tests. [[`e93d61f`](https://github.com/PrismJS/prism/commit/e93d61f)] -* __ERB__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] -* __Fortran__: - * Make single-line comments greedy. Update known failures and tests. [[`c083b78`](https://github.com/PrismJS/prism/commit/c083b78)] -* __Handlebars__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] -* __Java__: - * Add support for generics. Fix [#1351](https://github.com/PrismJS/prism/issues/1351) [[`a5cf302`](https://github.com/PrismJS/prism/commit/a5cf302)] -* __JavaScript__: - * Add support for constants. Fix [#1348](https://github.com/PrismJS/prism/issues/1348) [[`9084481`](https://github.com/PrismJS/prism/commit/9084481)] - * Improve Regex matching [[`172d351`](https://github.com/PrismJS/prism/commit/172d351)] -* __JSX__: - * Fix highlighting of empty objects. Fix [#1364](https://github.com/PrismJS/prism/issues/1364) [[`b26bbb8`](https://github.com/PrismJS/prism/commit/b26bbb8)] -* __Monkey__: - * Make comments greedy. Update known failures and tests. [[`d7b2b43`](https://github.com/PrismJS/prism/commit/d7b2b43)] -* __PHP__: - * Make highlighting work properly in NodeJS ([#1367](https://github.com/PrismJS/prism/issues/1367)) [[`5f9c078`](https://github.com/PrismJS/prism/commit/5f9c078)] -* __Puppet__: - * Make heredoc, comments, regexps and strings greedy. Update known failures and tests. [[`0c139d1`](https://github.com/PrismJS/prism/commit/0c139d1)] -* __Q__: - * Make comments greedy. Update known failures and tests. [[`a0f5081`](https://github.com/PrismJS/prism/commit/a0f5081)] -* __Ruby__: - * Make multi-line comments greedy, remove single-line comment hack. Update known failures and tests. [[`b0e34fb`](https://github.com/PrismJS/prism/commit/b0e34fb)] -* __SQL__: - * Add missing keywords. Fix [#1374](https://github.com/PrismJS/prism/issues/1374) [[`238b195`](https://github.com/PrismJS/prism/commit/238b195)] - -### Updated plugins -* __Command Line__: - * Command Line: Allow specifying output prefix using data-filter-output attribute. ([#856](https://github.com/PrismJS/prism/issues/856)) [[`094d546`](https://github.com/PrismJS/prism/commit/094d546)] -* __File Highlight__: - * Add option to provide a download button, when used with the Toolbar plugin. Fix [#1030](https://github.com/PrismJS/prism/issues/1030) [[`9f22952`](https://github.com/PrismJS/prism/commit/9f22952)] - -### Updated themes -* __Default__: - * Reach AA contrast ratio level ([#1296](https://github.com/PrismJS/prism/issues/1296)) [[`8aea939`](https://github.com/PrismJS/prism/commit/8aea939)] - -### Other changes -* Website: Remove broken third-party tutorials from homepage [[`0efd6e1`](https://github.com/PrismJS/prism/commit/0efd6e1)] -* Docs: Mention `loadLanguages()` function on homepage in the nodeJS section. Close [#972](https://github.com/PrismJS/prism/issues/972), close [#593](https://github.com/PrismJS/prism/issues/593) [[`4a14d20`](https://github.com/PrismJS/prism/commit/4a14d20)] -* Core: Greedy patterns should always be matched against the full string. Fix [#1355](https://github.com/PrismJS/prism/issues/1355) [[`294efaa`](https://github.com/PrismJS/prism/commit/294efaa)] -* Crystal: Update known failures. [[`e1d2d42`](https://github.com/PrismJS/prism/commit/e1d2d42)] -* D: Update known failures and tests. [[`13d9991`](https://github.com/PrismJS/prism/commit/13d9991)] -* Markdown: Update known failures. [[`5b6c76d`](https://github.com/PrismJS/prism/commit/5b6c76d)] -* Matlab: Update known failures. [[`259b6fc`](https://github.com/PrismJS/prism/commit/259b6fc)] -* Website: Remove non-existent anchor to failures. Reword on homepage to make is less misleading. [[`8c0911a`](https://github.com/PrismJS/prism/commit/8c0911a)] -* Website: Add link to Keep Markup plugin in FAQ [[`e8cb6d4`](https://github.com/PrismJS/prism/commit/e8cb6d4)] -* Test suite: Memory leak in vm.runInNewContext() seems fixed. Revert [[`9a4b6fa`](https://github.com/PrismJS/prism/commit/9a4b6fa)] to drastically improve tests execution time. [[`9bceece`](https://github.com/PrismJS/prism/commit/9bceece), [`7c7602b`](https://github.com/PrismJS/prism/commit/7c7602b)] -* Gulp: Don't minify `components/index.js` [[`689227b`](https://github.com/PrismJS/prism/commit/689227b)] -* Website: Fix theme selection on Download page, when theme is in query string or hash. [[`b4d3063`](https://github.com/PrismJS/prism/commit/b4d3063)] -* Update JSPM config to also include unminified components. Close [#995](https://github.com/PrismJS/prism/issues/995) [[`218f160`](https://github.com/PrismJS/prism/commit/218f160)] -* Core: Fix support for language alias containing dash `-` [[`659ea31`](https://github.com/PrismJS/prism/commit/659ea31)] - -## 1.13.0 (2018-03-21) - -### New components -* __ERB__ [[`e6213ac`](https://github.com/PrismJS/prism/commit/e6213ac)] -* __PL/SQL__ ([#1338](https://github.com/PrismJS/prism/issues/1338)) [[`3599e6a`](https://github.com/PrismJS/prism/commit/3599e6a)] - -### Updated components -* __JSX__: - * Add support for plain text inside tags ([#1357](https://github.com/PrismJS/prism/issues/1357)) [[`2b8321d`](https://github.com/PrismJS/prism/commit/2b8321d)] -* __Markup__: - * Make tags greedy. Fix [#1356](https://github.com/PrismJS/prism/issues/1356) [[`af834be`](https://github.com/PrismJS/prism/commit/af834be)] -* __Powershell__: - * Add lookbehind to fix function interpolation inside strings. Fix [#1361](https://github.com/PrismJS/prism/issues/1361) [[`d2c026e`](https://github.com/PrismJS/prism/commit/d2c026e)] -* __Rust__: - * Improve char pattern so that lifetime annotations are matched better. Fix [#1353](https://github.com/PrismJS/prism/issues/1353) [[`efdccbf`](https://github.com/PrismJS/prism/commit/efdccbf)] - -### Updated themes -* __Default__: - * Add color for class names [[`8572474`](https://github.com/PrismJS/prism/commit/8572474)] -* __Coy__: - * Inherit pre's height on code, so it does not break on Download page. [[`c6c7fd1`](https://github.com/PrismJS/prism/commit/c6c7fd1)] - -### Other changes -* Website: Auto-generate example headers [[`c3ed5b5`](https://github.com/PrismJS/prism/commit/c3ed5b5)] -* Core: Allow cloning of circular structures. ([#1345](https://github.com/PrismJS/prism/issues/1345)) [[`f90d555`](https://github.com/PrismJS/prism/commit/f90d555)] -* Core: Generate components.js from components.json and make it exportable to nodeJS. ([#1354](https://github.com/PrismJS/prism/issues/1354)) [[`ba60df0`](https://github.com/PrismJS/prism/commit/ba60df0)] -* Website: Improve appearance of theme selector [[`0460cad`](https://github.com/PrismJS/prism/commit/0460cad)] -* Website: Check stored theme by default + link both theme selectors together. Close [#1038](https://github.com/PrismJS/prism/issues/1038) [[`212dd4e`](https://github.com/PrismJS/prism/commit/212dd4e)] -* Tests: Use the new components.js file directly [[`0e1a8b7`](https://github.com/PrismJS/prism/commit/0e1a8b7)] -* Update .npmignore Close [#1274](https://github.com/PrismJS/prism/issues/1274) [[`a52319a`](https://github.com/PrismJS/prism/commit/a52319a)] -* Add a loadLanguages() function for easy component loading on NodeJS ([#1359](https://github.com/PrismJS/prism/issues/1359)) [[`a5331a6`](https://github.com/PrismJS/prism/commit/a5331a6)] - -## 1.12.2 (2018-03-08) - -### Other changes -* Test against NodeJS 4, 6, 8 and 9 ([#1329](https://github.com/PrismJS/prism/issues/1329)) [[`97b7d0a`](https://github.com/PrismJS/prism/commit/97b7d0a)] -* Stop testing against NodeJS 0.10 and 0.12 [[`df01b1b`](https://github.com/PrismJS/prism/commit/df01b1b)] - -## 1.12.1 (2018-03-08) - -### Updated components -* __C-like__: - * Revert [[`b98e5b9`](https://github.com/PrismJS/prism/commit/b98e5b9)] to fix [#1340](https://github.com/PrismJS/prism/issues/1340). Reopened [#1337](https://github.com/PrismJS/prism/issues/1337). [[`cebacdf`](https://github.com/PrismJS/prism/commit/cebacdf)] -* __JSX__: - * Allow for one level of nested curly braces inside tag attribute value. Fix [#1335](https://github.com/PrismJS/prism/issues/1335) [[`05bf67d`](https://github.com/PrismJS/prism/commit/05bf67d)] -* __Ruby__: - * Ensure module syntax is not confused with symbols. Fix [#1336](https://github.com/PrismJS/prism/issues/1336) [[`31a2a69`](https://github.com/PrismJS/prism/commit/31a2a69)] - -## 1.12.0 (2018-03-07) - -### New components -* __ARFF__ ([#1327](https://github.com/PrismJS/prism/issues/1327)) [[`0bc98ac`](https://github.com/PrismJS/prism/commit/0bc98ac)] -* __Clojure__ ([#1311](https://github.com/PrismJS/prism/issues/1311)) [[`8b4d3bd`](https://github.com/PrismJS/prism/commit/8b4d3bd)] -* __Liquid__ ([#1326](https://github.com/PrismJS/prism/issues/1326)) [[`f0b2c9e`](https://github.com/PrismJS/prism/commit/f0b2c9e)] - -### Updated components -* __Bash__: - * Add shell as an alias ([#1321](https://github.com/PrismJS/prism/issues/1321)) [[`67e16a2`](https://github.com/PrismJS/prism/commit/67e16a2)] - * Add support for quoted command substitution. Fix [#1287](https://github.com/PrismJS/prism/issues/1287) [[`63fc215`](https://github.com/PrismJS/prism/commit/63fc215)] -* __C#__: - * Add "dotnet" alias. [[`405867c`](https://github.com/PrismJS/prism/commit/405867c)] -* __C-like__: - * Change order of comment patterns and make multi-line one greedy. Fix [#1337](https://github.com/PrismJS/prism/issues/1337) [[`b98e5b9`](https://github.com/PrismJS/prism/commit/b98e5b9)] -* __NSIS__: - * Add support for NSIS 3.03 ([#1288](https://github.com/PrismJS/prism/issues/1288)) [[`bd1e98b`](https://github.com/PrismJS/prism/commit/bd1e98b)] - * Add missing NSIS commands ([#1289](https://github.com/PrismJS/prism/issues/1289)) [[`ad2948f`](https://github.com/PrismJS/prism/commit/ad2948f)] -* __PHP__: - * Add support for string interpolation inside double-quoted strings. Fix [#1146](https://github.com/PrismJS/prism/issues/1146) [[`9f1f8d6`](https://github.com/PrismJS/prism/commit/9f1f8d6)] - * Add support for Heredoc and Nowdoc strings [[`5d7223c`](https://github.com/PrismJS/prism/commit/5d7223c)] - * Fix shell-comment failure now that strings are greedy [[`ad25d22`](https://github.com/PrismJS/prism/commit/ad25d22)] -* __PowerShell__: - * Add support for two levels of nested brackets inside namespace pattern. Fixes [#1317](https://github.com/PrismJS/prism/issues/1317) [[`3bc3e9c`](https://github.com/PrismJS/prism/commit/3bc3e9c)] -* __Ruby__: - * Add keywords "protected", "private" and "public" [[`4593837`](https://github.com/PrismJS/prism/commit/4593837)] -* __Rust__: - * Add support for lifetime-annotation and => operator. Fix [#1339](https://github.com/PrismJS/prism/issues/1339) [[`926f6f8`](https://github.com/PrismJS/prism/commit/926f6f8)] -* __Scheme__: - * Don't highlight first number of a list as a function. Fix [#1331](https://github.com/PrismJS/prism/issues/1331) [[`51bff80`](https://github.com/PrismJS/prism/commit/51bff80)] -* __SQL__: - * Add missing keywords and functions, fix numbers [[`de29d4a`](https://github.com/PrismJS/prism/commit/de29d4a)] - -### Updated plugins -* __Autolinker__: - * Allow more chars in query string and hash to match more URLs. Fix [#1142](https://github.com/PrismJS/prism/issues/1142) [[`109bd6f`](https://github.com/PrismJS/prism/commit/109bd6f)] -* __Copy to Clipboard__: - * Bump ClipboardJS to 2.0.0 and remove hack ([#1314](https://github.com/PrismJS/prism/issues/1314)) [[`e9f410e`](https://github.com/PrismJS/prism/commit/e9f410e)] -* __Toolbar__: - * Prevent scrolling toolbar with content ([#1305](https://github.com/PrismJS/prism/issues/1305), [#1314](https://github.com/PrismJS/prism/issues/1314)) [[`84eeb89`](https://github.com/PrismJS/prism/commit/84eeb89)] -* __Unescaped Markup__: - * Use msMatchesSelector for IE11 and below. Fix [#1302](https://github.com/PrismJS/prism/issues/1302) [[`c246c1a`](https://github.com/PrismJS/prism/commit/c246c1a)] -* __WebPlatform Docs__: - * WebPlatform Docs plugin: Fix links. Fixes [#1290](https://github.com/PrismJS/prism/issues/1290) [[`7a9dbe0`](https://github.com/PrismJS/prism/commit/7a9dbe0)] - -### Other changes -* Fix Autoloader's demo page [[`3dddac9`](https://github.com/PrismJS/prism/commit/3dddac9)] -* Download page: Use hash instead of query-string for redownload URL. Fix [#1263](https://github.com/PrismJS/prism/issues/1263) [[`b03c02a`](https://github.com/PrismJS/prism/commit/b03c02a)] -* Core: Don't thow an error if lookbehing is used without anything matching. [[`e0cd47f`](https://github.com/PrismJS/prism/commit/e0cd47f)] -* Docs: Fix link to the `` element specification in HTML5 [[`a84263f`](https://github.com/PrismJS/prism/commit/a84263f)] -* Docs: Mention support for `lang-xxxx` class. Close [#1312](https://github.com/PrismJS/prism/issues/1312) [[`a9e76db`](https://github.com/PrismJS/prism/commit/a9e76db)] -* Docs: Add note on `async` parameter to clarify the requirement of using a single bundled file. Closes [#1249](https://github.com/PrismJS/prism/issues/1249) [[`eba0235`](https://github.com/PrismJS/prism/commit/eba0235)] - -## 1.11.0 (2018-02-05) - -### New components -* __Content-Security-Policy (CSP)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __HTTP Public-Key-Pins (HPKP)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __HTTP String-Transport-Security (HSTS)__ ([#1275](https://github.com/PrismJS/prism/issues/1275)) [[`b08cae5`](https://github.com/PrismJS/prism/commit/b08cae5)] -* __React TSX__ ([#1280](https://github.com/PrismJS/prism/issues/1280)) [[`fbe82b8`](https://github.com/PrismJS/prism/commit/fbe82b8)] - -### Updated components -* __C++__: - * Add C++ platform-independent types ([#1271](https://github.com/PrismJS/prism/issues/1271)) [[`3da238f`](https://github.com/PrismJS/prism/commit/3da238f)] -* __TypeScript__: - * Improve typescript with builtins ([#1277](https://github.com/PrismJS/prism/issues/1277)) [[`5de1b1f`](https://github.com/PrismJS/prism/commit/5de1b1f)] - -### Other changes -* Fix passing of non-enumerable Error properties from the child test runner ([#1276](https://github.com/PrismJS/prism/issues/1276)) [[`38df653`](https://github.com/PrismJS/prism/commit/38df653)] - -## 1.10.0 (2018-01-17) - -### New components -* __6502 Assembly__ ([#1245](https://github.com/PrismJS/prism/issues/1245)) [[`2ece18b`](https://github.com/PrismJS/prism/commit/2ece18b)] -* __Elm__ ([#1174](https://github.com/PrismJS/prism/issues/1174)) [[`d6da70e`](https://github.com/PrismJS/prism/commit/d6da70e)] -* __IchigoJam BASIC__ ([#1246](https://github.com/PrismJS/prism/issues/1246)) [[`cf840be`](https://github.com/PrismJS/prism/commit/cf840be)] -* __Io__ ([#1251](https://github.com/PrismJS/prism/issues/1251)) [[`84ed3ed`](https://github.com/PrismJS/prism/commit/84ed3ed)] - -### Updated components -* __BASIC__: - * Make strings greedy [[`60114d0`](https://github.com/PrismJS/prism/commit/60114d0)] -* __C++__: - * Add C++11 raw string feature ([#1254](https://github.com/PrismJS/prism/issues/1254)) [[`71595be`](https://github.com/PrismJS/prism/commit/71595be)] - -### Updated plugins -* __Autoloader__: - * Add support for `data-autoloader-path` ([#1242](https://github.com/PrismJS/prism/issues/1242)) [[`39360d6`](https://github.com/PrismJS/prism/commit/39360d6)] -* __Previewers__: - * New plugin combining previous plugins Previewer: Base, Previewer: Angle, Previewer: Color, Previewer: Easing, Previewer: Gradient and Previewer: Time. ([#1244](https://github.com/PrismJS/prism/issues/1244)) [[`28e4b4c`](https://github.com/PrismJS/prism/commit/28e4b4c)] -* __Unescaped Markup__: - * Make it work with any language ([#1265](https://github.com/PrismJS/prism/issues/1265)) [[`7bcdae7`](https://github.com/PrismJS/prism/commit/7bcdae7)] - -### Other changes -* Add attribute `style` in `package.json` ([#1256](https://github.com/PrismJS/prism/issues/1256)) [[`a9b6785`](https://github.com/PrismJS/prism/commit/a9b6785)] - -## 1.9.0 (2017-12-06) - -### New components -* __Flow__ [[`d27b70d`](https://github.com/PrismJS/prism/commit/d27b70d)] - -### Updated components -* __CSS__: - * Unicode characters in CSS properties ([#1227](https://github.com/PrismJS/prism/issues/1227)) [[`f234ea4`](https://github.com/PrismJS/prism/commit/f234ea4)] -* __JSX__: - * JSX: Improve highlighting support. Fix [#1235](https://github.com/PrismJS/prism/issues/1235) and [#1236](https://github.com/PrismJS/prism/issues/1236) [[`f41c5cd`](https://github.com/PrismJS/prism/commit/f41c5cd)] -* __Markup__: - * Make CSS and JS inclusions in Markup greedy. Fix [#1240](https://github.com/PrismJS/prism/issues/1240) [[`7dc1e45`](https://github.com/PrismJS/prism/commit/7dc1e45)] -* __PHP__: - * Add support for multi-line strings. Fix [#1233](https://github.com/PrismJS/prism/issues/1233) [[`9a542a0`](https://github.com/PrismJS/prism/commit/9a542a0)] - -### Updated plugins -* __Copy to clipboard__: - * Fix test for native Clipboard. Fix [#1241](https://github.com/PrismJS/prism/issues/1241) [[`e7b5e82`](https://github.com/PrismJS/prism/commit/e7b5e82)] - * Copy to clipboard: Update to v1.7.1. Fix [#1220](https://github.com/PrismJS/prism/issues/1220) [[`a1b85e3`](https://github.com/PrismJS/prism/commit/a1b85e3), [`af50e44`](https://github.com/PrismJS/prism/commit/af50e44)] -* __Line highlight__: - * Fixes to compatibility of line number and line higlight plugins ([#1194](https://github.com/PrismJS/prism/issues/1194)) [[`e63058f`](https://github.com/PrismJS/prism/commit/e63058f), [`3842a91`](https://github.com/PrismJS/prism/commit/3842a91)] -* __Unescaped Markup__: - * Fix ambiguity in documentation by improving examples. Fix [#1197](https://github.com/PrismJS/prism/issues/1197) [[`924784a`](https://github.com/PrismJS/prism/commit/924784a)] - -### Other changes -* Allow any element being root instead of document. ([#1230](https://github.com/PrismJS/prism/issues/1230)) [[`69f2e2c`](https://github.com/PrismJS/prism/commit/69f2e2c), [`6e50d44`](https://github.com/PrismJS/prism/commit/6e50d44)] -* Coy Theme: The 'height' element makes code blocks the height of the browser canvas. ([#1224](https://github.com/PrismJS/prism/issues/1224)) [[`ac219d7`](https://github.com/PrismJS/prism/commit/ac219d7)] -* Download page: Fix implicitly declared variable [[`f986551`](https://github.com/PrismJS/prism/commit/f986551)] -* Download page: Add version number at the beginning of the generated files. Fix [#788](https://github.com/PrismJS/prism/issues/788) [[`928790d`](https://github.com/PrismJS/prism/commit/928790d)] - -## 1.8.4 (2017-11-05) - -### Updated components - -* __ABAP__: - * Regexp optimisation [[`7547f83`](https://github.com/PrismJS/prism/commit/7547f83)] -* __ActionScript__: - * Fix XML regex + optimise [[`75d00d7`](https://github.com/PrismJS/prism/commit/75d00d7)] -* __Ada__: - * Regexp simplification [[`e881fe3`](https://github.com/PrismJS/prism/commit/e881fe3)] -* __Apacheconf__: - * Regexp optimisation [[`a065e61`](https://github.com/PrismJS/prism/commit/a065e61)] -* __APL__: - * Regexp simplification [[`33297c4`](https://github.com/PrismJS/prism/commit/33297c4)] -* __AppleScript__: - * Regexp optimisation [[`d879f36`](https://github.com/PrismJS/prism/commit/d879f36)] -* __Arduino__: - * Don't use captures if not needed [[`16b338f`](https://github.com/PrismJS/prism/commit/16b338f)] -* __ASP.NET__: - * Regexp optimisation [[`438926c`](https://github.com/PrismJS/prism/commit/438926c)] -* __AutoHotkey__: - * Regexp simplification + don't use captures if not needed [[`5edfd2f`](https://github.com/PrismJS/prism/commit/5edfd2f)] -* __Bash__: - * Regexp optimisation and simplification [[`75b9b29`](https://github.com/PrismJS/prism/commit/75b9b29)] -* __Bro__: - * Regexp simplification + don't use captures if not needed [[`d4b9003`](https://github.com/PrismJS/prism/commit/d4b9003)] -* __C__: - * Regexp optimisation + don't use captures if not needed [[`f61d487`](https://github.com/PrismJS/prism/commit/f61d487)] -* __C++__: - * Fix operator regexp + regexp simplification + don't use captures if not needed [[`ffeb26e`](https://github.com/PrismJS/prism/commit/ffeb26e)] -* __C#__: - * Remove duplicates in keywords + regexp optimisation + don't use captures if not needed [[`d28d178`](https://github.com/PrismJS/prism/commit/d28d178)] -* __C-like__: - * Regexp simplification + don't use captures if not needed [[`918e0ff`](https://github.com/PrismJS/prism/commit/918e0ff)] -* __CoffeeScript__: - * Regexp optimisation + don't use captures if not needed [[`5895978`](https://github.com/PrismJS/prism/commit/5895978)] -* __Crystal__: - * Remove trailing comma [[`16979a3`](https://github.com/PrismJS/prism/commit/16979a3)] -* __CSS__: - * Regexp simplification + don't use captures if not needed + handle multi-line style attributes [[`43d9f36`](https://github.com/PrismJS/prism/commit/43d9f36)] -* __CSS Extras__: - * Regexp simplification [[`134ed70`](https://github.com/PrismJS/prism/commit/134ed70)] -* __D__: - * Regexp optimisation [[`fbe39c9`](https://github.com/PrismJS/prism/commit/fbe39c9)] -* __Dart__: - * Regexp optimisation [[`f24e919`](https://github.com/PrismJS/prism/commit/f24e919)] -* __Django__: - * Regexp optimisation [[`a95c51d`](https://github.com/PrismJS/prism/commit/a95c51d)] -* __Docker__: - * Regexp optimisation [[`27f99ff`](https://github.com/PrismJS/prism/commit/27f99ff)] -* __Eiffel__: - * Regexp optimisation [[`b7cdea2`](https://github.com/PrismJS/prism/commit/b7cdea2)] -* __Elixir__: - * Regexp optimisation + uniform behavior between ~r and ~s [[`5d12e80`](https://github.com/PrismJS/prism/commit/5d12e80)] -* __Erlang__: - * Regexp optimisation [[`e7b411e`](https://github.com/PrismJS/prism/commit/e7b411e)] -* __F#__: - * Regexp optimisation + don't use captures if not needed [[`7753fc4`](https://github.com/PrismJS/prism/commit/7753fc4)] -* __Gherkin__: - * Regexp optimisation + don't use captures if not needed + added explanation comment on table-body regexp [[`f26197a`](https://github.com/PrismJS/prism/commit/f26197a)] -* __Git__: - * Regexp optimisation [[`b9483b9`](https://github.com/PrismJS/prism/commit/b9483b9)] -* __GLSL__: - * Regexp optimisation [[`e66d21b`](https://github.com/PrismJS/prism/commit/e66d21b)] -* __Go__: - * Regexp optimisation + don't use captures if not needed [[`88caabb`](https://github.com/PrismJS/prism/commit/88caabb)] -* __GraphQL__: - * Regexp optimisation and simplification [[`2474f06`](https://github.com/PrismJS/prism/commit/2474f06)] -* __Groovy__: - * Regexp optimisation + don't use captures if not needed [[`e74e00c`](https://github.com/PrismJS/prism/commit/e74e00c)] -* __Haml__: - * Regexp optimisation + don't use captures if not needed + fix typo in comment [[`23e3b43`](https://github.com/PrismJS/prism/commit/23e3b43)] -* __Handlebars__: - * Regexp optimisation + don't use captures if not needed [[`09dbfce`](https://github.com/PrismJS/prism/commit/09dbfce)] -* __Haskell__: - * Regexp simplification + don't use captures if not needed [[`f11390a`](https://github.com/PrismJS/prism/commit/f11390a)] -* __HTTP__: - * Regexp simplification + don't use captures if not needed [[`37ef24e`](https://github.com/PrismJS/prism/commit/37ef24e)] -* __Icon__: - * Regexp optimisation [[`9cf64a0`](https://github.com/PrismJS/prism/commit/9cf64a0)] -* __J__: - * Regexp simplification [[`de15150`](https://github.com/PrismJS/prism/commit/de15150)] -* __Java__: - * Don't use captures if not needed [[`96b35c8`](https://github.com/PrismJS/prism/commit/96b35c8)] -* __JavaScript__: - * Regexp optimisation + don't use captures if not needed [[`93d4002`](https://github.com/PrismJS/prism/commit/93d4002)] -* __Jolie__: - * Regexp optimisation + don't use captures if not needed + remove duplicates in keywords [[`a491f9e`](https://github.com/PrismJS/prism/commit/a491f9e)] -* __JSON__: - * Make strings greedy, remove negative look-ahead for ":". Fix [#1204](https://github.com/PrismJS/prism/issues/1204) [[`98acd2d`](https://github.com/PrismJS/prism/commit/98acd2d)] - * Regexp optimisation + don't use captures if not needed [[`8fc1b03`](https://github.com/PrismJS/prism/commit/8fc1b03)] -* __JSX__: - * Regexp optimisation + handle spread operator as a whole [[`28de4e2`](https://github.com/PrismJS/prism/commit/28de4e2)] -* __Julia__: - * Regexp optimisation and simplification [[`12684c0`](https://github.com/PrismJS/prism/commit/12684c0)] -* __Keyman__: - * Regexp optimisation + don't use captures if not needed [[`9726087`](https://github.com/PrismJS/prism/commit/9726087)] -* __Kotlin__: - * Regexp simplification [[`12ff8dc`](https://github.com/PrismJS/prism/commit/12ff8dc)] -* __LaTeX__: - * Regexp optimisation and simplification [[`aa426b0`](https://github.com/PrismJS/prism/commit/aa426b0)] -* __LiveScript__: - * Make interpolated strings greedy + fix variable and identifier regexps [[`c581049`](https://github.com/PrismJS/prism/commit/c581049)] -* __LOLCODE__: - * Don't use captures if not needed [[`52903af`](https://github.com/PrismJS/prism/commit/52903af)] -* __Makefile__: - * Regexp optimisation [[`20ae2e5`](https://github.com/PrismJS/prism/commit/20ae2e5)] -* __Markdown__: - * Don't use captures if not needed [[`f489a1e`](https://github.com/PrismJS/prism/commit/f489a1e)] -* __Markup__: - * Regexp optimisation + fix punctuation inside attr-value [[`ea380c6`](https://github.com/PrismJS/prism/commit/ea380c6)] -* __MATLAB__: - * Make strings greedy + handle line feeds better [[`4cd4f01`](https://github.com/PrismJS/prism/commit/4cd4f01)] -* __Monkey__: - * Don't use captures if not needed [[`7f47140`](https://github.com/PrismJS/prism/commit/7f47140)] -* __N4JS__: - * Don't use captures if not needed [[`2d3f9df`](https://github.com/PrismJS/prism/commit/2d3f9df)] -* __NASM__: - * Regexp optimisation and simplification + don't use captures if not needed [[`9937428`](https://github.com/PrismJS/prism/commit/9937428)] -* __nginx__: - * Remove trailing comma + remove duplicates in keywords [[`c6e7195`](https://github.com/PrismJS/prism/commit/c6e7195)] -* __NSIS__: - * Regexp optimisation + don't use captures if not needed [[`beeb107`](https://github.com/PrismJS/prism/commit/beeb107)] -* __Objective-C__: - * Don't use captures if not needed [[`9be0f88`](https://github.com/PrismJS/prism/commit/9be0f88)] -* __OCaml__: - * Regexp simplification [[`5f5f38c`](https://github.com/PrismJS/prism/commit/5f5f38c)] -* __OpenCL__: - * Don't use captures if not needed [[`5e70f1d`](https://github.com/PrismJS/prism/commit/5e70f1d)] -* __Oz__: - * Fix atom regexp [[`9320e92`](https://github.com/PrismJS/prism/commit/9320e92)] -* __PARI/GP__: - * Regexp optimisation [[`2c7b59b`](https://github.com/PrismJS/prism/commit/2c7b59b)] -* __Parser__: - * Regexp simplification [[`569d511`](https://github.com/PrismJS/prism/commit/569d511)] -* __Perl__: - * Regexp optimisation and simplification + don't use captures if not needed [[`0fe4cf6`](https://github.com/PrismJS/prism/commit/0fe4cf6)] -* __PHP__: - * Don't use captures if not needed Golmote [[`5235f18`](https://github.com/PrismJS/prism/commit/5235f18)] -* __PHP Extras__: - * Add word boundary after global keywords + don't use captures if not needed [[`9049a2a`](https://github.com/PrismJS/prism/commit/9049a2a)] -* __PowerShell__: - * Regexp optimisation + don't use captures if not needed [[`0d05957`](https://github.com/PrismJS/prism/commit/0d05957)] -* __Processing__: - * Regexp simplification [[`8110d38`](https://github.com/PrismJS/prism/commit/8110d38)] -* __.properties__: - * Regexp optimisation [[`678b621`](https://github.com/PrismJS/prism/commit/678b621)] -* __Protocol Buffers__: - * Don't use captures if not needed [[`3e256d8`](https://github.com/PrismJS/prism/commit/3e256d8)] -* __Pug__: - * Don't use captures if not needed [[`76dc925`](https://github.com/PrismJS/prism/commit/76dc925)] -* __Pure__: - * Make inline-lang greedy [[`92318b0`](https://github.com/PrismJS/prism/commit/92318b0)] -* __Python__: - * Add Python builtin function highlighting ([#1205](https://github.com/PrismJS/prism/issues/1205)) [[`2169c99`](https://github.com/PrismJS/prism/commit/2169c99)] - * Python: Add highlighting to functions with space between name and parentheses ([#1207](https://github.com/PrismJS/prism/issues/1207)) [[`3badd8a`](https://github.com/PrismJS/prism/commit/3badd8a)] - * Make triple-quoted strings greedy + regexp optimisation and simplification [[`f09f9f5`](https://github.com/PrismJS/prism/commit/f09f9f5)] -* __Qore__: - * Regexp simplification [[`69459f0`](https://github.com/PrismJS/prism/commit/69459f0)] -* __R__: - * Regexp optimisation [[`06a9da4`](https://github.com/PrismJS/prism/commit/06a9da4)] -* __Reason__: - * Regexp optimisation + don't use capture if not needed [[`19d79b4`](https://github.com/PrismJS/prism/commit/19d79b4)] -* __Ren'py__: - * Make strings greedy + don't use captures if not needed [[`91d84d9`](https://github.com/PrismJS/prism/commit/91d84d9)] -* __reST__: - * Regexp simplification + don't use captures if not needed [[`1a8b3e9`](https://github.com/PrismJS/prism/commit/1a8b3e9)] -* __Rip__: - * Regexp optimisation [[`d7f0ee8`](https://github.com/PrismJS/prism/commit/d7f0ee8)] -* __Ruby__: - * Regexp optimisation and simplification + don't use captures if not needed [[`4902ed4`](https://github.com/PrismJS/prism/commit/4902ed4)] -* __Rust__: - * Regexp optimisation and simplification + don't use captures if not needed [[`cc9d874`](https://github.com/PrismJS/prism/commit/cc9d874)] -* __Sass__: - * Regexp simplification Golmote [[`165d957`](https://github.com/PrismJS/prism/commit/165d957)] -* __Scala__: - * Regexp optimisation Golmote [[`5f50c12`](https://github.com/PrismJS/prism/commit/5f50c12)] -* __Scheme__: - * Regexp optimisation [[`bd19b04`](https://github.com/PrismJS/prism/commit/bd19b04)] -* __SCSS__: - * Regexp simplification [[`c60b7d4`](https://github.com/PrismJS/prism/commit/c60b7d4)] -* __Smalltalk__: - * Regexp simplification [[`41a2c76`](https://github.com/PrismJS/prism/commit/41a2c76)] -* __Smarty__: - * Regexp optimisation and simplification [[`e169be9`](https://github.com/PrismJS/prism/commit/e169be9)] -* __SQL__: - * Regexp optimisation [[`a6244a4`](https://github.com/PrismJS/prism/commit/a6244a4)] -* __Stylus__: - * Regexp optimisation [[`df9506c`](https://github.com/PrismJS/prism/commit/df9506c)] -* __Swift__: - * Don't use captures if not needed [[`a2d737a`](https://github.com/PrismJS/prism/commit/a2d737a)] -* __Tcl__: - * Regexp simplification + don't use captures if not needed [[`f0b8a33`](https://github.com/PrismJS/prism/commit/f0b8a33)] -* __Textile__: - * Regexp optimisation + don't use captures if not needed [[`08139ad`](https://github.com/PrismJS/prism/commit/08139ad)] -* __Twig__: - * Regexp optimisation and simplification + don't use captures if not needed [[`0b10fd0`](https://github.com/PrismJS/prism/commit/0b10fd0)] -* __TypeScript__: - * Don't use captures if not needed [[`e296caf`](https://github.com/PrismJS/prism/commit/e296caf)] -* __Verilog__: - * Regexp simplification [[`1b24b34`](https://github.com/PrismJS/prism/commit/1b24b34)] -* __VHDL__: - * Regexp optimisation and simplification [[`7af36df`](https://github.com/PrismJS/prism/commit/7af36df)] -* __vim__: - * Remove duplicates in keywords [[`700505e`](https://github.com/PrismJS/prism/commit/700505e)] -* __Wiki markup__: - * Fix escaping consistency [[`1fd690d`](https://github.com/PrismJS/prism/commit/1fd690d)] -* __YAML__: - * Regexp optimisation + don't use captures if not needed [[`1fd690d`](https://github.com/PrismJS/prism/commit/1fd690d)] - -### Other changes -* Remove comments spellcheck for AMP validation ([#1106](https://github.com/PrismJS/prism/issues/1106)) [[`de996d7`](https://github.com/PrismJS/prism/commit/de996d7)] -* Prevent error from throwing when element does not have a parentNode in highlightElement. [[`c33be19`](https://github.com/PrismJS/prism/commit/c33be19)] -* Provide a way to load Prism from inside a Worker without listening to messages. ([#1188](https://github.com/PrismJS/prism/issues/1188)) [[`d09982d`](https://github.com/PrismJS/prism/commit/d09982d)] - -## 1.8.3 (2017-10-19) - -### Other changes - -* Fix inclusion tests for Pug [[`955c2ab`](https://github.com/PrismJS/prism/commit/955c2ab)] - -## 1.8.2 (2017-10-19) - -### Updated components -* __Jade__: - * Jade has been renamed to __Pug__ ([#1201](https://github.com/PrismJS/prism/issues/1201)) [[`bcfef7c`](https://github.com/PrismJS/prism/commit/bcfef7c)] -* __JavaScript__: - * Better highlighting of functions ([#1190](https://github.com/PrismJS/prism/issues/1190)) [[`8ee2cd3`](https://github.com/PrismJS/prism/commit/8ee2cd3)] - -### Update plugins -* __Copy to clipboard__: - * Fix error occurring when using in Chrome 61+ ([#1206](https://github.com/PrismJS/prism/issues/1206)) [[`b41d571`](https://github.com/PrismJS/prism/commit/b41d571)] -* __Show invisibles__: - * Prevent error when using with Autoloader plugin ([#1195](https://github.com/PrismJS/prism/issues/1195)) [[`ed8bdb5`](https://github.com/PrismJS/prism/commit/ed8bdb5)] - -## 1.8.1 (2017-09-16) - -### Other changes - -* Add Arduino to components.js [[`290a3c6`](https://github.com/PrismJS/prism/commit/290a3c6)] - -## 1.8.0 (2017-09-16) - -### New components - -* __Arduino__ ([#1184](https://github.com/PrismJS/prism/issues/1184)) [[`edf2454`](https://github.com/PrismJS/prism/commit/edf2454)] -* __OpenCL__ ([#1175](https://github.com/PrismJS/prism/issues/1175)) [[`131e8fa`](https://github.com/PrismJS/prism/commit/131e8fa)] - -### Updated plugins - -* __Autolinker__: - * Silently catch any error thrown by decodeURIComponent. Fixes [#1186](https://github.com/PrismJS/prism/issues/1186) [[`2e43fcf`](https://github.com/PrismJS/prism/commit/2e43fcf)] - -## 1.7.0 (2017-09-09) - -### New components - -* __Django/Jinja2__ ([#1085](https://github.com/PrismJS/prism/issues/1085)) [[`345b1b2`](https://github.com/PrismJS/prism/commit/345b1b2)] -* __N4JS__ ([#1141](https://github.com/PrismJS/prism/issues/1141)) [[`eaa8ebb`](https://github.com/PrismJS/prism/commit/eaa8ebb)] -* __Ren'py__ ([#658](https://github.com/PrismJS/prism/issues/658)) [[`7ab4013`](https://github.com/PrismJS/prism/commit/7ab4013)] -* __VB.Net__ ([#1122](https://github.com/PrismJS/prism/issues/1122)) [[`5400651`](https://github.com/PrismJS/prism/commit/5400651)] - -### Updated components - -* __APL__: - * Add left shoe underbar and right shoe underbar ([#1072](https://github.com/PrismJS/prism/issues/1072)) [[`12238c5`](https://github.com/PrismJS/prism/commit/12238c5)] - * Update prism-apl.js ([#1126](https://github.com/PrismJS/prism/issues/1126)) [[`a5f3cdb`](https://github.com/PrismJS/prism/commit/a5f3cdb)] -* __C__: - * Add more keywords and constants for C. ([#1029](https://github.com/PrismJS/prism/issues/1029)) [[`43a388e`](https://github.com/PrismJS/prism/commit/43a388e)] -* __C#__: - * Fix wrong highlighting when three slashes appear inside string. Fix [#1091](https://github.com/PrismJS/prism/issues/1091) [[`dfb6f17`](https://github.com/PrismJS/prism/commit/dfb6f17)] -* __C-like__: - * Add support for unclosed block comments. Close [#828](https://github.com/PrismJS/prism/issues/828) [[`3426ed1`](https://github.com/PrismJS/prism/commit/3426ed1)] -* __Crystal__: - * Update Crystal keywords ([#1092](https://github.com/PrismJS/prism/issues/1092)) [[`125bff1`](https://github.com/PrismJS/prism/commit/125bff1)] -* __CSS Extras__: - * Support CSS #RRGGBBAA ([#1139](https://github.com/PrismJS/prism/issues/1139)) [[`07a6806`](https://github.com/PrismJS/prism/commit/07a6806)] -* __Docker__: - * Add dockerfile alias for docker language ([#1164](https://github.com/PrismJS/prism/issues/1164)) [[`601c47f`](https://github.com/PrismJS/prism/commit/601c47f)] - * Update the list of keywords for dockerfiles ([#1180](https://github.com/PrismJS/prism/issues/1180)) [[`f0d73e0`](https://github.com/PrismJS/prism/commit/f0d73e0)] -* __Eiffel__: - * Add class-name highlighting for Eiffel ([#471](https://github.com/PrismJS/prism/issues/471)) [[`cd03587`](https://github.com/PrismJS/prism/commit/cd03587)] -* __Handlebars__: - * Check for possible pre-existing marker strings in Handlebars [[`7a1a404`](https://github.com/PrismJS/prism/commit/7a1a404)] -* __JavaScript__: - * Properly match every operator as a whole token. Fix [#1133](https://github.com/PrismJS/prism/issues/1133) [[`9f649fb`](https://github.com/PrismJS/prism/commit/9f649fb)] - * Allows uppercase prefixes in JS number literals ([#1151](https://github.com/PrismJS/prism/issues/1151)) [[`d4ee904`](https://github.com/PrismJS/prism/commit/d4ee904)] - * Reduced backtracking in regex pattern. Fix [#1159](https://github.com/PrismJS/prism/issues/1159) [[`ac09e97`](https://github.com/PrismJS/prism/commit/ac09e97)] -* __JSON__: - * Fix property and string patterns performance. Fix [#1080](https://github.com/PrismJS/prism/issues/1080) [[`0ca1353`](https://github.com/PrismJS/prism/commit/0ca1353)] -* __JSX__: - * JSX spread operator break. Fixes [#1061](https://github.com/PrismJS/prism/issues/1061) ([#1094](https://github.com/PrismJS/prism/issues/1094)) [[`561bceb`](https://github.com/PrismJS/prism/commit/561bceb)] - * Fix highlighting of attributes containing spaces [[`867ea42`](https://github.com/PrismJS/prism/commit/867ea42)] - * Improved performance for tags (when not matching) Fix [#1152](https://github.com/PrismJS/prism/issues/1152) [[`b0fe103`](https://github.com/PrismJS/prism/commit/b0fe103)] -* __LOLCODE__: - * Make strings greedy Golmote [[`1a5e7a4`](https://github.com/PrismJS/prism/commit/1a5e7a4)] -* __Markup__: - * Support HTML entities in attribute values ([#1143](https://github.com/PrismJS/prism/issues/1143)) [[`1d5047d`](https://github.com/PrismJS/prism/commit/1d5047d)] -* __NSIS__: - * Update patterns ([#1033](https://github.com/PrismJS/prism/issues/1033)) [[`01a59d8`](https://github.com/PrismJS/prism/commit/01a59d8)] - * Add support for NSIS 3.02 ([#1169](https://github.com/PrismJS/prism/issues/1169)) [[`393b5f7`](https://github.com/PrismJS/prism/commit/393b5f7)] -* __PHP__: - * Fix the PHP language ([#1100](https://github.com/PrismJS/prism/issues/1100)) [[`1453fa7`](https://github.com/PrismJS/prism/commit/1453fa7)] - * Check for possible pre-existing marker strings in PHP [[`36bc560`](https://github.com/PrismJS/prism/commit/36bc560)] -* __Ruby__: - * Fix slash regex performance. Fix [#1083](https://github.com/PrismJS/prism/issues/1083) [[`a708730`](https://github.com/PrismJS/prism/commit/a708730)] - * Add support for =begin =end comments. Manual merge of [#1121](https://github.com/PrismJS/prism/issues/1121). [[`62cdaf8`](https://github.com/PrismJS/prism/commit/62cdaf8)] -* __Smarty__: - * Check for possible pre-existing marker strings in Smarty [[`5df26e2`](https://github.com/PrismJS/prism/commit/5df26e2)] -* __TypeScript__: - * Update typescript keywords ([#1064](https://github.com/PrismJS/prism/issues/1064)) [[`52020a0`](https://github.com/PrismJS/prism/commit/52020a0)] - * Chmod -x prism-typescript component ([#1145](https://github.com/PrismJS/prism/issues/1145)) [[`afe0542`](https://github.com/PrismJS/prism/commit/afe0542)] -* __YAML__: - * Make strings greedy (partial fix for [#1075](https://github.com/PrismJS/prism/issues/1075)) [[`565a2cc`](https://github.com/PrismJS/prism/commit/565a2cc)] - -### Updated plugins - -* __Autolinker__: - * Fixed an rendering issue for encoded urls ([#1173](https://github.com/PrismJS/prism/issues/1173)) [[`abc007f`](https://github.com/PrismJS/prism/commit/abc007f)] -* __Custom Class__: - * Add missing noCSS property for the Custom Class plugin [[`ba64f8d`](https://github.com/PrismJS/prism/commit/ba64f8d)] - * Added a default for classMap. Fixes [#1137](https://github.com/PrismJS/prism/issues/1137). ([#1157](https://github.com/PrismJS/prism/issues/1157)) [[`5400af9`](https://github.com/PrismJS/prism/commit/5400af9)] -* __Keep Markup__: - * Store highlightedCode after reinserting markup. Fix [#1127](https://github.com/PrismJS/prism/issues/1127) [[`6df2ceb`](https://github.com/PrismJS/prism/commit/6df2ceb)] -* __Line Highlight__: - * Cleanup left-over line-highlight tags before other plugins run [[`79b723d`](https://github.com/PrismJS/prism/commit/79b723d)] - * Avoid conflict between line-highlight and other plugins [[`224fdb8`](https://github.com/PrismJS/prism/commit/224fdb8)] -* __Line Numbers__: - * Support soft wrap for line numbers plugin ([#584](https://github.com/PrismJS/prism/issues/584)) [[`849f1d6`](https://github.com/PrismJS/prism/commit/849f1d6)] - * Plugins fixes (unescaped-markup, line-numbers) ([#1012](https://github.com/PrismJS/prism/issues/1012)) [[`3fb7cf8`](https://github.com/PrismJS/prism/commit/3fb7cf8)] -* __Normalize Whitespace__: - * Add Node.js support for the normalize-whitespace plugin [[`6c7dae2`](https://github.com/PrismJS/prism/commit/6c7dae2)] -* __Unescaped Markup__: - * Plugins fixes (unescaped-markup, line-numbers) ([#1012](https://github.com/PrismJS/prism/issues/1012)) [[`3fb7cf8`](https://github.com/PrismJS/prism/commit/3fb7cf8)] - -### Updated themes -* __Coy__: - * Scroll 'Coy' background with contents ([#1163](https://github.com/PrismJS/prism/issues/1163)) [[`310990b`](https://github.com/PrismJS/prism/commit/310990b)] - -### Other changes - -* Initial implementation of manual highlighting ([#1087](https://github.com/PrismJS/prism/issues/1087)) [[`bafc4cb`](https://github.com/PrismJS/prism/commit/bafc4cb)] -* Remove dead link in Third-party tutorials section. Fixes [#1028](https://github.com/PrismJS/prism/issues/1028) [[`dffadc6`](https://github.com/PrismJS/prism/commit/dffadc6)] -* Most languages now use the greedy flag for better highlighting [[`7549ecc`](https://github.com/PrismJS/prism/commit/7549ecc)] -* .npmignore: Unignore components.js ([#1108](https://github.com/PrismJS/prism/issues/1108)) [[`1f699e7`](https://github.com/PrismJS/prism/commit/1f699e7)] -* Run before-highlight and after-highlight hooks even when no grammar is found. Fix [#1134](https://github.com/PrismJS/prism/issues/1134) [[`70cb472`](https://github.com/PrismJS/prism/commit/70cb472)] -* Replace [\w\W] with [\s\S] and [0-9] with \d in regexes ([#1107](https://github.com/PrismJS/prism/issues/1107)) [[`8aa2cc4`](https://github.com/PrismJS/prism/commit/8aa2cc4)] -* Fix corner cases for the greedy flag ([#1095](https://github.com/PrismJS/prism/issues/1095)) [[`6530709`](https://github.com/PrismJS/prism/commit/6530709)] -* Add Third Party Tutorial ([#1156](https://github.com/PrismJS/prism/issues/1156)) [[`c34e57b`](https://github.com/PrismJS/prism/commit/c34e57b)] -* Add Composer support ([#648](https://github.com/PrismJS/prism/issues/648)) [[`2989633`](https://github.com/PrismJS/prism/commit/2989633)] -* Remove IE8 plugin ([#992](https://github.com/PrismJS/prism/issues/992)) [[`25788eb`](https://github.com/PrismJS/prism/commit/25788eb)] -* Website: remove width and height on logo.svg, so it becomes scalable. Close [#1005](https://github.com/PrismJS/prism/issues/1005) [[`0621ff7`](https://github.com/PrismJS/prism/commit/0621ff7)] -* Remove yarn.lock ([#1098](https://github.com/PrismJS/prism/issues/1098)) [[`11eed25`](https://github.com/PrismJS/prism/commit/11eed25)] - -## 1.6.0 (2016-12-03) - -### New components - -* __.properties__ ([#980](https://github.com/PrismJS/prism/issues/980)) [[`be6219a`](https://github.com/PrismJS/prism/commit/be6219a)] -* __Ada__ ([#949](https://github.com/PrismJS/prism/issues/949)) [[`65619f7`](https://github.com/PrismJS/prism/commit/65619f7)] -* __GraphQL__ ([#971](https://github.com/PrismJS/prism/issues/971)) [[`e018087`](https://github.com/PrismJS/prism/commit/e018087)] -* __Jolie__ ([#1014](https://github.com/PrismJS/prism/issues/1014)) [[`dfc1941`](https://github.com/PrismJS/prism/commit/dfc1941)] -* __LiveScript__ ([#982](https://github.com/PrismJS/prism/issues/982)) [[`62e258c`](https://github.com/PrismJS/prism/commit/62e258c)] -* __Reason__ (Fixes [#1046](https://github.com/PrismJS/prism/issues/1046)) [[`3cae6ce`](https://github.com/PrismJS/prism/commit/3cae6ce)] -* __Xojo__ ([#994](https://github.com/PrismJS/prism/issues/994)) [[`0224b7c`](https://github.com/PrismJS/prism/commit/0224b7c)] - -### Updated components - -* __APL__: - * Add iota underbar ([#1024](https://github.com/PrismJS/prism/issues/1024)) [[`3c5c89a`](https://github.com/PrismJS/prism/commit/3c5c89a), [`ac21d33`](https://github.com/PrismJS/prism/commit/ac21d33)] -* __AsciiDoc__: - * Optimized block regexps to prevent struggling on large files. Fixes [#1001](https://github.com/PrismJS/prism/issues/1001). [[`1a86d34`](https://github.com/PrismJS/prism/commit/1a86d34)] -* __Bash__: - * Add `npm` to function list ([#969](https://github.com/PrismJS/prism/issues/969)) [[`912bdfe`](https://github.com/PrismJS/prism/commit/912bdfe)] -* __CSS__: - * Make CSS strings greedy. Fix [#1013](https://github.com/PrismJS/prism/issues/1013). [[`e57e26d`](https://github.com/PrismJS/prism/commit/e57e26d)] -* __CSS Extras__: - * Match attribute inside selectors [[`13fed76`](https://github.com/PrismJS/prism/commit/13fed76)] -* _Groovy__: - * Fix order of decoding entities in groovy. Fixes [#1049](https://github.com/PrismJS/prism/issues/1049) ([#1050](https://github.com/PrismJS/prism/issues/1050)) [[`d75da8e`](https://github.com/PrismJS/prism/commit/d75da8e)] -* __Ini__: - * Remove important token in ini definition ([#1047](https://github.com/PrismJS/prism/issues/1047)) [[`fe8ad8b`](https://github.com/PrismJS/prism/commit/fe8ad8b)] -* __JavaScript__: - * Add exponentiation & spread/rest operator ([#991](https://github.com/PrismJS/prism/issues/991)) [[`b2de65a`](https://github.com/PrismJS/prism/commit/b2de65a), [`268d01e`](https://github.com/PrismJS/prism/commit/268d01e)] -* __JSON_: - * JSON: Fixed issues with properties and strings + added tests. Fix [#1025](https://github.com/PrismJS/prism/issues/1025) [[`25a541d`](https://github.com/PrismJS/prism/commit/25a541d)] -* __Markup__: - * Allow for dots in Markup tag names, but not in HTML tags included in Textile. Fixes [#888](https://github.com/PrismJS/prism/issues/888). [[`31ea66b`](https://github.com/PrismJS/prism/commit/31ea66b)] - * Make doctype case-insensitive ([#1009](https://github.com/PrismJS/prism/issues/1009)) [[`3dd7219`](https://github.com/PrismJS/prism/commit/3dd7219)] -* __NSIS__: - * Updated patterns ([#1032](https://github.com/PrismJS/prism/issues/1032)) [[`76ba1b8`](https://github.com/PrismJS/prism/commit/76ba1b8)] -* __PHP__: - * Make comments greedy. Fix [#197](https://github.com/PrismJS/prism/issues/197) [[`318aab3`](https://github.com/PrismJS/prism/commit/318aab3)] -* __PowerShell__: - * Fix highlighting of empty comments ([#977](https://github.com/PrismJS/prism/issues/977)) [[`4fda477`](https://github.com/PrismJS/prism/commit/4fda477)] -* __Puppet__: - * Fix over-greedy regexp detection ([#978](https://github.com/PrismJS/prism/issues/978)) [[`105be25`](https://github.com/PrismJS/prism/commit/105be25)] -* __Ruby__: - * Fix typo `Fload` to `Float` in prism-ruby.js ([#1023](https://github.com/PrismJS/prism/issues/1023)) [[`22cb018`](https://github.com/PrismJS/prism/commit/22cb018)] - * Make strings greedy. Fixes [#1048](https://github.com/PrismJS/prism/issues/1048) [[`8b0520a`](https://github.com/PrismJS/prism/commit/8b0520a)] -* __SCSS__: - * Alias statement as keyword. Fix [#246](https://github.com/PrismJS/prism/issues/246) [[`fd09391`](https://github.com/PrismJS/prism/commit/fd09391)] - * Highlight variables inside selectors and properties. [[`d6b5c2f`](https://github.com/PrismJS/prism/commit/d6b5c2f)] - * Highlight parent selector [[`8f5f1fa`](https://github.com/PrismJS/prism/commit/8f5f1fa)] -* __TypeScript__: - * Add missing `from` keyword to typescript & set `ts` as alias. ([#1042](https://github.com/PrismJS/prism/issues/1042)) [[`cba78f3`](https://github.com/PrismJS/prism/commit/cba78f3)] - -### New plugins - -* __Copy to Clipboard__ ([#891](https://github.com/PrismJS/prism/issues/891)) [[`07b81ac`](https://github.com/PrismJS/prism/commit/07b81ac)] -* __Custom Class__ ([#950](https://github.com/PrismJS/prism/issues/950)) [[`a0bd686`](https://github.com/PrismJS/prism/commit/a0bd686)] -* __Data-URI Highlight__ ([#996](https://github.com/PrismJS/prism/issues/996)) [[`bdca61b`](https://github.com/PrismJS/prism/commit/bdca61b)] -* __Toolbar__ ([#891](https://github.com/PrismJS/prism/issues/891)) [[`07b81ac`](https://github.com/PrismJS/prism/commit/07b81ac)] - -### Updated plugins - -* __Autoloader__: - * Updated documentation for Autoloader plugin [[`b4f3423`](https://github.com/PrismJS/prism/commit/b4f3423)] - * Download all grammars as a zip from Autoloader plugin page ([#981](https://github.com/PrismJS/prism/issues/981)) [[`0d0a007`](https://github.com/PrismJS/prism/commit/0d0a007), [`5c815d3`](https://github.com/PrismJS/prism/commit/5c815d3)] - * Removed duplicated script on Autoloader plugin page [[`9671996`](https://github.com/PrismJS/prism/commit/9671996)] - * Don't try to load "none" component. Fix [#1000](https://github.com/PrismJS/prism/issues/1000) [[`f89b0b9`](https://github.com/PrismJS/prism/commit/f89b0b9)] -* __WPD__: - * Fix at-rule detection + don't process if language is not handled [[`2626728`](https://github.com/PrismJS/prism/commit/2626728)] - -### Other changes - -* Improvement to greedy-flag ([#967](https://github.com/PrismJS/prism/issues/967)) [[`500121b`](https://github.com/PrismJS/prism/commit/500121b), [`9893489`](https://github.com/PrismJS/prism/commit/9893489)] -* Add setTimeout fallback for requestAnimationFrame. Fixes [#987](https://github.com/PrismJS/prism/issues/987). ([#988](https://github.com/PrismJS/prism/issues/988)) [[`c9bdcd3`](https://github.com/PrismJS/prism/commit/c9bdcd3)] -* Added aria-hidden attributes on elements created by the Line Highlight and Line Numbers plugins. Fixes [#574](https://github.com/PrismJS/prism/issues/574). [[`e5587a7`](https://github.com/PrismJS/prism/commit/e5587a7)] -* Don't insert space before ">" when there is no attributes [[`3dc8c9e`](https://github.com/PrismJS/prism/commit/3dc8c9e)] -* Added missing hooks-related tests for AsciiDoc, Groovy, Handlebars, Markup, PHP and Smarty [[`c1a0c1b`](https://github.com/PrismJS/prism/commit/c1a0c1b)] -* Fix issue when using Line numbers plugin and Normalise whitespace plugin together with Handlebars, PHP or Smarty. Fix [#1018](https://github.com/PrismJS/prism/issues/1018), [#997](https://github.com/PrismJS/prism/issues/997), [#935](https://github.com/PrismJS/prism/issues/935). Revert [#998](https://github.com/PrismJS/prism/issues/998). [[`86aa3d2`](https://github.com/PrismJS/prism/commit/86aa3d2)] -* Optimized logo ([#990](https://github.com/PrismJS/prism/issues/990)) ([#1002](https://github.com/PrismJS/prism/issues/1002)) [[`f69e570`](https://github.com/PrismJS/prism/commit/f69e570), [`218fd25`](https://github.com/PrismJS/prism/commit/218fd25)] -* Remove unneeded prefixed CSS ([#989](https://github.com/PrismJS/prism/issues/989)) [[`5e56833`](https://github.com/PrismJS/prism/commit/5e56833)] -* Optimize images ([#1007](https://github.com/PrismJS/prism/issues/1007)) [[`b2fa6d5`](https://github.com/PrismJS/prism/commit/b2fa6d5)] -* Add yarn.lock to .gitignore ([#1035](https://github.com/PrismJS/prism/issues/1035)) [[`03ecf74`](https://github.com/PrismJS/prism/commit/03ecf74)] -* Fix greedy flag bug. Fixes [#1039](https://github.com/PrismJS/prism/issues/1039) [[`32cd99f`](https://github.com/PrismJS/prism/commit/32cd99f)] -* Ruby: Fix test after [#1023](https://github.com/PrismJS/prism/issues/1023) [[`b15d43b`](https://github.com/PrismJS/prism/commit/b15d43b)] -* Ini: Fix test after [#1047](https://github.com/PrismJS/prism/issues/1047) [[`25cdd3f`](https://github.com/PrismJS/prism/commit/25cdd3f)] -* Reduce risk of XSS ([#1051](https://github.com/PrismJS/prism/issues/1051)) [[`17e33bc`](https://github.com/PrismJS/prism/commit/17e33bc)] -* env.code can be modified by before-sanity-check hook even when using language-none. Fix [#1066](https://github.com/PrismJS/prism/issues/1066) [[`83bafbd`](https://github.com/PrismJS/prism/commit/83bafbd)] - - -## 1.5.1 (2016-06-05) - -### Updated components - -* __Normalize Whitespace__: - * Add class that disables the normalize whitespace plugin [[`9385c54`](https://github.com/PrismJS/prism/commit/9385c54)] -* __JavaScript Language__: - * Rearrange the `string` and `template-string` token in JavaScript [[`1158e46`](https://github.com/PrismJS/prism/commit/1158e46)] -* __SQL Language__: - * add delimeter and delimeters keywords to sql ([#958](https://github.com/PrismJS/prism/pull/958)) [[`a9ef24e`](https://github.com/PrismJS/prism/commit/a9ef24e)] - * add AUTO_INCREMENT and DATE keywords to sql ([#954](https://github.com/PrismJS/prism/pull/954)) [[`caea2af`](https://github.com/PrismJS/prism/commit/caea2af)] -* __Diff Language__: - * Highlight diff lines with only + or - ([#952](https://github.com/PrismJS/prism/pull/952)) [[`4d0526f`](https://github.com/PrismJS/prism/commit/4d0526f)] - -### Other changes - -* Allow for asynchronous loading of prism.js ([#959](https://github.com/PrismJS/prism/pull/959)) -* Use toLowerCase on language names ([#957](https://github.com/PrismJS/prism/pull/957)) [[`acd9508`](https://github.com/PrismJS/prism/commit/acd9508)] -* link to index for basic usage - fixes [#945](https://github.com/PrismJS/prism/issues/945) ([#946](https://github.com/PrismJS/prism/pull/946)) [[`6c772d8`](https://github.com/PrismJS/prism/commit/6c772d8)] -* Fixed monospace typo ([#953](https://github.com/PrismJS/prism/pull/953)) [[`e6c3498`](https://github.com/PrismJS/prism/commit/e6c3498)] - -## 1.5.0 (2016-05-01) - -### New components - -* __Bro Language__ ([#925](https://github.com/PrismJS/prism/pull/925)) -* __Protocol Buffers Language__ ([#938](https://github.com/PrismJS/prism/pull/938)) [[`ae4a4f2`](https://github.com/PrismJS/prism/commit/ae4a4f2)] - -### Updated components - -* __Keep Markup__: - * Fix Keep Markup plugin incorrect highlighting ([#880](https://github.com/PrismJS/prism/pull/880)) [[`24841ef`](https://github.com/PrismJS/prism/commit/24841ef)] -* __Groovy Language__: - * Fix double HTML-encoding bug in Groovy language [[`24a0936`](https://github.com/PrismJS/prism/commit/24a0936)] -* __Java Language__: - * Adding annotation token for Java ([#905](https://github.com/PrismJS/prism/pull/905)) [[`367ace6`](https://github.com/PrismJS/prism/commit/367ace6)] -* __SAS Language__: - * Add missing keywords for SAS ([#922](https://github.com/PrismJS/prism/pull/922)) -* __YAML Language__: - * fix hilighting of YAML keys on first line of code block ([#943](https://github.com/PrismJS/prism/pull/943)) [[`f19db81`](https://github.com/PrismJS/prism/commit/f19db81)] -* __C# Language__: - * Support for generic methods in csharp [[`6f75735`](https://github.com/PrismJS/prism/commit/6f75735)] - -### New plugins - -* __Unescaped Markup__ [[`07d77e5`](https://github.com/PrismJS/prism/commit/07d77e5)] -* __Normalize Whitespace__ ([#847](https://github.com/PrismJS/prism/pull/847)) [[`e86ec01`](https://github.com/PrismJS/prism/commit/e86ec01)] - -### Other changes - -* Add JSPM support [[`ad048ab`](https://github.com/PrismJS/prism/commit/ad048ab)] -* update linear-gradient syntax from `left` to `to right` [[`cd234dc`](https://github.com/PrismJS/prism/commit/cd234dc)] -* Add after-property to allow ordering of plugins [[`224b7a1`](https://github.com/PrismJS/prism/commit/224b7a1)] -* Partial solution for the "Comment-like substrings"-problem [[`2705c50`](https://github.com/PrismJS/prism/commit/2705c50)] -* Add property 'aliasTitles' to components.js [[`54400fb`](https://github.com/PrismJS/prism/commit/54400fb)] -* Add before-highlightall hook [[`70a8602`](https://github.com/PrismJS/prism/commit/70a8602)] -* Fix catastrophic backtracking regex issues in JavaScript [[`ab65be2`](https://github.com/PrismJS/prism/commit/ab65be2)] - -## 1.4.1 (2016-02-03) - -### Other changes - -* Fix DFS bug in Prism core [[`b86c727`](https://github.com/PrismJS/prism/commit/b86c727)] - -## 1.4.0 (2016-02-03) - -### New components - -* __Solarized Light__ ([#855](https://github.com/PrismJS/prism/pull/855)) [[`70846ba`](https://github.com/PrismJS/prism/commit/70846ba)] -* __JSON__ ([#370](https://github.com/PrismJS/prism/pull/370)) [[`ad2fcd0`](https://github.com/PrismJS/prism/commit/ad2fcd0)] - -### Updated components - -* __Show Language__: - * Remove data-language attribute ([#840](https://github.com/PrismJS/prism/pull/840)) [[`eb9a83c`](https://github.com/PrismJS/prism/commit/eb9a83c)] - * Allow custom label without a language mapping ([#837](https://github.com/PrismJS/prism/pull/837)) [[`7e74aef`](https://github.com/PrismJS/prism/commit/7e74aef)] -* __JSX__: - * Better Nesting in JSX attributes ([#842](https://github.com/PrismJS/prism/pull/842)) [[`971dda7`](https://github.com/PrismJS/prism/commit/971dda7)] -* __File Highlight__: - * Defer File Highlight until the full DOM has loaded. ([#844](https://github.com/PrismJS/prism/pull/844)) [[`6f995ef`](https://github.com/PrismJS/prism/commit/6f995ef)] -* __Coy Theme__: - * Fix coy theme shadows ([#865](https://github.com/PrismJS/prism/pull/865)) [[`58d2337`](https://github.com/PrismJS/prism/commit/58d2337)] -* __Show Invisibles__: - * Ensure show-invisibles compat with autoloader ([#874](https://github.com/PrismJS/prism/pull/874)) [[`c3cfb1f`](https://github.com/PrismJS/prism/commit/c3cfb1f)] - * Add support for the space character for the show-invisibles plugin ([#876](https://github.com/PrismJS/prism/pull/876)) [[`05442d3`](https://github.com/PrismJS/prism/commit/05442d3)] - -### New plugins - -* __Command Line__ ([#831](https://github.com/PrismJS/prism/pull/831)) [[`8378906`](https://github.com/PrismJS/prism/commit/8378906)] - -### Other changes - -* Use document.currentScript instead of document.getElementsByTagName() [[`fa98743`](https://github.com/PrismJS/prism/commit/fa98743)] -* Add prefix for Firefox selection and move prefixed rule first [[`6d54717`](https://github.com/PrismJS/prism/commit/6d54717)] -* No background for `` in `
` [[`8c310bc`](https://github.com/PrismJS/prism/commit/8c310bc)]
-* Fixing to initial copyright year [[`69cbf7a`](https://github.com/PrismJS/prism/commit/69cbf7a)]
-* Simplify the “lang” regex [[`417f54a`](https://github.com/PrismJS/prism/commit/417f54a)]
-* Fix broken heading links [[`a7f9e62`](https://github.com/PrismJS/prism/commit/a7f9e62)]
-* Prevent infinite recursion in DFS [[`02894e1`](https://github.com/PrismJS/prism/commit/02894e1)]
-* Fix incorrect page title [[`544b56f`](https://github.com/PrismJS/prism/commit/544b56f)]
-* Link scss to webplatform wiki [[`08d979a`](https://github.com/PrismJS/prism/commit/08d979a)]
-* Revert white-space to normal when code is inline instead of in a pre [[`1a971b5`](https://github.com/PrismJS/prism/commit/1a971b5)]
-
-## 1.3.0 (2015-10-26)
-
-### New components
-
-* __AsciiDoc__ ([#800](https://github.com/PrismJS/prism/issues/800)) [[`6803ca0`](https://github.com/PrismJS/prism/commit/6803ca0)]
-* __Haxe__ ([#811](https://github.com/PrismJS/prism/issues/811)) [[`bd44341`](https://github.com/PrismJS/prism/commit/bd44341)]
-* __Icon__ ([#803](https://github.com/PrismJS/prism/issues/803)) [[`b43c5f3`](https://github.com/PrismJS/prism/commit/b43c5f3)]
-* __Kotlin ([#814](https://github.com/PrismJS/prism/issues/814)) [[`e8a31a5`](https://github.com/PrismJS/prism/commit/e8a31a5)]
-* __Lua__ ([#804](https://github.com/PrismJS/prism/issues/804)) [[`a36bc4a`](https://github.com/PrismJS/prism/commit/a36bc4a)]
-* __Nix__ ([#795](https://github.com/PrismJS/prism/issues/795)) [[`9b275c8`](https://github.com/PrismJS/prism/commit/9b275c8)]
-* __Oz__ ([#805](https://github.com/PrismJS/prism/issues/805)) [[`388c53f`](https://github.com/PrismJS/prism/commit/388c53f)]
-* __PARI/GP__ ([#802](https://github.com/PrismJS/prism/issues/802)) [[`253c035`](https://github.com/PrismJS/prism/commit/253c035)]
-* __Parser__ ([#808](https://github.com/PrismJS/prism/issues/808)) [[`a953b3a`](https://github.com/PrismJS/prism/commit/a953b3a)]
-* __Puppet__ ([#813](https://github.com/PrismJS/prism/issues/813)) [[`81933ee`](https://github.com/PrismJS/prism/commit/81933ee)]
-* __Roboconf__ ([#812](https://github.com/PrismJS/prism/issues/812)) [[`f5db346`](https://github.com/PrismJS/prism/commit/f5db346)]
-
-### Updated components
-
-* __C__:
-	* Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)]
-* __C#__:
-	* Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)]
-	* Fix detection of float numbers ([#806](https://github.com/PrismJS/prism/issues/806)) [[`1dae72b`](https://github.com/PrismJS/prism/commit/1dae72b)]
-* __F#__:
-	* Highlight directives in preprocessor lines ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)]
-* __JavaScript__:
-	* Highlight true and false as booleans ([#801](https://github.com/PrismJS/prism/issues/801)) [[`ad316a3`](https://github.com/PrismJS/prism/commit/ad316a3)]
-* __Python__:
-	* Highlight triple-quoted strings before comments. Fix [#815](https://github.com/PrismJS/prism/issues/815) [[`90fbf0b`](https://github.com/PrismJS/prism/commit/90fbf0b)]
-
-### New plugins
-
-* __Previewer: Time__ ([#790](https://github.com/PrismJS/prism/issues/790)) [[`88173de`](https://github.com/PrismJS/prism/commit/88173de)]
-* __Previewer: Angle__ ([#791](https://github.com/PrismJS/prism/issues/791)) [[`a434c86`](https://github.com/PrismJS/prism/commit/a434c86)]
-
-### Other changes
-
-* Increase mocha's timeout [[`f1c41db`](https://github.com/PrismJS/prism/commit/f1c41db)]
-* Prevent most errors in IE8. Fix [#9](https://github.com/PrismJS/prism/issues/9) [[`9652d75`](https://github.com/PrismJS/prism/commit/9652d75)]
-* Add U.S. Web Design Standards on homepage. Fix [#785](https://github.com/PrismJS/prism/issues/785) [[`e10d48b`](https://github.com/PrismJS/prism/commit/e10d48b), [`79ebbf8`](https://github.com/PrismJS/prism/commit/79ebbf8), [`2f7088d`](https://github.com/PrismJS/prism/commit/2f7088d)]
-* Added gulp task to autolink PRs and commits in changelog [[`5ec4e4d`](https://github.com/PrismJS/prism/commit/5ec4e4d)]
-* Use child processes to run each set of tests, in order to deal with the memory leak in vm.runInNewContext() [[`9a4b6fa`](https://github.com/PrismJS/prism/commit/9a4b6fa)]
-
-## 1.2.0 (2015-10-07)
-
-### New components
-
-* __Batch__ ([#781](https://github.com/PrismJS/prism/issues/781)) [[`eab5b06`](https://github.com/PrismJS/prism/commit/eab5b06)]
-
-### Updated components
-
-* __ASP.NET__:
-	* Simplified pattern for `
-
-
-
-
-
-
-
-
- -

Customize your download

-

Select your compression level, as well as the languages and plugins you need.

-
- -
-
-

- Compression level: - - -

- -
- -

- Total filesize: ( JavaScript + CSS) -

-

Note: The filesizes displayed refer to non-gizipped files and include any CSS code required. The CSS code is not minified.

- -
-
-
-
- Download JS -
- -
-
- Download CSS -
-
- -
- -
- -
- - - - - - - - - - diff --git a/docs/_style/prism-master/download.js b/docs/_style/prism-master/download.js deleted file mode 100644 index 054da79d..00000000 --- a/docs/_style/prism-master/download.js +++ /dev/null @@ -1,598 +0,0 @@ -/** - * Manage downloads - */ - -(function() { - -var cache = {}; -var form = $('form'); -var minified = true; - -var dependencies = {}; - -var treeURL = 'https://api.github.com/repos/PrismJS/prism/git/trees/gh-pages?recursive=1'; -var treePromise = new Promise(function(resolve) { - $u.xhr({ - url: treeURL, - callback: function(xhr) { - if (xhr.status < 400) { - resolve(JSON.parse(xhr.responseText).tree); - } - } - }); -}); - -var hstr = window.location.hash.match(/(?:languages|plugins)=[-+\w]+|themes=[-\w]+/g); -if (hstr) { - hstr.forEach(function(str) { - var kv = str.split('=', 2), - category = kv[0], - ids = kv[1].split('+'); - if (category !== 'meta' && category !== 'core' && components[category]) { - for (var id in components[category]) { - if (components[category][id].option) { - delete components[category][id].option; - } - } - if (category === 'themes' && ids.length) { - var themeInput = $('#theme input[value="' + ids[0] + '"]'); - if (themeInput) { - themeInput.checked = true; - } - setTheme(ids[0]); - } - var makeDefault = function (id) { - if (id !== 'meta') { - if (components[category][id]) { - if (components[category][id].option !== 'default') { - if (typeof components[category][id] === 'string') { - components[category][id] = { title: components[category][id] } - } - components[category][id].option = 'default'; - } - if (components[category][id].require) { - var deps = components[category][id].require; - if ($u.type(deps) !== 'array') { - deps = [deps]; - } - deps.forEach(makeDefault); - } - } - } - }; - ids.forEach(makeDefault); - } - }); -} - -// Stay compatible with old querystring feature -var qstr = window.location.search.match(/(?:languages|plugins)=[-+\w]+|themes=[-\w]+/g); -if (qstr && !hstr) { - window.location.hash = window.location.search.replace(/^\?/, ''); - window.location.search = ''; -} - -var storedTheme = localStorage.getItem('theme'); - -for (var category in components) { - var all = components[category]; - - all.meta.section = $u.element.create('section', { - className: 'options', - id: 'category-' + category, - contents: { - tag: 'h1', - contents: category.charAt(0).toUpperCase() + category.slice(1) - }, - inside: '#components' - }); - - if (all.meta.addCheckAll) { - $u.element.create('label', { - attributes: { - 'data-id': 'check-all-' + category - }, - contents: [ - { - tag: 'input', - properties: { - type: 'checkbox', - name: 'check-all-' + category, - value: '', - checked: false, - onclick: (function(category, all){ - return function () { - var checkAll = this; - $$('input[name="download-' + category + '"]').forEach(function(input) { - all[input.value].enabled = input.checked = checkAll.checked; - }); - - update(category); - }; - })(category, all) - } - }, - 'Select/unselect all' - ], - inside: all.meta.section - }); - } - - for (var id in all) { - if(id === 'meta') { - continue; - } - - var checked = false, disabled = false; - var option = all[id].option || all.meta.option; - - switch (option) { - case 'mandatory': disabled = true; // fallthrough - case 'default': checked = true; - } - if (category === 'themes' && storedTheme) { - checked = id === storedTheme; - } - - var filepath = all.meta.path.replace(/\{id}/g, id); - - var info = all[id] = { - title: all[id].title || all[id], - aliasTitles: all[id].aliasTitles, - noCSS: all[id].noCSS || all.meta.noCSS, - noJS: all[id].noJS || all.meta.noJS, - enabled: checked, - require: $u.type(all[id].require) === 'string' ? [all[id].require] : all[id].require, - after: $u.type(all[id].after) === 'string' ? [all[id].after] : all[id].after, - peerDependencies: $u.type(all[id].peerDependencies) === 'string' ? [all[id].peerDependencies] : all[id].peerDependencies, - owner: all[id].owner, - files: { - minified: { - paths: [], - size: 0 - }, - dev: { - paths: [], - size: 0 - } - } - }; - - if (info.require) { - info.require.forEach(function (v) { - dependencies[v] = (dependencies[v] || []).concat(id); - }); - } - - if (!all[id].noJS && !/\.css$/.test(filepath)) { - info.files.minified.paths.push(filepath.replace(/(\.js)?$/, '.min.js')); - info.files.dev.paths.push(filepath.replace(/(\.js)?$/, '.js')); - } - - - if ((!all[id].noCSS && !/\.js$/.test(filepath)) || /\.css$/.test(filepath)) { - var cssFile = filepath.replace(/(\.css)?$/, '.css'); - - info.files.minified.paths.push(cssFile); - info.files.dev.paths.push(cssFile); - } - - function getLanguageTitle(lang) { - if (!lang.aliasTitles) - return lang.title; - - var titles = [lang.title]; - for (var alias in lang.aliasTitles) - if (lang.aliasTitles.hasOwnProperty(alias)) - titles.push(lang.aliasTitles[alias]); - return titles.join(" + "); - } - - var label = $u.element.create('label', { - attributes: { - 'data-id': id - }, - contents: [ - { - tag: 'input', - properties: { - type: all.meta.exclusive? 'radio' : 'checkbox', - name: 'download-' + category, - value: id, - checked: checked, - disabled: disabled, - onclick: (function(id, category, all){ - return function () { - $$('input[name="' + this.name + '"]').forEach(function(input) { - all[input.value].enabled = input.checked; - }); - - if (all[id].require && this.checked) { - all[id].require.forEach(function(v) { - var input = $('label[data-id="' + v + '"] > input'); - input.checked = true; - - input.onclick(); - }); - } - - if (dependencies[id] && !this.checked) { // It’s required by others - dependencies[id].forEach(function(dependent) { - var input = $('label[data-id="' + dependent + '"] > input'); - input.checked = false; - - input.onclick(); - }); - } - - update(category, id); - }; - })(id, category, all) - } - }, - all.meta.link? { - tag: 'a', - properties: { - href: all.meta.link.replace(/\{id}/g, id), - className: 'name' - }, - contents: info.title - } : { - tag: 'span', - properties: { - className: 'name' - }, - contents: getLanguageTitle(info) - }, - ' ', - all[id].owner? { - tag: 'a', - properties: { - href: 'https://github.com/' + all[id].owner, - className: 'owner', - target: '_blank' - }, - contents: all[id].owner - } : ' ', - { - tag: 'strong', - className: 'filesize' - } - ], - inside: all.meta.section - }); - - // Add click events on main theme selector too. - (function (label) { - if (category === 'themes') { - var themeInput = $('#theme input[value="' + id + '"]'); - var input = $('input', label); - if (themeInput) { - var themeInputOnclick = themeInput.onclick; - themeInput.onclick = function () { - input.checked = true; - input.onclick(); - themeInputOnclick && themeInputOnclick.call(themeInput); - }; - } - } - }(label)); - } -} - -form.elements.compression[0].onclick = -form.elements.compression[1].onclick = function() { - minified = !!+this.value; - - getFilesSizes(); -}; - -function getFileSize(filepath) { - return treePromise.then(function(tree) { - for(var i=0, l=tree.length; i i) { - notNow = true; - break; - } - } - if (notNow) { - var tmp = sorted[i]; - sorted[i] = sorted[indexOfRequirement]; - sorted[indexOfRequirement] = tmp; - } - else { - i++; - } - } - return sorted; -} - -function getSortedComponentsByRequirements(components, afterName) { - var sorted = getSortedComponents(components, afterName); - return getSortedComponents(components, "require", sorted); -} - -function generateCode(){ - var promises = []; - var redownload = {}; - - for (var category in components) { - var all = components[category]; - - // In case if one component requires other, required component should go first. - var sorted = getSortedComponentsByRequirements(all, category === 'languages' ? 'peerDependencies' : 'after'); - - for (var i = 0; i < sorted.length; i++) { - var id = sorted[i]; - - if(id === 'meta') { - continue; - } - - var info = all[id]; - if (info.enabled) { - if (category !== 'core') { - redownload[category] = redownload[category] || []; - redownload[category].push(id); - } - info.files[minified? 'minified' : 'dev'].paths.forEach(function (path) { - if (cache[path]) { - var type = path.match(/\.(\w+)$/)[1]; - - promises.push({ - contentsPromise: cache[path].contentsPromise, - path: path, - type: type - }); - } - }); - } - } - } - - // Hide error message if visible - var error = $('#download .error'); - error.style.display = ''; - - Promise.all([buildCode(promises), getVersion()]).then(function(arr) { - var res = arr[0]; - var version = arr[1]; - var code = res.code; - var errors = res.errors; - - if(errors.length) { - error.style.display = 'block'; - error.innerHTML = ''; - $u.element.contents(error, errors); - } - - var redownloadUrl = window.location.href.split("#")[0] + "#"; - for (var category in redownload) { - redownloadUrl += category + "=" + redownload[category].join('+') + "&"; - } - redownloadUrl = redownloadUrl.replace(/&$/,""); - window.location.replace(redownloadUrl); - - var versionComment = "/* PrismJS " + version + "\n" + redownloadUrl + " */"; - - for (var type in code) { - var codeElement = $('#download-' + type + ' code'); - - codeElement.textContent = versionComment + "\n" + code[type]; - Prism.highlightElement(codeElement, true); - - $('#download-' + type + ' .download-button').href = 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(versionComment + "\n" + code[type]); - } - }); -} - -function buildCode(promises) { - var i = 0, - l = promises.length; - var code = {js: '', css: ''}; - var errors = []; - - var f = function(resolve) { - if(i < l) { - var p = promises[i]; - p.contentsPromise.then(function(contents) { - code[p.type] += contents + (p.type === 'js' && !/;\s*$/.test(contents) ? ';' : '') + '\n'; - i++; - f(resolve); - }); - p.contentsPromise['catch'](function() { - errors.push($u.element.create({ - tag: 'p', - prop: { - textContent: 'An error occurred while fetching the file "' + p.path + '".' - } - })); - i++; - f(resolve); - }); - } else { - resolve({code: code, errors: errors}); - } - }; - - return new Promise(f); -} - -function getVersion() { - return getFileContents('./package.json').then(function (jsonStr) { - return JSON.parse(jsonStr).version; - }); -} - -})(); diff --git a/docs/_style/prism-master/examples.html b/docs/_style/prism-master/examples.html deleted file mode 100644 index b7dce551..00000000 --- a/docs/_style/prism-master/examples.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - -Examples ▲ Prism - - - - - - - - - - -
-
- -

Examples

-

The examples in this page serve a dual purpose: They act as unit tests, making it easy to spot bugs, and at the same time demonstrate what Prism can do, on simple and on edge cases.

-
- -
-

Different markup

- -

code.language-css

- p { color: red; } - -

pre.language-css > code

-
p { color: red; }
- -

pre > code.language-css

-
p { color: red; }
- -

pre.language-css > code.language-*

-
p { color: red; }
- -

code.lang-css

- p { color: red; } - -

pre.lang-css > code

-
p { color: red; }
- -

pre > code

-

No language, should inherit .language-markup

-
<p>hi!</p>
- -

code.language-*

-

No language, should inherit .language-markup

- <p>hi!</p> - -

code.language-none

-

Should not be highlighted.

- <p>hi!</p> -
- -
-

Per language examples

-
-
-
- -
- - - - - - - - - - diff --git a/docs/_style/prism-master/examples.js b/docs/_style/prism-master/examples.js deleted file mode 100644 index ba275c55..00000000 --- a/docs/_style/prism-master/examples.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Manage examples - */ - -(function() { - -var examples = {}; - -var treeURL = 'https://api.github.com/repos/PrismJS/prism/git/trees/gh-pages?recursive=1'; -var treePromise = new Promise(function (resolve) { - $u.xhr({ - url: treeURL, - callback: function (xhr) { - if (xhr.status < 400) { - resolve(JSON.parse(xhr.responseText).tree); - } - } - }); -}); - -var languages = components.languages; - -for (var id in languages) { - if (id === 'meta') { - continue; - } - - (function (id) { - var language = languages[id]; - var checked = false; - - if (language.option === 'default') { - checked = true; - } - - language.enabled = checked; - language.path = languages.meta.path.replace(/\{id}/g, id) + '.js'; - language.examplesPath = languages.meta.examplesPath.replace(/\{id}/g, id) + '.html'; - - fileExists(language.examplesPath).then(function (exists) { - $u.element.create('label', { - attributes: { - 'data-id': id, - 'title': !exists ? 'No examples are available for this language.' : '' - }, - className: !exists ? 'unavailable' : '', - contents: [ - { - tag: 'input', - properties: { - type: 'checkbox', - name: 'language', - value: id, - checked: checked && exists, - disabled: !exists, - onclick: function () { - $$('input[name="' + this.name + '"]').forEach(function (input) { - languages[input.value].enabled = input.checked; - }); - - update(id); - } - } - }, - language.title - ], - inside: '#languages' - }); - examples[id] = $u.element.create('section', { - 'id': 'language-' + id, - 'className': 'language-' + id, - inside: '#examples' - }); - if (checked) { - update(id); - } - }); - }(id)); -} - -function fileExists(filepath) { - return treePromise.then(function (tree) { - for (var i = 0, l = tree.length; i < l; i++) { - if (tree[i].path === filepath) { - return true; - } - } - return false; - }); -} - -function getFileContents(filepath) { - return new Promise(function (resolve, reject) { - $u.xhr({ - url: filepath, - callback: function (xhr) { - if (xhr.status < 400 && xhr.responseText) { - resolve(xhr.responseText); - } else { - reject(); - } - } - }); - }); -} - -function buildContentsHeader(id) { - var language = languages[id]; - var header = '

' + language.title + '

'; - if (language.overrideExampleHeader) { - return header; - } - if (language.alias) { - var alias = language.alias; - if (Prism.util.type(alias) !== 'Array') { - alias = [alias]; - } - - header += '

To use this language, use one of the following classes:

'; - header += '
  • "language-' + id + '"
  • '; - alias.forEach(function (alias) { - header += '
  • "language-' + alias + '"
  • '; - }); - header += '
'; - } else { - header += '

To use this language, use the class "language-' + id + '".

'; - } - if (language.require) { - var require = language.require; - if (Prism.util.type(require) !== 'Array') { - require = [require]; - } - - header += '

Dependencies: The following dependencies need to be loaded before this component: '; - header += require.map(function (dep) { - return '' + dep + ''; - }).join(', '); - header += '.

'; - } - return header; -} - -function update(id) { - var language = languages[id]; - if (language.enabled) { - if (!language.examplesPromise) { - language.examplesPromise = getFileContents(language.examplesPath); - } - language.examplesPromise.then(function (contents) { - examples[id].innerHTML = buildContentsHeader(id) + contents; - - loadLanguage(id).then(function () { - var elements = examples[id].querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'); - - for (var i=0, element; element = elements[i++];) { - Prism.highlightElement(element); - } - }); - }); - } else { - examples[id].innerHTML = ''; - } -} - -/** - * Loads a language, including all dependencies - * - * @param {string} lang the language to load - * @type {Promise} the promise which resolves as soon as everything is loaded - */ -function loadLanguage (lang) -{ - // at first we need to fetch all dependencies for the main language - // Note: we need to do this, even if the main language already is loaded (just to be sure..) - // - // We load an array of all dependencies and call recursively this function on each entry - // - // dependencies is now an (possibly empty) array of loading-promises - var dependencies = getDependenciesOfLanguage(lang).map(loadLanguage); - - // We create a promise, which will resolve, as soon as all dependencies are loaded. - // They need to be fully loaded because the main language may extend them. - return Promise.all(dependencies) - .then(function () { - - // If the main language itself isn't already loaded, load it now - // and return the newly created promise (we chain the promises). - // If the language is already loaded, just do nothing - the next .then() - // will immediately be called - if (!Prism.languages[lang]) { - return new Promise(function (resolve) { - $u.script('components/prism-' + lang + '.js', resolve); - }); - } - }); -} - - -/** - * Returns all dependencies (as identifiers) of a specific language - * - * @param {string} lang - * @returns {Array.} the list of dependencies. Empty if the language has none. - */ -function getDependenciesOfLanguage (lang) -{ - if (!components.languages[lang] || !components.languages[lang].require) - { - return []; - } - - return ($u.type(components.languages[lang].require) === "array") - ? components.languages[lang].require - : [components.languages[lang].require]; -} - -}()); diff --git a/docs/_style/prism-master/examples/prism-abap.html b/docs/_style/prism-master/examples/prism-abap.html deleted file mode 100644 index dfda1f7f..00000000 --- a/docs/_style/prism-master/examples/prism-abap.html +++ /dev/null @@ -1,65 +0,0 @@ -

Comments

-

-* Line Comments
-" End of line comment used as line comment.
-value = 1. " End of line comment
-
-DATA:
-  "! ABAPDoc comment
-  value TYPE i.
-
- -

Strings

- -

-my_string = 'Simple string'.
-my_string = 'String with an escaped '' inside'.
-my_string = |A string template: { nvalue } times|.
-my_string = |A string template: { nvalue } times|.
-my_string = |Characters \|, \{, and \} have to be escaped by \\ in literal text.|. 
-
- -

Numbers and Operators

- -

-value = 001 + 2 - 3 * 4 / 5 ** 6.
-
-IF value < 1 OR
-   value = 2 OR
-   value > 3 OR
-   value <> 4 OR
-   value <= 5 OR
-   value >= 6.
-ENDIF.
-
-" Dynamic object assignment (with type cast check)
-lo_interface ?= lo_class.
-
- -

Structures and Classes

- -

-DATA:
-  BEGIN OF my_structure,
-    scomponent TYPE i,
-  END OF my_structure.
-
-CLASS lcl_my_class DEFINITION.
-  PUBLIC SECTION.
-    METHODS my_method
-      RETURNING
-        VALUE(ret_value) TYPE i.
-ENDCLASS.
-
-CLASS lcl_my_class IMPLEMENTATION.
-  METHOD my_method.
-    ret_value = 1.
-  ENDMETHOD
-ENDCLASS.
-
-DATA lo_instace TYPE REF TO lcl_my_class.
-
-CREATE OBJECT lo_instace.
-
-my_structure-component = lo_instace->my_method( ).
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-actionscript.html b/docs/_style/prism-master/examples/prism-actionscript.html deleted file mode 100644 index abb5804d..00000000 --- a/docs/_style/prism-master/examples/prism-actionscript.html +++ /dev/null @@ -1,133 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Literal values

-
17
-"hello"
--3
-9.4
-null
-true
-false
- -

Classes

-
class A {}
-class B extends A {}
- -

Inline XML

-
var employees:XML =
-    <employees>
-        <employee ssn="123-123-1234">
-            <name first="John" last="Doe"/>
-            <address>
-                <city>San Francisco</city>
-                <state>CA</state>
-                <zip>98765</zip>
-            </address>
-        </employee>
-        <employee ssn="789-789-7890">
-            <name first="Mary" last="Roe"/>
-            <address>
-                <city>Newton</city>
-                <state>MA</state>
-                <zip>01234</zip>
-            </address>
-        </employee>
-    </employees>;
- -

Full example

-
package {
-  import flash.display.*;
-  import flash.events.*;
-  import flash.filters.BlurFilter;
-  import flash.geom.*;
-  import flash.ui.*;
-  public class ch23ex2 extends Sprite {
-    protected const BMP_SCALE:Number = 1/2;
-    protected const D:Number = 1.015;
-    protected const DIM_EFFECT:ColorTransform = new ColorTransform(D, D, D);
-    protected const B:int = 16;
-    protected const BLUR_EFFECT:BlurFilter = new BlurFilter(B, B, 1);
-    protected var RLUT:Array, GLUT:Array, BLUT:Array;
-    protected var sourceBmp:BitmapData;
-    protected var colorBmp:BitmapData;
-    protected var touches:Array = new Array();
-    protected var fingerShape:Shape = new Shape();
-    public function ch23ex2() {
-      try {
-        var test:Class = Multitouch;
-        if (Multitouch.supportsTouchEvents) {
-          Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
-          init();
-        } else {
-          trace("Sorry, this example requires multitouch.");
-        }
-      } catch (error:ReferenceError) {
-        trace("Sorry, but multitouch is not supported in this runtime.");
-      }
-    }
-    protected function init():void {
-      //create a black-and-white bitmap and a color bitmap, only show the color
-      sourceBmp = new BitmapData(
-        stage.stageWidth*BMP_SCALE, stage.stageHeight*BMP_SCALE, false, 0);
-      colorBmp = sourceBmp.clone();
-      var bitmap:Bitmap = new Bitmap(colorBmp, PixelSnapping.ALWAYS, true);
-      bitmap.width = stage.stageWidth; bitmap.height = stage.stageHeight;
-      addChild(bitmap);
-
-      //create finger shape to paste onto the bitmap under your touches
-      fingerShape.graphics.beginFill(0xffffff, 0.1);
-      fingerShape.graphics.drawEllipse(-15, -20, 30, 40);
-      fingerShape.graphics.endFill();
-
-      //create the palette map from a gradient
-      var gradient:Shape = new Shape();
-      var m:Matrix = new Matrix();
-      m.createGradientBox(256, 10);
-      gradient.graphics.beginGradientFill(GradientType.LINEAR,
-        [0x313ad8, 0x2dce4a, 0xdae234, 0x7a1c1c, 0x0f0303],
-        [1, 1, 1, 1, 1], [0, 0.4*256, 0.75*256, 0.9*256, 255], m);
-      gradient.graphics.drawRect(0, 0, 256, 10);
-      var gradientBmp:BitmapData = new BitmapData(256, 10, false, 0);
-      gradientBmp.draw(gradient);
-      RLUT = new Array(); GLUT = new Array(); BLUT = new Array();
-      for (var i:int = 0; i < 256; i++) {
-        var pixelColor:uint = gradientBmp.getPixel(i, 0);
-        //I drew the gradient backwards, so sue me
-        RLUT[256-i] = pixelColor & 0xff0000;
-        GLUT[256-i] = pixelColor & 0x00ff00;
-        BLUT[256-i] = pixelColor & 0x0000ff;
-      }
-
-      stage.addEventListener(TouchEvent.TOUCH_BEGIN, assignTouch);
-      stage.addEventListener(TouchEvent.TOUCH_MOVE, assignTouch);
-      stage.addEventListener(TouchEvent.TOUCH_END, removeTouch);
-      stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
-    }
-    protected function assignTouch(event:TouchEvent):void {
-      touches[event.touchPointID] = event;
-    }
-    protected function removeTouch(event:TouchEvent):void {
-      delete touches[event.touchPointID];
-    }
-    protected function onEnterFrame(event:Event):void {
-      for (var key:String in touches) {
-        var touch:TouchEvent = touches[key] as TouchEvent;
-        if (touch) {
-          //plaster the finger image under your finger
-          var m:Matrix = new Matrix();
-          m.translate(touch.stageX*BMP_SCALE, touch.stageY*BMP_SCALE);
-          sourceBmp.draw(fingerShape, m, null, BlendMode.ADD);
-        }
-      }
-      var O:Point = new Point(0, 0);
-      //blur and ever-so-slightly brighten the image to make the color last
-      sourceBmp.applyFilter(sourceBmp, sourceBmp.rect, O, BLUR_EFFECT);
-      sourceBmp.colorTransform(sourceBmp.rect, DIM_EFFECT);
-      //we've calculated the image in grayscale brightnesses, now make it color
-      colorBmp.paletteMap(sourceBmp, sourceBmp.rect, O, RLUT, GLUT, BLUT, null);
-    }
-  }
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-ada.html b/docs/_style/prism-master/examples/prism-ada.html deleted file mode 100644 index 1783027e..00000000 --- a/docs/_style/prism-master/examples/prism-ada.html +++ /dev/null @@ -1,35 +0,0 @@ -

Strings

-
"foo ""bar"" baz"
-"Multi-line strings are appended with a " &
-"ampersand symbole."
- -

Ada83 example

-
WITH ADA.TEXT_IO;
-
---  Comments look like this.
-
-PROCEDURE TEST IS
-BEGIN
-   ADA.TEXT_IO.PUT_LINE ("Hello");   --  Comments look like this.
-END TEST;
- -

Ada 2012 full example

-
with Ada.Text_IO; Use Ada.Text_IO;
-
---  Comments look like this.
-procedure Test is
-   procedure Bah with
-    Import        => True,   --  Shows the new aspect feature of the language.
-    Convention    => C,
-    External_Name => "bah";
-
-   type Things is range 1 .. 10;
-begin
-   Put_Line ("Hello");   --  Comments look like this.
-
-   Bah;  -- Call C function.
-
-   for Index in Things'Range loop
-      null;
-   end loop;
-end Test;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-apacheconf.html b/docs/_style/prism-master/examples/prism-apacheconf.html deleted file mode 100644 index f6953e1b..00000000 --- a/docs/_style/prism-master/examples/prism-apacheconf.html +++ /dev/null @@ -1,54 +0,0 @@ -

Comments

-
# This is a comment
-# <VirtualHost *:80>
-
- -

Directives

-
<Files .htaccess>
-	Order allow,deny
-	Deny from all
-</Files>
-
- -

Variables

-
RewriteCond %{REQUEST_FILENAME}.php -f
- -

Regex

-
^(.*)$
-!^www\.
- -

Directive flags

-
[NC]
-[RC=301,L]
- -

Strings

-
AuthName "Fichiers réservés"
- -

Full example

-
## BASIC PASSWORD PROTECTION
-AuthType basic
-AuthName "prompt"
-AuthUserFile /.htpasswd
-AuthGroupFile /dev/null
-Require valid-user
-
-## ALLOW FROM IP OR VALID PASSWORD
-Require valid-user
-Allow from 192.168.1.23
-Satisfy Any
-
-## PROTECT FILES
-Order Allow,Deny
-Deny from all
-
-## REQUIRE SUBDOMAIN
-RewriteCond %{HTTP_HOST} !^$
-RewriteCond %{HTTP_HOST} !^subdomain\.domain\.tld$ [NC]
-RewriteRule ^/(.*)$ http://subdomain.domain.tld/$1 [L,R=301]
-
-ErrorDocument 403 http://www.example.com/logo.gif
-ErrorDocument 403 /images/you_bad_hotlinker.gif
-
-## REDIRECT UPLOADS
-RewriteCond %{REQUEST_METHOD} ^(PUT|POST)$ [NC]
-RewriteRule ^(.*)$ /cgi-bin/form-upload-processor.cgi?p=$1 [L,QSA]
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-apl.html b/docs/_style/prism-master/examples/prism-apl.html deleted file mode 100644 index 61a7e469..00000000 --- a/docs/_style/prism-master/examples/prism-apl.html +++ /dev/null @@ -1,26 +0,0 @@ -

Comments

-
#!/usr/bin/env runapl
-a←1 2 3 ⍝ this is a comment
- -

Strings

-
''
-'foobar'
-'foo''bar''baz'
- -

Numbers

-
42
-3.14159
-¯2
-∞
-2.8e¯4
-2j3
-¯4.3e2J1.9e¯4
- -

Primitive functions

-
a+b×c⍴⍳10
- -

Operators

-
+/ f⍣2
- -

Dfns

-
{0=⍴⍴⍺:'hello' ⋄ ∇¨⍵}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-applescript.html b/docs/_style/prism-master/examples/prism-applescript.html deleted file mode 100644 index c88390e8..00000000 --- a/docs/_style/prism-master/examples/prism-applescript.html +++ /dev/null @@ -1,41 +0,0 @@ -

Comments

-
-- Single line comment
-#!/usr/bin/osascript
-(* Here is
-a block
-comment *)
- -

Strings

-
"foo \"bar\" baz"
- -

Operators

-
a ≠ b
-12 + 2 * 5
-"DUMPtruck" is equal to "dumptruck"
-"zebra" comes after "aardvark"
-{ "this", "is", 2, "cool" } starts with "this"
-{ "is", 2} is contained by { "this", "is", 2, "cool" }
-set docRef to a reference to the first document
-
- -

Classes and units

-
tell application "Finder"
-text 1 thru 5 of "Bring me the mouse."
-set averageTemp to 63 as degrees Fahrenheit
-set circleArea to (pi * 7 * 7) as square yards
-
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Comments only support one level of nesting

-
(* Nested block
-	(* comments
-		(* on more than
-		2 levels *)
-	are *)
-not supported *)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-arduino.html b/docs/_style/prism-master/examples/prism-arduino.html deleted file mode 100644 index 915f700f..00000000 --- a/docs/_style/prism-master/examples/prism-arduino.html +++ /dev/null @@ -1,63 +0,0 @@ -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-"Multi-line strings ending with a \
-are supported too."
- -

Macro statements

-
#include <Bridge.h>
-#define SOME_PIN 11
-
- -

Booleans

-
true;
-false;
- -

Operators

-
a < b;
-c && d;
- -

Full example

-
#include <Bridge.h>
-
-// pin of the piezo speaker
-int piezo = 8;
-
-/**
- * setups
- * runs once before everyhing else
- */
-void setup() {
-    pinMode(piezo, OUTPUT);     
-}
-
-/**
- * loop
- * this will run forever and do what we want
- */
-void loop() {
-    playMelody(1);
-    delay(1000);
-}
-
-/**
- * playMelody
- * will play a simple melody on piezo speaker
- */
-void playMelody(int times) {
-    int melody[] = { 4699, 4699, 3520, 4699 };
-    int duration = 6;
-
-    for( int t = 0; t < times; t++ ) {
-        for( int i = 0; i < 4; i++ ) {
-            // pass tone to selected pin
-            tone(piezoPin, melody[i], 1000/duration);
-
-            // get a bit of time between the tones
-            delay(1000 / duration * 1.30 + 80);
-
-            // and don't forget to switch of the tone afterwards
-            noTone(piezoPin);
-        }
-    }
-}
diff --git a/docs/_style/prism-master/examples/prism-arff.html b/docs/_style/prism-master/examples/prism-arff.html deleted file mode 100644 index 41812099..00000000 --- a/docs/_style/prism-master/examples/prism-arff.html +++ /dev/null @@ -1,46 +0,0 @@ -

Comments

-
%
-% Some comments
-%
-%
- -

Keywords

-
@attribute
-@data
-@relation
- -

Numbers

-
42
-0.14
- -

Strings

-
'Single \'quoted\' string'
-"Double \"quoted\" string"
- -

Full example

-
% 1. Title: Iris Plants Database
-%
-% 2. Sources:
-%      (a) Creator: R.A. Fisher
-%      (b) Donor: Michael Marshall (MARSHALL%PLU@io.arc.nasa.gov)
-%      (c) Date: July, 1988
-%
-@RELATION iris
-
-@ATTRIBUTE sepallength  NUMERIC
-@ATTRIBUTE sepalwidth   NUMERIC
-@ATTRIBUTE petallength  NUMERIC
-@ATTRIBUTE petalwidth   NUMERIC
-@ATTRIBUTE class        {Iris-setosa,Iris-versicolor,Iris-virginica}
-
-@DATA
-5.1,3.5,1.4,0.2,Iris-setosa
-4.9,3.0,1.4,0.2,Iris-setosa
-4.7,3.2,1.3,0.2,Iris-setosa
-4.6,3.1,1.5,0.2,Iris-setosa
-5.0,3.6,1.4,0.2,Iris-setosa
-5.4,3.9,1.7,0.4,Iris-setosa
-4.6,3.4,1.4,0.3,Iris-setosa
-5.0,3.4,1.5,0.2,Iris-setosa
-4.4,2.9,1.4,0.2,Iris-setosa
-4.9,3.1,1.5,0.1,Iris-setosa
diff --git a/docs/_style/prism-master/examples/prism-asciidoc.html b/docs/_style/prism-master/examples/prism-asciidoc.html deleted file mode 100644 index d6df3021..00000000 --- a/docs/_style/prism-master/examples/prism-asciidoc.html +++ /dev/null @@ -1,104 +0,0 @@ -

Comments

-
/////
-Comment block
-/////
-
-// Comment line
- -

Titles

-
Level 0
-========
-Level 1
---------
-Level 2
-~~~~~~~~
-Level 3
-^^^^^^^^
-Level 4
-++++++++
-
-= Document Title (level 0) =
-== Section title (level 1) ==
-=== Section title (level 2) ===
-==== Section title (level 3) ====
-===== Section title (level 4) =====
-
-.Notes
- -

Blocks

-
++++++++++++++++++++++++++
-Passthrough block
-++++++++++++++++++++++++++
-
---------------------------
-Listing block
---------------------------
-
-..........................
-Literal block
-No *highlighting* _here_
-..........................
-
-**************************
-Sidebar block
-**************************
-
-[quote,'http://en.wikipedia.org/wiki/Samuel_Johnson[Samuel%20Johnson]']
-_____________________________________________________________________
-Sir, a woman's preaching is like a dog's walking on his hind legs. It
-is not done well; but you are surprised to find it done at all.
-_____________________________________________________________________
-
-==========================
-Example block
-==========================
- -

Lists

-
- List item.
-* List item.
-** List item.
-*** List item.
-**** List item.
-***** List item.
-
-1.   Arabic (decimal) numbered list item.
-a.   Lower case alpha (letter) numbered list item.
-F.   Upper case alpha (letter) numbered list item.
-iii) Lower case roman numbered list item.
-IX)  Upper case roman numbered list item.
-
-. Arabic (decimal) numbered list item.
-.. Lower case alpha (letter) numbered list item.
-... Lower case roman numbered list item.
-.... Upper case alpha (letter) numbered list item.
-..... Upper case roman numbered list item.
-
-Dolor::
-  Donec eget arcu bibendum nunc consequat lobortis.
-  Suspendisse;;
-    A massa id sem aliquam auctor.
-  Morbi;;
-    Pretium nulla vel lorem.
-  In;;
-    Dictum mauris in urna.
-    Vivamus::: Fringilla mi eu lacus.
-    Donec:::   Eget arcu bibendum nunc consequat lobortis.
- -

Tables

-
[cols="e,m,^,>s",width="25%"]
-|============================
-|1 >s|2 |3 |4
-^|5 2.2+^.^|6 .3+<.>m|7
-^|8
-|9 2+>|10
-|============================
- -

Inline styles

-
*Some bold text*
-This is an _emphasis_
-[[[walsh-muellner]]]
- -

Attribute entries

-
:Author Initials: JB
-{authorinitials}
-:Author Initials!:
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-asm6502.html b/docs/_style/prism-master/examples/prism-asm6502.html deleted file mode 100644 index 21f4b687..00000000 --- a/docs/_style/prism-master/examples/prism-asm6502.html +++ /dev/null @@ -1,39 +0,0 @@ -

Comments

-
; This is a comment
- -

Labels

-
label1:   ; a label
- -

Opcodes

-

-SEI
-CLC
-
-; lowercase
-inx
-bne label1
-
- -

Assembler directives

-

-.segment CODE
-.word $07d3
-
- -

Registers

-

-ASL A  ; "A"
-LDA label1,x  ; "x"
-
- -

Strings

-

-.include "header.asm"
-
- -

Numbers

-

-LDA #127
-STA $80f0
-LDY #%01011000
-
diff --git a/docs/_style/prism-master/examples/prism-aspnet.html b/docs/_style/prism-master/examples/prism-aspnet.html deleted file mode 100644 index d9c30fcf..00000000 --- a/docs/_style/prism-master/examples/prism-aspnet.html +++ /dev/null @@ -1,36 +0,0 @@ -

Comments

-
<%-- This is a comment --%>
-<%-- This is a
-multi-line comment --%>
- -

Page directives

-
<%@ Page Title="Products" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true"  CodeBehind="ProductList.aspx.cs" Inherits="WingtipToys.ProductList" %>
-
- -

Directive tag

-
<%: Page.Title %>
-<a href="http://github.com/ProductDetails.aspx?productID=%3C%#:Item.ProductID%%3E">
-<span>
-    <%#:Item.ProductName%>
-</span>
- -

Highlighted C# inside scripts

-

This requires the C# component to be loaded. - On this page, check C# before checking ASP.NET should make - the example below work properly.

-
<script runat="server">
-    // The following variables are visible to all procedures
-    // within the script block.
-    String str;
-    int i;
-    int i2;
-
-    int DoubleIt(int inpt)
-    {
-        // The following variable is visible only within
-        // the DoubleIt procedure.
-        int factor = 2;
-
-        return inpt * factor;
-    }
-</script>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-autohotkey.html b/docs/_style/prism-master/examples/prism-autohotkey.html deleted file mode 100644 index 620edb2e..00000000 --- a/docs/_style/prism-master/examples/prism-autohotkey.html +++ /dev/null @@ -1,68 +0,0 @@ -

Comments

-
; This is a comment
- -

Strings

-
"foo ""bar"" baz"
- -

Numbers

-
123
-123.456
-123.456e789
-0xAF
- -

Full example

-
;----Open the selected favorite
-f_OpenFavorite:
-; Fetch the array element that corresponds to the selected menu item:
-StringTrimLeft, f_path, f_path%A_ThisMenuItemPos%, 0
-if f_path =
-    return
-if f_class = #32770    ; It's a dialog.
-{
-    if f_Edit1Pos <>   ; And it has an Edit1 control.
-    {
-        ; Activate the window so that if the user is middle-clicking
-        ; outside the dialog, subsequent clicks will also work:
-        WinActivate ahk_id %f_window_id%
-        ; Retrieve any filename that might already be in the field so
-        ; that it can be restored after the switch to the new folder:
-        ControlGetText, f_text, Edit1, ahk_id %f_window_id%
-        ControlSetText, Edit1, %f_path%, ahk_id %f_window_id%
-        ControlSend, Edit1, {Enter}, ahk_id %f_window_id%
-        Sleep, 100  ; It needs extra time on some dialogs or in some cases.
-        ControlSetText, Edit1, %f_text%, ahk_id %f_window_id%
-        return
-    }
-    ; else fall through to the bottom of the subroutine to take standard action.
-}
-else if f_class in ExploreWClass,CabinetWClass  ; In Explorer, switch folders.
-{
-    if f_Edit1Pos <>   ; And it has an Edit1 control.
-    {
-        ControlSetText, Edit1, %f_path%, ahk_id %f_window_id%
-        ; Tekl reported the following: "If I want to change to Folder L:\folder
-        ; then the addressbar shows http://www.L:\folder.com. To solve this,
-        ; I added a {right} before {Enter}":
-        ControlSend, Edit1, {Right}{Enter}, ahk_id %f_window_id%
-        return
-    }
-    ; else fall through to the bottom of the subroutine to take standard action.
-}
-else if f_class = ConsoleWindowClass ; In a console window, CD to that directory
-{
-    WinActivate, ahk_id %f_window_id% ; Because sometimes the mclick deactivates it.
-    SetKeyDelay, 0  ; This will be in effect only for the duration of this thread.
-    IfInString, f_path, :  ; It contains a drive letter
-    {
-        StringLeft, f_path_drive, f_path, 1
-        Send %f_path_drive%:{enter}
-    }
-    Send, cd %f_path%{Enter}
-    return
-}
-; Since the above didn't return, one of the following is true:
-; 1) It's an unsupported window type but f_AlwaysShowMenu is y (yes).
-; 2) It's a supported type but it lacks an Edit1 control to facilitate the custom
-;    action, so instead do the default action below.
-Run, Explorer %f_path%  ; Might work on more systems without double quotes.
-return
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-autoit.html b/docs/_style/prism-master/examples/prism-autoit.html deleted file mode 100644 index 51220b42..00000000 --- a/docs/_style/prism-master/examples/prism-autoit.html +++ /dev/null @@ -1,52 +0,0 @@ -

Comments

-
; Single-line comment
-#comments-start
-	Multi-line
-	comment
-#comments-end
-#cs
-	Multi-line
-	comment
-#ce
-;#comments-start
-	foo()
-;#comments-end
- -

Strings

-
"foo'bar'baz"
-"foo""bar""baz"
-'foo"bar"baz'
-'foo''bar''baz'
- -

Numbers

-
2
-4.566
-1.5e3
-0x4fff
- -

Booleans

-
True
-False
- -

Keywords and variables

-
; Display all the numbers for 1 to 10 but skip displaying  7.
-For $i = 1 To 10
-    If $i = 7 Then
-        ContinueLoop ; Skip displaying the message box when $i is equal to 7.
-    EndIf
-    MsgBox($MB_SYSTEMMODAL, "", "The value of $i is: " & $i)
-Next
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested block comments

-
#cs
-	#cs
-		foo()
-	#ce
-#ce
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-bash.html b/docs/_style/prism-master/examples/prism-bash.html deleted file mode 100644 index 3e0febfa..00000000 --- a/docs/_style/prism-master/examples/prism-bash.html +++ /dev/null @@ -1,49 +0,0 @@ -

Shebang

-
#!/bin/bash
- -

Comments

-
# This is a comment
- -

Strings

-
STRING="Hello World"
-'Single and
-multi-line strings are supported.'
-"Single and
-multi-line strings are supported."
-cat << EOF
-Here-Documents
-are also supported
-EOF
- -

Variables

-
echo $STRING
-args=("$@")
-echo ${args[0]} ${args[1]} ${args[2]}
- -

Keywords

-
for (( i=0;i<$ELEMENTS;i++)); do
-	echo ${ARRAY[${i}]}
-done
-while read LINE; do
-    ARRAY[$count]=$LINE
-    ((count++))
-done
-if [ -d $directory ]; then
-	echo "Directory exists"
-else
-	echo "Directory does not exists"
-fi
-
- -

Some well-known commands

-
crontab -l -u USER | grep -v 'YOUR JOB COMMAND or PATTERN' | crontab -u USER -
-
-groups user1 user2|cut -d: -f2|xargs -n1|sort|uniq -d
-
-wget -q -O - http://www.example.com/automation/remotescript.sh | bash /dev/stdin parameter1 parameter2
-
-sudo dpkg -i vagrant_1.7.2_x86_64.deb
-
-git pull origin master
-
-sudo gpg --refresh-keys; sudo apt-key update; sudo rm -rf /var/lib/apt/{lists,lists.old}; sudo mkdir -p /var/lib/apt/lists/partial; sudo apt-get clean all; sudo apt-get update
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-basic.html b/docs/_style/prism-master/examples/prism-basic.html deleted file mode 100644 index 3630a8a2..00000000 --- a/docs/_style/prism-master/examples/prism-basic.html +++ /dev/null @@ -1,69 +0,0 @@ -

Note: this component focuses on first and second-generation BASICs (such as MSX BASIC, GW-BASIC, SuperBASIC, QuickBASIC, PowerBASIC...).

- -

Comments

-
! This is a comment
-REM This is a remark
- -

Strings

-
"This a string."
-"This is a string with ""quotes"" in it."
- -

Numbers

-
42
-3.14159
--42
--3.14159
-.5
-10.
-2E10
-4.2E-14
--3E+2
- -

Dartmouth Basic example

-
5 LET S = 0
-10 MAT INPUT V
-20 LET N = NUM
-30 IF N = 0 THEN 99
-40 FOR I = 1 TO N
-45 LET S = S + V(I)
-50 NEXT I
-60 PRINT S/N
-70 GO TO 5
-99 END
- -

GW-BASIC example

-
10 INPUT "What is your name: ", U$
-20 PRINT "Hello "; U$
-30 INPUT "How many stars do you want: ", N
-40 S$ = ""
-50 FOR I = 1 TO N
-60 S$ = S$ + "*"
-70 NEXT I
-80 PRINT S$
-90 INPUT "Do you want more stars? ", A$
-100 IF LEN(A$) = 0 THEN GOTO 90
-110 A$ = LEFT$(A$, 1)
-120 IF A$ = "Y" OR A$ = "y" THEN GOTO 30
-130 PRINT "Goodbye "; U$
-140 END
- -

QuickBASIC example

-
DECLARE SUB PrintSomeStars (StarCount!)
-REM QuickBASIC example
-INPUT "What is your name: ", UserName$
-PRINT "Hello "; UserName$
-DO
-   INPUT "How many stars do you want: ", NumStars
-   CALL PrintSomeStars(NumStars)
-   DO
-      INPUT "Do you want more stars? ", Answer$
-   LOOP UNTIL Answer$ <> ""
-   Answer$ = LEFT$(Answer$, 1)
-LOOP WHILE UCASE$(Answer$) = "Y"
-PRINT "Goodbye "; UserName$
-
-SUB PrintSomeStars (StarCount)
-   REM This procedure uses a local variable called Stars$
-   Stars$ = STRING$(StarCount, "*")
-   PRINT Stars$
-END SUB
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-batch.html b/docs/_style/prism-master/examples/prism-batch.html deleted file mode 100644 index 13f9018c..00000000 --- a/docs/_style/prism-master/examples/prism-batch.html +++ /dev/null @@ -1,17 +0,0 @@ -

Comments

-
::
-:: Foo bar
-REM This is a comment too
-REM Multi-line ^
-comment
- -

Labels

-
:foobar
-GOTO :EOF
- -

Commands

-
@ECHO OFF
-FOR /l %%a in (5,-1,1) do (TITLE %title% -- closing in %%as)
-SET title=%~n0
-if /i "%InstSize:~0,1%"=="M" set maxcnt=3
-ping -n 2 -w 1 127.0.0.1
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-bison.html b/docs/_style/prism-master/examples/prism-bison.html deleted file mode 100644 index 9c7edeb8..00000000 --- a/docs/_style/prism-master/examples/prism-bison.html +++ /dev/null @@ -1,104 +0,0 @@ -

Comments

-
// Single-line comment
-/* Multi-line
-comment */
- -

C prologue and Bison declarations

-
%{
-  #include <stdio.h>
-  #include <math.h>
-  int yylex (void);
-  void yyerror (char const *);
-%}
-
-%define api.value.type {double}
-%token NUM
-%union { char *string; }
-%%
-%%
- -

Grammar rules

-
%%
-exp:
-  NUM           { $$ = $1;           }
-| exp exp '+'   { $$ = $1 + $2;      }
-| exp exp '-'   { $$ = $1 - $2;      }
-| exp exp '*'   { $$ = $1 * $2;      }
-| exp exp '/'   { $$ = $1 / $2;      }
-| exp exp '^'   { $$ = pow($1, $2);  }  /* Exponentiation */
-| exp 'n'       { $$ = -$1;          }  /* Unary minus    */
-;
-
-$@1: %empty { a(); };
-$@2: %empty { c(); };
-$@3: %empty { d(); };
-exp: $@1 "b" $@2 $@3 "e" { f(); };
-%%
- -

Full example

-
/* Mini Calculator */
-/* calc.y */
-
-%{
-#include "heading.h"
-int yyerror(char *s);
-int yylex(void);
-%}
-
-%union{
-  int		int_val;
-  string*	op_val;
-}
-
-%start	input 
-
-%token	<int_val>	INTEGER_LITERAL
-%type	<int_val>	exp
-%left	PLUS
-%left	MULT
-
-%%
-
-input:		/* empty */
-		| exp	{ cout << "Result: " << $1 << endl; }
-		;
-
-exp:		INTEGER_LITERAL	{ $$ = $1; }
-		| exp PLUS exp	{ $$ = $1 + $3; }
-		| exp MULT exp	{ $$ = $1 * $3; }
-		;
-
-%%
-
-int yyerror(string s)
-{
-  extern int yylineno;	// defined and maintained in lex.c
-  extern char *yytext;	// defined and maintained in lex.c
-  
-  cerr << "ERROR: " << s << " at symbol \"" << yytext;
-  cerr << "\" on line " << yylineno << endl;
-  exit(1);
-}
-
-int yyerror(char *s)
-{
-  return yyerror(string(s));
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Two levels of nesting inside C section

-
{
-	if($1) {
-		if($2) {
-
-		}
-	}
-} // <- Broken
-%%
-%%
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-brainfuck.html b/docs/_style/prism-master/examples/prism-brainfuck.html deleted file mode 100644 index 89a435c9..00000000 --- a/docs/_style/prism-master/examples/prism-brainfuck.html +++ /dev/null @@ -1,37 +0,0 @@ -

Full example

-
+++++ +++               Set Cell #0 to 8
-[
-    >++++               Add 4 to Cell #1; this will always set Cell #1 to 4
-    [                   as the cell will be cleared by the loop
-        >++             Add 2 to Cell #2
-        >+++            Add 3 to Cell #3
-        >+++            Add 3 to Cell #4
-        >+              Add 1 to Cell #5
-        <<<<-           Decrement the loop counter in Cell #1
-    ]                   Loop till Cell #1 is zero; number of iterations is 4
-    >+                  Add 1 to Cell #2
-    >+                  Add 1 to Cell #3
-    >-                  Subtract 1 from Cell #4
-    >>+                 Add 1 to Cell #6
-    [<]                 Move back to the first zero cell you find; this will
-                        be Cell #1 which was cleared by the previous loop
-    <-                  Decrement the loop Counter in Cell #0
-]                       Loop till Cell #0 is zero; number of iterations is 8
-
-The result of this is:
-Cell No :   0   1   2   3   4   5   6
-Contents:   0   0  72 104  88  32   8
-Pointer :   ^
-
->>.                     Cell #2 has value 72 which is 'H'
->---.                   Subtract 3 from Cell #3 to get 101 which is 'e'
-+++++++..+++.           Likewise for 'llo' from Cell #3
->>.                     Cell #5 is 32 for the space
-<-.                     Subtract 1 from Cell #4 for 87 to give a 'W'
-<.                      Cell #3 was set to 'o' from the end of 'Hello'
-+++.------.--------.    Cell #3 for 'rl' and 'd'
->>+.                    Add 1 to Cell #5 gives us an exclamation point
->++.                    And finally a newline from Cell #6
- -

One-line example

-
++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.
diff --git a/docs/_style/prism-master/examples/prism-bro.html b/docs/_style/prism-master/examples/prism-bro.html deleted file mode 100644 index 83d6374e..00000000 --- a/docs/_style/prism-master/examples/prism-bro.html +++ /dev/null @@ -1,645 +0,0 @@ -

Comments

-
# Single line comment
-
- -

Strings

-

-"a", "b"
-
- -

Numbers

-
123
-123.456
--123.456
-
- -

Misc

-

-@ifndef ourexp
-@load-sigs somesigs
-
- -

Full example

-

-##! Scan detector ported from Bro 1.x.
-##!
-##! This script has evolved over many years and is quite a mess right now. We
-##! have adapted it to work with Bro 2.x, but eventually Bro 2.x will
-##! get its own rewritten and generalized scan detector.
-
-@load base/frameworks/notice/main
-
-module Scan;
-
-export {
-	redef enum Notice::Type += {
-		## The source has scanned a number of ports.
-		PortScan,
-		## The source has scanned a number of addresses.
-		AddressScan,
-		## Apparent flooding backscatter seen from source.
-		BackscatterSeen,
-
-		## Summary of scanning activity.
-		ScanSummary,
-		## Summary of distinct ports per scanner.
-		PortScanSummary,
-		## Summary of distinct low ports per scanner.
-		LowPortScanSummary,
-
-		## Source reached :bro:id:`Scan::shut_down_thresh`
-		ShutdownThresh,
-		## Source touched privileged ports.
-		LowPortTrolling,
-	};
-
-	# Whether to consider UDP "connections" for scan detection.
-	# Can lead to false positives due to UDP fanout from some P2P apps.
-	const suppress_UDP_scan_checks = F &redef;
-
-	const activate_priv_port_check = T &redef;
-	const activate_landmine_check = F &redef;
-	const landmine_thresh_trigger = 5 &redef;
-
-	const landmine_address: set[addr] &redef;
-
-	const scan_summary_trigger = 25 &redef;
-	const port_summary_trigger = 20 &redef;
-	const lowport_summary_trigger = 10 &redef;
-
-	# Raise ShutdownThresh after this many failed attempts
-	const shut_down_thresh = 100 &redef;
-
-	# Which services should be analyzed when detecting scanning
-	# (not consulted if analyze_all_services is set).
-	const analyze_services: set[port] &redef;
-	const analyze_all_services = T &redef;
-
-	# Track address scaners only if at least these many hosts contacted.
-	const addr_scan_trigger = 0 &redef;
-
-	# Ignore address scanners for further scan detection after
-	# scanning this many hosts.
-	# 0 disables.
-	const ignore_scanners_threshold = 0 &redef;
-
-	# Report a scan of peers at each of these points.
-	const report_peer_scan: vector of count = {
-		20, 100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000,
-	} &redef;
-
-	const report_outbound_peer_scan: vector of count = {
-		100, 1000, 10000,
-	} &redef;
-
-	# Report a scan of ports at each of these points.
-	const report_port_scan: vector of count = {
-		50, 250, 1000, 5000, 10000, 25000, 65000,
-	} &redef;
-
-	# Once a source has scanned this many different ports (to however many
-	# different remote hosts), start tracking its per-destination access.
-	const possible_port_scan_thresh = 20 &redef;
-
-	# Threshold for scanning privileged ports.
-	const priv_scan_trigger = 5 &redef;
-	const troll_skip_service = {
-		25/tcp, 21/tcp, 22/tcp, 20/tcp, 80/tcp,
-	} &redef;
-
-	const report_accounts_tried: vector of count = {
-		20, 100, 1000, 10000, 100000, 1000000,
-	} &redef;
-
-	const report_remote_accounts_tried: vector of count = {
-		100, 500,
-	} &redef;
-
-	# Report a successful password guessing if the source attempted
-	# at least this many.
-	const password_guessing_success_threshhold = 20 &redef;
-
-	const skip_accounts_tried: set[addr] &redef;
-
-	const addl_web = {
-		81/tcp, 443/tcp, 8000/tcp, 8001/tcp, 8080/tcp, }
-	&redef;
-
-	const skip_services = { 113/tcp, } &redef;
-	const skip_outbound_services = { 21/tcp, addl_web, }
-		&redef;
-
-	const skip_scan_sources = {
-		255.255.255.255,	# who knows why we see these, but we do
-	} &redef;
-
-	const skip_scan_nets: set[subnet] = {} &redef;
-
-	# List of well known local server/ports to exclude for scanning
-	# purposes.
-	const skip_dest_server_ports: set[addr, port] = {} &redef;
-
-	# Reverse (SYN-ack) scans seen from these ports are considered
-	# to reflect possible SYN-flooding backscatter, and not true
-	# (stealth) scans.
-	const backscatter_ports = {
-		80/tcp, 8080/tcp, 53/tcp, 53/udp, 179/tcp, 6666/tcp, 6667/tcp,
-	} &redef;
-
-	const report_backscatter: vector of count = {
-		20,
-	} &redef;
-
-	global check_scan:
-		function(c: connection, established: bool, reverse: bool): bool;
-
-	# The following tables are defined here so that we can redef
-	# the expire timeouts.
-	# FIXME: should we allow redef of attributes on IDs which
-	# are not exported?
-
-	# How many different hosts connected to with a possible
-	# backscatter signature.
-	global distinct_backscatter_peers: table[addr] of table[addr] of count
-		&read_expire = 15 min;
-
-	# Expire functions that trigger summaries.
-	global scan_summary:
-		function(t: table[addr] of set[addr], orig: addr): interval;
-	global port_summary:
-		function(t: table[addr] of set[port], orig: addr): interval;
-	global lowport_summary:
-		function(t: table[addr] of set[port], orig: addr): interval;
-
-	# Indexed by scanner address, yields # distinct peers scanned.
-	# pre_distinct_peers tracks until addr_scan_trigger hosts first.
-	global pre_distinct_peers: table[addr] of set[addr]
-		&read_expire = 15 mins &redef;
-
-	global distinct_peers: table[addr] of set[addr]
-		&read_expire = 15 mins &expire_func=scan_summary &redef;
-	global distinct_ports: table[addr] of set[port]
-		&read_expire = 15 mins &expire_func=port_summary &redef;
-	global distinct_low_ports: table[addr] of set[port]
-		&read_expire = 15 mins &expire_func=lowport_summary &redef;
-
-	# Indexed by scanner address, yields a table with scanned hosts
-	# (and ports).
-	global scan_triples: table[addr] of table[addr] of set[port];
-
-	global remove_possible_source:
-		function(s: set[addr], idx: addr): interval;
-	global possible_scan_sources: set[addr]
-		&expire_func=remove_possible_source &read_expire = 15 mins;
-
-	# Indexed by source address, yields user name & password tried.
-	global accounts_tried: table[addr] of set[string, string]
-		&read_expire = 1 days;
-
-	global ignored_scanners: set[addr] &create_expire = 1 day &redef;
-
-	# These tables track whether a threshold has been reached.
-	# More precisely, the counter is the next index of threshold vector.
-	global shut_down_thresh_reached: table[addr] of bool &default=F;
-	global rb_idx: table[addr] of count
-			&default=1 &read_expire = 1 days &redef;
-	global rps_idx: table[addr] of count
-			&default=1 &read_expire = 1 days &redef;
-	global rops_idx: table[addr] of count
-			&default=1 &read_expire = 1 days &redef;
-	global rpts_idx: table[addr,addr] of count
-			&default=1 &read_expire = 1 days &redef;
-	global rat_idx: table[addr] of count
-			&default=1 &read_expire = 1 days &redef;
-	global rrat_idx: table[addr] of count
-			&default=1 &read_expire = 1 days &redef;
-}
-
-global thresh_check: function(v: vector of count, idx: table[addr] of count,
-				orig: addr, n: count): bool;
-global thresh_check_2: function(v: vector of count,
-				idx: table[addr,addr] of count, orig: addr,
-				resp: addr, n: count): bool;
-
-function scan_summary(t: table[addr] of set[addr], orig: addr): interval
-	{
-	local num_distinct_peers = orig in t ? |t[orig]| : 0;
-
-	if ( num_distinct_peers >= scan_summary_trigger )
-		NOTICE([$note=ScanSummary, $src=orig, $n=num_distinct_peers,
-			$identifier=fmt("%s", orig),
-			$msg=fmt("%s scanned a total of %d hosts",
-					orig, num_distinct_peers)]);
-
-	return 0 secs;
-	}
-
-function port_summary(t: table[addr] of set[port], orig: addr): interval
-	{
-	local num_distinct_ports = orig in t ? |t[orig]| : 0;
-
-	if ( num_distinct_ports >= port_summary_trigger )
-		NOTICE([$note=PortScanSummary, $src=orig, $n=num_distinct_ports,
-			$identifier=fmt("%s", orig),
-			$msg=fmt("%s scanned a total of %d ports",
-					orig, num_distinct_ports)]);
-
-	return 0 secs;
-	}
-
-function lowport_summary(t: table[addr] of set[port], orig: addr): interval
-	{
-	local num_distinct_lowports = orig in t ? |t[orig]| : 0;
-
-	if ( num_distinct_lowports >= lowport_summary_trigger )
-		NOTICE([$note=LowPortScanSummary, $src=orig,
-			$n=num_distinct_lowports,
-			$identifier=fmt("%s", orig),
-			$msg=fmt("%s scanned a total of %d low ports",
-					orig, num_distinct_lowports)]);
-
-	return 0 secs;
-	}
-
-function clear_addr(a: addr)
-	{
-	delete distinct_peers[a];
-	delete distinct_ports[a];
-	delete distinct_low_ports[a];
-	delete scan_triples[a];
-	delete possible_scan_sources[a];
-	delete distinct_backscatter_peers[a];
-	delete pre_distinct_peers[a];
-	delete rb_idx[a];
-	delete rps_idx[a];
-	delete rops_idx[a];
-	delete rat_idx[a];
-	delete rrat_idx[a];
-	delete shut_down_thresh_reached[a];
-	delete ignored_scanners[a];
-	}
-
-function ignore_addr(a: addr)
-	{
-	clear_addr(a);
-	add ignored_scanners[a];
-	}
-
-function check_scan(c: connection, established: bool, reverse: bool): bool
-	{
-	local id = c$id;
-
-	local service = "ftp-data" in c$service ? 20/tcp
-			: (reverse ? id$orig_p : id$resp_p);
-	local rev_service = reverse ? id$resp_p : id$orig_p;
-	local orig = reverse ? id$resp_h : id$orig_h;
-	local resp = reverse ? id$orig_h : id$resp_h;
-	local outbound = Site::is_local_addr(orig);
-
-	# The following works better than using get_conn_transport_proto()
-	# because c might not correspond to an active connection (which
-	# causes the function to fail).
-	if ( suppress_UDP_scan_checks &&
-	     service >= 0/udp && service <= 65535/udp )
-		return F;
-
-	if ( service in skip_services && ! outbound )
-		return F;
-
-	if ( outbound && service in skip_outbound_services )
-		return F;
-
-	if ( orig in skip_scan_sources )
-		return F;
-
-	if ( orig in skip_scan_nets )
-		return F;
-
-	# Don't include well known server/ports for scanning purposes.
-	if ( ! outbound && [resp, service] in skip_dest_server_ports )
-		return F;
-
-	if ( orig in ignored_scanners)
-		return F;
-
-	if ( ! established &&
-		# not established, service not expressly allowed
-
-		# not known peer set
-		(orig !in distinct_peers || resp !in distinct_peers[orig]) &&
-
-		# want to consider service for scan detection
-		(analyze_all_services || service in analyze_services) )
-		{
-		if ( reverse && rev_service in backscatter_ports &&
-		     # reverse, non-priv backscatter port
-		     service >= 1024/tcp )
-			{
-			if ( orig !in distinct_backscatter_peers )
-				{
-				local empty_bs_table:
-					table[addr] of count &default=0;
-				distinct_backscatter_peers[orig] =
-					empty_bs_table;
-				}
-
-			if ( ++distinct_backscatter_peers[orig][resp] <= 2 &&
-			     # The test is <= 2 because we get two check_scan()
-			     # calls, once on connection attempt and once on
-			     # tear-down.
-
-			     distinct_backscatter_peers[orig][resp] == 1 &&
-
-			     # Looks like backscatter, and it's not scanning
-			     # a privileged port.
-
-			     thresh_check(report_backscatter, rb_idx, orig,
-					|distinct_backscatter_peers[orig]|)
-			   )
-				{
-				NOTICE([$note=BackscatterSeen, $src=orig,
-					$p=rev_service,
-					$identifier=fmt("%s", orig),
-					$msg=fmt("backscatter seen from %s (%d hosts; %s)",
-						orig, |distinct_backscatter_peers[orig]|, rev_service)]);
-				}
-
-			if ( ignore_scanners_threshold > 0 &&
-			     |distinct_backscatter_peers[orig]| >
-					ignore_scanners_threshold )
-				ignore_addr(orig);
-			}
-
-		else
-			{ # done with backscatter check
-			local ignore = F;
-
-			if ( orig !in distinct_peers && addr_scan_trigger > 0 )
-				{
-				if ( orig !in pre_distinct_peers )
-					pre_distinct_peers[orig] = set();
-
-				add pre_distinct_peers[orig][resp];
-				if ( |pre_distinct_peers[orig]| < addr_scan_trigger )
-					ignore = T;
-				}
-
-			if ( ! ignore )
-				{ # XXXXX
-
-				if ( orig !in distinct_peers )
-					distinct_peers[orig] = set() &mergeable;
-
-				if ( resp !in distinct_peers[orig] )
-					add distinct_peers[orig][resp];
-
-				local n = |distinct_peers[orig]|;
-
-				# Check for threshold if not outbound.
-				if ( ! shut_down_thresh_reached[orig] &&
-				     n >= shut_down_thresh &&
-				     ! outbound && orig !in Site::neighbor_nets )
-					{
-					shut_down_thresh_reached[orig] = T;
-					local msg = fmt("shutdown threshold reached for %s", orig);
-					NOTICE([$note=ShutdownThresh, $src=orig,
-						$identifier=fmt("%s", orig),
-						$p=service, $msg=msg]);
-					}
-
-				else
-					{
-					local address_scan = F;
-					if ( outbound &&
-					     # inside host scanning out?
-					     thresh_check(report_outbound_peer_scan, rops_idx, orig, n) )
-						address_scan = T;
-
-					if ( ! outbound &&
-					     thresh_check(report_peer_scan, rps_idx, orig, n) )
-						address_scan = T;
-
-					if ( address_scan )
-						NOTICE([$note=AddressScan,
-							$src=orig, $p=service,
-							$n=n,
-							$identifier=fmt("%s-%d", orig, n),
-							$msg=fmt("%s has scanned %d hosts (%s)",
-								orig, n, service)]);
-
-					if ( address_scan &&
-					     ignore_scanners_threshold > 0 &&
-					     n > ignore_scanners_threshold )
-						ignore_addr(orig);
-					}
-				}
-			} # XXXX
-		}
-
-	if ( established )
-		# Don't consider established connections for port scanning,
-		# it's too easy to be mislead by FTP-like applications that
-		# legitimately gobble their way through the port space.
-		return F;
-
-	# Coarse search for port-scanning candidates: those that have made
-	# connections (attempts) to possible_port_scan_thresh or more
-	# distinct ports.
-	if ( orig !in distinct_ports || service !in distinct_ports[orig] )
-		{
-		if ( orig !in distinct_ports )
-			distinct_ports[orig] = set() &mergeable;
-
-		if ( service !in distinct_ports[orig] )
-			add distinct_ports[orig][service];
-
-		if ( |distinct_ports[orig]| >= possible_port_scan_thresh &&
-			orig !in scan_triples )
-			{
-			scan_triples[orig] = table() &mergeable;
-			add possible_scan_sources[orig];
-			}
-		}
-
-	# Check for low ports.
-	if ( activate_priv_port_check && ! outbound && service < 1024/tcp &&
-	     service !in troll_skip_service )
-		{
-		if ( orig !in distinct_low_ports ||
-		     service !in distinct_low_ports[orig] )
-			{
-			if ( orig !in distinct_low_ports )
-				distinct_low_ports[orig] = set() &mergeable;
-
-			add distinct_low_ports[orig][service];
-
-			if ( |distinct_low_ports[orig]| == priv_scan_trigger &&
-			     orig !in Site::neighbor_nets )
-				{
-				local svrc_msg = fmt("low port trolling %s %s", orig, service);
-				NOTICE([$note=LowPortTrolling, $src=orig,
-					$identifier=fmt("%s", orig),
-					$p=service, $msg=svrc_msg]);
-				}
-
-			if ( ignore_scanners_threshold > 0 &&
-			     |distinct_low_ports[orig]| >
-					ignore_scanners_threshold )
-				ignore_addr(orig);
-			}
-		}
-
-	# For sources that have been identified as possible scan sources,
-	# keep track of per-host scanning.
-	if ( orig in possible_scan_sources )
-		{
-		if ( orig !in scan_triples )
-			scan_triples[orig] = table() &mergeable;
-
-		if ( resp !in scan_triples[orig] )
-			scan_triples[orig][resp] = set() &mergeable;
-
-		if ( service !in scan_triples[orig][resp] )
-			{
-			add scan_triples[orig][resp][service];
-
-			if ( thresh_check_2(report_port_scan, rpts_idx,
-					    orig, resp,
-					    |scan_triples[orig][resp]|) )
-				{
-				local m = |scan_triples[orig][resp]|;
-				NOTICE([$note=PortScan, $n=m, $src=orig,
-					$p=service,
-					$identifier=fmt("%s-%d", orig, n),
-					$msg=fmt("%s has scanned %d ports of %s",
-					orig, m, resp)]);
-				}
-			}
-		}
-
-	return T;
-	}
-
-
-# Hook into the catch&release dropping. When an address gets restored, we reset
-# the source to allow dropping it again.
-event Drop::address_restored(a: addr)
-	{
-	clear_addr(a);
-	}
-
-event Drop::address_cleared(a: addr)
-	{
-	clear_addr(a);
-	}
-
-# When removing a possible scan source, we automatically delete its scanned
-# hosts and ports.  But we do not want the deletion propagated, because every
-# peer calls the expire_function on its own (and thus applies the delete
-# operation on its own table).
-function remove_possible_source(s: set[addr], idx: addr): interval
-	{
-	suspend_state_updates();
-	delete scan_triples[idx];
-	resume_state_updates();
-
-	return 0 secs;
-	}
-
-# To recognize whether a certain threshhold vector (e.g. report_peer_scans)
-# has been transgressed, a global variable containing the next vector index
-# (idx) must be incremented.  This cumbersome mechanism is necessary because
-# values naturally don't increment by one (e.g. replayed table merges).
-function thresh_check(v: vector of count, idx: table[addr] of count,
-			orig: addr, n: count): bool
-	{
-	if ( ignore_scanners_threshold > 0 && n > ignore_scanners_threshold )
-		{
-		ignore_addr(orig);
-		return F;
-		}
-
-	if ( idx[orig] <= |v| && n >= v[idx[orig]] )
-		{
-		++idx[orig];
-		return T;
-		}
-	else
-		return F;
-	}
-
-# Same as above, except the index has a different type signature.
-function thresh_check_2(v: vector of count, idx: table[addr, addr] of count,
-			orig: addr, resp: addr, n: count): bool
-	{
-	if ( ignore_scanners_threshold > 0 && n > ignore_scanners_threshold )
-		{
-		ignore_addr(orig);
-		return F;
-		}
-
-	if ( idx[orig,resp] <= |v| && n >= v[idx[orig, resp]] )
-		{
-		++idx[orig,resp];
-		return T;
-		}
-	else
-		return F;
-	}
-
-event connection_established(c: connection)
-	{
-	local is_reverse_scan = (c$orig$state == TCP_INACTIVE);
-	Scan::check_scan(c, T, is_reverse_scan);
-	}
-
-event partial_connection(c: connection)
-	{
-	Scan::check_scan(c, T, F);
-	}
-
-event connection_attempt(c: connection)
-	{
-	Scan::check_scan(c, F, c$orig$state == TCP_INACTIVE);
-	}
-
-event connection_half_finished(c: connection)
-	{
-	# Half connections never were "established", so do scan-checking here.
-	Scan::check_scan(c, F, F);
-	}
-
-event connection_rejected(c: connection)
-	{
-	local is_reverse_scan = c$orig$state == TCP_RESET;
-
-	Scan::check_scan(c, F, is_reverse_scan);
-	}
-
-event connection_reset(c: connection)
-	{
-	if ( c$orig$state == TCP_INACTIVE || c$resp$state == TCP_INACTIVE )
-		# We never heard from one side - that looks like a scan.
-		Scan::check_scan(c, c$orig$size + c$resp$size > 0,
-				c$orig$state == TCP_INACTIVE);
-	}
-
-event connection_pending(c: connection)
-	{
-	if ( c$orig$state == TCP_PARTIAL && c$resp$state == TCP_INACTIVE )
-		Scan::check_scan(c, F, F);
-	}
-
-# Report the remaining entries in the tables.
-event bro_done()
-	{
-	for ( orig in distinct_peers )
-		scan_summary(distinct_peers, orig);
-
-	for ( orig in distinct_ports )
-		port_summary(distinct_ports, orig);
-
-	for ( orig in distinct_low_ports )
-		lowport_summary(distinct_low_ports, orig);
-	}
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-c.html b/docs/_style/prism-master/examples/prism-c.html deleted file mode 100644 index aee1c06a..00000000 --- a/docs/_style/prism-master/examples/prism-c.html +++ /dev/null @@ -1,22 +0,0 @@ -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-"Multi-line strings ending with a \
-are supported too."
- -

Macro statements

-
# include <stdio.h>
-#define PG_locked   0
-#define PG_error    1
-
- -

Full example

-
#include <stdio.h>
-main(int argc, char *argv[])
-{
-   int c;
-   printf("Number of command line arguments passed: %d\n", argc);
-   for ( c = 0 ; c < argc ; c++)
-      printf("%d. Command line argument passed is %s\n", c+1, argv[c]);
-   return 0;
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-clike.html b/docs/_style/prism-master/examples/prism-clike.html deleted file mode 100644 index 79a80764..00000000 --- a/docs/_style/prism-master/examples/prism-clike.html +++ /dev/null @@ -1,28 +0,0 @@ -

The C-like component is not really a language on its own, - it is the basis of many other components. To use it directly, however, - use the class "language-clike".

- -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz";
-'foo \'bar\' baz';
- -

Numbers

-
123
-123.456
--123.456
-1e-23
-123.456E789
-0xaf
-0xAF
-
- -

Functions

-
foo();
-Bar();
-_456();
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-clojure.html b/docs/_style/prism-master/examples/prism-clojure.html deleted file mode 100644 index abc50194..00000000 --- a/docs/_style/prism-master/examples/prism-clojure.html +++ /dev/null @@ -1,386 +0,0 @@ -

Full example

-

-; This code is copied from https://learnxinyminutes.com/docs/clojure/
-
-; Comments start with semicolons.
-
-; Clojure is written in "forms", which are just
-; lists of things inside parentheses, separated by whitespace.
-;
-; The clojure reader assumes that the first thing is a
-; function or macro to call, and the rest are arguments.
-
-; The first call in a file should be ns, to set the namespace
-(ns learnclojure)
-
-; More basic examples:
-
-; str will create a string out of all its arguments
-(str "Hello" " " "World") ; => "Hello World"
-
-; Math is straightforward
-(+ 1 1) ; => 2
-(- 2 1) ; => 1
-(* 1 2) ; => 2
-(/ 2 1) ; => 2
-
-; Equality is =
-(= 1 1) ; => true
-(= 2 1) ; => false
-
-; You need not for logic, too
-(not true) ; => false
-
-; Nesting forms works as you expect
-(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
-
-; Types
-;;;;;;;;;;;;;
-
-; Clojure uses Java's object types for booleans, strings and numbers.
-; Use `class` to inspect them.
-(class 1) ; Integer literals are java.lang.Long by default
-(class 1.); Float literals are java.lang.Double
-(class ""); Strings always double-quoted, and are java.lang.String
-(class false) ; Booleans are java.lang.Boolean
-(class nil); The "null" value is called nil
-
-; If you want to create a literal list of data, use ' to stop it from
-; being evaluated
-'(+ 1 2) ; => (+ 1 2)
-; (shorthand for (quote (+ 1 2)))
-
-; You can eval a quoted list
-(eval '(+ 1 2)) ; => 3
-
-; Collections & Sequences
-;;;;;;;;;;;;;;;;;;;
-
-; Lists are linked-list data structures, while Vectors are array-backed.
-; Vectors and Lists are java classes too!
-(class [1 2 3]); => clojure.lang.PersistentVector
-(class '(1 2 3)); => clojure.lang.PersistentList
-
-; A list would be written as just (1 2 3), but we have to quote
-; it to stop the reader thinking it's a function.
-; Also, (list 1 2 3) is the same as '(1 2 3)
-
-; "Collections" are just groups of data
-; Both lists and vectors are collections:
-(coll? '(1 2 3)) ; => true
-(coll? [1 2 3]) ; => true
-
-; "Sequences" (seqs) are abstract descriptions of lists of data.
-; Only lists are seqs.
-(seq? '(1 2 3)) ; => true
-(seq? [1 2 3]) ; => false
-
-; A seq need only provide an entry when it is accessed.
-; So, seqs which can be lazy -- they can define infinite series:
-(range 4) ; => (0 1 2 3)
-(range) ; => (0 1 2 3 4 ...) (an infinite series)
-(take 4 (range)) ;  (0 1 2 3)
-
-; Use cons to add an item to the beginning of a list or vector
-(cons 4 [1 2 3]) ; => (4 1 2 3)
-(cons 4 '(1 2 3)) ; => (4 1 2 3)
-
-; Conj will add an item to a collection in the most efficient way.
-; For lists, they insert at the beginning. For vectors, they insert at the end.
-(conj [1 2 3] 4) ; => [1 2 3 4]
-(conj '(1 2 3) 4) ; => (4 1 2 3)
-
-; Use concat to add lists or vectors together
-(concat [1 2] '(3 4)) ; => (1 2 3 4)
-
-; Use filter, map to interact with collections
-(map inc [1 2 3]) ; => (2 3 4)
-(filter even? [1 2 3]) ; => (2)
-
-; Use reduce to reduce them
-(reduce + [1 2 3 4])
-; = (+ (+ (+ 1 2) 3) 4)
-; => 10
-
-; Reduce can take an initial-value argument too
-(reduce conj [] '(3 2 1))
-; = (conj (conj (conj [] 3) 2) 1)
-; => [3 2 1]
-
-; Functions
-;;;;;;;;;;;;;;;;;;;;;
-
-; Use fn to create new functions. A function always returns
-; its last statement.
-(fn [] "Hello World") ; => fn
-
-; (You need extra parens to call it)
-((fn [] "Hello World")) ; => "Hello World"
-
-; You can create a var using def
-(def x 1)
-x ; => 1
-
-; Assign a function to a var
-(def hello-world (fn [] "Hello World"))
-(hello-world) ; => "Hello World"
-
-; You can shorten this process by using defn
-(defn hello-world [] "Hello World")
-
-; The [] is the list of arguments for the function.
-(defn hello [name]
-  (str "Hello " name))
-(hello "Steve") ; => "Hello Steve"
-
-; You can also use this shorthand to create functions:
-(def hello2 #(str "Hello " %1))
-(hello2 "Fanny") ; => "Hello Fanny"
-
-; You can have multi-variadic functions, too
-(defn hello3
-  ([] "Hello World")
-  ([name] (str "Hello " name)))
-(hello3 "Jake") ; => "Hello Jake"
-(hello3) ; => "Hello World"
-
-; Functions can pack extra arguments up in a seq for you
-(defn count-args [& args]
-  (str "You passed " (count args) " args: " args))
-(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
-
-; You can mix regular and packed arguments
-(defn hello-count [name & args]
-  (str "Hello " name ", you passed " (count args) " extra args"))
-(hello-count "Finn" 1 2 3)
-; => "Hello Finn, you passed 3 extra args"
-
-
-; Maps
-;;;;;;;;;;
-
-; Hash maps and array maps share an interface. Hash maps have faster lookups
-; but don't retain key order.
-(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
-(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap
-
-; Arraymaps will automatically become hashmaps through most operations
-; if they get big enough, so you don't need to worry.
-
-; Maps can use any hashable type as a key, but usually keywords are best
-; Keywords are like strings with some efficiency bonuses
-(class :a) ; => clojure.lang.Keyword
-
-(def stringmap {"a" 1, "b" 2, "c" 3})
-stringmap  ; => {"a" 1, "b" 2, "c" 3}
-
-(def keymap {:a 1, :b 2, :c 3})
-keymap ; => {:a 1, :c 3, :b 2}
-
-; By the way, commas are always treated as whitespace and do nothing.
-
-; Retrieve a value from a map by calling it as a function
-(stringmap "a") ; => 1
-(keymap :a) ; => 1
-
-; Keywords can be used to retrieve their value from a map, too!
-(:b keymap) ; => 2
-
-; Don't try this with strings.
-;("a" stringmap)
-; => Exception: java.lang.String cannot be cast to clojure.lang.IFn
-
-; Retrieving a non-present key returns nil
-(stringmap "d") ; => nil
-
-; Use assoc to add new keys to hash-maps
-(def newkeymap (assoc keymap :d 4))
-newkeymap ; => {:a 1, :b 2, :c 3, :d 4}
-
-; But remember, clojure types are immutable!
-keymap ; => {:a 1, :b 2, :c 3}
-
-; Use dissoc to remove keys
-(dissoc keymap :a :b) ; => {:c 3}
-
-; Sets
-;;;;;;
-
-(class #{1 2 3}) ; => clojure.lang.PersistentHashSet
-(set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3}
-
-; Add a member with conj
-(conj #{1 2 3} 4) ; => #{1 2 3 4}
-
-; Remove one with disj
-(disj #{1 2 3} 1) ; => #{2 3}
-
-; Test for existence by using the set as a function:
-(#{1 2 3} 1) ; => 1
-(#{1 2 3} 4) ; => nil
-
-; There are more functions in the clojure.sets namespace.
-
-; Useful forms
-;;;;;;;;;;;;;;;;;
-
-; Logic constructs in clojure are just macros, and look like
-; everything else
-(if false "a" "b") ; => "b"
-(if false "a") ; => nil
-
-; Use let to create temporary bindings
-(let [a 1 b 2]
-  (> a b)) ; => false
-
-; Group statements together with do
-(do
-  (print "Hello")
-  "World") ; => "World" (prints "Hello")
-
-; Functions have an implicit do
-(defn print-and-say-hello [name]
-  (print "Saying hello to " name)
-  (str "Hello " name))
-(print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff")
-
-; So does let
-(let [name "Urkel"]
-  (print "Saying hello to " name)
-  (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")
-
-
-; Use the threading macros (-> and ->>) to express transformations of
-; data more clearly.
-
-; The "Thread-first" macro (->) inserts into each form the result of
-; the previous, as the first argument (second item)
-(->  
-   {:a 1 :b 2} 
-   (assoc :c 3) ;=> (assoc {:a 1 :b 2} :c 3)
-   (dissoc :b)) ;=> (dissoc (assoc {:a 1 :b 2} :c 3) :b)
-
-; This expression could be written as:
-; (dissoc (assoc {:a 1 :b 2} :c 3) :b)
-; and evaluates to {:a 1 :c 3}
-
-; The double arrow does the same thing, but inserts the result of
-; each line at the *end* of the form. This is useful for collection
-; operations in particular:
-(->>
-   (range 10)
-   (map inc)     ;=> (map inc (range 10)
-   (filter odd?) ;=> (filter odd? (map inc (range 10))
-   (into []))    ;=> (into [] (filter odd? (map inc (range 10)))
-                 ; Result: [1 3 5 7 9]
-
-; When you are in a situation where you want more freedom as where to
-; put the result of previous data transformations in an 
-; expression, you can use the as-> macro. With it, you can assign a
-; specific name to transformations' output and use it as a
-; placeholder in your chained expressions:
-
-(as-> [1 2 3] input
-  (map inc input);=> You can use last transform's output at the last position
-  (nth input 2) ;=>  and at the second position, in the same expression
-  (conj [4 5 6] input [8 9 10])) ;=> or in the middle !
-
-
-
-; Modules
-;;;;;;;;;;;;;;;
-
-; Use "use" to get all functions from the module
-(use 'clojure.set)
-
-; Now we can use set operations
-(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
-(difference #{1 2 3} #{2 3 4}) ; => #{1}
-
-; You can choose a subset of functions to import, too
-(use '[clojure.set :only [intersection]])
-
-; Use require to import a module
-(require 'clojure.string)
-
-; Use / to call functions from a module
-; Here, the module is clojure.string and the function is blank?
-(clojure.string/blank? "") ; => true
-
-; You can give a module a shorter name on import
-(require '[clojure.string :as str])
-(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
-; (#"" denotes a regular expression literal)
-
-; You can use require (and use, but don't) from a namespace using :require.
-; You don't need to quote your modules if you do it this way.
-(ns test
-  (:require
-    [clojure.string :as str]
-    [clojure.set :as set]))
-
-; Java
-;;;;;;;;;;;;;;;;;
-
-; Java has a huge and useful standard library, so
-; you'll want to learn how to get at it.
-
-; Use import to load a java module
-(import java.util.Date)
-
-; You can import from an ns too.
-(ns test
-  (:import java.util.Date
-           java.util.Calendar))
-
-; Use the class name with a "." at the end to make a new instance
-(Date.) ; 
-
-; Use . to call methods. Or, use the ".method" shortcut
-(. (Date.) getTime) ; 
-(.getTime (Date.)) ; exactly the same thing.
-
-; Use / to call static methods
-(System/currentTimeMillis) ;  (system is always present)
-
-; Use doto to make dealing with (mutable) classes more tolerable
-(import java.util.Calendar)
-(doto (Calendar/getInstance)
-  (.set 2000 1 1 0 0 0)
-  .getTime) ; => A Date. set to 2000-01-01 00:00:00
-
-; STM
-;;;;;;;;;;;;;;;;;
-
-; Software Transactional Memory is the mechanism clojure uses to handle
-; persistent state. There are a few constructs in clojure that use this.
-
-; An atom is the simplest. Pass it an initial value
-(def my-atom (atom {}))
-
-; Update an atom with swap!.
-; swap! takes a function and calls it with the current value of the atom
-; as the first argument, and any trailing arguments as the second
-(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)
-(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)
-
-; Use '@' to dereference the atom and get the value
-my-atom  ;=> Atom<#...> (Returns the Atom object)
-@my-atom ; => {:a 1 :b 2}
-
-; Here's a simple counter using an atom
-(def counter (atom 0))
-(defn inc-counter []
-  (swap! counter inc))
-
-(inc-counter)
-(inc-counter)
-(inc-counter)
-(inc-counter)
-(inc-counter)
-
-@counter ; => 5
-
-; Other STM constructs are refs and agents.
-; Refs: http://clojure.org/refs
-; Agents: http://clojure.org/agents
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-coffeescript.html b/docs/_style/prism-master/examples/prism-coffeescript.html deleted file mode 100644 index a710aa99..00000000 --- a/docs/_style/prism-master/examples/prism-coffeescript.html +++ /dev/null @@ -1,61 +0,0 @@ -

Comments

-
# This is a comment
-### This is a
-multi-line comment###
- -

Strings

-
'foo \'bar\' baz'
-"foo \"bar\" baz"
-'Multi-line
-strings are supported'
-"Multi-line
-strings are supported"
-''' 'Block strings'
-are supported too'''
-""" "Block strings"
-are supported too"""
- -

String interpolation

-
"String #{interpolation} is supported"
-'This works #{only} between double-quoted strings'
- -

Object properties

-
kids =
-  brother:
-    name: "Max"
-    age:  11
-  sister:
-    name: "Ida"
-    age:  9
- -

Regexps

-
/normal [r]egexp?/;
-/// ^(
-  mul\t[i-l]ine
-  regexp          # with embedded comment
-) ///
- -

Classes

-
class Animal
-  constructor: (@name) ->
-  move: (meters) ->
-    alert @name + " moved #{meters}m."
-
-class Snake extends Animal
-  move: ->
-    alert "Slithering..."
-    super 5
-
-class Horse extends Animal
-  move: ->
-    alert "Galloping..."
-    super 45
-
-sam = new Snake "Sammy the Python"
-tom = new Horse "Tommy the Palomino"
-
-sam.move()
-tom.move()
- -

Inline JavaScript

-
`alert("foo")`
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-cpp.html b/docs/_style/prism-master/examples/prism-cpp.html deleted file mode 100644 index 32bdcbda..00000000 --- a/docs/_style/prism-master/examples/prism-cpp.html +++ /dev/null @@ -1,61 +0,0 @@ -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-"Multi-line strings ending with a \
-are supported too."
- -

Macro statements

-
# include <stdio.h>
-#define PG_locked   0
-#define PG_error    1
-
- -

Booleans

-
true;
-false;
- -

Operators

-
a and b;
-c bitand d;
- -

Full example

-
/*
-David Cary 2010-09-14
-quick demo for wikibooks
-public domain
-*/
-#include <iostream>
-#include <vector>
-using namespace std;
-
-vector<int> pick_vector_with_biggest_fifth_element(
-    vector<int> left,
-    vector<int> right
-){
-    if( (left[5]) < (right[5]) ){
-        return( right );
-    };
-    // else
-    return( left );
-}
-
-int vector_demo(void){
-    cout << "vector demo" << endl;
-    vector<int> left(7);
-    vector<int> right(7);
-
-    left[5] = 7;
-    right[5] = 8;
-    cout << left[5] << endl;
-    cout << right[5] << endl;
-    vector<int> biggest(
-        pick_vector_with_biggest_fifth_element( left, right )
-    );
-    cout << biggest[5] << endl;
-
-    return 0;
-}
-
-int main(void){
-    vector_demo();
-}
diff --git a/docs/_style/prism-master/examples/prism-crystal.html b/docs/_style/prism-master/examples/prism-crystal.html deleted file mode 100644 index c3cad166..00000000 --- a/docs/_style/prism-master/examples/prism-crystal.html +++ /dev/null @@ -1,16 +0,0 @@ -

Number literals with underscores and postfix

-
1_u32
-123_456.789e-10_f64
- -

Attributes

-
@[AlwaysInline]
-def foo
-	1
-end
- -

Macro expansions

-
{% for key, value in {foo: 100, bar: 20} %}
-	def {{ key.id }}
-		{{ value }}
-	end
-{% end %}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-csharp.html b/docs/_style/prism-master/examples/prism-csharp.html deleted file mode 100644 index c6d76c2b..00000000 --- a/docs/_style/prism-master/examples/prism-csharp.html +++ /dev/null @@ -1,60 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-@"Verbatim strings"
-@"Luis: ""Patrick, where did you get that overnight bag?""
-    Patrick: ""Jean Paul Gaultier.""";
-@'Luis: ''Patrick, where did you get that overnight bag?''
-    Patrick: ''Jean Paul Gaultier.''';
-
- -

Full example

-
using System.Windows.Forms;
-using System.Drawing;
-
-public static DialogResult InputBox(string title, string promptText, ref string value)
-{
-  Form form = new Form();
-  Label label = new Label();
-  TextBox textBox = new TextBox();
-  Button buttonOk = new Button();
-  Button buttonCancel = new Button();
-
-  form.Text = title;
-  label.Text = promptText;
-  textBox.Text = value;
-
-  buttonOk.Text = "OK";
-  buttonCancel.Text = "Cancel";
-  buttonOk.DialogResult = DialogResult.OK;
-  buttonCancel.DialogResult = DialogResult.Cancel;
-
-  label.SetBounds(9, 20, 372, 13);
-  textBox.SetBounds(12, 36, 372, 20);
-  buttonOk.SetBounds(228, 72, 75, 23);
-  buttonCancel.SetBounds(309, 72, 75, 23);
-
-  label.AutoSize = true;
-  textBox.Anchor = textBox.Anchor | AnchorStyles.Right;
-  buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
-  buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
-
-  form.ClientSize = new Size(396, 107);
-  form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel });
-  form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
-  form.FormBorderStyle = FormBorderStyle.FixedDialog;
-  form.StartPosition = FormStartPosition.CenterScreen;
-  form.MinimizeBox = false;
-  form.MaximizeBox = false;
-  form.AcceptButton = buttonOk;
-  form.CancelButton = buttonCancel;
-
-  DialogResult dialogResult = form.ShowDialog();
-  value = textBox.Text;
-  return dialogResult;
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-csp.html b/docs/_style/prism-master/examples/prism-csp.html deleted file mode 100644 index e10a416b..00000000 --- a/docs/_style/prism-master/examples/prism-csp.html +++ /dev/null @@ -1,13 +0,0 @@ -

A complete policy

-
default-src 'none';
-script-src my.cdn.com;
-img-src 'self' data:;
-child-src 'self' data: ms-appx-web:;
-block-all-mixed-content;
-report-uri https://my-reports.com/submit;
-
- -

An policy with unsafe source expressions

-
script-src 'self' 'unsafe-eval' 'unsafe-inline';
-style-src 'unsafe-inline' 'unsafe-hashed-attributes' 'self';
-
diff --git a/docs/_style/prism-master/examples/prism-css.html b/docs/_style/prism-master/examples/prism-css.html deleted file mode 100644 index a36f1966..00000000 --- a/docs/_style/prism-master/examples/prism-css.html +++ /dev/null @@ -1,34 +0,0 @@ -

Empty rule

-
*{} * {} p {}
-
ul,
-ol {}
- -

Simple rule

-
p { color: red; }
- -

Important rule

-

-p {
-    color: red !important;
-    line-height: normal!important;
-}
-p{position:absolute!important}
-
- -

@ rule

-
@media screen and (min-width: 100px) {}
- -

LESS variable

-
@main-color: red;
-.foo {
-	background: @main-color;
-}
- -

Comment

-
/* Simple comment here */
- -

String

-
content: 'foo';
- -

URL

-
content: url(http://github.com/foo.png);
diff --git a/docs/_style/prism-master/examples/prism-d.html b/docs/_style/prism-master/examples/prism-d.html deleted file mode 100644 index d4bf34cc..00000000 --- a/docs/_style/prism-master/examples/prism-d.html +++ /dev/null @@ -1,267 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-	comment */
-/+ Mutli-line
-	/+ nestable +/
-	comment +/
- -

Numbers

-
0 .. 2_147_483_647
-2_147_483_648 .. 9_223_372_036_854_775_807
-0L .. 9_223_372_036_854_775_807L
-0U .. 4_294_967_296U
-4_294_967_296U .. 18_446_744_073_709_551_615U
-0UL .. 18_446_744_073_709_551_615UL
-0x0 .. 0x7FFF_FFFF
-0x8000_0000 .. 0xFFFF_FFFF
-0x1_0000_0000 .. 0x7FFF_FFFF_FFFF_FFFF
-0x8000_0000_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF
-0x0L .. 0x7FFF_FFFF_FFFF_FFFFL
-0x8000_0000_0000_0000L .. 0xFFFF_FFFF_FFFF_FFFFL
-0x0U .. 0xFFFF_FFFFU
-0x1_0000_0000U .. 0xFFFF_FFFF_FFFF_FFFFU
-0x0UL .. 0xFFFF_FFFF_FFFF_FFFFUL
-
-123_456.567_8          // 123456.5678
-1_2_3_4_5_6_.5_6_7_8   // 123456.5678
-1_2_3_4_5_6_.5e-6_     // 123456.5e-6
-0x1.FFFFFFFFFFFFFp1023 // double.max
-0x1p-52                // double.epsilon
-1.175494351e-38F       // float.min
-6.3i                   // idouble 6.3
-6.3fi                  // ifloat 6.3
-6.3Li                  // ireal 6.3
-4.5 + 6.2i             // complex number (phased out)
- -

Strings

-
// WYSIWYG strings
-r"hello"
-r"c:\root\foo.exe"
-r"ab\n"
-`hello`
-`c:\root\foo.exe`
-`ab\n`
-
-// Double-quoted strings
-"hello"
-"c:\\root\\foo.exe"
-"ab\n"
-"ab
-"
-
-// Hex strings
-x"0A"
-x"00 FBCD 32FD 0A"
-
-// String postfix characters
-"hello"c  // string
-"hello"w  // wstring
-"hello"d  // dstring
-
-// Delimited strings
-q"(foo(xxx))"
-q"[foo{]"
-q"EOS
-This
-is a multi-line
-heredoc string
-EOS"
-q"/foo]/"
-
-// Token strings
-q{foo}
-q{/*}*/ }
-q{ foo(q{hello}); }
-q{ __TIME__ }
-
-// Character literals
-'a'
-'\u000A'
- -

Iasm registers

-
AL AH AX EAX
-BL BH BX EBX
-CL CH CX ECX
-DL DH DX EDX
-BP EBP
-SP ESP
-DI EDI
-SI ESI
-ES CS SS DS GS FS
-CR0 CR2 CR3 CR4
-DR0 DR1 DR2 DR3 DR6 DR7
-TR3 TR4 TR5 TR6 TR7
-ST
-ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7)
-MM0  MM1  MM2  MM3  MM4  MM5  MM6  MM7
-XMM0 XMM1 XMM2 XMM3 XMM4 XMM5 XMM6 XMM7
-
-RAX  RBX  RCX  RDX
-BPL  RBP
-SPL  RSP
-DIL  RDI
-SIL  RSI
-R8B  R8W  R8D  R8
-R9B  R9W  R9D  R9
-R10B R10W R10D R10
-R11B R11W R11D R11
-R12B R12W R12D R12
-R13B R13W R13D R13
-R14B R14W R14D R14
-R15B R15W R15D R15
-XMM8 XMM9 XMM10 XMM11 XMM12 XMM13 XMM14 XMM15
-YMM0 YMM1 YMM2  YMM3  YMM4  YMM5  YMM6  YMM7
-YMM8 YMM9 YMM10 YMM11 YMM12 YMM13 YMM14 YMM15
- -

Full example

-
#!/usr/bin/dmd -run
-/* sh style script syntax is supported! */
-/* Hello World in D
-   To compile:
-     dmd hello.d
-   or to optimize:
-     dmd -O -inline -release hello.d
-   or to get generated documentation:
-     dmd hello.d -D
-  */
-import std.stdio;  // References to  commonly used I/O routines.
-void main(char[][] args)   // 'void' here means return 0 by default.
-{
-    // Write-Formatted-Line
-     writefln("Hello World, "   // automatic concatenation of string literals
-              "Reloaded");
-     // Strings are denoted as a dynamic array of chars 'char[]'
-     // auto type inference and built-in foreach
-     foreach(argc, argv; args)
-    {
-        // OOP is supported, of course! And automatic type inference.
-         auto cl = new CmdLin(argc, argv);
-
-        // 'writefln' is the improved 'printf' !!
-         // user-defined class properties.
-         writefln(cl.argnum, cl.suffix, " arg: %s", cl.argv);
-        // Garbage Collection or explicit memory management - your choice!!!
-         delete cl;
-    }
-     // Nested structs, classes and functions!
-     struct specs
-    {
-        // all vars. automatically initialized
-         int count, allocated;
-    }
-
-    // Note that declarations read right-to-left.
-    // So that 'char[][]' reads as an array of an array of chars.
-
-    specs argspecs(char[][] args)
-    // Optional (built-in) function contracts.
-     in{
-        assert (args.length > 0); // assert built in
-     }
-    out(result){
-        assert(result.count == CmdLin.total);
-        assert(result.allocated > 0);
-    }
-    body{
-        specs* s = new specs;
-        // no need for '->'
-         s.count = args.length;  // The 'length' property is number of elements.
-         s.allocated = typeof(args).sizeof; // built-in properties for native types
-         foreach(argv; args)
-            s.allocated += argv.length * typeof(argv[0]).sizeof;
-        return *s;
-    }
-
-    // built-in string and common string operations, e.g. '~' is concatenate.
-     char[] argcmsg  = "argc = %d";
-    char[] allocmsg = "allocated = %d";
-    writefln(argcmsg ~ ", " ~ allocmsg,
-         argspecs(args).count,argspecs(args).allocated);
-}
-/**
-   Stores a single command line argument.
- */
- class CmdLin
-{
-    private {
-     int _argc;
-     char[] _argv;
-     static uint _totalc;
-    }
-
- public:
-/************
-      Object constructor.
-      params:
-        argc = ordinal count of this argument.
-        argv = text of the parameter
-  *********/
-     this(int argc, char[] argv)
-    {
-        _argc = argc + 1;
-        _argv = argv;
-        _totalc++;
-    }
-
-    ~this() /// Object destructor
-     {
-        // Doesn't actually do anything for this example.
-     }
-
-     int argnum() /// A property that returns arg number
-     {
-        return _argc;
-    }
-     char[] argv() /// A property  that returns arg text
-     {
-        return _argv;
-    }
-     wchar[] suffix() /// A property  that returns ordinal suffix
-     {
-        wchar[] suffix;  // Built in  Unicode strings (utf8,utf16, utf32)
-         switch(_argc)
-        {
-        case 1:
-            suffix = "st";
-            break;
-        case 2:
-            suffix = "nd";
-            break;
-        case 3:
-            suffix = "rd";
-            break;
-        default:  // 'default' is mandatory with "-w" compile switch.
-             suffix = "th";
-        }
-        return suffix;
-    }
-
-/* **************
-      * A property of the whole class, not just an instance.
-      * returns: The total number of commandline args added.
-      *************/
-     static typeof(_totalc) total()
-    {
-        return _totalc;
-    }
-     // Class invariant, things that must be true after any method is run.
-     invariant
-     {
-         assert(_argc > 0);
-         assert(_totalc >= _argc);
-     }
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Comments only support one level of nesting

-
/+ /+ /+ this does not work +/ +/ +/
- -

Token strings only support one level of nesting

-
q{ q{ q{ this does not work } } }
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-dart.html b/docs/_style/prism-master/examples/prism-dart.html deleted file mode 100644 index e38d85ea..00000000 --- a/docs/_style/prism-master/examples/prism-dart.html +++ /dev/null @@ -1,59 +0,0 @@ -

Comments

-
// Single line comment
-/// Documentation single line comment
-/* Block comment
-on several lines */
-/** Multi-line
-doc comment */
- -

Annotations

-
@todo('seth', 'make this do something')
-@deprecated // Metadata; makes Dart Editor warn about using activate().
- -

Numbers

-
var x = 1;
-var hex = 0xDEADBEEF;
-var bigInt = 346534658346524376592384765923749587398457294759347029438709349347;
-var y = 1.1;
-var exponents = 1.42e5;
-
- -

Strings

-
var s1 = 'Single quotes work well for string literals.';
-var s2 = "Double quotes work just as well.";
-var s3 = 'It\'s easy to escape the string delimiter.';
-var s4 = "It's even easier to just use the other string delimiter.";
-var s1 = '''
-You can create
-multi-line strings like this one.
-''';
-var s2 = """This is also a
-multi-line string.""";
-var s = r"In a raw string, even \n isn't special.";
- -

Full example

-
class Logger {
-  final String name;
-  bool mute = false;
-
-  // _cache is library-private, thanks to the _ in front of its name.
-  static final Map<String, Logger> _cache = <String, Logger>{};
-
-  factory Logger(String name) {
-    if (_cache.containsKey(name)) {
-      return _cache[name];
-    } else {
-      final logger = new Logger._internal(name);
-      _cache[name] = logger;
-      return logger;
-    }
-  }
-
-  Logger._internal(this.name);
-
-  void log(String msg) {
-    if (!mute) {
-      print(msg);
-    }
-  }
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-diff.html b/docs/_style/prism-master/examples/prism-diff.html deleted file mode 100644 index b9229a17..00000000 --- a/docs/_style/prism-master/examples/prism-diff.html +++ /dev/null @@ -1,33 +0,0 @@ -

Normal Diff

-
7c7
-< qt: core
----
-> qt: core quick
- -

Context Diff

-
*** qcli.yml	2014-12-16 11:43:41.000000000 +0800
---- /Users/uranusjr/Desktop/qcli.yml	2014-12-31 11:28:08.000000000 +0800
-***************
-*** 4,8 ****
-  project:
-      sources: "src/*.cpp"
-      headers: "src/*.h"
-!     qt: core
-  public_headers: "src/*.h"
---- 4,8 ----
-  project:
-      sources: "src/*.cpp"
-      headers: "src/*.h"
-!     qt: core gui
-  public_headers: "src/*.h"
- -

Unified Diff

-
--- qcli.yml	2014-12-16 11:43:41.000000000 +0800
-+++ /Users/uranusjr/Desktop/qcli.yml	2014-12-31 11:28:08.000000000 +0800
-@@ -4,5 +4,5 @@
- project:
-     sources: "src/*.cpp"
-     headers: "src/*.h"
--    qt: core
-+    qt: core gui
- public_headers: "src/*.h"
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-django.html b/docs/_style/prism-master/examples/prism-django.html deleted file mode 100644 index ac9cf0df..00000000 --- a/docs/_style/prism-master/examples/prism-django.html +++ /dev/null @@ -1,31 +0,0 @@ -

Comment

-
{# This is a comment #}
- -

Variable

-
{{ some_variable }}
- -

Template Tag

-
{% if some_condition %}
-Conditional block
-{% endif %}
-
- -

Full Example

-
{# This a Django template example #}
-{% extends "base_generic.html" %}
-
-{% block title %}{{ section.title }}{% endblock %}
-
-{% block content %}
-<h1>{{ section.title }}</h1>
-
-{% for story in story_list %}
-<h2>
-  <a href="http://github.com/%7B%7B%20story.get_absolute_url%20%7D%7D">
-    {{ story.headline|upper }}
-  </a>
-</h2>
-<p>{{ story.tease|truncatewords:"100" }}</p>
-{% endfor %}
-{% endblock %}
-
diff --git a/docs/_style/prism-master/examples/prism-docker.html b/docs/_style/prism-master/examples/prism-docker.html deleted file mode 100644 index 86511b38..00000000 --- a/docs/_style/prism-master/examples/prism-docker.html +++ /dev/null @@ -1,49 +0,0 @@ -

Comments

-
# These are the comments for a dockerfile.
-# I want to make sure $(variables) don't break out,
-# and we shouldn't see keywords like ADD or ENTRYPOINT
-
- -

Full example

-
# Nginx
-#
-# VERSION               0.0.1
-
-FROM      ubuntu
-MAINTAINER Victor Vieux 
-
-LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
-RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server
-
-# Firefox over VNC
-#
-# VERSION               0.3
-
-FROM ubuntu
-
-# Install vnc, xvfb in order to create a 'fake' display and firefox
-RUN apt-get update && apt-get install -y x11vnc xvfb firefox
-RUN mkdir ~/.vnc
-# Setup a password
-RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
-# Autostart firefox (might not be the best way, but it does the trick)
-RUN bash -c 'echo "firefox" >> /.bashrc'
-
-EXPOSE 5900
-CMD    ["x11vnc", "-forever", "-usepw", "-create"]
-
-# Multiple images example
-#
-# VERSION               0.1
-
-FROM ubuntu
-RUN echo foo > bar
-# Will output something like ===> 907ad6c2736f
-
-FROM ubuntu
-RUN echo moo > oink
-# Will output something like ===> 695d7793cbe4
-
-# You᾿ll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
-# /oink.
-
diff --git a/docs/_style/prism-master/examples/prism-eiffel.html b/docs/_style/prism-master/examples/prism-eiffel.html deleted file mode 100644 index 94c18b22..00000000 --- a/docs/_style/prism-master/examples/prism-eiffel.html +++ /dev/null @@ -1,72 +0,0 @@ -

Comments

-
-- A comment
-
- -

Simple string and character

-
"A simple string with %"double quotes%""
-'a'
-
- -

Verbatim-strings

-
"[
-  A aligned verbatim string
-]"
-"{
-  A non-aligned verbatim string
-}"
-
- -

Numbers

-
1_000
-1_000.
-1_000.e+1_000
-1_000.1_000e-1_000
-.1
-0b1010_0001
-0xAF_5B
-0c75_22
-
- -

Class names

-
deferred class
-    A [G]
-
-feature
-    items: G
-        deferred  end
-
-end
-
- -

Full example

-
note
-  description: "Represents a person."
-
-class
-  PERSON
-
-create
-  make, make_unknown
-
-feature {NONE} -- Creation
-
-  make (a_name: like name)
-      -- Create a person with `a_name' as `name'.
-    do
-      name := a_name
-    ensure
-      name = a_name
-    end
-
-    make_unknown
-    do ensure
-      name = Void
-      end
-
-feature -- Access
-
-  name: detachable STRING
-      -- Full name or Void if unknown.
-
-end
-
diff --git a/docs/_style/prism-master/examples/prism-elixir.html b/docs/_style/prism-master/examples/prism-elixir.html deleted file mode 100644 index 7112fbca..00000000 --- a/docs/_style/prism-master/examples/prism-elixir.html +++ /dev/null @@ -1,462 +0,0 @@ -

Comments

-
# This is a comment
- -

Atoms

-
:foo
-:bar
- -

Numbers

-
42
-0b1010
-0o777
-0x1F
-3.14159
-5.2e10
-100_000
- -

Strings and heredoc

-
'A string with \'quotes\'!'
-"A string with \"quotes\"!"
-"Multi-line
-strings are supported"
-""" "Heredoc" strings are
-also supported.
-"""
- -

Sigils

-
~s"""This is a sigil
-using heredoc delimiters"""
-~r/a [reg]exp/
-~r(another|regexp)
-~w[some words]s
-~c<a char list>
- -

Interpolation

-
"This is an #{:atom}"
-~s/#{40+2} is the answer/
- -

Function capturing

-
fun = &Math.zero?/1
-(&is_function/1).(fun)
-fun = &(&1 + 1)
-fun.(1)
-fun = &List.flatten(&1, &2)
-fun.([1, [[2], 3]], [4, 5])
- -

Module attributes

-
defmodule MyServer do
-  @vsn 2
-end
-
-defmodule Math do
-  @moduledoc """
-  Provides math-related functions.
-
-      iex> Math.sum(1, 2)
-      3
-
-  """
-
-  @doc """
-  Calculates the sum of two numbers.
-  """
-  def sum(a, b), do: a + b
-end
- -

Full example

-
# Example from http://learnxinyminutes.com/docs/elixir/
-
-# Single line comments start with a number symbol.
-
-# There's no multi-line comment,
-# but you can stack multiple comments.
-
-# To use the elixir shell use the `iex` command.
-# Compile your modules with the `elixirc` command.
-
-# Both should be in your path if you installed elixir correctly.
-
-## ---------------------------
-## -- Basic types
-## ---------------------------
-
-# There are numbers
-3    # integer
-0x1F # integer
-3.0  # float
-
-# Atoms, that are literals, a constant with name. They start with `:`.
-:hello # atom
-
-# Tuples that are stored contiguously in memory.
-{1,2,3} # tuple
-
-# We can access a tuple element with the `elem` function:
-elem({1, 2, 3}, 0) #=> 1
-
-# Lists that are implemented as linked lists.
-[1,2,3] # list
-
-# We can access the head and tail of a list as follows:
-[head | tail] = [1,2,3]
-head #=> 1
-tail #=> [2,3]
-
-# In elixir, just like in Erlang, the `=` denotes pattern matching and
-# not an assignment.
-#
-# This means that the left-hand side (pattern) is matched against a
-# right-hand side.
-#
-# This is how the above example of accessing the head and tail of a list works.
-
-# A pattern match will error when the sides don't match, in this example
-# the tuples have different sizes.
-# {a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2}
-
-# There are also binaries
-<<1,2,3>> # binary
-
-# Strings and char lists
-"hello" # string
-'hello' # char list
-
-# Multi-line strings
-"""
-I'm a multi-line
-string.
-"""
-#=> "I'm a multi-line\nstring.\n"
-
-# Strings are all encoded in UTF-8:
-"héllò" #=> "héllò"
-
-# Strings are really just binaries, and char lists are just lists.
-<<?a, ?b, ?c>> #=> "abc"
-[?a, ?b, ?c]   #=> 'abc'
-
-# `?a` in elixir returns the ASCII integer for the letter `a`
-?a #=> 97
-
-# To concatenate lists use `++`, for binaries use `<>`
-[1,2,3] ++ [4,5]     #=> [1,2,3,4,5]
-'hello ' ++ 'world'  #=> 'hello world'
-
-<<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>
-"hello " <> "world"  #=> "hello world"
-
-# Ranges are represented as `start..end` (both inclusive)
-1..10 #=> 1..10
-lower..upper = 1..10 # Can use pattern matching on ranges as well
-[lower, upper] #=> [1, 10]
-
-## ---------------------------
-## -- Operators
-## ---------------------------
-
-# Some math
-1 + 1  #=> 2
-10 - 5 #=> 5
-5 * 2  #=> 10
-10 / 2 #=> 5.0
-
-# In elixir the operator `/` always returns a float.
-
-# To do integer division use `div`
-div(10, 2) #=> 5
-
-# To get the division remainder use `rem`
-rem(10, 3) #=> 1
-
-# There are also boolean operators: `or`, `and` and `not`.
-# These operators expect a boolean as their first argument.
-true and true #=> true
-false or true #=> true
-# 1 and true    #=> ** (ArgumentError) argument error
-
-# Elixir also provides `||`, `&&` and `!` which accept arguments of any type.
-# All values except `false` and `nil` will evaluate to true.
-1 || true  #=> 1
-false && 1 #=> false
-nil && 20  #=> nil
-!true #=> false
-
-# For comparisons we have: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<` and `>`
-1 == 1 #=> true
-1 != 1 #=> false
-1 < 2  #=> true
-
-# `===` and `!==` are more strict when comparing integers and floats:
-1 == 1.0  #=> true
-1 === 1.0 #=> false
-
-# We can also compare two different data types:
-1 < :hello #=> true
-
-# The overall sorting order is defined below:
-# number < atom < reference < functions < port < pid < tuple < list < bit string
-
-# To quote Joe Armstrong on this: "The actual order is not important,
-# but that a total ordering is well defined is important."
-
-## ---------------------------
-## -- Control Flow
-## ---------------------------
-
-# `if` expression
-if false do
-  "This will never be seen"
-else
-  "This will"
-end
-
-# There's also `unless`
-unless true do
-  "This will never be seen"
-else
-  "This will"
-end
-
-# Remember pattern matching? Many control-flow structures in elixir rely on it.
-
-# `case` allows us to compare a value against many patterns:
-case {:one, :two} do
-  {:four, :five} ->
-    "This won't match"
-  {:one, x} ->
-    "This will match and bind `x` to `:two`"
-  _ ->
-    "This will match any value"
-end
-
-# It's common to bind the value to `_` if we don't need it.
-# For example, if only the head of a list matters to us:
-[head | _] = [1,2,3]
-head #=> 1
-
-# For better readability we can do the following:
-[head | _tail] = [:a, :b, :c]
-head #=> :a
-
-# `cond` lets us check for many conditions at the same time.
-# Use `cond` instead of nesting many `if` expressions.
-cond do
-  1 + 1 == 3 ->
-    "I will never be seen"
-  2 * 5 == 12 ->
-    "Me neither"
-  1 + 2 == 3 ->
-    "But I will"
-end
-
-# It is common to set the last condition equal to `true`, which will always match.
-cond do
-  1 + 1 == 3 ->
-    "I will never be seen"
-  2 * 5 == 12 ->
-    "Me neither"
-  true ->
-    "But I will (this is essentially an else)"
-end
-
-# `try/catch` is used to catch values that are thrown, it also supports an
-# `after` clause that is invoked whether or not a value is caught.
-try do
-  throw(:hello)
-catch
-  message -> "Got #{message}."
-after
-  IO.puts("I'm the after clause.")
-end
-#=> I'm the after clause
-# "Got :hello"
-
-## ---------------------------
-## -- Modules and Functions
-## ---------------------------
-
-# Anonymous functions (notice the dot)
-square = fn(x) -> x * x end
-square.(5) #=> 25
-
-# They also accept many clauses and guards.
-# Guards let you fine tune pattern matching,
-# they are indicated by the `when` keyword:
-f = fn
-  x, y when x > 0 -> x + y
-  x, y -> x * y
-end
-
-f.(1, 3)  #=> 4
-f.(-1, 3) #=> -3
-
-# Elixir also provides many built-in functions.
-# These are available in the current scope.
-is_number(10)    #=> true
-is_list("hello") #=> false
-elem({1,2,3}, 0) #=> 1
-
-# You can group several functions into a module. Inside a module use `def`
-# to define your functions.
-defmodule Math do
-  def sum(a, b) do
-    a + b
-  end
-
-  def square(x) do
-    x * x
-  end
-end
-
-Math.sum(1, 2)  #=> 3
-Math.square(3) #=> 9
-
-# To compile our simple Math module save it as `math.ex` and use `elixirc`
-# in your terminal: elixirc math.ex
-
-# Inside a module we can define functions with `def` and private functions with `defp`.
-# A function defined with `def` is available to be invoked from other modules,
-# a private function can only be invoked locally.
-defmodule PrivateMath do
-  def sum(a, b) do
-    do_sum(a, b)
-  end
-
-  defp do_sum(a, b) do
-    a + b
-  end
-end
-
-PrivateMath.sum(1, 2)    #=> 3
-# PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)
-
-# Function declarations also support guards and multiple clauses:
-defmodule Geometry do
-  def area({:rectangle, w, h}) do
-    w * h
-  end
-
-  def area({:circle, r}) when is_number(r) do
-    3.14 * r * r
-  end
-end
-
-Geometry.area({:rectangle, 2, 3}) #=> 6
-Geometry.area({:circle, 3})       #=> 28.25999999999999801048
-# Geometry.area({:circle, "not_a_number"})
-#=> ** (FunctionClauseError) no function clause matching in Geometry.area/1
-
-# Due to immutability, recursion is a big part of elixir
-defmodule Recursion do
-  def sum_list([head | tail], acc) do
-    sum_list(tail, acc + head)
-  end
-
-  def sum_list([], acc) do
-    acc
-  end
-end
-
-Recursion.sum_list([1,2,3], 0) #=> 6
-
-# Elixir modules support attributes, there are built-in attributes and you
-# may also add custom ones.
-defmodule MyMod do
-  @moduledoc """
-  This is a built-in attribute on a example module.
-  """
-
-  @my_data 100 # This is a custom attribute.
-  IO.inspect(@my_data) #=> 100
-end
-
-## ---------------------------
-## -- Structs and Exceptions
-## ---------------------------
-
-# Structs are extensions on top of maps that bring default values,
-# compile-time guarantees and polymorphism into Elixir.
-defmodule Person do
-  defstruct name: nil, age: 0, height: 0
-end
-
-joe_info = %Person{ name: "Joe", age: 30, height: 180 }
-#=> %Person{age: 30, height: 180, name: "Joe"}
-
-# Access the value of name
-joe_info.name #=> "Joe"
-
-# Update the value of age
-older_joe_info = %{ joe_info | age: 31 }
-#=> %Person{age: 31, height: 180, name: "Joe"}
-
-# The `try` block with the `rescue` keyword is used to handle exceptions
-try do
-  raise "some error"
-rescue
-  RuntimeError -> "rescued a runtime error"
-  _error -> "this will rescue any error"
-end
-
-# All exceptions have a message
-try do
-  raise "some error"
-rescue
-  x in [RuntimeError] ->
-    x.message
-end
-
-## ---------------------------
-## -- Concurrency
-## ---------------------------
-
-# Elixir relies on the actor model for concurrency. All we need to write
-# concurrent programs in elixir are three primitives: spawning processes,
-# sending messages and receiving messages.
-
-# To start a new process we use the `spawn` function, which takes a function
-# as argument.
-f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>
-spawn(f) #=> #PID<0.40.0>
-
-# `spawn` returns a pid (process identifier), you can use this pid to send
-# messages to the process. To do message passing we use the `send` operator.
-# For all of this to be useful we need to be able to receive messages. This is
-# achieved with the `receive` mechanism:
-defmodule Geometry do
-  def area_loop do
-    receive do
-      {:rectangle, w, h} ->
-        IO.puts("Area = #{w * h}")
-        area_loop()
-      {:circle, r} ->
-        IO.puts("Area = #{3.14 * r * r}")
-        area_loop()
-    end
-  end
-end
-
-# Compile the module and create a process that evaluates `area_loop` in the shell
-pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0>
-
-# Send a message to `pid` that will match a pattern in the receive statement
-send pid, {:rectangle, 2, 3}
-#=> Area = 6
-#   {:rectangle,2,3}
-
-send pid, {:circle, 2}
-#=> Area = 12.56000000000000049738
-#   {:circle,2}
-
-# The shell is also a process, you can use `self` to get the current pid
-self() #=> #PID<0.27.0>
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

String interpolation in single-quoted strings

-
'#{:atom} <- this should not be highligted'
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-elm.html b/docs/_style/prism-master/examples/prism-elm.html deleted file mode 100644 index 00d0e333..00000000 --- a/docs/_style/prism-master/examples/prism-elm.html +++ /dev/null @@ -1,91 +0,0 @@ -

Comments

-
-- Single line comment
-{- Multi-line
-comment -}
- -

Strings and characters

-
'a'
-'\n'
-'\x03'
-"foo \" bar"
-"""
-"multiline strings" are also
-supported!
-"""
- -

Full example

-
module Main exposing (..)
-
-import Html exposing (Html)
-import Svg exposing (..)
-import Svg.Attributes exposing (..)
-import Time exposing (Time, second)
-
-
-main =
-    Html.program
-        { init = init
-        , view = view
-        , update = update
-        , subscriptions = subscriptions
-        }
-
-
-
--- MODEL
-
-
-type alias Model =
-    Time
-
-
-init : ( Model, Cmd Msg )
-init =
-    ( 0, Cmd.none )
-
-
-
--- UPDATE
-
-
-type Msg
-    = Tick Time
-
-
-update : Msg -> Model -> ( Model, Cmd Msg )
-update msg model =
-    case msg of
-        Tick newTime ->
-            ( newTime, Cmd.none )
-
-
-
--- SUBSCRIPTIONS
-
-
-subscriptions : Model -> Sub Msg
-subscriptions model =
-    Time.every second (\time -> Tick time)
-
-
-
--- VIEW
-
-
-view : Model -> Html Msg
-view model =
-    let
-        angle =
-            turns (Time.inMinutes model)
-
-        handX =
-            toString (50 + 40 * cos angle)
-
-        handY =
-            toString (50 + 40 * sin angle)
-    in
-    svg [ viewBox "0 0 100 100", width "300px" ]
-        [ circle [ cx "50", cy "50", r "45", fill "#0B79CE" ] []
-        , line [ x1 "50", y1 "50", x2 handX, y2 handY, stroke "#023963" ] []
-        ]
-
diff --git a/docs/_style/prism-master/examples/prism-erb.html b/docs/_style/prism-master/examples/prism-erb.html deleted file mode 100644 index 46c0143f..00000000 --- a/docs/_style/prism-master/examples/prism-erb.html +++ /dev/null @@ -1,22 +0,0 @@ -

Full example

-
<%# index.erb %>
-<h1>Listing Books</h1>
-<table>
-  <tr>
-    <th>Title</th>
-    <th>Summary</th>
-    <th></th>
-    <th></th>
-    <th></th>
-  </tr>
-
-<% @books.each do |book| %>
-  <tr>
-    <td><%= book.title %></td>
-    <td><%= book.content %></td>
-    <td><%= link_to "Show", book %></td>
-    <td><%= link_to "Edit", edit_book_path(book) %></td>
-    <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
-  </tr>
-<% end %>
-</table>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-erlang.html b/docs/_style/prism-master/examples/prism-erlang.html deleted file mode 100644 index 05445adf..00000000 --- a/docs/_style/prism-master/examples/prism-erlang.html +++ /dev/null @@ -1,47 +0,0 @@ -

Comments

-
% This is a comment
-%% coding: utf-8
- -

Strings

-
"foo \"bar\" baz"
- -

Numbers

-
42.
-$A.
-$\n.
-2#101.
-16#1f.
-2.3.
-2.3e3.
-2.3e-3.
- -

Functions

-
P = spawn(m, loop, []).
-io:format("I am ~p~n", [self()]).
-'weird function'().
-
- -

Variables

-
P = {adam,24,{july,29}}.
-M1 = #{name=>adam,age=>24,date=>{july,29}}.
-M2 = maps:update(age,25,M1).
-io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X]).
- -

Operators

-
1==1.0.
-1=:=1.0.
-1 > a.
-+1.
--1.
-1+1.
-4/2.
-5 div 2.
-5 rem 2.
-2#10 band 2#01.
-2#10 bor 2#01.
-a + 10.
-1 bsl (1 bsl 64).
-not true.
-true and false.
-true xor false.
-true or garbage.
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-flow.html b/docs/_style/prism-master/examples/prism-flow.html deleted file mode 100644 index 76f3e14a..00000000 --- a/docs/_style/prism-master/examples/prism-flow.html +++ /dev/null @@ -1,18 +0,0 @@ -

Primitive types

-
function method(x: number, y: string, z: boolean) {}
-function stringifyBasicValue(value: string | number) {}
-function add(one: any, two: any): number {
-  return one + two;
-}
-
-const bar: number = 2;
-var barVar: number = 2;
-let barLet: number = 2;
-let isOneOf: number | boolean | string = foo;
- -

Keywords

-
type UnionAlias = 1 | 2 | 3;
-opaque type ID = string;
-declare opaque type PositiveNumber: number;
-type Country = $Keys<typeof countries>;
-type RequiredProps = $Diff<Props, DefaultProps>;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-fortran.html b/docs/_style/prism-master/examples/prism-fortran.html deleted file mode 100644 index 00b23f5a..00000000 --- a/docs/_style/prism-master/examples/prism-fortran.html +++ /dev/null @@ -1,71 +0,0 @@ -

Comments

-
! This is a comment
- -

Strings

-
"foo 'bar' baz"
-'foo ''bar'' baz'
-''
-ITALICS_'This is in italics'
-"test &
-	! Some "tricky comment" here
-	&test"
- -

Numbers

-
473
-+56
--101
-21_2
-21_SHORT
-1976354279568241_8
-B'01110'
-B"010"
-O'047'
-O"642"
-Z'F41A'
-Z"00BC"
--12.78
-+1.6E3
-2.1
--16.E4_8
-0.45E-4
-10.93E7_QUAD
-.123
-3E4
- -

Full example

-
MODULE MOD1
-TYPE INITIALIZED_TYPE
-	INTEGER :: I = 1 ! Default initialization
-END TYPE INITIALIZED_TYPE
-SAVE :: SAVED1, SAVED2
-INTEGER :: SAVED1, UNSAVED1
-TYPE(INITIALIZED_TYPE) :: SAVED2, UNSAVED2
-ALLOCATABLE :: SAVED1(:), SAVED2(:), UNSAVED1(:), UNSAVED2(:)
-END MODULE MOD1
-
-PROGRAM MAIN
-CALL SUB1 ! The values returned by the ALLOCATED intrinsic calls
-          ! in the PRINT statement are:
-          ! .FALSE., .FALSE., .FALSE., and .FALSE.
-          ! Module MOD1 is used, and its variables are allocated.
-          ! After return from the subroutine, whether the variables
-          ! which were not specified with the SAVE attribute
-          ! retain their allocation status is processor dependent.
-CALL SUB1 ! The values returned by the first two ALLOCATED intrinsic
-	      ! calls in the PRINT statement are:
-	      ! .TRUE., .TRUE.
-	      ! The values returned by the second two ALLOCATED
-	      ! intrinsic calls in the PRINT statement are
-	      ! processor dependent and each could be either
-	      ! .TRUE. or .FALSE.
-CONTAINS
-	SUBROUTINE SUB1
-	USE MOD1 ! Brings in saved and not saved variables.
-	PRINT *, ALLOCATED(SAVED1), ALLOCATED(SAVED2), &
-	         ALLOCATED(UNSAVED1), ALLOCATED(UNSAVED2)
-	IF (.NOT. ALLOCATED(SAVED1)) ALLOCATE(SAVED1(10))
-	IF (.NOT. ALLOCATED(SAVED2)) ALLOCATE(SAVED2(10))
-	IF (.NOT. ALLOCATED(UNSAVED1)) ALLOCATE(UNSAVED1(10))
-	IF (.NOT. ALLOCATED(UNSAVED2)) ALLOCATE(UNSAVED2(10))
-	END SUBROUTINE SUB1
-END PROGRAM MAIN
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-fsharp.html b/docs/_style/prism-master/examples/prism-fsharp.html deleted file mode 100644 index 6a4330b6..00000000 --- a/docs/_style/prism-master/examples/prism-fsharp.html +++ /dev/null @@ -1,89 +0,0 @@ -

Comments

-
// Single line comment
-(* Multi-line
-comment *)
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-@"Verbatim strings"
-"""Alternate "verbatim" strings"""
-
- -

Numbers

-
//8 bit Int
-86y
-0b00000101y
-//Unsigned 8 bit Int
-86uy
-0b00000101uy
-//16 bit Int
-86s
-//Unsigned 16 bit Int
-86us
-//Int
-86
-86l
-0b10000
-0x2A6
-//Unsigned Int
-86u
-86ul
-//unativeint
-0x00002D3Fun
-//Long
-86L
-//Unsigned Long
-86UL
-//Float
-4.14F
-4.14f
-4.f
-4.F
-0x0000000000000000lf
-//Double
-4.14
-2.3E+32
-2.3e+32
-2.3e-32
-2.3e32
-0x0000000000000000LF
-//BigInt
-9999999999999999999999999999I
-//Decimal
-0.7833M
-0.7833m
-3.m
-3.M
-
- -

Full example

-
// The declaration creates a constructor that takes two values, name and age. 
-type Person(name:string, age:int) =
-    // A Person object's age can be changed. The mutable keyword in the 
-    // declaration makes that possible. 
-    let mutable internalAge = age
-
-    // Declare a second constructor that takes only one argument, a name. 
-    // This constructor calls the constructor that requires two arguments, 
-    // sending 0 as the value for age. 
-    new(name:string) = Person(name, 0)
-
-    // A read-only property. 
-    member this.Name = name
-    // A read/write property. 
-    member this.Age
-        with get() = internalAge
-        and set(value) = internalAge <- value
-
-    // Instance methods. 
-    // Increment the person's age. 
-    member this.HasABirthday () = internalAge <- internalAge + 1
-
-    // Check current age against some threshold. 
-    member this.IsOfAge targetAge = internalAge >= targetAge
-
-    // Display the person's name and age. 
-    override this.ToString () = 
-        "Name:  " + name + "\n" + "Age:   " + (string)internalAge
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-gcode.html b/docs/_style/prism-master/examples/prism-gcode.html deleted file mode 100644 index ebc15615..00000000 --- a/docs/_style/prism-master/examples/prism-gcode.html +++ /dev/null @@ -1,22 +0,0 @@ -

Comments

-
; comment
-(some more comments)
-G28 (even in here) X0
-
- -

Quoted strings

-
"foo""bar"
- -

Full example

-
M190 S60 ; Heat bed to 60°C
-G21 ; Set units to millimeters
-G28 ; Move to Origin (Homing)
-G29 ; Auto Bed Leveling
-G28 X0 Y0 ; Home X and Y to min endstops
-M107 ; Fan off
-M109 S200 ; Heat hotend to 200°C
-G92 E0 ; Set current extruder position as zero
-G1 F200 E15 ; Extrude 15mm filament with 200mm/min
-G92 E0 ; Set current extruder position as zero
-G1 F500
-
diff --git a/docs/_style/prism-master/examples/prism-gedcom.html b/docs/_style/prism-master/examples/prism-gedcom.html deleted file mode 100644 index 3fee6ab0..00000000 --- a/docs/_style/prism-master/examples/prism-gedcom.html +++ /dev/null @@ -1,50 +0,0 @@ -

Full example

-
0 HEAD
-1 CHAR ASCII
-1 SOUR ID_OF_CREATING_FILE
-1 GEDC
-2 VERS 5.5
-2 FORM Lineage-Linked
-1 SUBM @SUBMITTER@
-0 @SUBMITTER@ SUBM
-1 NAME /Submitter/
-1 ADDR Submitters address
-2 CONT address continued here
-0 @FATHER@ INDI
-1 NAME /Father/
-1 SEX M
-1 BIRT
-2 PLAC birth place
-2 DATE 1 JAN 1899
-1 DEAT
-2 PLAC death place
-2 DATE 31 DEC 1990
-1 FAMS @FAMILY@
-0 @MOTHER@ INDI
-1 NAME /Mother/
-1 SEX F
-1 BIRT
-2 PLAC birth place
-2 DATE 1 JAN 1899
-1 DEAT
-2 PLAC death place
-2 DATE 31 DEC 1990
-1 FAMS @FAMILY@
-0 @CHILD@ INDI
-1 NAME /Child/
-1 BIRT
-2 PLAC birth place
-2 DATE 31 JUL 1950
-1 DEAT
-2 PLAC death place
-2 DATE 29 FEB 2000
-1 FAMC @FAMILY@
-0 @FAMILY@ FAM
-1 MARR
-2 PLAC marriage place
-2 DATE 1 APR 1950
-1 HUSB @FATHER@
-1 WIFE @MOTHER@
-1 CHIL @CHILD@
-0 TRLR
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-gherkin.html b/docs/_style/prism-master/examples/prism-gherkin.html deleted file mode 100644 index f38446e7..00000000 --- a/docs/_style/prism-master/examples/prism-gherkin.html +++ /dev/null @@ -1,74 +0,0 @@ -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-
-"""
-Some Title, Eh?
-===============
-Here is the first paragraph of my blog post.
-Lorem ipsum dolor sit amet, consectetur adipiscing
-elit.
-"""
-
- -

Keywords

-
Feature: Some terse yet descriptive text of what is desired
-    In order to realize a named business value
-    As an explicit system actor
-    I want to gain some beneficial outcome which furthers the goal
-
-    Additional text...
-
-    Scenario: Some determinable business situation
-    Given some precondition
-    And some other precondition
-    When some action by the actor
-    And some other action
-    And yet another action
-    Then some testable outcome is achieved
-    And something else we can check happens too
-
-    Scenario: A different situation
-    ...
- -

Comments and tags

-
# user.feature
-@users
-Feature: Sign in to the store
-  In order to view my orders list
-  As a visitor
-  I need to be able to log in to the store
-
-  @javascript @login
-  Scenario: Trying to login without credentials
-      Given I am on the store homepage
-        And I follow "Login"
-       When I press "Login"
-       Then I should be on login page
-       # And I should see "Invalid credentials"
-
- -

Tables and parameters

-
Scenario Outline: Eating
-  Given there are <start> cucumbers
-  When I eat <eat> cucumbers
-  Then I should have <left> cucumbers
-
-  Examples:
-    | start | eat | left |
-    |  12   |  5  |  7   |
-    |  20   |  5  |  15  |
- -

Localized keywords

-
#language: fr
-Fonctionnalité: Contrôle le format de la valeur saisie d'un champ d'une révision
-  En tant qu'expert ou analyste
-  Je ne dois pas pouvoir soumettre des données au mauvais format
-
-  Contexte:
-    Etant donné que je suis connecté avec le pseudo "p_flore" et le mot de passe "p4flore"
-    Et que la gamme du contrat 27156 supporte les révisions
-    Etant donné que le contrat ayant l'id "27156" a une révision
-    Et je suis sur "/contrat/27156/revision/1"
-    Et que j'attends quelques secondes
-    ...
diff --git a/docs/_style/prism-master/examples/prism-git.html b/docs/_style/prism-master/examples/prism-git.html deleted file mode 100644 index cd5668d1..00000000 --- a/docs/_style/prism-master/examples/prism-git.html +++ /dev/null @@ -1,39 +0,0 @@ -

Comments

-
# On branch prism-examples
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#       new file:   examples/prism-git.html
- -

Inserted and deleted lines

-
- Some deleted line
-+ Some added line
- -

Diff

-
$ git diff
-diff --git file.txt file.txt
-index 6214953..1d54a52 100644
---- file.txt
-+++ file.txt
-@@ -1 +1,2 @@
--Here's my tetx file
-+Here's my text file
-+And this is the second line
- -

Logs

-
$ git log
-commit a11a14ef7e26f2ca62d4b35eac455ce636d0dc09
-Author: lgiraudel
-Date:   Mon Feb 17 11:18:34 2014 +0100
-
-    Add of a new line
-
-commit 87edc4ad8c71b95f6e46f736eb98b742859abd95
-Author: lgiraudel
-Date:   Mon Feb 17 11:18:15 2014 +0100
-
-    Typo fix
-
-commit 3102416a90c431400d2e2a14e707fb7fd6d9e06d
-Author: lgiraudel
-Date:   Mon Feb 17 10:58:11 2014 +0100
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-glsl.html b/docs/_style/prism-master/examples/prism-glsl.html deleted file mode 100644 index 7492a422..00000000 --- a/docs/_style/prism-master/examples/prism-glsl.html +++ /dev/null @@ -1,65 +0,0 @@ -

Vertex shader example

-
attribute vec3 vertex;
-attribute vec3 normal;
-
-uniform mat4 _mvProj;
-uniform mat3 _norm;
-
-varying vec3 vColor;
-varying vec3 localPos;
-
-#pragma include "light.glsl"
-
-// constants
-vec3 materialColor = vec3(1.0,0.7,0.8);
-vec3 specularColor = vec3(1.0,1.0,1.0);
-
-void main(void) {
-    // compute position
-    gl_Position = _mvProj * vec4(vertex, 1.0);
-    
-    localPos = vertex;
-    
-    // compute light info
-    vec3 n = normalize(_norm * normal);
-    vec3 diffuse;
-    float specular;
-    float glowingSpecular = 50.0;
-    getDirectionalLight(n, _dLight, glowingSpecular, diffuse, specular);
-    vColor = max(diffuse,_ambient.xyz)*materialColor+specular*specularColor+_ambient;
-}
- -

Fragment shader example

-
#ifdef GL_ES
-precision highp float;
-#endif
-
-uniform vec3 BrickColor, MortarColor;
-uniform vec3 BrickSize;
-uniform vec3 BrickPct;
-
-varying vec3 vColor;
-varying vec3 localPos;
-void main()
-{
-    vec3 color;
-	vec3 position, useBrick;
-	
-
-	position = localPos / BrickSize.xyz;
-
-	if (fract(position.y * 0.5) > 0.5){
-		position.x += 0.5;
-        position.z += 0.5;
-	}
-    
-	position = fract(position);
-
-	useBrick = step(position, BrickPct.xyz);
-
-	color = mix(MortarColor, BrickColor, useBrick.x * useBrick.y * useBrick.z);
-	color *= vColor;
-
-	gl_FragColor = vec4(color, 1.0);
-}
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-gml.html b/docs/_style/prism-master/examples/prism-gml.html deleted file mode 100644 index a1649f6b..00000000 --- a/docs/_style/prism-master/examples/prism-gml.html +++ /dev/null @@ -1,29 +0,0 @@ -

Comments

-
// This is a comment
-/* This is a comment
-on multiple lines */
- -

Functions

-
variable_instance_set(_inst,_var_name,_start+_change);
- -

Full example

-
if(instance_exists(_inst) || _inst==global){
-	if(_delay<=0){
-		_time+=1;
-		if(_time<_duration){
-			event_user(0);
-		}else{
-			if(_inst!=global){
-				variable_instance_set(_inst,_var_name,_start+_change);
-			}else{
-				variable_global_set(_var_name,_start+_change);
-			}
-			instance_destroy();
-		}
-	}else{
-		_delay-=1;
-	}
-}else{
-	instance_destroy();
-}
-
diff --git a/docs/_style/prism-master/examples/prism-go.html b/docs/_style/prism-master/examples/prism-go.html deleted file mode 100644 index 205a1a7e..00000000 --- a/docs/_style/prism-master/examples/prism-go.html +++ /dev/null @@ -1,68 +0,0 @@ -

Comments

-
// This is a comment
-/* This is a comment
-on multiple lines */
- -

Numbers

-
42
-0600
-0xBadFace
-170141183460469231731687303715884105727
-0.
-72.40
-072.40
-2.71828
-1.e+0
-6.67428e-11
-1E6
-.25
-.12345E+5
-0i
-011i
-0.i
-2.71828i
-1.e+0i
-6.67428e-11i
-1E6i
-.25i
-.12345E+5i
- -

Runes and strings

-
'\t'
-'\000'
-'\x07'
-'\u12e4'
-'\U00101234'
-`abc`
-`multi-line
-string`
-"Hello, world!"
-"multi-line
-string"
- -

Functions

-
func(a, b int, z float64) bool { return a*b < int(z) }
- -

Full example

-
package main
-import "fmt"
-
-func sum(a []int, c chan int) {
-	sum := 0
-	for _, v := range a {
-		sum += v
-	}
-	c <- sum // send sum to c
-}
-
-func main() {
-	a := []int{7, 2, 8, -9, 4, 0}
-
-	c := make(chan int)
-	go sum(a[:len(a)/2], c)
-	go sum(a[len(a)/2:], c)
-	x, y := <-c, <-c // receive from c
-
-	fmt.Println(x, y, x+y)
-}
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-graphql.html b/docs/_style/prism-master/examples/prism-graphql.html deleted file mode 100644 index 12113a9d..00000000 --- a/docs/_style/prism-master/examples/prism-graphql.html +++ /dev/null @@ -1,31 +0,0 @@ -

Comments

-
# This is a comment
- -

Strings

-
""
-"foo \"bar\" baz"
- -

Numbers

-
0
-42
-3.14159
--9e-5
-0.9E+7
- -

Keywords

-
query withFragments {
-  user(id: 4) {
-    friends(first: 10) {
-      ...friendFields
-    }
-    mutualFriends(first: 10) {
-      ...friendFields
-    }
-  }
-}
-
-fragment friendFields on User {
-  id
-  name
-  profilePic(size: 50)
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-groovy.html b/docs/_style/prism-master/examples/prism-groovy.html deleted file mode 100644 index 3ec1d6d3..00000000 --- a/docs/_style/prism-master/examples/prism-groovy.html +++ /dev/null @@ -1,93 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo 'bar' baz"
-'foo "bar" baz'
-"""Multi-line
-string"""
-'''Multi-line
-string'''
-"String /containing/ slashes"
-
- -

Slashy strings (regex)

-
/.*foo.*/
-/regex"containing quotes"/
-$/.*"(.*)".*/(.*)/$
- -

Interpolation inside GStrings and regex

-
"The answer is ${21*2}"
-"The $foxtype ${foxcolor.join()} fox"
-/foo${21*2}baz/
-'No interpolation here : ${21*2}'
- -

Full example

-
#!/usr/bin/env groovy
-package model
-
-import groovy.transform.CompileStatic
-import java.util.List as MyList
-
-trait Distributable {
-    void distribute(String version) {}
-}
-
-@CompileStatic
-class Distribution implements Distributable {
-    double number = 1234.234 / 567
-    def otherNumber = 3 / 4
-    boolean archivable = condition ?: true
-    def ternary = a ? b : c
-    String name = "Guillaume"
-    Closure description = null
-    List<DownloadPackage> packages = []
-    String regex = ~/.*foo.*/
-    String multi = '''
-        multi line string
-    ''' + """
-        now with double quotes and ${gstring}
-    """ + $/
-        even with dollar slashy strings
-    /$
-
-    /**
-     * description method
-     * @param cl the closure
-     */
-    void description(Closure cl) { this.description = cl }
-
-    void version(String name, Closure versionSpec) {
-        def closure = { println "hi" } as Runnable
-
-        MyList ml = [1, 2, [a: 1, b:2,c :3]]
-        for (ch in "name") {}
-
-        // single line comment
-        DownloadPackage pkg = new DownloadPackage(version: name)
-
-        check that: true
-
-        label:
-        def clone = versionSpec.rehydrate(pkg, pkg, pkg)
-        /*
-            now clone() in a multiline comment
-        */
-        clone()
-        packages.add(pkg)
-
-        assert 4 / 2 == 2
-    }
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Two divisions on the same line

-
2 / 3 / 4
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-haml.html b/docs/_style/prism-master/examples/prism-haml.html deleted file mode 100644 index c2cc670c..00000000 --- a/docs/_style/prism-master/examples/prism-haml.html +++ /dev/null @@ -1,79 +0,0 @@ -

Comments

-

-/ This is comment
-    on multiple lines
-/ This is a comment
-but this is not
--# This is another comment
-    on multiple lines
- -

Doctype

-
!!! XML
-!!!
-!!! 5
- -

Tags

-
%div
-	%span
-%span(class="widget_#{@widget.number}")
-%div{:id => [@item.type, @item.number], :class => [@item.type, @item.urgency]}
-%html{:xmlns => "http://www.w3.org/1999/xhtml", "xml:lang" => "en", :lang => "en"}
-%html{html_attrs('fr-fr')}
-%div[@user, :greeting]
-%img
-%pre><
-  foo
-  bar
-%img
-
- -

Markup

-
%div
-  <p id="blah">Blah!</p>
- -

Inline Ruby

-
= ['hi', 'there', 'reader!'].join " "
-- foo = "hello"
-= link_to_remote "Add to cart",
-    :url => { :action => "add", :id => product.id },
-    :update => { :success => "cart", :failure => "error" }
-~ "Foo\n<pre>Bar\nBaz</pre>"
-%p
-  - case 2
-  - when 1
-    = "1!"
-  - when 2
-    = "2?"
-  - when 3
-    = "3."
-- (42...47).each do |i|
-  %p= i
-%p See, I can count!
-
- -

Filters

- -
%head
-	:css
-		#content: {
-			background: url('http://github.com/img/background.jpg');
-		}
-		div {
-			color: #333;
-		}
-	:javascript
-		(function() {
-			var test = "Do you like Prism?";
-			if(confirm(test)) {
-				do_something_great();
-			}
-		}());
-%body
-
- -

Filters require the desired language to be loaded. -On this page, check CoffeeScript before checking Haml should make -the example below work properly.

-
%script
-  :coffee
-    console.log 'This is coffee script'
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-handlebars.html b/docs/_style/prism-master/examples/prism-handlebars.html deleted file mode 100644 index c39c8b74..00000000 --- a/docs/_style/prism-master/examples/prism-handlebars.html +++ /dev/null @@ -1,41 +0,0 @@ -

Comments

-
{{! This is a comment with <p>some markup</p> in it }}
-{{! This is a comment }} {{ this_is_not }}
- -

Variables

-
<p>{{ text }}</p>
-<h1>{{article.title}}</h1>
-{{{ triple_stash_is_supported }}}
-{{articles.[10].[#comments]}}
- -

Strings, numbers and booleans

-
{{{link "See more..." story.url}}}
-{{ true }}
-{{ custom_helper 42 href="http://github.com/somepage.html" false }}
- -

Block helpers

-
<div class="body">
-	{{#bold}}{{body}}{{/bold}}
-</div>
-{{#with story}}
-	<div class="intro">{{{intro}}}</div>
-	<div class="body">{{{body}}}</div>
-{{/with}}
-<div class="{{#if test}}foo{{else}}bar{{/if}}"></div>
-{{#list array}}
-	{{@index}}. {{title}}
-{{/list}}
-{{#block-with-hyphens args=yep}}
-	This should probably work...
-{{/block-with-hyphens}}
-
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Handlebars tag in the middle of an HTML tag

-
<div{{#if test}} class="test"{{/if}}></div>
diff --git a/docs/_style/prism-master/examples/prism-haskell.html b/docs/_style/prism-master/examples/prism-haskell.html deleted file mode 100644 index de58eb79..00000000 --- a/docs/_style/prism-master/examples/prism-haskell.html +++ /dev/null @@ -1,80 +0,0 @@ -

Comments

-
-- Single line comment
-{- Multi-line
-comment -}
- -

Strings and characters

-
'a'
-'\n'
-'\^A'
-'\^]'
-'\NUL'
-'\23'
-'\o75'
-'\xFE'
-"Here is a backslant \\ as well as \137, \
-    \a numeric escape character, and \^X, a control character."
- -

Numbers

-
42
-123.456
-123.456e-789
-1e+3
-0o74
-0XAF
- -

Full example

-
hGetLine h =
-  wantReadableHandle_ "Data.ByteString.hGetLine" h $
-    \ h_@Handle__{haByteBuffer} -> do
-      flushCharReadBuffer h_
-      buf <- readIORef haByteBuffer
-      if isEmptyBuffer buf
-         then fill h_ buf 0 []
-         else haveBuf h_ buf 0 []
- where
-
-  fill h_@Handle__{haByteBuffer,haDevice} buf len xss =
-    len `seq` do
-    (r,buf') <- Buffered.fillReadBuffer haDevice buf
-    if r == 0
-       then do writeIORef haByteBuffer buf{ bufR=0, bufL=0 }
-               if len > 0
-                  then mkBigPS len xss
-                  else ioe_EOF
-       else haveBuf h_ buf' len xss
-
-  haveBuf h_@Handle__{haByteBuffer}
-          buf@Buffer{ bufRaw=raw, bufR=w, bufL=r }
-          len xss =
-    do
-        off <- findEOL r w raw
-        let new_len = len + off - r
-        xs <- mkPS raw r off
-
-      -- if eol == True, then off is the offset of the '\n'
-      -- otherwise off == w and the buffer is now empty.
-        if off /= w
-            then do if (w == off + 1)
-                            then writeIORef haByteBuffer buf{ bufL=0, bufR=0 }
-                            else writeIORef haByteBuffer buf{ bufL = off + 1 }
-                    mkBigPS new_len (xs:xss)
-            else do
-                 fill h_ buf{ bufL=0, bufR=0 } new_len (xs:xss)
-
-  -- find the end-of-line character, if there is one
-  findEOL r w raw
-        | r == w = return w
-        | otherwise =  do
-            c <- readWord8Buf raw r
-            if c == fromIntegral (ord '\n')
-                then return r -- NB. not r+1: don't include the '\n'
-                else findEOL (r+1) w raw
-
-mkPS :: RawBuffer Word8 -> Int -> Int -> IO ByteString
-mkPS buf start end =
- create len $ \p ->
-   withRawBuffer buf $ \pbuf -> do
-   copyBytes p (pbuf `plusPtr` start) len
- where
-   len = end - start
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-haxe.html b/docs/_style/prism-master/examples/prism-haxe.html deleted file mode 100644 index 61fbf0e4..00000000 --- a/docs/_style/prism-master/examples/prism-haxe.html +++ /dev/null @@ -1,37 +0,0 @@ -

Strings and string interpolation

-
"Foo
-bar $baz"
-'Foo
-bar'
-"${4 + 2}"
- -

Regular expressions

-
~/haxe/i
-~/[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z][A-Z][A-Z]?/i
-~/(dog|fox)/g
- -

Conditional compilation

-
#if !debug
-  trace("ok");
-#elseif (debug_level > 3)
-  trace(3);
-#else
-  trace("debug level too low");
-#end
- -

Metadata

-
@author("Nicolas")
-@debug
-class MyClass {
-  @range(1, 8)
-  var value:Int;
-
-  @broken
-  @:noCompletion
-  static function method() { }
-}
- -

Reification

-
macro static function add(e:Expr) {
-  return macro $e + $e;
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-hpkp.html b/docs/_style/prism-master/examples/prism-hpkp.html deleted file mode 100644 index ced16eb7..00000000 --- a/docs/_style/prism-master/examples/prism-hpkp.html +++ /dev/null @@ -1,11 +0,0 @@ -

Pin for one year with report-uri

-
pin-sha256="EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4=";
-max-age=31536000;
-includeSubDomains;
-report-uri="https://my-reports.com/submit"
-
- -

Pin for a short time (considered unsafe)

-
pin-sha256="EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4=";
-max-age=123
-
diff --git a/docs/_style/prism-master/examples/prism-hsts.html b/docs/_style/prism-master/examples/prism-hsts.html deleted file mode 100644 index f7d0e451..00000000 --- a/docs/_style/prism-master/examples/prism-hsts.html +++ /dev/null @@ -1,8 +0,0 @@ -

Policy with far-future max-age

-
max-age=31536000
- -

Policy with near-future max-age, considered unsafe

-
max-age=123
- -

Policy with extra directives

-
max-age=31536000; includeSubdomains; preload
diff --git a/docs/_style/prism-master/examples/prism-http.html b/docs/_style/prism-master/examples/prism-http.html deleted file mode 100644 index a2091768..00000000 --- a/docs/_style/prism-master/examples/prism-http.html +++ /dev/null @@ -1,33 +0,0 @@ -

Request header

-
GET http://localhost:9999/foo.html HTTP/1.1
-Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
-Accept-Encoding: gzip, deflate
- -

Response header

-
HTTP/1.1 200 OK
-Server: GitHub.com
-Date: Mon, 22 Dec 2014 18:25:30 GMT
-Content-Type: text/html; charset=utf-8
- -

Response body highlighted based on Content-Type

-

This currently supports the following content types : - "application/json", - "application/xml", - "text/xml" and - "text/html".

-
HTTP/1.1 200 OK
-Server: GitHub.com
-Date: Mon, 22 Dec 2014 18:25:30 GMT
-Content-Type: text/html; charset=utf-8
-Last-Modified: Sun, 21 Dec 2014 20:29:48 GMT
-Transfer-Encoding: chunked
-Expires: Mon, 22 Dec 2014 18:35:30 GMT
-Cache-Control: max-age=600
-Vary: Accept-Encoding
-Content-Encoding: gzip
-
-<!DOCTYPE html>
-<html lang="en">
-<head></head>
-<body></body>
-</html>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-ichigojam.html b/docs/_style/prism-master/examples/prism-ichigojam.html deleted file mode 100644 index 24bcbf22..00000000 --- a/docs/_style/prism-master/examples/prism-ichigojam.html +++ /dev/null @@ -1,29 +0,0 @@ -

Note: this component focuses on IchigoJam, which uses a small subset of basic and introduces its own markers.

- -

Comments

-
' This is a comment
-REM This is a remark
-'NoSpaceIsOK
-REMNOSPACE
- -

Strings

-
"This a string."
-"This is a string with ""quotes"" in it."
- -

Numbers

-
42
-3.14159
--42
--3.14159
-.5
-10.
-2E10
-4.2E-14
--3E+2
-#496F726953756B69
-`11100010
- -

IchigoJam Basic example

-
A=0
-FOR I=1 TO 100 : A=A+I : NEXT
-PRINT A
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-icon.html b/docs/_style/prism-master/examples/prism-icon.html deleted file mode 100644 index 1dce690c..00000000 --- a/docs/_style/prism-master/examples/prism-icon.html +++ /dev/null @@ -1,172 +0,0 @@ -

Comments

-
#
-# Foobar
- -

Strings and csets

-
""
-"Foo\"bar"
-''
-'a\'bcdefg'
- -

Numbers

-
42
-3.14159
-5.2E+8
-16rface
-2r1101
- -

Full example

-
# Author: Robert J. Alexander
-global GameObject, Tree, Learn
-record Question(question, yes, no)
-procedure main()
-   GameObject := "animal"
-   Tree := Question("Does it live in water", "goldfish", "canary")
-   Get()                                  # Recall prior knowledge
-   Game()                                 # Play a game
-   return
-end
-#  Game() -- Conducts a game.
-#
-procedure Game()
-   while Confirm("Are you thinking of ", Article(GameObject), " ",
-      GameObject) do Ask(Tree)
-   write("Thanks for a great game.")
-   if \Learn &Confirm("Want to save knowledge learned this session")
-   then Save()
-   return
-end
-#  Confirm() -- Handles yes/no questions and answers.
-#
-procedure Confirm(q[])
-   local answer, s
-   static ok
-   initial {
-      ok := table()
-      every ok["y" | "yes" | "yeah" | "uh huh"] := "yes"
-      every ok["n" | "no"  | "nope" | "uh uh" ] := "no"
-      }
-   while /answer do {
-      every writes(!q)
-      write("?")
-      case s := read() | exit(1) of {
-         #  Commands recognized at a yes/no prompt.
-         #
-         "save":    Save()
-         "get":     Get()
-         "list":    List()
-         "dump":    Output(Tree)
-         default:   {
-            (answer := \ok[map(s, &ucase, &lcase)]) |
-               write("This is a \"yes\" or \"no\" question.")
-            }
-         }
-      }
-   return answer == "yes"
-end
-#  Ask() -- Navigates through the barrage of questions leading to a
-#  guess.
-#
-procedure Ask(node)
-   local guess, question
-   case type(node) of {
-      "string":        {
-         if not Confirm("It must be ", Article(node), " ", node, ", right") then {
-            Learn := "yes"
-            write("What were you thinking of?")
-            guess := read() | exit(1)
-            write("What question would distinguish ", Article(guess), " ",
-               guess, " from ", Article(node), " ", node, "?")
-            question := read() | exit(1)
-            if question[-1] == "?" then question[-1] := ""
-            question[1] := map(question[1], &lcase, &ucase)
-            if Confirm("For ", Article(guess), " ", guess, ", what would the answer be")
-            then return Question(question, guess, node)
-         else return Question(question, node, guess)
-         }
-      }
-      "Question":  {
-         if Confirm(node.question) then node.yes := Ask(node.yes)
-         else node.no := Ask(node.no)
-         }
-      }
-end
-#  Article() -- Come up with the appropriate indefinite article.
-#
-procedure Article(word)
-   return if any('aeiouAEIOU', word) then "an" else "a"
-end
-#  Save() -- Store our acquired knowledge in a disk file name
-#  based on the GameObject.
-#
-procedure Save()
-   local f
-   f := open(GameObject || "s", "w")
-   Output(Tree, f)
-   close(f)
-   return
-end
-#  Output() -- Recursive procedure used to output the knowledge tree.
-#
-procedure Output(node, f, sense)
-   static indent
-   initial indent := 0
-   /f := &output
-   /sense := " "
-   case type(node) of {
-      "string":        write(f, repl(" ", indent), sense, "A: ", node)
-      "Question":  {
-         write(f, repl(" ", indent), sense, "Q: ", node.question)
-         indent +:= 1
-         Output(node.yes, f, "y")
-         Output(node.no, f, "n")
-         indent -:= 1
-         }
-      }
-   return
-end
-#  Get() -- Read in a knowledge base from a file.
-#
-procedure Get()
-   local f
-   f := open(GameObject || "s", "r") | fail
-   Tree := Input(f)
-   close(f)
-   return
-end
-#  Input() -- Recursive procedure used to input the knowledge tree.
-#
-procedure Input(f)
-   local nodetype, s
-   read(f) ? (tab(upto(~' \t')) & =("y" | "n" | "") &
-      nodetype := move(1) & move(2) & s := tab(0))
-   return if nodetype == "Q" then Question(s, Input(f), Input(f)) else s
-end
-#  List() -- Lists the objects in the knowledge base.
-#
-$define Length           78
-procedure List()
-   local lst, line, item
-   lst := Show(Tree, [ ])
-   line := ""
-   every item := !sort(lst) do {
-      if *line + *item > Length then {
-         write(trim(line))
-         line := ""
-         }
-      line ||:= item || ", "
-      }
-   write(line[1:-2])
-   return
-end
-#
-#  Show() -- Recursive procedure used to navigate the knowledge tree.
-#
-procedure Show(node, lst)
-   if type(node) == "Question" then {
-      lst := Show(node.yes, lst)
-      lst := Show(node.no, lst)
-      }
-   else put(lst, node)
-   return lst
-end
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-inform7.html b/docs/_style/prism-master/examples/prism-inform7.html deleted file mode 100644 index 9e2214f0..00000000 --- a/docs/_style/prism-master/examples/prism-inform7.html +++ /dev/null @@ -1,171 +0,0 @@ -

Comments

-
[This is a comment]
-[This is a
-multi-line comment]
- -

Texts

-
"This is a string"
-"This is a
-multi-line string"
- -

Numbers

-
42
-3.14159
-50kg
-100m
-one
-three
-twelve
- -

Titles

-
Section 2 - Flamsteed's Balloon
-
-Part SR1 - The Physical World Model
-
-Table of Floors
- -

Standard kinds, verbs and keywords

-
In the Treehouse is a container called the cardboard box.
-The cardboard box is a closed container. The glass bottle is a transparent open container. The box is fixed in place and openable.
-
-Check photographing:
-    if the noun is the camera, say "Sadly impossible." instead.
- -

Text substitution

-
"[if the player is in Center Ring]A magician's booth stands in the corner, painted dark blue with glittering gold stars.[otherwise if the magician's booth is closed]A crack of light indicates the way back out to the center ring.[otherwise]The door stands open to the outside.[end if]".
- -

Full example

-
"Lakeside Living"
-
-A volume is a kind of value. 15.9 fl oz specifies a volume with parts ounces and tenths (optional, preamble optional).
-
-A fluid container is a kind of container. A fluid container has a volume called a fluid capacity. A fluid container has a volume called current volume.
-
-The fluid capacity of a fluid container is usually 12.0 fl oz. The current volume of a fluid container is usually 0.0 fl oz.
-
-Liquid is a kind of value. The liquids are water, absinthe, and iced tea. A fluid container has a liquid.
-
-Instead of examining a fluid container:
-    if the noun is empty,
-        say "You catch just a hint of [the liquid of the noun] at the bottom.";
-    otherwise
-        say "[The noun] contains [current volume of the noun in rough terms] of [liquid of the noun]."
-
-To say (amount - a volume) in rough terms:
-    if the amount is less than 0.5 fl oz:
-        say "a swallow or two";
-    otherwise if tenths part of amount is greater than 3 and tenths part of amount is less than 7:
-        let estimate be ounces part of amount;
-        say "[estimate in words] or [estimate plus 1 in words] fluid ounces";
-    otherwise:
-        if tenths part of amount is greater than 6, increase amount by 1.0 fl oz;
-        say "about [ounces part of amount in words] fluid ounce[s]".
-
-Before printing the name of a fluid container (called the target) while not drinking or pouring:
-    if the target is empty:
-        say "empty ";
-    otherwise:
-        do nothing.
-
-After printing the name of a fluid container (called the target) while not examining or pouring:
-    unless the target is empty:
-        say " of [liquid of the target]";
-        omit contents in listing.
-
-Instead of inserting something into a fluid container:
-    say "[The second noun] has too narrow a mouth to accept anything but liquids."
-
-Definition: a fluid container is empty if the current volume of it is 0.0 fl oz. Definition: a fluid container is full if the current volume of it is the fluid capacity of it.
-
-Understand "drink from [fluid container]" as drinking.
-
-Instead of drinking a fluid container:
-    if the noun is empty:
-        say "There is no more [liquid of the noun] within." instead;
-    otherwise:
-        decrease the current volume of the noun by 0.2 fl oz;
-        if the current volume of the noun is less than 0.0 fl oz, now the current volume of the noun is 0.0 fl oz;
-        say "You take a sip of [the liquid of the noun][if the noun is empty], leaving [the noun] empty[end if]."
-
-Part 2 - Filling
-
-Understand the command "fill" as something new.
-
-Understand "fill [fluid container] with/from [full liquid source]" as filling it with. Understand "fill [fluid container] with/from [fluid container]" as filling it with.
-
-Understand "fill [something] with/from [something]" as filling it with.
-
-Filling it with is an action applying to two things. Carry out filling it with: try pouring the second noun into the noun instead.
-
-Understand "pour [fluid container] in/into/on/onto [fluid container]" as pouring it into. Understand "empty [fluid container] into [fluid container]" as pouring it into.
-
-Understand "pour [something] in/into/on/onto [something]" as pouring it into. Understand "empty [something] into [something]" as pouring it into.
-
-Pouring it into is an action applying to two things.
-
-Check pouring it into:
-    if the noun is not a fluid container, say "You can't pour [the noun]." instead;
-    if the second noun is not a fluid container, say "You can't pour liquids into [the second noun]." instead;
-    if the noun is the second noun, say "You can hardly pour [the noun] into itself." instead;
-    if the liquid of the noun is not the liquid of the second noun:
-        if the second noun is empty, now the liquid of the second noun is the liquid of the noun;
-        otherwise say "Mixing [the liquid of the noun] with [the liquid of the second noun] would give unsavory results." instead;
-    if the noun is empty, say "No more [liquid of the noun] remains in [the noun]." instead;
-    if the second noun is full, say "[The second noun] cannot contain any more than it already holds." instead.
-
-Carry out pouring it into:
-    let available capacity be the fluid capacity of the second noun minus the current volume of the second noun;
-    if the available capacity is greater than the current volume of the noun, now the available capacity is the current volume of the noun;
-    increase the current volume of the second noun by available capacity;
-    decrease the current volume of the noun by available capacity.
-
-Report pouring it into:
-    say "[if the noun is empty][The noun] is now empty;[otherwise][The noun] now contains [current volume of the noun in rough terms] of [liquid of the noun]; [end if]";
-    say "[the second noun] contains [current volume of the second noun in rough terms] of [liquid of the second noun][if the second noun is full], and is now full[end if]."
-
-Understand the liquid property as describing a fluid container. Understand "of" as a fluid container.
-
-A liquid source is a kind of fluid container. A liquid source has a liquid. A liquid source is usually scenery. The fluid capacity of a liquid source is usually 3276.7 fl oz. The current volume of a liquid source is usually 3276.7 fl oz. Instead of examining a liquid source: say "[The noun] is full of [liquid of the noun]."
-
-Carry out pouring a liquid source into something: now the current volume of the noun is 3276.7 fl oz.
-
-After pouring a liquid source into a fluid container:
-    say "You fill [the second noun] up with [liquid of the noun] from [the noun]."
-
-Instead of pouring a fluid container into a liquid source:
-    if the noun is empty, say "[The noun] is already empty." instead;
-    now the current volume of the noun is 0.0 fl oz;
-    say "You dump out [the noun] into [the second noun]."
-
-Swimming is an action applying to nothing. Understand "swim" or "dive" as swimming.
-
-Instead of swimming in the presence of a liquid source:
-    say "You don't feel like a dip just now."
-
-Before inserting something into a liquid source: say "[The noun] would get lost and never be seen again." instead.
-
-Part 3 - Scenario
-
-The Lakeside is a room. The Lakeside swing is an enterable supporter in the Lakeside. "Here you are by the lake, enjoying a summery view."
-
-The glass is a fluid container carried by the player. The liquid of the glass is absinthe. The current volume of the glass is 0.8 fl oz.
-
-The pitcher is a fluid container in the Lakeside. The fluid capacity of the pitcher is 32.0 fl oz. The current volume of the pitcher is 20.0 fl oz. The liquid of the pitcher is absinthe.
-
-The lake is a liquid source. It is in the Lakeside.
-
-The player wears a bathing outfit. The description of the bathing outfit is "Stylishly striped in blue and white, and daringly cut to reveal almost all of your calves, and quite a bit of upper arm, as well. You had a moral struggle, purchasing it; but mercifully the lakeshore is sufficiently secluded that no one can see you in this immodest apparel."
-
-Instead of taking off the outfit: say "What odd ideas come into your head sometimes!"
-
-Test me with "fill glass / empty absinthe into lake / fill glass / swim / drink lake / drink / x water / x lake". 
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Names starting with a number

-
The box 1A is a container
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-ini.html b/docs/_style/prism-master/examples/prism-ini.html deleted file mode 100644 index 284fbe44..00000000 --- a/docs/_style/prism-master/examples/prism-ini.html +++ /dev/null @@ -1,10 +0,0 @@ -

Comments

-
; This is a comment
- -

Section title

-
[owner]
-[database]
- -

Properties

-
name=prism
-file="somefile.txt"
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-io.html b/docs/_style/prism-master/examples/prism-io.html deleted file mode 100644 index ff5160a4..00000000 --- a/docs/_style/prism-master/examples/prism-io.html +++ /dev/null @@ -1,31 +0,0 @@ -

Comments

-
//
-// Foobar
-#!/usr/bin/env io
-/* multiline
-comment
-*/
- -

Strings

-
"this is a \"test\".\nThis is only a test."
-"""this is a "test".
-This is only a test."""
- -

Numbers

-
123
-123.456
-0.456
-123e-4
-123e4
-123.456e-7
-123.456e2
-
- -

Full example

-
"Hello, world!" println
-A := Object clone    // creates a new, empty object named "A"
-factorial := method(n,
-    if(n == 0, return 1)
-    res := 1
-    Range 1 to(n) foreach(i, res = res * i)
-)
diff --git a/docs/_style/prism-master/examples/prism-j.html b/docs/_style/prism-master/examples/prism-j.html deleted file mode 100644 index cf4c109a..00000000 --- a/docs/_style/prism-master/examples/prism-j.html +++ /dev/null @@ -1,59 +0,0 @@ -

Comments

-
NB. This is a comment
- -

Strings

-
'This is a string.'
-'This is a string with ''quotes'' in it.'
- -

Numbers

-
2.3e2 2.3e_2 2j3
-2p1 1p_1
-1x2 2x1 1x_1
-2e2j_2e2 2e2j2p1 2ad45 2ar0.785398
-16b1f 10b23 _10b23 1e2b23 2b111.111
- -

Verbs

-
%4
-3%4
-,b
-'I';'was';'here'
-3 5$'wake read lamp '
- -

Adverbs

-
1 2 3 */ 4 5 6 7
-'%*'(1 3;2 _1)} y
- -

Conjunctions

-
10&^. 2 3 10 100 200
-+`*
-+:@*: +/ -:@%:
- -

Examples

-
NB. The following functions E1, E2 and E3
-NB. interchange two rows of a matrix,
-NB. multiply a row by a constant,
-NB. and add a multiple of one row to another:
-
-E1=: <@] C. [
-E2=: f`g`[}
-E3=: F`g`[}
-f=: {:@] * {.@] { [
-F=: [: +/ (1:,{:@]) * (}:@] { [)
-g=: {.@]
-M=: i. 4 5
-M;(M E1 1 3);(M E2 1 10);(M E3 1 3 10)
- -
NB. Implementation of quicksort
-
-sel=: adverb def 'u # ['
-
-quicksort=: verb define
-  if. 1 >: #y do. y
-  else.
-    (quicksort y <sel e),(y =sel e),quicksort y >sel e=.y{~?#y
-  end.
-)
- -
NB. Implementation of quicksort (tacit programming)
-
-quicksort=: (($:@(<#[), (=#[), $:@(>#[)) ({~ ?@#)) ^: (1<#)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-java.html b/docs/_style/prism-master/examples/prism-java.html deleted file mode 100644 index 691a6b10..00000000 --- a/docs/_style/prism-master/examples/prism-java.html +++ /dev/null @@ -1,65 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz";
-'foo \'bar\' baz';
- -

Numbers

-
123
-123.456
--123.456
-.3f
-1.3e9d
-0xaf
-0xAF
-0xFF.AEP-4
-
- -

Full example

-
import java.util.Scanner;
-
-public class Life {
-
-    @Override @Bind("One")
-    public void show(boolean[][] grid){
-        String s = "";
-        for(boolean[] row : grid){
-            for(boolean val : row)
-                if(val)
-                    s += "*";
-                else
-                    s += ".";
-            s += "\n";
-        }
-        System.out.println(s);
-    }
-
-    public static boolean[][] gen(){
-        boolean[][] grid = new boolean[10][10];
-        for(int r = 0; r < 10; r++)
-            for(int c = 0; c < 10; c++)
-                if( Math.random() > 0.7 )
-                    grid[r][c] = true;
-        return grid;
-    }
-
-    public static void main(String[] args){
-        boolean[][] world = gen();
-        show(world);
-        System.out.println();
-        world = nextGen(world);
-        show(world);
-        Scanner s = new Scanner(System.in);
-        while(s.nextLine().length() == 0){
-            System.out.println();
-            world = nextGen(world);
-            show(world);
-
-        }
-    }
-
-	// [...]
-}
diff --git a/docs/_style/prism-master/examples/prism-javascript.html b/docs/_style/prism-master/examples/prism-javascript.html deleted file mode 100644 index 51fe4835..00000000 --- a/docs/_style/prism-master/examples/prism-javascript.html +++ /dev/null @@ -1,77 +0,0 @@ -

Variable assignment

-
var foo = "bar", baz = 5;
- -

Operators

-
(1 + 2 * 3)/4 >= 3 && 4 < 5 || 6 > 7
- -

Indented code

-
if (true) {
-	while (true) {
-		doSomething();
-	}
-}
- -

Regex with slashes

-
var foo = /([^/])\/(\\?.|\[.+?])+?\/[gim]{0,3}/g;
- -

Regex that ends with double slash

-
var bar = /\/\*[\w\W]*?\*\//g;
- -

Single line comments & regexes

-
// http://lea.verou.me
-var comment = /\/\*[\w\W]*?\*\//g;
- -

Link in comment

-
// http://lea.verou.me
-/* http://lea.verou.me */
- -

Nested strings

-
var foo = "foo", bar = "He \"said\" 'hi'!"
- -

Strings inside comments

-
// "foo"
-/* "foo" */
- -

Strings with slashes

-
env.content + '</' + env.tag + '>'
-var foo = "/" + "/";
-var foo = "http://prismjs.com/"; // Strings are strings and comments are comments ;)
- -

Regex inside single line comment

-
// hey, /this doesn’t fail!/ :D
- -

Two or more division operators on the same line

-
var foo = 5 / 6 / 7;
- -

A division operator on the same line as a regex

-
var foo = 1/2, bar = /a/g;
-var foo = /a/, bar = 3/4;
- -

ES6 features

-
// Regex "y" and "u" flags
-var a = /[a-zA-Z]+/gimyu;
-
-// for..of loops
-for(let x of y) { }
-
-// Modules: import
-import { foo as bar } from "file.js"
-
-// Template strings
-`Only on ${y} one line`
-`This template string ${x} is on
-
-multiple lines.`
-`40 + 2 = ${ 40 + 2 }`
-`The squares of the first 3 natural integers are ${[for (x of [1,2,3]) x*x].join(', ')}`
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

String interpolation containing a closing brace

-
`${ {foo:'bar'}.foo }`
-`${ '}' }`
diff --git a/docs/_style/prism-master/examples/prism-javastacktrace.html b/docs/_style/prism-master/examples/prism-javastacktrace.html deleted file mode 100644 index 3511aa6a..00000000 --- a/docs/_style/prism-master/examples/prism-javastacktrace.html +++ /dev/null @@ -1,63 +0,0 @@ -

Full example

-
javax.servlet.ServletException: Something bad happened
-    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
-    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
-    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
-    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
-    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
-    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
-    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
-    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
-    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
-    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
-    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
-    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
-    at org.mortbay.jetty.Server.handle(Server.java:326)
-    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
-    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
-    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
-    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
-    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
-    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
-    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
-Caused by: com.example.myproject.MyProjectServletException
-    at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
-    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
-    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
-    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
-    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
-    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
-    ... 27 more
-Suppressed: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
-    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
-    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
-    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
-    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
-    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
-    at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
-    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
-    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
-    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
-    at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
-    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
-    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
-    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
-    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
-    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
-    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
-    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
-    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
-    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
-    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
-    at java.lang.reflect.Method.invoke(Method.java:597)
-    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
-    at $Proxy19.save(Unknown Source)
-    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
-    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
-    ... 32 more
-Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
-    at org.hsqldb.jdbc.Util.throwError(Unknown Source)
-    at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
-    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
-    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
-    ... 54 more
diff --git a/docs/_style/prism-master/examples/prism-jolie.html b/docs/_style/prism-master/examples/prism-jolie.html deleted file mode 100644 index 8d23d995..00000000 --- a/docs/_style/prism-master/examples/prism-jolie.html +++ /dev/null @@ -1,162 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz";
-'foo \'bar\' baz'
- -

Numbers

-
42
-42L
-1.2e3
-0.1E-4
-0.2e+1
-
- -

Full example

-
include "console.iol"
-
-type HubType: void {
-  .sid: undefined
-  .nodes[1,*] : NodeType
-}
-
-type NodeType: void {
-  .sid: string
-  .node: string
-  .load?: int
-}
-
-type NetType: HubType | NodeType
-
-interface NetInterface {
-  OneWay: start( string ), addElement( NetType ), removeElement( NetType ), quit( void )
-  RequestResponse: showElements( void )( NetType ) throws SomeFault
-}
-
-type LogType: void {
-  .message: string
-}
-
-interface LoggerInterface {
-  RequestResponse: log( LogType )( void )
-}
-
-outputPort LoggerService {
-    Interfaces: LoggerInterface
-}
-
-embedded {
-  Jolie: "logger.ol" in LoggerService
-}
-
-type AuthenticationData: void {
-    .key:string
-}
-
-interface extender AuthInterfaceExtender {
-    OneWay: *(AuthenticationData)
-}
-
-service SubService 
-{
-  Interfaces: NetInterface
-
-  main
-  {
-     println@Console( "I do nothing" )()
-  }
-}
-
-inputPort ExtLogger {
-  Location: "socket://localhost:9000"
-  Protocol: sodep
-  Interfaces: LoggerInterface
-  Aggregates: LoggerService with AuthInterfaceExtender
-}
-
-courier ExtLogger {
-  [interface LoggerInterface( request )] {
-    if ( key == "secret" ){
-      forward ( request )
-    }
-  }
-}
-
-inputPort In {
-  Location: "socket://localhost:8000"
-  Protocol: http {
-    .debug = true;
-    .debug.showContent = true
-  }
-  Interfaces: NetInterface
-  Aggregates: SubService, 
-              LoggerService
-  Redirects: A => SubService, 
-             B => SubService
-}
-
-cset {
-  sid: HubType.sid NodeType.sid
-}
-
-execution{ concurrent }
-
-define netmodule {
-  if( request.load == 0 || request.load < 1 && 
-      request.load <= 2 || request.load >= 3 && 
-      request.load > 4  || request.load%4 == 2
-  ) {
-    scope( scopeName ) {   
-      // inline comment
-      install( MyFault => println@Console( "Something \"Went\" Wrong" + ' but it\'s ok' )() );
-      /*
-      * Multi-line
-      * Comment
-      */
-      install( this => cH; println@Console( "Something went wrong: " + ^load )() );
-      install( default => comp( scopeName ); println@Console( "Something went wrong" )() );
-      load -> request.( "load" );
-      { ++load | load++ | --load | load-- };
-      throw( MyFault )
-    }
-  } else {
-    foreach ( node -> request.nodes ) {
-      with( node ){
-        while( .load != 100 ) {
-          .load++
-        }   
-      }
-    } 
-  }
-}
-
-main
-{
-  start( sid );
-  synchronized( unneededSync ){
-    csets.sid = sid;
-    undef( sid )
-  };
-  provide
-    [ addElement( request ) ]{
-      if( request instanceof NodeType ) {
-        netmodule
-      }
-    }
-    [ removeElement() ]
-    [ showElements()( response ){
-       /*
-       * assemble response
-       */
-       nullProcess
-     }]{
-       // log the request
-       log@LoggerService( new )();
-       log @ LoggerService( new )()
-     }
-  until
-   [ quit() ]{ exit }
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-jsx.html b/docs/_style/prism-master/examples/prism-jsx.html deleted file mode 100644 index faff4920..00000000 --- a/docs/_style/prism-master/examples/prism-jsx.html +++ /dev/null @@ -1,18 +0,0 @@ -

Full example

-
var ExampleApplication = React.createClass({
-    render: function() {
-      var elapsed = Math.round(this.props.elapsed  / 100);
-      var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
-      var message =
-        'React has been successfully running for ' + seconds + ' seconds.';
-
-      return <p>{message}</p>;
-    }
-  });
-  var start = new Date().getTime();
-  setInterval(function() {
-    React.render(
-      <ExampleApplication elapsed={new Date().getTime() - start} />,
-      document.getElementById('container')
-    );
-  }, 50);
diff --git a/docs/_style/prism-master/examples/prism-julia.html b/docs/_style/prism-master/examples/prism-julia.html deleted file mode 100644 index b69d0454..00000000 --- a/docs/_style/prism-master/examples/prism-julia.html +++ /dev/null @@ -1,29 +0,0 @@ -

Full example

-
function mandel(z)
-    c = z
-    maxiter = 80
-    for n = 1:maxiter
-        if abs(z) > 2
-            return n-1
-        end
-        z = z^2 + c
-    end
-    return maxiter
-end
-
-function randmatstat(t)
-    n = 5
-    v = zeros(t)
-    w = zeros(t)
-    for i = 1:t
-        a = randn(n,n)
-        b = randn(n,n)
-        c = randn(n,n)
-        d = randn(n,n)
-        P = [a b c d]
-        Q = [a b; c d]
-        v[i] = trace((P.'*P)^4)
-        w[i] = trace((Q.'*Q)^4)
-    end
-    std(v)/mean(v), std(w)/mean(w)
-end
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-keyman.html b/docs/_style/prism-master/examples/prism-keyman.html deleted file mode 100644 index 770827d5..00000000 --- a/docs/_style/prism-master/examples/prism-keyman.html +++ /dev/null @@ -1,107 +0,0 @@ -

Comments

-
c This is a comment
- -

Strings, numbers and characters

-
"'this' is a string"
-'and so is "this"'
-U+0041 d65 x41   c these are all the letter A
-
- -

Prefixes and Virtual Keys

- -

-c Match RAlt+E on desktops, Ctrl+Alt+E on web because L/R Alt not consistently supported in browsers.
-$KeymanOnly: + [RALT K_E] > "€"
-$KeymanWeb: + [CTRL ALT K_E] > "€"
-
- -

Example Code

- -
c =====================Begin Identity Section===================================================
-c 
-c Mnemonic input method for Amharic script on US-QWERTY
-c keyboards for Keyman version 7.1, compliant with Unicode 4.1 and later.
-c 
-
-store(&VERSION) '9.0'
-store(&Name) "Amharic"
-c store(&MnemonicLayout) "1"
-store(&CapsAlwaysOff) "1"
-store(&Copyright) "Creative Commons Attribution 3.0"
-store(&Message) "This is an Amharic language mnemonic input method for Ethiopic script that requires Unicode 4.1 support."
-store(&WINDOWSLANGUAGES) 'x045E x045E'
-store(&LANGUAGE) 'x045E'
-store(&EthnologueCode) "amh"
-store(&VISUALKEYBOARD) 'gff-amh-7.kvk'
-store(&KMW_EMBEDCSS) 'gff-amh-7.css'
-HOTKEY "^%A"
-c 
-c =====================End Identity Section=====================================================
-
-c =====================Begin Data Section=======================================================
-
-c ---------------------Maps for Numbers---------------------------------------------------------
-store(ArabOnes) '23456789'
-store(ones)     '፪፫፬፭፮፯፰፱'
-store(tens)     '፳፴፵፶፷፸፹፺'
-store(arabNumbers) '123456789'
-store(ethNumbers) '፩፪፫፬፭፮፯፰፱፲፳፴፵፶፷፸፹፺፻፼'
-store(arabNumbersWithZero) '0123456789'
-store(ColonOrComma) ':,'
-store(ethWordspaceOrComma) '፡፣'
-c ---------------------End Numbers--------------------------------------------------------------
-
-c =====================End Data Section=========================================================
-
-c =====================Begin Functional Section=================================================
-c 
-store(&LAYOUTFILE) 'gff-amh-7_layout.js'
-store(&BITMAP) 'amharic.bmp'
-store(&TARGETS) 'any windows'
-begin Unicode > use(main)
-group(main) using keys    
-
-c ---------------------Input of Numbers---------------------------------------------------------
-
-c Special Rule for Arabic Numerals
-c 
-c The following attempts to auto-correct the use of Ethiopic wordspace and
-c Ethiopic comma within an Arabic numeral context.  Ethiopic wordspace gets
-c used erroneously in time formats and Ethiopic commas as an order of thousands
-c delimiter. The correction context is not known until numerals appear on _both_
-c sides of the punctuation.
-c 
-  any(arabNumbersWithZero) any(ethWordspaceOrComma) + any(arabNumbers) > index(arabNumbersWithZero,1) index(ColonOrComma,2) index(arabNumbers,3)
-
-c Ethiopic Numerals
-
-  "'" + '1' > '፩'
-  "'" + any(ArabOnes) > index(ones,2)
-
-c special cases for multiples of one
-  '፩'  + '0' > '፲'
-  '፲'  + '0' > '፻'
-  '፻'  + '0' > '፲፻'
-  '፲፻' + '0' > '፼'
-  '፼'  + '0' > '፲፼'    
-  '፲፼' + '0' > '፻፼' 
-  '፻፼'  + '0' > '፲፻፼'
-  '፲፻፼' + '0' > '፼፼'
-  '፼፼' + '0' > context beep  c do not go any higher, we could beep here
-
-c upto the order of 100 million
-  any(ones)     + '0' > index(tens,1)
-  any(tens)     + '0' > index(ones,1) '፻'  c Hundreds
-  any(ones)  '፻ '+ '0' > index(tens,1) '፻'  c Thousands
-  any(tens)  '፻' + '0' > index(ones,1) '፼'  c Ten Thousands
-  any(ones)  '፼' + '0' > index(tens,1) '፼'  c Hundred Thousands
-  any(tens)  '፼' + '0' > index(ones,1) '፻፼' c Millions
-  any(ones) '፻፼' + '0' > index(tens,1) '፻፼' c Ten Millions
-  any(tens) '፻፼' + '0' > index(ones,1) '፼፼' c Hundred Millions
-
-c enhance this later, look for something that can copy a match over
-  any(ethNumbers) + any(arabNumbers) > index(ethNumbers,1)  index(ethNumbers,2)
-c ---------------------End Input of Numbers-----------------------------------------------------
-                                            
-c =====================End Functional Section===================================================
-
diff --git a/docs/_style/prism-master/examples/prism-kotlin.html b/docs/_style/prism-master/examples/prism-kotlin.html deleted file mode 100644 index 7c8f7b4e..00000000 --- a/docs/_style/prism-master/examples/prism-kotlin.html +++ /dev/null @@ -1,134 +0,0 @@ -

Numbers

-
123
-123L
-0x0F
-0b00001011
-123.5
-123.5e10
-123.5f
-123.5F
- -

Strings and interpolation

-
'2'
-'\uFF00'
-'\''
-
-"foo $bar \"baz"
-"""
-foo ${40 + 2}
-baz${bar()}
-"""
- -

Labels

-
loop@ for (i in 1..100) {
-  for (j in 1..100) {
-    if (...)
-      break@loop
-  }
-}
- -

Annotations

-
public class MyTest {
-    lateinit var subject: TestSubject
-
-    @SetUp fun setup() {
-        subject = TestSubject()
-    }
-
-    @Test fun test() {
-        subject.method()  // dereference directly
-    }
-}
- -

Full example

-
package com.example.html
-
-interface Element {
-    fun render(builder: StringBuilder, indent: String)
-
-    override fun toString(): String {
-        val builder = StringBuilder()
-        render(builder, "")
-        return builder.toString()
-    }
-}
-
-class TextElement(val text: String): Element {
-    override fun render(builder: StringBuilder, indent: String) {
-        builder.append("$indent$text\n")
-    }
-}
-
-abstract class Tag(val name: String): Element {
-    val children = arrayListOf<Element>()
-    val attributes = hashMapOf<String, String>()
-
-    protected fun initTag<T: Element>(tag: T, init: T.() -> Unit): T {
-        tag.init()
-        children.add(tag)
-        return tag
-    }
-
-    override fun render(builder: StringBuilder, indent: String) {
-        builder.append("$indent<$name${renderAttributes()}>\n")
-        for (c in children) {
-            c.render(builder, indent + "  ")
-        }
-        builder.append("$indent</$name>\n")
-    }
-
-    private fun renderAttributes(): String? {
-        val builder = StringBuilder()
-        for (a in attributes.keySet()) {
-            builder.append(" $a=\"${attributes[a]}\"")
-        }
-        return builder.toString()
-    }
-}
-
-abstract class TagWithText(name: String): Tag(name) {
-    operator fun String.plus() {
-        children.add(TextElement(this))
-    }
-}
-
-class HTML(): TagWithText("html") {
-    fun head(init: Head.() -> Unit) = initTag(Head(), init)
-
-    fun body(init: Body.() -> Unit) = initTag(Body(), init)
-}
-
-class Head(): TagWithText("head") {
-    fun title(init: Title.() -> Unit) = initTag(Title(), init)
-}
-
-class Title(): TagWithText("title")
-
-abstract class BodyTag(name: String): TagWithText(name) {
-    fun b(init: B.() -> Unit) = initTag(B(), init)
-    fun p(init: P.() -> Unit) = initTag(P(), init)
-    fun h1(init: H1.() -> Unit) = initTag(H1(), init)
-    fun a(href: String, init: A.() -> Unit) {
-        val a = initTag(A(), init)
-        a.href = href
-    }
-}
-
-class Body(): BodyTag("body")
-
-class B(): BodyTag("b")
-class P(): BodyTag("p")
-class H1(): BodyTag("h1")
-class A(): BodyTag("a") {
-    public var href: String
-        get() = attributes["href"]!!
-        set(value) {
-            attributes["href"] = value
-        }
-}
-
-fun html(init: HTML.() -> Unit): HTML {
-    val html = HTML()
-    html.init()
-    return html
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-latex.html b/docs/_style/prism-master/examples/prism-latex.html deleted file mode 100644 index 137df360..00000000 --- a/docs/_style/prism-master/examples/prism-latex.html +++ /dev/null @@ -1,12 +0,0 @@ -

Comments

-
% This is a comment
- -

Commands

-
\begin{document}
-\documentstyle[twoside,epsfig]{article}
-\usepackage{epsfig,multicol}
- -

Math mode

-
$\alpha$
-H$_{2}$O
-45$^{\circ}$C
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-less.html b/docs/_style/prism-master/examples/prism-less.html deleted file mode 100644 index db8a5e64..00000000 --- a/docs/_style/prism-master/examples/prism-less.html +++ /dev/null @@ -1,70 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

Variables

-
@nice-blue: #5B83AD;
-@light-blue: @nice-blue + #111;
- -

At-rules

-
@media screen and (min-width: 320px) {}
- -

Mixins

-
.bordered {
-  border-top: dotted 1px black;
-  border-bottom: solid 2px black;
-}
-#menu a {
-  .bordered;
-}
-#header a {
-  color: orange;
-  #bundle > .button;
-}
- -

Mixins with parameters

-
.foo (@bg: #f5f5f5, @color: #900) {
-  background: @bg;
-  color: @color;
-}
-.bar {
-  .foo();
-}
-.class1 {
-  .mixin(@margin: 20px; @color: #33acfe);
-}
-.class2 {
-  .mixin(#efca44; @padding: 40px);
-}
- -

Interpolation

-
@mySelector: banner;
-.@{mySelector} {
-  font-weight: bold;
-}
-@property: color;
-.widget {
-  @{property}: #0ee;
-  background-@{property}: #999;
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

At-rules looking like variables

-
@import "http://github.com/some%20file.less";
- -

At-rules containing interpolation

-
@import "http://github.com/@%7Bthemes%7D/tidal-wave.less";
- -

extend is not highlighted consistently

-
nav ul {
-  &:extend(.inline);
-  background: blue;
-}
-.a:extend(.b) {}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-liquid.html b/docs/_style/prism-master/examples/prism-liquid.html deleted file mode 100644 index 74971975..00000000 --- a/docs/_style/prism-master/examples/prism-liquid.html +++ /dev/null @@ -1,75 +0,0 @@ -

Comments

-
{% comment %}This is a comment{% endcomment %}
- -

Control Flow

- -Liquid provides multiple control flow statements. - -

if

-

-{% if customer.name == 'kevin' %}
-  Hey Kevin!
-{% elsif customer.name == 'anonymous' %}
-  Hey Anonymous!
-{% else %}
-  Hi Stranger!
-{% endif %}
-
- -

unless

- -The opposite of if – executes a block of code only if a certain condition is not met. - -

-{% unless product.title == 'Awesome Shoes' %}
-These shoes are not awesome.
-{% endunless %}
-
- -

case

- -Creates a switch statement to compare a variable with different values. case initializes the switch statement, and when compares its values. - -

-{% assign handle = 'cake' %}
-{% case handle %}
-  {% when 'cake' %}
-    This is a cake
-  {% when 'cookie' %}
-    This is a cookie
-  {% else %}
-    This is not a cake nor a cookie
-{% endcase %}
-
- -

for

- -Repeatedly executes a block of code. - -break = Causes the loop to stop iterating when it encounters the break tag. -continue = Causes the loop to skip the current iteration when it encounters the continue tag. - -

-{% for i in (1..10) %}
-  {% if i == 4 %}
-    {% break %}
-  {% elsif i == 6 %}
-    {% continue %}
-  {% else %}
-    {{ i }}
-  {% endif %}
-{% endfor %}
-
- -

range

- -

-{% for i in (3..5) %}
-  {{ i }}
-{% endfor %}
-
-{% assign num = 4 %}
-{% for i in (1..num) %}
-  {{ i }}
-{% endfor %}
-
diff --git a/docs/_style/prism-master/examples/prism-lisp.html b/docs/_style/prism-master/examples/prism-lisp.html deleted file mode 100644 index 436d5c9c..00000000 --- a/docs/_style/prism-master/examples/prism-lisp.html +++ /dev/null @@ -1,46 +0,0 @@ -

Comments

-
;; (foo bar)
- -

Strings

-
(foo "bar")
- -

With nested symbols

-
(foo "A string with a `symbol ")
- -

With nested arguments

-
(foo "A string with an ARGUMENT ")
- -

Quoted symbols

-
(foo #'bar)
- -

Lisp properties

-
(foo :bar)
- -

Splices

-
(foo ,bar ,@bar)
- -

Keywords

-
(let foo (bar arg))
- -

Declarations

-
(declare foo)
- -

Booleans

-
(foo t)
-
(foo nil)
- -

Numbers

-
(foo 1)
-
(foo -1.5)
- -

Definitions

-
(defvar bar 23)
-
(defcustom bar 23)
- -

Function definitions

-
(defun multiply-by-seven (number)
-       "Multiply NUMBER by seven."
-       (* 7 number))
- -

Lambda expressions

-
(lambda (number) (* 7 number))
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-livescript.html b/docs/_style/prism-master/examples/prism-livescript.html deleted file mode 100644 index e9194d36..00000000 --- a/docs/_style/prism-master/examples/prism-livescript.html +++ /dev/null @@ -1,84 +0,0 @@ -

Comments

-
# This is a single line comment
-/* This is a
-multi line comment */
- -

Numbers

-
42
-42km
-3.754km_2
-16~BadFace
-36~azertyuiop0123456789
- -

Strings and interpolation

-
''
-''''''
-""
-""""""
-'Foo \' bar
-	baz'
-'''Foo \''' bar
-	bar'''
-"Foo #bar \"
-	#{2 + 2}\""
-"""#foobar \""" #{ if /test/ == 'test' then 3 else 4}
-	baz"""
- -

Regex

-
/foobar/ig
-//
-^foo # foo
-[bar]*bA?z # barbaz
-//m
- -

Full example

-
# example from Str.ls
-
-split = (sep, str) -->
-  str.split sep
-
-join = (sep, xs) -->
-  xs.join sep
-
-lines = (str) ->
-  return [] unless str.length
-  str.split '\n'
-
-unlines = (.join '\n')
-
-words = (str) ->
-  return [] unless str.length
-  str.split /[ ]+/
-
-unwords = (.join ' ')
-
-chars = (.split '')
-
-unchars = (.join '')
-
-reverse = (str) ->
-  str.split '' .reverse!.join ''
-
-repeat = (n, str) -->
-  result = ''
-  for til n
-    result += str
-  result
-
-capitalize = (str) ->
-  (str.char-at 0).to-upper-case! + str.slice 1
-
-camelize = (.replace /[-_]+(.)?/g, (, c) -> (c ? '').to-upper-case!)
-
-# convert camelCase to camel-case, and setJSON to set-JSON
-dasherize = (str) ->
-    str
-      .replace /([^-A-Z])([A-Z]+)/g, (, lower, upper) ->
-         "#{lower}-#{if upper.length > 1 then upper else upper.to-lower-case!}"
-      .replace /^([A-Z]+)/, (, upper) ->
-         if upper.length > 1 then "#upper-" else upper.to-lower-case!
-
-module.exports = {
-  split, join, lines, unlines, words, unwords, chars, unchars, reverse,
-  repeat, capitalize, camelize, dasherize,
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-lolcode.html b/docs/_style/prism-master/examples/prism-lolcode.html deleted file mode 100644 index 38308328..00000000 --- a/docs/_style/prism-master/examples/prism-lolcode.html +++ /dev/null @@ -1,62 +0,0 @@ -

Comments

-
BTW Single line comment
-OBTW Multi-line
-comment TLDR
- -

Strings and special characters

-
"foo :"bar:" baz"
-"foo:)bar:>baz"
-"Interpolation :{works} too!"
- -

Numbers

-
42
--42
-123.456
- -

Variable declaration

-
I HAS A var
-var R "THREE"
-var R 3
- -

Types

-
MAEK some_expr A YARN
-some_var IS NOW A NUMBR
- -

Full example

-
OBTW Convert a number to hexadecimal. This
-     is returned as a string.
-TLDR
-HOW IZ I decimal_to_hex YR num
-    I HAS A i ITZ 0
-    I HAS A rem
-    I HAS A hex_num ITZ A BUKKIT
-    I HAS A decimal_num ITZ num
-    IM IN YR num_loop
-        rem R MOD OF decimal_num AN 16
-        I HAS A hex_digit
-        rem, WTF?
-            OMG 10, hex_digit R "A", GTFO
-            OMG 11, hex_digit R "B", GTFO
-            OMG 12, hex_digit R "C", GTFO
-            OMG 13, hex_digit R "D", GTFO
-            OMG 14, hex_digit R "E", GTFO
-            OMG 15, hex_digit R "F", GTFO
-            OMGWTF, hex_digit R rem
-        OIC
-        hex_num HAS A SRS i ITZ hex_digit
-        decimal_num R QUOSHUNT OF decimal_num AN 16
-        BOTH SAEM decimal_num AN 0, O RLY?
-            YA RLY, GTFO
-            NO WAI, i R SUM OF i AN 1
-        OIC
-    IM OUTTA YR num_loop
-    I HAS A hex_string ITZ A YARN
-    IM IN YR string_reverse
-        DIFFRINT i AN BIGGR OF i AN 0, O RLY?
-            YA RLY, GTFO
-        OIC
-        hex_string R SMOOSH hex_string AN hex_num'Z SRS i MKAY
-        i R DIFF OF i AN 1
-    IM OUTTA YR string_reverse
-    FOUND YR hex_string
-IF U SAY SO
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-lua.html b/docs/_style/prism-master/examples/prism-lua.html deleted file mode 100644 index 288b7675..00000000 --- a/docs/_style/prism-master/examples/prism-lua.html +++ /dev/null @@ -1,89 +0,0 @@ -

Comments

-
#!/usr/local/bin/lua
---
--- Single line comment
---[[ Multi line
-comment ]]
---[====[ Multi line
-comment ]====]
- -

Strings

-
""
-"Foo\"bar"
-"Foo\
-bar \z
-baz"
-''
-'Foo\'bar'
-'Foo\
-bar \z
-baz'
-[[Multi "line"
-string]]
-[==[Multi [["line"]]
-string]==]
- -

Numbers

-
3
-345
-0xff
-0xBEBADA
-3, 3., 3.1, .3,
-3e12, 3.e-41, 3.1E+1, .3e1
-0x0.1E
-0xA23p-4
-0X1.921FB54442D18P+1
- -

Full example

-
function To_Functable(t, fn)
-  return setmetatable(t,
-    {
-     __index = function(t, k) return fn(k) end,
-     __call = function(t, k) return t[k] end
-    })
-end
-
--- Functable bottles of beer implementation
-
-spell_out = {
-  "One", "Two", "Three", "Four", "Five",
-  "Six", "Seven", "Eight", "Nine", "Ten",
-  [0] = "No more",
-  [-1] = "Lots more"
-}
-
-spell_out = To_Functable(spell_out, function(i) return i end)
-
-bottles = To_Functable({"Just one bottle of beer"},
-                       function(i)
-                         return spell_out(i) .. " bottles of beer"
-                       end)
-
-function line1(i)
-  return bottles(i) .. " on the wall, " .. bottles(i) .. "\n"
-end
-
-line2 = To_Functable({[0] = "Go to the store, Buy some more,\n"},
-                     function(i)
-                       return "Take one down and pass it around,\n"
-                     end)
-
-function line3(i)
-  return bottles(i) .. " on the wall.\n"
-end
-
-function song(n)
-  for i = n, 0, -1 do
-    io.write(line1(i), line2(i), line3(i - 1), "\n")
-  end
-end
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Functions with a single string parameter not using parentheses are not highlighted

-
foobar"param";
diff --git a/docs/_style/prism-master/examples/prism-makefile.html b/docs/_style/prism-master/examples/prism-makefile.html deleted file mode 100644 index 45f6f420..00000000 --- a/docs/_style/prism-master/examples/prism-makefile.html +++ /dev/null @@ -1,263 +0,0 @@ -

Comments

-
# This is a comment
-include foo # This is another comment
- -

Targets

-
kbd.o command.o files.o : command.h
-display.o insert.o search.o files.o : buffer.h
-
-.PHONY: clean
-clean:
-        rm *.o temp
- -

Variables

-
objects = main.o kbd.o command.o display.o \
-          insert.o search.o files.o utils.o
-
-edit : $(objects)
-        cc -o edit $(objects)
-
-$(objects) : defs.h
-
-%oo: $$< $$^ $$+ $$*
-
-foo : bar/lose
-        cd $(@D) && gobble $(@F) > ../$@
- -

Strings

-
STR = 'A string!'
-
-HELLO = 'hello \
-world'
-
-HELLO2 = "hello \
-world"
- -

Directives

-
include foo *.mk $(bar)
-
-vpath %.c foo
-
-override define two-lines =
-foo
-$(bar)
-endef
-
-ifeq ($(CC),gcc)
-  libs=$(libs_for_gcc)
-else
-  libs=$(normal_libs)
-endif
- -

Functions

-
whoami    := $(shell whoami)
-host-type := $(shell arch)
-
-y = $(subst 1,2,$(x))
-
-dirs := a b c d
-files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
-
-reverse = $(2) $(1)
-foo = $(call reverse,a,b)
-
-$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))
- -

Complete example

-
#!/usr/bin/make -f
-# Generated automatically from Makefile.in by configure.
-# Un*x Makefile for GNU tar program.
-# Copyright (C) 1991 Free Software Foundation, Inc.
-
-# This program is free software; you can redistribute
-# it and/or modify it under the terms of the GNU
-# General Public License …
-…
-…
-
-SHELL = /bin/sh
-
-#### Start of system configuration section. ####
-
-srcdir = .
-
-# If you use gcc, you should either run the
-# fixincludes script that comes with it or else use
-# gcc with the -traditional option.  Otherwise ioctl
-# calls will be compiled incorrectly on some systems.
-CC = gcc -O
-YACC = bison -y
-INSTALL = /usr/local/bin/install -c
-INSTALLDATA = /usr/local/bin/install -c -m 644
-
-# Things you might add to DEFS:
-# -DSTDC_HEADERS        If you have ANSI C headers and
-#                       libraries.
-# -DPOSIX               If you have POSIX.1 headers and
-#                       libraries.
-# -DBSD42               If you have sys/dir.h (unless
-#                       you use -DPOSIX), sys/file.h,
-#                       and st_blocks in `struct stat'.
-# -DUSG                 If you have System V/ANSI C
-#                       string and memory functions
-#                       and headers, sys/sysmacros.h,
-#                       fcntl.h, getcwd, no valloc,
-#                       and ndir.h (unless
-#                       you use -DDIRENT).
-# -DNO_MEMORY_H         If USG or STDC_HEADERS but do not
-#                       include memory.h.
-# -DDIRENT              If USG and you have dirent.h
-#                       instead of ndir.h.
-# -DSIGTYPE=int         If your signal handlers
-#                       return int, not void.
-# -DNO_MTIO             If you lack sys/mtio.h
-#                       (magtape ioctls).
-# -DNO_REMOTE           If you do not have a remote shell
-#                       or rexec.
-# -DUSE_REXEC           To use rexec for remote tape
-#                       operations instead of
-#                       forking rsh or remsh.
-# -DVPRINTF_MISSING     If you lack vprintf function
-#                       (but have _doprnt).
-# -DDOPRNT_MISSING      If you lack _doprnt function.
-#                       Also need to define
-#                       -DVPRINTF_MISSING.
-# -DFTIME_MISSING       If you lack ftime system call.
-# -DSTRSTR_MISSING      If you lack strstr function.
-# -DVALLOC_MISSING      If you lack valloc function.
-# -DMKDIR_MISSING       If you lack mkdir and
-#                       rmdir system calls.
-# -DRENAME_MISSING      If you lack rename system call.
-# -DFTRUNCATE_MISSING   If you lack ftruncate
-#                       system call.
-# -DV7                  On Version 7 Unix (not
-#                       tested in a long time).
-# -DEMUL_OPEN3          If you lack a 3-argument version
-#                       of open, and want to emulate it
-#                       with system calls you do have.
-# -DNO_OPEN3            If you lack the 3-argument open
-#                       and want to disable the tar -k
-#                       option instead of emulating open.
-# -DXENIX               If you have sys/inode.h
-#                       and need it 94 to be included.
-
-DEFS =  -DSIGTYPE=int -DDIRENT -DSTRSTR_MISSING \
-        -DVPRINTF_MISSING -DBSD42
-# Set this to rtapelib.o unless you defined NO_REMOTE,
-# in which case make it empty.
-RTAPELIB = rtapelib.o
-LIBS =
-DEF_AR_FILE = /dev/rmt8
-DEFBLOCKING = 20
-
-CDEBUG = -g
-CFLAGS = $(CDEBUG) -I. -I$(srcdir) $(DEFS) \
-        -DDEF_AR_FILE=\"$(DEF_AR_FILE)\" \
-        -DDEFBLOCKING=$(DEFBLOCKING)
-LDFLAGS = -g
-
-prefix = /usr/local
-# Prefix for each installed program,
-# normally empty or `g'.
-binprefix =
-
-# The directory to install tar in.
-bindir = $(prefix)/bin
-
-# The directory to install the info files in.
-infodir = $(prefix)/info
-
-#### End of system configuration section. ####
-
-SRCS_C  = tar.c create.c extract.c buffer.c   \
-          getoldopt.c update.c gnu.c mangle.c \
-          version.c list.c names.c diffarch.c \
-          port.c wildmat.c getopt.c getopt1.c \
-          regex.c
-SRCS_Y  = getdate.y
-SRCS    = $(SRCS_C) $(SRCS_Y)
-OBJS    = $(SRCS_C:.c=.o) $(SRCS_Y:.y=.o) $(RTAPELIB)
-
-AUX =   README COPYING ChangeLog Makefile.in  \
-        makefile.pc configure configure.in \
-        tar.texinfo tar.info* texinfo.tex \
-        tar.h port.h open3.h getopt.h regex.h \
-        rmt.h rmt.c rtapelib.c alloca.c \
-        msd_dir.h msd_dir.c tcexparg.c \
-        level-0 level-1 backup-specs testpad.c
-
-.PHONY: all
-all:    tar rmt tar.info
-
-tar:    $(OBJS)
-        $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LIBS)
-
-rmt:    rmt.c
-        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ rmt.c
-
-tar.info: tar.texinfo
-        makeinfo tar.texinfo
-
-.PHONY: install
-install: all
-        $(INSTALL) tar $(bindir)/$(binprefix)tar
-        -test ! -f rmt || $(INSTALL) rmt /etc/rmt
-        $(INSTALLDATA) $(srcdir)/tar.info* $(infodir)
-
-$(OBJS): tar.h port.h testpad.h
-regex.o buffer.o tar.o: regex.h
-# getdate.y has 8 shift/reduce conflicts.
-
-testpad.h: testpad
-        ./testpad
-
-testpad: testpad.o
-        $(CC) -o $@ testpad.o
-
-TAGS:   $(SRCS)
-        etags $(SRCS)
-
-.PHONY: clean
-clean:
-        rm -f *.o tar rmt testpad testpad.h core
-
-.PHONY: distclean
-distclean: clean
-        rm -f TAGS Makefile config.status
-
-.PHONY: realclean
-realclean: distclean
-        rm -f tar.info*
-
-.PHONY: shar
-shar: $(SRCS) $(AUX)
-        shar $(SRCS) $(AUX) | compress \
-          > tar-`sed -e '/version_string/!d' \
-                     -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
-                     -e q
-                     version.c`.shar.Z
-
-.PHONY: dist
-dist: $(SRCS) $(AUX)
-        echo tar-`sed \
-             -e '/version_string/!d' \
-             -e 's/[^0-9.]*\([0-9.]*\).*/\1/' \
-             -e q
-             version.c` > .fname
-        -rm -rf `cat .fname`
-        mkdir `cat .fname`
-        ln $(SRCS) $(AUX) `cat .fname`
-        tar chZf `cat .fname`.tar.Z `cat .fname`
-        -rm -rf `cat .fname` .fname
-
-tar.zoo: $(SRCS) $(AUX)
-        -rm -rf tmp.dir
-        -mkdir tmp.dir
-        -rm tar.zoo
-        for X in $(SRCS) $(AUX) ; do \
-            echo $$X ; \
-            sed 's/$$/^M/' $$X \
-            > tmp.dir/$$X ; done
-        cd tmp.dir ; zoo aM ../tar.zoo *
-        -rm -rf tmp.dir
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-markdown.html b/docs/_style/prism-master/examples/prism-markdown.html deleted file mode 100644 index a57eaeaf..00000000 --- a/docs/_style/prism-master/examples/prism-markdown.html +++ /dev/null @@ -1,86 +0,0 @@ -

Titles

-
Title 1
-==
-
-Title 2
--------
-
-# Title 1
-## Title 2
-### Title 3
-#### Title 4
-##### Title 5
-###### Title 6
-
- -

Bold and italic

-
*Italic*
-**Bold on
-multiple lines**
-*Italic on
-multiple lines too*
-__It also works with underscores__
-_It also works with underscores_
-
-__An empty line
-
-is not allowed__
-
- -

Links

-
[Prism](http://www.prismjs.com)
-[Prism](http://www.prismjs.com "Prism")
-
-[prism link]: http://www.prismjs.com (Prism)
-[Prism] [prism link]
-
- -

Lists and quotes

-
* This is
-* an unordered list
-
-1. This is an
-2. ordered list
-
-* *List item in italic*
-* **List item in bold**
-* [List item as a link](http://example.com "This is an example")
-
-> This is a quotation
->> With another quotation inside
-> _italic here_, __bold there__
-> And a [link](http://example.com)
-
- -

Code

-
Inline code between backticks `<p>Paragraph</p>`
-
-    some_code(); /* Indented
-    with four spaces */
-
-	some_code(); /* Indented
-	with a tab */
-
- -

Raw HTML

-
> This is a quotation
-> Containing <strong>raw HTML</strong>
-
-<p>*Italic text inside HTML tag*</p>
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nesting of elements is not fully supported

-
_ **bold** inside italic DOESN'T work _
-__ but *italic* inside bold DOES work __
-
-[Link partially *italic* DOESN'T work](http://example.com)
-_ [But link inside italic DOES work](http://example.com) _
-
-[Link partially **bold** DOESN'T work](http://example.com)
-__ [But link inside bold DOES work](http://example.com) __
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-markup.html b/docs/_style/prism-master/examples/prism-markup.html deleted file mode 100644 index ac00e7f6..00000000 --- a/docs/_style/prism-master/examples/prism-markup.html +++ /dev/null @@ -1,77 +0,0 @@ -

Empty tag

-
<p></p>
- -

Tag that spans multiple lines

-
<p
->hello!
-</p>
- -

Name-attribute pair

-
<p></p>
- -

Name-attribute pair without quotes

-
<p class=prism></p>
- -

Attribute without value

-
<p data-foo></p>
-<p data-foo ></p>
-
- -

Namespaces

-
<html:p foo:bar="baz" foo:weee></html:p>
- -

XML prolog

-
<?xml version="1.0" encoding="utf-8"?>
-<svg></svg>
- -

DOCTYPE

-
<!DOCTYPE html>
-<html></html>
- -

CDATA section

-
<ns1:description><![CDATA[
-  CDATA is <not> magical.
-]]></ns1:description>
- -

Comment

-
<!-- I'm a comment -->
-And i'm not
- -

Entities

-
&amp; &#x2665; &#160; &#x152;
- -

Embedded JS and CSS

-
<!DOCTYPE html>
-<html lang="en">
-<head>
-	<meta charset="utf-8" />
-	<title>I can haz embedded CSS and JS</title>
-	<style>
-		@media print {
-			p { color: red !important; }
-		}
-	</style>
-</head>
-<body>
-	<h1>I can haz embedded CSS and JS</h1>
-	<script>
-	if (true) {
-		console.log('foo');
-	}
-	</script>
-
-</body>
-</html>
- -

Invalid HTML

-
<l </ul>
- -

Multi-line attribute values

-
<p title="foo
-bar
-baz">
- -

XML tags with non-ASCII characters

-
<Läufer>foo</Läufer>
-<tag läufer="läufer">bar</tag>
-<läufer:tag>baz</läufer:tag>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-matlab.html b/docs/_style/prism-master/examples/prism-matlab.html deleted file mode 100644 index e78abe36..00000000 --- a/docs/_style/prism-master/examples/prism-matlab.html +++ /dev/null @@ -1,52 +0,0 @@ -

Strings

-
myString = 'Hello, world';
-otherString = 'You''re right';
- -

Comments

-
% Single line comment
-%{ Multi-line
-comment }%
- -

Numbers

-
x = 325.499
-realmax + .0001e+308
-e = 1 - 3*(4/3 - 1)
-b = 1e-16 + 1 - 1e-16;
-x = 2 + 3i;
-z =
-   4.7842 -1.0921i   0.8648 -1.5931i   1.2616 -2.2753i
-   2.6130 -0.0941i   4.8987 -2.3898i   4.3787 -3.7538i
-   4.4007 -7.1512i   1.3572 -5.2915i   3.6865 -0.5182i
-
- -

Control flow

-
if rem(a, 2) == 0
-    disp('a is even')
-    b = a/2;
-end
-switch dayString
-   case 'Monday'
-      disp('Start of the work week')
-   case 'Tuesday'
-      disp('Day 2')
-   case 'Wednesday'
-      disp('Day 3')
-   case 'Thursday'
-      disp('Day 4')
-   case 'Friday'
-      disp('Last day of the work week')
-   otherwise
-      disp('Weekend!')
-end
-n = 1;
-nFactorial = 1;
-while nFactorial < 1e100
-    n = n + 1;
-    nFactorial = nFactorial * n;
-end
- -

Functions

-
q = integral(sqr,0,1);
-y = parabola(x)
-mygrid = @(x,y) ndgrid((-x:x/c:x),(-y:y/c:y));
-[x,y] = mygrid(pi,2*pi);
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-mel.html b/docs/_style/prism-master/examples/prism-mel.html deleted file mode 100644 index e8879756..00000000 --- a/docs/_style/prism-master/examples/prism-mel.html +++ /dev/null @@ -1,137 +0,0 @@ -

Comments

-
// This is a comment
- -

Strings

-
"This is a string"
-"foo \"bar\" baz"
- -

Numbers

-
42
-3.14159
-0xA2F
- -

Variables

-
$x
-$floaty5000
-$longDescriptiveName
-$name_with_underscores
-$_line
-
-float $param;
-int $counter;
-string $name;
-vector $position;
- -

Arrays, vectors and matrices

-
string $array[3] = {"first\n", "second\n", "third\n"};
-print($array[0]); // Prints "first\n"
-print($array[1]); // Prints "second\n"
-print($array[2]); // Prints "third\n"
-
-vector $roger = <<3.0, 7.7, 9.1>>;
-vector $more = <<4.5, 6.789, 9.12356>>;
-// Assign a vector to variable $test:
-vector $test = <<3.0, 7.7, 9.1>>;
-$test = <<$test.x, 5.5, $test.z>>
-// $test is now <<3.0, 5.5, 9.1>>
-
-matrix $a3[3][4] = <<2.5, 4.5, 3.25, 8.05;
- 1.12, 1.3, 9.5, 5.2;
- 7.23, 6.006, 2.34, 4.67>>
- -

Commands

-
pickWalk -d down;
-string $mySelection[] = `ls -selection`;
-
-setAttr ($mySelection[0]+".particleRenderType") 5;
-
-addAttr -is true -ln "spriteTwist" -at "float" -min -180 -max 180 -dv 0.0 blue_nParticleShape;
- -

Full example

-
// From http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Example_scripts_Dynamics_Time_Playback
-// Alias Script File
-// MODIFY THIS AT YOUR OWN RISK
-//
-// Creation Date: 8 May 1996
-// Author: rh
-//
-// Description:
-// Playback from frame 0 to frame <n> and return the
-// 		the playback rate in frames/sec. If a negative frame
-// count is given, this indicates silent mode. In silent
-// mode, no output is printed.
-//
-// This version is intended for use in batch tests of dynamics.
-// It requests particle and rigid body positions every frame.
-//
-// RETURN
-// Frame rate in frames/sec
-//
-global proc float dynTimePlayback( float $frames )
-{
- int $silent;
- // Get the list of particle shapes.
- //
- string $particleObjects[] = `ls -type particle`;
- int $particleCount = size( $particleObjects );
- // Get the list of transforms.
- // This will include rigid bodies.
- //
- string $transforms[] = `ls -tr`;
- int $trCount = size( $transforms );
- 	// Check for negative $frames. This indicates
- // $silent mode.
- //
- if ($frames < 0)
- {
- $silent = 1;
- $frames = -$frames;
- }
- else
- {
- $silent = 0;
- }
- // Setup the playback options.
- //
- playbackOptions -min 1 -max $frames -loop "once";
- currentTime -edit 0;
- // Playback the animation using the timerX command
- // to compute the $elapsed time.
- //
- float $startTime, $elapsed;
- $startTime = `timerX`;
-// play -wait;
- int $i;
- for ($i = 1; $i < $frames; $i++ )
- {
- // Set time
- //
- currentTime -e $i;
- int $obj;
- // Request count for every particle object.
- //
- for ($obj = 0; $obj < $particleCount; $obj++)
- {
-			string $cmd = "getAttr " + $particleObjects[$obj]+".count";
- eval( $cmd );
- }
- // Request position for every transform
-		// (includes every rigid body).
- //
- for ($obj = 0; $obj < $trCount; $obj++)
- {
- string $cmd = "getAttr " + $transforms[$obj]+".translate";
- eval ($cmd);
- }
- }
- $elapsed = `timerX -st $startTime`;
- // Compute the playback frame $rate. Print results.
- //
- float $rate = ($elapsed == 0 ? 0.0 : $frames / $elapsed) ;
- if ( ! $silent)
- {
- print( "Playback time: " + $elapsed + " secs\n" );
- print( "Playback $rate: " + $rate + " $frames/sec\n" );
- }
- return ( $rate );
-} // timePlayback //
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-mizar.html b/docs/_style/prism-master/examples/prism-mizar.html deleted file mode 100644 index 98d626c0..00000000 --- a/docs/_style/prism-master/examples/prism-mizar.html +++ /dev/null @@ -1,45 +0,0 @@ -

Full example

- -
:: Example from http://webdocs.cs.ualberta.ca/~piotr/Mizar/Dagstuhl97/
-environ
-vocabulary SCM;
-constructors ARYTHM, PRE_FF, NAT_1, REAL_1;
-notation ARYTHM, PRE_FF, NAT_1;
-requirements ARYTHM;
-theorems REAL_1, PRE_FF, NAT_1, AXIOMS, CQC_THE1;
-schemes NAT_1;
-begin
-
-P: for k being Nat
-	st for n being Nat st n < k holds Fib (n+1) ≥ n
-		holds Fib (k+1) ≥ k
-proof let k be Nat; assume
-IH: for n being Nat st n < k holds Fib (n+1) ≥ n;
-	per cases;
-		suppose k ≤ 1; then k = 0 or k = 0+1 by CQC_THE1:2;
-			hence Fib (k+1) ≥ k by PRE_FF:1;
-		suppose 1 < k; then
-			1+1 ≤ k by NAT_1:38; then
-			consider m being Nat such that
-		A: k = 1+1+m by NAT_1:28;
-			thus Fib (k+1) ≥ k proof
-				per cases by NAT_1:19;
-				suppose S1: m = 0;
-					Fib (0+1+1+1) = Fib(0+1) + Fib(0+1+1) by PRE_FF:1
-					              = 1 + 1 by PRE_FF:1;
-					hence Fib (k+1) ≥ k by A, S1;
-				suppose m > 0; then
-					m+1 > 0+1 by REAL_1:59; then
-					m ≥ 1 by NAT_1:38; then
-				B: m+(m+1) ≥ m+1+1 by REAL_1:49;
-				C: k = m+1+1 by A, AXIOMS:13;
-				   m < m+1 & m+1 < m+1+1 by REAL_1:69; then
-				   m < k & m+1 < k by C, AXIOMS:22; then
-				D: Fib (m+1) ≥ m & Fib (m+1+1) ≥ m+1 by IH;
-				   Fib (m+1+1+1) = Fib (m+1) + Fib (m+1+1) by PRE_FF:1; then
-				   Fib (m+1+1+1) ≥ m+(m+1) by D, REAL_1:55;
-		hence Fib(k+1) ≥ k by C, B, AXIOMS:22;
-	end;
-end;
-
-for n being Nat holds Fib(n+1) ≥ n from Comp_Ind(P);
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-monkey.html b/docs/_style/prism-master/examples/prism-monkey.html deleted file mode 100644 index 6716a9a4..00000000 --- a/docs/_style/prism-master/examples/prism-monkey.html +++ /dev/null @@ -1,74 +0,0 @@ -

Comments

-
' This is a comment
-
-#Rem            ' This is the start of a comment block
-Some comment    ' We are inside the comment block
-#End
- -

Strings

-
"Hello World"
-"~qHello World~q"
-"~tIndented~n"
- -

Numbers

-
0
-1234
-$3D0DEAD
-$CAFEBABE
-
-.0
-0.0
-.5
-0.5
-1.0
-1.5
-1.00001
-3.14159265
- -

Variable types

-
Local myVariable:Bool = True
-Local myVariable? = True
-Local myVariable:Int = 1024
-Local myVariable% = 1024
-Local myVariable:Float = 3.141516
-Local myVariable# = 3.141516
-Local myVariable:String = "Hello world"
-Local myVariable$ = "Hello world"
- -

Full example

-
Import mojo
-
-Class MyApp Extends App
-
-    Method OnCreate()
-
-        SetUpdateRate 60
-
-    End
-
-    Method OnRender()
-
-        Local date:=GetDate()
-
-        Local months:=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
-
-        Local day:=("0"+date[2])[-2..]
-        Local month:=months[date[1]-1]
-        Local year:=date[0]
-        Local hour:=("0"+date[3])[-2..]
-        Local min:=("0"+date[4])[-2..]
-        Local sec:=("0"+date[5])[-2..] + "." + ("00"+date[6])[-3..]
-
-        Local now:=hour+":"+min+":"+sec+"  "+day+" "+month+" "+year
-
-        Cls
-        DrawText now,DeviceWidth/2,DeviceHeight/2,.5,.5
-    End
-
-End
-
-Function Main()
-
-    New MyApp
-
-End
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-n4js.html b/docs/_style/prism-master/examples/prism-n4js.html deleted file mode 100644 index 19e5a6d5..00000000 --- a/docs/_style/prism-master/examples/prism-n4js.html +++ /dev/null @@ -1,114 +0,0 @@ -

Keywords

-

-class C {..}
-interface I {..}
-
-foo(c: C, i: I) {
-    c instanceof C; // ok
-    c instanceof I; // ok
-}
-
- -

Annotations

-

-// Final Methods
-@Final
-private tasks = new Map<string,Task>();
-
-// Redefinition of Members
-@Override
-public async size(): int {
-  …
-}
-
-// Dependency Injection
-@Binder
-@Bind(Storage,StorageInMemory)
-class InMemoryBinder {}
-
-@GenerateInjector @UseBinder(InMemoryBinder)
-export public class TaskManagerTest {
-  …
-}
-
- -

Full example

-

-// A Web User Interface in HTML
-// NOTE: requires full example project bundled with N4JS IDE to run.
-
-import { TaskManager } from "TaskManager";
-import {Application, Response } from "express";
-import express from "express";
-import { Todo } from "model";
-
-
-export class WebUI {
-
-     private app: Application;
-
-     @Inject
-     private manager: TaskManager;
-
-     public start() {
-
-          this.app = express();
-
-          this.app.get('/', async (req, res) => {
-               let page = await this.renderHomePage();
-               res.send(page);
-          });
-
-          this.app.get("/clear", async (req, res) => {
-               await this.manager.clear();
-               redirect(res, '/');
-          });
-
-          this.app.get("/create", async (req, res) => {
-               let values = req.query as ~Object with {type: string, label: string};
-               if (values && values.type === 'Todo' && values.label && values.label.length > 0) {
-                    await this.manager.createTodo(values.label);
-               }
-               redirect(res, '/');
-          });
-
-          this.app.listen(4000, '0.0.0.0', 511, function() {
-               console.log("HTML server listening on http://localhost:4000/");
-          });
-     }
-
-     protected async renderHomePage(): string {
-          let tasks = await this.manager.getTasks();
-          let todos = tasks.filter((task) => task instanceof Todo);
-          return `
-
-<html>
-<body>
-     Your to-do's:
-     <ul>
-     ${
-          todos.length === 0 ? '<li><em>none</em></li>\n'
-          : todos.map((task) =>
-               '<li>' + task.label + ' <small>(id: ' + task.id + ')</small></li>'
-          ).join('\n')
-     }
-     </ul>
-     <hr/>
-     <form action="http://github.com/create" method="get">
-     <input type="hidden" name="type" value="Todo">
-     Label: <input type="text" name="label"><br>
-     <input type="submit" value="Create Todo">
-     </form>
-     <hr/>
-     <a href="http://github.com/clear">[Clear All]</a>
-</body>
-</html>
-`;
-     }
-}
-
-function redirect(res: Response, url: string) {
-     res.header('Cache-Control', 'no-cache');
-     res.redirect(301, url);
-}
-
diff --git a/docs/_style/prism-master/examples/prism-nasm.html b/docs/_style/prism-master/examples/prism-nasm.html deleted file mode 100644 index c1b7c926..00000000 --- a/docs/_style/prism-master/examples/prism-nasm.html +++ /dev/null @@ -1,74 +0,0 @@ -

Comments

-
; This is a comment
- -

Labels

-
label1:     ; a non-local label
-.local:     ; this is really label1.local
-..@foo:     ; this is a special symbol
-label2:     ; another non-local label
-.local:     ; this is really label2.local
-
- -

Registers

-
st0
-st1
-ax
-rax
-zmm4
- -

Strings

-

-mov eax,'abcd'
-
-db    'hello'               ; string constant
-db    'h','e','l','l','o'   ; equivalent character constants
-dd    'ninechars'           ; doubleword string constant
-dd    'nine','char','s'     ; becomes three doublewords
-db    'ninechars',0,0,0     ; and really looks like this
-
-db `\u263a`            ; UTF-8 smiley face
-db `\xe2\x98\xba`      ; UTF-8 smiley face
-db 0E2h, 098h, 0BAh    ; UTF-8 smiley face
-
- -

Numbers

-
mov     ax,200          ; decimal
-mov     ax,0200         ; still decimal
-mov     ax,0200d        ; explicitly decimal
-mov     ax,0d200        ; also decimal
-mov     ax,0c8h         ; hex
-mov     ax,$0c8         ; hex again: the 0 is required
-mov     ax,0xc8         ; hex yet again
-mov     ax,0hc8         ; still hex
-mov     ax,310q         ; octal
-mov     ax,310o         ; octal again
-mov     ax,0o310        ; octal yet again
-mov     ax,0q310        ; octal yet again
-mov     ax,11001000b    ; binary
-
-db    -0.2                    ; "Quarter precision"
-dw    -0.5                    ; IEEE 754r/SSE5 half precision
-dd    1.2                     ; an easy one
-dd    0x1p+2                  ; 1.0x2^2 = 4.0
-dq    0x1p+32                 ; 1.0x2^32 = 4 294 967 296.0
-dq    1.e10                   ; 10 000 000 000.0
-dq    1.e+10                  ; synonymous with 1.e10
-dq    1.e-10                  ; 0.000 000 000 1
-dt    3.141592653589793238462 ; pi
-do    1.e+4000                ; IEEE 754r quad precision
-
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Numbers with underscores

-
mov     ax,1100_1000b
-mov     ax,1100_1000y
-mov     ax,0b1100_1000
-mov     ax,0y1100_1000
-
-dd    1.222_222_222
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-nginx.html b/docs/_style/prism-master/examples/prism-nginx.html deleted file mode 100644 index 49d14664..00000000 --- a/docs/_style/prism-master/examples/prism-nginx.html +++ /dev/null @@ -1,25 +0,0 @@ -

Comments

-
# This is a comment
- -

Variables

-
fastcgi_param SERVER_NAME $server_name;
- -

Server Block

-

-server { # simple reverse-proxy
-  listen       80;
-  server_name  domain2.com www.domain2.com;
-  access_log   logs/domain2.access.log  main;
-  
-  # serve static files
-  
-  location ~ ^/(images|javascript|js|css|flash|media|static)/  {
-    root    /var/www/virtual/big.server.com/htdocs;
-    expires 30d;
-  }
-
-  # pass requests for dynamic content to rails/turbogears/zope, et al
-  location / {
-    proxy_pass      http://127.0.0.1:8080;
-  }
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-nim.html b/docs/_style/prism-master/examples/prism-nim.html deleted file mode 100644 index c6611259..00000000 --- a/docs/_style/prism-master/examples/prism-nim.html +++ /dev/null @@ -1,222 +0,0 @@ -

Comments

-
# This is a comment
- -

Strings

-
"This is a string."
-"This is a string with \"quotes\" in it."
-"""This is
-a "multi-line"
-string."""
-""""A long string within quotes.""""
-R"This is a raw string."
-r"Some ""quotes"" inside a raw string."
-r"""Raw strings
-can also be multi-line."""
-foo"This is a generalized raw string literal."
-bar"""This is also
-a generalized raw string literal."""
- -

Characters

-
'a'
-'\''
-'\t'
-'\15'
-'\xFC'
- -

Numbers

-
42
-0xaf
-0xf_2_c
-0o07
-0b1111_0000
-0B0_10001110100_0000101001000111101011101111111011000101001101001001'f64
-9_000'u
-32.
-32.1f32
-32.e-5
-32.2e+2
-2'i16
-2i16
-0xfe'f32
- -

Full example

-
# Example from http://nim-by-example.github.io/oop_macro/
-import macros
-
-macro class*(head: expr, body: stmt): stmt {.immediate.} =
-  # The macro is immediate so that it doesn't
-  # resolve identifiers passed to it
-
-  var typeName, baseName: NimNode
-
-  if head.kind == nnkIdent:
-    # `head` is expression `typeName`
-    # echo head.treeRepr
-    # --------------------
-    # Ident !"Animal"
-    typeName = head
-
-  elif head.kind == nnkInfix and $head[0] == "of":
-    # `head` is expression `typeName of baseClass`
-    # echo head.treeRepr
-    # --------------------
-    # Infix
-    #   Ident !"of"
-    #   Ident !"Animal"
-    #   Ident !"RootObj"
-    typeName = head[1]
-    baseName = head[2]
-
-  else:
-    quit "Invalid node: " & head.lispRepr
-
-  # echo treeRepr(body)
-  # --------------------
-  # StmtList
-  #   VarSection
-  #     IdentDefs
-  #       Ident !"name"
-  #       Ident !"string"
-  #       Empty
-  #     IdentDefs
-  #       Ident !"age"
-  #       Ident !"int"
-  #       Empty
-  #   MethodDef
-  #     Ident !"vocalize"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"string"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       StrLit ...
-  #   MethodDef
-  #     Ident !"age_human_yrs"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"int"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       DotExpr
-  #         Ident !"this"
-  #         Ident !"age"
-
-  # create a new stmtList for the result
-  result = newStmtList()
-
-  # var declarations will be turned into object fields
-  var recList = newNimNode(nnkRecList)
-
-  # Iterate over the statements, adding `this: T`
-  # to the parameters of functions
-  for node in body.children:
-    case node.kind:
-
-      of nnkMethodDef, nnkProcDef:
-        # inject `this: T` into the arguments
-        let p = copyNimTree(node.params)
-        p.insert(1, newIdentDefs(ident"this", typeName))
-        node.params = p
-        result.add(node)
-
-      of nnkVarSection:
-        # variables get turned into fields of the type.
-        for n in node.children:
-          recList.add(n)
-
-      else:
-        result.add(node)
-
-  # The following prints out the AST structure:
-  #
-  # import macros
-  # dumptree:
-  #   type X = ref object of Y
-  #     z: int
-  # --------------------
-  # TypeSection
-  #   TypeDef
-  #     Ident !"X"
-  #     Empty
-  #     RefTy
-  #       ObjectTy
-  #         Empty
-  #         OfInherit
-  #           Ident !"Y"
-  #         RecList
-  #           IdentDefs
-  #             Ident !"z"
-  #             Ident !"int"
-  #             Empty
-
-  result.insert(0,
-    if baseName == nil:
-      quote do:
-        type `typeName` = ref object of RootObj
-    else:
-      quote do:
-        type `typeName` = ref object of `baseName`
-  )
-  # Inspect the tree structure:
-  #
-  # echo result.treeRepr
-  # --------------------
-  # StmtList
-  #   StmtList
-  #     TypeSection
-  #       TypeDef
-  #         Ident !"Animal"
-  #         Empty
-  #         RefTy
-  #           ObjectTy
-  #             Empty
-  #             OfInherit
-  #               Ident !"RootObj"
-  #             Empty   <= We want to replace this
-  #   MethodDef
-  #   ...
-
-  result[0][0][0][2][0][2] = recList
-
-  # Lets inspect the human-readable version of the output
-  # echo repr(result)
-  # Output:
-  #  type
-  #    Animal = ref object of RootObj
-  #      name: string
-  #      age: int
-  #
-  #  method vocalize(this: Animal): string =
-  #    "..."
-  #
-  #  method age_human_yrs(this: Animal): int =
-  #    this.age
-
-# ---
-
-class Animal of RootObj:
-  var name: string
-  var age: int
-  method vocalize: string = "..."
-  method age_human_yrs: int = this.age # `this` is injected
-
-class Dog of Animal:
-  method vocalize: string = "woof"
-  method age_human_yrs: int = this.age * 7
-
-class Cat of Animal:
-  method vocalize: string = "meow"
-
-# ---
-
-var animals: seq[Animal] = @[]
-animals.add(Dog(name: "Sparky", age: 10))
-animals.add(Cat(name: "Mitten", age: 10))
-
-for a in animals:
-  echo a.vocalize()
-  echo a.age_human_yrs()
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-nix.html b/docs/_style/prism-master/examples/prism-nix.html deleted file mode 100644 index 90157769..00000000 --- a/docs/_style/prism-master/examples/prism-nix.html +++ /dev/null @@ -1,46 +0,0 @@ -

Comments

-
#
-# Single line comment
-/* Multi-line
-comment */
- -

String

-
""
-"foo\"bar"
-"foo
-bar"
-
-''''
-''foo'''bar''
-''
-foo
-bar
-''
- -

String interpolation

-
"foo${42}bar"
-"foo\${42}bar" # This is not interpolated
-''foo${42}bar''
-''foo''${42}bar'' # This is not interpolated
- -

URLs and paths

-
ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz
-http://example.org/foo.tar.bz2
-/bin/sh
-./builder.sh
-~/foo.bar
- -

Integers, booleans and null

-
0
-42
-
-true
-false
-
-null
- -

Builtin functions

-
name = baseNameOf (toString url);
-imap =
-	if builtins ? genList then
-		f: list: genList (n: f (n + 1) (elemAt list n)) (length list)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-nsis.html b/docs/_style/prism-master/examples/prism-nsis.html deleted file mode 100644 index 193791bb..00000000 --- a/docs/_style/prism-master/examples/prism-nsis.html +++ /dev/null @@ -1,18 +0,0 @@ -

Comments

-
; Single line comment
-# Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
- -

Variables

-
LicenseLangString myLicenseData ${LANG_ENGLISH} "bigtest.nsi"
-LicenseData $(myLicenseData)
-StrCmp $LANGUAGE ${LANG_ENGLISH} 0 +2
- -

Compiler commands

-
!define VERSION "1.0.3"
-!insertmacro MyFunc ""
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-objectivec.html b/docs/_style/prism-master/examples/prism-objectivec.html deleted file mode 100644 index 025a8d71..00000000 --- a/docs/_style/prism-master/examples/prism-objectivec.html +++ /dev/null @@ -1,44 +0,0 @@ -

Full example

-
#import <UIKit/UIKit.h>
-#import "Dependency.h"
-
-@protocol WorldDataSource
-@optional
-- (NSString*)worldName;
-@required
-- (BOOL)allowsToLive;
-@end
-
-@interface Test : NSObject <HelloDelegate, WorldDataSource> {
-  NSString *_greeting;
-}
-
-@property (nonatomic, readonly) NSString *greeting;
-- (IBAction) show;
-@end
-
-@implementation Test
-
-@synthesize test=_test;
-
-+ (id) test {
-  return [self testWithGreeting:@"Hello, world!\nFoo bar!"];
-}
-
-+ (id) testWithGreeting:(NSString*)greeting {
-  return [[[self alloc] initWithGreeting:greeting] autorelease];
-}
-
-- (id) initWithGreeting:(NSString*)greeting {
-  if ( (self = [super init]) ) {
-    _greeting = [greeting retain];
-  }
-  return self;
-}
-
-- (void) dealloc {
-  [_greeting release];
-  [super dealloc];
-}
-
-@end
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-ocaml.html b/docs/_style/prism-master/examples/prism-ocaml.html deleted file mode 100644 index 0534b1d1..00000000 --- a/docs/_style/prism-master/examples/prism-ocaml.html +++ /dev/null @@ -1,59 +0,0 @@ -

Comments

-
(* Simple comment *)
-(* Multi-line
-comment *)
- -

Numbers

-
42
-3.14159
-42.
-2.4E+2
-10_452_102
-0xf4 0xff_10_41
-0o427
-0b1100_1111_0000
- -

Strings and characters

-
"Simple string."
-"String with \"quotes\" in it."
-'c' `c`
-'\'' `\``
-'\123' `\123`
-'\xf4'
- -

Full example

-
module Make_interval(Endpoint : Comparable) = struct
-
-    type t = | Interval of Endpoint.t * Endpoint.t
-             | Empty
-
-    (** [create low high] creates a new interval from [low] to
-        [high].  If [low > high], then the interval is empty *)
-    let create low high =
-      if Endpoint.compare low high > 0 then Empty
-      else Interval (low,high)
-
-    (** Returns true iff the interval is empty *)
-    let is_empty = function
-      | Empty -> true
-      | Interval _ -> false
-
-    (** [contains t x] returns true iff [x] is contained in the
-        interval [t] *)
-    let contains t x =
-      match t with
-      | Empty -> false
-      | Interval (l,h) ->
-        Endpoint.compare x l >= 0 && Endpoint.compare x h <= 0
-
-    (** [intersect t1 t2] returns the intersection of the two input
-        intervals *)
-    let intersect t1 t2 =
-      let min x y = if Endpoint.compare x y <= 0 then x else y in
-      let max x y = if Endpoint.compare x y >= 0 then x else y in
-      match t1,t2 with
-      | Empty, _ | _, Empty -> Empty
-      | Interval (l1,h1), Interval (l2,h2) ->
-        create (max l1 l2) (min h1 h2)
-
-  end ;;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-opencl.html b/docs/_style/prism-master/examples/prism-opencl.html deleted file mode 100644 index f901230b..00000000 --- a/docs/_style/prism-master/examples/prism-opencl.html +++ /dev/null @@ -1,83 +0,0 @@ -

- To use this language, use the class "language-opencl" for OpenCL kernel code. - Host code is automatically highlighted in "language-c" - respectively "language-cpp" classes. -

- -

OpenCL host code

-
// OpenCL functions, constants, etc. are also highlighted in OpenCL host code in the c or cpp language
-cl::Event KernelFilterImages::runSingle(const cl::Image2D& imgSrc, SPImage2D& imgDst)
-{
-	const size_t rows = imgSrc.getImageInfo();
-	const size_t cols = imgSrc.getImageInfo();
-
-	ASSERT(rows > 0 && cols > 0, "The image object seems to be invalid, no rows/cols set");
-	ASSERT(imgSrc.getImageInfo().image_channel_data_type == CL_FLOAT, "Only float type images are supported");
-	ASSERT(imgSrc.getInfo() == CL_MEM_READ_ONLY || imgSrc.getInfo() == CL_MEM_READ_WRITE, "Can't read the input image");
-
-	imgDst = std::make_shared(*context, CL_MEM_READ_WRITE, cl::ImageFormat(CL_R, CL_FLOAT), cols, rows);
-
-	cl::Kernel kernel(*program, "filter_single");
-	kernel.setArg(0, imgSrc);
-	kernel.setArg(1, *imgDst);
-	kernel.setArg(2, bufferKernel1);
-	kernel.setArg(3, kernel1.rows);
-	kernel.setArg(4, kernel1.rows / 2);
-	kernel.setArg(5, kernel1.cols);
-	kernel.setArg(6, kernel1.cols / 2);
-	kernel.setArg(7, border);
-
-	cl::Event eventFilter;
-	const cl::NDRange global(cols, rows);
-	queue->enqueueNDRangeKernel(kernel, cl::NullRange, global, cl::NullRange, &events, &eventFilter);
-}
- -

OpenCL kernel code

-
// CLK_ADDRESS_CLAMP_TO_EDGE = aaa|abcd|ddd
-constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST;
-typedef float type_single;
-
-type_single filter_sum_single_3x3(read_only image2d_t imgIn,
-                                  constant float* filterKernel,
-                                  const int2 coordBase,
-                                  const int border)
-{
-    type_single sum = (type_single)(0.0f);
-    const int rows = get_image_height(imgIn);
-    const int cols = get_image_width(imgIn);
-    int2 coordCurrent;
-    int2 coordBorder;
-    float color;
-
-    // Image patch is row-wise accessed
-    // Filter kernel is centred in the middle
-    #pragma unroll
-    for (int y = -ROWS_HALF_3x3; y <= ROWS_HALF_3x3; ++y)       // Start at the top left corner of the filter
-    {
-        coordCurrent.y = coordBase.y + y;
-        #pragma unroll
-        for (int x = -COLS_HALF_3x3; x <= COLS_HALF_3x3; ++x)   // And end at the bottom right corner
-        {
-            coordCurrent.x = coordBase.x + x;
-            coordBorder = borderCoordinate(coordCurrent, rows, cols, border);
-            color = read_imagef(imgIn, sampler, coordBorder).x;
-
-            const int idx = (y + ROWS_HALF_3x3) * COLS_3x3 + x + COLS_HALF_3x3;
-            sum += color * filterKernel[idx];
-        }
-    }
-
-    return sum;
-}
-
-kernel void filter_single_3x3(read_only image2d_t imgIn,
-                              write_only image2d_t imgOut,
-                              constant float* filterKernel,
-                              const int border)
-{
-    int2 coordBase = (int2)(get_global_id(0), get_global_id(1));
-
-    type_single sum = filter_sum_single_3x3(imgIn, filterKernel, coordBase, border);
-
-    write_imagef(imgOut, coordBase, sum);
-}
diff --git a/docs/_style/prism-master/examples/prism-oz.html b/docs/_style/prism-master/examples/prism-oz.html deleted file mode 100644 index d70ec2ed..00000000 --- a/docs/_style/prism-master/examples/prism-oz.html +++ /dev/null @@ -1,89 +0,0 @@ -

Comments

-
%
-% Foobar
-
-/* Foo
-bar */
- -

Strings

-
""
-"Foo \"bar\" baz"
- -

Numbers

-
0
-42
-0154
-0xBadFace
-0B0101
-3.14159
-2e8
-3.E~7
-4.8E12
-&0
-&a
-&\n
-&\124
- -

Functions and procedures

-
proc {Max X Y Z}
-{Browse Z}
-f(M Y)
- -

Full example

-
proc {DisMember X Ys}
-   dis Ys = X|_ [] Yr in Ys = _|Yr {DisMember X Yr} end
-end
-
-class DataBase from BaseObject
-   attr d
-   meth init
-      d := {NewDictionary}
-   end
-   meth dic($) @d end
-   meth tell(I)
-      case {IsFree I.1} then
-         raise database(nonground(I)) end
-      else
-         Is = {Dictionary.condGet @d I.1 nil} in
-         {Dictionary.put @d I.1 {Append Is [I]}}
-      end
-   end
-   meth ask(I)
-      case {IsFree I} orelse {IsFree I.1} then
-         {DisMember I {Flatten {Dictionary.items @d}}}
-      else
-         {DisMember I {Dictionary.condGet @d I.1 nil}}
-      end
-   end
-   meth entries($)
-      {Dictionary.entries @d}
-   end
-end
-
-declare
-proc {Dynamic ?Pred}
-   Pred = {New DataBase init}
-end
-proc {Assert P I}
-   {P tell(I)}
-end
-proc {Query P I}
-   {P ask(I)}
-end
-
-EdgeP = {Dynamic}
-{ForAll
-[edge(1 2)
- edge(2 1)   % Cycle
- edge(2 3)
- edge(3 4)
- edge(2 5)
- edge(5 6)
- edge(4 6)
- edge(6 7)
- edge(6 8)
- edge(1 5)
- edge(5 1)  % Cycle
-]
-proc {$ I} {Assert EdgeP I} end
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-parigp.html b/docs/_style/prism-master/examples/prism-parigp.html deleted file mode 100644 index 029302bc..00000000 --- a/docs/_style/prism-master/examples/prism-parigp.html +++ /dev/null @@ -1,20 +0,0 @@ -

Comments

-
\\ Single line comment
-/* Multi line
-comment */
- -

Strings

-
""
-"Foo \"bar\" baz"
- -

Numbers

-
0.
-42
-3 . 14 15 9
-5.2 E +12
-.89
- -

Ignored whitespaces

-
p r i n t ("hello")
-if err(1/i, E, print (E))
-a + = b \ / c
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-parser.html b/docs/_style/prism-master/examples/prism-parser.html deleted file mode 100644 index 4b4e9c0d..00000000 --- a/docs/_style/prism-master/examples/prism-parser.html +++ /dev/null @@ -1,88 +0,0 @@ -

Comments

-
$foo[bar] # Some comment
- -

Variables and functions

-
@navigation[]
-$sections[^table::load[sections.cfg]]
-$sections.uri
- -

Literals

-
$foo(3+$bar)
-^switch[$sMode]{
-	^case[def]{$result(true)}
-}
-^if(in "/news/"){}
- -

Escape sequences

-
^^
-^"
-^;
- -

Embedded in markup

-
<nav>
-	<ul>
-	^sections.menu{
-		<li>
-			<a href="http://github.com/$sections.uri">$sections.name</a>
-		</li>
-	}
-	</ul>
-</nav>
- -

Full example

-
@CLASS
-MyTable
-
-@create[uParam]
-^switch[$uParam.CLASS_NAME]{
-   ^case[string;void]{$t[^table::create{$uParam}]}
-   ^case[table;MyTable]{$t[^table::create[$uParam]]}
-   ^case[DEFAULT]{^throw[MyTable;Unsupported type $uParam.CLASS_NAME]}
-}
-
-# method will return value in different calling contexts
-@GET[sMode]
-^switch[$sMode]{
-   ^case[table]{$result[$t]}
-   ^case[bool]{$result($t!=0)}
-   ^case[def]{$result(true)}
-   ^case[expression;double]{$result($t)}
-   ^case[DEFAULT]{^throw[MyTable;Unsupported mode '$sMode']}
-}
-
-
-# method will handle access to the "columns"
-@GET_DEFAULT[sName]
-$result[$t.$sName]
-
-
-# wrappers for all existing methods are required
-@count[]
-^t.count[]
-
-@menu[jCode;sSeparator]
-^t.menu{$jCode}[$sSeparator]
-
-
-# new functionality
-@remove[iOffset;iLimit]
-$iLimit(^iLimit.int(0))
-$t[^t.select(^t.offset[]<$iOffset || ^t.offset[]>=$iOffset+$iLimit)]
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Code block starting with a comment

-
# Doesn't work
-# Does work
-
 # Does work when prefixed with a space
- -

Comments inside expressions break literals and operators

-
^if(
-    $age>=4  # not too young
-    && $age<=80  # and not too old
-)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-pascal.html b/docs/_style/prism-master/examples/prism-pascal.html deleted file mode 100644 index dfea11a6..00000000 --- a/docs/_style/prism-master/examples/prism-pascal.html +++ /dev/null @@ -1,65 +0,0 @@ -

Comments

-
(* This is an
-old style comment *)
-{ This is a
-Turbo Pascal comment }
-// This is a Delphi comment.
- -

Strings and characters

-
'This is a pascal string'
-''
-'a'
-^G
-#7
-#$f4
-'A tabulator character: '#9' is easy to embed'
- -

Numbers

-
123
-123.456
-132.456e-789
-132.456e+789
-$7aff
-&17
-%11110101
- -

Full example

-
Type
-    Str25    = String[25];
-    TBookRec = Record
-                Title, Author,
-                ISBN  : Str25;
-                Price : Real;
-               End;
-
-Procedure EnterNewBook(var newBook : TBookRec);
-Begin
- Writeln('Please enter the book details: ');
- Write('Book Name: ');
- Readln(newBook.Title);
- Write('Author: ');
- Readln(newBook.Author);
- Write('ISBN: ');
- Readln(newBook.ISBN);
- Write('Price: ');
- Readln(newBook.Price);
-End;
-
-Var
-    bookRecArray : Array[1..10] of TBookRec;
-    i            : 1..10;
-
-Begin
- For i := 1 to 10 do
-  EnterNewBook(bookRecArray[i]);
- Writeln('Thanks for entering the book details');
- Write('Now choose a record to display from 1 to 10: ');
- Readln(i);
- Writeln('Here are the book details of record #',i,':');
- Writeln;
- Writeln('Title:  ', bookRecArray[i].Title);
- Writeln('Author: ', bookRecArray[i].Author);
- Writeln('ISBN:   ', bookRecArray[i].ISBN);
- Writeln('Price:  ', bookRecArray[i].Price);
- Readln;
-End.
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-perl.html b/docs/_style/prism-master/examples/prism-perl.html deleted file mode 100644 index 85c36815..00000000 --- a/docs/_style/prism-master/examples/prism-perl.html +++ /dev/null @@ -1,71 +0,0 @@ -

Comments

-
# Single line comment
-=head1 Here There
-	Be Pods!
-=cut
- -

Strings

-
q/foo bar baz/;
-q awhy not ?a;
-qw(foo bar baz) q{foo bar baz}
-q[foo bar baz] qq<foo bar baz>
-"foo bar baz" 'foo bar baz' `foo bar baz`
- -

Regex

-
m/foo/ s/foo/bar/
-m zfooz s zfoozbarz
-qr(foo) m{foo} s(foo)(bar) s{foo}{bar}
-m[foo] m<foo> tr[foo][bar] s<foo><bar>
-/foo/i
-
- -

Variables

-
${^POSTMATCH}
-$^V
-$element_count = scalar(@whatever);
-keys(%users) = 1000;
-$1, $_, %!;
- -

Numbers

-
12345
-12345.67
-.23E-10 # a very small number
-3.14_15_92 # a very important number
-4_294_967_296 # underscore for legibility
-0xff # hex
-0xdead_beef # more hex
-0377 # octal (only numbers, begins with 0)
-0b011011 # binary
- -

Full example

-
sub read_config_file {
-  my ($class, $filename) = @_;
-
-  unless (defined $filename) {
-    my $home  = File::HomeDir->my_home || '.';
-    $filename = File::Spec->catfile($home, '.pause');
-
-    return {} unless -e $filename and -r _;
-  }
-
-  my %conf;
-  if ( eval { require Config::Identity } ) {
-    %conf = Config::Identity->load($filename);
-    $conf{user} = delete $conf{username} unless $conf{user};
-  }
-  else { # Process .pause manually
-    open my $pauserc, '<', $filename
-      or die "can't open $filename for reading: $!";
-
-    while (<$pauserc>) {
-      chomp;
-      next unless $_ and $_ !~ /^\s*#/;
-
-      my ($k, $v) = /^\s*(\w+)\s+(.+)$/;
-      Carp::croak "multiple enties for $k" if $conf{$k};
-      $conf{$k} = $v;
-    }
-  }
-
-  return \%conf;
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-php.html b/docs/_style/prism-master/examples/prism-php.html deleted file mode 100644 index 1a25589c..00000000 --- a/docs/_style/prism-master/examples/prism-php.html +++ /dev/null @@ -1,67 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
-# Shell-like comment
- -

Strings

-
'foo \'bar\' baz'
-"foo \"bar\" baz"
-"a string # containing an hash"
-$foo = <<<FOO
-    Heredoc strings are supported too!
-FOO;
-$bar = <<<'BAR'
-    And also Nowdoc strings
-BAR;
- -

Variables

-
$some_var = 5;
-$otherVar = "Some text";
-$null = null;
-$false = false;
- -

Functions

-
$json = json_encode($my_object);
-$array1 = array("a" => "green", "red", "blue", "red");
-$array2 = array("b" => "green", "yellow", "red");
-$result = array_diff($array1, $array2);
- -

Constants

-
define('MAXSIZE', 42);
-echo MAXSIZE;
-json_decode($json, false, 512, JSON_BIGINT_AS_STRING)
- -

PHP 5.3+ support

-
namespace my\name;
-$c = new \my\name\MyClass;
-$arr = [1,2,3];
-trait ezcReflectionReturnInfo {
-    function getReturnType() { /*1*/ }
-    function getReturnDescription() { /*2*/ }
-}
-function gen_one_to_three() {
-    for ($i = 1; $i <= 3; $i++) {
-        // Note that $i is preserved between yields.
-        yield $i;
-    }
-}
- -

PHP embedded in HTML

-
<div class="<?php echo $a ? 'foo' : 'bar'; ?>">
-<?php if($var < 42) {
-    echo "Something";
-} else {
-    echo "Something else";
-} ?>
-</div>
- -

String interpolation

-
$str = "This is $great!";
-$foobar = "Another example: {${$foo->bar()}}";
-$a = <<<FOO
-    Hello $world!
-FOO;
-$b = <<<"FOOBAR"
-    Interpolation inside Heredoc strings {$obj->values[3]->name}
-FOOBAR;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-plsql.html b/docs/_style/prism-master/examples/prism-plsql.html deleted file mode 100644 index 2212b14e..00000000 --- a/docs/_style/prism-master/examples/prism-plsql.html +++ /dev/null @@ -1,40 +0,0 @@ -

Comments

-
-- Single line comment
-/* Multi-line
-comment */
- -

Operators

-
l_message  := 'Hello ' || place_in;
- -

Keywords

-
CREATE OR REPLACE PROCEDURE
-hello_place (place_in IN VARCHAR2)
-IS
-  l_message  VARCHAR2 (100);
-BEGIN
-  l_message  := 'Hello ' || place_in;
-  DBMS_OUTPUT.put_line (l_message);
-END hello_place;
-
-DECLARE
-  l_dept_id
-  employees.department_id%TYPE := 10;
-BEGIN
-  DELETE FROM employees
-       WHERE department_id = l_dept_id;
-
-  DBMS_OUTPUT.put_line (SQL%ROWCOUNT);
-END;
-
-DECLARE
-  l_message   VARCHAR2 (100) := 'Hello';
-  l_message2  VARCHAR2 (100) := ' World!';
-BEGIN
-  IF SYSDATE >= TO_DATE ('01-JAN-2011')
-  THEN
-    l_message2 := l_message || l_message2;
-    DBMS_OUTPUT.put_line (l_message2);
-  ELSE
-    DBMS_OUTPUT.put_line (l_message);
-  END IF;
-END;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-powershell.html b/docs/_style/prism-master/examples/prism-powershell.html deleted file mode 100644 index e5dba2e0..00000000 --- a/docs/_style/prism-master/examples/prism-powershell.html +++ /dev/null @@ -1,19 +0,0 @@ -

Comments

-
# This is a comment
-<# This is a
-multi-line comment #>
- -

Variable Interpolation

-
$Name = "Alice"
-Write-Host "Hello, my name is $Name."
- -

Full Example

-
Function SayHello([string]$name) {
-    Write-Host "Hello, $name."
-}
-$Names = @("Bob", "Alice")
-
-$Names | ForEach {
-    SayHello $_
-}
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-processing.html b/docs/_style/prism-master/examples/prism-processing.html deleted file mode 100644 index 3c74ba3b..00000000 --- a/docs/_style/prism-master/examples/prism-processing.html +++ /dev/null @@ -1,173 +0,0 @@ -

Full example

-
// Processing implementation of Game of Life by Joan Soler-Adillon
-// from https://processing.org/examples/gameoflife.html
-
-// Size of cells
-int cellSize = 5;
-
-// How likely for a cell to be alive at start (in percentage)
-float probabilityOfAliveAtStart = 15;
-
-// Variables for timer
-int interval = 100;
-int lastRecordedTime = 0;
-
-// Colors for active/inactive cells
-color alive = color(0, 200, 0);
-color dead = color(0);
-
-// Array of cells
-int[][] cells; 
-// Buffer to record the state of the cells and use this while changing the others in the interations
-int[][] cellsBuffer; 
-
-// Pause
-boolean pause = false;
-
-void setup() {
-  size (640, 360);
-
-  // Instantiate arrays 
-  cells = new int[width/cellSize][height/cellSize];
-  cellsBuffer = new int[width/cellSize][height/cellSize];
-
-  // This stroke will draw the background grid
-  stroke(48);
-
-  noSmooth();
-
-  // Initialization of cells
-  for (int x=0; x<width/cellSize; x++) {
-    for (int y=0; y<height/cellSize; y++) {
-      float state = random (100);
-      if (state > probabilityOfAliveAtStart) { 
-        state = 0;
-      }
-      else {
-        state = 1;
-      }
-      cells[x][y] = int(state); // Save state of each cell
-    }
-  }
-  background(0); // Fill in black in case cells don't cover all the windows
-}
-
-
-void draw() {
-
-  //Draw grid
-  for (int x=0; x<width/cellSize; x++) {
-    for (int y=0; y<height/cellSize; y++) {
-      if (cells[x][y]==1) {
-        fill(alive); // If alive
-      }
-      else {
-        fill(dead); // If dead
-      }
-      rect (x*cellSize, y*cellSize, cellSize, cellSize);
-    }
-  }
-  // Iterate if timer ticks
-  if (millis()-lastRecordedTime>interval) {
-    if (!pause) {
-      iteration();
-      lastRecordedTime = millis();
-    }
-  }
-
-  // Create  new cells manually on pause
-  if (pause && mousePressed) {
-    // Map and avoid out of bound errors
-    int xCellOver = int(map(mouseX, 0, width, 0, width/cellSize));
-    xCellOver = constrain(xCellOver, 0, width/cellSize-1);
-    int yCellOver = int(map(mouseY, 0, height, 0, height/cellSize));
-    yCellOver = constrain(yCellOver, 0, height/cellSize-1);
-
-    // Check against cells in buffer
-    if (cellsBuffer[xCellOver][yCellOver]==1) { // Cell is alive
-      cells[xCellOver][yCellOver]=0; // Kill
-      fill(dead); // Fill with kill color
-    }
-    else { // Cell is dead
-      cells[xCellOver][yCellOver]=1; // Make alive
-      fill(alive); // Fill alive color
-    }
-  } 
-  else if (pause && !mousePressed) { // And then save to buffer once mouse goes up
-    // Save cells to buffer (so we opeate with one array keeping the other intact)
-    for (int x=0; x<width/cellSize; x++) {
-      for (int y=0; y<height/cellSize; y++) {
-        cellsBuffer[x][y] = cells[x][y];
-      }
-    }
-  }
-}
-
-
-
-void iteration() { // When the clock ticks
-  // Save cells to buffer (so we opeate with one array keeping the other intact)
-  for (int x=0; x<width/cellSize; x++) {
-    for (int y=0; y<height/cellSize; y++) {
-      cellsBuffer[x][y] = cells[x][y];
-    }
-  }
-
-  // Visit each cell:
-  for (int x=0; x<width/cellSize; x++) {
-    for (int y=0; y<height/cellSize; y++) {
-      // And visit all the neighbours of each cell
-      int neighbours = 0; // We'll count the neighbours
-      for (int xx=x-1; xx<=x+1;xx++) {
-        for (int yy=y-1; yy<=y+1;yy++) {  
-          if (((xx>=0)&&(xx<width/cellSize))&&((yy>=0)&&(yy<height/cellSize))) { // Make sure you are not out of bounds
-            if (!((xx==x)&&(yy==y))) { // Make sure to to check against self
-              if (cellsBuffer[xx][yy]==1){
-                neighbours ++; // Check alive neighbours and count them
-              }
-            } // End of if
-          } // End of if
-        } // End of yy loop
-      } //End of xx loop
-      // We've checked the neigbours: apply rules!
-      if (cellsBuffer[x][y]==1) { // The cell is alive: kill it if necessary
-        if (neighbours < 2 || neighbours > 3) {
-          cells[x][y] = 0; // Die unless it has 2 or 3 neighbours
-        }
-      } 
-      else { // The cell is dead: make it live if necessary      
-        if (neighbours == 3 ) {
-          cells[x][y] = 1; // Only if it has 3 neighbours
-        }
-      } // End of if
-    } // End of y loop
-  } // End of x loop
-} // End of function
-
-void keyPressed() {
-  if (key=='r' || key == 'R') {
-    // Restart: reinitialization of cells
-    for (int x=0; x<width/cellSize; x++) {
-      for (int y=0; y<height/cellSize; y++) {
-        float state = random (100);
-        if (state > probabilityOfAliveAtStart) {
-          state = 0;
-        }
-        else {
-          state = 1;
-        }
-        cells[x][y] = int(state); // Save state of each cell
-      }
-    }
-  }
-  if (key==' ') { // On/off of pause
-    pause = !pause;
-  }
-  if (key=='c' || key == 'C') { // Clear all
-    for (int x=0; x<width/cellSize; x++) {
-      for (int y=0; y<height/cellSize; y++) {
-        cells[x][y] = 0; // Save all to zero
-      }
-    }
-  }
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-prolog.html b/docs/_style/prism-master/examples/prism-prolog.html deleted file mode 100644 index b231bcae..00000000 --- a/docs/_style/prism-master/examples/prism-prolog.html +++ /dev/null @@ -1,44 +0,0 @@ -

Comments

-
% This is a comment
-/* This is a
-multi-line comment */
- -

Numbers

-
42
-3.1415
- -

Strings

-
"This is a string."
-"This is a string \
-on multiple lines."
-"A string with \"quotes\" in it."
-"Another string with ""quotes"" in it."
- -

Example

-
:- dynamic fibo/2.
-fibo(0, 1). fibo(1, 1).
-fibo(N, F) :-
-N >= 2, N1 is N - 1, N2 is N - 2,
-fibo(N1, F1), fibo(N2, F2), F is F1 + F2,
-assert(fibo(N,F):-!). % assert as first clause
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Null-ary predicates are not highlighted

-
halt.
-trace.
-
-:- if(test1).
-section_1.
-:- elif(test2).
-section_2.
-:- elif(test3).
-section_3.
-:- else.
-section_else.
-:- endif.
diff --git a/docs/_style/prism-master/examples/prism-properties.html b/docs/_style/prism-master/examples/prism-properties.html deleted file mode 100644 index bbc81f99..00000000 --- a/docs/_style/prism-master/examples/prism-properties.html +++ /dev/null @@ -1,9 +0,0 @@ -

Comments

-
# This is a comment
-! This is a comment too
- -

Properties

-
some_key some_value
-some\ key\ with\ spaces : some value
-some_key = some \
-multiline value
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-pug.html b/docs/_style/prism-master/examples/prism-pug.html deleted file mode 100644 index d37de5ca..00000000 --- a/docs/_style/prism-master/examples/prism-pug.html +++ /dev/null @@ -1,85 +0,0 @@ -

Comments

-
// Some
-  multiline
-  comment !
-
-// This is a comment
-But this is not
- -

Doctype

-
doctype html
-doctype 1.1
-doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
- -

Tags

-
ul
-  li Item A
-  li Item B
-  li Item C
-foo(bar='baz')/
-input(type='checkbox', checked=true.toString())
-#content
-div#foo(data-bar="foo")&attributes({'data-foo': 'bar'})
- -

Markup

-
<div class="foo bar"></div>
- -

Control flow

-
#user
-  if user.description
-    p.description= user.description
-  else if authorised
-    p.description.
-      User has no description,
-      why not add one...
-  else
-    p.description User has no description
-ul
-  each val in [1, 2, 3, 4, 5]
-    li= val
-case friends
-  when 0
-    p you have no friends
-  when 1
-    p you have a friend
-  default
-    p you have #{friends} friends
-
- -

Inline JavaScript

-
script alert('test');
-script(type="text/javascript").
-  alert('foo');
-  alert('bar');
-- var classes = ['foo', 'bar', 'baz']
-- for (var x = 0; x < 3; x++)
-  li item
-
- -

Keywords

-
include ./includes/head.pug
-extends ./layout.pug
-block content
-append head
- -

Mixins

-
mixin list
-  ul
-    li foo
-    li bar
-    li baz
-+list
-mixin pet(name)
-  li.pet= name
-ul
-  +pet('cat')
-  +pet('dog')
-
- -

Filters

-

Filters require the desired language to be loaded. -On this page, check CoffeeScript before checking Pug should make -the example below work properly.

-
script
-  :coffee
-    console.log 'This is coffee script'
diff --git a/docs/_style/prism-master/examples/prism-puppet.html b/docs/_style/prism-master/examples/prism-puppet.html deleted file mode 100644 index 4892ab89..00000000 --- a/docs/_style/prism-master/examples/prism-puppet.html +++ /dev/null @@ -1,152 +0,0 @@ -

Comments

-
#
-# Foobar
-/* Foo
-bar */
- -

Strings and interpolation

-
'foo \'bar\' baz'
-"$foo \"bar\" ${baz}"
-
-@(FOOBAR) # Unquoted heredoc string
-Foo bar baz
-FOOBAR
-
-@("BARBAZ"/$L) # Quoted heredoc string
-	$foo bar ${baz}
-	|-BARBAZ
- -

Regular expressions

-
if $host =~ /^www(\d+)\./ {}
-$foo = /foo
-	bar # Extended regexes can include comments
-baz/x
- -

Variables

-
$foo
-$::foobar
-$foo::bar::baz
- -

Functions

-
require apache
-template('apache/vhost-default.conf.erb')
-[1,20,3].filter |$value| { $value < 10 }
- -

All-in-one example

-
file {'ntp.conf':
-  path    => '/etc/ntp.conf',
-  ensure  => file,
-  content => template('ntp/ntp.conf'),
-  owner   => 'root',
-  mode    => '0644',
-}
-package {'ntp':
-  ensure => installed,
-  before => File['ntp.conf'],
-}
-service {'ntpd':
-  ensure    => running,
-  subscribe => File['ntp.conf'],
-}
-Package['ntp'] -> File['ntp.conf'] ~> Service['ntpd']
-
-$package_list = ['ntp', 'apache2', 'vim-nox', 'wget']
-$myhash = { key => { subkey => 'b' }}
-
-include ntp
-require ntp
-class {'ntp':}
-
-define apache::vhost ($port, $docroot, $servername = $title, $vhost_name = '*') {
-  include apache
-  include apache::params
-  $vhost_dir = $apache::params::vhost_dir
-  file { "${vhost_dir}/${servername}.conf":
-      content => template('apache/vhost-default.conf.erb'),
-      owner   => 'www',
-      group   => 'www',
-      mode    => '644',
-      require => Package['httpd'],
-      notify  => Service['httpd'],
-  }
-}
-
-apache::vhost {'homepages':
-  port    => 8081,
-  docroot => '/var/www-testhost',
-}
-Apache::Vhost['homepages']
-
-node 'www1.example.com' {
-  include common
-  include apache
-  include squid
-}
-node /^www\d+$/ {
-  include common
-}
-
-# comment
-/* comment */
-
-if $is_virtual {
-  warning( 'Tried to include class ntp on virtual machine; this node may be misclassified.' )
-}
-elsif $operatingsystem == 'Darwin' {
-  warning( 'This NTP module does not yet work on our Mac laptops.' )
-else {
-  include ntp
-}
-
-if $hostname =~ /^www(\d+)\./ {
-  notify { "Welcome web server $1": }
-}
-
-case $operatingsystem {
-  'Solaris':          { include role::solaris }
-  'RedHat', 'CentOS': { include role::redhat  }
-  /^(Debian|Ubuntu)$/:{ include role::debian  }
-  default:            { include role::generic }
-}
-$rootgroup = $osfamily ? {
-    'Solaris'          => 'wheel',
-    /(Darwin|FreeBSD)/ => 'wheel',
-    default            => 'root',
-}
-
-User <| groups == 'admin' |>
-Concat::Fragment <<| tag == "bacula-storage-dir-${bacula_director}" |>>
-
-Exec <| title == 'update_migrations' |> {
-  environment => 'RUBYLIB=/usr/lib/ruby/site_ruby/1.8/',
-}
-
-@user {'deploy':
-  uid     => 2004,
-  comment => 'Deployment User',
-  group   => www-data,
-  groups  => ["enterprise"],
-  tag     => [deploy, web],
-}
-
-@@nagios_service { "check_zfs${hostname}":
-  use                 => 'generic-service',
-  host_name           => "$fqdn",
-  check_command       => 'check_nrpe_1arg!check_zfs',
-  service_description => "check_zfs${hostname}",
-  target              => '/etc/nagios3/conf.d/nagios_service.cfg',
-  notify              => Service[$nagios::params::nagios_service],
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

More than one level of nested braces inside interpolation

-
"Foobar ${foo({
-    bar => {baz => 42}
-    baz => 42
-})} <- broken"
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-pure.html b/docs/_style/prism-master/examples/prism-pure.html deleted file mode 100644 index d7a0129b..00000000 --- a/docs/_style/prism-master/examples/prism-pure.html +++ /dev/null @@ -1,115 +0,0 @@ -

Comments

-
#! shebang
-// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"This is a string."
-"This is a string with \"quotes\" in it."
- -

Numbers

-
4711
-4711L
-1.2e-3
-.14
-1000
-0x3e8
-01750
-0b1111101000
-inf
-nan
- -

Inline code

-

Inline code requires the desired language to be loaded. -On this page, check C, C++ and Fortran before checking Pure should make -the examples below work properly.

-
%<
-int mygcd(int x, int y)
-{
-  if (y == 0)
-    return x;
-  else
-    return mygcd(y, x%y);
-}
-%>
-
-%< -*- Fortran90 -*-
-function fact(n) result(p)
-  integer n, p
-  p = 1
-  do i = 1, n
-     p = p*i
-  end do
-end function fact
-%>
-
-%< -*- C++ -*-
-
-#include <pure/runtime.h>
-#include <string>
-#include <map>
-
-// An STL map mapping strings to Pure expressions.
-
-using namespace std;
-typedef map<string,pure_expr*> exprmap;
-
-// Since we can't directly deal with C++ classes in Pure, provide some C
-// functions to create, destroy and manipulate these objects.
-
-extern "C" exprmap *map_create()
-{
-  return new exprmap;
-}
-
-extern "C" void map_add(exprmap *m, const char *key, pure_expr *x)
-{
-  exprmap::iterator it = m->find(string(key));
-  if (it != m->end()) pure_free(it->second);
-  (*m)[key] = pure_new(x);
-}
-
-extern "C" void map_del(exprmap *m, const char *key)
-{
-  exprmap::iterator it = m->find(key);
-  if (it != m->end()) {
-    pure_free(it->second);
-    m->erase(it);
-  }
-}
-
-extern "C" pure_expr *map_get(exprmap *m, const char *key)
-{
-  exprmap::iterator it = m->find(key);
-  return (it != m->end())?it->second:0;
-}
-
-extern "C" pure_expr *map_keys(exprmap *m)
-{
-  size_t i = 0, n = m->size();
-  pure_expr **xs = new pure_expr*[n];
-  for (exprmap::iterator it = m->begin(); it != m->end(); ++it)
-    xs[i++] = pure_string_dup(it->first.c_str());
-  pure_expr *x = pure_listv(n, xs);
-  delete[] xs;
-  return x;
-}
-
-extern "C" void map_destroy(exprmap *m)
-{
-  for (exprmap::iterator it = m->begin(); it != m->end(); ++it)
-    pure_free(it->second);
-  delete m;
-}
-
-%>
- -

Example

-
queens n       = catch reverse (search n 1 []) with
-  search n i p = throw p if i>n;
-               = void [search n (i+1) ((i,j):p) | j = 1..n; safe (i,j) p];
-  safe (i,j) p = ~any (check (i,j)) p;
-  check (i1,j1) (i2,j2)
-               = i1==i2 || j1==j2 || i1+j1==i2+j2 || i1-j1==i2-j2;
-end;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-python.html b/docs/_style/prism-master/examples/prism-python.html deleted file mode 100644 index ce6efc00..00000000 --- a/docs/_style/prism-master/examples/prism-python.html +++ /dev/null @@ -1,61 +0,0 @@ -

Comments

-
# This is a comment
-# -*- coding: <encoding-name> -*-
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-""" "Multi-line" strings
-are supported."""
-''' 'Multi-line' strings
-are supported.'''
- -

Numbers

-
7
-2147483647
-0o177
-0b100110111
-3
-79228162514264337593543950336
-0o377
-0x100000000
-0xdeadbeef
-3.14
-10.
-.001
-1e100
-3.14e-10
-0e0
-3.14j
-10.j
-10j
-.001j
-1e100j
-3.14e-10j
-
- -

Full example

-
def median(pool):
-    '''Statistical median to demonstrate doctest.
-    >>> median([2, 9, 9, 7, 9, 2, 4, 5, 8])
-    7
-    '''
-    copy = sorted(pool)
-    size = len(copy)
-    if size % 2 == 1:
-        return copy[(size - 1) / 2]
-    else:
-        return (copy[size/2 - 1] + copy[size/2]) / 2
-if __name__ == '__main__':
-    import doctest
-    doctest.testmod()
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Interpolation expressions containing strings with { or }

-
f"{'}'}"
diff --git a/docs/_style/prism-master/examples/prism-q.html b/docs/_style/prism-master/examples/prism-q.html deleted file mode 100644 index 58d674f4..00000000 --- a/docs/_style/prism-master/examples/prism-q.html +++ /dev/null @@ -1,112 +0,0 @@ -

Comments

-
foo / This is a comment
-/ This is a comment too
-
-/
-Some multi-line
-comment here
-\
-
-\
-This comment will
-continue until the
-end of code
- -

Character data and strings

-
"q"
-"\""
-"\\"
-"\142"
-"foo bar baz"
- -

Symbols

-
`
-`q
-`zaphod
-`:198.162.0.2:5042
-`:www.yourco.com:5042
-`.new
- -

Numbers

-
42
-b:-123h
-c:1234567890j
-pi:3.14159265
-float1:1f
-r:1.4142e
-2.0
-4.00e
-f:1.23456789e-10
-r:1.2345678e-10e
-bit:0b
-byte:0x2a
-a:42
-bit:1b
-
-0w 0n 0W 0Wh 0Wj
- -

Dates

-
d:2006.07.04
-t:09:04:59.000
-dt:2006.07.04T09:04:59.000
-mon:2006.07m
-mm:09:04
-sec:09:04:59
-d:2006.07.04
-
-0Nm 0Nd 0Nz 0Nu 0Nv 0Wd 0Wt 0Wz
- -

Verbs

-
99+L
-x<42|x>98
-(x<42)|x>98
-42~(4 2;(1 0))
-(4 2)~(4; 2*1)
- -

Adverbs

-
" ," ,/: ("Now";"is";"the";"time")
-L1,/:\:L2
-0+/10 20 30
-(1#) each 1001 1002 1004 1003
- -

Built-in functions and q-sql

-
string 42
-L1 cross L2
-type c
-select from t where price=(max;price) fby ([]sym;ex)
-ungroup `p xgroup sp
-`instrument insert (`g; `$"Google"; `$"Internet")
- -

Example

-
/ Example from http://code.kx.com/wiki/Cookbook/CorporateActions
-getCAs:{[caTypes]
-    / handles multiplie corporate actions on one date
-    t:0!select factor:prd factor by date-1,sym from ca where caType in caTypes;
-    t,:update date:1901.01.01,factor:1.0 from ([]sym:distinct t`sym);
-    t:`date xasc t;
-    t:update factor:reverse prds reverse 1 rotate factor by sym from t;
-    :update `g#sym from 0!t;
-  };
-
-adjust:{[t;caTypes]
-    t:0!t;
-    factors:enlist 1.0^aj[`sym`date;([] date:t`date;sym:t`sym);getCAs caTypes]`factor;
-    mc:c where (lower c:cols t) like "*price"; / find columns to multiply
-    dc:c where lower[c] like "*size"; / find columns to divide
-    :![t;();0b;(mc,dc)!((*),/:mc,\:factors),((%),/:dc,\:factors)]; / multiply or divide out the columns
-  };
-
-/ get the adjustment factors considering all corporate actions
-getCAs exec distinct caType from ca
-
-adjust[t;`dividend] / adjust trades for dividends only
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

The global context is highlighted as a verb

-
\d .
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-qore.html b/docs/_style/prism-master/examples/prism-qore.html deleted file mode 100644 index 710e1614..00000000 --- a/docs/_style/prism-master/examples/prism-qore.html +++ /dev/null @@ -1,962 +0,0 @@ -

Full example

-
#!/usr/bin/env qore
-
-# database test script
-# databases users must be able to create and destroy tables and procedures, etc
-# in order to execute all tests
-
-%require-our
-%enable-all-warnings
-
-our ($o, $errors, $test_count);
-
-const opts =
-	( "help"    : "h,help",
-	  "host"    : "H,host=s",
-	  "pass"    : "p,pass=s",
-	  "db"      : "d,db=s",
-	  "user"    : "u,user=s",
-	  "type"    : "t,type=s",
-	  "enc"     : "e,encoding=s",
-	  "verbose" : "v,verbose:i+",
-	  "leave"   : "l,leave"
- );
-
-sub usage()
-{
-	printf("usage: %s [options]
- -h,--help          this help text
- -u,--user=ARG      set username
- -p,--pass=ARG      set password
- -d,--db=ARG        set database name
- -e,--encoding=ARG  set database character set encoding (i.e. \"utf8\")
- -H,--host=ARG      set hostname (for MySQL and PostgreSQL connections)
- -t,--type          set database driver (default mysql)
- -v,--verbose       more v's = more information
- -l,--leave         leave test tables in schema at end\n",
-	   basename($ENV."_"));
-	exit();
-}
-
-const object_map =
- ( "oracle" :
-   ( "tables" : ora_tables ),
-   "mysql"  :
-   ( "tables" : mysql_tables ),
-   "pgsql"  :
-   ( "tables" : pgsql_tables ),
-   "sybase" :
-   ( "tables" : syb_tables,
-	 "procs"  : sybase_procs ),
-   "freetds"  :
-   ( "tables" : freetds_sybase_tables,
-	 "procs"  : sybase_procs ) );
-
-const ora_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar2(80) not null
-)",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar2(250) not null,
-   dob date not null
-)",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar2(80) not null,
-   value varchar2(160) not null
-)" );
-
-const mysql_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar(80) not null
-) type = innodb",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar(250) not null,
-   dob date not null
-) type = innodb",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar(80) not null,
-   value varchar(160) not null
-) type = innodb" );
-
-const pgsql_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar(80) not null )",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar(250) not null,
-   dob date not null )",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar(80) not null,
-   value varchar(160) not null)",
-	"data_test" : "create table data_test (
-		int2_f smallint not null,
-		int4_f integer not null,
-		int8_f int8 not null,
-		bool_f boolean not null,
-
-		float4_f real not null,
-		float8_f double precision not null,
-
-		number_f numeric(16,3) not null,
-		money_f money not null,
-
-		text_f text not null,
-		varchar_f varchar(40) not null,
-		char_f char(40) not null,
-		name_f name not null,
-
-		date_f date not null,
-		abstime_f abstime not null,
-		reltime_f reltime not null,
-		interval_f interval not null,
-		time_f time not null,
-		timetz_f time with time zone not null,
-		timestamp_f timestamp not null,
-		timestamptz_f timestamp with time zone not null,
-		tinterval_f tinterval not null,
-
-		bytea_f bytea not null
-		--bit_f bit(11) not null,
-		--varbit_f bit varying(11) not null
-)" );
-
-const syb_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar(80) not null
-)",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar(250) not null,
-   dob date not null
-)",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar(80) not null,
-   value varchar(160) not null
-)",
-	"data_test" : "create table data_test (
-	null_f char(1) null,
-
-	varchar_f varchar(40) not null,
-	char_f char(40) not null,
-	unichar_f unichar(40) not null,
-	univarchar_f univarchar(40) not null,
-	text_f text not null,
-	unitext_f unitext not null, -- note that unitext is stored as 'image'
-
-		bit_f bit not null,
-	tinyint_f tinyint not null,
-	smallint_f smallint not null,
-	int_f int not null,
-		int_f2 int not null,
-
-	decimal_f decimal(10,4) not null,
-
-	float_f float not null,     -- 8-bytes
-	real_f real not null,       -- 4-bytes
-	money_f money not null,
-	smallmoney_f smallmoney not null,
-
-	date_f date not null,
-	time_f time not null,
-	datetime_f datetime not null,
-	smalldatetime_f smalldatetime not null,
-
-	binary_f binary(4) not null,
-	varbinary_f varbinary(4) not null,
-	image_f image not null
-)" );
-
-const sybase_procs = (
-	"find_family" :
-"create procedure find_family @name varchar(80)
-as
-select * from family where name = @name
-commit -- to maintain transaction count
-",
-	"get_values" :
-"create procedure get_values @string varchar(80) output, @int int output
-as
-select @string = 'hello there'
-select @int = 150
-commit -- to maintain transaction count
-",
-	"get_values_and_select" :
-"create procedure get_values_and_select @string varchar(80) output, @int int output
-as
-select @string = 'hello there'
-select @int = 150
-select * from family where family_id = 1
-commit -- to maintain transaction count
-",
-	"get_values_and_multiple_select" :
-"create procedure get_values_and_multiple_select @string varchar(80) output, @int int output
-as
-select @string = 'hello there'
-select @int = 150
-select * from family where family_id = 1
-select * from people where person_id = 1
-commit -- to maintain transaction count
-",
-	"just_select" :
-"create procedure just_select
-as
-select * from family where family_id = 1
-commit -- to maintain transaction count
-",
-	"multiple_select" :
-"create procedure multiple_select
-as
-select * from family where family_id = 1
-select * from people where person_id = 1
-commit -- to maintain transaction count
-"
- );
-
-const freetds_sybase_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar(80) not null
-)",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar(250) not null,
-   dob date not null
-)",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar(80) not null,
-   value varchar(160) not null
-)",
-	"data_test" : "create table data_test (
-	null_f char(1) null,
-
-	varchar_f varchar(40) not null,
-	char_f char(40) not null,
-	text_f text not null,
-	unitext_f unitext not null, -- note that unitext is stored as 'image'
-
-		bit_f bit not null,
-	tinyint_f tinyint not null,
-	smallint_f smallint not null,
-	int_f int not null,
-		int_f2 int not null,
-
-	decimal_f decimal(10,4) not null,
-
-	float_f float not null,     -- 8-bytes
-	real_f real not null,       -- 4-bytes
-	money_f money not null,
-	smallmoney_f smallmoney not null,
-
-	date_f date not null,
-	time_f time not null,
-	datetime_f datetime not null,
-	smalldatetime_f smalldatetime not null,
-
-	binary_f binary(4) not null,
-	varbinary_f varbinary(4) not null,
-	image_f image not null
-)" );
-
-const freetds_mssql_tables = (
-	"family" : "create table family (
-   family_id int not null,
-   name varchar(80) not null
-)",
-	"people" : "create table people (
-   person_id int not null,
-   family_id int not null,
-   name varchar(250) not null,
-   dob datetime not null
-)",
-	"attributes" : "create table attributes (
-   person_id int not null,
-   attribute varchar(80) not null,
-   value varchar(160) not null
-)",
-	"data_test" : "create table data_test (
-	null_f char(1) null,
-
-	varchar_f varchar(40) not null,
-	char_f char(40) not null,
-	text_f text not null,
-
-		bit_f bit not null,
-	tinyint_f tinyint not null,
-	smallint_f smallint not null,
-	int_f int not null,
-		int_f2 int not null,
-
-	decimal_f decimal(10,4) not null,
-
-	float_f float not null,     -- 8-bytes
-	real_f real not null,       -- 4-bytes
-	money_f money not null,
-	smallmoney_f smallmoney not null,
-
-	datetime_f datetime not null,
-	smalldatetime_f smalldatetime not null,
-
-	binary_f binary(4) not null,
-	varbinary_f varbinary(4) not null,
-	image_f image not null
-)" );
-
-sub parse_command_line()
-{
-	my $g = new GetOpt(opts);
-	$o = $g.parse(\$ARGV);
-	if ($o.help)
-	usage();
-
-	if (!strlen($o.db))
-	{
-	stderr.printf("set the login parameters with -u,-p,-d, etc (-h for help)\n");
-	exit(1);
-	}
-	if (elements $ARGV)
-	{
-	stderr.printf("excess arguments on command-line (%n): -h for help\n", $ARGV);
-	exit(1);
-	}
-	if (!strlen($o.type))
-	$o.type = "mysql";
-}
-
-sub create_datamodel($db)
-{
-	drop_test_datamodel($db);
-
-	my $driver = $db.getDriverName();
-	# create tables
-	my $tables = object_map.$driver.tables;
-	if ($driver == "freetds")
-	if ($db.is_sybase)
-		$tables = freetds_sybase_tables;
-		else
-		$tables = freetds_mssql_tables;
-
-	foreach my $table in (keys $tables)
-	{
-	tprintf(2, "creating table %n\n", $table);
-	$db.exec($tables.$table);
-	}
-
-	# create procedures if any
-	foreach my $proc in (keys object_map.$driver.procs)
-	{
-	tprintf(2, "creating procedure %n\n", $proc);
-	$db.exec(object_map.$driver.procs.$proc);
-	}
-
-	# create functions if any
-	foreach my $func in (keys object_map.$driver.funcs)
-	{
-	tprintf(2, "creating function %n\n", $func);
-	$db.exec(object_map.$driver.funcs.$func);
-	}
-
-	$db.exec("insert into family values ( 1, 'Smith' )");
-	$db.exec("insert into family values ( 2, 'Jones' )");
-
-	# we insert the dates here using binding by value so we don't have
-	# to worry about each database's specific date format
-	$db.exec("insert into people values ( 1, 1, 'Arnie', %v)", 1983-05-13);
-	$db.exec("insert into people values ( 2, 1, 'Sylvia', %v)", 1994-11-10);
-	$db.exec("insert into people values ( 3, 1, 'Carol', %v)", 2003-07-23);
-	$db.exec("insert into people values ( 4, 1, 'Bernard', %v)", 1979-02-27);
-	$db.exec("insert into people values ( 5, 1, 'Isaac', %v)", 2000-04-04);
-	$db.exec("insert into people values ( 6, 2, 'Alan', %v)", 1992-06-04);
-	$db.exec("insert into people values ( 7, 2, 'John', %v)", 1995-03-23);
-
-	$db.exec("insert into attributes values ( 1, 'hair', 'blond' )");
-	$db.exec("insert into attributes values ( 1, 'eyes', 'hazel' )");
-	$db.exec("insert into attributes values ( 2, 'hair', 'blond' )");
-	$db.exec("insert into attributes values ( 2, 'eyes', 'blue' )");
-	$db.exec("insert into attributes values ( 3, 'hair', 'brown' )");
-	$db.exec("insert into attributes values ( 3, 'eyes', 'grey')");
-	$db.exec("insert into attributes values ( 4, 'hair', 'brown' )");
-	$db.exec("insert into attributes values ( 4, 'eyes', 'brown' )");
-	$db.exec("insert into attributes values ( 5, 'hair', 'red' )");
-	$db.exec("insert into attributes values ( 5, 'eyes', 'green' )");
-	$db.exec("insert into attributes values ( 6, 'hair', 'black' )");
-	$db.exec("insert into attributes values ( 6, 'eyes', 'blue' )");
-	$db.exec("insert into attributes values ( 7, 'hair', 'brown' )");
-	$db.exec("insert into attributes values ( 7, 'eyes', 'brown' )");
-	$db.commit();
-}
-
-sub drop_test_datamodel($db)
-{
-	my $driver = $db.getDriverName();
-	# drop the tables and ignore exceptions
-	# the commits are needed for databases like postgresql, where errors will prohibit and further
-	# actions from being taken on the Datasource
-	foreach my $table in (keys object_map.$driver.tables)
-	try {
-		$db.exec("drop table " + $table);
-		$db.commit();
-		tprintf(2, "dropped table %n\n", $table);
-	}
-		catch ()
-	{
-		$db.commit();
-	}
-
-	# drop procedures and ignore exceptions
-	foreach my $proc in (keys object_map.$driver.procs)
-	{
-	my $cmd = object_map.$driver.drop_proc_cmd;
-	if (!exists $cmd)
-		$cmd = "drop procedure";
-	try {
-		$db.exec($cmd + " " + $proc);
-		$db.commit();
-		tprintf(2, "dropped procedure %n\n", $proc);
-	}
-	catch ()
-	{
-		$db.commit();
-	}
-	}
-
-	# drop functions and ignore exceptions
-	foreach my $func in (keys object_map.$driver.funcs)
-	{
-	my $cmd = object_map.$driver.drop_func_cmd;
-	if (!exists $cmd)
-		$cmd = "drop function";
-	try {
-		$db.exec($cmd + " " + $func);
-		$db.commit();
-		tprintf(2, "dropped function %n\n", $func);
-	}
-	catch ()
-	{
-		$db.commit();
-	}
-	}
-}
-
-sub getDS()
-{
-	my $ds = new Datasource($o.type, $o.user, $o.pass, $o.db, $o.enc);
-	if (strlen($o.host))
-	$ds.setHostName($o.host);
-	return $ds;
-}
-
-sub tprintf($v, $msg)
-{
-	if ($v <= $o.verbose)
-	vprintf($msg, $argv);
-}
-
-sub test_value($v1, $v2, $msg)
-{
-	++$test_count;
-	if ($v1 == $v2)
-	tprintf(1, "OK: %s test\n", $msg);
-	else
-	{
-		tprintf(0, "ERROR: %s test failed! (%n != %n)\n", $msg, $v1, $v2);
-		$errors++;
-	}
-}
-
-const family_hash = (
-  "Jones" : (
-	  "people" : (
-	  "John" : (
-		  "dob" : 1995-03-23,
-		  "eyes" : "brown",
-		  "hair" : "brown" ),
-	  "Alan" : (
-		  "dob" : 1992-06-04,
-		  "eyes" : "blue",
-		  "hair" : "black" ) ) ),
-	"Smith" : (
-	"people" : (
-		"Arnie" : (
-		"dob" : 1983-05-13,
-		"eyes" : "hazel",
-		"hair" : "blond" ),
-		"Carol" : (
-		"dob" : 2003-07-23,
-		"eyes" : "grey",
-		"hair" : "brown" ),
-		"Isaac" : (
-		"dob" : 2000-04-04,
-		"eyes" : "green",
-		"hair" : "red" ),
-		"Bernard" : (
-		"dob" : 1979-02-27,
-		"eyes" : "brown",
-		"hair" : "brown" ),
-		"Sylvia" : (
-		"dob" : 1994-11-10,
-		"eyes" : "blue",
-		"hair" : "blond" ) ) ) );
-
-sub context_test($db)
-{
-	# first we select all the data from the tables and then use
-	# context statements to order the output hierarchically
-
-	# context statements are most useful when a set of queries can be executed once
-	# and the results processed many times by creating "views" with context statements
-
-	my $people = $db.select("select * from people");
-	my $attributes = $db.select("select * from attributes");
-
-	my $today = format_date("YYYYMMDD", now());
-
-	# in this test, we create a big hash structure out of the queries executed above
-	# and compare it at the end to the expected result
-
-	# display each family sorted by family name
-	my $fl;
-	context family ($db.select("select * from family")) sortBy (%name)
-	{
-	my $pl;
-	tprintf(2, "Family %d: %s\n", %family_id, %name);
-
-	# display people, sorted by eye color, descending
-	context people ($people)
-		sortDescendingBy (find %value in $attributes
-				  where (%attribute == "eyes"
-					 && %person_id == %people:person_id))
-		where (%family_id == %family:family_id)
-	{
-		my $al;
-		tprintf(2, "  %s, born %s\n", %name, format_date("Month DD, YYYY", %dob));
-		context ($attributes) sortBy (%attribute) where (%person_id == %people:person_id)
-		{
-		$al.%attribute = %value;
-		tprintf(2, "    has %s %s\n", %value, %attribute);
-		}
-		# leave out the ID fields and name from hash under name; subtracting a
-		# string from a hash removes that key from the result
-		# this is "doing it the hard way", there is only one key left,
-		# "dob", then attributes are added directly into the person hash
-		$pl.%name = %% - "family_id" - "person_id" - "name" + $al;
-	}
-	# leave out family_id and name fields (leaving an empty hash)
-	$fl.%name = %% - "family_id" - "name" + ( "people" : $pl );
-	}
-
-	# test context ordering
-	test_value(keys $fl, ("Jones", "Smith"), "first context");
-	test_value(keys $fl.Smith.people, ("Arnie", "Carol", "Isaac", "Bernard", "Sylvia"), "second context");
-	# test entire context value
-	test_value($fl, family_hash, "third context");
-}
-
-
-sub test_timeout($db, $c)
-{
-	$db.setTransactionLockTimeout(1ms);
-	try {
-	# this should cause a TRANSACTION-LOCK-TIMEOUT exception to be thrown
-	$db.exec("insert into family values (3, 'Test')\n");
-	test_value(True, False, "transaction timeout");
-	$db.exec("delete from family where name = 'Test'");
-	}
-	catch ($ex)
-	{
-	test_value(True, True, "transaction timeout");
-	}
-	# signal parent thread to continue
-	$c.dec();
-}
-
-sub transaction_test($db)
-{
-	my $ndb = getDS();
-	my $r;
-	tprintf(2, "db.autocommit=%N, ndb.autocommit=%N\n", $db.getAutoCommit(), $ndb.getAutoCommit());
-
-	# first, we insert a new row into "family" but do not commit it
-	my $rows = $db.exec("insert into family values (3, 'Test')\n");
-	if ($rows !== 1)
-	printf("FAILED INSERT, rows=%N\n", $rows);
-
-	# now we verify that the new row is not visible to the other datasource
-	# unless it's a sybase/ms sql server datasource, in which case this would deadlock :-(
-	if ($o.type != "sybase" && $o.type != "freetds")
-	{
-	$r = $ndb.selectRow("select name from family where family_id = 3").name;
-	test_value($r, NOTHING, "first transaction");
-	}
-
-	# now we verify that the new row is visible to the inserting datasource
-	$r = $db.selectRow("select name from family where family_id = 3").name;
-	test_value($r, "Test", "second transaction");
-
-	# test datasource timeout
-	# this Counter variable will allow the parent thread to sleep
-	# until the child thread times out
-	my $c = new Counter(1);
-	background test_timeout($db, $c);
-
-	# wait for child thread to time out
-	$c.waitForZero();
-
-	# now, we commit the transaction
-	$db.commit();
-
-	# now we verify that the new row is visible in the other datasource
-	$r = $ndb.selectRow("select name from family where family_id = 3").name;
-	test_value($r, "Test", "third transaction");
-
-	# now we delete the row we inserted (so we can repeat the test)
-	$r = $ndb.exec("delete from family where family_id = 3");
-	test_value($r, 1, "delete row count");
-	$ndb.commit();
-}
-
-sub oracle_test()
-{
-}
-
-# here we use a little workaround for modules that provide functions,
-# namespace additions (constants, classes, etc) needed by test functions
-# at parse time.  To avoid parse errors (as database modules are loaded
-# in this script at run-time when the Datasource class is instantiated)
-# we use a Program object that we parse and run on demand to return the
-# value required
-sub get_val($code)
-{
-	my $p = new Program();
-
-	my $str = sprintf("return %s;", $code);
-	$p.parse($str, "code");
-	return $p.run();
-}
-
-sub pgsql_test($db)
-{
-	my $args = ( "int2_f"          : 258,
-		 "int4_f"          : 233932,
-		 "int8_f"          : 239392939458,
-		 "bool_f"          : True,
-		 "float4_f"        : 21.3444,
-		 "float8_f"        : 49394.23423491,
-		 "number_f"        : get_val("pgsql_bind(PG_TYPE_NUMERIC, '7235634215.3250')"),
-		 "money_f"         : get_val("pgsql_bind(PG_TYPE_CASH, \"400.56\")"),
-		 "text_f"          : 'some text  ',
-		 "varchar_f"       : 'varchar ',
-		 "char_f"          : 'char text',
-		 "name_f"          : 'name',
-		 "date_f"          : 2004-01-05,
-		 "abstime_f"       : 2005-12-03T10:00:01,
-		 "reltime_f"       : 5M + 71D + 19h + 245m + 51s,
-		 "interval_f"      : 6M + 3D + 2h + 45m + 15s,
-		 "time_f"          : 11:35:00,
-		 "timetz_f"        : get_val("pgsql_bind(PG_TYPE_TIMETZ, \"11:38:21 CST\")"),
-		 "timestamp_f"     : 2005-04-01T11:35:26,
-		 "timestamptz_f"   : 2005-04-01T11:35:26.259,
-		 "tinterval_f"     : get_val("pgsql_bind(PG_TYPE_TINTERVAL, '[\"May 10, 1947 23:59:12\" \"Jan 14, 1973 03:14:21\"]')"),
-		 "bytea_f"         : <bead>
-		 #bit_f             :
-		 #varbit_f          :
-	);
-
-	$db.vexec("insert into data_test values (%v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v)", hash_values($args));
-
-	my $q = $db.selectRow("select * from data_test");
-	if ($o.verbose > 1)
-	foreach my $k in (keys $q)
-		tprintf(2, " %-16s= %-10s %N\n", $k, type($q.$k), $q.$k);
-
-	# fix values where we know the return type is different
-	$args.money_f = 400.56;
-	$args.timetz_f = 11:38:21;
-	$args.tinterval_f = '["1947-05-10 21:59:12" "1973-01-14 02:14:21"]';
-	$args.number_f = "7235634215.3250";
-	$args.reltime_f = 19177551s;
-	$args.interval_f = 6M + 3D + 9915s;
-
-	# rounding errors can happen in float4
-	$q.float4_f = round($q.float4_f);
-	$args.float4_f = round($args.float4_f);
-
-	# remove values where we know they won't match
-	# abstime and timestamptz are converted to GMT by the server
-	delete $q.abstime_f;
-	delete $q.timestamptz_f;
-
-	# compare each value
-	foreach my $k in (keys $q)
-	test_value($q.$k, $args.$k, sprintf("%s bind and retrieve", $k));
-
-	$db.commit();
-}
-
-sub mysql_test()
-{
-}
-
-const family_q = ( "family_id" : 1,
-		   "name" : "Smith" );
-const person_q = ( "person_id" : 1,
-		   "family_id" : 1,
-		   "name" : "Arnie",
-		   "dob" : 1983-05-13 );
-const params = ( "string" : "hello there",
-		 "int" : 150 );
-
-sub sybase_test($db)
-{
-	# simple stored proc test, bind by name
-	my $x = $db.exec("exec find_family %v", "Smith");
-	test_value($x, ("name": list("Smith"), "family_id" : list(1)), "simple stored proc");
-
-	# stored proc execute with output params
-	$x = $db.exec("declare @string varchar(40), @int int
-exec get_values :string output, :int output");
-	test_value($x, params + ("rowcount":1), "get_values");
-
-	# we use Datasource::selectRows() in the following queries because we
-	# get hash results instead of a hash of lists as with exec in the queries
-	# normally we should not use selectRows to execute a stored procedure,
-	# as the Datasource::selectRows() method will not grab the transaction lock,
-	# but we already called Datasource::exec() above, so we have it already.
-	# the other alternative would be to call Datasource::beginTransaction() before
-	# Datasource::selectRows()
-
-	# simple stored proc test, bind by name, returns hash
-	$x = $db.selectRows("exec find_family %v", "Smith");
-	test_value($x, family_q, "simple stored proc");
-
-	# stored proc execute with output params and select results
-	$x = $db.selectRows("declare @string varchar(40), @int int
-exec get_values_and_select :string output, :int output");
-	test_value($x, ("query":family_q,"params":params), "get_values_and_select");
-
-	# stored proc execute with output params and multiple select results
-	$x = $db.selectRows("declare @string varchar(40), @int int
-exec get_values_and_multiple_select :string output, :int output");
-	test_value($x, ("query":("query0":family_q,"query1":person_q),"params":params), "get_values_and_multiple_select");
-
-	# stored proc execute with just select results
-	$x = $db.selectRows("exec just_select");
-	test_value($x, family_q, "just_select");
-
-	# stored proc execute with multiple select results
-	$x = $db.selectRows("exec multiple_select");
-	test_value($x, ("query0":family_q,"query1":person_q), "multiple_select");
-
-	my $args = ( "null_f"          : NULL,
-		 "varchar_f"       : "varchar",
-		 "char_f"          : "char",
-		 "unichar_f"       : "unichar",
-		 "univarchar_f"    : "univarchar",
-		 "text_f"          : "test",
-		 "unitext_f"       : "test",
-		 "bit_f"           : True,
-		 "tinyint_f"       : 55,
-		 "smallint_f"      : 4285,
-		 "int_f"           : 405402,
-		 "int_f2"          : 214123498,
-		 "decimal_f"       : 500.1231,
-		 "float_f"         : 23443.234324234,
-		 "real_f"          : 213.123,
-		 "money_f"         : 3434234250.2034,
-		 "smallmoney_f"    : 211100.1012,
-		 "date_f"          : 2007-05-01,
-			 "time_f"          : 10:30:01,
-		 "datetime_f"      : 3459-01-01T11:15:02.250,
-		 "smalldatetime_f" : 2007-12-01T12:01:00,
-		 "binary_f"        : <0badbeef>,
-		 "varbinary_f"     : <feedface>,
-		 "image_f"         : <cafebead> );
-
-	# insert data
-	my $rows = $db.vexec("insert into data_test values (%v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %d, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v, %v)", hash_values($args));
-
-	my $q = $db.selectRow("select * from data_test");
-	if ($o.verbose > 1)
-	foreach my $k in (keys $q)
-		tprintf(2, " %-16s= %-10s %N\n", $k, type($q.$k), $q.$k);
-
-	# remove values where we know they won't match
-	# unitext_f is returned as IMAGE by the server
-	delete $q.unitext_f;
-	delete $args.unitext_f;
-	# rounding errors can happen in real
-	$q.real_f = round($q.real_f);
-	$args.real_f = round($args.real_f);
-
-	# compare each value
-	foreach my $k in (keys $q)
-	test_value($q.$k, $args.$k, sprintf("%s bind and retrieve", $k));
-
-	$db.commit();
-}
-
-sub freetds_test($db)
-{
-	# simple stored proc test, bind by name
-	my $x = $db.exec("exec find_family %v", "Smith");
-	test_value($x, ("name": list("Smith"), "family_id" : list(1)), "simple stored proc");
-
-	# we cannot retrieve parameters from newer SQL Servers with the approach we use;
-	# Microsoft changed the handling of the protocol and require us to use RPC calls,
-	# this will be implemented in the next version of qore where the "freetds" driver will
-	# be able to add custom methods to the Datasource class.  For now, we skip these tests
-
-	if ($db.is_sybase)
-	{
-	$x = $db.exec("declare @string varchar(40), @int int
-exec get_values :string output, :int output");
-	test_value($x, params, "get_values");
-	}
-
-	# we use Datasource::selectRows() in the following queries because we
-	# get hash results instead of a hash of lists as with exec in the queries
-	# normally we should not use selectRows to execute a stored procedure,
-	# as the Datasource::selectRows() method will not grab the transaction lock,
-	# but we already called Datasource::exec() above, so we have it already.
-	# the other alternative would be to call Datasource::beginTransaction() before
-	# Datasource::selectRows()
-
-	# simple stored proc test, bind by name, returns hash
-	$x = $db.selectRows("exec find_family %v", "Smith");
-	test_value($x, family_q, "simple stored proc");
-
-	# stored proc execute with output params and select results
-	if ($db.is_sybase)
-	{
-	$x = $db.selectRows("declare @string varchar(40), @int int
-exec get_values_and_select :string output, :int output");
-	test_value($x, ("query":family_q,"params":params), "get_values_and_select");
-
-	# stored proc execute with output params and multiple select results
-	$x = $db.selectRows("declare @string varchar(40), @int int
-exec get_values_and_multiple_select :string output, :int output");
-	test_value($x, ("query":("query0":family_q,"query1":person_q),"params":params), "get_values_and_multiple_select");
-	}
-
-	# stored proc execute with just select results
-	$x = $db.selectRows("exec just_select");
-	test_value($x, family_q, "just_select");
-
-	# stored proc execute with multiple select results
-	$x = $db.selectRows("exec multiple_select");
-	test_value($x, ("query0":family_q,"query1":person_q), "multiple_select");
-
-	# the freetds driver does not work with the following sybase column types:
-	# unichar, univarchar
-
-	my $args = ( "null_f"          : NULL,
-		 "varchar_f"       : "test",
-		 "char_f"          : "test",
-		 "text_f"          : "test",
-		 "unitext_f"       : "test",
-		 "bit_f"           : True,
-		 "tinyint_f"       : 55,
-		 "smallint_f"      : 4285,
-		 "int_f"           : 405402,
-		 "int_f2"          : 214123498,
-		 "decimal_f"       : 500.1231,
-		 "float_f"         : 23443.234324234,
-		 "real_f"          : 213.123,
-		 "money_f"         : 3434234250.2034,
-		 "smallmoney_f"    : 211100.1012,
-		 "date_f"          : 2007-05-01,
-			 "time_f"          : 10:30:01,
-		 "datetime_f"      : 3459-01-01T11:15:02.250,
-		 "smalldatetime_f" : 2007-12-01T12:01:00,
-		 "binary_f"        : <0badbeef>,
-		 "varbinary_f"     : <feedface>,
-		 "image_f"         : <cafebead> );
-
-	# remove fields not supported by sql server
-	if (!$db.is_sybase)
-	{
-	delete $args.unitext_f;
-	delete $args.date_f;
-	delete $args.time_f;
-	}
-
-	my $sql = "insert into data_test values (";
-	for (my $i; $i < elements $args; ++$i)
-	$sql += "%v, ";
-	$sql = substr($sql, 0, -2) + ")";
-
-	# insert data, using the values from the hash above
-	my $rows = $db.vexec($sql, hash_values($args));
-
-	my $q = $db.selectRow("select * from data_test");
-	if ($o.verbose > 1)
-	foreach my $k in (keys $q)
-		tprintf(2, " %-16s= %-10s %N\n", $k, type($q.$k), $q.$k);
-
-	# remove values where we know they won't match
-	# unitext_f is returned as IMAGE by the server
-	delete $q.unitext_f;
-	delete $args.unitext_f;
-	# rounding errors can happen in real
-	$q.real_f = round($q.real_f);
-	$args.real_f = round($args.real_f);
-
-	# compare each value
-	foreach my $k in (keys $q)
-	test_value($q.$k, $args.$k, sprintf("%s bind and retrieve", $k));
-
-	$db.commit();
-}
-
-sub main()
-{
-	my $test_map =
-	( "sybase" : \sybase_test(),
-	  "freetds"  : \freetds_test(),
-	  "mysql"  : \mysql_test(),
-	  "pgsql"  : \pgsql_test(),
-	  "oracle" : \oracle_test());
-
-	parse_command_line();
-	my $db = getDS();
-
-	my $driver = $db.getDriverName();
-	printf("testing %s driver\n", $driver);
-	my $sv = $db.getServerVersion();
-	if ($o.verbose > 1)
-	tprintf(2, "client version=%n\nserver version=%n\n", $db.getClientVersion(), $sv);
-
-	# determine if the server is a sybase or sql server dataserver
-	if ($driver == "freetds")
-	if ($sv !~ /microsoft/i)
-		$db.is_sybase = True;
-
-	create_datamodel($db);
-
-	context_test($db);
-	transaction_test($db);
-	my $test = $test_map.($db.getDriverName());
-	if (exists $test)
-	$test($db);
-
-	if (!$o.leave)
-	drop_test_datamodel($db);
-	printf("%d/%d tests OK\n", $test_count - $errors, $test_count);
-}
-
-main();
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-r.html b/docs/_style/prism-master/examples/prism-r.html deleted file mode 100644 index 3c694189..00000000 --- a/docs/_style/prism-master/examples/prism-r.html +++ /dev/null @@ -1,38 +0,0 @@ -

Comments

-
# This is a comment
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
- -

Full example

-
# Goal: To make a latex table with results of an OLS regression.
-
-# Get an OLS --
-x1 = runif(100)
-x2 = runif(100, 0, 2)
-y = 2 + 3*x1 + 4*x2 + rnorm(100)
-m = lm(y ~ x1 + x2)
-
-# and print it out prettily --
-library(xtable)
-# Bare --
-xtable(m)
-xtable(anova(m))
-
-# Better --
-print.xtable(xtable(m, caption="My regression",
-                    label="t:mymodel",
-                    digits=c(0,3,2,2,3)),
-             type="latex",
-             file="xtable_demo_ols.tex",
-             table.placement = "tp",
-             latex.environments=c("center", "footnotesize"))
-
-print.xtable(xtable(anova(m),
-                    caption="ANOVA of my regression",
-                    label="t:anova_mymodel"),
-             type="latex",
-             file="xtable_demo_anova.tex",
-             table.placement = "tp",
-             latex.environments=c("center", "footnotesize"))
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-reason.html b/docs/_style/prism-master/examples/prism-reason.html deleted file mode 100644 index 8842f24c..00000000 --- a/docs/_style/prism-master/examples/prism-reason.html +++ /dev/null @@ -1,35 +0,0 @@ -

Comments

-
/* This is a comment */
- -

Strings and characters

-
"This is a \"string\""
-'a'
-'\\'
-'\o123'
-'\x4a'
- -

Constructors

-
type response =
-  | Yes
-  | No
-  | PrettyMuch;
- -

Example

-
type car = {maker: string, model: string};
-type carList =
-  | List car carList
-  | NoMore;
-
-let chevy = {maker: "Chevy", model: "Suburban"};
-let toyota = {maker: "Toyota", model: "Tacoma"};
-let myCarList = List chevy (List toyota NoMore);
-
-let hasExactlyTwoCars = fun lst =>
-  switch lst {
-    | NoMore => false                              /* 0 */
-    | List p NoMore => false                       /* 1 */
-    | List p (List p2 NoMore) => true              /* 2 */
-    | List p (List p2 (List p3 theRest)) => false  /* 3+ */
-  };
-
-let justTwo = hasExactlyTwoCars myCarList;  /* true! */
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-renpy.html b/docs/_style/prism-master/examples/prism-renpy.html deleted file mode 100644 index dffac07c..00000000 --- a/docs/_style/prism-master/examples/prism-renpy.html +++ /dev/null @@ -1,123 +0,0 @@ -

Comments

-
-    # This is a comment
-
- -

Strings

-
-    "foo \"bar\" baz"
-'foo \'bar\' baz'
-""" "Multi-line" strings
-are supported."""
-''' 'Multi-line' strings
-are supported.'''
-
- -

Python

-
-    class Dog:
-
-    tricks = []             # mistaken use of a class variable
-
-    def __init__(self, name):
-        self.name = name
-
-    def add_trick(self, trick):
-        self.tricks.append(trick)
-
- -

Properties

-
-    style my_text is text:
-    size 40
-    font "gentium.ttf"
-
- -

Configuration

-
-    init -1:
-    python hide:
-
-        ## Should we enable the use of developer tools? This should be
-        ## set to False before the game is released, so the user can't
-        ## cheat using developer tools.
-
-        config.developer = True
-
-        ## These control the width and height of the screen.
-
-        config.screen_width = 800
-        config.screen_height = 600
-
-        ## This controls the title of the window, when Ren'Py is
-        ## running in a window.
-
-        config.window_title = u"The Question"
-
- - -

Full example

-
# Declare images used by this game.
-image bg lecturehall = "lecturehall.jpg"
-image bg uni = "uni.jpg"
-image bg meadow = "meadow.jpg"
-image bg club = "club.jpg"
-
-image sylvie normal = "sylvie_normal.png"
-image sylvie giggle = "sylvie_giggle.png"
-image sylvie smile = "sylvie_smile.png"
-image sylvie surprised = "sylvie_surprised.png"
-
-image sylvie2 normal = "sylvie2_normal.png"
-image sylvie2 giggle = "sylvie2_giggle.png"
-image sylvie2 smile = "sylvie2_smile.png"
-image sylvie2 surprised = "sylvie2_surprised.png"
-
-# Define characters used by this game.
-define s = Character('Sylvie', color="#c8ffc8")
-define m = Character('Me', color="#c8c8ff")
-
-
-# The game starts here.
-label start:
-
-    $ bl_game = False
-
-    play music "illurock.ogg"
-
-    scene bg lecturehall
-    with fade
-
-    "Well, professor Eileen's lecture was interesting."
-    "But to be honest, I couldn't concentrate on it very much."
-    "I had a lot of other thoughts on my mind."
-    "And they all ended up with a question."
-    "A question, I've been meaning to ask someone."
-
-    scene bg uni
-    with fade
-
-    "When we came out of the university, I saw her."
-
-    show sylvie normal
-    with dissolve
-
-    "She was a wonderful person."
-    "I've known her ever since we were children."
-    "And she's always been a good friend."
-    "But..."
-    "Recently..."
-    "I think..."
-    "... that I wanted more."
-    "More just talking... more than just walking home together when our classes ended."
-    "And I decided..."
-
-    menu:
-
-        "... to ask her right away.":
-
-            jump rightaway
-
-        "... to ask her later.":
-
-            jump later
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-rest.html b/docs/_style/prism-master/examples/prism-rest.html deleted file mode 100644 index 511e87c9..00000000 --- a/docs/_style/prism-master/examples/prism-rest.html +++ /dev/null @@ -1,329 +0,0 @@ -

Titles

-
===============
- Section Title
-===============
-
----------------
- Section Title
----------------
-
-Section Title
-=============
-
-Section Title
--------------
-
-Section Title
-`````````````
-
-Section Title
-'''''''''''''
-
-Section Title
-.............
-
-Section Title
-~~~~~~~~~~~~~
-
-Section Title
-*************
-
-Section Title
-+++++++++++++
-
-Section Title
-^^^^^^^^^^^^^
- -

Lists

-
- This is the first bullet list item.
-- This is the first paragraph in the second item in the list.
-
-  This is the second paragraph in the second item in the list.
-
-  - This is a sublist.  The bullet lines up with the left edge of
-    the text blocks above.
-
-- This is the third item of the main list.
-
-This paragraph is not part of the list.
-
-1. Item 1 initial text.
-
-   a) Item 1a.
-   b) Item 1b.
-
-2. a) Item 2a.
-   b) Item 2b.
- -

Field lists

-
:Date: 2001-08-16
-:Version: 1
-:Authors: - Me
-          - Myself
-          - I
-:Indentation: Since the field marker may be quite long, the second
-   and subsequent lines of the field body do not have to line up
-   with the first line, but they must be indented relative to the
-   field name marker, and they must line up with each other.
-:Parameter i: integer
- -

Option lists

-
-a         Output all.
--b         Output both (this description is
-           quite long).
--c arg     Output just arg.
---long     Output all day long.
-
--p         This option has two paragraphs in the description.
-           This is the first.
-
-           This is the second.  Blank lines may be omitted between
-           options (as above) or left in (as here and below).
-
---very-long-option  A VMS-style option.  Note the adjustment for
-                    the required two spaces.
-
---an-even-longer-option
-           The description can also start on the next line.
-
--2, --two  This option has two variants.
-
--f FILE, --file=FILE  These two options are synonyms; both have
-                      arguments.
-
-/V         A VMS/DOS-style option.
- -

Literal blocks

-
::
-
-    for a in [5,4,3,2,1]:   # this is program code, shown as-is
-        print a
-    print "it's..."
-    # a literal block continues until the indentation ends
-
-John Doe wrote::
-
->> *Great* idea!
->
-> Why didn't I think of that?
-
-You just did!  ;-)
- -

Line blocks

-
| Lend us a couple of bob till Thursday.
-| I'm absolutely skint.
-| But I'm expecting a postal order and I can pay you back
-  as soon as it comes.
-| Love, Ewan.
-
-Take it away, Eric the Orchestra Leader!
-
-    | A one, two, a one two three four
-    |
-    | Half a bee, philosophically,
-    |     must, *ipso facto*, half not be.
-    | But half the bee has got to be,
-    |     *vis a vis* its entity.  D'you see?
-    |
-    | But can a bee be said to be
-    |     or not to be an entire bee,
-    |         when half the bee is not a bee,
-    |             due to some ancient injury?
-    |
-    | Singing...
- -

Grid tables and simple tables

-
+------------------------+------------+----------+----------+
-| Header row, column 1   | Header 2   | Header 3 | Header 4 |
-| (header rows optional) |            |          |          |
-+========================+============+==========+==========+
-| body row 1, column 1   | column 2   | column 3 | column 4 |
-+------------------------+------------+----------+----------+
-| body row 2             | Cells may span columns.          |
-+------------------------+------------+---------------------+
-| body row 3             | Cells may  | - Table cells       |
-+------------------------+ span rows. | - contain           |
-| body row 4             |            | - body elements.    |
-+------------------------+------------+---------------------+
-
-	+--------------+----------+-----------+-----------+
-	| row 1, col 1 | column 2 | column 3  | column 4  |
-	+--------------+----------+-----------+-----------+
-	| row 2        |                                  |
-	+--------------+----------+-----------+-----------+
-	| row 3        |          |           |           |
-	+--------------+----------+-----------+-----------+
-
-=====  =====  =======
-  A      B    A and B
-=====  =====  =======
-False  False  False
-True   False  False
-False  True   False
-True   True   True
-=====  =====  =======
-
-	=====  =====  ======
-	   Inputs     Output
-	------------  ------
-	  A      B    A or B
-	=====  =====  ======
-	False  False  False
-	True   False  True
-	False  True   True
-	True   True   True
-	=====  =====  ======
- -

Footnotes and links

-
.. [1] Body elements go here.
-
-If [#note]_ is the first footnote reference, it will show up as
-"[1]".  We can refer to it again as [#note]_ and again see
-"[1]".  We can also refer to it as note_ (an ordinary internal
-hyperlink reference).
-
-.. [#note] This is the footnote labeled "note".
-
-Here is a symbolic footnote reference: [*]_.
-
-.. [*] This is the footnote.
-
-[2]_ will be "2" (manually numbered),
-[#]_ will be "3" (anonymous auto-numbered), and
-[#label]_ will be "1" (labeled auto-numbered).
-
-.. [2] This footnote is labeled manually, so its number is fixed.
-
-.. [#label] This autonumber-labeled footnote will be labeled "1".
-   It is the first auto-numbered footnote and no other footnote
-   with label "1" exists.  The order of the footnotes is used to
-   determine numbering, not the order of the footnote references.
-
-.. [#] This footnote will be labeled "3".  It is the second
-   auto-numbered footnote, but footnote label "2" is already used.
-
-Here is a citation reference: [CIT2002]_.
-
-.. [CIT2002] This is the citation.  It's just like a footnote,
-   except the label is textual.
-
-.. _hyperlink-name: link-block
-
-.. __: anonymous-hyperlink-target-link-block
-
-__ anonymous-hyperlink-target-link-block
-
-Clicking on this internal hyperlink will take us to the target_
-below.
-
-.. _target:
-
-The hyperlink target above points to this paragraph.
- -

Directives

-
.. image:: mylogo.jpeg
-
-.. figure:: larch.png
-
-   The larch.
-
-.. note:: This is a paragraph
-
-   - Here is a bullet list.
-
-.. figure:: picture.png
-   :scale: 50 %
-   :alt: map to buried treasure
-
-   This is the caption of the figure (a simple paragraph).
-
-   The legend consists of all elements after the caption.  In this
-   case, the legend consists of this paragraph and the following
-   table:
-
-   +-----------------------+-----------------------+
-   | Symbol                | Meaning               |
-   +=======================+=======================+
-   | .. image:: tent.png   | Campground            |
-   +-----------------------+-----------------------+
-   | .. image:: waves.png  | Lake                  |
-   +-----------------------+-----------------------+
-   | .. image:: peak.png   | Mountain              |
-   +-----------------------+-----------------------+
- -

Substitutions

-
The |biohazard| symbol must be used on containers used to
-dispose of medical waste.
-
-.. |biohazard| image:: biohazard.png
-
-|Michael| and |Jon| are our widget-wranglers.
-
-.. |Michael| user:: mjones
-.. |Jon|     user:: jhl
-
-West led the |H| 3, covered by dummy's |H| Q, East's |H| K,
-and trumped in hand with the |S| 2.
-
-.. |H| image:: /images/heart.png
-   :height: 11
-   :width: 11
-.. |S| image:: /images/spade.png
-   :height: 11
-   :width: 11
-
-* |Red light| means stop.
-* |Green light| means go.
-* |Yellow light| means go really fast.
-
-.. |Red light|    image:: red_light.png
-.. |Green light|  image:: green_light.png
-.. |Yellow light| image:: yellow_light.png
- -

Comments

-
.. This is a comment
-
-..
-   _so: is this!
-
-..
-   [and] this!
-
-..
-   this:: too!
-
-..
-   |even| this:: !
- -

Inline markup

-
This is *emphasized text*.
-This is **strong text**.
-This is `interpreted text`.
-:role:`interpreted text`
-`interpreted text`:role:
-This text is an example of ``inline literals``.
-The regular expression ``[+-]?(\d+(\.\d*)?|\.\d+)`` matches
-floating-point numbers (without exponents).
-
-See the `Python home page <http://www.python.org>`_ for info.
-
-Oh yes, the _`Norwegian Blue`.  What's, um, what's wrong with it?
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nothing is highlighted inside table cells

-
+---------------+----------+
-| column 1     | column 2  |
-+--------------+-----------+
-| **bold**?    | *italic*? |
-+--------------+-----------+
- -

The inline markup recognition rules are not as strict as they are in the spec

-

No inline markup should be highlighted in the following code.

-
2 * x a ** b (* BOM32_* ` `` _ __ |
-"*" '|' (*) [*] {*} <*> ‘*’ ‚*‘ ‘*‚ ’*’ ‚*’ “*” „*“ “*„ ”*” „*” »*« ›*‹ «*» »*» ›*›
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-rip.html b/docs/_style/prism-master/examples/prism-rip.html deleted file mode 100644 index 99769c3b..00000000 --- a/docs/_style/prism-master/examples/prism-rip.html +++ /dev/null @@ -1,12 +0,0 @@ -

Comments

-
# This is a comment
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
- -

Regex

-
regular_expression = /abc/
- -

Symbols

-
string_symbol = :rip 
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-roboconf.html b/docs/_style/prism-master/examples/prism-roboconf.html deleted file mode 100644 index ca57221e..00000000 --- a/docs/_style/prism-master/examples/prism-roboconf.html +++ /dev/null @@ -1,49 +0,0 @@ -

Full example

-
ApacheServer {
-    # Apache instances will be deployed by Roboconf's Puppet extension
-    installer: puppet;
-
-    # Web applications could be deployed over this Apache server
-    children: My-Dash-Board, Marketing-Suite;
-
-    # Properties exported by this component.
-    # 'port' should have a default value, or we will have to set it when we create an instance.
-    exports: port = 19099;
-
-    # 'ip' is a special variable. It will be updated at runtime by a Roboconf agent.
-    exports: ip;
-
-    # Other components properties that this server needs to have so that it can start.
-    imports: LB.port (optional), LB.ip (optional);
-
-    # Here, the Apache may also be notified about components instances of type LB.
-    # The imports are marked as optional. It means that if there is no LB instance, an
-    # Apache instance will be able to start anyway.
-    #
-    # If the import was not optional, e.g.
-    #
-    # imports: LB.port, LB.ip;
-    # or even
-    # imports: LB.port (optional), LB.ip;
-    #
-    # ... then an Apache instance would need at least one LB instance somewhere.
-
-    # Imports may also reference variables from other applications
-    imports: external Lamp.lb-ip;
-}
-
-facet LoadBalanced {
-    exports: ip, port;  # Define we export two variables.
-}
-
-instance of VM {
-
-    # This will create 5 VM instances, called VM 1, VM 2, VM3, VM 4 and VM 5.
-    name: VM ;  # Yes, there is a space at the end... :)
-    count: 5;
-
-    # On every VM instance, we will deploy...
-    instance of Tomcat {
-        name: Tomcat;
-    }
-}
diff --git a/docs/_style/prism-master/examples/prism-ruby.html b/docs/_style/prism-master/examples/prism-ruby.html deleted file mode 100644 index 4f1e3906..00000000 --- a/docs/_style/prism-master/examples/prism-ruby.html +++ /dev/null @@ -1,30 +0,0 @@ -

Comments

-
# This is a comment
-=begin
-Multi-line
-comment
-=end
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
- -

Regular expressions

-
/foo?[ ]*bar/
- -

Variables

-
$foo = 5;
-class InstTest
-  def set_foo(n)
-    @foo = n
-  end
-  def set_bar(n)
-    @bar = n
-  end
-end
- -

Symbols

-
mystring = :steveT;
- -

String Interpolation

-
"foo #{'bar'+my_variable}"
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-rust.html b/docs/_style/prism-master/examples/prism-rust.html deleted file mode 100644 index d4479684..00000000 --- a/docs/_style/prism-master/examples/prism-rust.html +++ /dev/null @@ -1,68 +0,0 @@ -

Comments

-
// Single line comment
-/// Doc comments
-/* Multiline
-comment */
- -

Strings

-
'C'; '\''; '\n'; '\u7FFF'; // Characters
-"foo \"bar\" baz"; // String
-r##"foo #"bar"# baz"##; // Raw string with # pairs
-b'C'; b'\''; b'\n'; // Bytes
-b"foo \"bar\" baz"; // Byte string
-br##"foo #"bar"# baz"##; // Raw byte string with # pairs
-
- -

Numbers

-
123i;                              // type int
-123u;                              // type uint
-123_u;                             // type uint
-0xff_u8;                           // type u8
-0o70_i16;                          // type i16
-0b1111_1111_1001_0000_i32;         // type i32
-
-123.0f64;        // type f64
-0.1f64;          // type f64
-0.1f32;          // type f32
-12E+99_f64;      // type f64
-
- -

Booleans

-
true; false;
- -

Functions and macros

-
println!("x is {}", x);
-fn next_two(x: int) -> (int, int) { (x + 1i, x + 2i) }
-next_two(5i);
-vec![1i, 2, 3];
-
- -

Attributes

-
#![warn(unstable)]
-#[test]
-fn a_test() {
-	// ...
-}
- -

Closure parameters and bitwise OR

-
let x = a | b;
-let y = c || d;
-let add_one = |x: int| -> int { 1i + x };
-let printer = || { println!("x is: {}", x); };
-
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested block comments

-
/* Nested block
-	/* comments
-	are */
-not supported */
- -

Delimiters of parameters for closures that don't use braces

-
|x| x + 1i;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-sas.html b/docs/_style/prism-master/examples/prism-sas.html deleted file mode 100644 index 8932ebb0..00000000 --- a/docs/_style/prism-master/examples/prism-sas.html +++ /dev/null @@ -1,158 +0,0 @@ -

Comments

-
/* This is a
-multi-line comment */
-
-* This is a comment too;
- -

Numbers, dates and times

-
42; 4.5; 4.5e-10; -3; -3.5e2; -4.2e-23;
-0afx; 0123x; abcdefx;
-'1jan2013'd; '01jan09'd;
-'9:25't; '9:25:19pm't;
-'01may12:9:30:00'dt; '18jan2003:9:27:05am'dt;
-'2013-05-17T09:15:30–05:00'dt; '2013-05-17T09:15:30–05'dt;
-'2013-07-20T12:00:00+00:00'dt; '2013-07-20T12:00:00Z'dt;
- -

Strings

-
'Single quoted string';
-"Double quoted string";
-'String ''quoted'' string "containing" quote';
-"Double ""quoted"" string 'containing' quote";
- -

Operators

-
A**B;
-'foo'||'bar'!!'baz'¦¦'test';
-A<>B><C;
-A~=B¬=C^=D>=E<=F;
-a*b/c+d-e<f>g&h|i!j¦k;
-~a;¬b;^c;
-(a eq b) ne (c gt d) lt e ge f le h;
-state in ('NY','NJ','PA');
-not a;
- -

More examples

-
/* Some examples adapted from the documentation (http://support.sas.com/documentation/cdl/en/basess/64003/PDF/default/basess.pdf) */
-
-data city; * another inline comment;
-
-	input Year 4. @7 ServicesPolice comma6.
-		@15 ServicesFire comma6. @22 ServicesWater_Sewer comma6.
-		@30 AdminLabor comma6. @39 AdminSupplies comma6.
-		@45 AdminUtilities comma6.;
-	ServicesTotal=ServicesPolice+ServicesFire+ServicesWater_Sewer;
-	AdminTotal=AdminLabor+AdminSupplies+AdminUtilities;
-	Total=ServicesTotal+AdminTotal;
-
-	Test='A string '' whith a quote';
-	Test2 = "A string "" whith a quote";
-
-	label   Total='Total Outlays'
-			ServicesTotal='Services: Total'
-			ServicesPolice='Services: Police'
-			ServicesFire='Services: Fire'
-			ServicesWater_Sewer='Services: Water & Sewer'
-			AdminTotal='Administration: Total'
-			AdminLabor='Administration: Labor'
-			AdminSupplies='Administration: Supplies'
-			AdminUtilities='Administration: Utilities';
-	datalines;
-1993 2,819 1,120 422 391 63 98
-1994 2,477 1,160 500 172 47 70
-1995 2,028 1,061 510 269 29 79
-1996 2,754 893 540 227 21 67
-1997 2,195 963 541 214 21 59
-1998 1,877 926 535 198 16 80
-1999 1,727 1,111 535 213 27 70
-2000 1,532 1,220 519 195 11 69
-2001 1,448 1,156 577 225 12 58
-2002 1,500 1,076 606 235 19 62
-2003 1,934 969 646 266 11 63
-2004 2,195 1,002 643 256 24 55
-2005 2,204 964 692 256 28 70
-2006 2,175 1,144 735 241 19 83
-2007 2,556 1,341 813 238 25 97
-2008 2,026 1,380 868 226 24 97
-2009 2,526 1,454 946 317 13 89
-2010 2,027 1,486 1,043 226 . 82
-2011 2,037 1,667 1,152 244 20 88
-2012 2,852 1,834 1,318 270 23 74
-2013 2,787 1,701 1,317 307 26 66
-;
-proc datasets library=work nolist
-;
-contents data=city
-;
-run;
-
-
-data city3;
-	set city(firstobs=10 obs=15);
-run;
-
-data services (keep=ServicesTotal ServicesPolice ServicesFire
-				ServicesWater_Sewer)
-	admin (keep=AdminTotal AdminLabor AdminSupplies
-			AdminUtilities);
-	set city(drop=Total);
-run;
-proc print data=services;
-	title 'City Expenditures: Services';
-run;
-
-data newlength;
-	set mylib.internationaltours;
-	length Remarks $ 30;
-	if Vendor = 'Hispania' then Remarks = 'Bonus for 10+ people';
-	else if Vendor = 'Mundial' then Remarks = 'Bonus points';
-	else if Vendor = 'Major' then Remarks = 'Discount for 30+ people';
-run;
-proc print data=newlength;
-	var Country Vendor Remarks;
-	title 'Information About Vendors';
-run;
-
-libname mylib 'permanent-data-library';
-data mylib.departures;
-	input Country $ 1-9 CitiesInTour 11-12 USGate $ 14-26
-	ArrivalDepartureGates $ 28-48;
-	datalines;
-Japan 5 San Francisco Tokyo, Osaka
-Italy 8 New York Rome, Naples
-Australia 12 Honolulu Sydney, Brisbane
-Venezuela 4 Miami Caracas, Maracaibo
-Brazil 4 Rio de Janeiro, Belem
-;
-proc print data=mylib.departures;
-	title 'Data Set AIR.DEPARTURES';
-run;
-
-data missingval;
-	length Country $ 10 TourGuide $ 10;
-	input Country TourGuide;
-	* lines is an alias for datalines;
-	lines;
-Japan Yamada
-Italy Militello
-Australia Edney
-Venezuela .
-Brazil Cardoso
-;
-
-data inventory_tool;
-	input PartNumber $ Description $ InStock @17
-		ReceivedDate date9. @27 Price;
-	format ReceivedDate date9.;
-	* cards is an alias for datalines;
-	cards;
-K89R seal 34 27jul2010 245.00
-M4J7 sander 98 20jun2011 45.88
-LK43 filter 121 19may2011 10.99
-MN21 brace 43 10aug2012 27.87
-BC85 clamp 80 16aug2012 9.55
-NCF3 valve 198 20mar2012 24.50
-KJ66 cutter 6 18jun2010 19.77
-UYN7 rod 211 09sep2010 11.55
-JD03 switch 383 09jan2013 13.99
-BV1E timer 26 03aug2013 34.50
-;
-run;
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-sass.html b/docs/_style/prism-master/examples/prism-sass.html deleted file mode 100644 index 7200e789..00000000 --- a/docs/_style/prism-master/examples/prism-sass.html +++ /dev/null @@ -1,47 +0,0 @@ -

Comments

-
/* This comment will appear in the CSS output.
-  This is nested beneath the comment,
-  so it's part of it
-
-// This comment will not appear in the CSS output.
-  This is nested beneath the comment as well,
-  so it also won't appear
- -

At-rules and shortcuts

-
@mixin large-text
-  color: #ff0000
-
-@media (min-width: 600px)
-  h1
-    @include large-text
-
-=large-text
-  color: #ff0000
-
-h1
-  +large-text
- -

Variables

-
$width: 5em
-#main
-  width: $width
-
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Deprecated Sass syntax is not supported

-
.page
-  color = 5px + 9px
-
-!width = 13px
-.icon
-  width = !width
- -

Selectors with pseudo classes are highlighted as property/value pairs

-
a:hover
-  text-decoration: underline
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-scala.html b/docs/_style/prism-master/examples/prism-scala.html deleted file mode 100644 index 1dc56f87..00000000 --- a/docs/_style/prism-master/examples/prism-scala.html +++ /dev/null @@ -1,100 +0,0 @@ -

Comments

-
// Single line comment
-/* Mutli-line
-comment */
- -

Strings and characters

-
'a'
-"foo bar baz"
-"""Multi-line
-string"""
- -

Numbers

-
0
-21
-0xFFFFFFFF
--42L
-0.0
-1e30f
-3.14159f
-1.0e-100
-.1
-
- -

Symbols

-
'x
-'identifier
- -

Full example

-
// Contributed by John Williams
-package examples
-
-object lazyLib {
-
-  /** Delay the evaluation of an expression until it is needed. */
-  def delay[A](value: => A): Susp[A] = new SuspImpl[A](value)
-
-  /** Get the value of a delayed expression. */
-  implicit def force[A](s: Susp[A]): A = s()
-
-  /**
-   * Data type of suspended computations. (The name froms from ML.)
-   */
-  abstract class Susp[+A] extends Function0[A]
-
-  /**
-   * Implementation of suspended computations, separated from the
-   * abstract class so that the type parameter can be invariant.
-   */
-  class SuspImpl[A](lazyValue: => A) extends Susp[A] {
-    private var maybeValue: Option[A] = None
-
-    override def apply() = maybeValue match {
-      case None =>
-        val value = lazyValue
-        maybeValue = Some(value)
-        value
-	  case Some(value) =>
-        value
-    }
-
-    override def toString() = maybeValue match {
-      case None => "Susp(?)"
-      case Some(value) => "Susp(" + value + ")"
-    }
-  }
-}
-
-object lazyEvaluation {
-  import lazyLib._
-
-  def main(args: Array[String]) = {
-    val s: Susp[Int] = delay { println("evaluating..."); 3 }
-
-    println("s     = " + s)       // show that s is unevaluated
-    println("s()   = " + s())     // evaluate s
-    println("s     = " + s)       // show that the value is saved
-    println("2 + s = " + (2 + s)) // implicit call to force()
-
-    val sl = delay { Some(3) }
-    val sl1: Susp[Some[Int]] = sl
-    val sl2: Susp[Option[Int]] = sl1   // the type is covariant
-
-    println("sl2   = " + sl2)
-    println("sl2() = " + sl2())
-    println("sl2   = " + sl2)
-  }
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested block comments

-
/* Nested block
-	/* comments
-	are */
-not supported */
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-scheme.html b/docs/_style/prism-master/examples/prism-scheme.html deleted file mode 100644 index de50f226..00000000 --- a/docs/_style/prism-master/examples/prism-scheme.html +++ /dev/null @@ -1,35 +0,0 @@ -

Comments

-
; This is a comment
- -

Booleans

-
#t
-#f
- -

Strings

-
"two \"quotes\" within"
- -

Functions

-
(lambda (x) (+ x 3))
-(apply vector 'a 'b '(c d e))
-
- -

Full example

-
;; Calculation of Hofstadter's male and female sequences as a list of pairs
-
-(define (hofstadter-male-female n)
-  (letrec ((female (lambda (n)
-		     (if (= n 0)
-			 1
-			 (- n (male (female (- n 1)))))))
-	   (male (lambda (n)
-		   (if (= n 0)
-		       0
-		       (- n (female (male (- n 1))))))))
-    (let loop ((i 0))
-      (if (> i n)
-	  '()
-	  (cons (cons (female i)
-		      (male i))
-		(loop (+ i 1)))))))
-
-(hofstadter-male-female 8)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-scss.html b/docs/_style/prism-master/examples/prism-scss.html deleted file mode 100644 index f0daaf57..00000000 --- a/docs/_style/prism-master/examples/prism-scss.html +++ /dev/null @@ -1,31 +0,0 @@ -

Comments

-
// Single line comment
-/* Multi-line
-comment */
- -

At-rules

-
@import "http://github.com/foo.scss";
-@media (min-width: 600px) {}
-.seriousError {
-    @extend .error;
-}
-@for $i from 1 through 3 {}
-
- -

Compass URLs

-
@font-face {
-	font-family: "opensans";
-	src: font-url("http://github.com/opensans.ttf");
-}
- -

Variables

-
$width: 5em;
-#main {
-    width: $width;
-}
- -

Interpolations are highlighted in property names

-
p.#{$name} {
-    #{$attr}-color: blue;
-}
-
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-smalltalk.html b/docs/_style/prism-master/examples/prism-smalltalk.html deleted file mode 100644 index 7fb70f8f..00000000 --- a/docs/_style/prism-master/examples/prism-smalltalk.html +++ /dev/null @@ -1,92 +0,0 @@ -

Numbers

-
3
-30.45
--3
-0.005
--14.0
-13772
-8r377
-8r153
-8r34.1
-8r-37
-16r106
-16rFF
-16rAC.DC
-16r-1.C
-1.586e5
-1.586e-3
-8r3e2
-2r11e6
- -

Strings and characters

-
$a
-$M
-$-
-$$
-$1
-'hi'
-'food'
-'the Smalltalk-80 system'
-'can''t'
- -

Symbols

-
#bill
-#M63
-#+
-#*
- -

Arrays

-
#(1 2 3)
-#('food' 'utilities' 'rent' 'household' 'transportation' 'taxes' 'recreation')
-#(('one' 1) ('not' 'negative') 0 -1)
-#(9 'nine' $9 (0 'zero' $0 ( ) 'e' $f 'g' $h 'i'))
- -

Blocks

-
sum := 0.
-#(2 3 5 7 11) do: [ :primel | sum := sum + (prime * prime)]
-
-sizeAdder := [ :array | total := total + array size].
-
-[ :x :y | (x * x) + (y * y)]
-[ :frame :clippingBox | frame intersect: clippingBox]
- -

Full example

-
Object>>method: num
-    "comment 123"
-    | var1 var2 |
-    (1 to: num) do: [:i | |var| ^i].
-    Klass with: var1.
-    Klass new.
-    arr := #('123' 123.345 #hello Transcript var $@).
-    arr := #().
-    var2 = arr at: 3.
-    ^ self abc
-
-heapExample
-    "HeapTest new heapExample"
-    "Multiline
-    decription"
-    | n rnd array time sorted |
-    n := 5000.
-    "# of elements to sort"
-    rnd := Random new.
-    array := (1 to: n)
-                collect: [:i | rnd next].
-    "First, the heap version"
-    time := Time
-                millisecondsToRun: [sorted := Heap withAll: array.
-    1
-        to: n
-        do: [:i |
-            sorted removeFirst.
-            sorted add: rnd next]].
-    Transcript cr; show: 'Time for Heap: ' , time printString , ' msecs'.
-    "The quicksort version"
-    time := Time
-                millisecondsToRun: [sorted := SortedCollection withAll: array.
-    1
-        to: n
-        do: [:i |
-            sorted removeFirst.
-            sorted add: rnd next]].
-    Transcript cr; show: 'Time for SortedCollection: ' , time printString , ' msecs'
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-smarty.html b/docs/_style/prism-master/examples/prism-smarty.html deleted file mode 100644 index 5e57922f..00000000 --- a/docs/_style/prism-master/examples/prism-smarty.html +++ /dev/null @@ -1,81 +0,0 @@ -

Comments

-
{* This is a comment with <p>some markup</p> in it *}
-{* Multi-line
-comment *}
- -

Variables

-
{$foo}
-{$foo.bar}
-{$foo.$bar}
-{$foo[$bar]}
-{$foo->bar}
-{$foo->bar()}
-{#foo#}
-{$smarty.config.foo}
-{$foo[bar]}
-
- -

Strings and numbers

-
{$foo[4]}
-{$foo['bar']}
- -

Tags and filters

-
{assign var=foo value='baa'}
-{include file='header.tpl'}
-{$smarty.now|date_format:'%Y-%m-%d %H:%M:%S'}
-{$title|truncate:40:'...'}
-{$myArray|@count}
-
-{math equation="height * width / division"
-   height=$row_height
-   width=$row_width
-   division=#col_div#}
- -

Control flow

- -
{if ( $amount < 0 or $amount > 1000 ) and $volume >= #minVolAmt#}
-   ...
-{/if}
-{if count($var) gt 0}{/if}
-{if $var is even by 3}
-   ...
-{/if}
-
-{foreach from=$myArray item=i name=foo}
-	{$smarty.foreach.foo.index}|{$smarty.foreach.foo.iteration},
-{/foreach}
-
-<ul>
-{foreach from=$items key=myId item=i}
-  <li><a href="http://github.com/item.php?id={$myId}">{$i.no}: {$i.label}</li>
-{/foreach}
-</ul>
-
- -

Literal section

-
{literal}
-	<script>
-		(function() { /* This is JS, not Smarty */ } ());
-	</script>
-{/literal}
-
-<style type="text/css">
-{literal}
-/* this is an intersting idea for this section */
-.madIdea{
-    border: 3px outset #ffffff;
-    margin: 2 3 4 5px;
-    background-color: #001122;
-}
-{/literal}
-</style>
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Smarty tag in the middle of an HTML tag

-
<div{if $test} class="test"{/if}></div>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-soy.html b/docs/_style/prism-master/examples/prism-soy.html deleted file mode 100644 index 648b8e5f..00000000 --- a/docs/_style/prism-master/examples/prism-soy.html +++ /dev/null @@ -1,36 +0,0 @@ -

Comments

-
/* Multi-line
-comment */
-// This is a comment with <p>some markup</p> in it
- -

Variable

-
{$name}
-{$folders[0]['name']}
-{$aaa?.bbb.ccc?[0]}
- -

Commands

-
{template .helloNames}
-  // Greet the person.
-  {call .helloName data="all" /}
- // Greet the additional people. - {foreach $additionalName in $additionalNames} - {call .helloName} - {param name: $additionalName /} - {/call} - {if not isLast($additionalName)} - <br> // break after every line except the last - {/if} - {ifempty} - No additional people to greet. - {/foreach} -{/template}
- -

Functions and print directives

-
{if length($items) > 5}
-{$foo|changeNewlineToBr}
-{$bar|truncate: 4, false}
- -

Literal section

-
{literal}
-This is not a {$variable}
-{/literal}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-sql.html b/docs/_style/prism-master/examples/prism-sql.html deleted file mode 100644 index 6fe99775..00000000 --- a/docs/_style/prism-master/examples/prism-sql.html +++ /dev/null @@ -1,34 +0,0 @@ -

Comments

-
# Single line comment
--- Single line comment
-// Single line comment
-/* Multi-line
-comment */
- -

Strings

-
"foo \"bar\" baz"
-'foo \'bar\' baz'
-"Multi-line strings
-are supported"
-'Multi-line strings
-are supported'
- -

Variables

-
SET @variable = 1;
-SET @$_ = 2;
-SET @"quoted-variable" = 3;
-SET @'quoted-variable' = 3;
-SET @`quoted-variable` = 3;
- -

Operators

-
SELECT 1 && 1;
-SELECT 1 OR NULL;
-SELECT 5 & 2*3;
-SELECT 2 BETWEEN 1 AND 3;
- -

Functions and keywords

-
SELECT COUNT(*) AS cpt, MAX(t.pos) AS max_pos
-FROM `my_table`
-LEFT JOIN `other_table` AS t
-WHERE `somecol` IS NOT NULL
-ORDER BY t.other_col DESC
diff --git a/docs/_style/prism-master/examples/prism-stylus.html b/docs/_style/prism-master/examples/prism-stylus.html deleted file mode 100644 index 2b8ffdca..00000000 --- a/docs/_style/prism-master/examples/prism-stylus.html +++ /dev/null @@ -1,72 +0,0 @@ -

Full Example

-
/*!
- * Adds the given numbers together.
- */
-/*
- * Adds the given numbers together.
- */
-// I'm a comment!
-body {
-	font: 12px Helvetica, Arial, sans-serif;
-}
-a.button {
-	-webkit-border-radius: 5px;
-	-moz-border-radius: 5px;
-	border-radius: 5px;
-}
-
-body
-	font: 12px Helvetica, Arial, sans-serif;
-
-a.button:after
-	-webkit-border-radius: 5px;
-	-moz-border-radius: 5px;
-	border-radius: 5px;
-
-body
-	font: 12px Helvetica, Arial, sans-serif
-
-a.link > button#test, input[type=button], a:after
-	-webkit-border-radius: 5px
-	-moz-border-radius: 5px
-	border-radius: 5px
-
-font-size = 14px
-font = font-size "Lucida Grande", Arial
-
-body {
-	padding: 50px;
-	font: 14px/1.4 fonts;
-}
-
-border-radius()
-	-webkit-border-radius arguments
-	-moz-border-radius arguments
-	border-radius arguments
-
-body
-	font 12px Helvetica, Arial, sans-serif
-
-a.button
-	border-radius(5px)
-
-@media (max-width: 30em) {
-	body {
-		color: #fff;
-	}
-}
-
-@media (max-width: 500px)
-	.foo
-		color: #000
-
-	@media (min-width: 100px), (min-height: 200px)
-		.foo
-			color: #100
-
-sum(nums...)
-	sum = 0
-	sum += n for n in nums
-
-sum(1 2 3 4)
-// => 10
diff --git a/docs/_style/prism-master/examples/prism-swift.html b/docs/_style/prism-master/examples/prism-swift.html deleted file mode 100644 index 98d6eb55..00000000 --- a/docs/_style/prism-master/examples/prism-swift.html +++ /dev/null @@ -1,80 +0,0 @@ -

Comments

-
// this is a comment
-/* this is also a comment,
-but written over multiple lines */
-
- -

Numbers

-
42
--23
-3.14159
-0.1
--273.15
-1.25e-2
-0xC.3p0
-1_000_000
-1_000_000.000_000_1
- -

Strings

-
let someString = "Some string literal value"
-var emptyString = ""
-// String interpolation
-let multiplier = 3
-"\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
- -

Control flow

-
for index in 1...5 {
-	println("\(index) times 5 is \(index * 5)")
-}
-for _ in 1...power {
-	answer *= base
-}
-while square < finalSquare {
-	// roll the dice
-	if ++diceRoll == 7 { diceRoll = 1 }
-	// move by the rolled amount
-	square += diceRoll
-	if square < board.count {
-		// if we're still on the board, move up or down for a snake or a ladder
-		square += board[square]
-	}
-}
-switch someCharacter {
-	case "a", "e", "i", "o", "u":
-		println("\(someCharacter) is a vowel")
-	case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
-		"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
-		println("\(someCharacter) is a consonant")
-	default:
-		println("\(someCharacter) is not a vowel or a consonant")
-}
-
- -

Classes and attributes

-
class MyViewController: UIViewController {
-    @IBOutlet weak var button: UIButton!
-    @IBOutlet var textFields: [UITextField]!
-    @IBAction func buttonTapped(AnyObject) {
-	    println("button tapped!")
-	}
-}
-
-@IBDesignable
-class MyCustomView: UIView {
-    @IBInspectable var textColor: UIColor
-    @IBInspectable var iconHeight: CGFloat
-    /* ... */
-}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested block comments

-
/* Nested block
-	/* comments
-	are */
-not supported */
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-tcl.html b/docs/_style/prism-master/examples/prism-tcl.html deleted file mode 100644 index 080d817c..00000000 --- a/docs/_style/prism-master/examples/prism-tcl.html +++ /dev/null @@ -1,26 +0,0 @@ -

Comments

-
# This is a comment
- -

Strings

-
"foo \"bar\" baz"
-"foo\
-bar\
-baz"
- -

Variables

-
$foo
-$foo::bar_42
-$::baz
-${foobar}
-set foo::bar "baz"
- -

Functions

-
proc foobar {baz} {
-	puts $baz
-}
-
-proc RESTORE/post/:post_id/comment/:comment_id {post_id comment_id} {
-    #| Restore a comment handler
-    comment_restore $comment_id
-    qc::actions redirect [url "/post/$post_id" show_deleted_comment_ids $comment_id]
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-textile.html b/docs/_style/prism-master/examples/prism-textile.html deleted file mode 100644 index 4ff4d127..00000000 --- a/docs/_style/prism-master/examples/prism-textile.html +++ /dev/null @@ -1,178 +0,0 @@ -

HTML

-
I am <b>very</b> serious.
-
-<div style="background:#fff">Foo bar</div>
- -

Blocks

-
h1. Header 1
-
-h2. Header 2
-
-h3. Header 3 written on
-multiple lines
-
-bq. A block quotation
-on multiple lines.
- -

Footnotes

-
This is covered elsewhere[1].
-
-fn1. Down here, in fact.
- -

Structural emphasis

-
I _believe_ every word.
-And then? She *fell*!
-
-I __know__.
-I **really** __know__.
-
-??Cat's Cradle?? by Vonnegut
-
-Convert with @r.to_html@
-
-I'm -sure- not sure.
-
-You are a +pleasant+ child.
-
-a ^2^ + b ^2^ = c ^2^
-log ~2~ x
- -

Block attributes

-
p(example1). An example
-
-p(#big-red). Red here
-
-p(example1#big-red2). Red here
-
-p{color:blue;margin:30px}. Spacey blue
-
-p[fr]. rouge
- -

Phrase attributes

-
I seriously *{color:red}blushed*
-when I _(big)sprouted_ that
-corn stalk from my
-%[es]cabeza%.
- -

Phrase alignments and indentation

-
p<. align left
-
-p>. align right
-
-p=. centered
-
-p<>. justified
-
-p(. left ident 1em
-
-p((. left ident 2em
-
-p))). right ident 3em
- -

Attributes and alignments combined

-
h2()>. Bingo.
-
-h3()>[no]{color:red}. Bingo
- -

Lists

-
# First item
-# Second item
-# Third
-
-# Fuel could be:
-## Coal
-## Gasoline
-## Electricity
-# Humans need only:
-## Water
-## Protein
-
-* First item
-* Second item
-* Third
-
-* Fuel could be:
-** Coal
-** Gasoline
-** Electricity
-* Humans need only:
-** Water
-** Protein
-
-#(foo) List can have attributes too
-#{background: red} Red item
- -

Links and images

-
I searched "Google":http://google.com.
-
-I am crazy about "Hobix":hobix
-and "it's":hobix "all":hobix I ever
-"link to":hobix!
-
-[hobix]http://hobix.com
-
-And "(some-link)[en]links":# can have attributes too!
-
-!http://redcloth.org/hobix.com/textile/sample.jpg!
-!openwindow1.gif(Bunny.)!
-!openwindow1.gif!:http://hobix.com/
-
-!>obake.gif!
-
-And others sat all round the small
-machine and paid it to sing to them.
- -

Tables

-
| name | age | sex |
-| joan | 24 | f |
-| archie | 29 | m |
-| bella | 45 | f |
-
-|_. name |_. age |_. sex |
-| joan | 24 | f |
-| archie | 29 | m |
-| bella | 45 | f |
-
-|_. attribute list |
-|<. align left |
-|>. align right|
-|=. center |
-|<>. justify |
-|^. valign top |
-|~. bottom |
-
-|\2. spans two cols |
-| col 1 | col 2 |
-
-|/3. spans 3 rows | a |
-| b |
-| c |
-
-|{background:#ddd}. Grey cell|
-
-table{border:1px solid black}.
-|This|is|a|row|
-|This|is|a|row|
-
-|This|is|a|row|
-{background:#ddd}. |This|is|grey|row|
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested styles are only partially supported

- -

Only one level of nesting is supported.

- -
*A bold paragraph %containing a span with broken _italic_ inside%!*
- -

HTML inside Textile is not supported

- -

But Textile inside HTML should be just fine.

- -
<strong>This _should_ work properly.</strong>
-*But this is <em>definitely</em> broken.*
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-tsx.html b/docs/_style/prism-master/examples/prism-tsx.html deleted file mode 100644 index d83d1dd8..00000000 --- a/docs/_style/prism-master/examples/prism-tsx.html +++ /dev/null @@ -1,31 +0,0 @@ -

Full example

-
import * as React from 'react';
-
-interface IState {
-	clicks: number;
-}
-
-export class Clicker extends React.Component<any, IState> {
-	constructor(props) {
-		super(props);
-
-		this.state = {
-			clicks: 0,
-		};
-	}
-
-	public clickHandler = () => {
-		this.setState({ clicks: this.state.clicks + 1 });
-	}
-
-	public render() {
-		return (
-			<div>
-				<p>You have clicked the button {this.state.clicks} time(s).</p>
-				<p>
-					<button onClick={this.clickHandler}>click me</button>
-				</p>
-			</div>
-		);
-	}
-}
diff --git a/docs/_style/prism-master/examples/prism-tt2.html b/docs/_style/prism-master/examples/prism-tt2.html deleted file mode 100644 index 1a6711f9..00000000 --- a/docs/_style/prism-master/examples/prism-tt2.html +++ /dev/null @@ -1,61 +0,0 @@ -

Comments

-
[%# this entire directive is ignored no
-    matter how many lines it wraps onto
-%]
-[% # this is a comment
-   theta = 20      # so is this
-   rho   = 30      # <aol>me too!</aol>
-%]
-
- -

Variables

-
[% text %]
-[% article.title %]
-[%= eat.whitespace.left %]
-[% eat.whitespace.right =%]
-[%= eat.whitespace.both =%]
-[% object.method() %]
- - -

Conditionals and Loops

-
[% IF foo = bar %]
-this
-[% ELSE %]
-that
-[% END %]
-[% FOREACH post IN q.listPosts(lingua = "de") %]
-  <a href="http://github.com/[%%20post.permalink%20%]">[% post.title | html %]</a>
-[% END %]
- -

Multiple Directives

-
[% IF title;
-      INCLUDE header;
-   ELSE;
-      INCLUDE other/header  title="Some Other Title";
-   END
-%]
- -

Operators

-
[% FOREACH post IN q.listPosts(lingua => 'de') %]
-  [% post.title | myfilter(foo = "bar") %]
-[% END %]
- -

Known Limitations

-
    -
  • - Outline tags are not supported.
  • -
  • The arguments to - TAGS - are usually misinterpreted
  • -
  • In TT2, you can use keywords as identifiers where this is - unambiguous. But these keywords will be highlighted as keywords, not - as variables here.
  • -
  • The - ANYCASE - option is not supported.
  • -
  • - Any number of backslashes in front of dollar signs inside of double quoted - strings are ignored since the behavior of Template Toolkit 2.26 seems to be - inconsistent. -
  • -
diff --git a/docs/_style/prism-master/examples/prism-twig.html b/docs/_style/prism-master/examples/prism-twig.html deleted file mode 100644 index 0a707815..00000000 --- a/docs/_style/prism-master/examples/prism-twig.html +++ /dev/null @@ -1,35 +0,0 @@ -

Comments

-
{# Some comment
-on multiple lines
-with <html></html>
-inside #}
- -

Keywords

-
{% if foo %} bar {% endif %}
-{% for key, value in arr if value %} {{ do_something() }} {% endfor %}
-{% include 'header.html' %}
-{% include 'template.html' with {'foo': 'bar'} %}
- -

Operators

-
{{ not a }}
-{{ 20 // 7 }}
-{{ b b-and c }}
-{% if phone matches '/^[\\d\\.]+$/' %} ... {% endif %}
- -

Twig embedded in HTML

-
<div>
-{% if foo %}
-	<p>Foo!</p>
-{% else %}
-	<p>Not foo...</p>
-{% endif %}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Tag containing Twig is not highlighted

-
<div{% if foo %} class="bar"{% endif %}></div>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-typescript.html b/docs/_style/prism-master/examples/prism-typescript.html deleted file mode 100644 index 561c4dc4..00000000 --- a/docs/_style/prism-master/examples/prism-typescript.html +++ /dev/null @@ -1,28 +0,0 @@ -

Full example

-
interface SearchFunc {
-  (source: string, subString: string): boolean;
-}
-
-var mySearch: SearchFunc;
-mySearch = function(source: string, subString: string) {
-  var result = source.search(subString);
-  if (result == -1) {
-    return false;
-  }
-  else {
-    return true;
-  }
-}
-
-class Greeter {
-    greeting: string;
-    constructor(message: string) {
-        this.greeting = message;
-    }
-    greet() {
-        return "Hello, " + this.greeting;
-    }
-}
-
-var greeter = new Greeter("world");
-
diff --git a/docs/_style/prism-master/examples/prism-vala.html b/docs/_style/prism-master/examples/prism-vala.html deleted file mode 100644 index ef5e98a9..00000000 --- a/docs/_style/prism-master/examples/prism-vala.html +++ /dev/null @@ -1,33 +0,0 @@ -

Comments

-
// Single line comment
-/** Multi-line
-doc comment */
- -

Strings

-
"foo \"bar\" baz"
-"Multi-line strings ending with a \
-are supported too."
-"""Verbatim strings
-You can create
-multi-line strings like this too."""
-@"Template string with variables $var1 $(var2 * 2)"
- -

Regex

-
/foo?[ ]*bar/
- -

Full example

-
using Gtk;
-
-int main (string[] args) {
-	Gtk.init(ref args);
-
-	var window = new Window();
-
-	var button = new Button.with_label("Click me!");
-
-	window.add(button);
-	window.show_all();
-
-	Gtk.main();
-	return 0;
-}
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-vbnet.html b/docs/_style/prism-master/examples/prism-vbnet.html deleted file mode 100644 index bc2a376b..00000000 --- a/docs/_style/prism-master/examples/prism-vbnet.html +++ /dev/null @@ -1,16 +0,0 @@ -

Comments

-
!foobar
-REM foobar
-'foobar
- -

Example

-
Public Function findValue(ByVal arr() As Double,
-    ByVal searchValue As Double) As Double
-    Dim i As Integer = 0
-    While i <= UBound(arr) AndAlso arr(i) <> searchValue
-        ' If i is greater than UBound(arr), searchValue is not checked.
-        i += 1
-    End While
-    If i > UBound(arr) Then i = -1
-    Return i
-End Function
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-velocity.html b/docs/_style/prism-master/examples/prism-velocity.html deleted file mode 100644 index a97fa05e..00000000 --- a/docs/_style/prism-master/examples/prism-velocity.html +++ /dev/null @@ -1,47 +0,0 @@ -

Comments

-
## Single line comment
-#* Multi-line
-comment *#
- -

Unparsed sections

-
## Section below is not parsed
-#[[
-	## This is not a comment
-]]#
- -

Variables

-
$mud
-$customer.Name
-$flogger.getPromo( $mud )
-$!{mudSlinger_9}
-$foo[0]
-$foo[$i]
-$foo["bar"]
-$foo.bar[1].junk
-$foo.callMethod()[1]
- -

Directives

-
#set($foo.bar[1] = 3)
-#if($a==1)true enough#{else}no way!#end
-#macro( d )
-<tr><td>$!bodyContent</td></tr>
-#end
-#@d()Hello!#end
- -

Integration with HTML

-
<html>
-  <body>
-    Hello $customer.Name!
-    <table>
-    #foreach( $mud in $mudsOnSpecial )
-      #if ( $customer.hasPurchased($mud) )
-        <tr>
-          <td>
-            $flogger.getPromo( $mud )
-          </td>
-        </tr>
-      #end
-    #end
-    </table>
-  </body>
-</html>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-verilog.html b/docs/_style/prism-master/examples/prism-verilog.html deleted file mode 100644 index d7c9a0bf..00000000 --- a/docs/_style/prism-master/examples/prism-verilog.html +++ /dev/null @@ -1,103 +0,0 @@ -

Note that this package supports syntax highlighting for both Verilog and System Verilog.

- -

Comments

-
/* Multiline comments in Verilog
-   look like C comments and // is OK in here. */
-// Single-line comment in Verilog.
- -

Literals

-
// example code from: http://iroi.seu.edu.cn/books/asics/Book2/CH11/CH11.02.htm
-module declarations;
-  parameter H12_UNSIZED = 'h 12;
-  parameter H12_SIZED = 6'h 12;
-  parameter D42 = 8'B0010_1010;
-  parameter D123 = 123;
-  parameter D63 = 8'o 77;
-  parameter A = 'h x, B = 'o x, C = 8'b x, D = 'h z, E = 16'h ????;
-  reg [3:0] B0011,Bxxx1,Bzzz1;
-  real R1,R2,R3;
-  integer I1,I3,I_3;
-  parameter BXZ = 8'b1x0x1z0z;
-
-  initial begin
-    B0011 = 4'b11; Bxxx1 = 4'bx1; Bzzz1 = 4'bz1;
-    R1 = 0.1e1; R2 = 2.0; R3 = 30E-01;
-    I1 = 1.1; I3 = 2.5; I_3 = -2.5;
-  end
-
-  initial begin #1;
-    $display("H12_UNSIZED, H12_SIZED (hex) = %h, %h",H12_UNSIZED, H12_SIZED);
-    $display("D42 (bin) = %b",D42," (dec) = %d",D42);
-    $display("D123 (hex) = %h",D123," (dec) = %d",D123);
-    $display("D63 (oct) = %o",D63);
-    $display("A (hex) = %h",A," B (hex) = %h",B);
-    $display("C (hex) = %h",C," D (hex) = %h",D," E (hex) = %h",E);
-    $display("BXZ (bin) = %b",BXZ," (hex) = %h",BXZ);
-    $display("B0011, Bxxx1, Bzzz1 (bin) = %b, %b, %b",B0011,Bxxx1,Bzzz1);
-    $display("R1, R2, R3 (e, f, g) = %e, %f, %g", R1, R2, R3);
-    $display("I1, I3, I_3 (d) = %d, %d, %d", I1, I3, I_3);
-  end
-endmodule
- -

Full example

-
`include "internal_defines.vh"
-
-//*****************************************************************************
-// memory_decoder: a custom module used to handle memory transactions
-//*****************************************************************************
-//
-// out_mem (output) - The output to memory
-// out_reg (output) - The output to the register file
-// mem_we  (output) - Which byte in the word to write too
-// mem_in  (input)  - The input from memory
-// addr_in (input)  - The lowest 2 bits of byte offset to store in memory
-// data_in (input)  - The input from the register file to be stored
-// l_bit   (input)  - The load bit signal (control)
-// b_bit   (input)  - The byte bit signal (control)
-//
-module memory_decoder(out_mem, out_reg, mem_in, data_in, l_bit, b_bit, addr_in,
-                      mem_we);
-
-  output reg  [31:0]  out_mem, out_reg;
-  output reg  [3:0]   mem_we;
-  input       [31:0]  mem_in, data_in;
-  input       [1:0]   addr_in;
-  input               l_bit, b_bit;
-
-  always_comb begin
-    mem_we = 4'b0000;     // dont write memory by default
-    if (l_bit == 1) begin // ldr and ldrb
-      out_mem = mem_in;   // dont change memory!
-      if (b_bit == 1) begin
-        /* figure out which byte to load from memory */
-        case (addr_in)
-          2'b00: out_reg = {24'b00, mem_in[7:0]};
-          2'b01: out_reg = {24'b00, mem_in[15:8]};
-          2'b10: out_reg = {24'b00, mem_in[23:16]};
-          2'b11: out_reg = {24'b00, mem_in[31:24]};
-        endcase
-      end
-      else begin
-        out_reg = mem_in;
-      end
-    end
-    else begin            // str and strb
-      out_reg = `UNKNOWN; // We are not reading from mem
-      if (b_bit == 1) begin
-        /* figure out which byte to write to in memory */
-        out_mem = {4{data_in[7:0]}};
-        case (addr_in)
-          2'b00: mem_we = 4'b1000;
-          2'b01: mem_we = 4'b0100;
-          2'b10: mem_we = 4'b0010;
-          2'b11: mem_we = 4'b0001;
-        endcase
-      end
-      else begin
-        mem_we = 4'b1111; // write to all channels
-        out_mem = data_in;
-      end
-    end
-  end
-
-endmodule
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-vhdl.html b/docs/_style/prism-master/examples/prism-vhdl.html deleted file mode 100644 index b4950851..00000000 --- a/docs/_style/prism-master/examples/prism-vhdl.html +++ /dev/null @@ -1,92 +0,0 @@ -

Comments

-
-- I am a comment
-I am not
- -

Literals

-
constant FREEZE : integer := 32;
-constant TEMP : real := 32.0;
-A_INT <= 16#FF#;
-B_INT <= 2#1010_1010#;
-MONEY := 1_000_000.0;
-FACTOR := 2.2E-6;
-constant DEL1 :time := 10 ns;
-constant DEL2 :time := 2.27 us;
-type MY_LOGIC is ('X','0','1','Z');
-type T_STATE is (IDLE, READ, END_CYC);
-signal CLK : MY_LOGIC := '0';
-signal STATE : T_STATE := IDLE;
-constant FLAG :bit_vector(0 to 7) := "11111111";
-constant MSG : string := "Hello";
-BIT_8_BUS <= B"1111_1111";
-BIT_9_BUS <= O"353";
-BIT_16_BUS <= X"AA55";
-constant TWO_LINE_MSG : string := "Hello" & CR & "World";
- -

Full example

-
-- example code from: http://www.csee.umbc.edu/portal/help/VHDL/samples/samples.html
-library IEEE;
-use IEEE.std_logic_1164.all;
-
-entity fadd is               -- full adder stage, interface
-  port(a    : in  std_logic;
-       b    : in  std_logic;
-       cin  : in  std_logic;
-       s    : out std_logic;
-       cout : out std_logic);
-end entity fadd;
-
-architecture circuits of fadd is  -- full adder stage, body
-begin  -- circuits of fadd
-  s <= a xor b xor cin after 1 ns;
-  cout <= (a and b) or (a and cin) or (b and cin) after 1 ns;
-end architecture circuits; -- of fadd
-
-library IEEE;
-use IEEE.std_logic_1164.all;
-entity add32 is             -- simple 32 bit ripple carry adder
-  port(a    : in  std_logic_vector(31 downto 0);
-       b    : in  std_logic_vector(31 downto 0);
-       cin  : in  std_logic;
-       sum  : out std_logic_vector(31 downto 0);
-       cout : out std_logic);
-end entity add32;
-
-architecture circuits of add32 is
-  signal c : std_logic_vector(0 to 30); -- internal carry signals
-begin  -- circuits of add32
-  a0: entity WORK.fadd port map(a(0), b(0), cin, sum(0), c(0));
-  stage: for I in 1 to 30 generate
-             as: entity WORK.fadd port map(a(I), b(I), c(I-1) , sum(I), c(I));
-         end generate stage;
-  a31: entity WORK.fadd port map(a(31), b(31), c(30) , sum(31), cout);
-end architecture circuits;  -- of add32
-
-use STD.textio.all;
-library IEEE;
-use IEEE.std_logic_1164.all;
-use IEEE.std_logic_textio.all;
-
-entity signal_trace is
-end signal_trace;
-
-architecture circuits of signal_trace is
-  signal a:    std_logic_vector(31 downto 0) := x"00000000";
-  signal b:    std_logic_vector(31 downto 0) := x"FFFFFFFF";
-  signal cin:  std_logic := '1';
-  signal cout: std_logic;
-  signal sum:  std_logic_vector(31 downto 0);
-begin  -- circuits of signal_trace
-  adder: entity WORK.add32 port map(a, b, cin, sum, cout); -- parallel circuit
-
-  prtsum: process (sum)
-            variable my_line : LINE;
-            alias swrite is write [line, string, side, width] ;
-          begin
-            swrite(my_line, "sum=");
-            write(my_line, sum);
-            swrite(my_line, ",  at=");
-            write(my_line, now);
-            writeline(output, my_line);
-          end process prtsum;
-
-end architecture circuits; -- of signal_trace
diff --git a/docs/_style/prism-master/examples/prism-vim.html b/docs/_style/prism-master/examples/prism-vim.html deleted file mode 100644 index 567b8206..00000000 --- a/docs/_style/prism-master/examples/prism-vim.html +++ /dev/null @@ -1,25 +0,0 @@ -

Comments

-
" This is a comment
- -

Variables

-

-set softab = 2
-map <leader>tn :tabnew
-
- -

Map

-
mystring = :steveT;
- -

Functions

-

-func! DeleteTrailingWS()
-  exe "normal mz"
-  %s/\s\+$//ge
-  exe "normal `z"
-endfunc
- -

Logic

-

-if has("mac")
-  nmap <D-j> <M-j>
-endif
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-visual-basic.html b/docs/_style/prism-master/examples/prism-visual-basic.html deleted file mode 100644 index 733faa82..00000000 --- a/docs/_style/prism-master/examples/prism-visual-basic.html +++ /dev/null @@ -1,36 +0,0 @@ -

Comments

-
' Comment
-REM This is a comment too
- -

Strings and characters

-
"Foo""bar"
-“”
-"a"c
- -

Dates and times

-
# 8/23/1970 3:45:39AM #
-#8/23/1970 #
-# 3:45:39AM #
-# 3:45:39#
-# 13:45:39 #
-# 1AM #
-# 13:45:39PM #
- -

Numbers

-
42S
-.369E+14
-3.1415R
- -

Preprocessing directives

-
#ExternalChecksum("c:\wwwroot\inetpub\test.aspx", _
-    "{12345678-1234-1234-1234-123456789abc}", _
-    "1a2b3c4e5f617239a49b9a9c0391849d34950f923fab9484")
- -

Keywords

-
Function AddNumbers(ByVal X As Integer, ByVal Y As Integer)
-    AddNumbers = X + Y
-End Function
-Module Test
-    Sub Main()
-    End Sub
-End Module
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-wasm.html b/docs/_style/prism-master/examples/prism-wasm.html deleted file mode 100644 index 5cc1b65c..00000000 --- a/docs/_style/prism-master/examples/prism-wasm.html +++ /dev/null @@ -1,43 +0,0 @@ -

Comments

-
;; Single line comment
-(; Multi-line
-comment ;)
- -

Strings

-
""
-"Foobar"
-"Foo\"ba\\r"
- -

Numbers

-
42
-3.1415
-0.4E-4
--3.1_41_5
-0xBADFACE
-0xB_adF_a_c_e
-+0x4E.F7
-0xFFp+4
-inf
-nan
-nan:0xf4
- -

Keywords

-
(func (param i32) (param f32) (local f64)
-  get_local 0
-  get_local 1
-  get_local 2)
- -

Identifiers

-
$p
-$getAnswer
-$return_i32
- -

Full example

-
(module
-  (import "js" "memory" (memory 1))
-  (import "js" "table" (table 1 anyfunc))
-  (elem (i32.const 0) $shared0func)
-  (func $shared0func (result i32)
-   i32.const 0
-   i32.load)
-)
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-wiki.html b/docs/_style/prism-master/examples/prism-wiki.html deleted file mode 100644 index 295eb3f8..00000000 --- a/docs/_style/prism-master/examples/prism-wiki.html +++ /dev/null @@ -1,165 +0,0 @@ -

Embedded markup

-
Paragraphs can be forced in lists by using HTML tags.
-Two line break symbols, <code><nowiki><br /><br /></nowiki></code>, will create the desired effect. So will enclosing all but the first paragraph with <code><nowiki><p>...</p></nowiki></code>
- -

Headings

-
= Header 1 =
-== Header 2 ==
-=== Header 3 ===
-==== Header 4 ====
-===== Header 5 =====
-====== Header 6 ======
- -

Bold and italic

-
'''''Both bold and italic'''''
-'''Only bold'''
-''Only italic''
- -

Links and Magic links

-
[[w:en:Formal_grammar|Formal grammar]]
-[http://www.cl.cam.ac.uk/~mgk25/iso-ebnf.html EBNF help]
-
-ISBN 1234567890
-ISBN 123456789x
-ISBN      1 2 3-4-5 6789 X
-ISBN 978-9999999999
-
-RFC 822
-PMID 822
- -

Magic words and special symbols

-
#REDIRECT [[somewhere]]
-
-{{SITENAME}}
-{{PAGESINCATEGORY:category}}
-{{#dateformat:2009-12-25|mdy}}
-
-__NOTOC__
-
-{{!}}
-
-~~~ ~~~~ ~~~~~
- -

Lists

-
* Lists are easy to do:
-** start every line
-* with a star
-** more stars mean
-*** deeper levels
-
-# Numbered lists are good
-## very organized
-## easy to follow
-
-; Definition lists
-; item : definition
-; semicolon plus term
-: colon plus definition
-
-* Or create mixed lists
-*# and nest them
-*#* like this
-*#*; definitions
-*#*: work:
-*#*; apple
-*#*; banana
-*#*: fruits
- -

Tables

-
{|
-|Orange
-|Apple
-|-
-|Bread
-|Pie
-|-
-|Butter
-|Ice cream
-|}
-
-{|
-|Lorem ipsum dolor sit amet,
-consetetur sadipscing elitr,
-sed diam nonumy eirmod tempor invidunt
-ut labore et dolore magna aliquyam erat,
-sed diam voluptua.
-
-At vero eos et accusam et justo duo dolores
-et ea rebum. Stet clita kasd gubergren,
-no sea takimata sanctus est Lorem ipsum
-dolor sit amet.
-|
-* Lorem ipsum dolor sit amet
-* consetetur sadipscing elitr
-* sed diam nonumy eirmod tempor invidunt
-|}
-
-{|
-|  Orange    ||   Apple   ||   more
-|-
-|   Bread    ||   Pie     ||   more
-|-
-|   Butter   || Ice cream ||  and more
-|}
-
-{|
-! style="text-align:left;"| Item
-! Amount
-! Cost
-|-
-|Orange
-|10
-|7.00
-|-
-|Bread
-|4
-|3.00
-|-
-|Butter
-|1
-|5.00
-|-
-!Total
-|
-|15.00
-|}
-
-{|
-! style="text-align:left;"| Item !! style="color:red;"| Amount !! Cost
-|-
-|Orange
-|10
-|7.00
-|-
-| style="text-align:right;"| Bread
-|4
-|3.00
-|-
-|Butter
-|1
-|5.00
-|-
-!Total
-|
-|15.00
-|}
- -

Known failures

-

There are certain edge cases where Prism will fail. - There are always such cases in every regex-based syntax highlighter. - However, Prism dares to be open and honest about them. - If a failure is listed here, it doesn’t mean it will never be fixed. This is more of a “known bugs” list, just with a certain type of bug. -

- -

Nested magic words are not supported

- -
{{#switch:{{PAGENAME}}
-| L'Aquila = No translation
-| L = Not OK
-| L'Aquila = Entity escaping
-| L'Aquila = Numeric char encoding
-}}
- -

Nesting of bold and italic is not supported

-
''Italic with '''bold''' inside''
- diff --git a/docs/_style/prism-master/examples/prism-xeora.html b/docs/_style/prism-master/examples/prism-xeora.html deleted file mode 100644 index 541a9165..00000000 --- a/docs/_style/prism-master/examples/prism-xeora.html +++ /dev/null @@ -1,111 +0,0 @@ -

Special Constants

-
$DomainContents$
-$PageRenderDuration$
- -

Operators & Variables

-
$SearchKey$
-$^SearchKey$
-$~SearchKey$
-$-SearchKey$
-$+SearchKey$
-$=SearchKey$
-$#SearchKey$
-$##SearchKey$
-
-$*SearchKey$
-
-$@SearchObject.SearchProperty$
-$@#SearchObject.SearchProperty$
-$@-SearchObject.SearchProperty$
- -

Controls

-
$C:ControlID$
-$C:ControlID:{ <!-- Something --> }:ControlID$
-$C:ControlID:{ <!-- Something --> }:ControlID:{ <!-- Something (Alternative) --> }:ControlID$
-
-Control with Parent
-$C[Control1]:Control2$
-$C[Control2]:Control3:{ <!-- Something --> }:Control3$
-$C[Control2]:Control3:{ <!-- Something --> }:Control3:{ <!-- Something (Alternative) --> }:Control3$
-
-Control with Parent & Leveling
-$C#1[ParentControlID]:ControlID:{ <!-- Something --> }:ControlID$
-
-All Control Tags has leveling specification;
-$C:LoopControl1:{
-	$#FirstLoopSQLField1$
-
-	$C:ControlID:{ <!-- Something --> }:ControlID$
-
-	$C:LoopControl2:{
-		$##FirstLoopSQLField1$
-		$#SecondLoopSQLField1$
-
-		$C#1:ControlID:{ <!-- Something --> }:ControlID$
-	}:LoopControl2$
-}:LoopControl1$
-
-XML setup on a Control in Controls.xml
-<Control id="[ControlID]">
-	<Type>[ControlType]</Type>
-
-	<Bind>[ThemeID|AddonID]?[ControlClass].[FunctionName],SomeOperatorTags(seperated with |)</Bind>
-
-	<BlockIDsToUpdate localupdate="True|False">
-		<BlockID>[BlockID]</BlockID>
-		<BlockID>[BlockID]</BlockID>
-		<BlockID>[BlockID]</BlockID>
-	</BlockIDsToUpdate>
-
-	<DefaultButtonID>[ControlID]</DefaultButtonID>
-
-	<Text>[TextBox, Password value or Button Text]</Text>
-
-	<Content>[Textarea Content]</Content>
-
-	<Source>[Image URL]</Source>
-
-	<Url>[Link URL]</Url>
-
-	<Attributes>
-		<Attribute key="[HTMLAttributeKey]">[AttributeValue]</Attributes>
-	</Attributes>
-</Control>
- -

Directives

-
$T:TemplateID$
-$L:TranslationID$
-$P:TemplateID$
- -

Executable Functions

-
$F:AddonLib1?GlobalControls.PrintOutSums$
-$F:AddonLib1?GlobalControls.PrintOut,~FormField$
-$F:AddonLib1?GlobalControls.SumNumbers,~FormField|=5$
- -

Client Side Function Binding

-
$XF:{AddonLib1?GlobalControls.SumNumbers,~FormField|=5}:XF$
- -

Inline Statements

-
$S:StatementID:{ <!-- C# Code --> }:StatementID$
-$S:StatementID:{!NOCACHE <!-- C# Code --> }:StatementID$
-
-$S:Statement1:{
-	int intvalue1 = 5;
-	int intvalue2 = Integer.Parse("0" + $~FormValue$);
-
-	return intvalue1 * intvalue2;
-}:Statement1$
- -

Request Blocks

-
$H:RequestBlockID:{ <!-- Something --> }:RequestBlockID$
-$H:RequestBlockID:{!RENDERONREQUEST <!-- Something --> }:RequestBlockID$
- -

Cache Block

-
$PC:{ <!-- Page Content Part --> }:PC$
- -

Message Handling Block

-
$MB:{ <!-- Message Output Content --> }:MB$
-$MB:{
-	$#Message$
-	$#MessageType$
-}:MB$
diff --git a/docs/_style/prism-master/examples/prism-xojo.html b/docs/_style/prism-master/examples/prism-xojo.html deleted file mode 100644 index 35480744..00000000 --- a/docs/_style/prism-master/examples/prism-xojo.html +++ /dev/null @@ -1,63 +0,0 @@ -

Comments

-
' This is a comment
-// This is a comment too
-Rem This is a remark
- -

Strings

-
""
-"foo ""bar"" baz"
- -

Numbers and colors

-
42
-3.14159
-3E4
-&b0110
-&cAABBCCDD
-&hBadFace
-&o777
-&u9
- -

Example

-
Dim g As Graphics
-Dim yOffSet As Integer
-g = OpenPrinterDialog()
-If g <> Nil Then
-  If MainDishMenu.ListIndex <> -1 Then
-    g.Bold = True
-    g.DrawString("Main Dish:",20,20)
-    g.Bold = False
-    g.DrawString(MainDishMenu.Text,100,20)
-    g.Bold = True
-    g.DrawString("Side Order:",20,40)
-    g.Bold = False
-    If FriesRadio.Value Then
-      g.DrawString(FriesRadio.Caption,100,40)
-    End If
-    If PotatoRadio.Value Then
-      g.DrawString(PotatoRadio.Caption,100,40)
-    End If
-    If OnionRingRadio.Value Then
-      g.DrawString(OnionRingRadio.Caption,100,40)
-    End If
-    yOffSet = 60
-    If CheeseCheckBox.Value Then
-      g.Bold = True
-      g.DrawString("Extra:",20,yOffSet)
-      g.Bold = False
-      g.DrawString(CheeseCheckBox.Caption,100,yOffSet)
-      yOffSet = yOffSet + 20
-    End If
-    If BaconCheckBox.Value Then
-      g.Bold = True
-      g.DrawString("Extra:",20,yOffSet)
-      g.Bold = False
-      g.DrawString(BaconCheckBox.Caption,100,yOffSet)
-      yOffSet = yOffSet + 20
-    End If
-    g.Bold = True
-    g.DrawString("Notes:",20,yOffSet)
-    g.Bold = False
-    g.DrawString(NotesField.Text,100,yOffSet,(g.Width-40))
-  End If
-End If
-
diff --git a/docs/_style/prism-master/examples/prism-xquery.html b/docs/_style/prism-master/examples/prism-xquery.html deleted file mode 100644 index 8166d771..00000000 --- a/docs/_style/prism-master/examples/prism-xquery.html +++ /dev/null @@ -1,47 +0,0 @@ -

Comments

-
(::)
-(: Comment :)
-(: Multi-line
-comment :)
-(:~
-: The <b>functx:substring-after-last</b> function returns the part
-: of <b>$string</b> that appears after the last occurrence of
-: <b>$delim</b>. If <b>$string</b> does not contain
-: <b>$delim</b>, the entire string is returned.
-:
-: @param $string the string to substring
-: @param $delim the delimiter
-: @return the substring
-:)
- -

Variables

-
$myProduct
-$foo-bar
-$strings:LetterA
- -

Functions

-
document-node(schema-element(catalog))
-strings:trim($arg as xs:string?)
-false()
- -

Keywords

-
xquery version "1.0";
-declare default element namespace "http://datypic.com/cat";
-declare boundary-space preserve;
-declare default collation "http://datypic.com/collation/custom";
- -

Types

-
xs:anyAtomicType
-element
-xs:double
- -

Full example

-
<report xmlns="http://datypic.com/report"
-xmlns:cat="http://datypic.com/cat"
-xmlns:prod="http://datypic.com/prod"> {
-for $product in doc("prod_ns.xml")/prod:product
-return <lineItem>
-{$product/prod:number}
-{$product/prod:name}
-</lineItem>
-} </report>
\ No newline at end of file diff --git a/docs/_style/prism-master/examples/prism-yaml.html b/docs/_style/prism-master/examples/prism-yaml.html deleted file mode 100644 index bea52f86..00000000 --- a/docs/_style/prism-master/examples/prism-yaml.html +++ /dev/null @@ -1,107 +0,0 @@ -

Null and Boolean

-
---
-A null: null
-A null: ~
-Also a null: # Empty
-Not a null: ""
-Booleans: [ true, True, false, FALSE ]
-
- -

Numbers and timestamps

-
---
-Integers: [ 0, -0, 3, 0o7, 0x3A, -19 ]
-Floats: [ 0., -0.0, .5, 12e03, +12e03, -2E+05 ]
-Also floats: [ .inf, -.Inf, +.INF, .NAN ]
-Timestamps:
-  canonical: 2001-12-15T02:59:43.1Z
-  iso8601: 2001-12-14t21:59:43.10-05:00
-  spaced: 2001-12-14 21:59:43.10 -5
-  date: 2002-12-14
-  times:
-    - 10:53
-    - 10:53:20.53
-
- -

Strings

-
---
-product: High Heeled "Ruby" Slippers
-description: "Putting on these \"slippers\" is easy."
-address:
-  city:   East Centerville
-  street: !!str |
-    123 Tornado Alley
-    Suite 16
-
-  specialDelivery:  >
-    Follow the Yellow Brick
-    Road to the Emerald City.
-    #Pay no attention to the
-    man behind the curtain.
-
- -

Sequences and maps

-
---
-- Casablanca
-- North by Northwest
-- {
-    name: John Smith, age: 33}
-- name: Mary Smith
-  age: 27
----
-"name": John Smith
-age: 33
-men: [ John Smith,
-    "Bill Jones" ]
-women:
- - Mary Smith
- - "Susan Williams"
-
- -

Tags

-
---
-!!map {
-  ? !!str friends: !!seq [
-    !!map {
-      ? !!str "age"
-      : !!int 33,
-      ? !!str "name"
-      : !!str "John Smith",
-    }
-  ],
-  men:
-    [ !!str "John Smith", !!str "Bill Jones"]
-}
-
- -

Full example

-
%YAML 1.2
---- !<tag:clarkevans.com,2002:invoice>
-invoice: 34843
-date   : 2001-01-23
-bill-to: &id001
-  given  : Chris
-  family : Dumars
-  address:
-    lines: |
-      458 Walkman Dr.
-      Suite #292
-    city    : Royal Oak
-    state   : MI
-    postal  : 48046
-ship-to:
-  <<: *id001
-  product:
-    - sku         : BL394D
-      quantity    : 4
-      description : Basketball
-      price       : 450.00
-    - sku         : BL4438H
-      quantity    : 1
-      description : Super Hoop
-      price       : 2392.00
-tax  : 251.42
-total: 4443.52
-comments:
-    Late afternoon is best.
-    Backup contact is Nancy
-
diff --git a/docs/_style/prism-master/extending.html b/docs/_style/prism-master/extending.html deleted file mode 100644 index 59101b6f..00000000 --- a/docs/_style/prism-master/extending.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - -Extending Prism ▲ Prism - - - - - - - - - -
-
- -

Extending Prism

-

Prism is awesome out of the box, but it’s even awesomer when it’s customized to your own needs. This section will help you write new language definitions, plugins and all-around Prism hacking.

-
- -
-

Language definitions

- -

Every language is defined as a set of tokens, which are expressed as regular expressions. For example, this is the language definition for CSS:

-

-
-	

A regular expression literal is the simplest way to express a token. An alternative way, with more options, is by using an object literal. With that notation, the regular expression describing the token would be the pattern attribute:

-
...
-'tokenname': {
-	pattern: /regex/
-}
-...
-

So far the functionality is exactly the same between the short and extended notations. However, the extended notation allows for additional options:

- -
-
inside
-
This property accepts another object literal, with tokens that are allowed to be nested in this token. - This makes it easier to define certain languages. However, keep in mind that they’re slower and if coded poorly, can even result in infinite recursion. - For an example of nested tokens, check out the Markup language definition: -
- -
lookbehind
-
This option mitigates JavaScript’s lack of lookbehind. When set to true, - the first capturing group in the regex pattern is discarded when matching this token, so it effectively behaves - as if it was lookbehind. For an example of this, check out the C-like language definition, in particular the comment and class-name tokens: -
- -
rest
-
Accepts an object literal with tokens and appends them to the end of the current object literal. Useful for referring to tokens defined elsewhere. For an example where rest is useful, check the Markup definitions above.
- -
alias
-
This option can be used to define one or more aliases for the matched token. The result will be, that - the styles of the token and its aliases are combined. This can be useful, to combine the styling of a well known - token, which is already supported by most of the themes, with a semantically correct token name. The option - can be set to a string literal or an array of string literals. In the following example the token - name latex-equation is not supported by any theme, but it will be highlighted the same as a string. -
{
-	'latex-equation': {
-		pattern: /\$(\\?.)*?\$/g,
-		alias: 'string'
-	}
-}
- -
greedy
-
This is a boolean attribute. It is intended to solve a common problem with - patterns that match long strings like comments, regex or string literals. For example, - comments are parsed first, but if the string /* foo */ - appears inside a string, you would not want it to be highlighted as a comment. - The greedy-property allows a pattern to ignore previous matches of other patterns, and - overwrite them when necessary. Use this flag with restraint, as it incurs a small performance overhead. - The following example demonstrates its usage: -
'string': {
-	pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
-	greedy: true
-}
-
- -

Unless explicitly allowed through the inside property, each token cannot contain other tokens, so their order is significant. Although per the ECMAScript specification, objects are not required to have a specific ordering of their properties, in practice they do in every modern browser.

- -

In most languages there are multiple different ways of declaring the same constructs (e.g. comments, strings, ...) and sometimes it is difficult or unpractical to match all of them with one single regular expression. To add multiple regular expressions for one token name an array can be used:

- -
...
-'tokenname': [ /regex0/, /regex1/, { pattern: /regex2/ } ]
-...
- -
-

Prism.languages.insertBefore(inside, before, insert, root)

- -

This is a helper method to ease modifying existing languages. For example, the CSS language definition not only defines CSS highlighting for CSS documents, - but also needs to define highlighting for CSS embedded in HTML through <style> elements. To do this, it needs to modify - Prism.languages.markup and add the appropriate tokens. However, Prism.languages.markup - is a regular JavaScript object literal, so if you do this:

- -
Prism.languages.markup.style = {
-	/* tokens */
-};
- -

then the style token will be added (and processed) at the end. Prism.languages.insertBefore allows you to insert - tokens before existing tokens. For the CSS example above, you would use it like this:

- -
Prism.languages.insertBefore('markup', 'cdata', {
-	'style': {
-		/* tokens */
-	}
-});
- -

Parameters

-
-
inside
-
The property of root that contains the object to be modified.
- -
before
-
Key to insert before (String)
- -
insert
-
An object containing the key-value pairs to be inserted
- -
root
-
The root object, i.e. the object that contains the object that will be modified. Optional, default value is Prism.languages.
-
-
-
- -
-

Writing plugins

- -

Prism’s plugin architecture is fairly simple. To add a callback, you use Prism.hooks.add(hookname, callback). - hookname is a string with the hook id, that uniquely identifies the hook your code should run at. - callback is a function that accepts one parameter: an object with various variables that can be modified, since objects in JavaScript are passed by reference. - For example, here’s a plugin from the Markup language definition that adds a tooltip to entity tokens which shows the actual character encoded: -

Prism.hooks.add('wrap', function(env) {
-	if (env.token === 'entity') {
-		env.attributes['title'] = env.content.replace(/&amp;/, '&');
-	}
-});
-

Of course, to understand which hooks to use you would have to read Prism’s source. Imagine where you would add your code and then find the appropriate hook. - If there is no hook you can use, you may request one to be added, detailing why you need it there. -

- -
-

API documentation

- -
-

Prism.highlightAll(async, callback)

-

This is the most high-level function in Prism’s API. It fetches all the elements that have a .language-xxxx class - and then calls Prism.highlightElement() on each one of them.

- -

Parameters

-
-
async
-
- Whether to use Web Workers to improve performance and avoid blocking the UI when highlighting very large - chunks of code. False by default - (why?).
- Note: All language definitions required to highlight the code must be included in the main prism.js - file for the async highlighting to work. You can build your own bundle on the Download page. -
- -
callback
-
- An optional callback to be invoked after the highlighting is done. Mostly useful when async - is true, since in that case, the highlighting is done asynchronously. -
-
-
- -
-

Prism.highlightAllUnder(element, async, callback)

-

Fetches all the descendants of element that have a .language-xxxx class - and then calls Prism.highlightElement() on each one of them.

- -

Parameters

-
-
element
-
The root element, whose descendants that have a .language-xxxx class will be highlighted.
- -
async
-
Same as in Prism.highlightAll()
- -
callback
-
Same as in Prism.highlightAll()
-
-
- -
-

Prism.highlightElement(element, async, callback)

-

Highlights the code inside a single element.

- -

Parameters

-
-
element
-
The element containing the code. It must have a class of language-xxxx to be processed, where xxxx is a valid language identifier.
- -
async
-
Same as in Prism.highlightAll()
-
callback
-
Same as in Prism.highlightAll()
-
-
- -
-

Prism.highlight(text, grammar)

-

Low-level function, only use if you know what you’re doing. - It accepts a string of text as input and the language definitions to use, and returns a string with the HTML produced.

- -

Parameters

-
-
text
-
A string with the code to be highlighted.
-
grammar
-
An object containing the tokens to use. Usually a language definition like Prism.languages.markup
-
- -

Returns

-

The highlighted HTML

-
- -
-

Prism.tokenize(text, grammar)

-

This is the heart of Prism, and the most low-level function you can use. It accepts a string of text as input and the language definitions to use, and returns an array with the tokenized code. - When the language definition includes nested tokens, the function is called recursively on each of these tokens. This method could be useful in other contexts as well, as a very crude parser.

- -

Parameters

-
-
text
-
A string with the code to be highlighted.
-
grammar
-
An object containing the tokens to use. Usually a language definition like Prism.languages.markup
-
- -

Returns

-

An array of strings, tokens (class Prism.Token) and other arrays.

-
-
- -
- - - - - - - - diff --git a/docs/_style/prism-master/faq.html b/docs/_style/prism-master/faq.html deleted file mode 100644 index ae32cb68..00000000 --- a/docs/_style/prism-master/faq.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - -FAQ ▲ Prism - - - - - - - - - - -
-
- -

FAQ

-

Frequently Asked Questions, with a few Questions I want people to Frequently Ask.

-
- -
-

This page doesn’t work in Opera!

- -

Prism works fine in Opera. However, this page might sometimes appear to not be working in Opera, due to the theme switcher triggering an Opera bug. - This will be fixed soon.

-
- -
-

Isn’t it bad to do syntax highlighting with regular expressions?

- -

It is true that to correctly handle every possible case of syntax found in the wild, one would need to write a full-blown parser. - However, in most web applications and websites a small error margin is usually acceptable and a rare highlighting failure is not the end of the world. - A syntax highlighter based on regular expressions might only be accurate 99% of the time (the actual percentage is just a guess), - but in exchange for the small error margin, it offers some very important benefits: - -

    -
  • Smaller filesize. Proper parsers are very big.
  • -
  • Extensibility. Authors can define new languages simply by knowing how to code regular expressions. - Writing a correct, unambiguous BNF grammar is a task at least an order of magnitude harder.
  • -
  • Graceful error recovery. Parsers fail on incorrect syntax, where regular expressions keep matching.
  • -
- -

For this reason, most syntax highlighters on the web and on desktop, are powered by regular expressions. This includes the internal syntax - highlighters used by popular native applications like Espresso and Sublime Text, at the time of writing. - Of course, not every regex-powered syntax highlighter is created equal. The number and type of failures can be vastly different, depending on - the exact algorithm used. Prism’s known failures are documented in the Examples section.

-
- -
-

Why is asynchronous highlighting disabled by default?

- -

Web Workers are good for preventing syntax highlighting of really large code blocks from blocking the main UI thread. - In most cases, you will want to highlight reasonably sized chunks of code, and this will not be needed. - Furthermore, using Web Workers is actually slower than synchronously highlighting, due to the overhead of creating and terminating - the Worker. It just appears faster in these cases because it doesn’t block the main thread. - In addition, since Web Workers operate on files instead of objects, plugins that hook on core parts of Prism (e.g. modify language definitions) - will not work unless included in the same file (using the builder in the Download page will protect you from this pitfall). - Lastly, Web Workers cannot interact with the DOM and most other APIs (e.g. the console), so they are notoriously hard to debug. -

-
- -
-

Why is pre-existing HTML stripped off?

- -

Because it would complicate the code a lot, although it’s not a crucial feature for most people. - If it’s very important to you, you can use the Keep Markup plugin.

-
- -
-

If pre-existing HTML is stripped off, how can I highlight certain parts of the code?

- -

There is a number of ways around it. You can always break the block of code into multiple parts, and wrap the HTML around it (or just use a .highlight class). - You can see an example of this in action at the “Basic usage” section of the homepage.

-

Another way around the limitation is to use the Line Highlght plugin, to highlight and link to specific lines and/or line ranges. -

- -
-

How do I know which tokens I can style for every language?

- -

Every token that is highlighted gets two classes: token and a class with the token type (e.g. comment). - You can find the different types of tokens either by looking at the keys of the object defining the language or by running this snippet in the console: -

function printTokens(o, prefix) { for (var i in o) { console.log((prefix? prefix + ' > ' : '') + i); if (o[i].inside) printTokens(o[i].inside, (prefix? prefix + ' > ' : '') + i); } };
-

Then you can use the function for every language you want to examine. For example, markup:

-
printTokens(Prism.languages.markup);
-

which outputs:

-
comment
-prolog
-doctype
-script
-script > tag
-script > tag > tag
-script > tag > tag > punctuation
-script > tag > tag > namespace
-script > tag > attr-value
-script > tag > attr-value > punctuation
-script > tag > punctuation
-script > tag > attr-name
-script > tag > attr-name > namespace
-script > rest
-style
-style > tag
-style > tag > tag
-style > tag > tag > punctuation
-style > tag > tag > namespace
-style > tag > attr-value
-style > tag > attr-value > punctuation
-style > tag > punctuation
-style > tag > attr-name
-style > tag > attr-name > namespace
-style > rest
-cdata
-tag
-tag > tag
-tag > tag > punctuation
-tag > tag > namespace
-tag > attr-value
-tag > attr-value > punctuation
-tag > punctuation
-tag > attr-name
-tag > attr-name > namespace
-entity
-
- -
-

How can I use different highlighting for tokens with the same name in different languages?

-

Just use a descendant selector, that includes the language class. The default prism.css does this, to have different colors for - JavaScript strings (which are very common) and CSS strings (which are relatively rare). Here’s that code, simplified to illustrate the technique: -


-.token.string {
-	color: #690;
-}
-
-.language-css .token.string,
-.style .token.string {
-	color: #a67f59;
-}
- -

Abbreviated language classes (e.g. lang-css) will be converted to their extended forms, so you don’t need to account for them.

-

The same technique can be used to differentiate XML tag namespaces from attribute namespaces:

-
.tag > .token.namespace {
-	color: #b37298;
-}
-.attr-name > .token.namespace {
-	color: #ab6;
-}
-
- -
- - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/favicon.png b/docs/_style/prism-master/favicon.png deleted file mode 100644 index 11838d355486c147ded60afcf81aa67e3c69cce1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209 zcmV;?051QDP)\n' + - '********************************************** */\n\n')) - .pipe(concat('prism.js')) - .pipe(gulp.dest('./')); -}); - -gulp.task('plugins', ['languages-plugins'], function() { - return gulp.src(paths.plugins) - .pipe(uglify()) - .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest('plugins')); -}); - -gulp.task('components-json', function (cb) { - componentsPromise.then(function (data) { - data = 'var components = ' + JSON.stringify(data) + ';\n' + - 'if (typeof module !== \'undefined\' && module.exports) { module.exports = components; }'; - fs.writeFile(paths.componentsFileJS, data, cb); - }); -}); - -gulp.task('watch', function() { - gulp.watch(paths.components, ['components', 'build']); - gulp.watch(paths.plugins, ['plugins', 'build']); -}); - -gulp.task('languages-plugins', function (cb) { - componentsPromise.then(function (data) { - var languagesMap = {}; - var dependenciesMap = {}; - for (var p in data.languages) { - if (p !== 'meta') { - var title = data.languages[p].displayTitle || data.languages[p].title; - var ucfirst = p.substring(0, 1).toUpperCase() + p.substring(1); - if (title !== ucfirst) { - languagesMap[p] = title; - } - - for (var name in data.languages[p].aliasTitles) { - languagesMap[name] = data.languages[p].aliasTitles[name]; - } - - if(data.languages[p].require) { - dependenciesMap[p] = data.languages[p].require; - } - } - } - - var jsonLanguagesMap = JSON.stringify(languagesMap); - var jsonDependenciesMap = JSON.stringify(dependenciesMap); - - var tasks = [ - {plugin: paths.showLanguagePlugin, map: jsonLanguagesMap}, - {plugin: paths.autoloaderPlugin, map: jsonDependenciesMap} - ]; - - var cpt = 0; - var l = tasks.length; - var done = function() { - cpt++; - if(cpt === l) { - cb && cb(); - } - }; - - tasks.forEach(function(task) { - var stream = gulp.src(task.plugin) - .pipe(replace( - /\/\*languages_placeholder\[\*\/[\s\S]*?\/\*\]\*\//, - '/*languages_placeholder[*/' + task.map + '/*]*/' - )) - .pipe(gulp.dest(task.plugin.substring(0, task.plugin.lastIndexOf('/')))); - - stream.on('error', done); - stream.on('end', done); - }); - }); -}); - -gulp.task('changelog', function (cb) { - return gulp.src(paths.changelog) - .pipe(replace( - /#(\d+)(?![\d\]])/g, - '[#$1](https://github.com/PrismJS/prism/issues/$1)' - )) - .pipe(replace( - /\[[\da-f]+(?:, *[\da-f]+)*\]/g, - function (match) { - return match.replace(/([\da-f]{7})[\da-f]*/g, '[`$1`](https://github.com/PrismJS/prism/commit/$1)'); - } - )) - .pipe(gulp.dest('.')); -}); - -gulp.task('default', ['components', 'components-json', 'plugins', 'build']); diff --git a/docs/_style/prism-master/img/logo-ala.png b/docs/_style/prism-master/img/logo-ala.png deleted file mode 100644 index ebd3f129a98eb5c16ee5b7559c994beba3c81e8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1745 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 diff --git a/docs/_style/prism-master/img/logo-drupal.png b/docs/_style/prism-master/img/logo-drupal.png deleted file mode 100644 index 6f2b026b7c26e66455a02de72b2ba17f949455d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2371 zcmYLK2{@E%8y-e7c9QHF*=7vNdLq({Y#|0eN+Al-PbHi>97#<&l~EHF5oXL{3CSK; zBbso^(PEzk$u?!{h%D#*hOVybn|Ge?d7tNg?)!e<>-y3>+#RLFRm5R1n3S`VJs$kC zz|T|+4%ScaYT{rp;X)5r?}OkKL+2XMIT+fo0fUR#ctD0RVo2eE44TT*r*aG!JOikV zt5?wrTVDqOEE%jI$@5{XqXk<4WTOVG+T;Py3wFU4h9Ss9ubU5=#?!7Iz<_qf2avRD zh7i!SU+o5f44|}8Pum=AK;!69hoLe+hQ>kDIff-1Q^pVkOXHe8=rk%DMA5jY5|H7c zO4$MeT&S${Cjx_qhO|I5Fd(4wj2R<8SphO2-W&=>qiLXqYsBChGDbiQaDG+-bD)V3 zJwS#gK%=oupN>He0I@WV5reIn_scX4g9nPi3eBZ+fc%Xv2oORR*aDn~`~(L9m_Q^k z)Io3@0m7Y(pkA5gH1_>J|wY3Vhb50ha(( zFpGzx^Y#Mmf*1_|1&UCmo2*c@VaN?C;J`*f^q~B&69@h?CIR@S4;%M{222CeK#~pR z!7ZY3P;|DSQJ_x1>SqfFP&FzT-#C}e6;iQ@sRqBl7hF?rEZ*G<_AYJL5ypiEc{waj z^VE+uDWNaIC)44DOZ9Clv(g>$k0K@;KdmnMY`rG>S>*iT&cq4xz&oSI`MrKB!ATE( z*Ow_y-J)osW4-g~J-XKOg?*OI-|T8t%ck74E6LNj&Q?gnqZuj7V^Nu}h&?S1;%9>{ z>xtEUWgmPJ{WAH?Ez;oKzYe?!sBleSsfUz@?*2aT{~g;#+Rd)NH5kvHj~u=<_xHu{ z*9wmPf9U!XyzLQrXq$qE%3jr&n|mxgP`9skb!$k*-T3#|$DzG6U@XcVbEz!>Q2fP!6_&xJiMqP#5X2Yf<_`TI<223NrHdHV5SM!ff zMGaWwu8(&4On9qUC%z8;wq(AdOLq~T9DB6HIkbLYJl%pcY~`J)zfY{jwWi-BEJ?C! zJT@gs;Yrf?awLKxtJTon)UJN=)m@ezF~Bip?aV;#A4Aui>LlTJYAvKBI`x!ThYBqn zoP68n7x(IkiF#Yc;!-g)r8NzBG1323%sPmrN%ysFEmLR8+>K5pF4h_PBPMPxQ4g0s zTzYd`_hM9YF0Q`Xy&mUqCh(fPJmOX%`8Gay;)2?VP{|AnMZ6UZ=8s?^Yx#%nTeZLZ zc(^Q9$S+f~FF(9Pj*(wUXwDeID(?Rg<0B&+)0&%Y&>7^w8#Jge>OfvEtWtn6^#>8K z+kAJIw{OsqZ8%Rmq;Ng1ZvH)grAnfyvObVpMsMgjE!M41{uhnh1}mV_j>`YRvW(N| z9&dTuaujEKYfi>bDx$E;7l9DUOw206T0HA^oIJm~+_B3yZ|De1g2eX3;tifQId~!W zuPU$hO(%aTj9T_G4M&N%DQJ;oHC%#Yke+SCWy=Z9XKscMZk^pxsW7K3ZvBC{7>*7t zCtfzQn-38+87r6XcFK2_%C935l)I{``?g6=qYRL%3iw29wXOelcEFF~uoP*3=k(0k zdwJ>_X`Mx{5Vfr(6y1>DTRQFiz!vJQ#uny;x{LWOXJoG~2xarsfHG;=w;Tkw`fHS%uJ-)`u4GRGxI zqOOE#a(Qe9QQ4*V@%@5whxL^ek;WXd2aJTbf*1ObPIO+=KJ29ws7)qx%PRCOYf4dL z6pl9)tf;wqDP8_Y)a9R3xF&mga0YRQ{v*O09=WTOFf+AVr7%V&IYePDXyNiFVl>|B zsnhl1`^=ihszvWhQgc!de5^rD``Shn1d&deljn|qdo8V2AV~<3PG~$Ffk#C2Z}r(v zp$g>`nF)U=`TMlfcFNj8+-a0KF67DM8C89x8lj-+K2v;0@>ehM3S5{>ek;)tkBSc?`&@+5xh)J!!n#eJ6?+x`&fCew`SsQ3I$?`n85N%Ix;5;b5H@W0y7kY^ z`UPWIGvdLMla1ee`z@NC(gR!g!+S1<=9T5rlk~F9+fg<{2P#48K;X05|jCL&vFDYB?p?G%X@TNlEN`jq5t9CpUP*j1Nq zJEu{p!6Yh+sHPY$_yiGrSu+i0?cd=F1mAZZA@}0_q!ANJb~8xUAxc&nK7nxE|JdgM z8F-JPs?VQMglEJs_ir)>#7#u9*X(@`!nx}5@PL@^mg=#y2@QDM&=vl<5lx&{tZ&1Y zhNrtMO4N?`7m}%Ry{|g{LiJX1`nNVCl>+p9A2uJ6*&h(Lj<>JrDHesjba0N-G(P6^ z;?IHBtBsXyzFNh31g4Vkf+)FeF4W?;1wd}d(I2i@(Nkl>n;}k0YX7h^JVSmq5n)c+qx#n7V#YJdQRN0_d6sfQWgBcqo9w#z-N<(xH-5 z@^0Tu3?mK#aD;dW01=l?&Jfibnm^|Q*MeLPq9b6( z<5Jis*aSQUyaudn$p*R*;WpqR4n%CR|I-EBU~2{NC*TEO56y?Rz*Pvh0KEw=iywg$)bP{kq$hANh4vAPo?9YR<)gORIf!Bd2fe$HO&I0)fa53Qj)_B07 z;_(Ap8-VBRWo}b*G|*UtTY(EW8f3o?M-aH$)(YS+b^!NM__PMDNBA+&n{P!9%0P#q zEAT5@bATs+w=@N+hz-=$u4~2tCv&uj9H^tMq5d1VA9xdZ8u%xL$GO0dL4F8?I4b0T zm~gc57;S51+K^WTo&q)(lYu4x*8`(Ps4sS`j)@3xjjhiGW&(c&cGGmUQ*e`MnwJ3s zs5c5k5a1Z1(mS0uCZV_orRD(70IwFRfqL3MxbZ+Qj*|mA4~~&RrZ5QjrCo5(1m@Cs zTmW2dD}cyBbFzA8AY1iD&_FH%_KC0USWE-f6@r1T04@VA1we%3M#L7Dj*}2@qpiij zE5KBsiiW2Ha2+rX7;;c7>KUJdYarDS@i^sxT4|;okC}~api_Wr5z9E;IZ=oJLIX0? z*01fLy$HT4NBmYytS zCj;3y8HlU_(F|0lAdyG8b;`?poZKnIu#?Le)w?zQx|Yzcl@C&R*p-61tYxm2h?9X7 zU0t{~{9Te~3r%7gT5?WaS?L)fd6GlZ>mSw7nyXYSe>QaclD9(h zIOID_NolfnSDdGoZ0GTL8%Sn#0wGU*OjKXfI7^tow@;!q1*fR1UYM=-MVg*YXHs`YJ#G6El-19#3P+LEgiqLHBk+9=oUyLnexdv=>$&8AuKlFr~2AnShX+2gY{emm@pU7$^mK5n{<4kUQ$%V+cfJ8Qgl7 z@%=mT)Rj4IetsEQmxkM|A>y?0jf18jlZS0}oY}jKWw-RV%u@^~g;f)!l5uVtdJ;FD z*OmO96sA&~NSqo-4lvK|6e#$`m~K3LPA7^DW%=L4mrwKb4|?*>H@n%gJBdH+LF5EH z*ygnHvVW$-jyRPSVWy1h!MT+YiWN;MV#zEekqEa9tKgm&S73%bsD|(3oeZRuf@BVA zl8ipBoL4UIgsRqkH&KyXA(iFLx6nHcq9M^+;M|7lF zk;_wA;-kFO%Z}_ z=24`oli*U!xNRs`46G!dNm{jmu4^<`G^NPo3`{f5`2H32yEn|wUS7eRC0oh)-FRF1 z(EyRd-?8Z|VVWYfJw}^$LEheHp1~DuNG9S|9i(ZB=8awRnL;#H$H{F&%)WIP(@)#N z|DCsvRjaBY;Ktp`k4qhJKalC13qvU|f$WwTy?T`K-tEKaSP~)@iy=x`ffW&20?H0Z zZ8VABqjU9;E?hpKJx_kLnP)%U&S$HtEt65|Lkj|`g=ZkU)56#h=bY0zFz42xcwH`P zqjgr+QM61nOdEb+Iy3W&oxHkiC$H>Nerp{>f)H|nO9N9{Rt=;y zMJ8(?RdL3j-IbXWhX5wE(Zv2G%d2a618zW(N@QB3k?OJrW+q0^tutY8Crc~0)$siC zC~vKev20TM`^?h$}$Z|mJh$KX8xwV zTy#o1237`XiD*h$t$i|kiFE8KC)Gw=edrb{&O+uLu{4Nkcn#pErK4TEbn@qWL1QP~!rY2g) zXIrBz*sz!R>uOlCEk>p$1)FQpPj179_nyUwF72$2Ozcfn2M|E9AKWgDD+jc*v}#v^ z|N3w%Q@+@Vu{{p0{f(B9T%14zFj6@#8`+uiu#Zdr@-g$*?j{<`LLv)37w8I-S;k)4 zhZz%3!|!$zjYdJ!@NqOa^lBI6>2W7>-??45@4XE?yKpNcl$DV%q(GO4UcGB1KkVDdnn1_Xc{J^K=RgkUoK(uP ze;dY`lRstSo+O05#oflcY~v`fhlo)syOi?I9V6*gUP>Y!CvP_71lplf$ittmIEB%_ zT?9c7s3JcXrW6<^Oj`nL^0sULC%c=X0GketW~ z^tKq|&N+!!ZW#^;duwCXvKH~Xz~u+YuqT?fGS>0q5ccQ%;Ff+sxSz48b0G&N`bv*mQS42hl$%w9$d39^faeO!gJ-QXkq+x)#in9hX z5sa-duKIot%RG<=%}XfkV!`SvUixAOlV`28j<>vY`v}S-Uh5<%D2@*Y1w_0$^jcFSE`TaFa{^tf(uGtMa1M(&}+&qM5t{VpA>|J@S z0o!qaSvr*jRoh2c;U@j$)^2Z@>jz0}8zGrlCYHHvjHQ$-26SQ=_m{Qj1q+;L7X zYk=r-X|yVd5Rb>%uqR1nnV$y4$v^;7WHpecDUzui`Mim{JcOoeWNK58$nwnp8Nm(1 zy90S#-T+n6I-Z=nf@cjsISE9=T)?`D}`GF2BF~ zYM2D%@Yupl5KV(iE6&YSm!`D)pljD1mHk3W6Tqy>k?xQxl6*>i3{| z13;epUtD6H75~@ooyxJ}F!|#xurm%_OTm=ly`fBDBX&Vum*LreA4Qk8r5Kq6Wu>JU z`3%!PUdyj%EM@hE8vgP4nB&-$cQ8bFa^YrZ;p?h6UoEeKGJhi&sF5&DA=p05 zlJ$G|kJp!z*cyfMFdnZ?wmOctb%0s_G7`}7`21|HjuHyGC<}O~9}ZKNY=dYTO83`4 z#a{#I3bMeA57vWA!_zSWKsJ_!jKSaUJBtow5diM}+hUgPPBZ`C(9caG$|-W9CeS;N2l zVIlJuZe;G{v9$8LC`LpCk4wWJ2ml(8#{$h3AQ_U$9QotO5I=wQYaXAq3cM}?r9R6* zR3)Iq3w}2kW;4AfT0kj=k?&u^ZM}T`K!3?FN-k0+`&VSUVRwAZ}(zK#7 zj!RbrgAqV@cj+eHTDqO}HAzgM)bFN8+YtRKN*LUwj4tgXfNQ^@Qpp^-Ll{b|c(I?k zE35h03(Husq6*rDz@?F_O@pSOZ243wq=@ZtKs^y?5N;&Tv6boxPH|pKpOw4m>XGH4IDvk4~^{ zh+!wSW?)4Lr*&z?@NR9WEDz(i4Fx7(fN6r}1~k@GC;9aoE1CAs4VFRZe87P4z-%qj z{I9rEA_Ut8xoyVheEi8~XcL0k6wm+YG(czLo+y9(`+7dxlj6r`c4kmTghE5?Wx2E@ z0J!*UeH9PCv7A>Htb?2h?ZbH51P?lCYX~G#S>`R;$h=G*ye>lJVTN>VMV}5OoYXqN z$?d~*C=C$uxvZ1D)4tfv@8)epcE+GX*fJr8*>W%0)LlANwJEB$M$y}aF{C%4m zt!&NR(>~_0nX4ceV%DTH8FOkmg@!3&P0B+h5jNMvc=WYpJTdnhh^C=K1h@SV*P-rz z)&bq?f_*}PA=cHu%wM#b`B?*$f`A)>9(-OG>3AB_ZfG5bE>_kl+#AY-M#BC95b+`c zddO>SbM*Vq*({s2obxUn#Cwx2pb!yj719U^PLR)}q!qeAS` z+UJIS0uThlel2-AZojLweF*eJ7RJuYRurHjUY?n|8q#^fgDW|AP)DXO+)SS?r3~m8 zp^(s&5D12uzIYwi-1jzwJh-~I21AerSXLBR*$qtxHz0x`AjR>{RGI|InYcn;@&ONZ zRZ%8Q{+QE7_Tq_7g=HSos(HdJ+q8>o9+?LbAMP?gk{3=G!Vz&^wqlzoqAP}--i1k* z^yADv6%-nNuMP-TJ+l~6Ib4-Nwfe609p7k51W5&v`T859~`mj(!r&0P(KiSAO3)C~y; zP6j#>ngS{HGx?cMXx}Qpy<-MYNcaO@)@-Y0{#QGpbr1}pQ4osBK+>e`vF_F|+Xk5Q z#1h)JZOctVJ5mS;0B%0nuor5QXyu_siQK_eSs5n-)ob$*IZQAGz7v9s!OS_UnEBC0t{l;Yd&c&mZ)GVBh1;!L$F854 zw;D8shJetxh!pS=R=6BFUWR~6gHEN8$}!`umCX2P6Qc)q;JQ&=x$N{#xOA;SvZ0s) zN`X)$8OW)D>Ng|tx}b9_NavU{e;so^SkJ$8Z^KzV+j8;X4t%fgcPI&Zs4s?L;`Vr% zFtjs^7py}NR1;*N`r~wA^*53s;MSpI35c+5dz8Pe+Qr{yuO!l`lrwv_rEf)qJ{2YO zt0<*gd6QS_i3Y6QH6jKv~dD(CfnI)=>(JtO2P+oOmL^j=gELh!@PJSVx>LtbWJ| zNdG~PB?|%a1~t`n)NHL~Y1TNjF_aG3IAqfh^r5#2f`&u=mat}8G`e(41UWoPL(A%D zc^{&h@MZr}L`Ad|ND(ask+KC5TS&3Q!&$p&#*sNVc*51d4}tFjpo+SE zeg}9H)N~+P53^0PH2~oX;9}rp095nKKndU_%Q)}`>bRG?KiC=wG9Hslfu7i)3O)m^ z1YQGP1im`Xcr3N`3y@1JBbkf``~axpu7T#E)a&*#wVQ_L3tRVEfrD|tcm!Vo)<8*= znrR#7O->B2+4?zfIdBQW*~Qj{Z3f-~o&}cBc)V`wZiMTAOMxK;aLdPH;1#>R*+t_} z1^kaCVVGTXj{zzQ)j&DmZD2Yu6A+q?9l)a?`@}HddXS6iA3SM@e?-p#pU|`{vV`0Y zj0dg(z7Obyu$lUeUC2!VwM{7sjYUeSMPiBzxB_G%a87*&iBi8MY@SluUs38sF$_~_ ziUPs_Ua%x|vSV>I(4(=7=%~HUPC@Zcii13M*P@i_AtF~HTno|-IQ;X@N9Ub2z*Jxc zighT8fi1xOw$4VFXxEV86WPXp2s~}K;4zAqH41gVAonY!ouw2vnTB}<3LU`$b;Jj8 za=;AWFQ68H0)!&veOsl#B_KBe!y76CZ3CuQrcr!GGeha>15NRPnU{M&jaEuc6p^9d zayD(D-5ktDv7P2cEilEBFvKzwTn~JwJ{idVpLr2@9@INPp5}^xV6g?^DVN9flxY}4 z48s@)G7+R5V(vhxXOz-j13nWGS^!II{R-p);0D`J%RWEpLh#%LkKf3GXMa97#rzX9gkJV zuQVg*`0MdleCBxI!RueaTVrqF16*-PfVMl<4GA?yyLuq4kgo25zTHSQ0D$&9%H|6G zikT@KfkjKX{>DfV&_2g(06Q!b8AX`13QR^`Qd$Nstpb;pf&Tpn z9Y@2t-+)``8T=jVc%?4niO2iEVX%OJ0I2{uDJ;$dCZnRF0+W`7$;wI|BP9I-F?d&k zB*stpkAfc34}nAZ;89o%^tYm`8`dAME_CelKPjMn{*lG_{avQxg24!`J}?<6>E9{+ z0h*cp|4=mgAG9Cd3i)5Z|EI8@O`s1FW`*>_`r{DC2X{mGcPJmYE)MC6$Kq_T*qeW< zXyJ*)WBoj_K2TlV-&IqBirJ$u?$`jo3x9FU%;3ftKfEgjfi%`r7dlpvLZRH@I?B>Y zI`YzTG6p*OGBWzgI=a#Z272<^it_SudNSI&f4F*Bgg+XI!T;g9|CcNOuiW45fc80# ztcS#*ZXw+ba9A|-ue9N)fA=EuuX=xT-T&Q-?7wnh$7W!^5BGl^_Mffe4*EU)r*)4P z|1>`mbKLQ`=PvYndKHV?#;OworLaW!J8R(1_#8 zr|Z_6$HP@aSC=Sy`&D~o!Hv$XVNH6kA{4Q$!$ z=`Y{x2m33!K8Nk_QRAt#?fsEebTI8aTGS#RH}@KDZ@M@Z+}3=F3VA%*mJ!i^nZ_;h z1%HUd4&vB z^Ac?}7hKZRrB_RHWlfv_ceKLoN?);1`OmUD4>#04%>rsbnwlGyaBJI^E1E^5yKZuV zH-UkgJ7?CW#P%b5lDmq)pK`sJQo_$>G^rrzSIx)N%JbuBO*Kydci5?c)J=TmXs_08 zh7U=r38H2^?*tT#^BawuOkM0O+*4uyO6qOTu|8e_^ z*U-9}B6eYZBrxD>+NmZ!o&xa)0JX;V-$ZswR)=Dg1Jrg7Zf}Qv3^>Zg@si)1M2=D{ zG)0zermI)AVloZ+m0kIo@xxo*46UoKSVIC zdArxE*t!PX%!_lACB*l<9uN)Lt`ha)*g=si+tzF5@0IRlRL84XtsUx5Bo7$-=C6H7 zYV^6db8fjVGQ%qzzB8Mtz259fpzhC<%?}#J+&yx zQR<@qMkntO*D|4K#euYEEqm`7n;5`;o$2t|is3bS z=&uG4tfm8|Jy7}B3GYGUSg+bO`^{2-@#q< zUA}-*Km#>EUFOBJZ+_H42)nyeoNC&%@#qObyYoCDSnro?&A`{Gs`!}hn5 zJeDXH*ny!5a;V`!(V;-7<+GZp5+f1Fm7{%^=FoB0=d>Y}udO=K*WBMjg13`fV%bh+ z7;_N0Uo|^X_tL@D%}oha8S#pVzL4Y^P8(9UvOPKEFhBA{YK=P2reiz<`G?|E{o9lw zjdhW0?fY-X5W&Ttd7};&;hf+DCU^@u;QAd4^@O5=lIZQup{wsz6P}OrXPU@LU&UUx z61IN0Jk;F8z1bbeVKfqJ#CwLh(J(L<1$Mk<0`}QO9ZCqy0UDnvpEBHKRQmqQMt^{k zE>AWOF3)=wy}rTZ3<_gc0~C>)Z+9hcAP-k$8cCbE(d&H1Xy_C^T9uf zY3>7n7$!hAh zs$?es-Ym0^N~f$&+8Od(Zs(Z;IuZyU?!lkf!?yVMA1(5f>r8mGwZ^~C`bfFpwe7+m zL>d5LwDb2ZsE^U1>{b_S@YXZ->C6t0ME4;G!z>teL0(dX{^NbsfxA1hkYQ-7xyZth|tl|yMa3+ zK9vzRqtDcQ`tl<=%!YWXZb0Q1D#om2w7Mg^gNy-xQ~f1C-!KE$(c+~4gc%+c{6T5# zU0NO{WA8LtBw40Lfm@u`YsL2_To&*CRNio?MkBBfDLIj!Xu}^KoS>_b7-Cxs&P81> zsGDPRUPZSKld1m_{*ZhU<$BJqa zg5OQ$35uAtn^E6cw_<6yCP@;yy#olQ)z$uBW!u5Y+GPvl8D;*oPjOj$yKCIhFAO6V z%pQtz4e|zn+zZ)g%a&-3!;^yF?S`<(5NoI!!*h+&US;W&;1)P(Pbk&B<#ge85BK`r zR~^0~l}xAAW^IMAss@w$+G$4>B>^Up!_L^^99F?W0oEqx(gWHcQZ>1Vd!{Gq?9kWK zsVojg*Nd41PTE$=Uz2KQR6yaIA@x6m4H^fUH^l^g<*<*2eB{dpH@vOyqbL^SL*1nG zu8`4%_Ds@O_U8G?larBGg{lm1@rBv#l`u7g_FrU%3L=u=jn6b6cYF z-{j@SnwR*S9;Fj1Q}?JF-?FsxU0(E;nabgScLRe`yf~6cpl9|>*=fS$)j{w|nb*s; zCuLHc)UHxV1(W@o@S5al}_@M)7N$x8E5pr7iC>nEN373_8mGW?qk;x2!b*s?D8~Dl+ke;)h%Wh z`}XNOiO?of_XG&8TJv!W6$}gT!I<-<0f}w<7a$~c8ZEzO@(c^&$1_jx=c~&$MZ-3X zVbA1xAB%aUxg2OjKUIGPU0VBI^i%lm$)=b_Ko$Is9Cv25e22i5bK(satg!V*{$=xEC-^ zBMi}fCoW~eeRIP65yTFyV;74FO0}208I#y6+QFxf78*9`awdbkw(FCqGcG%+-*mhqKxzKF9Q@ z-0B`jYXE8tpYYLXS=eReLD;u=U~xub_Sn0N5>GB?53-JA{sL_`LW&8xB}5NBK*k0cEPr(HU4QRE z^S9ngyNhOpn9e|tW*vaA?==xD(s-LZqRvcPA_{8L#=NKm zN7sJjYi`pk-68{58^>En)U!MmE<2+l-_9hnlErg@4z&xxM2oUmNYXJ)X~k% z%|h=|NEDe^?TmcKLmCCvJ1hquDo--yLfsl#Q>CPye$E$Y1Zt%_U2IIL(jd}5%6H(T6@5KP*ZN+JGI!l_nDg< zX&#>!%%V@rrtC zQ1rMC=J|HA7H zgPYt|OE((gKJ_2pl5VW=0_60GsUi!qh?7aK4u_FjwZ>X}G+%fI3meijxpKptyt-bI jF|}u!j+{4~fgpg+c3aT>$!i_IfBG2fo9k8TxJ3OAR^x`5 diff --git a/docs/_style/prism-master/img/logo-sitepoint.png b/docs/_style/prism-master/img/logo-sitepoint.png deleted file mode 100644 index cf275f6eca9f4cbd7c3b46e79c034de121c295da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2703 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 JPDHLkV1g(N_A~$h diff --git a/docs/_style/prism-master/img/logo-smashing.png b/docs/_style/prism-master/img/logo-smashing.png deleted file mode 100644 index 8d34f6f38d345c08fc3263b3bd8573d8a07fff65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14160 zcmV-WH?PQvP)h=Zi#t7FlydWK*MTy)%uT=BHyljUz^D)$CaPhMyXh8=PR& z)GS5vutI3mg8TzP%OIPr<@4^j;lV3sJ#gR4=~IsxOzqr<$^PXr(IH50 zvJ#TpH(>S-?Z^()W3;aP`O)>(Tb=~FNQ5L1__HlgvlEE0XN3rn_*j{G=}5XoDfJPb z)9_Yy5*YavlkdM`&A#PIGjqgNDls5P^oyC?rcz;(Iv6$Ox02*RBuw&W3IqgmFLb^= zDCvziqRvVoJ{G0*X30)Hmo54~Y{*uI_FC361x5+*4GU>T~Xed!u{od(H*f8`1S!ACw> z7GRq8v$xd57{(sGQzbWD1v9yd#A+)JC&?cdNg(hia$ne?AUh0T7)bp5D6+!Di!b&A zIG*mI&nW}{Qa*vum$INtwmCKS)O|>FiJ9HL6{AfR(@1Dx-e!fIiNHO7=X%J0NXR}f z=NupQo%8|)><4F{*~IGyk;Mj=U3a~abvM{P*41{tv8p_sK5D<8%$PBy2?S{Zp({0~ z)ygE0Ier%;Hd`^9+qo67w(|GlQn@tA8-0CIlKGV%D4K0VodG3Y?m%-xrJq<_Zm&z< zD-!bSA5aoea}Aq@oA2cpB3Mhe~)fI=Zkyo=lZzp|B<}=@w}H(#2bte zS!KzQHOd-CE6esX8Y`}jbymF%Z=^Qgjjb~d~ltzYLfg(lFT#bqRmT?=3zw%>Ax$E6%w~J zJ(}oH^F8W>gOyOHQ;?4lR>4`$9fu#W56*HdO@Zd1pWba3VfEil_MM|Nx(B}Z(K9fzsR z%p7KBZm3L71Gf}GHNu(_xsLB9PnKdA2sfckNWP7jry)cpNs7` zx{{~+-ib7k{d3WM^{ufzyxtz8J2tHMBu0!C>eG%_BTXsoWr*wK>m-MK?~L=_qJ75p z=q`Orc$dC2)Z>|pG~Rbdc)PbZvfb#GYix&iA=09od>;;N@otlK2yOCiGqu6@KBP^F z?$d%sY?Z?sUg_=$u5@+>RycYB%N(sp6HRlhse#|;dwrzcQRkoUSR8C|bdERFEC@8X zPMG>{js=tXW!zP>FE-zy%FG>--G+*8Lp$qB(OzFEI?!B+?l`d;KXq}n^y2dqzEPP~ ziZnC(sur`VTF8J{;n=Vs`}ddqhgEcBIbE!et4Mc)3UBh3i)-j+>9t=*8k%&#ml)lH zt#$3daX+eNJ3K18(5-TdJu0_ERQwc!W_g2(_UVb}fZ;|*{++EOn|62oyy!mP)hJUwy+KvZ zXi(KN8ckJ1r#Gt9fqKQ?KN;KW>qXk6^jp=LO)9n5r_||9+y~>Y$-Ry5K$;DvsZEYo zBQ4tRZA}gsN{!U3%z#hHdyVZfPDNT|r+&X8`$+Wg_k)cpvQ7U5(xSjo*x^&f?LO9n zzuQr-!Y6w!MjEm|$FzG)Ao`BlV5r1u)_lEA>g~D(X=rN7rior?tC<^UC-}8Fe;sIX z_)$L4TvrJ+IkNtSx_|oX9d`!m>xSbl*V~XLnz^5}7w6RJfyTO2mNsf+i;;_T>y^k> zqZ;9jM|uEds7*yr)>P_epHiw+v;mnl92Nhws_@$zRqpn0sqEF~sq7VJtL$~3RoUCV zuW}Dur3!z3P?g92hpLwHlRsWAo7dcfx2o{kx61os1J$d@CU3FSWhj56<37do9ckzl zNSjUSVt@ERi|eF7vm;#sjL>|i5=fFAY<3_?FLJBcLYE3Hbd@8EovO0Jt4K5Rp(U>E zNQ+oj%K^qYJCfkuZ(xZR2G{8pQ#XZ`kS7N8=syOTuNX|ji0JMKL zw#Vy@?J=HFXRyil*Q?m5QOO;wR{}~2@xA$HxQGc%?$mn4zyB1{OzkyptQ>4mTnm*0 z4FbrciT;+iqoV>6>C;pG;NX=@%_=sXZy z;Cvy={md=&s6?B~yw^C03O74cve~I}%`TN_bf|z)duYs<*NHSV4HDd=_AABZ9`g-y z(-eR}I5%)7fb8N!4K}kmH8%5&(*T$a9K}PotMoS(snn-FqEZ)pP$kd%m`a}g5tTgS z11fRqJ5_x2ttxSFo=Tj*T%~?=mdgL`9#u`pCz`iTJzxWCdb>OJ^G_u9*SHE4}e|V>!FPnhq_aZH_ zm6Qx9;uaufR4i@t2_%0`b~U~O{g>1yvd;b0f&jAGRmcGdsn)e<5+dGDXo>T}>@v5? zk_m7xOE^d|+Rpt!89+(3a?hF^3Kz-y>+7nyMyK){wdFCrZUku}`}b*nZ|(gVq9J*3 zC9=tM5k7zqKqNx3!2R+;KS-qiwsj5N`DP~b*V~y34MOC zN_}RLN`9h4CC+M6@%^qN7wL#DU~ZuzOge3!yz3lYEok_-;ATTyGszjushh;b@XBNnHXcdB6}iNmT&~SguDJP+g@?YZM2`mjQ*}btuvAcsrT~$Y0rz z$a0VY9At>s?889@+2}Y30uUTz5HNP)AOiXh<33~pA`X&}fZWagvmCWrAoXNzK8rLo zE!c$Cy1!QJ(q)~C1##__?rV`|E4)m25uI|ql$n3({aM%2t$yljY(!asojHcCCCLkMhG7EPxFMzCa zU57LT9L}Bbj<&XT0b=9~9Nm(_y zlwYsS1wC~-GM%4uO*LQ0uQXFE2%Z4Mu0I9@5SJ{HD*ofp>;a*k$lr8<%6#brmHqB= zmH8%cd>wrieYpb%X~#hT4%>gh3~pkPxX2 z6=Q^;YpM3NhkX3KUFq=go^hJfUkp+$~cajjpm*k1^> zN|6pAKpI+R0TKa_ps3STwQidGOl1Bpdt-*WT!R?^2M*+}MsW}zF##!)R&YH6$n(2C z`dUPB?)x~Y$l+YCkM+Bw!98$i37$89OS|1sBt^>$U`{EMK9nkq-K{LaS-4Em_xBsy>CS`AhX;g zG30A99b}k&V*xT*gNTC+O#m`52?*L8+cx1K01}sUi$Ex8XN5Hho$R^C4lwp>hSmef zM%H2-fQVzSa^8rv_-fbNxCc*|sqa!(HoL;3s2QIMEp{77JC*`X&YkRuY@`85e6@e^ z>vdO+>GSH5dH=Mr+S;xtxpoxy0FWwFPY0yWqhiAjRs8GkXAelVtnzn$0YKiXvfp2+ zGT&;SbP)mM3lo5R3P8@EuM%gssQBpsvR`fp+D+sdY9dlvsh;H^hB6(F+&#O&JeT#f z|BWAiH?jekm4l4eJ1)-xNU+gS3{k96tXG3A&WpIV#uj?tinQY?xR9KGiR;ng1R!Am zp%yvJ&B+Hlot4r$vw*zcPCaae*OOkE?%}SWTxf&)*@y*6sl)~mK&|q)C8oAizw19AkpuV`XYUx z={-pMccPPZefi)dAYA{XsBMsTG{uMYdOB*kj5H4h07S0+#y&I&NUsG5d+R=A0kZuF zAXT!241qcm0}@HKvf?Bg_1);mJ+uJ{1IR=zas$#rosKQZm2PqMY77?vl2mYsyB%rv zP*C=Xt*He)-K=$E^|d?4d~@H4G&EU*giffL>u;$Gr%%?gIioxYhp)NM~SIVJZrT|_z>*;corN1jK2B0v4Hf4*yQe7>VWU_ct0%!5g>Oh7ul zidv+YkejjGc^QB_B{wL($}LeXK`LM%O%zuA+w{EZYv8_kKZP_l+J$PkK>*2Cxzg3T328^vU#~}pjARk_0Fa#2 zB2sAMB#PSNZ`31KP-h&#HQMbN$V|od*?{;;sZp~?5ApL?Mt2)O;rCq}88I%3?e<*= zgqP3|_+fO|xFa@bl;d=^LE0?OPQBc|2I^}sE45Ht0Sd)>3HLAy_AE6? zzMVg((?RDW9&C61G1%ey)>xa%hcslid8tJX0f^}!`A&~nyc4;8TwddqdZd_NJDFyQ z!fRc-GF@J|sU^kd2%qHnZe*kTKu%KH4c-!fun{IxED@i#Io$2d$NB6DHV~;+mey!9 z(qcVES7MhTZQp9DkH0%bk4Ja;T7b1RfadOM=LyNE1a)@_bzY!I6vQjb<%!soA+&>HL+r zcL)~`lkQTiFF`ZAoKtJom)j7SZbDiugX5X|$VxjrHQ$v19B`B;i*z41K)N7pY|uBU zYI{7e-mQ36SFAH(FmXpg0YeH}3X1RVvCr~ooN0f-5TaUZe(*$E&(l9l_s z)%%uQ?n&w$U| zq=^&RL&o5+_e9dHvq+16j)EyRxzrRzBr^~kq<~5p+(Dg9&AH$B2@(gnS_X(wK#G|) z3{JAwNRw(M`92lht*1z>kReLqP#IukuK=0}Nc|)reG`D}nhpp$0!YTHwVoc|q`wPk zxaSYc>_aKoi&oqBvFBMg2Cbi|nl20=rjtwnvf2V9whBNx0c3?^4l)5b78nvJgAI=N z2Io7xWD*<48y#o+8)|>beB{A#qxO0B*9#h7w6PQB)d$-g$<$`Uszb0fYfxxL0isA7 zHmIreg`Z@%mtM+olj)$u##P}rH>k`H`&IIsI&RK;85EMYpV>|g(m{)n8^Xd;_gH}R zpdCmSh;&30TH}_MTqSDM0!U3f=yiC}zZ)1HH)_bjvrt~1f$zgXeuAC|0!W&4i&U%1 z0m)o`vFRjZfklp1I$94`R#|0OjPsHrENEZH7Dt4Y6prT~Omq*yT9 zyuN=V_8BW;qxCx9w?+2qEs5PmGv~BAB6~fK_=qtW8#W3laSkAv35eN}J{lm>x(1Rg z!H{!W|Df)8KQhm0;UZ7v%?5_IoVQ%|g2)EXCrVbDS}I$$$YqjZu`hl#F=4NmbEy@M zMQA!8D74VIko)*Wf1C3{hQkjL2%qDAr$a5&q6EksBLJyU3)1p6YM<2jqQ$ijzm0;w0Yr2ji~REn4ni$bOf56piNi>v7U__%?q8R?TC!5DtB?jDKdW@qhUp-4v)Cn*OS9K|${_>TcOJTddhZ@SwdoiGUMNui>ma`zpL!sm#V~PHgiZ)W3d2bsFlLA z5f?MCU9`n=4-O7WS|wyxagS=oS0}K18y$CGXh)H8W8NwJyq}}|&yD+PgX8tJk35gX zBGYbX9&7ivxLi2N3j#@~g9Sv)M!v!VKU@L~oTQjshRZBFxQj5fK z5daZbZk8fFM;Zn|WpNn*c{DYuzYA&7j_b{~ zzyc)JHvz~l<33~oA}+}1$nKJCVLBipT=eFNRI7wk%<2MGrCnZN{V3Y6M_PEDbB8!J zsa8b}GP3S5>cDzrozap2YIiahJLk(etpZsA8A6&sSj0sXss@|tDwu^zT7aB$B=s0` z&O5+x#hQh5Bl|p-?IMF{Un^07M_<3VU6lj>w4G#jHOb6*$&3`O5*g>b*9|Iu)_T$q z4M07-c8ir>bpr>H+3yoLECuwMvIR)ZBX)XqtUAPYiJ)HFUut0p_!5vjShfM7cB9=pgftOp*7u9nl)WTYA6|)8 zxyk}aXtn2?NE1cZxz9AaF zF=T_4lXSBHB?}}!VyYfhG&`hFR#MmX8wZhw%o&ThUTkt36kOsm0mBA_0{sfUzh)b+2j&OnjNt(732S>{zZNik&}i+2nl&D0X-8rr|* zxRq*^QAr}2xJYERX9dzk%ty{BZ8UFiLAqJE$34%VuIb38dg1MJKFC@5r}Esi zXt!QOYMP74Ok}`@q?Hf*0qTdPD*uO{3mE^Kpj7`0D1|3~&+D9|f?kb|lEFrH*bZ_8 zkg%7lr$z<5wI`t?PBBx{(k5^1PfNH6N1V$*SDHA7^CF;p*WXy%&j4{N(PM;TC)fB} ztoaBQoUJVM3Lt+l4>EQ*e_dW`I!Gb4WI7;;6^^&#B#+ARouIe~faKSB#YuiI-4>*Y zBI`V-7dM(YbV0f@ILQs%;jgbsj~zO zBmYtWF{k!N1%z}BK+IaC&$tiaAjbs6p67@(XDI+XY&gU@s(#Nrm6CMJQ~OzT+*Yr>*oQrHKL8|;l^a<=`F%hV zZjhYQp~5mlmIq~cOs>tvVNF>TELmE-%(TsH*J{Mo?l50P*or+Y;`PFQv+1)CV zY8A7q&4T@l$Ub-{fSi`=A{|)g%H@CoKyKm&|2S?%$70$%-tYZedaLt2NHYgn_Osnw z?V)0HhtUossd$f>VwK~knAcCT0e_$vx@Lwq!uhEI^8P6J;_i;q)}{%O9EU87pYFvB1!J?Gh_?J0{5E$`LGNQrxmy3&YDl> z_$6|zJhI8GMXVY?An8TBkW4n$=xho({@uuFNnHh;@<=R)5^ud;l_McFJ!b-x%Kthj z<#0%a_YSDBHvvhD4jA*0_uj~7-{26oeLw&TuQChm13=A zvP%usj>NHFi#UGbx83NMaE#DN?og=9ol>N>(#BLf8{|Ix0BN5-+K~u*0|8}} zGg@kQlNp88zGmraJsIe8oxp(ZE9Cv<3h9k((A)^(TG&^w@*)5!;T~n+pzRw2hSQNI z0+4&;ly3oOc!Dqk0>ZbodHetu-Q;+<`P?XAtI|W`KAO zE)tg6h@@HxTqMeUh%9%Rr+t$szYJikXGZ=SXm_j#wK{)PSl}`5IkS;VkfzBOJIxxK zJNG2&kZMA#pF@EWvmQYdhJZqm>80ffZLLJa4lnM3minj>nj!9ED!~4X40Wr_O;@YZ zVY7&zk$R!oLt;N!NT=(0629LWH5BZ(N$U{V zuWh!8L(AM>=fSGmL(84F({B8-U3A+2hNlnLh1R<6!bz@=taDu_DHgg(a%zguo4MgP zMmX+`jydD8hk>}rb_(1(IqiEb4=CM2p?-}3g0xs4^~-?%UGzIroiSzwWYPtK} zNDH?+8p#m;so2-lBIhCi;fsB;@$d&vI1y_(obA%hQ>OM4hGFa~xB$``(R?J3bjaY$ zYe_&^)=1~3Nd@(HtH4}wluniW@pn|=FORdsi!Z%KnK5h0+;_Jc*EDIpS*>0m33zKt zG4uPH+_-1nB6Q@l99J_o+zX90?-*~Wa|9Y|T0~=w&idH=+K)_wjTB98!2j+4tI7W{ z&74{EwZ19T=bk6W(v#E+iIMuo;2vX+J$*+eaQ4Rh|4T1z?PflxCP(2(b$t};t8w%7 z)P^e~D?E;=C$8;%<8O60GeG>8zu7T>G>tD)>JZ1P{l1z9&2w2Bbc!KqnE?bT2g4Im z4Hue#q-`cB9t)J^&A}rh-&L|DWxh1#}z7w}+?9%-n_E}QGc$85Ck{G| zo0#;z-+FH{*-Fa#kM29oIbU|QFf-EL-`sm=MzXxfz*30+!`6f?=8_8`F_}>Q3I4O< z@P1lH!@9GrJNnyMJ(l?Gb6l(E8)w~>@UPR-lcx2C_XFqn&ng!BVd#th*iti42ohPn&}`#S2!*mPtT!B?fQdzO1`-S+Y1*97tE6vs46C=t6fpr(uo8hv4mtpEF6mxl%E3I=6DDxJD2Btc+4-u z_I={_u69qpI_WsxIl|4uIm8ZZMeYv8%Is!)>^k#A)&_fR$H4y_eBy{c`aa`6Uu~ZU zd!1*c@0|Xqi=*73w*f-w)XadCFc zCMDcQIjbTRRz5U0T#@Z)ZjQU~(aS}C;7j`CBFoK7E`k>s+FzcdwWUiS?>7@FL9Asb zsZ?t25SI`2LPs2N#5}w6L4qsHU2|D^aoPQ@yXI2)))u`67Z=^5dcoy`Yvw%Sh=!n! zjUeTr*8&Tk1Dv7vt@FH?1Dp?!LFe-RlE2Ez_de7XyXRCW4O$aCfy6@w0nvYR6nyRdGee4t4COA22wV>4 z(s^)&@Y|EmSnYTmR9=ktJxAg99sl0H+&>E^#tt65FpK}*36^n;LLkA~CliSjYiMYgr?j+m0dR%zjbA-yd3AbZMa>zTS5%xa!tX)j7&m3i5Azmk*YTHL zIv?9*PxVM9&s>K3meamIgJ_ShHkOs0vxs@b==;<;aJkSuI-f`6YVVS&H;US0<@O|O zXO;vAf!rX0FiO;+MS&oZ_(G9LASxCyBtN>Qm~z+Ed`4e9z-rm)pMBD;ukud0>y9pg zj8+I3ELLQzJcKV$jT~?(ILMiY*hAAcSygvac~#v*bZTdxL|VBC)oelu9kG z`gkALffIiDR)1MV?WUCb4(H!geX1i_T}OTCR|Ky=OjOpd1J1-&o;quFu7B8$TbKrp z>od2D3P%BMI!IA<}kvX;Sz`hV`2d?!9b&7=X9N}XJ%Z6{`)<5bnC0U zzdvxFCF;VwNb4VgKnA{~Rk@J!m*4FnsuwKBNG`nW>TIGiT>)lX{#HhJ`{fNcW_gge z$fxEnRs};!&&EDn)UDV^EArenfK6mT2)^z{-K zWo(nEtliPtBxvLA@}}T=68?*s6NV!PZor(vALD}=>;4^$+JY~V%rY*US!IzxBod24 zFNTOM49kPW6~g)|38eYjhVBXE{(D1#3^9RdFrjTSAr||MT960@1#;@C=PiUl?&4uh zfagy=HMK0vlDL;u))#g4>gkU1>iVm|2{Syucb8#f@VD}yFXf)6!3nDmqCI*GpZ6i) zq9m*9%Q-()Mheake{5d@=bFdiZ0>o8?ZsB7->yQr4A*YLz2CFl%!F@klvm~0XA1qm z7l;YqEBHlqgA<;Bw&C@s<*8IJhzjH??z4g_AAkUSksox%Q zicDb+*ZgqWX{qI@^9a>nN^B15*vJ*QWHL2?&*mG%vE`oy zQonB)vTet9td(KsbsQmljrZss^&pgaHEq2QTrTW$>Ph%7qN`r-pNb&lK{6%~j54z% zv*ZWv9ZcMHOc4Mz;hv!!8=~Xd*D>Zx_)AfJR|>q) zwD;eQ;XOKRtNK)zhIvgKCJ>3QW$f_5>p@O|n6b%F2oYYYsH!`jP~`$1@Dtz++#WL$`dv*spM#*c zN90X1T<`>CL_8RYFm3w;ZUZOGGx-9Mkyq5zZV6EjvUd{k=(XMl_fO|NjRq$YHD_!q z50gtEc;E(g5a9kQ_%Hm45(%WTOCTiaLZexV^OZN;clWGI(?5JaJF)(BMyQH(zDq2N z!WzqDGlP0bT!Q^I2+J0qaZU5pSH&8|vU!k>DN|V8d!+7V=(s2-a}4d$&qYpUE`-sn zFPlaWTmcG(Sn9QTh;K*$dbo(;XFvYM2=?XNcN%fg1LfEwzCgU5PJ()-qNXkd4lEJ< zRAPX@_{@`31;(8I)x3lC_9Vr9h{?BCkF5|z9)g@=1#pJ7y)2blS>-F#0Vl#7>ES#B z34WLsda(3+`V4%b!>j_S&L@!iJrjI38VLiZ5S23D^*qQjxeK16B-=P}7jOaMIJO?gnk-rOjf;N+aQo*%hDEDxex2ntFm1-fl z-yujdny}aYN{M|UarR>2h}_{A+<5963cesLY!Mn9s8LuX0YH$;fHUFvbet%FZBQpfrA2+KEdw@#HHFCu@FRQ$8`$FaEXJ$1B08W^3DRfgk)OiA! zKmx(i8!x5vx|VY9q7^943B&I^NQV7O#_#bGTO?hjOQy=Q#p# zfb-~?6j2@&Q4W+gLgx~Kj1+gLmc~? z$1rhpM<9&0^x@(C#0b;oJnR#dN7%LWy3cX!esIDJMZ>3x#kuyv1ct=W>7Jn{*aGUI zQA4;8HIh52^Eq%rUI&~H*SwunZWQrY8k$8A8QO8Yl3^LEasqiUjiRyrSR-^09hDl$ zxpAIXnmc4pU+~c!MDGj!ED+8`dwY3V&9!tT@#zmJqaQ>^u^K|}>{OnfLpYH+|D|m0 zGw(@64@4#m2cE+hk`(W^%X>_^_=0!~4LK*Vbn2SiEi8_xKuRT$7w{Za;6M-Wf0KxThxeBC#34`miKQ8IAf!r$Nz2QZ z0OuovQsNGE5G3nnV48fR%-?lCxG3Z_C!Ah)uDn4Sq8{r8wHsW7aPFdRQaHC@a~i>b zggl#iZ*cig8zaNKUxZ?hCh2?X9J$bn;4GKXYYhz}l%LtYQ(m6x1<}sof8f^HwF7$Y zKR;1t^lFa@B`i1`$T04JRXV4A@Lv9$A)ePTL9_{Zi2_%T<@{@GuV`;-I;?3(ucDuw zTM}Fp|Ib(#!iIU3FKCj?pCOmw$}kmAm*O433C;8(oTtDGT?m6nl+EZAD(M)^ZHAd( zm{|GADXG-bpli&~!#pBAIf22b-YR-{%3GVVu2j;?zQU;U!6qDoUY&cUaLth>0M$qC z;15kdq5HuZj2vBCU3+#G;hl_!+arwm4-)Jj%=QR*7%=ZhU4hsdD;SZA4)fBP?5}fW zWvP`R5U{n8YqzdF<9vyGDjs$fhe<mp*eI7i zGxG|1Pn>&Cbqlpa?a{v4fr!h%LC^LiCXnEFZ%c;1K6N?5t8>n5?P$zIhhL-NQfEv- zOoI2|HaHbFftA4JArOOk3KPVC3vZOD@JiswwIZ5?YPp7T73*;!SDBs&W7L@=mNyR; zMF#$_FvjjA*Qzs4J9_$zy5%9~w(~vk%&Ln|AG`gQol|bTDcknT&rw0Nh2(Q9zrqV} z2^<2&uq3z~%n5T#6yg3H{|Q0-hnHs0QE0h4xP0_vBQlZve-CIFgzCE z41XTM@D~jhrf{#Qa>M*(ZtLN#{2cd1+hG)Jm-Wtt(0y@@J$F0eeKvM3A3afEg+FXO z2{=~uuEWIPr!5Tgwy=!kcMY{_RI7xaQAwQA-15@%?{u`bUeA5UW!u^|WV>R4O6P`o zwC(9V&fok^p_7sCs`K{%hx9$3WgWL?)-Ar5wN{rf?7H8#&JF$AxkbU{LZ0ETDu}=N aoBsn@F1rZE`tJS!00000eZJrC@A%J^Oz_yQtgt}=001bvy5PK| zYp(Qjl#`MEjb(1mOPBS0Jc;kk4&+CWxpaUdjZLA0U76${x)+^Hiwx_aqXB?bh9N`} zpXBa_qOzH$Hk!~ z(RoyE2!|iSW`UO#$rScMJ_aI<^zRgyoL{mm-j6a#3kDaEIdG&YVmYOsKzH~54`nic zp?Q2S`akjhPhlQ0l0%1k(Ru8HT&lEjf%?m?I4CDBoy=!*iEK9GXB7#-Y(AS8%;tcd zoR+I*4K~^x!lJPwc*fs3?(Qg87LQM6QR%KY3`DA88WKW7;c<9tOGig@GlYdX5{b9L zS~)u6aL$f4&gRzU=1vwrxi~iUAd}AG|K!sC;adEbyF3m|j?^=b&J78t)10|%Cir{W zsF2^sg8Z%C4=(NZv6%gq3zr51UvBPyHT$PYIzh|ZU!yBM{2G2bOFHpf>1flZYzzPZ zd7&%Lkti5=@2|+vz-tveEv_dPks?8BGCnP{j+=OG8WVuMJ<%>u(>PMg@FYGmtl& zKloSp*wF_T4f-INH5HN||9kpRjC<$Bi} ztPKjItHBj|W@?)KeSxZx^26~mp53+zgm4-ZCkxP z-|uQT>l!UrWjV!_TxexbR46+aE8WNgkGip!;?a_ut){IWdZm-Y-;RU|n_7Y;w{0w{ zySg(|V8XVYXBy*XF=6Y%%%@CX71wNg+mGoz@1?;hTb5!Q(`R+UR7894mw!-%fxcZm zD^Q>Eqg0%Ai&lVvB+*gX8&pBQ8$?La3Q;Dhg?&xe`|gWxs>z^Mq9lYu)+i0tP(H8T zrQwWL4}TM89~W*Am1NsG)2c+NZA;%mUbO@e#gye8T!^xENMjO#Di*P~CI=1us&z&B zqAjg*O*s>n0EB}`qL!wX@xr48w{ttt;up5N#QR#CZjsx;F?-2z9PIRKa9BkfPse%V z*0k2cQKzl_#IM^e7Ex213j2OxsyonPhx(Bh9!)6GYqyhm|BI`+@xuTyR3 z(w()Fbk*01kUF=~lH;4w@0HCh0V6&lCL*Tp*ylCI_1U}F-5?FdXAlCQ{aGQcIi-bJ z@(qZGa-iAB%x6wZg(K_kq;_{g$#Df{8tH@gyLiXb!(UX?GTOUCmrlrfkBh@ z!r?uM?9e|wE%TI^;?zob7k>gRvLWYR0OA98p(rdkLw>-n%P3{Cf54DgMwUy;%+8> zm!a8G`L06`C^4aelhc{logCV-ii^cBjLSXjSq78%s+Rn5&*NDVNL+dv(f;?_G-*$e*b~msp;M%B?4U z8)wW)qW8el8(pjm7H^2DRx6)smo=&n>{*N3quKV+hhD~wvy+k2pRVgIQPew}3DRfp zM!V&QC@}BCmYi3;5^kE^LDuNe^GVP-Jj81Jbn1?^SBVwMs2(Ocs)85Ya;I^VNtPD* z$#{*giuySNE>o1N$+@udFTHQp9yS;xtGBFLINFczzQA~EFE7eS|b#tr&Gnntnsz9>2dr{Zq-?1~riBquV3ayNyrBw7DhbE9ls zQ=NZHPxg3XH$u}ROt7#6KYhvL>crcOz7*&Hc>G(E*!BUwBriVtL_QpQ2STn)nEPW* zneV15v*LU&{wq;#G#{ht+xT8-rkOB&l-ckqsJ|Edh@dMNsZmnwJCtmgSAkLd5Fy$8 z-e4baj7EBcA&p9659AYV#_*~)YBjJ8j^!Iirj5ATUU0}Cve3bc06^d&cdGqfe#Xa{MP z@|=NvmTbj`1xWV&%b!-M%B;U^e)F^Z7VMweYBHk= z+~MLRd0+Krfan1y@&N=tf6YUVnN$OxN?2#<{!Z)-_N#NOoyfu-`PUh5Xiogu50CL4 zHK`jz-*x1qm`2Y#j5FG253Xv$h0oycDj{|H7p}I6<<}Sv#))_ z-m}C1229YNrCo$(b@690HSn!Zy-<5}mQoQQgiRJ5Rc1cK22;<{EgWv`;%OOZnas7^^EpLy zIrmxPYMAfm_$Rns3RIQTk=PHH=O)Hp$5V$EpHPPs8L{0Sy_F1aynzbo&slVY;^WfZ z7OR~sNE(>DeN&F_XGHcr{S~ZjZ=b5ZdC@WSvPUDv-FLUdUUd^L8>zJDBw>f8JqYCE~89xH})#p4=Zi08cOM!4gOAOQD knH)7A7kT)m>XIA+&{Q#FnNbO5Eni|>@gBHp?EYi_0a`k|vj6}9 diff --git a/docs/_style/prism-master/img/spectrum.png b/docs/_style/prism-master/img/spectrum.png deleted file mode 100644 index 5954bd46ff3f78b6c101148f6475165c9392fcf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359393 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

  • Um-~ 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

    EL7`ns6- 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 z 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

    aUzBd&FnTy%XLNK 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 - - - - - - -Prism - - - - - - - - - -

    -
    - -
      -
    • - Dead simple - Include prism.css and prism.js, use proper HTML5 code tags (code.language-xxxx), done! -
    • -
    • - Intuitive - Language classes are inherited so you can only define the language once for multiple code snippets. -
    • -
    • - Light as a feather - The core is 2KB minified & gzipped. Languages add 0.3-0.5KB each, themes are around 1KB. -
    • -
    • - Blazing fast - Supports parallelism with Web Workers, if available. -
    • -
    • - Extensible - Define new languages or extend existing ones. - Add new features thanks to Prism’s plugin architecture. -
    • -
    • - Easy styling - All styling is done through CSS, with sensible class names like .comment, .string, .property etc -
    • -
    - -
    - -
    -

    Used By

    - -

    Prism is used on several websites, small and large. Some of them are:

    - -
    - Smashing Magazine - A List Apart - Mozilla Developer Network (MDN) - CSS-Tricks - SitePoint - Drupal - React - Stripe -
    -
    - -
    -

    Examples

    - -

    The Prism source, highlighted with Prism (don’t you just love how meta this is?):

    -
    
    -
    -	

    This page’s CSS code, highlighted with Prism:

    -
    
    -
    -	

    This page’s HTML, highlighted with Prism:

    -
    
    -
    -	

    This page’s logo (SVG), highlighted with Prism:

    -
    
    -
    -	

    If you’re still not sold, you can view more examples or try it out for yourself.

    -
    - -
    -

    Full list of features

    -
      -
    • Only 2KB minified & gzipped (core). Each language definition adds roughly 300-500 bytes.
    • -
    • Encourages good author practices. Other highlighters encourage or even force you to use elements that are semantically wrong, - like <pre> (on its own) or <script>. - Prism forces you to use the correct element for marking up code: <code>. - On its own for inline code, or inside a <pre> for blocks of code. - In addition, the language is defined through the way recommended in the HTML5 draft: through a language-xxxx class.
    • -
    • The language definition is inherited. This means that if multiple code snippets have the same language, you can just define it once, in one of their common ancestors.
    • -
    • Supports parallelism with Web Workers, if available. Disabled by default (why?).
    • -
    • Very easy to extend without modifying the code, due to Prism’s plugin architecture. Multiple hooks are scattered throughout the source.
    • -
    • Very easy to define new languages. Only thing you need is a good understanding of regular expressions
    • -
    • All styling is done through CSS, with sensible class names rather than ugly namespaced abbreviated nonsense.
    • -
    • Wide browser support: IE9+, Firefox, Chrome, Safari, Opera, most Mobile browsers
    • -
    • Highlights embedded languages (e.g. CSS inside HTML, JavaScript inside HTML)
    • -
    • Highlights inline code as well, not just code blocks
    • -
    • Highlights nested languages (CSS in HTML, JavaScript in HTML)
    • -
    • It doesn’t force you to use any Prism-specific markup, not even a Prism-specific class name, only standard markup you should be using anyway. So, you can just try it for a while, remove it if you don’t like it and leave no traces behind.
    • -
    • Highlight specific lines and/or line ranges (requires plugin)
    • -
    • Show invisible characters like tabs, line breaks etc (requires plugin)
    • -
    • Autolink URLs and emails, use Markdown links in comments (requires plugin)
    • -
    -
    - -
    -

    Limitations

    -
      -
    • Any pre-existing HTML in the code will be stripped off. There are ways around it though.
    • -
    • Regex-based so it *will* fail on certain edge cases, which are documented in the Examples section.
    • -
    • No IE 6-8 support. If someone can read code, they are probably in the 85% of the population with a modern browser.
    • -
    -
    - -
    -

    Basic usage

    - -

    You will need to include the prism.css and prism.js files you downloaded in your page. Example: -

    <!DOCTYPE html>
    -<html>
    -<head>
    -	...
    -	<link href="http://github.com/themes/prism.css" rel="stylesheet" />
    -</head>
    -<body>
    -	...
    -	<script src="http://github.com/prism.js"></script>
    -</body>
    -</html>
    - -

    Prism does its best to encourage good authoring practices. Therefore, it only works with <code> elements, since marking up code without a <code> element is semantically invalid. - According to the HTML5 spec, the recommended way to define a code language is a language-xxxx class, which is what Prism uses. - Alternatively, Prism also supports a shorter version: lang-xxxx.

    -

    To make things easier however, Prism assumes that this language definition is inherited. Therefore, if multiple <code> elements have the same language, you can add the language-xxxx class on one of their common ancestors. - This way, you can also define a document-wide default language, by adding a language-xxxx class on the <body> or <html> element.

    - -

    If you want to opt-out of highlighting for a <code> element that is a descendant of an element with a declared code language, you can add the class language-none to it (or any non-existing language, really).

    - -

    The recommended way to mark up a code block - (both for semantics and for Prism) is a <pre> element with a <code> element inside, like so:

    -
    <pre><code class="language-css">p { color: red }</code></pre>
    -

    If you use that pattern, the <pre> will automatically get the language-xxxx class (if it doesn’t already have it) and will be styled as a code block.

    - -

    If you want to prevent any elements from being automatically highlighted, you can use the attribute data-manual on the <script> element you used for prism and use the API. - Example:

    -
    <script src="http://github.com/prism.js" data-manual></script>
    - -

    Usage with Webpack, Browserify, & Other Bundlers

    - -

    If you want to use Prism with a bundler, install Prism with npm:

    - -
    $ npm install prismjs
    - -

    You can then import into your bundle:

    - -
    import Prism from 'prismjs';
    - -

    To make it easy to configure your Prism instance with only the languages and plugins you need, use the babel plugin, - babel-plugin-prismjs. This will allow you to load - the minimum number of languages and plugins to satisfy your needs. - See that plugin's documentation for configuration details.

    - -

    Usage with Node

    - -

    If you want to use Prism on the server or through the command line, Prism can be used with Node.js as well. - This might be useful if you're trying to generate static HTML pages with highlighted code for environments that don't support browser-side JS, like AMP pages.

    - -

    Example:

    -
    var Prism = require('prismjs');
    -
    -// The code snippet you want to highlight, as a string
    -var code = "var data = 1;";
    -
    -// Returns a highlighted HTML string
    -var html = Prism.highlight(code, Prism.languages.javascript, 'javascript');
    - -

    Requiring prismjs will load the default languages: markup, css, - clike and javascript. You can load more languages with the - loadLanguages() utility, which will automatically handle any required dependencies.

    -

    Example:

    - -
    var Prism = require('prismjs');
    -var loadLanguages = require('prismjs/components/');
    -loadLanguages(['haml']);
    -
    -// The code snippet you want to highlight, as a string
    -var code = "= ['hi', 'there', 'reader!'].join \" \"";
    -
    -// Returns a highlighted HTML string
    -var html = Prism.highlight(code, Prism.languages.haml, 'haml');
    - -

    Note: Do not use loadLanguages() with Webpack or another bundler, as this will cause Webpack to include all languages and plugins. Use the babel plugin described above.

    - -
    - -
    -

    Supported languages

    -

    This is the list of all languages currently supported by Prism, with - their corresponding alias, to use in place of xxxx in the language-xxxx (or lang-xxxx) class:

    -
    - -
    -

    Plugins

    -

    Plugins are additional scripts (and CSS code) that extend Prism’s functionality. Many of the following plugins are official, but are released as plugins to keep the Prism Core small for those who don’t need the extra functionality.

    -
      - -

      No assembly required to use them. Just select them in the download page.

      -

      It’s very easy to write your own Prism plugins. Did you write a plugin for Prism that you want added to this list? Send a pull request!

      -
      - -
      -

      Third-party language definitions

      - - -
      - -
      -

      Third-party tutorials

      - -

      Several tutorials have been written by members of the community to help you integrate Prism into multiple different website types and configurations:

      - - - -

      Please note that the tutorials listed here are not verified to contain correct information. Read at your risk and always check the official documentation here if something doesn’t work :)

      - -

      Have you written a tutorial about Prism that’s not already included here? Send a pull request!

      -
      - -
      -

      Credits

      - -
      - -
      - - - - - - - - - diff --git a/docs/_style/prism-master/logo.svg b/docs/_style/prism-master/logo.svg deleted file mode 100644 index 22fcdec9..00000000 --- a/docs/_style/prism-master/logo.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/_style/prism-master/package.json b/docs/_style/prism-master/package.json deleted file mode 100644 index cbffb02a..00000000 --- a/docs/_style/prism-master/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "prismjs", - "version": "1.15.0", - "description": "Lightweight, robust, elegant syntax highlighting. A spin-off project from Dabblet.", - "main": "prism.js", - "style": "themes/prism.css", - "scripts": { - "test": "mocha tests/testrunner-tests.js && mocha tests/run.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/LeaVerou/prism.git" - }, - "keywords": [ - "prism", - "highlight" - ], - "author": "Lea Verou", - "license": "MIT", - "readmeFilename": "README.md", - "optionalDependencies": { - "clipboard": "^2.0.0" - }, - "devDependencies": { - "chai": "^2.3.0", - "gulp": "^3.8.6", - "gulp-concat": "^2.3.4", - "gulp-header": "^1.0.5", - "gulp-rename": "^1.2.0", - "gulp-uglify": "^0.3.1", - "gulp-replace": "^0.5.4", - "mocha": "^2.2.5", - "yargs": "^3.26.0" - }, - "jspm": { - "main": "prism", - "registry": "jspm", - "jspmPackage": true, - "format": "global", - "files": [ - "components/**/*.js", - "plugins/**/*", - "themes/*.css", - "prism.js" - ] - } -} diff --git a/docs/_style/prism-master/plugins/autolinker/index.html b/docs/_style/prism-master/plugins/autolinker/index.html deleted file mode 100644 index 6053b62c..00000000 --- a/docs/_style/prism-master/plugins/autolinker/index.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -Autolinker ▲ Prism plugins - - - - - - - - - - - -
      -
      - -

      Autolinker

      -

      Converts URLs and emails in code to clickable links. Parses Markdown links in comments.

      -
      - -
      -

      How to use

      -

      URLs and emails will be linked automatically, you don’t need to do anything. To link some text inside a comment to a certain URL, - you may use the Markdown syntax: -

      [Text you want to see](http://url-goes-here.com)
      -
      - -
      -

      Examples

      - -

      JavaScript

      -
      /**
      - * Prism: Lightweight, robust, elegant syntax highlighting
      - * MIT license http://www.opensource.org/licenses/mit-license.php/
      - * @author Lea Verou http://lea.verou.me
      - * Reach Lea at fake@email.com (no, not really)
      - * And this is [a Markdown link](http://prismjs.com). Sweet, huh?
      - */
      -var foo = 5;
      -// And a single line comment http://google.com
      - -

      CSS

      -
      @font-face {
      -	src: url(http://lea.verou.me/logo.otf);
      -	font-family: 'LeaVerou';
      -}
      - -

      HTML

      -
      <!-- Links in HTML, woo!
      -Lea Verou http://lea.verou.me or, with Markdown, [Lea Verou](http://lea.verou.me) -->
      -<img src="http://prismjs.com/img/spectrum.png" alt="In attributes too!" />
      -<p>Autolinking in raw text: http://prismjs.com</p>
      -
      - -
      - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.css b/docs/_style/prism-master/plugins/autolinker/prism-autolinker.css deleted file mode 100644 index b5f76309..00000000 --- a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.css +++ /dev/null @@ -1,3 +0,0 @@ -.token a { - color: inherit; -} \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.js b/docs/_style/prism-master/plugins/autolinker/prism-autolinker.js deleted file mode 100644 index 3913c98a..00000000 --- a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.js +++ /dev/null @@ -1,81 +0,0 @@ -(function(){ - -if ( - typeof self !== 'undefined' && !self.Prism || - typeof global !== 'undefined' && !global.Prism -) { - return; -} - -var url = /\b([a-z]{3,7}:\/\/|tel:)[\w\-+%~/.:=&]+(?:\?[\w\-+%~/.:#=?&!$'()*,;]*)?(?:#[\w\-+%~/.:#=?&!$'()*,;]*)?/, - email = /\b\S+@[\w.]+[a-z]{2}/, - linkMd = /\[([^\]]+)]\(([^)]+)\)/, - - // Tokens that may contain URLs and emails - candidates = ['comment', 'url', 'attr-value', 'string']; - -Prism.plugins.autolinker = { - processGrammar: function (grammar) { - // Abort if grammar has already been processed - if (!grammar || grammar['url-link']) { - return; - } - Prism.languages.DFS(grammar, function (key, def, type) { - if (candidates.indexOf(type) > -1 && Prism.util.type(def) !== 'Array') { - if (!def.pattern) { - def = this[key] = { - pattern: def - }; - } - - def.inside = def.inside || {}; - - if (type == 'comment') { - def.inside['md-link'] = linkMd; - } - if (type == 'attr-value') { - Prism.languages.insertBefore('inside', 'punctuation', { 'url-link': url }, def); - } - else { - def.inside['url-link'] = url; - } - - def.inside['email-link'] = email; - } - }); - grammar['url-link'] = url; - grammar['email-link'] = email; - } -}; - -Prism.hooks.add('before-highlight', function(env) { - Prism.plugins.autolinker.processGrammar(env.grammar); -}); - -Prism.hooks.add('wrap', function(env) { - if (/-link$/.test(env.type)) { - env.tag = 'a'; - - var href = env.content; - - if (env.type == 'email-link' && href.indexOf('mailto:') != 0) { - href = 'mailto:' + href; - } - else if (env.type == 'md-link') { - // Markdown - var match = env.content.match(linkMd); - - href = match[2]; - env.content = match[1]; - } - - env.attributes.href = href; - } - - // Silently catch any error thrown by decodeURIComponent (#1186) - try { - env.content = decodeURIComponent(env.content); - } catch(e) {} -}); - -})(); diff --git a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.min.js b/docs/_style/prism-master/plugins/autolinker/prism-autolinker.min.js deleted file mode 100644 index 9fdced95..00000000 --- a/docs/_style/prism-master/plugins/autolinker/prism-autolinker.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if(("undefined"==typeof self||self.Prism)&&("undefined"==typeof global||global.Prism)){var i=/\b([a-z]{3,7}:\/\/|tel:)[\w\-+%~\/.:=&]+(?:\?[\w\-+%~\/.:#=?&!$'()*,;]*)?(?:#[\w\-+%~\/.:#=?&!$'()*,;]*)?/,n=/\b\S+@[\w.]+[a-z]{2}/,e=/\[([^\]]+)]\(([^)]+)\)/,t=["comment","url","attr-value","string"];Prism.plugins.autolinker={processGrammar:function(r){r&&!r["url-link"]&&(Prism.languages.DFS(r,function(r,a,l){t.indexOf(l)>-1&&"Array"!==Prism.util.type(a)&&(a.pattern||(a=this[r]={pattern:a}),a.inside=a.inside||{},"comment"==l&&(a.inside["md-link"]=e),"attr-value"==l?Prism.languages.insertBefore("inside","punctuation",{"url-link":i},a):a.inside["url-link"]=i,a.inside["email-link"]=n)}),r["url-link"]=i,r["email-link"]=n)}},Prism.hooks.add("before-highlight",function(i){Prism.plugins.autolinker.processGrammar(i.grammar)}),Prism.hooks.add("wrap",function(i){if(/-link$/.test(i.type)){i.tag="a";var n=i.content;if("email-link"==i.type&&0!=n.indexOf("mailto:"))n="mailto:"+n;else if("md-link"==i.type){var t=i.content.match(e);n=t[2],i.content=t[1]}i.attributes.href=n}try{i.content=decodeURIComponent(i.content)}catch(r){}})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/autoloader/index.html b/docs/_style/prism-master/plugins/autoloader/index.html deleted file mode 100644 index 3e5357e0..00000000 --- a/docs/_style/prism-master/plugins/autoloader/index.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - -Autoloader ▲ Prism plugins - - - - - - - - - - - -
      -
      - -

      Autoloader

      -

      Automatically loads the needed languages to highlight the code blocks.

      -
      - -
      -

      How to use

      - -

      - The plugin will automatically handle missing grammars and load them for you. - To do this, you need to provide it with a directory of all the grammars you want. -

      -

      - You can download all the available grammars by clicking on the following link: .
      - Alternatively, you can also clone the GitHub repo and take the components folder from there. -

      -

      - You can then download Prism core and any plugins from the Download page, without checking any languages (or just check the languages you want to load by default, e.g. if you're using a language a lot, then you probably want to save the extra HTTP request). -

      -

      - A couple of additional options are available through the configuration object Prism.plugins.autoloader. -

      - -

      Specifying the grammars path

      - -

      - By default, the plugin will look for the missing grammars in the components folder. - If your files are in a different location, you can specify it using the languages_path option: -

      - -
      Prism.plugins.autoloader.languages_path = 'path/to/grammars/';
      - -

      Using development versions

      - -

      - By default, the plugin uses the minified versions of the grammars. - If you wish to use the development versions instead, you can set the use_minified option to false: -

      - -
      Prism.plugins.autoloader.use_minified = false;
      - -

      Specifying additional dependencies

      - -

      - All default dependencies are already included in the plugin. - However, there are some cases where you might want to load an additional dependency for a specific code block. - To do so, just add a data-dependencies attribute on you <code> or <pre> tags, - containing a list of comma-separated language aliases. -

      - -
      <pre><code class="language-pug" data-dependencies="less">
      -:less
      -	foo {
      -		color: @red;
      -	}
      -</code><pre>
      - -

      Force to reload a grammar

      - -

      - The plugin usually doesn't reload a grammar if it already exists. - In some very specific cases, you might however want to do so. - If you add an exclamation mark after an alias in the data-dependencies attribute, - this language will be reloaded. -

      - -
      <pre class="language-markup" data-dependencies="markup,css!"><code>
      - -
      - -
      -

      Examples

      - -

      Note that no languages are loaded on this page by default.

      - -

      Basic usage with some Perl code:

      -
      my ($class, $filename) = @_;
      - -

      The Less filter used in Pug:

      -
      :less
      -	foo {
      -		color: @red;
      -	}
      - -
      - -
      - - - - - - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/autoloader/prism-autoloader.js b/docs/_style/prism-master/plugins/autoloader/prism-autoloader.js deleted file mode 100644 index 4d6a6014..00000000 --- a/docs/_style/prism-master/plugins/autoloader/prism-autoloader.js +++ /dev/null @@ -1,209 +0,0 @@ -(function () { - if (typeof self === 'undefined' || !self.Prism || !self.document || !document.createElement) { - return; - } - - // The dependencies map is built automatically with gulp - var lang_dependencies = /*languages_placeholder[*/{"javascript":"clike","actionscript":"javascript","arduino":"cpp","aspnet":["markup","csharp"],"bison":"c","c":"clike","csharp":"clike","cpp":"c","coffeescript":"javascript","crystal":"ruby","css-extras":"css","d":"clike","dart":"clike","django":"markup","erb":["ruby","markup-templating"],"fsharp":"clike","flow":"javascript","glsl":"clike","gml":"clike","go":"clike","groovy":"clike","haml":"ruby","handlebars":"markup-templating","haxe":"clike","java":"clike","jolie":"clike","kotlin":"clike","less":"css","markdown":"markup","markup-templating":"markup","n4js":"javascript","nginx":"clike","objectivec":"c","opencl":"cpp","parser":"markup","php":["clike","markup-templating"],"php-extras":"php","plsql":"sql","processing":"clike","protobuf":"clike","pug":"javascript","qore":"clike","jsx":["markup","javascript"],"tsx":["jsx","typescript"],"reason":"clike","ruby":"clike","sass":"css","scss":"css","scala":"java","smarty":"markup-templating","soy":"markup-templating","swift":"clike","tap":"yaml","textile":"markup","tt2":["clike","markup-templating"],"twig":"markup","typescript":"javascript","vala":"clike","vbnet":"basic","velocity":"markup","wiki":"markup","xeora":"markup","xquery":"markup"}/*]*/; - - var lang_data = {}; - - var ignored_language = 'none'; - - var script = document.getElementsByTagName('script'); - script = script[script.length - 1]; - var languages_path = 'components/'; - if(script.hasAttribute('data-autoloader-path')) { - var path = script.getAttribute('data-autoloader-path').trim(); - if(path.length > 0 && !/^[a-z]+:\/\//i.test(script.src)) { - languages_path = path.replace(/\/?$/, '/'); - } - } else if (/[\w-]+\.js$/.test(script.src)) { - languages_path = script.src.replace(/[\w-]+\.js$/, 'components/'); - } - var config = Prism.plugins.autoloader = { - languages_path: languages_path, - use_minified: true - }; - - /** - * Lazy loads an external script - * @param {string} src - * @param {function=} success - * @param {function=} error - */ - var addScript = function (src, success, error) { - var s = document.createElement('script'); - s.src = src; - s.async = true; - s.onload = function() { - document.body.removeChild(s); - success && success(); - }; - s.onerror = function() { - document.body.removeChild(s); - error && error(); - }; - document.body.appendChild(s); - }; - - /** - * Returns the path to a grammar, using the language_path and use_minified config keys. - * @param {string} lang - * @returns {string} - */ - var getLanguagePath = function (lang) { - return config.languages_path + - 'prism-' + lang - + (config.use_minified ? '.min' : '') + '.js' - }; - - /** - * Tries to load a grammar and - * highlight again the given element once loaded. - * @param {string} lang - * @param {HTMLElement} elt - */ - var registerElement = function (lang, elt) { - var data = lang_data[lang]; - if (!data) { - data = lang_data[lang] = {}; - } - - // Look for additional dependencies defined on the or
       tags
      -		var deps = elt.getAttribute('data-dependencies');
      -		if (!deps && elt.parentNode && elt.parentNode.tagName.toLowerCase() === 'pre') {
      -			deps = elt.parentNode.getAttribute('data-dependencies');
      -		}
      -
      -		if (deps) {
      -			deps = deps.split(/\s*,\s*/g);
      -		} else {
      -			deps = [];
      -		}
      -
      -		loadLanguages(deps, function () {
      -			loadLanguage(lang, function () {
      -				Prism.highlightElement(elt);
      -			});
      -		});
      -	};
      -
      -	/**
      -	 * Sequentially loads an array of grammars.
      -	 * @param {string[]|string} langs
      -	 * @param {function=} success
      -	 * @param {function=} error
      -	 */
      -	var loadLanguages = function (langs, success, error) {
      -		if (typeof langs === 'string') {
      -			langs = [langs];
      -		}
      -		var i = 0;
      -		var l = langs.length;
      -		var f = function () {
      -			if (i < l) {
      -				loadLanguage(langs[i], function () {
      -					i++;
      -					f();
      -				}, function () {
      -					error && error(langs[i]);
      -				});
      -			} else if (i === l) {
      -				success && success(langs);
      -			}
      -		};
      -		f();
      -	};
      -
      -	/**
      -	 * Load a grammar with its dependencies
      -	 * @param {string} lang
      -	 * @param {function=} success
      -	 * @param {function=} error
      -	 */
      -	var loadLanguage = function (lang, success, error) {
      -		var load = function () {
      -			var force = false;
      -			// Do we want to force reload the grammar?
      -			if (lang.indexOf('!') >= 0) {
      -				force = true;
      -				lang = lang.replace('!', '');
      -			}
      -
      -			var data = lang_data[lang];
      -			if (!data) {
      -				data = lang_data[lang] = {};
      -			}
      -			if (success) {
      -				if (!data.success_callbacks) {
      -					data.success_callbacks = [];
      -				}
      -				data.success_callbacks.push(success);
      -			}
      -			if (error) {
      -				if (!data.error_callbacks) {
      -					data.error_callbacks = [];
      -				}
      -				data.error_callbacks.push(error);
      -			}
      -
      -			if (!force && Prism.languages[lang]) {
      -				languageSuccess(lang);
      -			} else if (!force && data.error) {
      -				languageError(lang);
      -			} else if (force || !data.loading) {
      -				data.loading = true;
      -				var src = getLanguagePath(lang);
      -				addScript(src, function () {
      -					data.loading = false;
      -					languageSuccess(lang);
      -
      -				}, function () {
      -					data.loading = false;
      -					data.error = true;
      -					languageError(lang);
      -				});
      -			}
      -		};
      -		var dependencies = lang_dependencies[lang];
      -		if(dependencies && dependencies.length) {
      -			loadLanguages(dependencies, load);
      -		} else {
      -			load();
      -		}
      -	};
      -
      -	/**
      -	 * Runs all success callbacks for this language.
      -	 * @param {string} lang
      -	 */
      -	var languageSuccess = function (lang) {
      -		if (lang_data[lang] && lang_data[lang].success_callbacks && lang_data[lang].success_callbacks.length) {
      -			lang_data[lang].success_callbacks.forEach(function (f) {
      -				f(lang);
      -			});
      -		}
      -	};
      -
      -	/**
      -	 * Runs all error callbacks for this language.
      -	 * @param {string} lang
      -	 */
      -	var languageError = function (lang) {
      -		if (lang_data[lang] && lang_data[lang].error_callbacks && lang_data[lang].error_callbacks.length) {
      -			lang_data[lang].error_callbacks.forEach(function (f) {
      -				f(lang);
      -			});
      -		}
      -	};
      -
      -	Prism.hooks.add('complete', function (env) {
      -		if (env.element && env.language && !env.grammar) {
      -			if (env.language !== ignored_language) {
      -				registerElement(env.language, env.element);
      -			}
      -		}
      -	});
      -
      -}());
      \ No newline at end of file
      diff --git a/docs/_style/prism-master/plugins/autoloader/prism-autoloader.min.js b/docs/_style/prism-master/plugins/autoloader/prism-autoloader.min.js
      deleted file mode 100644
      index 629f1b2b..00000000
      --- a/docs/_style/prism-master/plugins/autoloader/prism-autoloader.min.js
      +++ /dev/null
      @@ -1 +0,0 @@
      -!function(){if("undefined"!=typeof self&&self.Prism&&self.document&&document.createElement){var e={javascript:"clike",actionscript:"javascript",arduino:"cpp",aspnet:["markup","csharp"],bison:"c",c:"clike",csharp:"clike",cpp:"c",coffeescript:"javascript",crystal:"ruby","css-extras":"css",d:"clike",dart:"clike",django:"markup",erb:["ruby","markup-templating"],fsharp:"clike",flow:"javascript",glsl:"clike",gml:"clike",go:"clike",groovy:"clike",haml:"ruby",handlebars:"markup-templating",haxe:"clike",java:"clike",jolie:"clike",kotlin:"clike",less:"css",markdown:"markup","markup-templating":"markup",n4js:"javascript",nginx:"clike",objectivec:"c",opencl:"cpp",parser:"markup",php:["clike","markup-templating"],"php-extras":"php",plsql:"sql",processing:"clike",protobuf:"clike",pug:"javascript",qore:"clike",jsx:["markup","javascript"],tsx:["jsx","typescript"],reason:"clike",ruby:"clike",sass:"css",scss:"css",scala:"java",smarty:"markup-templating",soy:"markup-templating",swift:"clike",tap:"yaml",textile:"markup",tt2:["clike","markup-templating"],twig:"markup",typescript:"javascript",vala:"clike",vbnet:"basic",velocity:"markup",wiki:"markup",xeora:"markup",xquery:"markup"},a={},c="none",t=document.getElementsByTagName("script");t=t[t.length-1];var r="components/";if(t.hasAttribute("data-autoloader-path")){var s=t.getAttribute("data-autoloader-path").trim();s.length>0&&!/^[a-z]+:\/\//i.test(t.src)&&(r=s.replace(/\/?$/,"/"))}else/[\w-]+\.js$/.test(t.src)&&(r=t.src.replace(/[\w-]+\.js$/,"components/"));var i=Prism.plugins.autoloader={languages_path:r,use_minified:!0},n=function(e,a,c){var t=document.createElement("script");t.src=e,t.async=!0,t.onload=function(){document.body.removeChild(t),a&&a()},t.onerror=function(){document.body.removeChild(t),c&&c()},document.body.appendChild(t)},l=function(e){return i.languages_path+"prism-"+e+(i.use_minified?".min":"")+".js"},o=function(e,c){var t=a[e];t||(t=a[e]={});var r=c.getAttribute("data-dependencies");!r&&c.parentNode&&"pre"===c.parentNode.tagName.toLowerCase()&&(r=c.parentNode.getAttribute("data-dependencies")),r=r?r.split(/\s*,\s*/g):[],p(r,function(){u(e,function(){Prism.highlightElement(c)})})},p=function(e,a,c){"string"==typeof e&&(e=[e]);var t=0,r=e.length,s=function(){r>t?u(e[t],function(){t++,s()},function(){c&&c(e[t])}):t===r&&a&&a(e)};s()},u=function(c,t,r){var s=function(){var e=!1;c.indexOf("!")>=0&&(e=!0,c=c.replace("!",""));var s=a[c];if(s||(s=a[c]={}),t&&(s.success_callbacks||(s.success_callbacks=[]),s.success_callbacks.push(t)),r&&(s.error_callbacks||(s.error_callbacks=[]),s.error_callbacks.push(r)),!e&&Prism.languages[c])m(c);else if(!e&&s.error)k(c);else if(e||!s.loading){s.loading=!0;var i=l(c);n(i,function(){s.loading=!1,m(c)},function(){s.loading=!1,s.error=!0,k(c)})}},i=e[c];i&&i.length?p(i,s):s()},m=function(e){a[e]&&a[e].success_callbacks&&a[e].success_callbacks.length&&a[e].success_callbacks.forEach(function(a){a(e)})},k=function(e){a[e]&&a[e].error_callbacks&&a[e].error_callbacks.length&&a[e].error_callbacks.forEach(function(a){a(e)})};Prism.hooks.add("complete",function(e){e.element&&e.language&&!e.grammar&&e.language!==c&&o(e.language,e.element)})}}();
      \ No newline at end of file
      diff --git a/docs/_style/prism-master/plugins/command-line/index.html b/docs/_style/prism-master/plugins/command-line/index.html
      deleted file mode 100644
      index 109138b6..00000000
      --- a/docs/_style/prism-master/plugins/command-line/index.html
      +++ /dev/null
      @@ -1,111 +0,0 @@
      -
      -
      -
      -
      -
      -
      -Command Line ▲ Prism plugins
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - -

      Command Line

      -

      Display a command line with a prompt and, optionally, the output/response from the commands.

      -
      - -
      -

      How to use

      - -

      This is intended for code blocks (<pre><code>) and not for inline code.

      - -

      Add class command-line to your <pre>. For a server command line, specify the user and host names using the data-user and data-host attributes. The resulting prompt displays a # for the root user and $ for all other users. For any other command line, such as a Windows prompt, you may specify the entire prompt using the data-prompt attribute.

      - -

      Optional: You may specify the lines to be presented as output (no prompt and no highlighting) through the data-output attribute on the <pre> element in the following simple format:

      -
        -
      • A single number refers to the line with that number
      • -
      • Ranges are denoted by two numbers, separated with a hyphen (-)
      • -
      • Multiple line numbers or ranges are separated by commas.
      • -
      • Whitespace is allowed anywhere and will be stripped off.
      • -
      - -

      Examples:

      -
      -
      5
      -
      The 5th line
      - -
      1-5
      -
      Lines 1 through 5
      - -
      1,4
      -
      Line 1 and line 4
      - -
      1-2, 5, 9-20
      -
      Lines 1 through 2, line 5, lines 9 through 20
      -
      - -

      Optional: To automatically present some lines as output, you can prefix those lines with any string and specify the prefix using the data-filter-output attribute on the <pre> element. For example, data-filter-output="(out)" will treat lines beginning with (out) as output and remove the prefix.

      -
      - -
      -

      Examples

      - -

      Root User Without Output

      -
      cd /usr/local/etc
      -cp php.ini php.ini.bak
      -vi php.ini
      - -

      Non-Root User With Output

      -
      pwd
      -/usr/home/chris/bin
      -ls -la
      -total 2
      -drwxr-xr-x   2 chris  chris     11 Jan 10 16:48 .
      -drwxr--r-x  45 chris  chris     92 Feb 14 11:10 ..
      --rwxr-xr-x   1 chris  chris    444 Aug 25  2013 backup
      --rwxr-xr-x   1 chris  chris    642 Jan 17 14:42 deploy
      - -

      Windows PowerShell With Output

      -
      dir
      -
      -
      -    Directory: C:\Users\Chris
      -
      -
      -Mode                LastWriteTime     Length Name
      -----                -------------     ------ ----
      -d-r--        10/14/2015   5:06 PM            Contacts
      -d-r--        12/12/2015   1:47 PM            Desktop
      -d-r--         11/4/2015   7:59 PM            Documents
      -d-r--        10/14/2015   5:06 PM            Downloads
      -d-r--        10/14/2015   5:06 PM            Favorites
      -d-r--        10/14/2015   5:06 PM            Links
      -d-r--        10/14/2015   5:06 PM            Music
      -d-r--        10/14/2015   5:06 PM            Pictures
      -d-r--        10/14/2015   5:06 PM            Saved Games
      -d-r--        10/14/2015   5:06 PM            Searches
      -d-r--        10/14/2015   5:06 PM            Videos
      - -
      - -
      - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/command-line/prism-command-line.css b/docs/_style/prism-master/plugins/command-line/prism-command-line.css deleted file mode 100644 index 153a8707..00000000 --- a/docs/_style/prism-master/plugins/command-line/prism-command-line.css +++ /dev/null @@ -1,33 +0,0 @@ -.command-line-prompt { - border-right: 1px solid #999; - display: block; - float: left; - font-size: 100%; - letter-spacing: -1px; - margin-right: 1em; - pointer-events: none; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.command-line-prompt > span:before { - color: #999; - content: ' '; - display: block; - padding-right: 0.8em; -} - -.command-line-prompt > span[data-user]:before { - content: "[" attr(data-user) "@" attr(data-host) "] $"; -} - -.command-line-prompt > span[data-user="root"]:before { - content: "[" attr(data-user) "@" attr(data-host) "] #"; -} - -.command-line-prompt > span[data-prompt]:before { - content: attr(data-prompt); -} diff --git a/docs/_style/prism-master/plugins/command-line/prism-command-line.js b/docs/_style/prism-master/plugins/command-line/prism-command-line.js deleted file mode 100644 index 3c779914..00000000 --- a/docs/_style/prism-master/plugins/command-line/prism-command-line.js +++ /dev/null @@ -1,139 +0,0 @@ -(function() { - -if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; -} - -var clsReg = /(?:^|\s)command-line(?:\s|$)/; - -Prism.hooks.add('before-highlight', function (env) { - var vars = env.vars = env.vars || {}; - var commandLine = vars['command-line'] = vars['command-line'] || {}; - - if (commandLine.complete || !env.code) { - commandLine.complete = true; - return; - } - - // Works only for wrapped inside
       (not inline).
      -	var pre = env.element.parentNode;
      -	if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the 
       nor the  have the class
      -		(!clsReg.test(pre.className) && !clsReg.test(env.element.className))) {
      -		commandLine.complete = true;
      -		return;
      -	}
      -
      -	if (env.element.querySelector('.command-line-prompt')) { // Abort if prompt already exists.
      -		commandLine.complete = true;
      -		return;
      -	}
      -
      -	var codeLines = env.code.split('\n');
      -	commandLine.numberOfLines = codeLines.length;
      -	var outputLines = commandLine.outputLines = [];
      -
      -	var outputSections = pre.getAttribute('data-output');
      -	var outputFilter = pre.getAttribute('data-filter-output');
      -	if (outputSections || outputSections === '') { // The user specified the output lines. -- cwells
      -		outputSections = outputSections.split(',');
      -		for (var i = 0; i < outputSections.length; i++) { // Parse the output sections into start/end ranges. -- cwells
      -			var range = outputSections[i].split('-');
      -			var outputStart = parseInt(range[0], 10);
      -			var outputEnd = (range.length === 2 ? parseInt(range[1], 10) : outputStart);
      -
      -			if (!isNaN(outputStart) && !isNaN(outputEnd)) {
      -				if (outputStart < 1) {
      -					outputStart = 1;
      -				}
      -				if (outputEnd > codeLines.length) {
      -					outputEnd = codeLines.length;
      -				}
      -				// Convert start and end to 0-based to simplify the arrays. -- cwells
      -				outputStart--;
      -				outputEnd--;
      -				// Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
      -				for (var j = outputStart; j <= outputEnd; j++) {
      -					outputLines[j] = codeLines[j];
      -					codeLines[j] = '';
      -				}
      -			}
      -		}
      -	} else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
      -		for (var i = 0; i < codeLines.length; i++) {
      -			if (codeLines[i].indexOf(outputFilter) === 0) { // This line is output. -- cwells
      -				outputLines[i] = codeLines[i].slice(outputFilter.length);
      -				codeLines[i] = '';
      -			}
      -		}
      -	}
      -
      -	env.code = codeLines.join('\n');
      -});
      -
      -Prism.hooks.add('before-insert', function (env) {
      -	var vars = env.vars = env.vars || {};
      -	var commandLine = vars['command-line'] = vars['command-line'] || {};
      -	if (commandLine.complete) {
      -		return;
      -	}
      -
      -	// Reinsert the output lines into the highlighted code. -- cwells
      -	var codeLines = env.highlightedCode.split('\n');
      -	for (var i = 0; i < commandLine.outputLines.length; i++) {
      -		if (commandLine.outputLines.hasOwnProperty(i)) {
      -			codeLines[i] = commandLine.outputLines[i];
      -		}
      -	}
      -	env.highlightedCode = codeLines.join('\n');
      -});
      -
      -Prism.hooks.add('complete', function (env) {
      -	var vars = env.vars = env.vars || {};
      -	var commandLine = vars['command-line'] = vars['command-line'] || {};
      -	if (commandLine.complete) {
      -		return;
      -	}
      -
      -	var pre = env.element.parentNode;
      -	if (clsReg.test(env.element.className)) { // Remove the class "command-line" from the 
      -		env.element.className = env.element.className.replace(clsReg, ' ');
      -	}
      -	if (!clsReg.test(pre.className)) { // Add the class "command-line" to the 
      -		pre.className += ' command-line';
      -	}
      -
      -	var getAttribute = function(key, defaultValue) {
      -		return (pre.getAttribute(key) || defaultValue).replace(/"/g, '"');
      -	};
      -
      -	// Create the "rows" that will become the command-line prompts. -- cwells
      -	var promptLines = new Array(commandLine.numberOfLines + 1);
      -	var promptText = getAttribute('data-prompt', '');
      -	if (promptText !== '') {
      -		promptLines = promptLines.join('');
      -	} else {
      -		var user = getAttribute('data-user', 'user');
      -		var host = getAttribute('data-host', 'localhost');
      -		promptLines = promptLines.join('');
      -	}
      -
      -	// Create the wrapper element. -- cwells
      -	var prompt = document.createElement('span');
      -	prompt.className = 'command-line-prompt';
      -	prompt.innerHTML = promptLines;
      -
      -	// Remove the prompt from the output lines. -- cwells
      -	for (var i = 0; i < commandLine.outputLines.length; i++) {
      -		if (commandLine.outputLines.hasOwnProperty(i)) {
      -			var node = prompt.children[i];
      -			node.removeAttribute('data-user');
      -			node.removeAttribute('data-host');
      -			node.removeAttribute('data-prompt');
      -		}
      -	}
      -
      -	env.element.insertBefore(prompt, env.element.firstChild);
      -	commandLine.complete = true;
      -});
      -
      -}());
      diff --git a/docs/_style/prism-master/plugins/command-line/prism-command-line.min.js b/docs/_style/prism-master/plugins/command-line/prism-command-line.min.js
      deleted file mode 100644
      index 44d011ac..00000000
      --- a/docs/_style/prism-master/plugins/command-line/prism-command-line.min.js
      +++ /dev/null
      @@ -1 +0,0 @@
      -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e=/(?:^|\s)command-line(?:\s|$)/;Prism.hooks.add("before-highlight",function(t){var a=t.vars=t.vars||{},n=a["command-line"]=a["command-line"]||{};if(n.complete||!t.code)return n.complete=!0,void 0;var r=t.element.parentNode;if(!r||!/pre/i.test(r.nodeName)||!e.test(r.className)&&!e.test(t.element.className))return n.complete=!0,void 0;if(t.element.querySelector(".command-line-prompt"))return n.complete=!0,void 0;var o=t.code.split("\n");n.numberOfLines=o.length;var s=n.outputLines=[],i=r.getAttribute("data-output"),l=r.getAttribute("data-filter-output");if(i||""===i){i=i.split(",");for(var m=0;mp&&(p=1),c>o.length&&(c=o.length),p--,c--;for(var u=p;c>=u;u++)s[u]=o[u],o[u]=""}}}else if(l)for(var m=0;m');else{var l=o("data-user","user"),m=o("data-host","localhost");s=s.join('')}var d=document.createElement("span");d.className="command-line-prompt",d.innerHTML=s;for(var p=0;p
      -
      -
      -
      -	
      -	
      -	Copy to Clipboard ▲ Prism plugins
      -	
      -	
      -	
      -	
      -	
      -
      -	
      -	
      -
      -
      -
      -
      -
      - -

      Copy to Clipboard

      -

      Add a button that copies the code block to the clipboard when clicked.

      -
      - -
      -

      How to use

      -

      In addition to including the plugin file with your PrismJS build, ensure Clipboard.js is loaded before the plugin.

      - -

      The simplest way to include Clipboard.js is to use any of the - recommended CDNs. If you're using Browserify, Clipboard.js will be loaded auotmatically - if it's included in your package.json. - If you don't load Clipboard.js yourself, the plugin will load it from a CDN for you.

      - -
      
      -
      - -
      - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.js b/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.js deleted file mode 100644 index 0b185f47..00000000 --- a/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.js +++ /dev/null @@ -1,75 +0,0 @@ -(function(){ - if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; - } - - if (!Prism.plugins.toolbar) { - console.warn('Copy to Clipboard plugin loaded before Toolbar plugin.'); - - return; - } - - var ClipboardJS = window.ClipboardJS || undefined; - - if (!ClipboardJS && typeof require === 'function') { - ClipboardJS = require('clipboard'); - } - - var callbacks = []; - - if (!ClipboardJS) { - var script = document.createElement('script'); - var head = document.querySelector('head'); - - script.onload = function() { - ClipboardJS = window.ClipboardJS; - - if (ClipboardJS) { - while (callbacks.length) { - callbacks.pop()(); - } - } - }; - - script.src = 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js'; - head.appendChild(script); - } - - Prism.plugins.toolbar.registerButton('copy-to-clipboard', function (env) { - var linkCopy = document.createElement('a'); - linkCopy.textContent = 'Copy'; - - if (!ClipboardJS) { - callbacks.push(registerClipboard); - } else { - registerClipboard(); - } - - return linkCopy; - - function registerClipboard() { - var clip = new ClipboardJS(linkCopy, { - 'text': function () { - return env.code; - } - }); - - clip.on('success', function() { - linkCopy.textContent = 'Copied!'; - - resetText(); - }); - clip.on('error', function () { - linkCopy.textContent = 'Press Ctrl+C to copy'; - - resetText(); - }); - } - - function resetText() { - setTimeout(function () { - linkCopy.textContent = 'Copy'; - }, 5000); - } - }); -})(); diff --git a/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js b/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js deleted file mode 100644 index aa5742db..00000000 --- a/docs/_style/prism-master/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."),void 0;var o=window.ClipboardJS||void 0;o||"function"!=typeof require||(o=require("clipboard"));var e=[];if(!o){var t=document.createElement("script"),n=document.querySelector("head");t.onload=function(){if(o=window.ClipboardJS)for(;e.length;)e.pop()()},t.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",n.appendChild(t)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){function n(){var e=new o(i,{text:function(){return t.code}});e.on("success",function(){i.textContent="Copied!",r()}),e.on("error",function(){i.textContent="Press Ctrl+C to copy",r()})}function r(){setTimeout(function(){i.textContent="Copy"},5e3)}var i=document.createElement("a");return i.textContent="Copy",o?n():e.push(n),i})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/custom-class/index.html b/docs/_style/prism-master/plugins/custom-class/index.html deleted file mode 100644 index ee4fade7..00000000 --- a/docs/_style/prism-master/plugins/custom-class/index.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - -Custom Class ▲ Prism plugins - - - - - - - - - -
      -
      - -

      Custom Class

      -

      This plugin allows you to prefix Prism default classes (.comment will become .namespace--comment) or replace them with your defined ones (like .editor__comment or .comment_7sh3a).

      -
      - -
      -

      Motivation

      - -

      Prism default classes are sensible but fixed and too generic. This plugin provide some ways to customize those classes to suit your needs. Example usages:

      - -
        -
      • - You want to add namespace for all of them (like .prism--comment) to avoid conflict with your existing classes. -
      • -
      • - You use a naming convention (like BEM). You want to write classes like .editor__comment. -
      • -
      • You use - CSS Modules. You want to use your hashed classes, like .comment_7sh3a. -
      • -
      - -

      Features

      - -

      This plugin currently provides 2 features:

      - -

      1. Prefix all Prism classes with a string

      - - Prism.plugins.customClass.prefix('prism--') - -

      2. Replace some Prism classes with your defined ones via an object

      - -
      Prism.plugins.customClass.map({
      -	keyword: 'special-keyword',
      -	string: 'string_ch29s',
      -	comment: 'comment_93jsa'
      -})
      - -

      Object's keys are the tokens you want to replace (eg: comment), with their values being the classes you want to use (eg: my-comment). Tokens which are not specified will stay the same.

      - -

      Notes

      - -
        -
      • -

        Feature functions must be called AFTER Prism and this plugin. For example:

        - -
        <!-- 1. load prism -->
        -<script src="prism.js"></script>
        -<!-- 2. load the plugin if you don't include it inside prism when download -->
        -<script src="plugins/custom-class/custom-class.js"></script>
        -<!-- 3. call the feature you want to use -->
        -<script>
        -	Prism.plugins.customClass.map(myClassMap);
        -	Prism.plugins.customClass.prefix(myPrefixString);
        -</script>
        - -
      • - -
      • In most cases, using 1 feature is enough. However, it is possible to use both of them together if you want (Result will be like .my-namespace--comment_93jsa).
      • - -
      - -

      CSS Modules Usage:

      - -

      The initial purpose of this plugin is to be used with CSS Modules. It works perfectly with the class map object returned by CSS Modules. For example:

      - -
      import Prism from 'prismjs';
      -import classMap from 'styles/editor-class-map.css';
      -Prism.plugins.customClass.map(classMap)
      - -
      - -
      -

      Example

      - -

      Input

      -
      <pre class="language-javascript"><code>
      -	var foo = 'bar';
      -</code></pre>
      - -

      Options

      -
      Prism.plugins.customClass.map({
      -	keyword: 'special-keyword',
      -	string: 'my-string'
      -});
      -Prism.plugins.customClass.prefix('pr-');
      - -

      Output

      -
      <pre class="language-javascript"><code>
      -	<span class="pr-token pr-special-keyword">var</span>
      -	foo
      -	<span class="pr-token pr-operator">=</span>
      -	<span class="pr-my-string">'bar'</span>
      -	<span class="pr-token pr-punctuation">;</span>
      -</code></pre>
      -
      - -
      -

      Todo

      - -
      - -
      - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/custom-class/prism-custom-class.js b/docs/_style/prism-master/plugins/custom-class/prism-custom-class.js deleted file mode 100644 index bb5bd6a6..00000000 --- a/docs/_style/prism-master/plugins/custom-class/prism-custom-class.js +++ /dev/null @@ -1,31 +0,0 @@ -(function(){ - -if ( - (typeof self === 'undefined' || !self.Prism) && - (typeof global === 'undefined' || !global.Prism) -) { - return; -} - -var options = { - classMap: {} -}; -Prism.plugins.customClass = { - map: function map(cm) { - options.classMap = cm; - }, - prefix: function prefix(string) { - options.prefixString = string; - } -} - -Prism.hooks.add('wrap', function (env) { - if (!options.classMap && !options.prefixString) { - return; - } - env.classes = env.classes.map(function(c) { - return (options.prefixString || '') + (options.classMap[c] || c); - }); -}); - -})(); diff --git a/docs/_style/prism-master/plugins/custom-class/prism-custom-class.min.js b/docs/_style/prism-master/plugins/custom-class/prism-custom-class.min.js deleted file mode 100644 index f22063f5..00000000 --- a/docs/_style/prism-master/plugins/custom-class/prism-custom-class.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if("undefined"!=typeof self&&self.Prism||"undefined"!=typeof global&&global.Prism){var s={classMap:{}};Prism.plugins.customClass={map:function(i){s.classMap=i},prefix:function(i){s.prefixString=i}},Prism.hooks.add("wrap",function(i){(s.classMap||s.prefixString)&&(i.classes=i.classes.map(function(i){return(s.prefixString||"")+(s.classMap[i]||i)}))})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/data-uri-highlight/index.html b/docs/_style/prism-master/plugins/data-uri-highlight/index.html deleted file mode 100644 index 380be2d6..00000000 --- a/docs/_style/prism-master/plugins/data-uri-highlight/index.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - -Data-URI Highlight ▲ Prism plugins - - - - - - - - - - - -
      -
      - -

      Data-URI Highlight

      -

      Highlights data-URI contents.

      -
      - -
      -

      How to use

      -

      Data-URIs will be highlighted automatically, provided the needed grammar is loaded. - The grammar to use is guessed using the MIME type information.

      -
      - -
      -

      Example

      - -
      div {
      -    border: 40px solid transparent;
      -    border-image: 33.334% url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30"> \
      -                          <circle cx="5" cy="5" r="5" fill="%23ab4"/><circle cx="15" cy="5" r="5" fill="%23655"/> \
      -                          <circle cx="25" cy="5" r="5" fill="%23e07"/><circle cx="5" cy="15" r="5" fill="%23655"/> \
      -                          <circle cx="15" cy="15" r="5" fill="hsl(15, 25%, 75%)"/> \
      -                          <circle cx="25" cy="15" r="5" fill="%23655"/><circle cx="5" cy="25" r="5" fill="%23fb3"/> \
      -                          <circle cx="15" cy="25" r="5" fill="%23655"/><circle cx="25" cy="25" r="5" fill="%2358a"/></svg>');
      -    padding: 1em;
      -    max-width: 20em;
      -    font: 130%/1.6 Baskerville, Palatino, serif;
      -}
      - -
      - -
      - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.js b/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.js deleted file mode 100644 index 7ff8d1fd..00000000 --- a/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.js +++ /dev/null @@ -1,98 +0,0 @@ -(function () { - - if ( - typeof self !== 'undefined' && !self.Prism || - typeof global !== 'undefined' && !global.Prism - ) { - return; - } - - var autoLinkerProcess = function (grammar) { - if (Prism.plugins.autolinker) { - Prism.plugins.autolinker.processGrammar(grammar); - } - return grammar; - }; - var dataURI = { - pattern: /(.)\bdata:[^\/]+\/[^,]+,(?:(?!\1)[\s\S]|\\\1)+(?=\1)/, - lookbehind: true, - inside: { - 'language-css': { - pattern: /(data:[^\/]+\/(?:[^+,]+\+)?css,)[\s\S]+/, - lookbehind: true - }, - 'language-javascript': { - pattern: /(data:[^\/]+\/(?:[^+,]+\+)?javascript,)[\s\S]+/, - lookbehind: true - }, - 'language-json': { - pattern: /(data:[^\/]+\/(?:[^+,]+\+)?json,)[\s\S]+/, - lookbehind: true - }, - 'language-markup': { - pattern: /(data:[^\/]+\/(?:[^+,]+\+)?(?:html|xml),)[\s\S]+/, - lookbehind: true - } - } - }; - - // Tokens that may contain URLs - var candidates = ['url', 'attr-value', 'string']; - - Prism.plugins.dataURIHighlight = { - processGrammar: function (grammar) { - // Abort if grammar has already been processed - if (!grammar || grammar['data-uri']) { - return; - } - - Prism.languages.DFS(grammar, function (key, def, type) { - if (candidates.indexOf(type) > -1 && Prism.util.type(def) !== 'Array') { - if (!def.pattern) { - def = this[key] = { - pattern: def - }; - } - - def.inside = def.inside || {}; - - if (type == 'attr-value') { - Prism.languages.insertBefore('inside', def.inside['url-link'] ? 'url-link' : 'punctuation', { - 'data-uri': dataURI - }, def); - } - else { - if (def.inside['url-link']) { - Prism.languages.insertBefore('inside', 'url-link', { - 'data-uri': dataURI - }, def); - } else { - def.inside['data-uri'] = dataURI; - } - } - } - }); - grammar['data-uri'] = dataURI; - } - }; - - Prism.hooks.add('before-highlight', function (env) { - // Prepare the needed grammars for this code block - if (dataURI.pattern.test(env.code)) { - for (var p in dataURI.inside) { - if (dataURI.inside.hasOwnProperty(p)) { - if (!dataURI.inside[p].inside && dataURI.inside[p].pattern.test(env.code)) { - var lang = p.match(/^language-(.+)/)[1]; - if (Prism.languages[lang]) { - dataURI.inside[p].inside = { - rest: autoLinkerProcess(Prism.languages[lang]) - }; - } - } - } - } - } - - Prism.plugins.dataURIHighlight.processGrammar(env.grammar); - }); -}()); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.min.js b/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.min.js deleted file mode 100644 index 479828d9..00000000 --- a/docs/_style/prism-master/plugins/data-uri-highlight/prism-data-uri-highlight.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if(("undefined"==typeof self||self.Prism)&&("undefined"==typeof global||global.Prism)){var i=function(i){return Prism.plugins.autolinker&&Prism.plugins.autolinker.processGrammar(i),i},a={pattern:/(.)\bdata:[^\/]+\/[^,]+,(?:(?!\1)[\s\S]|\\\1)+(?=\1)/,lookbehind:!0,inside:{"language-css":{pattern:/(data:[^\/]+\/(?:[^+,]+\+)?css,)[\s\S]+/,lookbehind:!0},"language-javascript":{pattern:/(data:[^\/]+\/(?:[^+,]+\+)?javascript,)[\s\S]+/,lookbehind:!0},"language-json":{pattern:/(data:[^\/]+\/(?:[^+,]+\+)?json,)[\s\S]+/,lookbehind:!0},"language-markup":{pattern:/(data:[^\/]+\/(?:[^+,]+\+)?(?:html|xml),)[\s\S]+/,lookbehind:!0}}},n=["url","attr-value","string"];Prism.plugins.dataURIHighlight={processGrammar:function(i){i&&!i["data-uri"]&&(Prism.languages.DFS(i,function(i,e,r){n.indexOf(r)>-1&&"Array"!==Prism.util.type(e)&&(e.pattern||(e=this[i]={pattern:e}),e.inside=e.inside||{},"attr-value"==r?Prism.languages.insertBefore("inside",e.inside["url-link"]?"url-link":"punctuation",{"data-uri":a},e):e.inside["url-link"]?Prism.languages.insertBefore("inside","url-link",{"data-uri":a},e):e.inside["data-uri"]=a)}),i["data-uri"]=a)}},Prism.hooks.add("before-highlight",function(n){if(a.pattern.test(n.code))for(var e in a.inside)if(a.inside.hasOwnProperty(e)&&!a.inside[e].inside&&a.inside[e].pattern.test(n.code)){var r=e.match(/^language-(.+)/)[1];Prism.languages[r]&&(a.inside[e].inside={rest:i(Prism.languages[r])})}Prism.plugins.dataURIHighlight.processGrammar(n.grammar)})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/file-highlight/index.html b/docs/_style/prism-master/plugins/file-highlight/index.html deleted file mode 100644 index e66b9897..00000000 --- a/docs/_style/prism-master/plugins/file-highlight/index.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - -File Highlight ▲ Prism plugins - - - - - - - - - - - -
      -
      - -

      File Highlight

      -

      Fetch external files and highlight them with Prism. Used on the Prism website itself.

      -
      - -
      -

      How to use

      - -

      Use the data-src attribute on empty <pre> elements, like so:

      - -
      <pre data-src="http://github.com/myfile.js"></pre>
      - -

      You don’t need to specify the language, it’s automatically determined by the file extension. - If, however, the language cannot be determined from the file extension or the file extension is incorrect, you may specify a language as well (with the usual class name way).

      - -

      Please note that the files are fetched with XMLHttpRequest. This means that if the file is on a different origin, fetching it will fail, unless CORS is enabled on that website.

      - -

      - When used in conjunction with the Toolbar plugin, this plugin can also display a button to download the file. - To use it, add a data-download-link attribute on the <pre> element.
      - Optionally, the text can also be customized by using a data-download-link-label attribute. -

      -
      <pre data-src="http://github.com/myfile.js" data-download-link data-download-link-label="Download this file"></pre>
      -
      - -
      -

      Examples

      - -

      The plugin’s JS code:

      -
      
      -	
      -	

      This page:

      -
      
      -
      -	

      File that doesn’t exist:

      -
      
      -
      -	

      With a download button:

      -
      
      -
      -	

      For more examples, browse around the Prism website. Most large code samples are actually files fetched with this plugin.

      -
      - -
      - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.js b/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.js deleted file mode 100644 index 65efaf9f..00000000 --- a/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.js +++ /dev/null @@ -1,105 +0,0 @@ -(function () { - if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) { - return; - } - - /** - * @param {Element} [container=document] - */ - self.Prism.fileHighlight = function(container) { - container = container || document; - - var Extensions = { - 'js': 'javascript', - 'py': 'python', - 'rb': 'ruby', - 'ps1': 'powershell', - 'psm1': 'powershell', - 'sh': 'bash', - 'bat': 'batch', - 'h': 'c', - 'tex': 'latex' - }; - - Array.prototype.slice.call(container.querySelectorAll('pre[data-src]')).forEach(function (pre) { - // ignore if already loaded - if (pre.hasAttribute('data-src-loaded')) { - return; - } - - // load current - var src = pre.getAttribute('data-src'); - - var language, parent = pre; - var lang = /\blang(?:uage)?-([\w-]+)\b/i; - while (parent && !lang.test(parent.className)) { - parent = parent.parentNode; - } - - if (parent) { - language = (pre.className.match(lang) || [, ''])[1]; - } - - if (!language) { - var extension = (src.match(/\.(\w+)$/) || [, ''])[1]; - language = Extensions[extension] || extension; - } - - var code = document.createElement('code'); - code.className = 'language-' + language; - - pre.textContent = ''; - - code.textContent = 'Loading…'; - - pre.appendChild(code); - - var xhr = new XMLHttpRequest(); - - xhr.open('GET', src, true); - - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - - if (xhr.status < 400 && xhr.responseText) { - code.textContent = xhr.responseText; - - Prism.highlightElement(code); - // mark as loaded - pre.setAttribute('data-src-loaded', ''); - } - else if (xhr.status >= 400) { - code.textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText; - } - else { - code.textContent = '✖ Error: File does not exist or is empty'; - } - } - }; - - xhr.send(null); - }); - - if (Prism.plugins.toolbar) { - Prism.plugins.toolbar.registerButton('download-file', function (env) { - var pre = env.element.parentNode; - if (!pre || !/pre/i.test(pre.nodeName) || !pre.hasAttribute('data-src') || !pre.hasAttribute('data-download-link')) { - return; - } - var src = pre.getAttribute('data-src'); - var a = document.createElement('a'); - a.textContent = pre.getAttribute('data-download-link-label') || 'Download'; - a.setAttribute('download', ''); - a.href = src; - return a; - }); - } - - }; - - document.addEventListener('DOMContentLoaded', function () { - // execute inside handler, for dropping Event as argumnet - self.Prism.fileHighlight(); - }); - -})(); diff --git a/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.min.js b/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.min.js deleted file mode 100644 index 31869b3c..00000000 --- a/docs/_style/prism-master/plugins/file-highlight/prism-file-highlight.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector&&(self.Prism.fileHighlight=function(t){t=t||document;var e={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"};Array.prototype.slice.call(t.querySelectorAll("pre[data-src]")).forEach(function(t){if(!t.hasAttribute("data-src-loaded")){for(var a,n=t.getAttribute("data-src"),r=t,s=/\blang(?:uage)?-([\w-]+)\b/i;r&&!s.test(r.className);)r=r.parentNode;if(r&&(a=(t.className.match(s)||[,""])[1]),!a){var o=(n.match(/\.(\w+)$/)||[,""])[1];a=e[o]||o}var l=document.createElement("code");l.className="language-"+a,t.textContent="",l.textContent="Loading…",t.appendChild(l);var i=new XMLHttpRequest;i.open("GET",n,!0),i.onreadystatechange=function(){4==i.readyState&&(i.status<400&&i.responseText?(l.textContent=i.responseText,Prism.highlightElement(l),t.setAttribute("data-src-loaded","")):l.textContent=i.status>=400?"✖ Error "+i.status+" while fetching file: "+i.statusText:"✖ Error: File does not exist or is empty")},i.send(null)}}),Prism.plugins.toolbar&&Prism.plugins.toolbar.registerButton("download-file",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-src")&&e.hasAttribute("data-download-link")){var a=e.getAttribute("data-src"),n=document.createElement("a");return n.textContent=e.getAttribute("data-download-link-label")||"Download",n.setAttribute("download",""),n.href=a,n}})},document.addEventListener("DOMContentLoaded",function(){self.Prism.fileHighlight()}))}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/highlight-keywords/index.html b/docs/_style/prism-master/plugins/highlight-keywords/index.html deleted file mode 100644 index 46658734..00000000 --- a/docs/_style/prism-master/plugins/highlight-keywords/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - -Highlight Keywords ▲ Prism plugins - - - - - - - - - - -
      -
      - -

      Highlight Keywords

      -

      This plugin adds special CSS classes for each keyword matched in the code. For example, the keyword if will have the class keyword-if as well. You can have fine grained control over the appearance of each keyword by providing your own CSS rules.

      -
      - -
      -

      Examples

      - -

      JavaScript

      -
      
      -
      -	

      HTML (Markup)

      -
      
      -
      -
      - - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.js b/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.js deleted file mode 100644 index 32b28122..00000000 --- a/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.js +++ /dev/null @@ -1,17 +0,0 @@ -(function(){ - -if ( - typeof self !== 'undefined' && !self.Prism || - typeof global !== 'undefined' && !global.Prism -) { - return; -} - -Prism.hooks.add('wrap', function(env) { - if (env.type !== "keyword") { - return; - } - env.classes.push('keyword-' + env.content); -}); - -})(); diff --git a/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.min.js b/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.min.js deleted file mode 100644 index c4326fea..00000000 --- a/docs/_style/prism-master/plugins/highlight-keywords/prism-highlight-keywords.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"undefined"!=typeof self&&!self.Prism||"undefined"!=typeof global&&!global.Prism||Prism.hooks.add("wrap",function(e){"keyword"===e.type&&e.classes.push("keyword-"+e.content)})}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/index.html b/docs/_style/prism-master/plugins/index.html deleted file mode 100644 index 7b4a48a7..00000000 --- a/docs/_style/prism-master/plugins/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - -Plugins ▲ Prism - - - - - - - - - - - -
      -
      -
      - -
      -

      Available plugins

      -
        -
        - -
        -

        Contribute

        -

        Writing Prism plugins is easy! Read how at the “Extending Prism” section. -

        - -
        - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/jsonp-highlight/index.html b/docs/_style/prism-master/plugins/jsonp-highlight/index.html deleted file mode 100644 index 2ad4ac9a..00000000 --- a/docs/_style/prism-master/plugins/jsonp-highlight/index.html +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - -JSONP Highlight ▲ Prism plugins - - - - - - - - - - -
        -
        - -

        JSONP Highlight

        -

        Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).

        -
        - -
        -

        How to use

        - -

        Use the data-jsonp attribute on <pre> elements, like so:

        - -
        <pre
        -  class="language-javascript"
        -  data-jsonp="https://api.github.com/repos/leaverou/prism/contents/prism.js">
        -</pre>
        - -

        - Don't specifiy the callback query parameter in the URL; this will be added - automatically. If the API expects a different callback parameter name however, use the - data-callback parameter to specify the name: -

        - -
        <pre class="…" data-jsonp="…" data-callback="cb"></pre>
        - -

        - The next trick is of course actually extracting something from the JSONP response worth - highlighting, which means processing the response to extract the interesting data. -

        - -

        The following JSONP APIs are automatically detected and parsed:

        - - - -

        If you need to do your own parsing, you can hook your your own data adapters in two ways:

        -
          -
        1. - Supply the data-adapter parameter on the <pre> element. - This must be the name of a globally defined function. - The plugin will use only this adapter to parse the response. -
        2. -
        3. - Register your adapter function by calling - Prism.plugins.jsonphighlight.registerAdapter(function(rsp) { … }). - It will be added to the list of inbuilt adapters and used if no other registered - adapater (e.g. GitHub/Bitbucket) can parse the response. -
        4. -
        - -

        - In either case, the function must accept at least a single parameter (the JSONP response) and - returns a string of the content to highlight. If your adapter cannot parse the response, you - must return null. The DOM node that will contain the highlighted code will also - be passed in as the second argument, incase you need to use it to query any extra information - (maybe you wish to inspect the class or data-jsonp attributes to - assist in parsing the response). -

        - -

        - The following example demonstrates both methods of using a custom adapter, to simply return - the stringyfied JSONP response (i.e highlight the entire JSONP data): -

        - -
        <!-- perhaps this is in a .js file elsewhere -->
        -<script>
        -	function dump_json(rsp) {
        -		return "using dump_json: " + JSON.stringify(rsp,null,2);
        -	}
        -</script>
        -
        -<!-- … include prism.js … -->
        -<script>
        -	Prism.plugins.jsonphighlight.registerAdapter(function(rsp) {
        -		return "using registerAdapter: " + JSON.stringify(rsp,null,2);
        -	})
        -</script>
        -
        - -

        And later in your HTML:

        - -
        <!-- using the data-adapter attribute -->
        -<pre class="language-javascript" data-jsonp="…" data-adapter="dump_json"></pre>
        -
        -<!-- using whatever data adapters are available -->
        -<pre class="language-javascript" data-jsonp="…"></pre>
        -
        - -

        - Finally, unlike like the File Highlight - plugin, you do need to supply the appropriate class with the language - to highlight. This could have been auto-detected, but since you're not actually linking to - a file it's not always possible (see below in the example using GitHub status). - Furthermore, if you're linking to files with a .xaml extension for example, - this plugin then needs to somehow map that to highlight as markup, which just - means more bloat. You know what you're trying to highlight, just say so :) -

        - -

        Caveat for Gists

        - -

        - There's a bit of a catch with gists, as they can actually contain multiple files. - There are two options to handle this: -

        - -
          -
        1. - If your gist only contains one file, you don't need to to anything; the one and only - file will automatically be chosen and highlighted -
        2. -
        3. - If your file contains multiple files, the first one will be chosen by default. - However, you can supply the filename in the data-filename attribute, and - this file will be highlighted instead: -
          <pre class="…" data-jsonp="…" data-filename="mydemo.js"></pre>
          -
        4. -
        -
        - -
        -

        Examples

        - -

        The plugin’s JS code (from GitHub):

        -
        
        -
        -	

        GitHub Gist (gist contains a single file, automatically selected):

        -
        
        -
        -	

        GitHub Gist (gist contains a multiple files, file to load specified):

        -
        
        -
        - 	

        Bitbucket API:

        -
        
        - 	
        - 	

        Custom adapter (JSON.stringify showing GitHub status):

        -
        
        -
        -	

        Registered adapter (as above, but without explicitly declaring the data-adapter attribute):

        -
        
        -
        - -
        - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/jsonp-highlight/prism-jsonp-highlight.js b/docs/_style/prism-master/plugins/jsonp-highlight/prism-jsonp-highlight.js deleted file mode 100644 index 4ce5ca50..00000000 --- a/docs/_style/prism-master/plugins/jsonp-highlight/prism-jsonp-highlight.js +++ /dev/null @@ -1,151 +0,0 @@ -(function() { - if ( !self.Prism || !self.document || !document.querySelectorAll || ![].filter) return; - - var adapters = []; - function registerAdapter(adapter) { - if (typeof adapter === "function" && !getAdapter(adapter)) { - adapters.push(adapter); - } - } - function getAdapter(adapter) { - if (typeof adapter === "function") { - return adapters.filter(function(fn) { return fn.valueOf() === adapter.valueOf()})[0]; - } - else if (typeof adapter === "string" && adapter.length > 0) { - return adapters.filter(function(fn) { return fn.name === adapter})[0]; - } - return null; - } - function removeAdapter(adapter) { - if (typeof adapter === "string") - adapter = getAdapter(adapter); - if (typeof adapter === "function") { - var index = adapters.indexOf(adapter); - if (index >=0) { - adapters.splice(index,1); - } - } - } - - Prism.plugins.jsonphighlight = { - registerAdapter: registerAdapter, - removeAdapter: removeAdapter, - highlight: highlight - }; - registerAdapter(function github(rsp, el) { - if ( rsp && rsp.meta && rsp.data ) { - if ( rsp.meta.status && rsp.meta.status >= 400 ) { - return "Error: " + ( rsp.data.message || rsp.meta.status ); - } - else if ( typeof(rsp.data.content) === "string" ) { - return typeof(atob) === "function" - ? atob(rsp.data.content.replace(/\s/g, "")) - : "Your browser cannot decode base64"; - } - } - return null; - }); - registerAdapter(function gist(rsp, el) { - if ( rsp && rsp.meta && rsp.data && rsp.data.files ) { - if ( rsp.meta.status && rsp.meta.status >= 400 ) { - return "Error: " + ( rsp.data.message || rsp.meta.status ); - } - else { - var filename = el.getAttribute("data-filename"); - if (filename == null) { - // Maybe in the future we can somehow render all files - // But the standard - - - - - - -
        -
        - -

        Keep markup

        -

        Prevents custom markup from being dropped out during highlighting.

        -
        - -
        - -

        How to use

        -

        You have nothing to do. With this plugin loaded, all markup inside code will be kept.

        - -

        Examples

        - -

        The following source code

        -
        <pre><code class="language-css">
        -@media <mark>screen</mark> {
        -	div {
        -		<mark>text</mark>-decoration: <mark><mark>under</mark>line</mark>;
        -		back<mark>ground: url</mark>('foo.png');
        -	}
        -}</code></pre>
        - -

        would render like this:

        -
        
        -@media screen {
        -	div {
        -		text-decoration: underline;
        -		background: url('foo.png');
        -	}
        -}
        - -

        - It also works for inline code: - var bar = function () { /* foo */ }; -

        - -
        - -
        - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.js b/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.js deleted file mode 100644 index bc136c67..00000000 --- a/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.js +++ /dev/null @@ -1,99 +0,0 @@ -(function () { - - if (typeof self === 'undefined' || !self.Prism || !self.document || !document.createRange) { - return; - } - - Prism.plugins.KeepMarkup = true; - - Prism.hooks.add('before-highlight', function (env) { - if (!env.element.children.length) { - return; - } - - var pos = 0; - var data = []; - var f = function (elt, baseNode) { - var o = {}; - if (!baseNode) { - // Clone the original tag to keep all attributes - o.clone = elt.cloneNode(false); - o.posOpen = pos; - data.push(o); - } - for (var i = 0, l = elt.childNodes.length; i < l; i++) { - var child = elt.childNodes[i]; - if (child.nodeType === 1) { // element - f(child); - } else if(child.nodeType === 3) { // text - pos += child.data.length; - } - } - if (!baseNode) { - o.posClose = pos; - } - }; - f(env.element, true); - - if (data && data.length) { - // data is an array of all existing tags - env.keepMarkup = data; - } - }); - - Prism.hooks.add('after-highlight', function (env) { - if(env.keepMarkup && env.keepMarkup.length) { - - var walk = function (elt, nodeState) { - for (var i = 0, l = elt.childNodes.length; i < l; i++) { - - var child = elt.childNodes[i]; - - if (child.nodeType === 1) { // element - if (!walk(child, nodeState)) { - return false; - } - - } else if (child.nodeType === 3) { // text - if(!nodeState.nodeStart && nodeState.pos + child.data.length > nodeState.node.posOpen) { - // We found the start position - nodeState.nodeStart = child; - nodeState.nodeStartPos = nodeState.node.posOpen - nodeState.pos; - } - if(nodeState.nodeStart && nodeState.pos + child.data.length >= nodeState.node.posClose) { - // We found the end position - nodeState.nodeEnd = child; - nodeState.nodeEndPos = nodeState.node.posClose - nodeState.pos; - } - - nodeState.pos += child.data.length; - } - - if (nodeState.nodeStart && nodeState.nodeEnd) { - // Select the range and wrap it with the clone - var range = document.createRange(); - range.setStart(nodeState.nodeStart, nodeState.nodeStartPos); - range.setEnd(nodeState.nodeEnd, nodeState.nodeEndPos); - nodeState.node.clone.appendChild(range.extractContents()); - range.insertNode(nodeState.node.clone); - range.detach(); - - // Process is over - return false; - } - } - return true; - }; - - // For each tag, we walk the DOM to reinsert it - env.keepMarkup.forEach(function (node) { - walk(env.element, { - node: node, - pos: 0 - }); - }); - // Store new highlightedCode for later hooks calls - env.highlightedCode = env.element.innerHTML; - } - }); -}()); diff --git a/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.min.js b/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.min.js deleted file mode 100644 index 7f54d276..00000000 --- a/docs/_style/prism-master/plugins/keep-markup/prism-keep-markup.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"undefined"!=typeof self&&self.Prism&&self.document&&document.createRange&&(Prism.plugins.KeepMarkup=!0,Prism.hooks.add("before-highlight",function(e){if(e.element.children.length){var n=0,o=[],t=function(e,d){var r={};d||(r.clone=e.cloneNode(!1),r.posOpen=n,o.push(r));for(var a=0,s=e.childNodes.length;s>a;a++){var l=e.childNodes[a];1===l.nodeType?t(l):3===l.nodeType&&(n+=l.data.length)}d||(r.posClose=n)};t(e.element,!0),o&&o.length&&(e.keepMarkup=o)}}),Prism.hooks.add("after-highlight",function(e){if(e.keepMarkup&&e.keepMarkup.length){var n=function(e,o){for(var t=0,d=e.childNodes.length;d>t;t++){var r=e.childNodes[t];if(1===r.nodeType){if(!n(r,o))return!1}else 3===r.nodeType&&(!o.nodeStart&&o.pos+r.data.length>o.node.posOpen&&(o.nodeStart=r,o.nodeStartPos=o.node.posOpen-o.pos),o.nodeStart&&o.pos+r.data.length>=o.node.posClose&&(o.nodeEnd=r,o.nodeEndPos=o.node.posClose-o.pos),o.pos+=r.data.length);if(o.nodeStart&&o.nodeEnd){var a=document.createRange();return a.setStart(o.nodeStart,o.nodeStartPos),a.setEnd(o.nodeEnd,o.nodeEndPos),o.node.clone.appendChild(a.extractContents()),a.insertNode(o.node.clone),a.detach(),!1}}return!0};e.keepMarkup.forEach(function(o){n(e.element,{node:o,pos:0})}),e.highlightedCode=e.element.innerHTML}}))}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/line-highlight/index.html b/docs/_style/prism-master/plugins/line-highlight/index.html deleted file mode 100644 index f3b58b4b..00000000 --- a/docs/_style/prism-master/plugins/line-highlight/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -Line highlight ▲ Prism plugins - - - - - - - - - - - -
        -
        - -

        Line highlight

        -

        Highlights specific lines and/or line ranges.

        -
        - -
        -

        How to use

        - -

        Obviously, this only works on code blocks (<pre><code>) and not for inline code. - -

        You specify the lines to be highlighted through the data-line attribute on the <pre> element, in the following simple format:

        -
          -
        • A single number refers to the line with that number
        • -
        • Ranges are denoted by two numbers, separated with a hyphen (-)
        • -
        • Multiple line numbers or ranges are separated by commas.
        • -
        • Whitespace is allowed anywhere and will be stripped off.
        • -
        - -

        Examples:

        -
        -
        5
        -
        The 5th line
        - -
        1-5
        -
        Lines 1 through 5
        - -
        1,4
        -
        Line 1 and line 4
        - -
        1-2, 5, 9-20
        -
        Lines 1 through 2, line 5, lines 9 through 20
        -
        - -

        In case you want the line numbering to be offset by a certain number (for example, you want the 1st line to be number 41 instead of 1, which is an offset of 40), you can additionally use the data-line-offset attribute. - -

        You can also link to specific lines on any code snippet, by using the following as a url hash: #{element-id}.{lines} where - {element-id} is the id of the <pre> element and {lines} is one or more lines or line ranges that follow the format - outlined above. For example, if there is an element with id="play" on the page, you can link to lines 5-6 by linking to #play.5-6

        -
        - -
        -

        Examples

        - -

        Line 2

        -
        
        -	
        -	

        Lines 15-25

        -
        
        -	
        -	

        Line 1 and lines 3-4 and line 42

        -
        
        -	
        -	

        Line 43, starting from line 41

        -
        
        -	
        -	

        Linking example

        -
        - -
        - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.css b/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.css deleted file mode 100644 index 6058db44..00000000 --- a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.css +++ /dev/null @@ -1,49 +0,0 @@ -pre[data-line] { - position: relative; - padding: 1em 0 1em 3em; -} - -.line-highlight { - position: absolute; - left: 0; - right: 0; - padding: inherit 0; - margin-top: 1em; /* Same as .prism’s padding-top */ - - background: hsla(24, 20%, 50%,.08); - background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); - - pointer-events: none; - - line-height: inherit; - white-space: pre; -} - - .line-highlight:before, - .line-highlight[data-end]:after { - content: attr(data-start); - position: absolute; - top: .4em; - left: .6em; - min-width: 1em; - padding: 0 .5em; - background-color: hsla(24, 20%, 50%,.4); - color: hsl(24, 20%, 95%); - font: bold 65%/1.5 sans-serif; - text-align: center; - vertical-align: .3em; - border-radius: 999px; - text-shadow: none; - box-shadow: 0 1px white; - } - - .line-highlight[data-end]:after { - content: attr(data-end); - top: auto; - bottom: .4em; - } - -.line-numbers .line-highlight:before, -.line-numbers .line-highlight:after { - content: none; -} diff --git a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.js b/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.js deleted file mode 100644 index 5d919ae6..00000000 --- a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.js +++ /dev/null @@ -1,181 +0,0 @@ -(function(){ - -if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) { - return; -} - -function $$(expr, con) { - return Array.prototype.slice.call((con || document).querySelectorAll(expr)); -} - -function hasClass(element, className) { - className = " " + className + " "; - return (" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf(className) > -1 -} - -// Some browsers round the line-height, others don't. -// We need to test for it to position the elements properly. -var isLineHeightRounded = (function() { - var res; - return function() { - if(typeof res === 'undefined') { - var d = document.createElement('div'); - d.style.fontSize = '13px'; - d.style.lineHeight = '1.5'; - d.style.padding = 0; - d.style.border = 0; - d.innerHTML = ' 
         '; - document.body.appendChild(d); - // Browsers that round the line-height should have offsetHeight === 38 - // The others should have 39. - res = d.offsetHeight === 38; - document.body.removeChild(d); - } - return res; - } -}()); - -function highlightLines(pre, lines, classes) { - lines = typeof lines === 'string' ? lines : pre.getAttribute('data-line'); - - var ranges = lines.replace(/\s+/g, '').split(','), - offset = +pre.getAttribute('data-line-offset') || 0; - - var parseMethod = isLineHeightRounded() ? parseInt : parseFloat; - var lineHeight = parseMethod(getComputedStyle(pre).lineHeight); - var hasLineNumbers = hasClass(pre, 'line-numbers'); - - for (var i=0, currentRange; currentRange = ranges[i++];) { - var range = currentRange.split('-'); - - var start = +range[0], - end = +range[1] || start; - - var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div'); - - line.setAttribute('aria-hidden', 'true'); - line.setAttribute('data-range', currentRange); - line.className = (classes || '') + ' line-highlight'; - - //if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers - if(hasLineNumbers && Prism.plugins.lineNumbers) { - var startNode = Prism.plugins.lineNumbers.getLine(pre, start); - var endNode = Prism.plugins.lineNumbers.getLine(pre, end); - - if (startNode) { - line.style.top = startNode.offsetTop + 'px'; - } - - if (endNode) { - line.style.height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px'; - } - } else { - line.setAttribute('data-start', start); - - if(end > start) { - line.setAttribute('data-end', end); - } - - line.style.top = (start - offset - 1) * lineHeight + 'px'; - - line.textContent = new Array(end - start + 2).join(' \n'); - } - - //allow this to play nicely with the line-numbers plugin - if(hasLineNumbers) { - //need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning - pre.appendChild(line); - } else { - (pre.querySelector('code') || pre).appendChild(line); - } - } -} - -function applyHash() { - var hash = location.hash.slice(1); - - // Remove pre-existing temporary lines - $$('.temporary.line-highlight').forEach(function (line) { - line.parentNode.removeChild(line); - }); - - var range = (hash.match(/\.([\d,-]+)$/) || [,''])[1]; - - if (!range || document.getElementById(hash)) { - return; - } - - var id = hash.slice(0, hash.lastIndexOf('.')), - pre = document.getElementById(id); - - if (!pre) { - return; - } - - if (!pre.hasAttribute('data-line')) { - pre.setAttribute('data-line', ''); - } - - highlightLines(pre, range, 'temporary '); - - document.querySelector('.temporary.line-highlight').scrollIntoView(); -} - -var fakeTimer = 0; // Hack to limit the number of times applyHash() runs - -Prism.hooks.add('before-sanity-check', function(env) { - var pre = env.element.parentNode; - var lines = pre && pre.getAttribute('data-line'); - - if (!pre || !lines || !/pre/i.test(pre.nodeName)) { - return; - } - - /* - * Cleanup for other plugins (e.g. autoloader). - * - * Sometimes blocks are highlighted multiple times. It is necessary - * to cleanup any left-over tags, because the whitespace inside of the
        - * tags change the content of the tag. - */ - var num = 0; - $$('.line-highlight', pre).forEach(function (line) { - num += line.textContent.length; - line.parentNode.removeChild(line); - }); - // Remove extra whitespace - if (num && /^( \n)+$/.test(env.code.slice(-num))) { - env.code = env.code.slice(0, -num); - } -}); - -Prism.hooks.add('complete', function completeHook(env) { - var pre = env.element.parentNode; - var lines = pre && pre.getAttribute('data-line'); - - if (!pre || !lines || !/pre/i.test(pre.nodeName)) { - return; - } - - clearTimeout(fakeTimer); - - var hasLineNumbers = Prism.plugins.lineNumbers; - var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers; - - if (hasClass(pre, 'line-numbers') && hasLineNumbers && !isLineNumbersLoaded) { - Prism.hooks.add('line-numbers', completeHook); - } else { - highlightLines(pre, lines); - fakeTimer = setTimeout(applyHash, 1); - } -}); - - window.addEventListener('hashchange', applyHash); - window.addEventListener('resize', function () { - var preElements = document.querySelectorAll('pre[data-line]'); - Array.prototype.forEach.call(preElements, function (pre) { - highlightLines(pre); - }); - }); - -})(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.min.js b/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.min.js deleted file mode 100644 index 6c870914..00000000 --- a/docs/_style/prism-master/plugins/line-highlight/prism-line-highlight.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){function e(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function t(e,t){return t=" "+t+" ",(" "+e.className+" ").replace(/[\n\t]/g," ").indexOf(t)>-1}function n(e,n,i){n="string"==typeof n?n:e.getAttribute("data-line");for(var o,l=n.replace(/\s+/g,"").split(","),a=+e.getAttribute("data-line-offset")||0,s=r()?parseInt:parseFloat,d=s(getComputedStyle(e).lineHeight),u=t(e,"line-numbers"),c=0;o=l[c++];){var p=o.split("-"),m=+p[0],f=+p[1]||m,h=e.querySelector('.line-highlight[data-range="'+o+'"]')||document.createElement("div");if(h.setAttribute("aria-hidden","true"),h.setAttribute("data-range",o),h.className=(i||"")+" line-highlight",u&&Prism.plugins.lineNumbers){var g=Prism.plugins.lineNumbers.getLine(e,m),y=Prism.plugins.lineNumbers.getLine(e,f);g&&(h.style.top=g.offsetTop+"px"),y&&(h.style.height=y.offsetTop-g.offsetTop+y.offsetHeight+"px")}else h.setAttribute("data-start",m),f>m&&h.setAttribute("data-end",f),h.style.top=(m-a-1)*d+"px",h.textContent=new Array(f-m+2).join(" \n");u?e.appendChild(h):(e.querySelector("code")||e).appendChild(h)}}function i(){var t=location.hash.slice(1);e(".temporary.line-highlight").forEach(function(e){e.parentNode.removeChild(e)});var i=(t.match(/\.([\d,-]+)$/)||[,""])[1];if(i&&!document.getElementById(t)){var r=t.slice(0,t.lastIndexOf(".")),o=document.getElementById(r);o&&(o.hasAttribute("data-line")||o.setAttribute("data-line",""),n(o,i,"temporary "),document.querySelector(".temporary.line-highlight").scrollIntoView())}}if("undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector){var r=function(){var e;return function(){if("undefined"==typeof e){var t=document.createElement("div");t.style.fontSize="13px",t.style.lineHeight="1.5",t.style.padding=0,t.style.border=0,t.innerHTML=" 
         ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}}(),o=0;Prism.hooks.add("before-sanity-check",function(t){var n=t.element.parentNode,i=n&&n.getAttribute("data-line");if(n&&i&&/pre/i.test(n.nodeName)){var r=0;e(".line-highlight",n).forEach(function(e){r+=e.textContent.length,e.parentNode.removeChild(e)}),r&&/^( \n)+$/.test(t.code.slice(-r))&&(t.code=t.code.slice(0,-r))}}),Prism.hooks.add("complete",function l(e){var r=e.element.parentNode,a=r&&r.getAttribute("data-line");if(r&&a&&/pre/i.test(r.nodeName)){clearTimeout(o);var s=Prism.plugins.lineNumbers,d=e.plugins&&e.plugins.lineNumbers;t(r,"line-numbers")&&s&&!d?Prism.hooks.add("line-numbers",l):(n(r,a),o=setTimeout(i,1))}}),window.addEventListener("hashchange",i),window.addEventListener("resize",function(){var e=document.querySelectorAll("pre[data-line]");Array.prototype.forEach.call(e,function(e){n(e)})})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/line-numbers/index.html b/docs/_style/prism-master/plugins/line-numbers/index.html deleted file mode 100644 index 5af756ca..00000000 --- a/docs/_style/prism-master/plugins/line-numbers/index.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - -Line Numbers ▲ Prism plugins - - - - - - - - - - - -
        -
        - -

        Line Numbers

        -

        Line number at the beginning of code lines.

        -
        - -
        -

        How to use

        - -

        Obviously, this is supposed to work only for code blocks (<pre><code>) and not for inline code.

        -

        Add class line-numbers to your desired <pre> and line-numbers plugin will take care.

        -

        Optional: You can specify the data-start (Number) attribute on the <pre> element. It will shift the line counter.

        -

        Optional: To support multiline line numbers using soft wrap add css white-space to pre-line or pre-wrap.

        -
        - -
        -

        Examples

        - -

        JavaScript

        -
        
        -
        -  

        CSS

        -
        
        -
        -  

        HTML

        -

        Please note the data-start="-5" in the code below.

        -
        
        -
        -  

        Unknown languages

        -
        This raw text
        -is not highlighted
        -but it still has
        -lines numbers
        - -

        Soft wrap support

        -
        
        -
        -
        - -
        - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.css b/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.css deleted file mode 100644 index 08b29ed6..00000000 --- a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.css +++ /dev/null @@ -1,41 +0,0 @@ -pre[class*="language-"].line-numbers { - position: relative; - padding-left: 3.8em; - counter-reset: linenumber; -} - -pre[class*="language-"].line-numbers > code { - position: relative; - white-space: inherit; -} - -.line-numbers .line-numbers-rows { - position: absolute; - pointer-events: none; - top: 0; - font-size: 100%; - left: -3.8em; - width: 3em; /* works for line-numbers below 1000 lines */ - letter-spacing: -1px; - border-right: 1px solid #999; - - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - -} - - .line-numbers-rows > span { - pointer-events: none; - display: block; - counter-increment: linenumber; - } - - .line-numbers-rows > span:before { - content: counter(linenumber); - color: #999; - display: block; - padding-right: 0.8em; - text-align: right; - } diff --git a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.js b/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.js deleted file mode 100644 index e9e684fe..00000000 --- a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.js +++ /dev/null @@ -1,159 +0,0 @@ -(function () { - - if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; - } - - /** - * Plugin name which is used as a class name for
         which is activating the plugin
        -	 * @type {String}
        -	 */
        -	var PLUGIN_NAME = 'line-numbers';
        -	
        -	/**
        -	 * Regular expression used for determining line breaks
        -	 * @type {RegExp}
        -	 */
        -	var NEW_LINE_EXP = /\n(?!$)/g;
        -
        -	/**
        -	 * Resizes line numbers spans according to height of line of code
        -	 * @param {Element} element 
         element
        -	 */
        -	var _resizeElement = function (element) {
        -		var codeStyles = getStyles(element);
        -		var whiteSpace = codeStyles['white-space'];
        -
        -		if (whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line') {
        -			var codeElement = element.querySelector('code');
        -			var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
        -			var lineNumberSizer = element.querySelector('.line-numbers-sizer');
        -			var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
        -
        -			if (!lineNumberSizer) {
        -				lineNumberSizer = document.createElement('span');
        -				lineNumberSizer.className = 'line-numbers-sizer';
        -
        -				codeElement.appendChild(lineNumberSizer);
        -			}
        -
        -			lineNumberSizer.style.display = 'block';
        -
        -			codeLines.forEach(function (line, lineNumber) {
        -				lineNumberSizer.textContent = line || '\n';
        -				var lineSize = lineNumberSizer.getBoundingClientRect().height;
        -				lineNumbersWrapper.children[lineNumber].style.height = lineSize + 'px';
        -			});
        -
        -			lineNumberSizer.textContent = '';
        -			lineNumberSizer.style.display = 'none';
        -		}
        -	};
        -
        -	/**
        -	 * Returns style declarations for the element
        -	 * @param {Element} element
        -	 */
        -	var getStyles = function (element) {
        -		if (!element) {
        -			return null;
        -		}
        -
        -		return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
        -	};
        -
        -	window.addEventListener('resize', function () {
        -		Array.prototype.forEach.call(document.querySelectorAll('pre.' + PLUGIN_NAME), _resizeElement);
        -	});
        -
        -	Prism.hooks.add('complete', function (env) {
        -		if (!env.code) {
        -			return;
        -		}
        -
        -		// works only for  wrapped inside 
         (not inline)
        -		var pre = env.element.parentNode;
        -		var clsReg = /\s*\bline-numbers\b\s*/;
        -		if (
        -			!pre || !/pre/i.test(pre.nodeName) ||
        -			// Abort only if nor the 
         nor the  have the class
        -			(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
        -		) {
        -			return;
        -		}
        -
        -		if (env.element.querySelector('.line-numbers-rows')) {
        -			// Abort if line numbers already exists
        -			return;
        -		}
        -
        -		if (clsReg.test(env.element.className)) {
        -			// Remove the class 'line-numbers' from the 
        -			env.element.className = env.element.className.replace(clsReg, ' ');
        -		}
        -		if (!clsReg.test(pre.className)) {
        -			// Add the class 'line-numbers' to the 
        -			pre.className += ' line-numbers';
        -		}
        -
        -		var match = env.code.match(NEW_LINE_EXP);
        -		var linesNum = match ? match.length + 1 : 1;
        -		var lineNumbersWrapper;
        -
        -		var lines = new Array(linesNum + 1);
        -		lines = lines.join('');
        -
        -		lineNumbersWrapper = document.createElement('span');
        -		lineNumbersWrapper.setAttribute('aria-hidden', 'true');
        -		lineNumbersWrapper.className = 'line-numbers-rows';
        -		lineNumbersWrapper.innerHTML = lines;
        -
        -		if (pre.hasAttribute('data-start')) {
        -			pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
        -		}
        -
        -		env.element.appendChild(lineNumbersWrapper);
        -
        -		_resizeElement(pre);
        -
        -		Prism.hooks.run('line-numbers', env);
        -	});
        -
        -	Prism.hooks.add('line-numbers', function (env) {
        -		env.plugins = env.plugins || {};
        -		env.plugins.lineNumbers = true;
        -	});
        -	
        -	/**
        -	 * Global exports
        -	 */
        -	Prism.plugins.lineNumbers = {
        -		/**
        -		 * Get node for provided line number
        -		 * @param {Element} element pre element
        -		 * @param {Number} number line number
        -		 * @return {Element|undefined}
        -		 */
        -		getLine: function (element, number) {
        -			if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
        -				return;
        -			}
        -
        -			var lineNumberRows = element.querySelector('.line-numbers-rows');
        -			var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
        -			var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
        -
        -			if (number < lineNumberStart) {
        -				number = lineNumberStart;
        -			}
        -			if (number > lineNumberEnd) {
        -				number = lineNumberEnd;
        -			}
        -
        -			var lineIndex = number - lineNumberStart;
        -
        -			return lineNumberRows.children[lineIndex];
        -		}
        -	};
        -
        -}());
        \ No newline at end of file
        diff --git a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.min.js b/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.min.js
        deleted file mode 100644
        index 5291216f..00000000
        --- a/docs/_style/prism-master/plugins/line-numbers/prism-line-numbers.min.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e="line-numbers",t=/\n(?!$)/g,n=function(e){var n=r(e),s=n["white-space"];if("pre-wrap"===s||"pre-line"===s){var l=e.querySelector("code"),i=e.querySelector(".line-numbers-rows"),a=e.querySelector(".line-numbers-sizer"),o=l.textContent.split(t);a||(a=document.createElement("span"),a.className="line-numbers-sizer",l.appendChild(a)),a.style.display="block",o.forEach(function(e,t){a.textContent=e||"\n";var n=a.getBoundingClientRect().height;i.children[t].style.height=n+"px"}),a.textContent="",a.style.display="none"}},r=function(e){return e?window.getComputedStyle?getComputedStyle(e):e.currentStyle||null:null};window.addEventListener("resize",function(){Array.prototype.forEach.call(document.querySelectorAll("pre."+e),n)}),Prism.hooks.add("complete",function(e){if(e.code){var r=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(r&&/pre/i.test(r.nodeName)&&(s.test(r.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s," ")),s.test(r.className)||(r.className+=" line-numbers");var l,i=e.code.match(t),a=i?i.length+1:1,o=new Array(a+1);o=o.join(""),l=document.createElement("span"),l.setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=o,r.hasAttribute("data-start")&&(r.style.counterReset="linenumber "+(parseInt(r.getAttribute("data-start"),10)-1)),e.element.appendChild(l),n(r),Prism.hooks.run("line-numbers",e)}}}),Prism.hooks.add("line-numbers",function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}),Prism.plugins.lineNumbers={getLine:function(t,n){if("PRE"===t.tagName&&t.classList.contains(e)){var r=t.querySelector(".line-numbers-rows"),s=parseInt(t.getAttribute("data-start"),10)||1,l=s+(r.children.length-1);s>n&&(n=s),n>l&&(n=l);var i=n-s;return r.children[i]}}}}}();
        \ No newline at end of file
        diff --git a/docs/_style/prism-master/plugins/normalize-whitespace/demo.html b/docs/_style/prism-master/plugins/normalize-whitespace/demo.html
        deleted file mode 100644
        index a6bcf22b..00000000
        --- a/docs/_style/prism-master/plugins/normalize-whitespace/demo.html
        +++ /dev/null
        @@ -1,33 +0,0 @@
        -
        -
        - -
        -
        -	
        -
        -
        -		var example = {
        -			foo: true,
        -
        -			bar: false
        -		};
        -
        -
        -	
        -
        -
        - -
        - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/normalize-whitespace/index.html b/docs/_style/prism-master/plugins/normalize-whitespace/index.html deleted file mode 100644 index e1469de6..00000000 --- a/docs/_style/prism-master/plugins/normalize-whitespace/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - Normalize Whitespace ▲ Prism plugins - - - - - - - - - - - - -
        -
        - -

        Normalize Whitespace

        -

        Supports multiple operations to normalize whitespace in code blocks.

        -
        - -
        -

        How to use

        - -

        Obviously, this is supposed to work only for code blocks (<pre><code>) and not for inline code.

        -

        By default the plugin trims all leading and trailing whitespace of every code block. - It also removes extra indents and trailing whitespace on every line.

        - -

        The plugin can be disabled for a particular code block by adding the class no-whitespace-normalization to - either the <pre> or <code> tag.

        - -

        The default settings can be overridden with the setDefaults() method - like so:

        - -
        
        -Prism.plugins.NormalizeWhitespace.setDefaults({
        -	'remove-trailing': true,
        -	'remove-indent': true,
        -	'left-trim': true,
        -	'right-trim': true,
        -	/*'break-lines': 80,
        -	'indent': 2,
        -	'remove-initial-line-feed': false,
        -	'tabs-to-spaces': 4,
        -	'spaces-to-tabs': 4*/
        -});
        -
        - -

        The following settings are available:

        - -
        -
        remove-trailing
        -
        Removes trailing whitespace on all lines.
        -
        remove-indent
        -
        If the whole code block is indented too much it removes the extra indent.
        -
        left-trim
        -
        Removes all whitespace from the top of the code block.
        -
        right-trim
        -
        Removes all whitespace from the bottom of the code block.
        -
        break-lines
        -
        Simple way of breaking long lines at a certain length (default is 80 characters).
        -
        indent
        -
        Adds a certain number of tabs to every line.
        -
        remove-initial-line-feed
        -
        Less aggressive version of left-trim. - It only removes a single line feed from the top of the code block.
        -
        tabs-to-spaces
        -
        Converts all tabs to a certain number of spaces (default is 4 spaces).
        -
        spaces-to-tabs
        -
        Converts a certain number of spaces to a tab (default is 4 spaces).
        -
        -
        - -
        -

        Examples

        - -

        The following example demonstrates the use of this plugin:

        - -
        
        -
        -	

        The result looks like this:

        - -
        -
        -	
        -
        -
        -		var example = {
        -			foo: true,
        -
        -			bar: false
        -		};
        -
        -
        -	
        -
        -
        - -

        It is also compatible with the keep-markup plugin:

        - -
        -
        -	
        -
        -
        -	@media screen {
        -		div {
        -			text-decoration: underline;
        -			background: url('foo.png');
        -		}
        -	}
        -
        -
        -
        - -

        This plugin can also be used on the server or on the command line with Node.js:

        - -
        
        -var Prism = require('prismjs');
        -var Normalizer = require('prismjs/plugins/normalize-whitespace/prism-normalize-whitespace');
        -// Create a new Normalizer object
        -var nw = new Normalizer({
        -	'remove-trailing': true,
        -	'remove-indent': true,
        -	'left-trim': true,
        -	'right-trim': true,
        -	/*'break-lines': 80,
        -	'indent': 2,
        -	'remove-initial-line-feed': false,
        -	'tabs-to-spaces': 4,
        -	'spaces-to-tabs': 4*/
        -});
        -
        -// ..or use the default object from Prism
        -nw = Prism.plugins.NormalizeWhitespace;
        -
        -// The code snippet you want to highlight, as a string
        -var code = "\t\t\tvar data = 1;    ";
        -
        -// Removes leading and trailing whitespace
        -// and then indents by 1 tab
        -code = nw.normalize(code, {
        -	// Extra settings
        -	indent: 1
        -});
        -
        -// Returns a highlighted HTML string
        -var html = Prism.highlight(code, Prism.languages.javascript);
        -	
        - - -
        - -
        - - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.js b/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.js deleted file mode 100644 index b6c64727..00000000 --- a/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.js +++ /dev/null @@ -1,190 +0,0 @@ -(function() { - -var assign = Object.assign || function (obj1, obj2) { - for (var name in obj2) { - if (obj2.hasOwnProperty(name)) - obj1[name] = obj2[name]; - } - return obj1; -} - -function NormalizeWhitespace(defaults) { - this.defaults = assign({}, defaults); -} - -function toCamelCase(value) { - return value.replace(/-(\w)/g, function(match, firstChar) { - return firstChar.toUpperCase(); - }); -} - -function tabLen(str) { - var res = 0; - for (var i = 0; i < str.length; ++i) { - if (str.charCodeAt(i) == '\t'.charCodeAt(0)) - res += 3; - } - return str.length + res; -} - -NormalizeWhitespace.prototype = { - setDefaults: function (defaults) { - this.defaults = assign(this.defaults, defaults); - }, - normalize: function (input, settings) { - settings = assign(this.defaults, settings); - - for (var name in settings) { - var methodName = toCamelCase(name); - if (name !== "normalize" && methodName !== 'setDefaults' && - settings[name] && this[methodName]) { - input = this[methodName].call(this, input, settings[name]); - } - } - - return input; - }, - - /* - * Normalization methods - */ - leftTrim: function (input) { - return input.replace(/^\s+/, ''); - }, - rightTrim: function (input) { - return input.replace(/\s+$/, ''); - }, - tabsToSpaces: function (input, spaces) { - spaces = spaces|0 || 4; - return input.replace(/\t/g, new Array(++spaces).join(' ')); - }, - spacesToTabs: function (input, spaces) { - spaces = spaces|0 || 4; - return input.replace(RegExp(' {' + spaces + '}', 'g'), '\t'); - }, - removeTrailing: function (input) { - return input.replace(/\s*?$/gm, ''); - }, - // Support for deprecated plugin remove-initial-line-feed - removeInitialLineFeed: function (input) { - return input.replace(/^(?:\r?\n|\r)/, ''); - }, - removeIndent: function (input) { - var indents = input.match(/^[^\S\n\r]*(?=\S)/gm); - - if (!indents || !indents[0].length) - return input; - - indents.sort(function(a, b){return a.length - b.length; }); - - if (!indents[0].length) - return input; - - return input.replace(RegExp('^' + indents[0], 'gm'), ''); - }, - indent: function (input, tabs) { - return input.replace(/^[^\S\n\r]*(?=\S)/gm, new Array(++tabs).join('\t') + '$&'); - }, - breakLines: function (input, characters) { - characters = (characters === true) ? 80 : characters|0 || 80; - - var lines = input.split('\n'); - for (var i = 0; i < lines.length; ++i) { - if (tabLen(lines[i]) <= characters) - continue; - - var line = lines[i].split(/(\s+)/g), - len = 0; - - for (var j = 0; j < line.length; ++j) { - var tl = tabLen(line[j]); - len += tl; - if (len > characters) { - line[j] = '\n' + line[j]; - len = tl; - } - } - lines[i] = line.join(''); - } - return lines.join('\n'); - } -}; - -// Support node modules -if (typeof module !== 'undefined' && module.exports) { - module.exports = NormalizeWhitespace; -} - -// Exit if prism is not loaded -if (typeof Prism === 'undefined') { - return; -} - -Prism.plugins.NormalizeWhitespace = new NormalizeWhitespace({ - 'remove-trailing': true, - 'remove-indent': true, - 'left-trim': true, - 'right-trim': true, - /*'break-lines': 80, - 'indent': 2, - 'remove-initial-line-feed': false, - 'tabs-to-spaces': 4, - 'spaces-to-tabs': 4*/ -}); - -Prism.hooks.add('before-sanity-check', function (env) { - var Normalizer = Prism.plugins.NormalizeWhitespace; - - // Check settings - if (env.settings && env.settings['whitespace-normalization'] === false) { - return; - } - - // Simple mode if there is no env.element - if ((!env.element || !env.element.parentNode) && env.code) { - env.code = Normalizer.normalize(env.code, env.settings); - return; - } - - // Normal mode - var pre = env.element.parentNode; - var clsReg = /\bno-whitespace-normalization\b/; - if (!env.code || !pre || pre.nodeName.toLowerCase() !== 'pre' || - clsReg.test(pre.className) || clsReg.test(env.element.className)) - return; - - var children = pre.childNodes, - before = '', - after = '', - codeFound = false; - - // Move surrounding whitespace from the
         tag into the  tag
        -	for (var i = 0; i < children.length; ++i) {
        -		var node = children[i];
        -
        -		if (node == env.element) {
        -			codeFound = true;
        -		} else if (node.nodeName === "#text") {
        -			if (codeFound) {
        -				after += node.nodeValue;
        -			} else {
        -				before += node.nodeValue;
        -			}
        -
        -			pre.removeChild(node);
        -			--i;
        -		}
        -	}
        -
        -	if (!env.element.children.length || !Prism.plugins.KeepMarkup) {
        -		env.code = before + env.code + after;
        -		env.code = Normalizer.normalize(env.code, env.settings);
        -	} else {
        -		// Preserve markup for keep-markup plugin
        -		var html = before + env.element.innerHTML + after;
        -		env.element.innerHTML = Normalizer.normalize(html, env.settings);
        -		env.code = env.element.textContent;
        -	}
        -});
        -
        -}());
        diff --git a/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.min.js b/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.min.js
        deleted file mode 100644
        index 39fa5795..00000000
        --- a/docs/_style/prism-master/plugins/normalize-whitespace/prism-normalize-whitespace.min.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -!function(){function e(e){this.defaults=r({},e)}function n(e){return e.replace(/-(\w)/g,function(e,n){return n.toUpperCase()})}function t(e){for(var n=0,t=0;tn&&(o[s]="\n"+o[s],a=l)}r[i]=o.join("")}return r.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=e),"undefined"!=typeof Prism&&(Prism.plugins.NormalizeWhitespace=new e({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",function(e){var n=Prism.plugins.NormalizeWhitespace;if(!e.settings||e.settings["whitespace-normalization"]!==!1){if((!e.element||!e.element.parentNode)&&e.code)return e.code=n.normalize(e.code,e.settings),void 0;var t=e.element.parentNode,r=/\bno-whitespace-normalization\b/;if(e.code&&t&&"pre"===t.nodeName.toLowerCase()&&!r.test(t.className)&&!r.test(e.element.className)){for(var i=t.childNodes,o="",a="",s=!1,l=0;l
        -
        -
        -
        -	
        -	
        -	Previewers ▲ Prism plugins
        -	
        -	
        -	
        -	
        -	
        -
        -	
        -	
        -
        -
        -
        -
        -
        - -

        Previewers

        -

        Previewers for angles, colors, gradients, easing and time.

        -
        - -
        -

        How to use

        - -

        You don't need to do anything. With this plugin loaded, a previewer will appear on hovering some values in code blocks. - The following previewers are supported:

        -
          -
        • angle for angles
        • -
        • color for colors
        • -
        • gradient for gradients
        • -
        • easing for easing functions
        • -
        • time for durations
        • -
        -

        This plugin is compatible with CSS, Less, Markup attributes, Sass, Scss and Stylus.

        -
        - -
        -

        Examples

        - -

        CSS

        -
        .example-gradient {
        -	background: -webkit-linear-gradient(left,     #cb60b3 0%, #c146a1 50%, #a80077 51%, #db36a4 100%); /* Chrome10+, Safari5.1+ */
        -	background:    -moz-linear-gradient(left,     #cb60b3 0%, #c146a1 50%, #a80077 51%, #db36a4 100%); /* FF3.6+ */
        -	background:     -ms-linear-gradient(left,     #cb60b3 0%, #c146a1 50%, #a80077 51%, #db36a4 100%); /* IE10+ */
        -	background:      -o-linear-gradient(left,     #cb60b3 0%, #c146a1 50%, #a80077 51%, #db36a4 100%); /* Opera 11.10+ */
        -	background:         linear-gradient(to right, #cb60b3 0%, #c146a1 50%, #a80077 51%, #db36a4 100%); /* W3C */
        -}
        -.example-angle {
        -	transform: rotate(10deg);
        -}
        -.example-color {
        -	color: rgba(255, 0, 0, 0.2);
        -	background: purple;
        -	border: 1px solid hsl(100, 70%, 40%);
        -}
        -.example-easing {
        -	transition-timing-function: linear;
        -}
        -.example-time {
        -	transition-duration: 3s;
        -}
        - -

        Markup attributes

        -
        <table bgcolor="#6E5494">
        -<tr style="background: lightblue;">
        - -

        Less

        -
        @gradient: linear-gradient(135deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%);
        -.example-gradient {
        -	background: -webkit-linear-gradient(-45deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* Chrome10+, Safari5.1+ */
        -	background:    -moz-linear-gradient(-45deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
        -	background:     -ms-linear-gradient(-45deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* IE10+ */
        -	background:      -o-linear-gradient(-45deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* Opera 11.10+ */
        -	background:         linear-gradient(135deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* W3C */
        -}
        -@angle: 3rad;
        -.example-angle {
        -	transform: rotate(.4turn)
        -}
        -@nice-blue: #5B83AD;
        -.example-color {
        -	color: hsla(102, 53%, 42%, 0.4);
        -}
        -@easing: cubic-bezier(0.1, 0.3, 1, .4);
        -.example-easing {
        -	transition-timing-function: ease;
        -}
        -@time: 1s;
        -.example-time {
        -	transition-duration: 2s;
        -}
        - -

        Sass

        -
        $gradient: linear-gradient(135deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%)
        -@mixin example-gradient
        -	background: -moz-radial-gradient(center, ellipse cover, #f2f6f8 0%, #d8e1e7 50%, #b5c6d0 51%, #e0eff9 100%)
        -	background: radial-gradient(ellipse at center, #f2f6f8 0%, #d8e1e7 50%, #b5c6d0 51%, #e0eff9 100%)
        -$angle: 380grad
        -@mixin example-angle
        -	transform: rotate(-120deg)
        -.example-angle
        -	transform: rotate(18rad)
        -$color: blue
        -@mixin example-color
        -	color: rgba(147, 32, 34, 0.8)
        -.example-color
        -	color: pink
        -$easing: ease-out
        -.example-easing
        -	transition-timing-function: ease-in-out
        -$time: 3s
        -@mixin example-time
        -	transition-duration: 800ms
        -.example-time
        -	transition-duration: 0.8s
        -
        - -

        Scss

        -
        $gradient: linear-gradient(135deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%);
        -$attr: background;
        -.example-gradient {
        -	#{$attr}-image: repeating-linear-gradient(10deg, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1) 10px, rgba(255, 0, 0, 0) 20px);
        -}
        -$angle: 1.8turn;
        -.example-angle {
        -	transform: rotate(-3rad)
        -}
        -$color: blue;
        -.example-color {
        -	#{$attr}-color: rgba(255, 255, 0, 0.75);
        -}
        -$easing: linear;
        -.example-easing {
        -	transition-timing-function: cubic-bezier(0.9, 0.1, .2, .4);
        -}
        -$time: 1s;
        -.example-time {
        -	transition-duration: 10s
        -}
        - -

        Stylus

        -
        gradient = linear-gradient(135deg, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%)
        -.example-gradient
        -	background-image: repeating-radial-gradient(circle, rgba(255, 0, 0, 0), rgba(255, 0, 0, 1) 10px, rgba(255, 0, 0, 0) 20px)
        -angle = 357deg
        -.example-angle
        -	transform: rotate(100grad)
        -color = olive
        -.example-color
        -	color: #000
        -easing = ease-in
        -.example-easing
        -	transition-timing-function: ease-out
        -time = 3s
        -.example-time
        -	transition-duration: 0.5s
        -
        - -
        -

        Disabling a previewer

        -

        All previewers are enabled by default. To enable only a subset of them, a data-previewers attribute - can be added on a code block or any ancestor. Its value should be a space-separated list of previewers - representing the subset.

        -

        For example:

        -
        <pre class="language-css" data-previewers="color time"><code>div {
        -	/* Only the previewer for color and time are enabled */
        -	color: red;
        -	transition-duration: 1s;
        -	/* The previewer for angles is not enabled. */
        -	transform: rotate(10deg);
        -}</code></pre>
        -

        will give the following result:

        -
        div {
        -	/* Only the previewers for color and time are enabled */
        -	color: red;
        -	transition-duration: 1s;
        -	/* The previewer for angles is not enabled. */
        -	transform: rotate(10deg);
        -}
        -
        - -
        -

        API

        -

        This plugins provides a constructor that can be accessed through Prism.plugins.Previewer.

        -

        Once a previewer has been instantiated, an HTML element is appended to the document body. - This element will appear when specific tokens are hovered.

        - -

        new Prism.plugins.Previewer(type, updater, supportedLanguages)

        - -
          -
        • -

          type: the token type this previewer is associated to. - The previewer will be shown when hovering tokens of this type.

          -
        • -
        • -

          updater: the function that will be called each time an associated token is hovered. - This function takes the text content of the token as its only parameter. - The previewer HTML element can be accessed through the keyword this. - This function must return true for the previewer to be shown.

          -
        • -
        • -

          supportedLanguages: an optional array of supported languages. - The previewer will be available only for those. - Defaults to '*', which means every languages.

          -
        • -
        • -

          initializer: an optional function. - This function will be called when the previewer is initialized, - right after the HTML element has been appended to the document body.

          -
        • -
        - -
        - -
        - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/previewers/prism-previewers.css b/docs/_style/prism-master/plugins/previewers/prism-previewers.css deleted file mode 100644 index b36988c2..00000000 --- a/docs/_style/prism-master/plugins/previewers/prism-previewers.css +++ /dev/null @@ -1,242 +0,0 @@ -.prism-previewer, -.prism-previewer:before, -.prism-previewer:after { - position: absolute; - pointer-events: none; -} -.prism-previewer, -.prism-previewer:after { - left: 50%; -} -.prism-previewer { - margin-top: -48px; - width: 32px; - height: 32px; - margin-left: -16px; - - opacity: 0; - -webkit-transition: opacity .25s; - -o-transition: opacity .25s; - transition: opacity .25s; -} -.prism-previewer.flipped { - margin-top: 0; - margin-bottom: -48px; -} -.prism-previewer:before, -.prism-previewer:after { - content: ''; - position: absolute; - pointer-events: none; -} -.prism-previewer:before { - top: -5px; - right: -5px; - left: -5px; - bottom: -5px; - border-radius: 10px; - border: 5px solid #fff; - box-shadow: 0 0 3px rgba(0, 0, 0, 0.5) inset, 0 0 10px rgba(0, 0, 0, 0.75); -} -.prism-previewer:after { - top: 100%; - width: 0; - height: 0; - margin: 5px 0 0 -7px; - border: 7px solid transparent; - border-color: rgba(255, 0, 0, 0); - border-top-color: #fff; -} -.prism-previewer.flipped:after { - top: auto; - bottom: 100%; - margin-top: 0; - margin-bottom: 5px; - border-top-color: rgba(255, 0, 0, 0); - border-bottom-color: #fff; -} -.prism-previewer.active { - opacity: 1; -} - -.prism-previewer-angle:before { - border-radius: 50%; - background: #fff; -} -.prism-previewer-angle:after { - margin-top: 4px; -} -.prism-previewer-angle svg { - width: 32px; - height: 32px; - -webkit-transform: rotate(-90deg); - -moz-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - -o-transform: rotate(-90deg); - transform: rotate(-90deg); -} -.prism-previewer-angle[data-negative] svg { - -webkit-transform: scaleX(-1) rotate(-90deg); - -moz-transform: scaleX(-1) rotate(-90deg); - -ms-transform: scaleX(-1) rotate(-90deg); - -o-transform: scaleX(-1) rotate(-90deg); - transform: scaleX(-1) rotate(-90deg); -} -.prism-previewer-angle circle { - fill: transparent; - stroke: hsl(200, 10%, 20%); - stroke-opacity: 0.9; - stroke-width: 32; - stroke-dasharray: 0, 500; -} - -.prism-previewer-gradient { - background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, #eee 25%, #eee 75%, #bbb 75%, #bbb); - background-size: 10px 10px; - background-position: 0 0, 5px 5px; - - width: 64px; - margin-left: -32px; -} -.prism-previewer-gradient:before { - content: none; -} -.prism-previewer-gradient div { - position: absolute; - top: -5px; - left: -5px; - right: -5px; - bottom: -5px; - border-radius: 10px; - border: 5px solid #fff; - box-shadow: 0 0 3px rgba(0, 0, 0, 0.5) inset, 0 0 10px rgba(0, 0, 0, 0.75); -} - -.prism-previewer-color { - background-image: linear-gradient(45deg, #bbb 25%, transparent 25%, transparent 75%, #bbb 75%, #bbb), linear-gradient(45deg, #bbb 25%, #eee 25%, #eee 75%, #bbb 75%, #bbb); - background-size: 10px 10px; - background-position: 0 0, 5px 5px; -} -.prism-previewer-color:before { - background-color: inherit; - background-clip: padding-box; -} - -.prism-previewer-easing { - margin-top: -76px; - margin-left: -30px; - width: 60px; - height: 60px; - background: #333; -} -.prism-previewer-easing.flipped { - margin-bottom: -116px; -} -.prism-previewer-easing svg { - width: 60px; - height: 60px; -} -.prism-previewer-easing circle { - fill: hsl(200, 10%, 20%); - stroke: white; -} -.prism-previewer-easing path { - fill: none; - stroke: white; - stroke-linecap: round; - stroke-width: 4; -} -.prism-previewer-easing line { - stroke: white; - stroke-opacity: 0.5; - stroke-width: 2; -} - -@-webkit-keyframes prism-previewer-time { - 0% { - stroke-dasharray: 0, 500; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 100, 500; - stroke-dashoffset: 0; - } - 100% { - stroke-dasharray: 0, 500; - stroke-dashoffset: -100; - } -} - -@-o-keyframes prism-previewer-time { - 0% { - stroke-dasharray: 0, 500; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 100, 500; - stroke-dashoffset: 0; - } - 100% { - stroke-dasharray: 0, 500; - stroke-dashoffset: -100; - } -} - -@-moz-keyframes prism-previewer-time { - 0% { - stroke-dasharray: 0, 500; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 100, 500; - stroke-dashoffset: 0; - } - 100% { - stroke-dasharray: 0, 500; - stroke-dashoffset: -100; - } -} - -@keyframes prism-previewer-time { - 0% { - stroke-dasharray: 0, 500; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 100, 500; - stroke-dashoffset: 0; - } - 100% { - stroke-dasharray: 0, 500; - stroke-dashoffset: -100; - } -} - -.prism-previewer-time:before { - border-radius: 50%; - background: #fff; -} -.prism-previewer-time:after { - margin-top: 4px; -} -.prism-previewer-time svg { - width: 32px; - height: 32px; - -webkit-transform: rotate(-90deg); - -moz-transform: rotate(-90deg); - -ms-transform: rotate(-90deg); - -o-transform: rotate(-90deg); - transform: rotate(-90deg); -} -.prism-previewer-time circle { - fill: transparent; - stroke: hsl(200, 10%, 20%); - stroke-opacity: 0.9; - stroke-width: 32; - stroke-dasharray: 0, 500; - stroke-dashoffset: 0; - -webkit-animation: prism-previewer-time linear infinite 3s; - -moz-animation: prism-previewer-time linear infinite 3s; - -o-animation: prism-previewer-time linear infinite 3s; - animation: prism-previewer-time linear infinite 3s; -} \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/previewers/prism-previewers.js b/docs/_style/prism-master/plugins/previewers/prism-previewers.js deleted file mode 100644 index 0f0d2f9c..00000000 --- a/docs/_style/prism-master/plugins/previewers/prism-previewers.js +++ /dev/null @@ -1,708 +0,0 @@ -(function() { - - if ( - typeof self !== 'undefined' && !self.Prism || - !self.document || !Function.prototype.bind - ) { - return; - } - - var previewers = { - // gradient must be defined before color and angle - 'gradient': { - create: (function () { - - // Stores already processed gradients so that we don't - // make the conversion every time the previewer is shown - var cache = {}; - - /** - * Returns a W3C-valid linear gradient - * @param {string} prefix Vendor prefix if any ("-moz-", "-webkit-", etc.) - * @param {string} func Gradient function name ("linear-gradient") - * @param {string[]} values Array of the gradient function parameters (["0deg", "red 0%", "blue 100%"]) - */ - var convertToW3CLinearGradient = function(prefix, func, values) { - // Default value for angle - var angle = '180deg'; - - if (/^(?:-?\d*\.?\d+(?:deg|rad)|to\b|top|right|bottom|left)/.test(values[0])) { - angle = values.shift(); - if (angle.indexOf('to ') < 0) { - // Angle uses old keywords - // W3C syntax uses "to" + opposite keywords - if (angle.indexOf('top') >= 0) { - if (angle.indexOf('left') >= 0) { - angle = 'to bottom right'; - } else if (angle.indexOf('right') >= 0) { - angle = 'to bottom left'; - } else { - angle = 'to bottom'; - } - } else if (angle.indexOf('bottom') >= 0) { - if (angle.indexOf('left') >= 0) { - angle = 'to top right'; - } else if (angle.indexOf('right') >= 0) { - angle = 'to top left'; - } else { - angle = 'to top'; - } - } else if (angle.indexOf('left') >= 0) { - angle = 'to right'; - } else if (angle.indexOf('right') >= 0) { - angle = 'to left'; - } else if (prefix) { - // Angle is shifted by 90deg in prefixed gradients - if (angle.indexOf('deg') >= 0) { - angle = (90 - parseFloat(angle)) + 'deg'; - } else if (angle.indexOf('rad') >= 0) { - angle = (Math.PI / 2 - parseFloat(angle)) + 'rad'; - } - } - } - } - - return func + '(' + angle + ',' + values.join(',') + ')'; - }; - - /** - * Returns a W3C-valid radial gradient - * @param {string} prefix Vendor prefix if any ("-moz-", "-webkit-", etc.) - * @param {string} func Gradient function name ("linear-gradient") - * @param {string[]} values Array of the gradient function parameters (["0deg", "red 0%", "blue 100%"]) - */ - var convertToW3CRadialGradient = function(prefix, func, values) { - if (values[0].indexOf('at') < 0) { - // Looks like old syntax - - // Default values - var position = 'center'; - var shape = 'ellipse'; - var size = 'farthest-corner'; - - if (/\bcenter|top|right|bottom|left\b|^\d+/.test(values[0])) { - // Found a position - // Remove angle value, if any - position = values.shift().replace(/\s*-?\d+(?:rad|deg)\s*/, ''); - } - if (/\bcircle|ellipse|closest|farthest|contain|cover\b/.test(values[0])) { - // Found a shape and/or size - var shapeSizeParts = values.shift().split(/\s+/); - if (shapeSizeParts[0] && (shapeSizeParts[0] === 'circle' || shapeSizeParts[0] === 'ellipse')) { - shape = shapeSizeParts.shift(); - } - if (shapeSizeParts[0]) { - size = shapeSizeParts.shift(); - } - - // Old keywords are converted to their synonyms - if (size === 'cover') { - size = 'farthest-corner'; - } else if (size === 'contain') { - size = 'clothest-side'; - } - } - - return func + '(' + shape + ' ' + size + ' at ' + position + ',' + values.join(',') + ')'; - } - return func + '(' + values.join(',') + ')'; - }; - - /** - * Converts a gradient to a W3C-valid one - * Does not support old webkit syntax (-webkit-gradient(linear...) and -webkit-gradient(radial...)) - * @param {string} gradient The CSS gradient - */ - var convertToW3CGradient = function(gradient) { - if (cache[gradient]) { - return cache[gradient]; - } - var parts = gradient.match(/^(\b|\B-[a-z]{1,10}-)((?:repeating-)?(?:linear|radial)-gradient)/); - // "", "-moz-", etc. - var prefix = parts && parts[1]; - // "linear-gradient", "radial-gradient", etc. - var func = parts && parts[2]; - - var values = gradient.replace(/^(?:\b|\B-[a-z]{1,10}-)(?:repeating-)?(?:linear|radial)-gradient\(|\)$/g, '').split(/\s*,\s*/); - - if (func.indexOf('linear') >= 0) { - return cache[gradient] = convertToW3CLinearGradient(prefix, func, values); - } else if (func.indexOf('radial') >= 0) { - return cache[gradient] = convertToW3CRadialGradient(prefix, func, values); - } - return cache[gradient] = func + '(' + values.join(',') + ')'; - }; - - return function () { - new Prism.plugins.Previewer('gradient', function(value) { - this.firstChild.style.backgroundImage = ''; - this.firstChild.style.backgroundImage = convertToW3CGradient(value); - return !!this.firstChild.style.backgroundImage; - }, '*', function () { - this._elt.innerHTML = '
        '; - }); - }; - }()), - tokens: { - 'gradient': { - pattern: /(?:\b|\B-[a-z]{1,10}-)(?:repeating-)?(?:linear|radial)-gradient\((?:(?:rgb|hsl)a?\(.+?\)|[^\)])+\)/gi, - inside: { - 'function': /[\w-]+(?=\()/, - 'punctuation': /[(),]/ - } - } - }, - languages: { - 'css': true, - 'less': true, - 'sass': [ - { - lang: 'sass', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['variable-line'] - }, - { - lang: 'sass', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['property-line'] - } - ], - 'scss': true, - 'stylus': [ - { - lang: 'stylus', - before: 'func', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['property-declaration'].inside - }, - { - lang: 'stylus', - before: 'func', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['variable-declaration'].inside - } - ] - } - }, - 'angle': { - create: function () { - new Prism.plugins.Previewer('angle', function(value) { - var num = parseFloat(value); - var unit = value.match(/[a-z]+$/i); - var max, percentage; - if (!num || !unit) { - return false; - } - unit = unit[0]; - - switch(unit) { - case 'deg': - max = 360; - break; - case 'grad': - max = 400; - break; - case 'rad': - max = 2 * Math.PI; - break; - case 'turn': - max = 1; - } - - percentage = 100 * num/max; - percentage %= 100; - - this[(num < 0? 'set' : 'remove') + 'Attribute']('data-negative', ''); - this.querySelector('circle').style.strokeDasharray = Math.abs(percentage) + ',500'; - return true; - }, '*', function () { - this._elt.innerHTML = '' + - '' + - ''; - }); - }, - tokens: { - 'angle': /(?:\b|\B-|(?=\B\.))\d*\.?\d+(?:deg|g?rad|turn)\b/i - }, - languages: { - 'css': true, - 'less': true, - 'markup': { - lang: 'markup', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.markup && Prism.languages.markup['tag'].inside['attr-value'] - }, - 'sass': [ - { - lang: 'sass', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['property-line'] - }, - { - lang: 'sass', - before: 'operator', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['variable-line'] - } - ], - 'scss': true, - 'stylus': [ - { - lang: 'stylus', - before: 'func', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['property-declaration'].inside - }, - { - lang: 'stylus', - before: 'func', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['variable-declaration'].inside - } - ] - } - }, - 'color': { - create: function () { - new Prism.plugins.Previewer('color', function(value) { - this.style.backgroundColor = ''; - this.style.backgroundColor = value; - return !!this.style.backgroundColor; - }); - }, - tokens: { - 'color': { - pattern: /\B#(?:[0-9a-f]{3}){1,2}\b|\b(?:rgb|hsl)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:rgb|hsl)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B|\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGray|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGray|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGray|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gray|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGray|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGray|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGray|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i, - inside: { - 'function': /[\w-]+(?=\()/, - 'punctuation': /[(),]/ - } - } - }, - languages: { - 'css': true, - 'less': true, - 'markup': { - lang: 'markup', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.markup && Prism.languages.markup['tag'].inside['attr-value'] - }, - 'sass': [ - { - lang: 'sass', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['variable-line'] - }, - { - lang: 'sass', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['property-line'] - } - ], - 'scss': true, - 'stylus': [ - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['property-declaration'].inside - }, - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['variable-declaration'].inside - } - ] - } - }, - 'easing': { - create: function () { - new Prism.plugins.Previewer('easing', function (value) { - - value = { - 'linear': '0,0,1,1', - 'ease': '.25,.1,.25,1', - 'ease-in': '.42,0,1,1', - 'ease-out': '0,0,.58,1', - 'ease-in-out':'.42,0,.58,1' - }[value] || value; - - var p = value.match(/-?\d*\.?\d+/g); - - if(p.length === 4) { - p = p.map(function(p, i) { return (i % 2? 1 - p : p) * 100; }); - - this.querySelector('path').setAttribute('d', 'M0,100 C' + p[0] + ',' + p[1] + ', ' + p[2] + ',' + p[3] + ', 100,0'); - - var lines = this.querySelectorAll('line'); - lines[0].setAttribute('x2', p[0]); - lines[0].setAttribute('y2', p[1]); - lines[1].setAttribute('x2', p[2]); - lines[1].setAttribute('y2', p[3]); - - return true; - } - - return false; - }, '*', function () { - this._elt.innerHTML = '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''; - }); - }, - tokens: { - 'easing': { - pattern: /\bcubic-bezier\((?:-?\d*\.?\d+,\s*){3}-?\d*\.?\d+\)\B|\b(?:linear|ease(?:-in)?(?:-out)?)(?=\s|[;}]|$)/i, - inside: { - 'function': /[\w-]+(?=\()/, - 'punctuation': /[(),]/ - } - } - }, - languages: { - 'css': true, - 'less': true, - 'sass': [ - { - lang: 'sass', - inside: 'inside', - before: 'punctuation', - root: Prism.languages.sass && Prism.languages.sass['variable-line'] - }, - { - lang: 'sass', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['property-line'] - } - ], - 'scss': true, - 'stylus': [ - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['property-declaration'].inside - }, - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['variable-declaration'].inside - } - ] - } - }, - - 'time': { - create: function () { - new Prism.plugins.Previewer('time', function(value) { - var num = parseFloat(value); - var unit = value.match(/[a-z]+$/i); - if (!num || !unit) { - return false; - } - unit = unit[0]; - this.querySelector('circle').style.animationDuration = 2 * num + unit; - return true; - }, '*', function () { - this._elt.innerHTML = '' + - '' + - ''; - }); - }, - tokens: { - 'time': /(?:\b|\B-|(?=\B\.))\d*\.?\d+m?s\b/i - }, - languages: { - 'css': true, - 'less': true, - 'markup': { - lang: 'markup', - before: 'punctuation', - inside: 'inside', - root: Prism.languages.markup && Prism.languages.markup['tag'].inside['attr-value'] - }, - 'sass': [ - { - lang: 'sass', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['property-line'] - }, - { - lang: 'sass', - before: 'operator', - inside: 'inside', - root: Prism.languages.sass && Prism.languages.sass['variable-line'] - } - ], - 'scss': true, - 'stylus': [ - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['property-declaration'].inside - }, - { - lang: 'stylus', - before: 'hexcode', - inside: 'rest', - root: Prism.languages.stylus && Prism.languages.stylus['variable-declaration'].inside - } - ] - } - } - }; - - /** - * Returns the absolute X, Y offsets for an element - * @param {HTMLElement} element - * @returns {{top: number, right: number, bottom: number, left: number, width: number, height: number}} - */ - var getOffset = function (element) { - var elementBounds = element.getBoundingClientRect(); - var left = elementBounds.left; - var top = elementBounds.top; - var documentBounds = document.documentElement.getBoundingClientRect(); - left -= documentBounds.left; - top -= documentBounds.top; - - return { - top: top, - right: innerWidth - left - elementBounds.width, - bottom: innerHeight - top - elementBounds.height, - left: left, - width: elementBounds.width, - height: elementBounds.height - }; - }; - - var tokenRegexp = /(?:^|\s)token(?=$|\s)/; - var activeRegexp = /(?:^|\s)active(?=$|\s)/g; - var flippedRegexp = /(?:^|\s)flipped(?=$|\s)/g; - - /** - * Previewer constructor - * @param {string} type Unique previewer type - * @param {function} updater Function that will be called on mouseover. - * @param {string[]|string=} supportedLanguages Aliases of the languages this previewer must be enabled for. Defaults to "*", all languages. - * @param {function=} initializer Function that will be called on initialization. - * @constructor - */ - var Previewer = function (type, updater, supportedLanguages, initializer) { - this._elt = null; - this._type = type; - this._clsRegexp = RegExp('(?:^|\\s)' + type + '(?=$|\\s)'); - this._token = null; - this.updater = updater; - this._mouseout = this.mouseout.bind(this); - this.initializer = initializer; - - var self = this; - - if (!supportedLanguages) { - supportedLanguages = ['*']; - } - if (Prism.util.type(supportedLanguages) !== 'Array') { - supportedLanguages = [supportedLanguages]; - } - supportedLanguages.forEach(function (lang) { - if (typeof lang !== 'string') { - lang = lang.lang; - } - if (!Previewer.byLanguages[lang]) { - Previewer.byLanguages[lang] = []; - } - if (Previewer.byLanguages[lang].indexOf(self) < 0) { - Previewer.byLanguages[lang].push(self); - } - }); - Previewer.byType[type] = this; - }; - - /** - * Creates the HTML element for the previewer. - */ - Previewer.prototype.init = function () { - if (this._elt) { - return; - } - this._elt = document.createElement('div'); - this._elt.className = 'prism-previewer prism-previewer-' + this._type; - document.body.appendChild(this._elt); - if(this.initializer) { - this.initializer(); - } - }; - - Previewer.prototype.isDisabled = function (token) { - do { - if (token.hasAttribute && token.hasAttribute('data-previewers')) { - var previewers = token.getAttribute('data-previewers'); - return (previewers || '').split(/\s+/).indexOf(this._type) === -1; - } - } while(token = token.parentNode); - return false; - }; - - /** - * Checks the class name of each hovered element - * @param token - */ - Previewer.prototype.check = function (token) { - if (tokenRegexp.test(token.className) && this.isDisabled(token)) { - return; - } - do { - if (tokenRegexp.test(token.className) && this._clsRegexp.test(token.className)) { - break; - } - } while(token = token.parentNode); - - if (token && token !== this._token) { - this._token = token; - this.show(); - } - }; - - /** - * Called on mouseout - */ - Previewer.prototype.mouseout = function() { - this._token.removeEventListener('mouseout', this._mouseout, false); - this._token = null; - this.hide(); - }; - - /** - * Shows the previewer positioned properly for the current token. - */ - Previewer.prototype.show = function () { - if (!this._elt) { - this.init(); - } - if (!this._token) { - return; - } - - if (this.updater.call(this._elt, this._token.textContent)) { - this._token.addEventListener('mouseout', this._mouseout, false); - - var offset = getOffset(this._token); - this._elt.className += ' active'; - - if (offset.top - this._elt.offsetHeight > 0) { - this._elt.className = this._elt.className.replace(flippedRegexp, ''); - this._elt.style.top = offset.top + 'px'; - this._elt.style.bottom = ''; - } else { - this._elt.className += ' flipped'; - this._elt.style.bottom = offset.bottom + 'px'; - this._elt.style.top = ''; - } - - this._elt.style.left = offset.left + Math.min(200, offset.width / 2) + 'px'; - } else { - this.hide(); - } - }; - - /** - * Hides the previewer. - */ - Previewer.prototype.hide = function () { - this._elt.className = this._elt.className.replace(activeRegexp, ''); - }; - - /** - * Map of all registered previewers by language - * @type {{}} - */ - Previewer.byLanguages = {}; - - /** - * Map of all registered previewers by type - * @type {{}} - */ - Previewer.byType = {}; - - /** - * Initializes the mouseover event on the code block. - * @param {HTMLElement} elt The code block (env.element) - * @param {string} lang The language (env.language) - */ - Previewer.initEvents = function (elt, lang) { - var previewers = []; - if (Previewer.byLanguages[lang]) { - previewers = previewers.concat(Previewer.byLanguages[lang]); - } - if (Previewer.byLanguages['*']) { - previewers = previewers.concat(Previewer.byLanguages['*']); - } - elt.addEventListener('mouseover', function (e) { - var target = e.target; - previewers.forEach(function (previewer) { - previewer.check(target); - }); - }, false); - }; - Prism.plugins.Previewer = Previewer; - - Prism.hooks.add('before-highlight', function (env) { - for (var previewer in previewers) { - var languages = previewers[previewer].languages; - if (env.language && languages[env.language] && !languages[env.language].initialized) { - var lang = languages[env.language]; - if (Prism.util.type(lang) !== 'Array') { - lang = [lang]; - } - lang.forEach(function (lang) { - var before, inside, root, skip; - if (lang === true) { - before = 'important'; - inside = env.language; - lang = env.language; - } else { - before = lang.before || 'important'; - inside = lang.inside || lang.lang; - root = lang.root || Prism.languages; - skip = lang.skip; - lang = env.language; - } - - if (!skip && Prism.languages[lang]) { - Prism.languages.insertBefore(inside, before, previewers[previewer].tokens, root); - env.grammar = Prism.languages[lang]; - - languages[env.language] = {initialized: true}; - } - }); - } - } - }); - - // Initialize the previewers only when needed - Prism.hooks.add('after-highlight', function (env) { - if(Previewer.byLanguages['*'] || Previewer.byLanguages[env.language]) { - Previewer.initEvents(env.element, env.language); - } - }); - - for (var previewer in previewers) { - previewers[previewer].create(); - } - -}()); diff --git a/docs/_style/prism-master/plugins/previewers/prism-previewers.min.js b/docs/_style/prism-master/plugins/previewers/prism-previewers.min.js deleted file mode 100644 index 477d4e52..00000000 --- a/docs/_style/prism-master/plugins/previewers/prism-previewers.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if(("undefined"==typeof self||self.Prism)&&self.document&&Function.prototype.bind){var e={gradient:{create:function(){var e={},s=function(e,s,i){var t="180deg";return/^(?:-?\d*\.?\d+(?:deg|rad)|to\b|top|right|bottom|left)/.test(i[0])&&(t=i.shift(),t.indexOf("to ")<0&&(t.indexOf("top")>=0?t=t.indexOf("left")>=0?"to bottom right":t.indexOf("right")>=0?"to bottom left":"to bottom":t.indexOf("bottom")>=0?t=t.indexOf("left")>=0?"to top right":t.indexOf("right")>=0?"to top left":"to top":t.indexOf("left")>=0?t="to right":t.indexOf("right")>=0?t="to left":e&&(t.indexOf("deg")>=0?t=90-parseFloat(t)+"deg":t.indexOf("rad")>=0&&(t=Math.PI/2-parseFloat(t)+"rad")))),s+"("+t+","+i.join(",")+")"},i=function(e,s,i){if(i[0].indexOf("at")<0){var t="center",a="ellipse",r="farthest-corner";if(/\bcenter|top|right|bottom|left\b|^\d+/.test(i[0])&&(t=i.shift().replace(/\s*-?\d+(?:rad|deg)\s*/,"")),/\bcircle|ellipse|closest|farthest|contain|cover\b/.test(i[0])){var n=i.shift().split(/\s+/);!n[0]||"circle"!==n[0]&&"ellipse"!==n[0]||(a=n.shift()),n[0]&&(r=n.shift()),"cover"===r?r="farthest-corner":"contain"===r&&(r="clothest-side")}return s+"("+a+" "+r+" at "+t+","+i.join(",")+")"}return s+"("+i.join(",")+")"},t=function(t){if(e[t])return e[t];var a=t.match(/^(\b|\B-[a-z]{1,10}-)((?:repeating-)?(?:linear|radial)-gradient)/),r=a&&a[1],n=a&&a[2],l=t.replace(/^(?:\b|\B-[a-z]{1,10}-)(?:repeating-)?(?:linear|radial)-gradient\(|\)$/g,"").split(/\s*,\s*/);return e[t]=n.indexOf("linear")>=0?s(r,n,l):n.indexOf("radial")>=0?i(r,n,l):n+"("+l.join(",")+")"};return function(){new Prism.plugins.Previewer("gradient",function(e){return this.firstChild.style.backgroundImage="",this.firstChild.style.backgroundImage=t(e),!!this.firstChild.style.backgroundImage},"*",function(){this._elt.innerHTML="
        "})}}(),tokens:{gradient:{pattern:/(?:\b|\B-[a-z]{1,10}-)(?:repeating-)?(?:linear|radial)-gradient\((?:(?:rgb|hsl)a?\(.+?\)|[^\)])+\)/gi,inside:{"function":/[\w-]+(?=\()/,punctuation:/[(),]/}}},languages:{css:!0,less:!0,sass:[{lang:"sass",before:"punctuation",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["variable-line"]},{lang:"sass",before:"punctuation",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["property-line"]}],scss:!0,stylus:[{lang:"stylus",before:"func",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["property-declaration"].inside},{lang:"stylus",before:"func",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["variable-declaration"].inside}]}},angle:{create:function(){new Prism.plugins.Previewer("angle",function(e){var s,i,t=parseFloat(e),a=e.match(/[a-z]+$/i);if(!t||!a)return!1;switch(a=a[0]){case"deg":s=360;break;case"grad":s=400;break;case"rad":s=2*Math.PI;break;case"turn":s=1}return i=100*t/s,i%=100,this[(0>t?"set":"remove")+"Attribute"]("data-negative",""),this.querySelector("circle").style.strokeDasharray=Math.abs(i)+",500",!0},"*",function(){this._elt.innerHTML=''})},tokens:{angle:/(?:\b|\B-|(?=\B\.))\d*\.?\d+(?:deg|g?rad|turn)\b/i},languages:{css:!0,less:!0,markup:{lang:"markup",before:"punctuation",inside:"inside",root:Prism.languages.markup&&Prism.languages.markup.tag.inside["attr-value"]},sass:[{lang:"sass",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["property-line"]},{lang:"sass",before:"operator",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["variable-line"]}],scss:!0,stylus:[{lang:"stylus",before:"func",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["property-declaration"].inside},{lang:"stylus",before:"func",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["variable-declaration"].inside}]}},color:{create:function(){new Prism.plugins.Previewer("color",function(e){return this.style.backgroundColor="",this.style.backgroundColor=e,!!this.style.backgroundColor})},tokens:{color:{pattern:/\B#(?:[0-9a-f]{3}){1,2}\b|\b(?:rgb|hsl)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:rgb|hsl)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B|\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGray|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGray|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGray|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gray|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGray|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGray|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGray|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,inside:{"function":/[\w-]+(?=\()/,punctuation:/[(),]/}}},languages:{css:!0,less:!0,markup:{lang:"markup",before:"punctuation",inside:"inside",root:Prism.languages.markup&&Prism.languages.markup.tag.inside["attr-value"]},sass:[{lang:"sass",before:"punctuation",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["variable-line"]},{lang:"sass",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["property-line"]}],scss:!0,stylus:[{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["property-declaration"].inside},{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["variable-declaration"].inside}]}},easing:{create:function(){new Prism.plugins.Previewer("easing",function(e){e={linear:"0,0,1,1",ease:".25,.1,.25,1","ease-in":".42,0,1,1","ease-out":"0,0,.58,1","ease-in-out":".42,0,.58,1"}[e]||e;var s=e.match(/-?\d*\.?\d+/g);if(4===s.length){s=s.map(function(e,s){return 100*(s%2?1-e:e)}),this.querySelector("path").setAttribute("d","M0,100 C"+s[0]+","+s[1]+", "+s[2]+","+s[3]+", 100,0");var i=this.querySelectorAll("line");return i[0].setAttribute("x2",s[0]),i[0].setAttribute("y2",s[1]),i[1].setAttribute("x2",s[2]),i[1].setAttribute("y2",s[3]),!0}return!1},"*",function(){this._elt.innerHTML=''})},tokens:{easing:{pattern:/\bcubic-bezier\((?:-?\d*\.?\d+,\s*){3}-?\d*\.?\d+\)\B|\b(?:linear|ease(?:-in)?(?:-out)?)(?=\s|[;}]|$)/i,inside:{"function":/[\w-]+(?=\()/,punctuation:/[(),]/}}},languages:{css:!0,less:!0,sass:[{lang:"sass",inside:"inside",before:"punctuation",root:Prism.languages.sass&&Prism.languages.sass["variable-line"]},{lang:"sass",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["property-line"]}],scss:!0,stylus:[{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["property-declaration"].inside},{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["variable-declaration"].inside}]}},time:{create:function(){new Prism.plugins.Previewer("time",function(e){var s=parseFloat(e),i=e.match(/[a-z]+$/i);return s&&i?(i=i[0],this.querySelector("circle").style.animationDuration=2*s+i,!0):!1},"*",function(){this._elt.innerHTML=''})},tokens:{time:/(?:\b|\B-|(?=\B\.))\d*\.?\d+m?s\b/i},languages:{css:!0,less:!0,markup:{lang:"markup",before:"punctuation",inside:"inside",root:Prism.languages.markup&&Prism.languages.markup.tag.inside["attr-value"]},sass:[{lang:"sass",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["property-line"]},{lang:"sass",before:"operator",inside:"inside",root:Prism.languages.sass&&Prism.languages.sass["variable-line"]}],scss:!0,stylus:[{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["property-declaration"].inside},{lang:"stylus",before:"hexcode",inside:"rest",root:Prism.languages.stylus&&Prism.languages.stylus["variable-declaration"].inside}]}}},s=function(e){var s=e.getBoundingClientRect(),i=s.left,t=s.top,a=document.documentElement.getBoundingClientRect();return i-=a.left,t-=a.top,{top:t,right:innerWidth-i-s.width,bottom:innerHeight-t-s.height,left:i,width:s.width,height:s.height}},i=/(?:^|\s)token(?=$|\s)/,t=/(?:^|\s)active(?=$|\s)/g,a=/(?:^|\s)flipped(?=$|\s)/g,r=function(e,s,i,t){this._elt=null,this._type=e,this._clsRegexp=RegExp("(?:^|\\s)"+e+"(?=$|\\s)"),this._token=null,this.updater=s,this._mouseout=this.mouseout.bind(this),this.initializer=t;var a=this;i||(i=["*"]),"Array"!==Prism.util.type(i)&&(i=[i]),i.forEach(function(e){"string"!=typeof e&&(e=e.lang),r.byLanguages[e]||(r.byLanguages[e]=[]),r.byLanguages[e].indexOf(a)<0&&r.byLanguages[e].push(a)}),r.byType[e]=this};r.prototype.init=function(){this._elt||(this._elt=document.createElement("div"),this._elt.className="prism-previewer prism-previewer-"+this._type,document.body.appendChild(this._elt),this.initializer&&this.initializer())},r.prototype.isDisabled=function(e){do if(e.hasAttribute&&e.hasAttribute("data-previewers")){var s=e.getAttribute("data-previewers");return-1===(s||"").split(/\s+/).indexOf(this._type)}while(e=e.parentNode);return!1},r.prototype.check=function(e){if(!i.test(e.className)||!this.isDisabled(e)){do if(i.test(e.className)&&this._clsRegexp.test(e.className))break;while(e=e.parentNode);e&&e!==this._token&&(this._token=e,this.show())}},r.prototype.mouseout=function(){this._token.removeEventListener("mouseout",this._mouseout,!1),this._token=null,this.hide()},r.prototype.show=function(){if(this._elt||this.init(),this._token)if(this.updater.call(this._elt,this._token.textContent)){this._token.addEventListener("mouseout",this._mouseout,!1);var e=s(this._token);this._elt.className+=" active",e.top-this._elt.offsetHeight>0?(this._elt.className=this._elt.className.replace(a,""),this._elt.style.top=e.top+"px",this._elt.style.bottom=""):(this._elt.className+=" flipped",this._elt.style.bottom=e.bottom+"px",this._elt.style.top=""),this._elt.style.left=e.left+Math.min(200,e.width/2)+"px"}else this.hide()},r.prototype.hide=function(){this._elt.className=this._elt.className.replace(t,"")},r.byLanguages={},r.byType={},r.initEvents=function(e,s){var i=[];r.byLanguages[s]&&(i=i.concat(r.byLanguages[s])),r.byLanguages["*"]&&(i=i.concat(r.byLanguages["*"])),e.addEventListener("mouseover",function(e){var s=e.target;i.forEach(function(e){e.check(s)})},!1)},Prism.plugins.Previewer=r,Prism.hooks.add("before-highlight",function(s){for(var i in e){var t=e[i].languages;if(s.language&&t[s.language]&&!t[s.language].initialized){var a=t[s.language];"Array"!==Prism.util.type(a)&&(a=[a]),a.forEach(function(a){var r,n,l,o;a===!0?(r="important",n=s.language,a=s.language):(r=a.before||"important",n=a.inside||a.lang,l=a.root||Prism.languages,o=a.skip,a=s.language),!o&&Prism.languages[a]&&(Prism.languages.insertBefore(n,r,e[i].tokens,l),s.grammar=Prism.languages[a],t[s.language]={initialized:!0})})}}}),Prism.hooks.add("after-highlight",function(e){(r.byLanguages["*"]||r.byLanguages[e.language])&&r.initEvents(e.element,e.language)});for(var n in e)e[n].create()}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/remove-initial-line-feed/index.html b/docs/_style/prism-master/plugins/remove-initial-line-feed/index.html deleted file mode 100644 index 8eb942fa..00000000 --- a/docs/_style/prism-master/plugins/remove-initial-line-feed/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - Remove initial line feed ▲ Prism plugins - - - - - - - - - - -
        -
        - -

        Remove initial line feed

        -

        Removes the initial line feed in code blocks.

        -
        - -
        -

        How to use (DEPRECATED)

        - -

        This plugin will be removed in the future. Please use the general purpose Normalize Whitespace plugin instead.

        -

        Obviously, this is supposed to work only for code blocks (<pre><code>) and not for inline code.

        -

        With this plugin included, any initial line feed will be removed by default.

        -

        To bypass this behaviour, you may add the class keep-initial-line-feed to your desired <pre>.

        -
        - -
        -

        Examples

        - -

        Without adding the class

        -
        
        -<div></div>
        -
        - -

        With the class added

        -
        
        -<div></div>
        -
        - -
        - -
        - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.js b/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.js deleted file mode 100644 index f62a8bfe..00000000 --- a/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.js +++ /dev/null @@ -1,21 +0,0 @@ -(function() { - -if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; -} - -Prism.hooks.add('before-sanity-check', function (env) { - if (env.code) { - var pre = env.element.parentNode; - var clsReg = /\s*\bkeep-initial-line-feed\b\s*/; - if ( - pre && pre.nodeName.toLowerCase() === 'pre' && - // Apply only if nor the
         or the  have the class
        -			(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
        -		) {
        -			env.code = env.code.replace(/^(?:\r?\n|\r)/, '');
        -		}
        -	}
        -});
        -
        -}());
        \ No newline at end of file
        diff --git a/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.min.js b/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.min.js
        deleted file mode 100644
        index e0016a49..00000000
        --- a/docs/_style/prism-master/plugins/remove-initial-line-feed/prism-remove-initial-line-feed.min.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("before-sanity-check",function(e){if(e.code){var s=e.element.parentNode,n=/\s*\bkeep-initial-line-feed\b\s*/;!s||"pre"!==s.nodeName.toLowerCase()||n.test(s.className)||n.test(e.element.className)||(e.code=e.code.replace(/^(?:\r?\n|\r)/,""))}})}();
        \ No newline at end of file
        diff --git a/docs/_style/prism-master/plugins/show-invisibles/index.html b/docs/_style/prism-master/plugins/show-invisibles/index.html
        deleted file mode 100644
        index cb0ad436..00000000
        --- a/docs/_style/prism-master/plugins/show-invisibles/index.html
        +++ /dev/null
        @@ -1,46 +0,0 @@
        -
        -
        -
        -
        -
        -
        -Show Invisibles ▲ Prism plugins
        -
        -
        -
        -
        -
        -
        -
        -
        -
        -
        -
        -
        -
        - -

        Show Invisibles

        -

        Show hidden characters such as tabs and line breaks.

        -
        - -
        -

        Examples

        - -
        
        -	
        -	
        
        -	
        -	
        
        -
        - -
        - - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.css b/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.css deleted file mode 100644 index c57be588..00000000 --- a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.css +++ /dev/null @@ -1,34 +0,0 @@ -.token.tab:not(:empty), -.token.cr, -.token.lf, -.token.space { - position: relative; -} - -.token.tab:not(:empty):before, -.token.cr:before, -.token.lf:before, -.token.space:before { - color: inherit; - opacity: 0.4; - position: absolute; -} - -.token.tab:not(:empty):before { - content: '\21E5'; -} - -.token.cr:before { - content: '\240D'; -} - -.token.crlf:before { - content: '\240D\240A'; -} -.token.lf:before { - content: '\240A'; -} - -.token.space:before { - content: '\00B7'; -} diff --git a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.js b/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.js deleted file mode 100644 index f3d120a9..00000000 --- a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.js +++ /dev/null @@ -1,21 +0,0 @@ -(function(){ - -if ( - typeof self !== 'undefined' && !self.Prism || - typeof global !== 'undefined' && !global.Prism -) { - return; -} - -Prism.hooks.add('before-highlight', function(env) { - var tokens = env.grammar; - - if (!tokens) return; - - tokens.tab = /\t/g; - tokens.crlf = /\r\n/g; - tokens.lf = /\n/g; - tokens.cr = /\r/g; - tokens.space = / /g; -}); -})(); diff --git a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.min.js b/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.min.js deleted file mode 100644 index 9df7258c..00000000 --- a/docs/_style/prism-master/plugins/show-invisibles/prism-show-invisibles.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"undefined"!=typeof self&&!self.Prism||"undefined"!=typeof global&&!global.Prism||Prism.hooks.add("before-highlight",function(e){var f=e.grammar;f&&(f.tab=/\t/g,f.crlf=/\r\n/g,f.lf=/\n/g,f.cr=/\r/g,f.space=/ /g)})}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/show-language/index.html b/docs/_style/prism-master/plugins/show-language/index.html deleted file mode 100644 index e79b1ccf..00000000 --- a/docs/_style/prism-master/plugins/show-language/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - -Show Language ▲ Prism plugins - - - - - - - - - - - -
        -
        - -

        Show Language

        -

        Display the highlighted language in code blocks (inline code does not show the label).

        -
        - -
        -

        Examples

        - -

        JavaScript

        -
        
        -
        -	

        CSS

        -
        
        -
        -	

        HTML (Markup)

        -
        
        -
        -	

        SVG

        -

        The data-language attribute can be used to display a specific label whether it has been defined as a language or not.

        -
        
        -
        - -
        - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/show-language/prism-show-language.js b/docs/_style/prism-master/plugins/show-language/prism-show-language.js deleted file mode 100644 index 38675bb6..00000000 --- a/docs/_style/prism-master/plugins/show-language/prism-show-language.js +++ /dev/null @@ -1,31 +0,0 @@ -(function(){ - -if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; -} - -if (!Prism.plugins.toolbar) { - console.warn('Show Languages plugin loaded before Toolbar plugin.'); - - return; -} - -// The languages map is built automatically with gulp -var Languages = /*languages_placeholder[*/{"html":"HTML","xml":"XML","svg":"SVG","mathml":"MathML","css":"CSS","clike":"C-like","javascript":"JavaScript","abap":"ABAP","actionscript":"ActionScript","apacheconf":"Apache Configuration","apl":"APL","applescript":"AppleScript","arff":"ARFF","asciidoc":"AsciiDoc","asm6502":"6502 Assembly","aspnet":"ASP.NET (C#)","autohotkey":"AutoHotkey","autoit":"AutoIt","shell":"Shell","basic":"BASIC","csharp":"C#","cpp":"C++","cil":"CIL","coffeescript":"CoffeeScript","csp":"Content-Security-Policy","css-extras":"CSS Extras","django":"Django/Jinja2","erb":"ERB","fsharp":"F#","gcode":"G-code","gedcom":"GEDCOM","glsl":"GLSL","gml":"GameMaker Language","graphql":"GraphQL","http":"HTTP","hpkp":"HTTP Public-Key-Pins","hsts":"HTTP Strict-Transport-Security","ichigojam":"IchigoJam","inform7":"Inform 7","javastacktrace":"Java stack trace","json":"JSON","jsonp":"JSONP","latex":"LaTeX","livescript":"LiveScript","lolcode":"LOLCODE","markup-templating":"Markup templating","matlab":"MATLAB","mel":"MEL","n4js":"N4JS","nasm":"NASM","nginx":"nginx","nsis":"NSIS","objectivec":"Objective-C","ocaml":"OCaml","opencl":"OpenCL","parigp":"PARI/GP","objectpascal":"Object Pascal","php":"PHP","php-extras":"PHP Extras","plsql":"PL/SQL","powershell":"PowerShell","properties":".properties","protobuf":"Protocol Buffers","q":"Q (kdb+ database)","jsx":"React JSX","tsx":"React TSX","renpy":"Ren'py","rest":"reST (reStructuredText)","sas":"SAS","sass":"Sass (Sass)","scss":"Sass (Scss)","sql":"SQL","soy":"Soy (Closure Template)","tap":"TAP","toml":"TOML","tt2":"Template Toolkit 2","typescript":"TypeScript","vbnet":"VB.Net","vhdl":"VHDL","vim":"vim","visual-basic":"Visual Basic","wasm":"WebAssembly","wiki":"Wiki markup","xeoracube":"XeoraCube","xojo":"Xojo (REALbasic)","xquery":"XQuery","yaml":"YAML"}/*]*/; -Prism.plugins.toolbar.registerButton('show-language', function(env) { - var pre = env.element.parentNode; - if (!pre || !/pre/i.test(pre.nodeName)) { - return; - } - var language = pre.getAttribute('data-language') || Languages[env.language] || (env.language && (env.language.substring(0, 1).toUpperCase() + env.language.substring(1))); - - if(!language) { - return; - } - var element = document.createElement('span'); - element.textContent = language; - - return element; -}); - -})(); diff --git a/docs/_style/prism-master/plugins/show-language/prism-show-language.min.js b/docs/_style/prism-master/plugins/show-language/prism-show-language.min.js deleted file mode 100644 index 7f4dd60f..00000000 --- a/docs/_style/prism-master/plugins/show-language/prism-show-language.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){if(!Prism.plugins.toolbar)return console.warn("Show Languages plugin loaded before Toolbar plugin."),void 0;var e={html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",css:"CSS",clike:"C-like",javascript:"JavaScript",abap:"ABAP",actionscript:"ActionScript",apacheconf:"Apache Configuration",apl:"APL",applescript:"AppleScript",arff:"ARFF",asciidoc:"AsciiDoc",asm6502:"6502 Assembly",aspnet:"ASP.NET (C#)",autohotkey:"AutoHotkey",autoit:"AutoIt",shell:"Shell",basic:"BASIC",csharp:"C#",cpp:"C++",cil:"CIL",coffeescript:"CoffeeScript",csp:"Content-Security-Policy","css-extras":"CSS Extras",django:"Django/Jinja2",erb:"ERB",fsharp:"F#",gcode:"G-code",gedcom:"GEDCOM",glsl:"GLSL",gml:"GameMaker Language",graphql:"GraphQL",http:"HTTP",hpkp:"HTTP Public-Key-Pins",hsts:"HTTP Strict-Transport-Security",ichigojam:"IchigoJam",inform7:"Inform 7",javastacktrace:"Java stack trace",json:"JSON",jsonp:"JSONP",latex:"LaTeX",livescript:"LiveScript",lolcode:"LOLCODE","markup-templating":"Markup templating",matlab:"MATLAB",mel:"MEL",n4js:"N4JS",nasm:"NASM",nginx:"nginx",nsis:"NSIS",objectivec:"Objective-C",ocaml:"OCaml",opencl:"OpenCL",parigp:"PARI/GP",objectpascal:"Object Pascal",php:"PHP","php-extras":"PHP Extras",plsql:"PL/SQL",powershell:"PowerShell",properties:".properties",protobuf:"Protocol Buffers",q:"Q (kdb+ database)",jsx:"React JSX",tsx:"React TSX",renpy:"Ren'py",rest:"reST (reStructuredText)",sas:"SAS",sass:"Sass (Sass)",scss:"Sass (Scss)",sql:"SQL",soy:"Soy (Closure Template)",tap:"TAP",toml:"TOML",tt2:"Template Toolkit 2",typescript:"TypeScript",vbnet:"VB.Net",vhdl:"VHDL",vim:"vim","visual-basic":"Visual Basic",wasm:"WebAssembly",wiki:"Wiki markup",xeoracube:"XeoraCube",xojo:"Xojo (REALbasic)",xquery:"XQuery",yaml:"YAML"};Prism.plugins.toolbar.registerButton("show-language",function(a){var t=a.element.parentNode;if(t&&/pre/i.test(t.nodeName)){var s=t.getAttribute("data-language")||e[a.language]||a.language&&a.language.substring(0,1).toUpperCase()+a.language.substring(1);if(s){var r=document.createElement("span");return r.textContent=s,r}}})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/toolbar/index.html b/docs/_style/prism-master/plugins/toolbar/index.html deleted file mode 100644 index 5547c5de..00000000 --- a/docs/_style/prism-master/plugins/toolbar/index.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - Toolbar ▲ Prism plugins - - - - - - - - - - - -
        -
        - -

        Toolbar

        -

        Attach a toolbar for plugins to easily register buttons on the top of a code block.

        -
        - -
        -

        How to use

        -

        The Toolbar plugin allows for several methods to register your button, using the Prism.plugins.toolbar.registerButton function.

        - -

        The simplest method is through the HTML API. Add a data-label attribute to the pre element, and the Toolbar - plugin will read the value of that attribute and append a label to the code snippet.

        - -
        <pre data-src="plugins/toolbar/prism-toolbar.js" data-label="Hello World!"></pre>
        - -

        If you want to provide arbitrary HTML to the label, create a template element with the HTML you want in the label, and provide the - template element's id to data-label. The Toolbar plugin will use the template's content for the button. - You can also use to declare your event handlers inline:

        - -
        <pre data-src="plugins/toolbar/prism-toolbar.js" data-label="my-label-button"></pre>
        - -
        <template id="my-label-button"><button onclick="console.log('This is an inline-handler');">My button</button></template>
        - -

        For more flexibility, the Toolbar exposes a JavaScript function that can be used to register new buttons or labels to the Toolbar, - Prism.plugins.toolbar.registerButton.

        - -

        The function accepts a key for the button and an object with a text property string and an optional - onClick function or url string. The onClick function will be called when the button is clicked, while the - url property will be set to the anchor tag's href.

        - -
        Prism.plugins.toolbar.registerButton('hello-world', {
        -	text: 'Hello World!', // required
        -	onClick: function (env) { // optional
        -		alert('This code snippet is written in ' + env.language + '.');
        -	}
        -});
        - -

        See how the above code registers the Hello World! button? You can use this in your plugins to register your own buttons with the toolbar.

        - -

        If you need more control, you can provide a function to registerButton that returns either a span, a, or - button element.

        - -
        Prism.plugins.toolbar.registerButton('select-code', function() {
        -	var button = document.createElement('button');
        -	button.innerHTML = 'Select Code';
        -
        -	button.addEventListener('click', function () {
        -		// Source: http://stackoverflow.com/a/11128179/2757940
        -		if (document.body.createTextRange) { // ms
        -			var range = document.body.createTextRange();
        -			range.moveToElementText(env.element);
        -			range.select();
        -		} else if (window.getSelection) { // moz, opera, webkit
        -			var selection = window.getSelection();
        -			var range = document.createRange();
        -			range.selectNodeContents(env.element);
        -			selection.removeAllRanges();
        -			selection.addRange(range);
        -		}
        -	});
        -
        -	return button;
        -});
        - -

        The above function creates the Select Code button you see, and when you click it, the code gets highlighted.

        - -

        By default, the buttons will be added to the code snippet in the order they were registered. If more control over - the order is needed, an HTML attribute can be added to the body tag with a comma-separated string indicating the - order.

        - -
        <body data-toolbar-order="select-code,hello-world,label">
        -
        - -
        - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.css b/docs/_style/prism-master/plugins/toolbar/prism-toolbar.css deleted file mode 100644 index 2b234e19..00000000 --- a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.css +++ /dev/null @@ -1,58 +0,0 @@ -div.code-toolbar { - position: relative; -} - -div.code-toolbar > .toolbar { - position: absolute; - top: .3em; - right: .2em; - transition: opacity 0.3s ease-in-out; - opacity: 0; -} - -div.code-toolbar:hover > .toolbar { - opacity: 1; -} - -div.code-toolbar > .toolbar .toolbar-item { - display: inline-block; -} - -div.code-toolbar > .toolbar a { - cursor: pointer; -} - -div.code-toolbar > .toolbar button { - background: none; - border: 0; - color: inherit; - font: inherit; - line-height: normal; - overflow: visible; - padding: 0; - -webkit-user-select: none; /* for button */ - -moz-user-select: none; - -ms-user-select: none; -} - -div.code-toolbar > .toolbar a, -div.code-toolbar > .toolbar button, -div.code-toolbar > .toolbar span { - color: #bbb; - font-size: .8em; - padding: 0 .5em; - background: #f5f2f0; - background: rgba(224, 224, 224, 0.2); - box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); - border-radius: .5em; -} - -div.code-toolbar > .toolbar a:hover, -div.code-toolbar > .toolbar a:focus, -div.code-toolbar > .toolbar button:hover, -div.code-toolbar > .toolbar button:focus, -div.code-toolbar > .toolbar span:hover, -div.code-toolbar > .toolbar span:focus { - color: inherit; - text-decoration: none; -} diff --git a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.js b/docs/_style/prism-master/plugins/toolbar/prism-toolbar.js deleted file mode 100644 index 93294514..00000000 --- a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.js +++ /dev/null @@ -1,137 +0,0 @@ -(function(){ - if (typeof self === 'undefined' || !self.Prism || !self.document) { - return; - } - - var callbacks = []; - var map = {}; - var noop = function() {}; - - Prism.plugins.toolbar = {}; - - /** - * Register a button callback with the toolbar. - * - * @param {string} key - * @param {Object|Function} opts - */ - var registerButton = Prism.plugins.toolbar.registerButton = function (key, opts) { - var callback; - - if (typeof opts === 'function') { - callback = opts; - } else { - callback = function (env) { - var element; - - if (typeof opts.onClick === 'function') { - element = document.createElement('button'); - element.type = 'button'; - element.addEventListener('click', function () { - opts.onClick.call(this, env); - }); - } else if (typeof opts.url === 'string') { - element = document.createElement('a'); - element.href = opts.url; - } else { - element = document.createElement('span'); - } - - element.textContent = opts.text; - - return element; - }; - } - - callbacks.push(map[key] = callback); - }; - - /** - * Post-highlight Prism hook callback. - * - * @param env - */ - var hook = Prism.plugins.toolbar.hook = function (env) { - // Check if inline or actual code block (credit to line-numbers plugin) - var pre = env.element.parentNode; - if (!pre || !/pre/i.test(pre.nodeName)) { - return; - } - - // Autoloader rehighlights, so only do this once. - if (pre.parentNode.classList.contains('code-toolbar')) { - return; - } - - // Create wrapper for
         to prevent scrolling toolbar with content
        -		var wrapper = document.createElement("div");
        -		wrapper.classList.add("code-toolbar");
        -		pre.parentNode.insertBefore(wrapper, pre);
        -		wrapper.appendChild(pre);
        -
        -		// Setup the toolbar
        -		var toolbar = document.createElement('div');
        -		toolbar.classList.add('toolbar');
        -
        -		if (document.body.hasAttribute('data-toolbar-order')) {
        -			callbacks = document.body.getAttribute('data-toolbar-order').split(',').map(function(key) {
        -				return map[key] || noop;
        -			});
        -		}
        -
        -		callbacks.forEach(function(callback) {
        -			var element = callback(env);
        -
        -			if (!element) {
        -				return;
        -			}
        -
        -			var item = document.createElement('div');
        -			item.classList.add('toolbar-item');
        -
        -			item.appendChild(element);
        -			toolbar.appendChild(item);
        -		});
        -
        -		// Add our toolbar to the currently created wrapper of 
         tag
        -		wrapper.appendChild(toolbar);
        -	};
        -
        -	registerButton('label', function(env) {
        -		var pre = env.element.parentNode;
        -		if (!pre || !/pre/i.test(pre.nodeName)) {
        -			return;
        -		}
        -
        -		if (!pre.hasAttribute('data-label')) {
        -			return;
        -		}
        -
        -		var element, template;
        -		var text = pre.getAttribute('data-label');
        -		try {
        -			// Any normal text will blow up this selector.
        -			template = document.querySelector('template#' + text);
        -		} catch (e) {}
        -
        -		if (template) {
        -			element = template.content;
        -		} else {
        -			if (pre.hasAttribute('data-url')) {
        -				element = document.createElement('a');
        -				element.href = pre.getAttribute('data-url');
        -			} else {
        -				element = document.createElement('span');
        -			}
        -
        -			element.textContent = text;
        -		}
        -
        -		return element;
        -	});
        -
        -	/**
        -	 * Register the toolbar with Prism.
        -	 */
        -	Prism.hooks.add('complete', hook);
        -})();
        diff --git a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.min.js b/docs/_style/prism-master/plugins/toolbar/prism-toolbar.min.js
        deleted file mode 100644
        index 17cee962..00000000
        --- a/docs/_style/prism-master/plugins/toolbar/prism-toolbar.min.js
        +++ /dev/null
        @@ -1 +0,0 @@
        -!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var t=[],e={},n=function(){};Prism.plugins.toolbar={};var a=Prism.plugins.toolbar.registerButton=function(n,a){var o;o="function"==typeof a?a:function(t){var e;return"function"==typeof a.onClick?(e=document.createElement("button"),e.type="button",e.addEventListener("click",function(){a.onClick.call(this,t)})):"string"==typeof a.url?(e=document.createElement("a"),e.href=a.url):e=document.createElement("span"),e.textContent=a.text,e},t.push(e[n]=o)},o=Prism.plugins.toolbar.hook=function(a){var o=a.element.parentNode;if(o&&/pre/i.test(o.nodeName)&&!o.parentNode.classList.contains("code-toolbar")){var r=document.createElement("div");r.classList.add("code-toolbar"),o.parentNode.insertBefore(r,o),r.appendChild(o);var i=document.createElement("div");i.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(t=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return e[t]||n})),t.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),i.appendChild(n)}}),r.appendChild(i)}};a("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a,o=e.getAttribute("data-label");try{a=document.querySelector("template#"+o)}catch(r){}return a?n=a.content:(e.hasAttribute("data-url")?(n=document.createElement("a"),n.href=e.getAttribute("data-url")):n=document.createElement("span"),n.textContent=o),n}}),Prism.hooks.add("complete",o)}}();
        \ No newline at end of file
        diff --git a/docs/_style/prism-master/plugins/unescaped-markup/index.html b/docs/_style/prism-master/plugins/unescaped-markup/index.html
        deleted file mode 100644
        index 442ef2aa..00000000
        --- a/docs/_style/prism-master/plugins/unescaped-markup/index.html
        +++ /dev/null
        @@ -1,195 +0,0 @@
        -
        -
        -
        -
        -	
        -	
        -	Unescaped markup ▲ Prism plugins
        -	
        -	
        -	
        -	
        -
        -	
        -
        -	
        -	
        -
        -
        -
        -
        -
        - -

        Unescaped markup

        -

        Write markup without having to escape anything.

        -
        - -
        -

        How to use

        -

        This plugin provides several methods of achieving the same thing:

        - -
          -
        • Instead of using <pre><code> elements, use <script type="text/plain"> -
          <script type="text/plain" class="language-markup">
          -<p>Example</p>
          -</script>
          -
        • -
        • Use a HTML-comment to escape your code -
          <pre class="language-markup"><code><!--
          -<p>Example</p>
          ---></code></pre>
        • -
        -
        - -
        -

        Examples

        - -

        View source to see that the following didn’t need escaping (except for </script>, that does):

        - - - -

        The next example uses the HTML-comment method:

        - -
        -
        - -
        -

        FAQ

        - -

        Why not use the HTML <template> tag?

        - -

        Because it is a PITA to get its textContent and needs to be pointlessly cloned. - Feel free to implement it yourself and send a pull request though, if you are so inclined.

        - -

        Can I use this inline?

        - -

        Not out of the box, because I figured it’s more of a hassle to type <script type="text/plain"> than escape the 1-2 < characters you need to escape in inline code. - Also inline code is not as frequently copy-pasted, which was the major source of annoyance that got me to write this plugin.

        -
        - -
        - - - - - - - - - diff --git a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.css b/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.css deleted file mode 100644 index 3ba2a1e6..00000000 --- a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.css +++ /dev/null @@ -1,10 +0,0 @@ -/* Fallback, in case JS does not run, to ensure the code is at least visible */ -[class*='lang-'] script[type='text/plain'], -[class*='language-'] script[type='text/plain'], -script[type='text/plain'][class*='lang-'], -script[type='text/plain'][class*='language-'] { - display: block; - font: 100% Consolas, Monaco, monospace; - white-space: pre; - overflow: auto; -} diff --git a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.js b/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.js deleted file mode 100644 index 8684ebac..00000000 --- a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.js +++ /dev/null @@ -1,44 +0,0 @@ -(function () { - - if (typeof self === 'undefined' || !self.Prism || !self.document || !Prism.languages.markup) { - return; - } - - Prism.plugins.UnescapedMarkup = true; - - Prism.hooks.add('before-highlightall', function (env) { - env.selector += ", [class*='lang-'] script[type='text/plain'], [class*='language-'] script[type='text/plain']" + - ", script[type='text/plain'][class*='lang-'], script[type='text/plain'][class*='language-']"; - }); - - Prism.hooks.add('before-sanity-check', function (env) { - if ((env.element.matches || env.element.msMatchesSelector).call(env.element, "script[type='text/plain']")) { - var code = document.createElement("code"); - var pre = document.createElement("pre"); - - pre.className = code.className = env.element.className; - - if (env.element.dataset) { - Object.keys(env.element.dataset).forEach(function (key) { - if (Object.prototype.hasOwnProperty.call(env.element.dataset, key)) { - pre.dataset[key] = env.element.dataset[key]; - } - }); - } - - env.code = env.code.replace(/<\/script(>|>)/gi, ""); - code.textContent = env.code; - - pre.appendChild(code); - env.element.parentNode.replaceChild(pre, env.element); - env.element = code; - return; - } - - var pre = env.element.parentNode; - if (!env.code && pre && pre.nodeName.toLowerCase() == 'pre' && - env.element.childNodes.length && env.element.childNodes[0].nodeName == "#comment") { - env.element.textContent = env.code = env.element.childNodes[0].textContent; - } - }); -}()); diff --git a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.min.js b/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.min.js deleted file mode 100644 index f48514a3..00000000 --- a/docs/_style/prism-master/plugins/unescaped-markup/prism-unescaped-markup.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.languages.markup&&(Prism.plugins.UnescapedMarkup=!0,Prism.hooks.add("before-highlightall",function(e){e.selector+=", [class*='lang-'] script[type='text/plain'], [class*='language-'] script[type='text/plain'], script[type='text/plain'][class*='lang-'], script[type='text/plain'][class*='language-']"}),Prism.hooks.add("before-sanity-check",function(e){if((e.element.matches||e.element.msMatchesSelector).call(e.element,"script[type='text/plain']")){var t=document.createElement("code"),n=document.createElement("pre");return n.className=t.className=e.element.className,e.element.dataset&&Object.keys(e.element.dataset).forEach(function(t){Object.prototype.hasOwnProperty.call(e.element.dataset,t)&&(n.dataset[t]=e.element.dataset[t])}),e.code=e.code.replace(/<\/script(>|>)/gi,""),t.textContent=e.code,n.appendChild(t),e.element.parentNode.replaceChild(n,e.element),e.element=t,void 0}var n=e.element.parentNode;!e.code&&n&&"pre"==n.nodeName.toLowerCase()&&e.element.childNodes.length&&"#comment"==e.element.childNodes[0].nodeName&&(e.element.textContent=e.code=e.element.childNodes[0].textContent)}))}(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/wpd/index.html b/docs/_style/prism-master/plugins/wpd/index.html deleted file mode 100644 index 9157a483..00000000 --- a/docs/_style/prism-master/plugins/wpd/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -WebPlatform Docs ▲ Prism plugins - - - - - - - - - - - -
        -
        - -

        WebPlatform Docs

        -

        Makes tokens link to WebPlatform.org documentation. The links open in a new tab.

        -
        - -
        -

        How to use

        - -

        No setup required, just include the plugin in your download and you’re good to go!

        - -

        Tokens that currently link to documentation:

        - -
          -
        • HTML, MathML and SVG tags
        • -
        • HTML, MathML and SVG non-namespaced attributes
        • -
        • (Non-prefixed) CSS properties
        • -
        • (Non-prefixed) CSS @rules
        • -
        • (Non-prefixed) CSS pseudo-classes
        • -
        • (Non-prefixed) CSS pseudo-elements (starting with ::)
        • -
        - -

        Beta: This plugin is still in beta. Please help make it better: Test it and report any false positives etc!

        -
        - -
        -

        Examples

        - -

        CSS

        -
        
        -	
        
        -
        -	

        HTML

        -
        
        -
        -	

        SVG

        -
        
        -
        - -
        - - - - - - - - - \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/wpd/prism-wpd.css b/docs/_style/prism-master/plugins/wpd/prism-wpd.css deleted file mode 100644 index 43b7165a..00000000 --- a/docs/_style/prism-master/plugins/wpd/prism-wpd.css +++ /dev/null @@ -1,11 +0,0 @@ -code[class*="language-"] a[href], -pre[class*="language-"] a[href] { - cursor: help; - text-decoration: none; -} - -code[class*="language-"] a[href]:hover, -pre[class*="language-"] a[href]:hover { - cursor: help; - text-decoration: underline; -} \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/wpd/prism-wpd.js b/docs/_style/prism-master/plugins/wpd/prism-wpd.js deleted file mode 100644 index dc147d4f..00000000 --- a/docs/_style/prism-master/plugins/wpd/prism-wpd.js +++ /dev/null @@ -1,169 +0,0 @@ -(function(){ - -if ( - typeof self !== 'undefined' && !self.Prism || - typeof global !== 'undefined' && !global.Prism -) { - return; -} - -if (Prism.languages.css) { - // check whether the selector is an advanced pattern before extending it - if (Prism.languages.css.selector.pattern) - { - Prism.languages.css.selector.inside['pseudo-class'] = /:[\w-]+/; - Prism.languages.css.selector.inside['pseudo-element'] = /::[\w-]+/; - } - else - { - Prism.languages.css.selector = { - pattern: Prism.languages.css.selector, - inside: { - 'pseudo-class': /:[\w-]+/, - 'pseudo-element': /::[\w-]+/ - } - }; - } -} - -if (Prism.languages.markup) { - Prism.languages.markup.tag.inside.tag.inside['tag-id'] = /[\w-]+/; - - var Tags = { - HTML: { - 'a': 1, 'abbr': 1, 'acronym': 1, 'b': 1, 'basefont': 1, 'bdo': 1, 'big': 1, 'blink': 1, 'cite': 1, 'code': 1, 'dfn': 1, 'em': 1, 'kbd': 1, 'i': 1, - 'rp': 1, 'rt': 1, 'ruby': 1, 's': 1, 'samp': 1, 'small': 1, 'spacer': 1, 'strike': 1, 'strong': 1, 'sub': 1, 'sup': 1, 'time': 1, 'tt': 1, 'u': 1, - 'var': 1, 'wbr': 1, 'noframes': 1, 'summary': 1, 'command': 1, 'dt': 1, 'dd': 1, 'figure': 1, 'figcaption': 1, 'center': 1, 'section': 1, 'nav': 1, - 'article': 1, 'aside': 1, 'hgroup': 1, 'header': 1, 'footer': 1, 'address': 1, 'noscript': 1, 'isIndex': 1, 'main': 1, 'mark': 1, 'marquee': 1, - 'meter': 1, 'menu': 1 - }, - SVG: { - 'animateColor': 1, 'animateMotion': 1, 'animateTransform': 1, 'glyph': 1, 'feBlend': 1, 'feColorMatrix': 1, 'feComponentTransfer': 1, - 'feFuncR': 1, 'feFuncG': 1, 'feFuncB': 1, 'feFuncA': 1, 'feComposite': 1, 'feConvolveMatrix': 1, 'feDiffuseLighting': 1, 'feDisplacementMap': 1, - 'feFlood': 1, 'feGaussianBlur': 1, 'feImage': 1, 'feMerge': 1, 'feMergeNode': 1, 'feMorphology': 1, 'feOffset': 1, 'feSpecularLighting': 1, - 'feTile': 1, 'feTurbulence': 1, 'feDistantLight': 1, 'fePointLight': 1, 'feSpotLight': 1, 'linearGradient': 1, 'radialGradient': 1, 'altGlyph': 1, - 'textPath': 1, 'tref': 1, 'altglyph': 1, 'textpath': 1, 'altglyphdef': 1, 'altglyphitem': 1, 'clipPath': 1, 'color-profile': 1, 'cursor': 1, - 'font-face': 1, 'font-face-format': 1, 'font-face-name': 1, 'font-face-src': 1, 'font-face-uri': 1, 'foreignObject': 1, 'glyphRef': 1, - 'hkern': 1, 'vkern': 1 - }, - MathML: {} - } -} - -var language; - -Prism.hooks.add('wrap', function(env) { - if ((env.type == 'tag-id' - || (env.type == 'property' && env.content.indexOf('-') != 0) - || (env.type == 'rule'&& env.content.indexOf('@-') != 0) - || (env.type == 'pseudo-class'&& env.content.indexOf(':-') != 0) - || (env.type == 'pseudo-element'&& env.content.indexOf('::-') != 0) - || (env.type == 'attr-name' && env.content.indexOf('data-') != 0) - ) && env.content.indexOf('<') === -1 - ) { - if (env.language == 'css' - || env.language == 'scss' - || env.language == 'markup' - ) { - var href = 'https://webplatform.github.io/docs/'; - var content = env.content; - - if (env.language == 'css' || env.language == 'scss') { - href += 'css/'; - - if (env.type == 'property') { - href += 'properties/'; - } - else if (env.type == 'rule') { - href += 'atrules/'; - content = content.substring(1); - } - else if (env.type == 'pseudo-class') { - href += 'selectors/pseudo-classes/'; - content = content.substring(1); - } - else if (env.type == 'pseudo-element') { - href += 'selectors/pseudo-elements/'; - content = content.substring(2); - } - } - else if (env.language == 'markup') { - if (env.type == 'tag-id') { - // Check language - language = getLanguage(env.content) || language; - - if (language) { - href += language + '/elements/'; - } - else { - return; // Abort - } - } - else if (env.type == 'attr-name') { - if (language) { - href += language + '/attributes/'; - } - else { - return; // Abort - } - } - } - - href += content; - env.tag = 'a'; - env.attributes.href = href; - env.attributes.target = '_blank'; - } - } -}); - -function getLanguage(tag) { - var tagL = tag.toLowerCase(); - - if (Tags.HTML[tagL]) { - return 'html'; - } - else if (Tags.SVG[tag]) { - return 'svg'; - } - else if (Tags.MathML[tag]) { - return 'mathml'; - } - - // Not in dictionary, perform check - if (Tags.HTML[tagL] !== 0 && typeof document !== 'undefined') { - var htmlInterface = (document.createElement(tag).toString().match(/\[object HTML(.+)Element\]/) || [])[1]; - - if (htmlInterface && htmlInterface != 'Unknown') { - Tags.HTML[tagL] = 1; - return 'html'; - } - } - - Tags.HTML[tagL] = 0; - - if (Tags.SVG[tag] !== 0 && typeof document !== 'undefined') { - var svgInterface = (document.createElementNS('http://www.w3.org/2000/svg', tag).toString().match(/\[object SVG(.+)Element\]/) || [])[1]; - - if (svgInterface && svgInterface != 'Unknown') { - Tags.SVG[tag] = 1; - return 'svg'; - } - } - - Tags.SVG[tag] = 0; - - // Lame way to detect MathML, but browsers don’t expose interface names there :( - if (Tags.MathML[tag] !== 0) { - if (tag.indexOf('m') === 0) { - Tags.MathML[tag] = 1; - return 'mathml'; - } - } - - Tags.MathML[tag] = 0; - - return null; -} - -})(); \ No newline at end of file diff --git a/docs/_style/prism-master/plugins/wpd/prism-wpd.min.js b/docs/_style/prism-master/plugins/wpd/prism-wpd.min.js deleted file mode 100644 index 86bad362..00000000 --- a/docs/_style/prism-master/plugins/wpd/prism-wpd.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){function e(e){var n=e.toLowerCase();if(t.HTML[n])return"html";if(t.SVG[e])return"svg";if(t.MathML[e])return"mathml";if(0!==t.HTML[n]&&"undefined"!=typeof document){var a=(document.createElement(e).toString().match(/\[object HTML(.+)Element\]/)||[])[1];if(a&&"Unknown"!=a)return t.HTML[n]=1,"html"}if(t.HTML[n]=0,0!==t.SVG[e]&&"undefined"!=typeof document){var s=(document.createElementNS("http://www.w3.org/2000/svg",e).toString().match(/\[object SVG(.+)Element\]/)||[])[1];if(s&&"Unknown"!=s)return t.SVG[e]=1,"svg"}return t.SVG[e]=0,0!==t.MathML[e]&&0===e.indexOf("m")?(t.MathML[e]=1,"mathml"):(t.MathML[e]=0,null)}if(("undefined"==typeof self||self.Prism)&&("undefined"==typeof global||global.Prism)){if(Prism.languages.css&&(Prism.languages.css.selector.pattern?(Prism.languages.css.selector.inside["pseudo-class"]=/:[\w-]+/,Prism.languages.css.selector.inside["pseudo-element"]=/::[\w-]+/):Prism.languages.css.selector={pattern:Prism.languages.css.selector,inside:{"pseudo-class":/:[\w-]+/,"pseudo-element":/::[\w-]+/}}),Prism.languages.markup){Prism.languages.markup.tag.inside.tag.inside["tag-id"]=/[\w-]+/;var t={HTML:{a:1,abbr:1,acronym:1,b:1,basefont:1,bdo:1,big:1,blink:1,cite:1,code:1,dfn:1,em:1,kbd:1,i:1,rp:1,rt:1,ruby:1,s:1,samp:1,small:1,spacer:1,strike:1,strong:1,sub:1,sup:1,time:1,tt:1,u:1,"var":1,wbr:1,noframes:1,summary:1,command:1,dt:1,dd:1,figure:1,figcaption:1,center:1,section:1,nav:1,article:1,aside:1,hgroup:1,header:1,footer:1,address:1,noscript:1,isIndex:1,main:1,mark:1,marquee:1,meter:1,menu:1},SVG:{animateColor:1,animateMotion:1,animateTransform:1,glyph:1,feBlend:1,feColorMatrix:1,feComponentTransfer:1,feFuncR:1,feFuncG:1,feFuncB:1,feFuncA:1,feComposite:1,feConvolveMatrix:1,feDiffuseLighting:1,feDisplacementMap:1,feFlood:1,feGaussianBlur:1,feImage:1,feMerge:1,feMergeNode:1,feMorphology:1,feOffset:1,feSpecularLighting:1,feTile:1,feTurbulence:1,feDistantLight:1,fePointLight:1,feSpotLight:1,linearGradient:1,radialGradient:1,altGlyph:1,textPath:1,tref:1,altglyph:1,textpath:1,altglyphdef:1,altglyphitem:1,clipPath:1,"color-profile":1,cursor:1,"font-face":1,"font-face-format":1,"font-face-name":1,"font-face-src":1,"font-face-uri":1,foreignObject:1,glyphRef:1,hkern:1,vkern:1},MathML:{}}}var n;Prism.hooks.add("wrap",function(t){if(("tag-id"==t.type||"property"==t.type&&0!=t.content.indexOf("-")||"rule"==t.type&&0!=t.content.indexOf("@-")||"pseudo-class"==t.type&&0!=t.content.indexOf(":-")||"pseudo-element"==t.type&&0!=t.content.indexOf("::-")||"attr-name"==t.type&&0!=t.content.indexOf("data-"))&&-1===t.content.indexOf("<")&&("css"==t.language||"scss"==t.language||"markup"==t.language)){var a="https://webplatform.github.io/docs/",s=t.content;if("css"==t.language||"scss"==t.language)a+="css/","property"==t.type?a+="properties/":"rule"==t.type?(a+="atrules/",s=s.substring(1)):"pseudo-class"==t.type?(a+="selectors/pseudo-classes/",s=s.substring(1)):"pseudo-element"==t.type&&(a+="selectors/pseudo-elements/",s=s.substring(2));else if("markup"==t.language)if("tag-id"==t.type){if(n=e(t.content)||n,!n)return;a+=n+"/elements/"}else if("attr-name"==t.type){if(!n)return;a+=n+"/attributes/"}a+=s,t.tag="a",t.attributes.href=a,t.attributes.target="_blank"}})}}(); \ No newline at end of file diff --git a/docs/_style/prism-master/prefixfree.min.js b/docs/_style/prism-master/prefixfree.min.js deleted file mode 100644 index 94fc4087..00000000 --- a/docs/_style/prism-master/prefixfree.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * StyleFix 1.0.3 & PrefixFree 1.0.7 - * @author Lea Verou - * MIT license - */(function(){function t(e,t){return[].slice.call((t||document).querySelectorAll(e))}if(!window.addEventListener)return;var e=window.StyleFix={link:function(t){try{if(t.rel!=="stylesheet"||t.hasAttribute("data-noprefix"))return}catch(n){return}var r=t.href||t.getAttribute("data-href"),i=r.replace(/[^\/]+$/,""),s=t.parentNode,o=new XMLHttpRequest,u;o.onreadystatechange=function(){o.readyState===4&&u()};u=function(){var n=o.responseText;if(n&&t.parentNode&&(!o.status||o.status<400||o.status>600)){n=e.fix(n,!0,t);if(i){n=n.replace(/url\(\s*?((?:"|')?)(.+?)\1\s*?\)/gi,function(e,t,n){return/^([a-z]{3,10}:|\/|#)/i.test(n)?e:'url("http://github.com/'+i+n+'")'});var r=i.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1");n=n.replace(RegExp("\\b(behavior:\\s*?url\\('?\"?)"+r,"gi"),"$1")}var u=document.createElement("style");u.textContent=n;u.media=t.media;u.disabled=t.disabled;u.setAttribute("data-href",t.getAttribute("href"));s.insertBefore(u,t);s.removeChild(t);u.media=t.media}};try{o.open("GET",r);o.send(null)}catch(n){if(typeof XDomainRequest!="undefined"){o=new XDomainRequest;o.onerror=o.onprogress=function(){};o.onload=u;o.open("GET",r);o.send(null)}}t.setAttribute("data-inprogress","")},styleElement:function(t){if(t.hasAttribute("data-noprefix"))return;var n=t.disabled;t.textContent=e.fix(t.textContent,!0,t);t.disabled=n},styleAttribute:function(t){var n=t.getAttribute("style");n=e.fix(n,!1,t);t.setAttribute("style",n)},process:function(){t('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);t("style").forEach(StyleFix.styleElement);t("[style]").forEach(StyleFix.styleAttribute)},register:function(t,n){(e.fixers=e.fixers||[]).splice(n===undefined?e.fixers.length:n,0,t)},fix:function(t,n,r){for(var i=0;i-1&&(e=e.replace(/(\s|:|,)(repeating-)?linear-gradient\(\s*(-?\d*\.?\d*)deg/ig,function(e,t,n,r){r=Math.abs(r-450)%360;return t+(n||"")+"linear-gradient("+r+"deg"}));e=t("functions","(\\s|:|,)","\\s*\\(","$1"+s+"$2(",e);e=t("keywords","(\\s|:)","(\\s|;|\\}|$)","$1"+s+"$2$3",e);e=t("properties","(^|\\{|\\s|;)","\\s*:","$1"+s+"$2:",e);if(n.properties.length){var o=RegExp("\\b("+n.properties.join("|")+")(?!:)","gi");e=t("valueProperties","\\b",":(.+?);",function(e){return e.replace(o,s+"$1")},e)}if(r){e=t("selectors","","\\b",n.prefixSelector,e);e=t("atrules","@","\\b","@"+s+"$1",e)}e=e.replace(RegExp("-"+s,"g"),"-");e=e.replace(/-\*-(?=[a-z]+)/gi,n.prefix);return e},property:function(e){return(n.properties.indexOf(e)?n.prefix:"")+e},value:function(e,r){e=t("functions","(^|\\s|,)","\\s*\\(","$1"+n.prefix+"$2(",e);e=t("keywords","(^|\\s)","(\\s|$)","$1"+n.prefix+"$2$3",e);return e},prefixSelector:function(e){return e.replace(/^:{1,2}/,function(e){return e+n.prefix})},prefixProperty:function(e,t){var r=n.prefix+e;return t?StyleFix.camelCase(r):r}};(function(){var e={},t=[],r={},i=getComputedStyle(document.documentElement,null),s=document.createElement("div").style,o=function(n){if(n.charAt(0)==="-"){t.push(n);var r=n.split("-"),i=r[1];e[i]=++e[i]||1;while(r.length>3){r.pop();var s=r.join("-");u(s)&&t.indexOf(s)===-1&&t.push(s)}}},u=function(e){return StyleFix.camelCase(e)in s};if(i.length>0)for(var a=0;a text.length) { - // Something went terribly wrong, ABORT, ABORT! - return; - } - - if (str instanceof Token) { - continue; - } - - if (greedy && i != strarr.length - 1) { - pattern.lastIndex = pos; - var match = pattern.exec(text); - if (!match) { - break; - } - - var from = match.index + (lookbehind ? match[1].length : 0), - to = match.index + match[0].length, - k = i, - p = pos; - - for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) { - p += strarr[k].length; - // Move the index i to the element in strarr that is closest to from - if (from >= p) { - ++i; - pos = p; - } - } - - // If strarr[i] is a Token, then the match starts inside another Token, which is invalid - if (strarr[i] instanceof Token) { - continue; - } - - // Number of tokens to delete and replace with the new match - delNum = k - i; - str = text.slice(pos, p); - match.index -= pos; - } else { - pattern.lastIndex = 0; - - var match = pattern.exec(str), - delNum = 1; - } - - if (!match) { - if (oneshot) { - break; - } - - continue; - } - - if(lookbehind) { - lookbehindLength = match[1] ? match[1].length : 0; - } - - var from = match.index + lookbehindLength, - match = match[0].slice(lookbehindLength), - to = from + match.length, - before = str.slice(0, from), - after = str.slice(to); - - var args = [i, delNum]; - - if (before) { - ++i; - pos += before.length; - args.push(before); - } - - var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy); - - args.push(wrapped); - - if (after) { - args.push(after); - } - - Array.prototype.splice.apply(strarr, args); - - if (delNum != 1) - _.matchGrammar(text, strarr, grammar, i, pos, true, token); - - if (oneshot) - break; - } - } - } - }, - - tokenize: function(text, grammar, language) { - var strarr = [text]; - - var rest = grammar.rest; - - if (rest) { - for (var token in rest) { - grammar[token] = rest[token]; - } - - delete grammar.rest; - } - - _.matchGrammar(text, strarr, grammar, 0, 0, false); - - return strarr; - }, - - hooks: { - all: {}, - - add: function (name, callback) { - var hooks = _.hooks.all; - - hooks[name] = hooks[name] || []; - - hooks[name].push(callback); - }, - - run: function (name, env) { - var callbacks = _.hooks.all[name]; - - if (!callbacks || !callbacks.length) { - return; - } - - for (var i=0, callback; callback = callbacks[i++];) { - callback(env); - } - } - } -}; - -var Token = _.Token = function(type, content, alias, matchedStr, greedy) { - this.type = type; - this.content = content; - this.alias = alias; - // Copy of the full string this token was created from - this.length = (matchedStr || "").length|0; - this.greedy = !!greedy; -}; - -Token.stringify = function(o, language, parent) { - if (typeof o == 'string') { - return o; - } - - if (_.util.type(o) === 'Array') { - return o.map(function(element) { - return Token.stringify(element, language, o); - }).join(''); - } - - var env = { - type: o.type, - content: Token.stringify(o.content, language, parent), - tag: 'span', - classes: ['token', o.type], - attributes: {}, - language: language, - parent: parent - }; - - if (o.alias) { - var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias]; - Array.prototype.push.apply(env.classes, aliases); - } - - _.hooks.run('wrap', env); - - var attributes = Object.keys(env.attributes).map(function(name) { - return name + '="' + (env.attributes[name] || '').replace(/"/g, '"') + '"'; - }).join(' '); - - return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + ''; - -}; - -if (!_self.document) { - if (!_self.addEventListener) { - // in Node.js - return _self.Prism; - } - - if (!_.disableWorkerMessageHandler) { - // In worker - _self.addEventListener('message', function (evt) { - var message = JSON.parse(evt.data), - lang = message.language, - code = message.code, - immediateClose = message.immediateClose; - - _self.postMessage(_.highlight(code, _.languages[lang], lang)); - if (immediateClose) { - _self.close(); - } - }, false); - } - - return _self.Prism; -} - -//Get current script and highlight -var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop(); - -if (script) { - _.filename = script.src; - - if (!_.manual && !script.hasAttribute('data-manual')) { - if(document.readyState !== "loading") { - if (window.requestAnimationFrame) { - window.requestAnimationFrame(_.highlightAll); - } else { - window.setTimeout(_.highlightAll, 16); - } - } - else { - document.addEventListener('DOMContentLoaded', _.highlightAll); - } - } -} - -return _self.Prism; - -})(); - -if (typeof module !== 'undefined' && module.exports) { - module.exports = Prism; -} - -// hack for components to work correctly in node.js -if (typeof global !== 'undefined') { - global.Prism = Prism; -} - - -/* ********************************************** - Begin prism-markup.js -********************************************** */ - -Prism.languages.markup = { - 'comment': //, - 'prolog': /<\?[\s\S]+?\?>/, - 'doctype': //i, - 'cdata': //i, - 'tag': { - pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i, - greedy: true, - inside: { - 'tag': { - pattern: /^<\/?[^\s>\/]+/i, - inside: { - 'punctuation': /^<\/?/, - 'namespace': /^[^\s>\/:]+:/ - } - }, - 'attr-value': { - pattern: /=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i, - inside: { - 'punctuation': [ - /^=/, - { - pattern: /(^|[^\\])["']/, - lookbehind: true - } - ] - } - }, - 'punctuation': /\/?>/, - 'attr-name': { - pattern: /[^\s>\/]+/, - inside: { - 'namespace': /^[^\s>\/:]+:/ - } - } - - } - }, - 'entity': /&#?[\da-z]{1,8};/i -}; - -Prism.languages.markup['tag'].inside['attr-value'].inside['entity'] = - Prism.languages.markup['entity']; - -// Plugin to make entity title show the real entity, idea by Roman Komarov -Prism.hooks.add('wrap', function(env) { - - if (env.type === 'entity') { - env.attributes['title'] = env.content.replace(/&/, '&'); - } -}); - -Prism.languages.xml = Prism.languages.markup; -Prism.languages.html = Prism.languages.markup; -Prism.languages.mathml = Prism.languages.markup; -Prism.languages.svg = Prism.languages.markup; - - -/* ********************************************** - Begin prism-css.js -********************************************** */ - -Prism.languages.css = { - 'comment': /\/\*[\s\S]*?\*\//, - 'atrule': { - pattern: /@[\w-]+?.*?(?:;|(?=\s*\{))/i, - inside: { - 'rule': /@[\w-]+/ - // See rest below - } - }, - 'url': /url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i, - 'selector': /[^{}\s][^{};]*?(?=\s*\{)/, - 'string': { - pattern: /("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, - greedy: true - }, - 'property': /[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i, - 'important': /!important\b/i, - 'function': /[-a-z0-9]+(?=\()/i, - 'punctuation': /[(){};:,]/ -}; - -Prism.languages.css['atrule'].inside.rest = Prism.languages.css; - -if (Prism.languages.markup) { - Prism.languages.insertBefore('markup', 'tag', { - 'style': { - pattern: /()[\s\S]*?(?=<\/style>)/i, - lookbehind: true, - inside: Prism.languages.css, - alias: 'language-css', - greedy: true - } - }); - - Prism.languages.insertBefore('inside', 'attr-value', { - 'style-attr': { - pattern: /\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i, - inside: { - 'attr-name': { - pattern: /^\s*style/i, - inside: Prism.languages.markup.tag.inside - }, - 'punctuation': /^\s*=\s*['"]|['"]\s*$/, - 'attr-value': { - pattern: /.+/i, - inside: Prism.languages.css - } - }, - alias: 'language-css' - } - }, Prism.languages.markup.tag); -} - - -/* ********************************************** - Begin prism-clike.js -********************************************** */ - -Prism.languages.clike = { - 'comment': [ - { - pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, - lookbehind: true - }, - { - pattern: /(^|[^\\:])\/\/.*/, - lookbehind: true, - greedy: true - } - ], - 'string': { - pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, - greedy: true - }, - 'class-name': { - pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i, - lookbehind: true, - inside: { - punctuation: /[.\\]/ - } - }, - 'keyword': /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/, - 'boolean': /\b(?:true|false)\b/, - 'function': /\w+(?=\()/, - 'number': /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i, - 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/, - 'punctuation': /[{}[\];(),.:]/ -}; - - -/* ********************************************** - Begin prism-javascript.js -********************************************** */ - -Prism.languages.javascript = Prism.languages.extend('clike', { - 'class-name': [ - Prism.languages.clike['class-name'], - { - pattern: /(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/, - lookbehind: true - } - ], - 'keyword': [ - { - pattern: /((?:^|})\s*)(?:catch|finally)\b/, - lookbehind: true - }, - /\b(?:as|async|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/ - ], - 'number': /\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/, - // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444) - 'function': /[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\(|\.(?:apply|bind|call)\()/, - 'operator': /-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/ -}); - -Prism.languages.javascript['class-name'][0].pattern = /(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/ - -Prism.languages.insertBefore('javascript', 'keyword', { - 'regex': { - pattern: /((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/, - lookbehind: true, - greedy: true - }, - // This must be declared before keyword because we use "function" inside the look-forward - 'function-variable': { - pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\([^()]*\)|[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/i, - alias: 'function' - }, - 'parameter': [ - { - pattern: /(function(?:\s+[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)[^\s()][^()]*?(?=\s*\))/, - lookbehind: true, - inside: Prism.languages.javascript - }, - { - pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/, - inside: Prism.languages.javascript - }, - { - pattern: /(\(\s*)[^\s()][^()]*?(?=\s*\)\s*=>)/, - lookbehind: true, - inside: Prism.languages.javascript - }, - { - pattern: /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)[^\s()][^()]*?(?=\s*\)\s*\{)/, - lookbehind: true, - inside: Prism.languages.javascript - } - ], - 'constant': /\b[A-Z][A-Z\d_]*\b/ -}); - -Prism.languages.insertBefore('javascript', 'string', { - 'template-string': { - pattern: /`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/, - greedy: true, - inside: { - 'interpolation': { - pattern: /\${[^}]+}/, - inside: { - 'interpolation-punctuation': { - pattern: /^\${|}$/, - alias: 'punctuation' - }, - rest: Prism.languages.javascript - } - }, - 'string': /[\s\S]+/ - } - } -}); - -if (Prism.languages.markup) { - Prism.languages.insertBefore('markup', 'tag', { - 'script': { - pattern: /()[\s\S]*?(?=<\/script>)/i, - lookbehind: true, - inside: Prism.languages.javascript, - alias: 'language-javascript', - greedy: true - } - }); -} - -Prism.languages.js = Prism.languages.javascript; - - -/* ********************************************** - Begin prism-file-highlight.js -********************************************** */ - -(function () { - if (typeof self === 'undefined' || !self.Prism || !self.document || !document.querySelector) { - return; - } - - /** - * @param {Element} [container=document] - */ - self.Prism.fileHighlight = function(container) { - container = container || document; - - var Extensions = { - 'js': 'javascript', - 'py': 'python', - 'rb': 'ruby', - 'ps1': 'powershell', - 'psm1': 'powershell', - 'sh': 'bash', - 'bat': 'batch', - 'h': 'c', - 'tex': 'latex' - }; - - Array.prototype.slice.call(container.querySelectorAll('pre[data-src]')).forEach(function (pre) { - // ignore if already loaded - if (pre.hasAttribute('data-src-loaded')) { - return; - } - - // load current - var src = pre.getAttribute('data-src'); - - var language, parent = pre; - var lang = /\blang(?:uage)?-([\w-]+)\b/i; - while (parent && !lang.test(parent.className)) { - parent = parent.parentNode; - } - - if (parent) { - language = (pre.className.match(lang) || [, ''])[1]; - } - - if (!language) { - var extension = (src.match(/\.(\w+)$/) || [, ''])[1]; - language = Extensions[extension] || extension; - } - - var code = document.createElement('code'); - code.className = 'language-' + language; - - pre.textContent = ''; - - code.textContent = 'Loading…'; - - pre.appendChild(code); - - var xhr = new XMLHttpRequest(); - - xhr.open('GET', src, true); - - xhr.onreadystatechange = function () { - if (xhr.readyState == 4) { - - if (xhr.status < 400 && xhr.responseText) { - code.textContent = xhr.responseText; - - Prism.highlightElement(code); - // mark as loaded - pre.setAttribute('data-src-loaded', ''); - } - else if (xhr.status >= 400) { - code.textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText; - } - else { - code.textContent = '✖ Error: File does not exist or is empty'; - } - } - }; - - xhr.send(null); - }); - - if (Prism.plugins.toolbar) { - Prism.plugins.toolbar.registerButton('download-file', function (env) { - var pre = env.element.parentNode; - if (!pre || !/pre/i.test(pre.nodeName) || !pre.hasAttribute('data-src') || !pre.hasAttribute('data-download-link')) { - return; - } - var src = pre.getAttribute('data-src'); - var a = document.createElement('a'); - a.textContent = pre.getAttribute('data-download-link-label') || 'Download'; - a.setAttribute('download', ''); - a.href = src; - return a; - }); - } - - }; - - document.addEventListener('DOMContentLoaded', function () { - // execute inside handler, for dropping Event as argumnet - self.Prism.fileHighlight(); - }); - -})(); diff --git a/docs/_style/prism-master/style.css b/docs/_style/prism-master/style.css deleted file mode 100644 index 80a674ed..00000000 --- a/docs/_style/prism-master/style.css +++ /dev/null @@ -1,407 +0,0 @@ -@import url(https://fonts.googleapis.com/css?family=Questrial); -@import url(https://fonts.googleapis.com/css?family=Arvo); - -@font-face { - src: url(https://lea.verou.me/logo.otf); - font-family: 'LeaVerou'; -} - -/* - Shared styles - */ - -section h1, -#features li strong, -header h2, -footer p { - font: 100% Rockwell, Arvo, serif; -} - -/* - Styles - */ - -* { - margin: 0; - padding: 0; - font-weight: normal; -} - -body { - font: 100%/1.5 Questrial, sans-serif; - tab-size: 4; - hyphens: auto; -} - -a { - color: inherit; -} - -section h1 { - font-size: 250%; -} - - section section h1 { - font-size: 150%; - } - - section h1 code { - font-style: normal; - } - - section h1 > a { - text-decoration: none; - } - - section h1 > a:before { - content: '§'; - position: absolute; - padding: 0 .2em; - margin-left: -1em; - border-radius: .2em; - color: silver; - text-shadow: 0 1px white; - } - - section h1 > a:hover:before { - color: black; - background: #f1ad26; - } - -p { - margin: 1em 0; -} - -section h1, -h2 { - margin: 1em 0 .3em; -} - -dt { - margin: 1em 0 0 0; - font-size: 130%; -} - - dt:after { - content: ':'; - } - -dd { - margin-left: 2em; -} - -strong { - font-weight: bold; -} - -code, pre { - font-family: Consolas, Monaco, 'Andale Mono', 'Lucida Console', monospace; - hyphens: none; -} - -pre { - max-height: 30em; - overflow: auto; -} - -pre > code.highlight { - outline: .4em solid red; - outline-offset: .4em; -} - -header, -body > section { - display: block; - max-width: 900px; - margin: auto; -} - -header, footer { - position: relative; - padding: 30px -webkit-calc(50% - 450px); /* Workaround for bug */ - padding: 30px calc(50% - 450px); - color: white; - text-shadow: 0 -1px 2px black; - background: url(http://github.com/img/spectrum.png) fixed; -} - -header:before, -footer:before { - content: ''; - position: absolute; - bottom: 0; left: 0; right: 0; - height: 20px; - background-size: 20px 40px; - background-repeat: repeat-x; - background-image: linear-gradient(45deg, transparent 34%, white 34%, white 66%, transparent 66%), - linear-gradient(135deg, transparent 34%, white 34%, white 66%, transparent 66%); -} - -header { - -} - - header .intro, - html.simple header { - overflow: hidden; - } - - header h1 { - float: left; - margin-right: 30px; - color: #7fab14; - text-align: center; - font-size: 140%; - text-transform: uppercase; - letter-spacing: .25em; - } - - header h2 { - margin-top: .5em; - color: #f1ad26; - } - - header h1 a { - text-decoration: none; - } - - header img { - display: block; - width: 150px; - height: 128px; - margin-bottom: .3em; - border: 0; - } - - header h2 { - font-size: 300%; - } - - header .intro p { - margin: 0; - font: 150%/1.4 Questrial, sans-serif; - font-size: 150%; - } - - #features { - width: 66em; - margin-top: 2em; - font-size: 80%; - } - - #features li { - margin: 0 0 2em 0; - list-style: none; - display: inline-block; - width: 27em; - vertical-align: top; - } - - #features li:nth-child(odd) { - margin-right: 5em; - } - - #features li:before { - content: '✓'; - float: left; - margin-left: -.8em; - color: #7fab14; - font-size: 400%; - line-height: 1; - } - - #features li strong { - display: block; - margin-bottom: .1em; - font-size: 200%; - } - - header .download-button { - float: right; - margin: 0 0 .5em .5em; - } - - #theme { - position: relative; - z-index: 1; - float: right; - margin-right: -1em; - text-align: center; - text-transform: uppercase; - letter-spacing: .2em; - } - - #theme > p { - position: absolute; - left: 100%; - transform: translateX(50%) rotate(90deg) ; - transform-origin: top left; - font-size: 130%; - } - - #theme > label { - position: relative; - display: flex; - justify-content: center; - align-items: center; - width: 8.5em; - height: 8.5em; - line-height: 1em; - border-radius: 50%; - background: hsla(0,0%,100%,.5); - cursor: pointer; - font-size: 90%; - padding: 0; - } - - #theme > label:before { - content: ''; - position: absolute; - top: 0; right: 0; bottom: 0; left: 0; - z-index: -1; - border-radius: inherit; - background: url(http://github.com/img/spectrum.png) fixed; - } - - #theme > label:nth-of-type(n+2) { - margin-top: -2.5em; - } - - #theme > input:not(:checked) + label:hover { - background: hsla(77, 80%, 60%, .5); - } - - #theme > input { - position: absolute; - clip: rect(0,0,0,0); - } - - #theme > input:checked + label { - background: #7fab14; - } - -footer { - margin-top: 2em; - background-position: bottom; - color: white; - text-shadow: 0 -1px 2px black; -} - - footer:before { - bottom: auto; - top: 0; - background-position: bottom; - } - - footer p { - font-size: 150%; - } - - footer ul { - column-count: 3; - } - -.download-button { - display: block; - padding: .2em .8em .1em; - border: 1px solid rgba(0,0,0,0.5); - border-radius: 10px; - background: #39a1cf; - box-shadow: 0 2px 10px black, - inset 0 1px hsla(0,0%,100%,.3), - inset 0 .4em hsla(0,0%,100%,.2), - inset 0 10px 20px hsla(0,0%,100%,.25), - inset 0 -15px 30px rgba(0,0,0,0.3); - color: white; - text-shadow: 0 -1px 2px black; - text-align: center; - font-size: 250%; - line-height: 1.5; - text-transform: uppercase; - text-decoration: none; - hyphens: manual; -} - -.download-button:hover { - background-color: #7fab14; -} - -.download-button:active { - box-shadow: inset 0 2px 8px rgba(0,0,0,.8); -} - -#toc { - position: fixed; - left: 1%; - max-width: calc(48% - 450px); - font-size: 80%; - opacity: .3; -} - -@media (max-width: 1200px) { - #toc { - display: none; - } -} - -#toc:hover { - opacity: 1; -} - - #toc h1 { - font-size: 180%; - } - - #toc li { - list-style: none; - } - -#logo:before { - content: '☠'; - float: right; - font: 100px/1.6 LeaVerou; -} - -.used-by-logos { - overflow: hidden; -} - .used-by-logos > a { - float: left; - width: 33.33%; - height: 100px; - text-align: center; - background: #F5F2F0; - box-sizing: border-box; - border: 5px solid white; - position: relative; - } - .used-by-logos > a > img { - max-height: 100%; - max-width: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - -label a.owner { - margin: 0 .5em; -} - -label a.owner:not(:hover) { - text-decoration: none; - color: #aaa; -} - -#languages-list ul { - column-count: 3; -} - #languages-list li { - padding: .2em; - } - #languages-list li[data-id="javascript"] { - border-bottom: 1px solid #aaa; - padding-bottom: 1em; - margin-bottom: 1em; - margin-right: 1em; - } diff --git a/docs/_style/prism-master/templates/footer.html b/docs/_style/prism-master/templates/footer.html deleted file mode 100644 index b0e306a5..00000000 --- a/docs/_style/prism-master/templates/footer.html +++ /dev/null @@ -1,15 +0,0 @@ - -

        Handcrafted with ♥, by Lea Verou, Golmote -& all these awesome people

        - - diff --git a/docs/_style/prism-master/templates/header-download.html b/docs/_style/prism-master/templates/header-download.html deleted file mode 100644 index 4e3df4e2..00000000 --- a/docs/_style/prism-master/templates/header-download.html +++ /dev/null @@ -1,2 +0,0 @@ -Download Prism -Customize your download to include only the languages and plugins you need and the compression level you prefer. \ No newline at end of file diff --git a/docs/_style/prism-master/templates/header-main.html b/docs/_style/prism-master/templates/header-main.html deleted file mode 100644 index 01fe9406..00000000 --- a/docs/_style/prism-master/templates/header-main.html +++ /dev/null @@ -1,12 +0,0 @@ -

        Prism

        - -Download - -

        - Prism is a lightweight, extensible syntax highlighter, built with modern web standards in mind. - It’s used in thousands of websites, including some of those you visit daily. -

        - - diff --git a/docs/_style/prism-master/templates/header-plugins.html b/docs/_style/prism-master/templates/header-plugins.html deleted file mode 100644 index 45e2aac8..00000000 --- a/docs/_style/prism-master/templates/header-plugins.html +++ /dev/null @@ -1,8 +0,0 @@ -

        Prism plugins

        - -Download - -

        - Prism is a lightweight, extensible syntax highlighter, built with modern web standards in mind. - It’s used in thousands of websites, including some of those you visit daily. -

        diff --git a/docs/_style/prism-master/test-suite.html b/docs/_style/prism-master/test-suite.html deleted file mode 100644 index 5aeebf2a..00000000 --- a/docs/_style/prism-master/test-suite.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -Running the test suite ▲ Prism - - - - - - - - - -
        -
        - -

        Running the test suite

        -

        Prism has a test suite, that ensures that the correct tokens are matched.

        -
        - -
        -

        Running the test suite

        - -

        Running the test suite is simple: just call npm test.

        -

        All test files are run in isolation. A new prism instance is created for each test case. This will slow the test runner a bit down, but we can be sure that nothing leaks into the next test case.

        - -
        -

        Running tests for specific languages

        - -

        To run the tests only for one language, you can use the language parameter: npm test -- --language=markup.

        -

        You can even specify multiple languages: npm test -- --language=markup --language=css.

        -
        -
        - -
        -

        Writing tests

        - -

        Thank you for writing tests! Tests are awesome! They ensure, that we can improve the codebase without breaking anything. Also, this way, we can ensure that upgrading Prism is as painless as possible for you.

        -

        You can add new tests by creating a new test case file (with the .test file extension) in the tests directory which is located at /tests/languages/${language}.

        - -
        -

        Language directories

        -

        All tests are sorted into directories in the tests/languages directory. Each directory name encodes, which language you are currently testing.

        -

        All language names must match the names from the definition in components.js.

        - -

        Example 1: testing a language in isolation (default use case)

        -

        Just put your test file into the directory of the language you want to test.

        -

        So, if you want to test CSS, put your test file in /tests/languages/css to test CSS only. If you create a test case in this directory, the test runner will ensure that the css language definition including all required language definitions are correctly loaded.

        - -

        Example 2: testing language injection

        -

        If you want to test language injection, you typically need to load two or more languages where one language is the “main” language that is being tested, with all other languages being injected into it.

        -

        You need to define multiple languages by separating them using a + sign: markup+php.

        -

        The languages are loaded in order, so first markup (+ dependencies) is loaded, then php (+ dependencies). The test loader ensures that no language is loaded more than once (for example if two languages have the same dependencies).

        -

        By default the last language is the main language: php+markup will have markup as main language. This is equal to putting your code in the following code block:

        -
        ...
        -<pre><code class="language-markup">
        -	<!-- your code here -->
        -</code><pre>
        -...
        - -

        If you need to load the languages in a given order, but you don't want to use the last language as main language, you can mark the main language with an exclamation mark: php!+markup. This will use php as main language. (You can only define one main language. The test runner will fail all tests in directories with more than one main language.)

        - -

        Note: by loading multiple languages you can do integration tests (ensure that loading two or more languages together won't break anything).

        -
        - -
        -

        Creating your test case file

        -

        At first you need to create a new file in the language directory, you want to test.

        -

        Use a proper name for your test case. Please use one case of the following conventions:

        -
          -
        • issue{issueid}: reference a github issue id (example: issue588.test).
        • -
        • {featurename}_feature: group all tests to one feature in one file (example: string_interpolation_feature.test).
        • -
        • {language}_inclusion: test inclusion of one language into the other (example: markup!+css/css_inclusion.test will test CSS inclusion into markup).
        • -
        -

        You can use all conventions as a prefix, so string_interpolation_feature_inline.test is possible. But please take a minute or two to think of a proper name of your test case file. You are writing code not only for the computers, but also for your fellow developers.

        -
        - -
        -

        Writing your test

        -

        The structure of a test case file is as follows:

        -
        
        -... language snippet...
        -----
        -... the simplified token stream you expect ...
        - -

        Your file is built up of two or three sections, separated by ten or more dashes -, starting at the begin of the line:

        -
          -
        1. Your language snippet. The code you want to compile using Prism. (required)
        2. -
        3. The simplified token stream you expect. Needs to be valid JSON. (required)
        4. -
        5. A comment explaining the test case. (optional)
        6. -
        -

        The easiest way would be to look at an existing test file:

        -
        var a = 5;
        -
        -----------------------------------------------------
        -
        -[
        -	["keyword", "var"],
        -	" a ",
        -	["operator", "="],
        -	["number", "5"],
        -	["punctuation", ";"]
        -]
        -
        -----------------------------------------------------
        -
        -This is a comment explaining this test case.
        -
        - -
        -

        Explaining the simplified token stream

        -

        While compiling, Prism transforms your source code into a token stream. This is basically a tree of nested tokens (or arrays, or strings).

        -

        As these trees are hard to write by hand, the test runner uses a simplified version of it.

        -

        It uses the following rules:

        -
          -
        • Token objects are transformed into an array: [token.type, token.content] (whereas token.content can be a nested structure).
        • -
        • All strings that are either empty or only contain whitespace, are removed from the token stream.
        • -
        • All empty structures are removed.
        • -
        -

        For further information: reading the tests of the test runner (tests/testrunner-tests.js) will help you understand the transformation.

        -
        -
        - -
        -

        Writing specific tests

        - -

        Sometimes, using the token stream tests is not powerful enough. By creating a test file with the file extension .js instead of .test, you can make Prism highlight arbitrary pieces of code and check their HTML results.

        -

        The language is determined by the folder containing the test file lies, as explained in the previous section.

        -

        The structure of your test file will look like this, for example:

        -
        module.exports = {
        -	'&#x278a;': '<span class="token entity" title="&#x278a;">&amp;#x278a;</span>',
        -	'&#182;': '<span class="token entity" title="&#182;">&amp;#182;</span>',
        -};
        -

        The keys are the codes which will be highlighted by Prism. The values are the expected results, as HTML.

        -
        - -
        -

        Test runner tests

        -

        The test runner itself is tested in a separate test case. You can find all “test core” related tests in tests/testrunner-tests.js.

        -

        You shouldn't need to touch this file ever, except you modify the test runner code.

        -
        - -
        -

        Internal structure

        -

        The global test flow is at follows:

        -
          -
        1. Run all internal tests (test the test runner).
        2. -
        3. Find all language tests.
        4. -
        5. Run all language tests individually.
        6. -
        7. Report the results.
        8. -
        -
        - - -
        - - - - - - - - - diff --git a/docs/_style/prism-master/test.html b/docs/_style/prism-master/test.html deleted file mode 100644 index e28cce1d..00000000 --- a/docs/_style/prism-master/test.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - -Test drive ▲ Prism - - - - - - - - - - -
        -
        - -

        Test drive

        -

        Take Prism for a spin!

        -
        - -
        -
        -

        - -

        - -

        Result:

        -
        - -

        - Language: -

        -
        -
        - -
        - - - - - - - - - - - diff --git a/docs/_style/prism-master/tests/helper/prism-loader.js b/docs/_style/prism-master/tests/helper/prism-loader.js deleted file mode 100644 index 3257485a..00000000 --- a/docs/_style/prism-master/tests/helper/prism-loader.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var vm = require("vm"); -var components = require("../../components"); -var languagesCatalog = components.languages; - - -module.exports = { - - /** - * Creates a new Prism instance with the given language loaded - * - * @param {string|string[]} languages - * @returns {Prism} - */ - createInstance: function (languages) { - var context = { - loadedLanguages: [], - Prism: this.createEmptyPrism() - }; - - context = this.loadLanguages(languages, context); - - return context.Prism; - }, - - /** - * Loads the given languages and appends the config to the given Prism object - * - * @private - * @param {string|string[]} languages - * @param {{loadedLanguages: string[], Prism: Prism}} context - * @returns {{loadedLanguages: string[], Prism: Prism}} - */ - loadLanguages: function (languages, context) { - if (typeof languages === 'string') { - languages = [languages]; - } - - var self = this; - - languages.forEach(function (language) { - context = self.loadLanguage(language, context); - }); - - return context; - }, - - /** - * Loads the given language (including recursively loading the dependencies) and - * appends the config to the given Prism object - * - * @private - * @param {string} language - * @param {{loadedLanguages: string[], Prism: Prism}} context - * @returns {{loadedLanguages: string[], Prism: Prism}} - */ - loadLanguage: function (language, context) { - if (!languagesCatalog[language]) { - throw new Error("Language '" + language + "' not found."); - } - - // the given language was already loaded - if (-1 < context.loadedLanguages.indexOf(language)) { - return context; - } - - // if the language has a dependency -> load it first - if (languagesCatalog[language].require) { - context = this.loadLanguages(languagesCatalog[language].require, context); - } - - // load the language itself - var languageSource = this.loadFileSource(language); - context.Prism = this.runFileWithContext(languageSource, {Prism: context.Prism}).Prism; - context.loadedLanguages.push(language); - - return context; - }, - - - /** - * Creates a new empty prism instance - * - * @private - * @returns {Prism} - */ - createEmptyPrism: function () { - var coreSource = this.loadFileSource("core"); - var context = this.runFileWithContext(coreSource); - return context.Prism; - }, - - - /** - * Cached file sources, to prevent massive HDD work - * - * @private - * @type {Object.} - */ - fileSourceCache: {}, - - - /** - * Loads the given file source as string - * - * @private - * @param {string} name - * @returns {string} - */ - loadFileSource: function (name) { - return this.fileSourceCache[name] = this.fileSourceCache[name] || fs.readFileSync(__dirname + "/../../components/prism-" + name + ".js", "utf8"); - }, - - - /** - * Runs a VM for a given file source with the given context - * - * @private - * @param {string} fileSource - * @param {*} [context] - * - * @returns {*} - */ - runFileWithContext: function (fileSource, context) { - context = context || {}; - vm.runInNewContext(fileSource, context); - return context; - } -}; diff --git a/docs/_style/prism-master/tests/helper/test-case.js b/docs/_style/prism-master/tests/helper/test-case.js deleted file mode 100644 index cbf51a95..00000000 --- a/docs/_style/prism-master/tests/helper/test-case.js +++ /dev/null @@ -1,196 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var assert = require("chai").assert; -var PrismLoader = require("./prism-loader"); -var TokenStreamTransformer = require("./token-stream-transformer"); - -/** - * Handles parsing of a test case file. - * - * - * A test case file consists of at least two parts, separated by a line of dashes. - * This separation line must start at the beginning of the line and consist of at least three dashes. - * - * The test case file can either consist of two parts: - * - * {source code} - * ---- - * {expected token stream} - * - * - * or of three parts: - * - * {source code} - * ---- - * {expected token stream} - * ---- - * {text comment explaining the test case} - * - * If the file contains more than three parts, the remaining parts are just ignored. - * If the file however does not contain at least two parts (so no expected token stream), - * the test case will later be marked as failed. - * - * - * @type {{runTestCase: Function, transformCompiledTokenStream: Function, parseTestCaseFile: Function}} - */ -module.exports = { - - /** - * Runs the given test case file and asserts the result - * - * The passed language identifier can either be a language like "css" or a composed language - * identifier like "css+markup". Composed identifiers can be used for testing language inclusion. - * - * When testing language inclusion, the first given language is the main language which will be passed - * to Prism for highlighting ("css+markup" will result in a call to Prism to highlight with the "css" grammar). - * But it will be ensured, that the additional passed languages will be loaded too. - * - * The languages will be loaded in the order they were provided. - * - * @param {string} languageIdentifier - * @param {string} filePath - */ - runTestCase: function (languageIdentifier, filePath) { - var testCase = this.parseTestCaseFile(filePath); - var usedLanguages = this.parseLanguageNames(languageIdentifier); - - if (null === testCase) { - throw new Error("Test case file has invalid format (or the provided token stream is invalid JSON), please read the docs."); - } - - var Prism = PrismLoader.createInstance(usedLanguages.languages); - // the first language is the main language to highlight - var mainLanguageGrammar = Prism.languages[usedLanguages.mainLanguage]; - var env = { - code: testCase.testSource, - grammar: mainLanguageGrammar, - language: usedLanguages.mainLanguage - }; - Prism.hooks.run('before-tokenize', env); - env.tokens = Prism.tokenize(env.code, env.grammar); - Prism.hooks.run('after-tokenize', env); - var compiledTokenStream = env.tokens; - - var simplifiedTokenStream = TokenStreamTransformer.simplify(compiledTokenStream); - - var tzd = JSON.stringify( simplifiedTokenStream ); var exp = JSON.stringify( testCase.expectedTokenStream ); - var i = 0; var j = 0; var diff = ""; - while ( j < tzd.length ){ if (exp[i] != tzd[j] || i == exp.length) diff += tzd[j]; else i++; j++; } - - // var message = "\nToken Stream: \n" + JSON.stringify( simplifiedTokenStream, null, " " ) + - var message = "\nToken Stream: \n" + tzd + - "\n-----------------------------------------\n" + - "Expected Token Stream: \n" + exp + - "\n-----------------------------------------\n" + diff; - - var result = assert.deepEqual(simplifiedTokenStream, testCase.expectedTokenStream, testCase.comment + message); - }, - - - /** - * Parses the language names and finds the main language. - * - * It is either the last language or the language followed by a exclamation mark “!”. - * There should only be one language with an exclamation mark. - * - * @param {string} languageIdentifier - * - * @returns {{languages: string[], mainLanguage: string}} - */ - parseLanguageNames: function (languageIdentifier) { - var languages = languageIdentifier.split("+"); - var mainLanguage = null; - - languages = languages.map( - function (language) { - var pos = language.indexOf("!"); - - if (-1 < pos) { - if (mainLanguage) { - throw "There are multiple main languages defined."; - } - - mainLanguage = language.replace("!", ""); - return mainLanguage; - } - - return language; - } - ); - - if (!mainLanguage) { - mainLanguage = languages[languages.length-1]; - } - - return { - languages: languages, - mainLanguage: mainLanguage - }; - }, - - - /** - * Parses the test case from the given test case file - * - * @private - * @param {string} filePath - * @returns {{testSource: string, expectedTokenStream: Array.>, comment:string?}|null} - */ - parseTestCaseFile: function (filePath) { - var testCaseSource = fs.readFileSync(filePath, "utf8"); - var testCaseParts = testCaseSource.split(/^-{10,}\w*$/m); - - try { - var testCase = { - testSource: testCaseParts[0].trim(), - expectedTokenStream: JSON.parse(testCaseParts[1]), - comment: null - }; - - // if there are three parts, the third one is the comment - // explaining the test case - if (testCaseParts[2]) { - testCase.comment = testCaseParts[2].trim(); - } - - return testCase; - } - catch (e) { - // the JSON can't be parsed (e.g. it could be empty) - return null; - } - }, - - /** - * Runs the given pieces of codes and asserts their result. - * - * Code is provided as the key and expected result as the value. - * - * @param {string} languageIdentifier - * @param {object} codes - */ - runTestsWithHooks: function (languageIdentifier, codes) { - var usedLanguages = this.parseLanguageNames(languageIdentifier); - var Prism = PrismLoader.createInstance(usedLanguages.languages); - // the first language is the main language to highlight - - for (var code in codes) { - if (codes.hasOwnProperty(code)) { - var env = { - element: {}, - language: usedLanguages.mainLanguage, - grammar: Prism.languages[usedLanguages.mainLanguage], - code: code - }; - Prism.hooks.run('before-highlight', env); - env.highlightedCode = Prism.highlight(env.code, env.grammar, env.language); - Prism.hooks.run('before-insert', env); - env.element.innerHTML = env.highlightedCode; - Prism.hooks.run('after-highlight', env); - Prism.hooks.run('complete', env); - assert.equal(env.highlightedCode, codes[code]); - } - } - } -}; diff --git a/docs/_style/prism-master/tests/helper/test-discovery.js b/docs/_style/prism-master/tests/helper/test-discovery.js deleted file mode 100644 index b2aade1a..00000000 --- a/docs/_style/prism-master/tests/helper/test-discovery.js +++ /dev/null @@ -1,115 +0,0 @@ -"use strict"; - -var fs = require("fs"); -var path = require("path"); - - -module.exports = { - - /** - * Loads the list of all available tests - * - * @param {string} rootDir - * @returns {Object.} - */ - loadAllTests: function (rootDir) { - var testSuite = {}; - var self = this; - - this.getAllDirectories(rootDir).forEach( - function (language) { - testSuite[language] = self.getAllFiles(path.join(rootDir, language)); - } - ); - - return testSuite; - }, - - /** - * Loads the list of available tests that match the given languages - * - * @param {string} rootDir - * @param {string|string[]} languages - * @returns {Object.} - */ - loadSomeTests: function (rootDir, languages) { - var testSuite = {}; - var self = this; - - this.getSomeDirectories(rootDir, languages).forEach( - function (language) { - testSuite[language] = self.getAllFiles(path.join(rootDir, language)); - } - ); - - return testSuite; - }, - - - /** - * Returns a list of all (sub)directories (just the directory names, not full paths) - * in the given src directory - * - * @param {string} src - * @returns {Array.} - */ - getAllDirectories: function (src) { - return fs.readdirSync(src).filter( - function (file) { - return fs.statSync(path.join(src, file)).isDirectory(); - } - ); - }, - - /** - * Returns a list of all (sub)directories (just the directory names, not full paths) - * in the given src directory, matching the given languages - * - * @param {string} src - * @param {string|string[]} languages - * @returns {Array.} - */ - getSomeDirectories: function (src, languages) { - var self = this; - return fs.readdirSync(src).filter( - function (file) { - return fs.statSync(path.join(src, file)).isDirectory() && self.directoryMatches(file, languages); - } - ); - }, - - /** - * Returns whether a directory matches one of the given languages. - * @param {string} directory - * @param {string|string[]} languages - */ - directoryMatches: function (directory, languages) { - if (!Array.isArray(languages)) { - languages = [languages]; - } - var dirLanguages = directory.split(/!?\+!?/); - return dirLanguages.some(function (lang) { - return languages.indexOf(lang) >= 0; - }); - }, - - - /** - * Returns a list of all full file paths to all files in the given src directory - * - * @private - * @param {string} src - * @returns {Array.} - */ - getAllFiles: function (src) { - return fs.readdirSync(src).filter( - function (fileName) { - return fs.statSync(path.join(src, fileName)).isFile(); - } - ).map( - function (fileName) { - return path.join(src, fileName); - } - ); - } -}; diff --git a/docs/_style/prism-master/tests/helper/token-stream-transformer.js b/docs/_style/prism-master/tests/helper/token-stream-transformer.js deleted file mode 100644 index deb831c4..00000000 --- a/docs/_style/prism-master/tests/helper/token-stream-transformer.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; - - -module.exports = { - /** - * Simplifies the token stream to ease the matching with the expected token stream. - * - * * Strings are kept as-is - * * In arrays each value is transformed individually - * * Values that are empty (empty arrays or strings only containing whitespace) - * - * - * @param {Array} tokenStream - * @returns {Array.} - */ - simplify: function (tokenStream) { - if (Array.isArray(tokenStream)) { - return tokenStream - .map(this.simplify.bind(this)) - .filter(function (value) { - return !(Array.isArray(value) && !value.length) && !(typeof value === "string" && !value.trim().length); - } - ); - } - else if (typeof tokenStream === "object") { - return [tokenStream.type, this.simplify(tokenStream.content)]; - } - else { - return tokenStream; - } - } -}; diff --git a/docs/_style/prism-master/tests/languages/abap/comment_feature.test b/docs/_style/prism-master/tests/languages/abap/comment_feature.test deleted file mode 100644 index e0f28feb..00000000 --- a/docs/_style/prism-master/tests/languages/abap/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -* -* Foobar - ----------------------------------------------------- - -[ - ["comment", "*"], - ["comment", "* Foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/eol-comment_feature.test b/docs/_style/prism-master/tests/languages/abap/eol-comment_feature.test deleted file mode 100644 index f29fc84a..00000000 --- a/docs/_style/prism-master/tests/languages/abap/eol-comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -" -" foobar - ----------------------------------------------------- - -[ - ["eol-comment", "\""], - ["eol-comment", "\" foobar"] -] - ----------------------------------------------------- - -Checks for EOL comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/keyword_feature.test b/docs/_style/prism-master/tests/languages/abap/keyword_feature.test deleted file mode 100644 index 0a5a94d8..00000000 --- a/docs/_style/prism-master/tests/languages/abap/keyword_feature.test +++ /dev/null @@ -1,1801 +0,0 @@ -SCIENTIFIC_WITH_LEADING_ZERO -SCALE_PRESERVING_SCIENTIFIC -RMC_COMMUNICATION_FAILURE -END-ENHANCEMENT-SECTION -MULTIPLY-CORRESPONDING -SUBTRACT-CORRESPONDING -VERIFICATION-MESSAGE -DIVIDE-CORRESPONDING -ENHANCEMENT-SECTION -CURRENCY_CONVERSION -RMC_SYSTEM_FAILURE -START-OF-SELECTION -MOVE-CORRESPONDING -RMC_INVALID_STATUS -CUSTOMER-FUNCTION -END-OF-DEFINITION -ENHANCEMENT-POINT -SYSTEM-EXCEPTIONS -ADD-CORRESPONDING -SCALE_PRESERVING -SELECTION-SCREEN -CURSOR-SELECTION -END-OF-SELECTION -LOAD-OF-PROGRAM -SCROLL-BOUNDARY -SELECTION-TABLE -EXCEPTION-TABLE -IMPLEMENTATIONS -PARAMETER-TABLE -RIGHT-JUSTIFIED -UNIT_CONVERSION -AUTHORITY-CHECK -LIST-PROCESSING -SIGN_AS_POSTFIX -COL_BACKGROUND -IMPLEMENTATION -INTERFACE-POOL -TRANSFORMATION -IDENTIFICATION -ENDENHANCEMENT -LINE-SELECTION -INITIALIZATION -LEFT-JUSTIFIED -SELECT-OPTIONS -SELECTION-SETS -COMMUNICATION -CORRESPONDING -DECIMAL_SHIFT -PRINT-CONTROL -VALUE-REQUEST -CHAIN-REQUEST -FUNCTION-POOL -FIELD-SYMBOLS -FUNCTIONALITY -INVERTED-DATE -SELECTION-SET -CLASS-METHODS -OUTPUT-LENGTH -CLASS-CODING -COL_NEGATIVE -ERRORMESSAGE -FIELD-GROUPS -HELP-REQUEST -NO-EXTENSION -NO-TOPOFPAGE -REDEFINITION -DISPLAY-MODE -ENDINTERFACE -EXIT-COMMAND -FIELD-SYMBOL -NO-SCROLLING -SHORTDUMP-ID -ACCESSPOLICY -CLASS-EVENTS -COL_POSITIVE -DECLARATIONS -ENHANCEMENTS -FILTER-TABLE -SWITCHSTATES -SYNTAX-CHECK -TRANSPORTING -ASYNCHRONOUS -SYNTAX-TRACE -TOKENIZATION -USER-COMMAND -WITH-HEADING -ABAP-SOURCE -BREAK-POINT -CHAIN-INPUT -COMPRESSION -FIXED-POINT -NEW-SECTION -NON-UNICODE -OCCURRENCES -RESPONSIBLE -SYSTEM-CALL -TRACE-TABLE -ABBREVIATED -CHAR-TO-HEX -END-OF-FILE -ENDFUNCTION -ENVIRONMENT -ASSOCIATION -COL_HEADING -EDITOR-CALL -END-OF-PAGE -ENGINEERING -IMPLEMENTED -INTENSIFIED -RADIOBUTTON -SYSTEM-EXIT -TOP-OF-PAGE -TRANSACTION -APPLICATION -CONCATENATE -DESTINATION -ENHANCEMENT -IMMEDIATELY -NO-GROUPING -PRECOMPILED -REPLACEMENT -TITLE-LINES -ACTIVATION -BYTE-ORDER -CLASS-POOL -CONNECTION -CONVERSION -DEFINITION -DEPARTMENT -EXPIRATION -INHERITING -MESSAGE-ID -NO-HEADING -PERFORMING -QUEUE-ONLY -RIGHTSPACE -SCIENTIFIC -STATUSINFO -STRUCTURES -SYNCPOINTS -WITH-TITLE -ATTRIBUTES -BOUNDARIES -CLASS-DATA -COL_NORMAL -DD/MM/YYYY -DESCENDING -INTERFACES -LINE-COUNT -MM/DD/YYYY -NON-UNIQUE -PRESERVING -SELECTIONS -STATEMENTS -SUBROUTINE -TRUNCATION -TYPE-POOLS -ARITHMETIC -BACKGROUND -ENDPROVIDE -EXCEPTIONS -IDENTIFIER -INDEX-LINE -OBLIGATORY -PARAMETERS -PERCENTAGE -PUSHBUTTON -RESOLUTION -COMPONENTS -DEALLOCATE -DISCONNECT -DUPLICATES -FIRST-LINE -HEAD-LINES -NO-DISPLAY -OCCURRENCE -RESPECTING -RETURNCODE -SUBMATCHES -TRACE-FILE -ASCENDING -BYPASSING -ENDMODULE -EXCEPTION -EXCLUDING -EXPORTING -INCREMENT -MATCHCODE -PARAMETER -PARTIALLY -PREFERRED -REFERENCE -REPLACING -RETURNING -SELECTION -SEPARATED -SPECIFIED -STATEMENT -TIMESTAMP -TYPE-POOL -ACCEPTING -APPENDAGE -ASSIGNING -COL_GROUP -COMPARING -CONSTANTS -DANGEROUS -IMPORTING -INSTANCES -LEFTSPACE -LOG-POINT -QUICKINFO -READ-ONLY -SCROLLING -SQLSCRIPT -STEP-LOOP -TOP-LINES -TRANSLATE -APPENDING -AUTHORITY -CHARACTER -COMPONENT -CONDITION -DIRECTORY -DUPLICATE -MESSAGING -RECEIVING -SUBSCREEN -ACCORDING -COL_TOTAL -END-LINES -ENDMETHOD -ENDSELECT -EXPANDING -EXTENSION -INCLUDING -INFOTYPES -INTERFACE -INTERVALS -LINE-SIZE -PF-STATUS -PROCEDURE -PROTECTED -REQUESTED -RESUMABLE -RIGHTPLUS -SAP-SPOOL -SECONDARY -STRUCTURE -SUBSTRING -TABLEVIEW -NUMOFCHAR -ADJACENT -ANALYSIS -ASSIGNED -BACKWARD -CHANNELS -CHECKBOX -CONTINUE -CRITICAL -DATAINFO -DD/MM/YY -DURATION -ENCODING -ENDCLASS -FUNCTION -LEFTPLUS -LINEFEED -MM/DD/YY -OVERFLOW -RECEIVED -SKIPPING -SORTABLE -STANDARD -SUBTRACT -SUPPRESS -TABSTRIP -TITLEBAR -TRUNCATE -UNASSIGN -WHENEVER -ANALYZER -COALESCE -COMMENTS -CONDENSE -DECIMALS -DEFERRED -ENDWHILE -EXPLICIT -KEYWORDS -MESSAGES -POSITION -PRIORITY -RECEIVER -RENAMING -TIMEZONE -TRAILING -ALLOCATE -CENTERED -CIRCULAR -CONTROLS -CURRENCY -DELETING -DESCRIBE -DISTANCE -ENDCATCH -EXPONENT -EXTENDED -GENERATE -IGNORING -INCLUDES -INTERNAL -MAJOR-ID -MODIFIER -NEW-LINE -OPTIONAL -PROPERTY -ROLLBACK -STARTING -SUPPLIED -ABSTRACT -CHANGING -CONTEXTS -CREATING -CUSTOMER -DATABASE -DAYLIGHT -DEFINING -DISTINCT -DIVISION -ENABLING -ENDCHAIN -ESCAPING -HARMLESS -IMPLICIT -INACTIVE -LANGUAGE -MINOR-ID -MULTIPLY -NEW-PAGE -NO-TITLE -POS_HIGH -SEPARATE -TEXTPOOL -TRANSFER -SELECTOR -DBMAXLEN -ITERATOR -SELECTOR -ARCHIVE -BIT-XOR -BYTE-CO -COLLECT -COMMENT -CURRENT -DEFAULT -DISPLAY -ENDFORM -EXTRACT -LEADING -LISTBOX -LOCATOR -MEMBERS -METHODS -NESTING -POS_LOW -PROCESS -PROVIDE -RAISING -RESERVE -SECONDS -SUMMARY -VISIBLE -BETWEEN -BIT-AND -BYTE-CS -CLEANUP -COMPUTE -CONTROL -CONVERT -DATASET -ENDCASE -FORWARD -HEADERS -HOTSPOT -INCLUDE -INVERSE -KEEPING -NO-ZERO -OBJECTS -OVERLAY -PADDING -PATTERN -PROGRAM -REFRESH -SECTION -SUMMING -TESTING -VERSION -WINDOWS -WITHOUT -BIT-NOT -BYTE-CA -BYTE-NA -CASTING -CONTEXT -COUNTRY -DYNAMIC -ENABLED -ENDLOOP -EXECUTE -FRIENDS -HANDLER -HEADING -INITIAL - *-INPUT -LOGFILE -MAXIMUM -MINIMUM -NO-GAPS -NO-SIGN -PRAGMAS -PRIMARY -PRIVATE -REDUCED -REPLACE -REQUEST -RESULTS -UNICODE -WARNING -ALIASES -BYTE-CN -BYTE-NS -CALLING -COL_KEY -COLUMNS -CONNECT -ENDEXEC -ENTRIES -EXCLUDE -FILTERS -FURTHER -HELP-ID -LOGICAL -MAPPING -MESSAGE -NAMETAB -OPTIONS -PACKAGE -PERFORM -RECEIVE -STATICS -VARYING -BINDING -CHARLEN -GREATER -XSTRLEN -ACCEPT -APPEND -DETAIL -ELSEIF -ENDING -ENDTRY -FORMAT -FRAMES -GIVING -HASHED -HEADER -IMPORT -INSERT -MARGIN -MODULE -NATIVE -OBJECT -OFFSET -REMOTE -RESUME -SAVING -SIMPLE -SUBMIT -TABBED -TOKENS -UNIQUE -UNPACK -UPDATE -WINDOW -YELLOW -ACTUAL -ASPECT -CENTER -CURSOR -DELETE -DIALOG -DIVIDE -DURING -ERRORS -EVENTS -EXTEND -FILTER -HANDLE -HAVING -IGNORE -LITTLE -MEMORY -NO-GAP -OCCURS -OPTION -PERSON -PLACES -PUBLIC -REDUCE -REPORT -RESULT -SINGLE -SORTED -SWITCH -SYNTAX -TARGET -VALUES -WRITER -ASSERT -BLOCKS -BOUNDS -BUFFER -CHANGE -COLUMN -COMMIT -CONCAT -COPIES -CREATE -DDMMYY -DEFINE -ENDIAN -ESCAPE -EXPAND -KERNEL -LAYOUT -LEGACY -LEVELS -MMDDYY -NUMBER -OUTPUT -RANGES -READER -RETURN -SCREEN -SEARCH -SELECT -SHARED -SOURCE -STABLE -STATIC -SUBKEY -SUFFIX -TABLES -UNWIND -YYMMDD -ASSIGN -BACKUP -BEFORE -BINARY -BIT-OR -BLANKS -CLIENT -CODING -COMMON -DEMAND -DYNPRO -EXCEPT -EXISTS -EXPORT -FIELDS -GLOBAL -GROUPS -LENGTH -LOCALE -MEDIUM -METHOD -MODIFY -NESTED -OTHERS -REJECT -SCROLL -SUPPLY -SYMBOL -ENDFOR -STRLEN -ALIGN -BEGIN -BOUND -ENDAT -ENTRY -EVENT -FINAL -FLUSH -GRANT -INNER -SHORT -USING -WRITE -AFTER -BLACK -BLOCK -CLOCK -COLOR -COUNT -DUMMY -EMPTY -ENDDO -ENDON -GREEN -INDEX -INOUT -LEAVE -LEVEL -LINES -MODIF -ORDER -OUTER -RANGE -RESET -RETRY -RIGHT -SMART -SPLIT -STYLE -TABLE -THROW -UNDER -UNTIL -UPPER -UTF-8 -WHERE -ALIAS -BLANK -CLEAR -CLOSE -EXACT -FETCH -FIRST -FOUND -GROUP -LLANG -LOCAL -OTHER -REGEX -SPOOL -TITLE -TYPES -VALID -WHILE -ALPHA -BOXED -CATCH -CHAIN -CHECK -CLASS -COVER -ENDIF -EQUIV -FIELD -FLOOR -FRAME -INPUT -LOWER -MATCH -NODES -PAGES -PRINT -RAISE -ROUND -SHIFT -SPACE -SPOTS -STAMP -STATE -TASKS -TIMES -TRMAC -ULINE -UNION -VALUE -WIDTH -EQUAL -LOG10 -TRUNC -BLOB -CASE -CEIL -CLOB -COND -EXIT -FILE -GAPS -HOLD -INCL -INTO -KEEP -KEYS -LAST -LINE -LONG -LPAD -MAIL -MODE -OPEN -PINK -READ -ROWS -TEST -THEN -ZERO -AREA -BACK -BADI -BYTE -CAST -EDIT -EXEC -FAIL -FIND -FKEQ -FONT -FREE -GKEQ -HIDE -INIT -ITNO -LATE -LOOP -MAIN -MARK -MOVE -NEXT -NULL -RISK -ROLE -UNIT -WAIT -ZONE -BASE -CALL -CODE -DATA -DATE -FKGE -GKGE -HIGH -KIND -LEFT -LIST -MASK -MESH -NAME -NODE -PACK -PAGE -POOL -SEND -SIGN -SIZE -SOME -STOP -TASK -TEXT -TIME -USER -VARY -WITH -WORD -BLUE -CONV -COPY -DEEP -ELSE -FORM -FROM -HINT -ICON -JOIN -LIKE -LOAD -ONLY -PART -SCAN -SKIP -SORT -TYPE -UNIX -VIEW -WHEN -WORK -ACOS -ASIN -ATAN -COSH -EACH -FRAC -LESS -RTTI -SINH -SQRT -TANH -AVG -BIT -DIV -ISO -LET -OUT -PAD -SQL -ALL -CI_ -CPI -END -LOB -LPI -MAX -MIN -NEW -OLE -RUN -SET -?TO -YES -ABS -ADD -AND -BIG -FOR -HDB -JOB -LOW -NOT -SAP -TRY -VIA -XML -ANY -GET -IDS -KEY -MOD -OFF -PUT -RAW -RED -REF -SUM -TAB -XSD -CNT -COS -EXP -LOG -SIN -TAN -XOR -AT -CO -CP -DO -GT -ID -IF -NS -OR -BT -CA -CS -GE -NA -NB -EQ -IN -LT -NE -NO -OF -ON -PF -TO -AS -BY -CN -IS -LE -NP -UP -E -I -M -O -Z -C -X - ----------------------------------------------------- - -[ - ["keyword", "SCIENTIFIC_WITH_LEADING_ZERO"], - ["keyword", "SCALE_PRESERVING_SCIENTIFIC"], - ["keyword", "RMC_COMMUNICATION_FAILURE"], - ["keyword", "END-ENHANCEMENT-SECTION"], - ["keyword", "MULTIPLY-CORRESPONDING"], - ["keyword", "SUBTRACT-CORRESPONDING"], - ["keyword", "VERIFICATION-MESSAGE"], - ["keyword", "DIVIDE-CORRESPONDING"], - ["keyword", "ENHANCEMENT-SECTION"], - ["keyword", "CURRENCY_CONVERSION"], - ["keyword", "RMC_SYSTEM_FAILURE"], - ["keyword", "START-OF-SELECTION"], - ["keyword", "MOVE-CORRESPONDING"], - ["keyword", "RMC_INVALID_STATUS"], - ["keyword", "CUSTOMER-FUNCTION"], - ["keyword", "END-OF-DEFINITION"], - ["keyword", "ENHANCEMENT-POINT"], - ["keyword", "SYSTEM-EXCEPTIONS"], - ["keyword", "ADD-CORRESPONDING"], - ["keyword", "SCALE_PRESERVING"], - ["keyword", "SELECTION-SCREEN"], - ["keyword", "CURSOR-SELECTION"], - ["keyword", "END-OF-SELECTION"], - ["keyword", "LOAD-OF-PROGRAM"], - ["keyword", "SCROLL-BOUNDARY"], - ["keyword", "SELECTION-TABLE"], - ["keyword", "EXCEPTION-TABLE"], - ["keyword", "IMPLEMENTATIONS"], - ["keyword", "PARAMETER-TABLE"], - ["keyword", "RIGHT-JUSTIFIED"], - ["keyword", "UNIT_CONVERSION"], - ["keyword", "AUTHORITY-CHECK"], - ["keyword", "LIST-PROCESSING"], - ["keyword", "SIGN_AS_POSTFIX"], - ["keyword", "COL_BACKGROUND"], - ["keyword", "IMPLEMENTATION"], - ["keyword", "INTERFACE-POOL"], - ["keyword", "TRANSFORMATION"], - ["keyword", "IDENTIFICATION"], - ["keyword", "ENDENHANCEMENT"], - ["keyword", "LINE-SELECTION"], - ["keyword", "INITIALIZATION"], - ["keyword", "LEFT-JUSTIFIED"], - ["keyword", "SELECT-OPTIONS"], - ["keyword", "SELECTION-SETS"], - ["keyword", "COMMUNICATION"], - ["keyword", "CORRESPONDING"], - ["keyword", "DECIMAL_SHIFT"], - ["keyword", "PRINT-CONTROL"], - ["keyword", "VALUE-REQUEST"], - ["keyword", "CHAIN-REQUEST"], - ["keyword", "FUNCTION-POOL"], - ["keyword", "FIELD-SYMBOLS"], - ["keyword", "FUNCTIONALITY"], - ["keyword", "INVERTED-DATE"], - ["keyword", "SELECTION-SET"], - ["keyword", "CLASS-METHODS"], - ["keyword", "OUTPUT-LENGTH"], - ["keyword", "CLASS-CODING"], - ["keyword", "COL_NEGATIVE"], - ["keyword", "ERRORMESSAGE"], - ["keyword", "FIELD-GROUPS"], - ["keyword", "HELP-REQUEST"], - ["keyword", "NO-EXTENSION"], - ["keyword", "NO-TOPOFPAGE"], - ["keyword", "REDEFINITION"], - ["keyword", "DISPLAY-MODE"], - ["keyword", "ENDINTERFACE"], - ["keyword", "EXIT-COMMAND"], - ["keyword", "FIELD-SYMBOL"], - ["keyword", "NO-SCROLLING"], - ["keyword", "SHORTDUMP-ID"], - ["keyword", "ACCESSPOLICY"], - ["keyword", "CLASS-EVENTS"], - ["keyword", "COL_POSITIVE"], - ["keyword", "DECLARATIONS"], - ["keyword", "ENHANCEMENTS"], - ["keyword", "FILTER-TABLE"], - ["keyword", "SWITCHSTATES"], - ["keyword", "SYNTAX-CHECK"], - ["keyword", "TRANSPORTING"], - ["keyword", "ASYNCHRONOUS"], - ["keyword", "SYNTAX-TRACE"], - ["keyword", "TOKENIZATION"], - ["keyword", "USER-COMMAND"], - ["keyword", "WITH-HEADING"], - ["keyword", "ABAP-SOURCE"], - ["keyword", "BREAK-POINT"], - ["keyword", "CHAIN-INPUT"], - ["keyword", "COMPRESSION"], - ["keyword", "FIXED-POINT"], - ["keyword", "NEW-SECTION"], - ["keyword", "NON-UNICODE"], - ["keyword", "OCCURRENCES"], - ["keyword", "RESPONSIBLE"], - ["keyword", "SYSTEM-CALL"], - ["keyword", "TRACE-TABLE"], - ["keyword", "ABBREVIATED"], - ["keyword", "CHAR-TO-HEX"], - ["keyword", "END-OF-FILE"], - ["keyword", "ENDFUNCTION"], - ["keyword", "ENVIRONMENT"], - ["keyword", "ASSOCIATION"], - ["keyword", "COL_HEADING"], - ["keyword", "EDITOR-CALL"], - ["keyword", "END-OF-PAGE"], - ["keyword", "ENGINEERING"], - ["keyword", "IMPLEMENTED"], - ["keyword", "INTENSIFIED"], - ["keyword", "RADIOBUTTON"], - ["keyword", "SYSTEM-EXIT"], - ["keyword", "TOP-OF-PAGE"], - ["keyword", "TRANSACTION"], - ["keyword", "APPLICATION"], - ["keyword", "CONCATENATE"], - ["keyword", "DESTINATION"], - ["keyword", "ENHANCEMENT"], - ["keyword", "IMMEDIATELY"], - ["keyword", "NO-GROUPING"], - ["keyword", "PRECOMPILED"], - ["keyword", "REPLACEMENT"], - ["keyword", "TITLE-LINES"], - ["keyword", "ACTIVATION"], - ["keyword", "BYTE-ORDER"], - ["keyword", "CLASS-POOL"], - ["keyword", "CONNECTION"], - ["keyword", "CONVERSION"], - ["keyword", "DEFINITION"], - ["keyword", "DEPARTMENT"], - ["keyword", "EXPIRATION"], - ["keyword", "INHERITING"], - ["keyword", "MESSAGE-ID"], - ["keyword", "NO-HEADING"], - ["keyword", "PERFORMING"], - ["keyword", "QUEUE-ONLY"], - ["keyword", "RIGHTSPACE"], - ["keyword", "SCIENTIFIC"], - ["keyword", "STATUSINFO"], - ["keyword", "STRUCTURES"], - ["keyword", "SYNCPOINTS"], - ["keyword", "WITH-TITLE"], - ["keyword", "ATTRIBUTES"], - ["keyword", "BOUNDARIES"], - ["keyword", "CLASS-DATA"], - ["keyword", "COL_NORMAL"], - ["keyword", "DD/MM/YYYY"], - ["keyword", "DESCENDING"], - ["keyword", "INTERFACES"], - ["keyword", "LINE-COUNT"], - ["keyword", "MM/DD/YYYY"], - ["keyword", "NON-UNIQUE"], - ["keyword", "PRESERVING"], - ["keyword", "SELECTIONS"], - ["keyword", "STATEMENTS"], - ["keyword", "SUBROUTINE"], - ["keyword", "TRUNCATION"], - ["keyword", "TYPE-POOLS"], - ["keyword", "ARITHMETIC"], - ["keyword", "BACKGROUND"], - ["keyword", "ENDPROVIDE"], - ["keyword", "EXCEPTIONS"], - ["keyword", "IDENTIFIER"], - ["keyword", "INDEX-LINE"], - ["keyword", "OBLIGATORY"], - ["keyword", "PARAMETERS"], - ["keyword", "PERCENTAGE"], - ["keyword", "PUSHBUTTON"], - ["keyword", "RESOLUTION"], - ["keyword", "COMPONENTS"], - ["keyword", "DEALLOCATE"], - ["keyword", "DISCONNECT"], - ["keyword", "DUPLICATES"], - ["keyword", "FIRST-LINE"], - ["keyword", "HEAD-LINES"], - ["keyword", "NO-DISPLAY"], - ["keyword", "OCCURRENCE"], - ["keyword", "RESPECTING"], - ["keyword", "RETURNCODE"], - ["keyword", "SUBMATCHES"], - ["keyword", "TRACE-FILE"], - ["keyword", "ASCENDING"], - ["keyword", "BYPASSING"], - ["keyword", "ENDMODULE"], - ["keyword", "EXCEPTION"], - ["keyword", "EXCLUDING"], - ["keyword", "EXPORTING"], - ["keyword", "INCREMENT"], - ["keyword", "MATCHCODE"], - ["keyword", "PARAMETER"], - ["keyword", "PARTIALLY"], - ["keyword", "PREFERRED"], - ["keyword", "REFERENCE"], - ["keyword", "REPLACING"], - ["keyword", "RETURNING"], - ["keyword", "SELECTION"], - ["keyword", "SEPARATED"], - ["keyword", "SPECIFIED"], - ["keyword", "STATEMENT"], - ["keyword", "TIMESTAMP"], - ["keyword", "TYPE-POOL"], - ["keyword", "ACCEPTING"], - ["keyword", "APPENDAGE"], - ["keyword", "ASSIGNING"], - ["keyword", "COL_GROUP"], - ["keyword", "COMPARING"], - ["keyword", "CONSTANTS"], - ["keyword", "DANGEROUS"], - ["keyword", "IMPORTING"], - ["keyword", "INSTANCES"], - ["keyword", "LEFTSPACE"], - ["keyword", "LOG-POINT"], - ["keyword", "QUICKINFO"], - ["keyword", "READ-ONLY"], - ["keyword", "SCROLLING"], - ["keyword", "SQLSCRIPT"], - ["keyword", "STEP-LOOP"], - ["keyword", "TOP-LINES"], - ["keyword", "TRANSLATE"], - ["keyword", "APPENDING"], - ["keyword", "AUTHORITY"], - ["keyword", "CHARACTER"], - ["keyword", "COMPONENT"], - ["keyword", "CONDITION"], - ["keyword", "DIRECTORY"], - ["keyword", "DUPLICATE"], - ["keyword", "MESSAGING"], - ["keyword", "RECEIVING"], - ["keyword", "SUBSCREEN"], - ["keyword", "ACCORDING"], - ["keyword", "COL_TOTAL"], - ["keyword", "END-LINES"], - ["keyword", "ENDMETHOD"], - ["keyword", "ENDSELECT"], - ["keyword", "EXPANDING"], - ["keyword", "EXTENSION"], - ["keyword", "INCLUDING"], - ["keyword", "INFOTYPES"], - ["keyword", "INTERFACE"], - ["keyword", "INTERVALS"], - ["keyword", "LINE-SIZE"], - ["keyword", "PF-STATUS"], - ["keyword", "PROCEDURE"], - ["keyword", "PROTECTED"], - ["keyword", "REQUESTED"], - ["keyword", "RESUMABLE"], - ["keyword", "RIGHTPLUS"], - ["keyword", "SAP-SPOOL"], - ["keyword", "SECONDARY"], - ["keyword", "STRUCTURE"], - ["keyword", "SUBSTRING"], - ["keyword", "TABLEVIEW"], - ["keyword", "NUMOFCHAR"], - ["keyword", "ADJACENT"], - ["keyword", "ANALYSIS"], - ["keyword", "ASSIGNED"], - ["keyword", "BACKWARD"], - ["keyword", "CHANNELS"], - ["keyword", "CHECKBOX"], - ["keyword", "CONTINUE"], - ["keyword", "CRITICAL"], - ["keyword", "DATAINFO"], - ["keyword", "DD/MM/YY"], - ["keyword", "DURATION"], - ["keyword", "ENCODING"], - ["keyword", "ENDCLASS"], - ["keyword", "FUNCTION"], - ["keyword", "LEFTPLUS"], - ["keyword", "LINEFEED"], - ["keyword", "MM/DD/YY"], - ["keyword", "OVERFLOW"], - ["keyword", "RECEIVED"], - ["keyword", "SKIPPING"], - ["keyword", "SORTABLE"], - ["keyword", "STANDARD"], - ["keyword", "SUBTRACT"], - ["keyword", "SUPPRESS"], - ["keyword", "TABSTRIP"], - ["keyword", "TITLEBAR"], - ["keyword", "TRUNCATE"], - ["keyword", "UNASSIGN"], - ["keyword", "WHENEVER"], - ["keyword", "ANALYZER"], - ["keyword", "COALESCE"], - ["keyword", "COMMENTS"], - ["keyword", "CONDENSE"], - ["keyword", "DECIMALS"], - ["keyword", "DEFERRED"], - ["keyword", "ENDWHILE"], - ["keyword", "EXPLICIT"], - ["keyword", "KEYWORDS"], - ["keyword", "MESSAGES"], - ["keyword", "POSITION"], - ["keyword", "PRIORITY"], - ["keyword", "RECEIVER"], - ["keyword", "RENAMING"], - ["keyword", "TIMEZONE"], - ["keyword", "TRAILING"], - ["keyword", "ALLOCATE"], - ["keyword", "CENTERED"], - ["keyword", "CIRCULAR"], - ["keyword", "CONTROLS"], - ["keyword", "CURRENCY"], - ["keyword", "DELETING"], - ["keyword", "DESCRIBE"], - ["keyword", "DISTANCE"], - ["keyword", "ENDCATCH"], - ["keyword", "EXPONENT"], - ["keyword", "EXTENDED"], - ["keyword", "GENERATE"], - ["keyword", "IGNORING"], - ["keyword", "INCLUDES"], - ["keyword", "INTERNAL"], - ["keyword", "MAJOR-ID"], - ["keyword", "MODIFIER"], - ["keyword", "NEW-LINE"], - ["keyword", "OPTIONAL"], - ["keyword", "PROPERTY"], - ["keyword", "ROLLBACK"], - ["keyword", "STARTING"], - ["keyword", "SUPPLIED"], - ["keyword", "ABSTRACT"], - ["keyword", "CHANGING"], - ["keyword", "CONTEXTS"], - ["keyword", "CREATING"], - ["keyword", "CUSTOMER"], - ["keyword", "DATABASE"], - ["keyword", "DAYLIGHT"], - ["keyword", "DEFINING"], - ["keyword", "DISTINCT"], - ["keyword", "DIVISION"], - ["keyword", "ENABLING"], - ["keyword", "ENDCHAIN"], - ["keyword", "ESCAPING"], - ["keyword", "HARMLESS"], - ["keyword", "IMPLICIT"], - ["keyword", "INACTIVE"], - ["keyword", "LANGUAGE"], - ["keyword", "MINOR-ID"], - ["keyword", "MULTIPLY"], - ["keyword", "NEW-PAGE"], - ["keyword", "NO-TITLE"], - ["keyword", "POS_HIGH"], - ["keyword", "SEPARATE"], - ["keyword", "TEXTPOOL"], - ["keyword", "TRANSFER"], - ["keyword", "SELECTOR"], - ["keyword", "DBMAXLEN"], - ["keyword", "ITERATOR"], - ["keyword", "SELECTOR"], - ["keyword", "ARCHIVE"], - ["keyword", "BIT-XOR"], - ["keyword", "BYTE-CO"], - ["keyword", "COLLECT"], - ["keyword", "COMMENT"], - ["keyword", "CURRENT"], - ["keyword", "DEFAULT"], - ["keyword", "DISPLAY"], - ["keyword", "ENDFORM"], - ["keyword", "EXTRACT"], - ["keyword", "LEADING"], - ["keyword", "LISTBOX"], - ["keyword", "LOCATOR"], - ["keyword", "MEMBERS"], - ["keyword", "METHODS"], - ["keyword", "NESTING"], - ["keyword", "POS_LOW"], - ["keyword", "PROCESS"], - ["keyword", "PROVIDE"], - ["keyword", "RAISING"], - ["keyword", "RESERVE"], - ["keyword", "SECONDS"], - ["keyword", "SUMMARY"], - ["keyword", "VISIBLE"], - ["keyword", "BETWEEN"], - ["keyword", "BIT-AND"], - ["keyword", "BYTE-CS"], - ["keyword", "CLEANUP"], - ["keyword", "COMPUTE"], - ["keyword", "CONTROL"], - ["keyword", "CONVERT"], - ["keyword", "DATASET"], - ["keyword", "ENDCASE"], - ["keyword", "FORWARD"], - ["keyword", "HEADERS"], - ["keyword", "HOTSPOT"], - ["keyword", "INCLUDE"], - ["keyword", "INVERSE"], - ["keyword", "KEEPING"], - ["keyword", "NO-ZERO"], - ["keyword", "OBJECTS"], - ["keyword", "OVERLAY"], - ["keyword", "PADDING"], - ["keyword", "PATTERN"], - ["keyword", "PROGRAM"], - ["keyword", "REFRESH"], - ["keyword", "SECTION"], - ["keyword", "SUMMING"], - ["keyword", "TESTING"], - ["keyword", "VERSION"], - ["keyword", "WINDOWS"], - ["keyword", "WITHOUT"], - ["keyword", "BIT-NOT"], - ["keyword", "BYTE-CA"], - ["keyword", "BYTE-NA"], - ["keyword", "CASTING"], - ["keyword", "CONTEXT"], - ["keyword", "COUNTRY"], - ["keyword", "DYNAMIC"], - ["keyword", "ENABLED"], - ["keyword", "ENDLOOP"], - ["keyword", "EXECUTE"], - ["keyword", "FRIENDS"], - ["keyword", "HANDLER"], - ["keyword", "HEADING"], - ["keyword", "INITIAL"], - ["keyword", "*-INPUT"], - ["keyword", "LOGFILE"], - ["keyword", "MAXIMUM"], - ["keyword", "MINIMUM"], - ["keyword", "NO-GAPS"], - ["keyword", "NO-SIGN"], - ["keyword", "PRAGMAS"], - ["keyword", "PRIMARY"], - ["keyword", "PRIVATE"], - ["keyword", "REDUCED"], - ["keyword", "REPLACE"], - ["keyword", "REQUEST"], - ["keyword", "RESULTS"], - ["keyword", "UNICODE"], - ["keyword", "WARNING"], - ["keyword", "ALIASES"], - ["keyword", "BYTE-CN"], - ["keyword", "BYTE-NS"], - ["keyword", "CALLING"], - ["keyword", "COL_KEY"], - ["keyword", "COLUMNS"], - ["keyword", "CONNECT"], - ["keyword", "ENDEXEC"], - ["keyword", "ENTRIES"], - ["keyword", "EXCLUDE"], - ["keyword", "FILTERS"], - ["keyword", "FURTHER"], - ["keyword", "HELP-ID"], - ["keyword", "LOGICAL"], - ["keyword", "MAPPING"], - ["keyword", "MESSAGE"], - ["keyword", "NAMETAB"], - ["keyword", "OPTIONS"], - ["keyword", "PACKAGE"], - ["keyword", "PERFORM"], - ["keyword", "RECEIVE"], - ["keyword", "STATICS"], - ["keyword", "VARYING"], - ["keyword", "BINDING"], - ["keyword", "CHARLEN"], - ["keyword", "GREATER"], - ["keyword", "XSTRLEN"], - ["keyword", "ACCEPT"], - ["keyword", "APPEND"], - ["keyword", "DETAIL"], - ["keyword", "ELSEIF"], - ["keyword", "ENDING"], - ["keyword", "ENDTRY"], - ["keyword", "FORMAT"], - ["keyword", "FRAMES"], - ["keyword", "GIVING"], - ["keyword", "HASHED"], - ["keyword", "HEADER"], - ["keyword", "IMPORT"], - ["keyword", "INSERT"], - ["keyword", "MARGIN"], - ["keyword", "MODULE"], - ["keyword", "NATIVE"], - ["keyword", "OBJECT"], - ["keyword", "OFFSET"], - ["keyword", "REMOTE"], - ["keyword", "RESUME"], - ["keyword", "SAVING"], - ["keyword", "SIMPLE"], - ["keyword", "SUBMIT"], - ["keyword", "TABBED"], - ["keyword", "TOKENS"], - ["keyword", "UNIQUE"], - ["keyword", "UNPACK"], - ["keyword", "UPDATE"], - ["keyword", "WINDOW"], - ["keyword", "YELLOW"], - ["keyword", "ACTUAL"], - ["keyword", "ASPECT"], - ["keyword", "CENTER"], - ["keyword", "CURSOR"], - ["keyword", "DELETE"], - ["keyword", "DIALOG"], - ["keyword", "DIVIDE"], - ["keyword", "DURING"], - ["keyword", "ERRORS"], - ["keyword", "EVENTS"], - ["keyword", "EXTEND"], - ["keyword", "FILTER"], - ["keyword", "HANDLE"], - ["keyword", "HAVING"], - ["keyword", "IGNORE"], - ["keyword", "LITTLE"], - ["keyword", "MEMORY"], - ["keyword", "NO-GAP"], - ["keyword", "OCCURS"], - ["keyword", "OPTION"], - ["keyword", "PERSON"], - ["keyword", "PLACES"], - ["keyword", "PUBLIC"], - ["keyword", "REDUCE"], - ["keyword", "REPORT"], - ["keyword", "RESULT"], - ["keyword", "SINGLE"], - ["keyword", "SORTED"], - ["keyword", "SWITCH"], - ["keyword", "SYNTAX"], - ["keyword", "TARGET"], - ["keyword", "VALUES"], - ["keyword", "WRITER"], - ["keyword", "ASSERT"], - ["keyword", "BLOCKS"], - ["keyword", "BOUNDS"], - ["keyword", "BUFFER"], - ["keyword", "CHANGE"], - ["keyword", "COLUMN"], - ["keyword", "COMMIT"], - ["keyword", "CONCAT"], - ["keyword", "COPIES"], - ["keyword", "CREATE"], - ["keyword", "DDMMYY"], - ["keyword", "DEFINE"], - ["keyword", "ENDIAN"], - ["keyword", "ESCAPE"], - ["keyword", "EXPAND"], - ["keyword", "KERNEL"], - ["keyword", "LAYOUT"], - ["keyword", "LEGACY"], - ["keyword", "LEVELS"], - ["keyword", "MMDDYY"], - ["keyword", "NUMBER"], - ["keyword", "OUTPUT"], - ["keyword", "RANGES"], - ["keyword", "READER"], - ["keyword", "RETURN"], - ["keyword", "SCREEN"], - ["keyword", "SEARCH"], - ["keyword", "SELECT"], - ["keyword", "SHARED"], - ["keyword", "SOURCE"], - ["keyword", "STABLE"], - ["keyword", "STATIC"], - ["keyword", "SUBKEY"], - ["keyword", "SUFFIX"], - ["keyword", "TABLES"], - ["keyword", "UNWIND"], - ["keyword", "YYMMDD"], - ["keyword", "ASSIGN"], - ["keyword", "BACKUP"], - ["keyword", "BEFORE"], - ["keyword", "BINARY"], - ["keyword", "BIT-OR"], - ["keyword", "BLANKS"], - ["keyword", "CLIENT"], - ["keyword", "CODING"], - ["keyword", "COMMON"], - ["keyword", "DEMAND"], - ["keyword", "DYNPRO"], - ["keyword", "EXCEPT"], - ["keyword", "EXISTS"], - ["keyword", "EXPORT"], - ["keyword", "FIELDS"], - ["keyword", "GLOBAL"], - ["keyword", "GROUPS"], - ["keyword", "LENGTH"], - ["keyword", "LOCALE"], - ["keyword", "MEDIUM"], - ["keyword", "METHOD"], - ["keyword", "MODIFY"], - ["keyword", "NESTED"], - ["keyword", "OTHERS"], - ["keyword", "REJECT"], - ["keyword", "SCROLL"], - ["keyword", "SUPPLY"], - ["keyword", "SYMBOL"], - ["keyword", "ENDFOR"], - ["keyword", "STRLEN"], - ["keyword", "ALIGN"], - ["keyword", "BEGIN"], - ["keyword", "BOUND"], - ["keyword", "ENDAT"], - ["keyword", "ENTRY"], - ["keyword", "EVENT"], - ["keyword", "FINAL"], - ["keyword", "FLUSH"], - ["keyword", "GRANT"], - ["keyword", "INNER"], - ["keyword", "SHORT"], - ["keyword", "USING"], - ["keyword", "WRITE"], - ["keyword", "AFTER"], - ["keyword", "BLACK"], - ["keyword", "BLOCK"], - ["keyword", "CLOCK"], - ["keyword", "COLOR"], - ["keyword", "COUNT"], - ["keyword", "DUMMY"], - ["keyword", "EMPTY"], - ["keyword", "ENDDO"], - ["keyword", "ENDON"], - ["keyword", "GREEN"], - ["keyword", "INDEX"], - ["keyword", "INOUT"], - ["keyword", "LEAVE"], - ["keyword", "LEVEL"], - ["keyword", "LINES"], - ["keyword", "MODIF"], - ["keyword", "ORDER"], - ["keyword", "OUTER"], - ["keyword", "RANGE"], - ["keyword", "RESET"], - ["keyword", "RETRY"], - ["keyword", "RIGHT"], - ["keyword", "SMART"], - ["keyword", "SPLIT"], - ["keyword", "STYLE"], - ["keyword", "TABLE"], - ["keyword", "THROW"], - ["keyword", "UNDER"], - ["keyword", "UNTIL"], - ["keyword", "UPPER"], - ["keyword", "UTF-8"], - ["keyword", "WHERE"], - ["keyword", "ALIAS"], - ["keyword", "BLANK"], - ["keyword", "CLEAR"], - ["keyword", "CLOSE"], - ["keyword", "EXACT"], - ["keyword", "FETCH"], - ["keyword", "FIRST"], - ["keyword", "FOUND"], - ["keyword", "GROUP"], - ["keyword", "LLANG"], - ["keyword", "LOCAL"], - ["keyword", "OTHER"], - ["keyword", "REGEX"], - ["keyword", "SPOOL"], - ["keyword", "TITLE"], - ["keyword", "TYPES"], - ["keyword", "VALID"], - ["keyword", "WHILE"], - ["keyword", "ALPHA"], - ["keyword", "BOXED"], - ["keyword", "CATCH"], - ["keyword", "CHAIN"], - ["keyword", "CHECK"], - ["keyword", "CLASS"], - ["keyword", "COVER"], - ["keyword", "ENDIF"], - ["keyword", "EQUIV"], - ["keyword", "FIELD"], - ["keyword", "FLOOR"], - ["keyword", "FRAME"], - ["keyword", "INPUT"], - ["keyword", "LOWER"], - ["keyword", "MATCH"], - ["keyword", "NODES"], - ["keyword", "PAGES"], - ["keyword", "PRINT"], - ["keyword", "RAISE"], - ["keyword", "ROUND"], - ["keyword", "SHIFT"], - ["keyword", "SPACE"], - ["keyword", "SPOTS"], - ["keyword", "STAMP"], - ["keyword", "STATE"], - ["keyword", "TASKS"], - ["keyword", "TIMES"], - ["keyword", "TRMAC"], - ["keyword", "ULINE"], - ["keyword", "UNION"], - ["keyword", "VALUE"], - ["keyword", "WIDTH"], - ["keyword", "EQUAL"], - ["keyword", "LOG10"], - ["keyword", "TRUNC"], - ["keyword", "BLOB"], - ["keyword", "CASE"], - ["keyword", "CEIL"], - ["keyword", "CLOB"], - ["keyword", "COND"], - ["keyword", "EXIT"], - ["keyword", "FILE"], - ["keyword", "GAPS"], - ["keyword", "HOLD"], - ["keyword", "INCL"], - ["keyword", "INTO"], - ["keyword", "KEEP"], - ["keyword", "KEYS"], - ["keyword", "LAST"], - ["keyword", "LINE"], - ["keyword", "LONG"], - ["keyword", "LPAD"], - ["keyword", "MAIL"], - ["keyword", "MODE"], - ["keyword", "OPEN"], - ["keyword", "PINK"], - ["keyword", "READ"], - ["keyword", "ROWS"], - ["keyword", "TEST"], - ["keyword", "THEN"], - ["keyword", "ZERO"], - ["keyword", "AREA"], - ["keyword", "BACK"], - ["keyword", "BADI"], - ["keyword", "BYTE"], - ["keyword", "CAST"], - ["keyword", "EDIT"], - ["keyword", "EXEC"], - ["keyword", "FAIL"], - ["keyword", "FIND"], - ["keyword", "FKEQ"], - ["keyword", "FONT"], - ["keyword", "FREE"], - ["keyword", "GKEQ"], - ["keyword", "HIDE"], - ["keyword", "INIT"], - ["keyword", "ITNO"], - ["keyword", "LATE"], - ["keyword", "LOOP"], - ["keyword", "MAIN"], - ["keyword", "MARK"], - ["keyword", "MOVE"], - ["keyword", "NEXT"], - ["keyword", "NULL"], - ["keyword", "RISK"], - ["keyword", "ROLE"], - ["keyword", "UNIT"], - ["keyword", "WAIT"], - ["keyword", "ZONE"], - ["keyword", "BASE"], - ["keyword", "CALL"], - ["keyword", "CODE"], - ["keyword", "DATA"], - ["keyword", "DATE"], - ["keyword", "FKGE"], - ["keyword", "GKGE"], - ["keyword", "HIGH"], - ["keyword", "KIND"], - ["keyword", "LEFT"], - ["keyword", "LIST"], - ["keyword", "MASK"], - ["keyword", "MESH"], - ["keyword", "NAME"], - ["keyword", "NODE"], - ["keyword", "PACK"], - ["keyword", "PAGE"], - ["keyword", "POOL"], - ["keyword", "SEND"], - ["keyword", "SIGN"], - ["keyword", "SIZE"], - ["keyword", "SOME"], - ["keyword", "STOP"], - ["keyword", "TASK"], - ["keyword", "TEXT"], - ["keyword", "TIME"], - ["keyword", "USER"], - ["keyword", "VARY"], - ["keyword", "WITH"], - ["keyword", "WORD"], - ["keyword", "BLUE"], - ["keyword", "CONV"], - ["keyword", "COPY"], - ["keyword", "DEEP"], - ["keyword", "ELSE"], - ["keyword", "FORM"], - ["keyword", "FROM"], - ["keyword", "HINT"], - ["keyword", "ICON"], - ["keyword", "JOIN"], - ["keyword", "LIKE"], - ["keyword", "LOAD"], - ["keyword", "ONLY"], - ["keyword", "PART"], - ["keyword", "SCAN"], - ["keyword", "SKIP"], - ["keyword", "SORT"], - ["keyword", "TYPE"], - ["keyword", "UNIX"], - ["keyword", "VIEW"], - ["keyword", "WHEN"], - ["keyword", "WORK"], - ["keyword", "ACOS"], - ["keyword", "ASIN"], - ["keyword", "ATAN"], - ["keyword", "COSH"], - ["keyword", "EACH"], - ["keyword", "FRAC"], - ["keyword", "LESS"], - ["keyword", "RTTI"], - ["keyword", "SINH"], - ["keyword", "SQRT"], - ["keyword", "TANH"], - ["keyword", "AVG"], - ["keyword", "BIT"], - ["keyword", "DIV"], - ["keyword", "ISO"], - ["keyword", "LET"], - ["keyword", "OUT"], - ["keyword", "PAD"], - ["keyword", "SQL"], - ["keyword", "ALL"], - ["keyword", "CI_"], - ["keyword", "CPI"], - ["keyword", "END"], - ["keyword", "LOB"], - ["keyword", "LPI"], - ["keyword", "MAX"], - ["keyword", "MIN"], - ["keyword", "NEW"], - ["keyword", "OLE"], - ["keyword", "RUN"], - ["keyword", "SET"], - ["keyword", "?TO"], - ["keyword", "YES"], - ["keyword", "ABS"], - ["keyword", "ADD"], - ["keyword", "AND"], - ["keyword", "BIG"], - ["keyword", "FOR"], - ["keyword", "HDB"], - ["keyword", "JOB"], - ["keyword", "LOW"], - ["keyword", "NOT"], - ["keyword", "SAP"], - ["keyword", "TRY"], - ["keyword", "VIA"], - ["keyword", "XML"], - ["keyword", "ANY"], - ["keyword", "GET"], - ["keyword", "IDS"], - ["keyword", "KEY"], - ["keyword", "MOD"], - ["keyword", "OFF"], - ["keyword", "PUT"], - ["keyword", "RAW"], - ["keyword", "RED"], - ["keyword", "REF"], - ["keyword", "SUM"], - ["keyword", "TAB"], - ["keyword", "XSD"], - ["keyword", "CNT"], - ["keyword", "COS"], - ["keyword", "EXP"], - ["keyword", "LOG"], - ["keyword", "SIN"], - ["keyword", "TAN"], - ["keyword", "XOR"], - ["keyword", "AT"], - ["keyword", "CO"], - ["keyword", "CP"], - ["keyword", "DO"], - ["keyword", "GT"], - ["keyword", "ID"], - ["keyword", "IF"], - ["keyword", "NS"], - ["keyword", "OR"], - ["keyword", "BT"], - ["keyword", "CA"], - ["keyword", "CS"], - ["keyword", "GE"], - ["keyword", "NA"], - ["keyword", "NB"], - ["keyword", "EQ"], - ["keyword", "IN"], - ["keyword", "LT"], - ["keyword", "NE"], - ["keyword", "NO"], - ["keyword", "OF"], - ["keyword", "ON"], - ["keyword", "PF"], - ["keyword", "TO"], - ["keyword", "AS"], - ["keyword", "BY"], - ["keyword", "CN"], - ["keyword", "IS"], - ["keyword", "LE"], - ["keyword", "NP"], - ["keyword", "UP"], - ["keyword", "E"], - ["keyword", "I"], - ["keyword", "M"], - ["keyword", "O"], - ["keyword", "Z"], - ["keyword", "C"], - ["keyword", "X"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/number_feature.test b/docs/_style/prism-master/tests/languages/abap/number_feature.test deleted file mode 100644 index 0c9556e7..00000000 --- a/docs/_style/prism-master/tests/languages/abap/number_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -0 -42 -123456789 - ----------------------------------------------------- - -[ - ["number", "0"], - ["number", "42"], - ["number", "123456789"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/operator_feature.test b/docs/_style/prism-master/tests/languages/abap/operator_feature.test deleted file mode 100644 index 23cb4a04..00000000 --- a/docs/_style/prism-master/tests/languages/abap/operator_feature.test +++ /dev/null @@ -1,38 +0,0 @@ -. -+ - -/ * ** -< > <= >= -= ?= <> - -& && - -a-b -a~b -a->b -a=>b -a|b -a{b}c - ----------------------------------------------------- - -[ - ["punctuation", "."], - ["operator", "+"], ["operator", "-"], - ["operator", "/"], ["operator", "*"], ["operator", "**"], - ["operator", "<"], ["operator", ">"], ["operator", "<="], ["operator", ">="], - ["operator", "="], ["operator", "?="], ["operator", "<>"], - - ["string-operator", "&"], ["string-operator", "&&"], - - "\r\n\r\na", ["token-operator", "-"], - "b\r\na", ["token-operator", "~"], - "b\r\na", ["token-operator", "->"], - "b\r\na", ["token-operator", "=>"], - "b\r\na", ["token-operator", "|"], - "b\r\na", ["token-operator", "{"], "b", ["token-operator", "}"], "c" -] - ----------------------------------------------------- - -Checks for operators, string-operators and token-operators. -The leading dot serves only because tests are trimmed. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/string-template_feature.test b/docs/_style/prism-master/tests/languages/abap/string-template_feature.test deleted file mode 100644 index 7979fbef..00000000 --- a/docs/_style/prism-master/tests/languages/abap/string-template_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -|foobar| -|foo\|b\{a}r| -|foo { bar } baz| - ----------------------------------------------------- - -[ - ["token-operator", "|"], ["string-template", "foobar"], ["token-operator", "|"], - ["token-operator", "|"], ["string-template", "foo\\|b\\{a}r"], ["token-operator", "|"], - ["token-operator", "|"], ["string-template", "foo "], ["token-operator", "{"], - " bar ", - ["token-operator", "}"], ["string-template", " baz"], ["token-operator", "|"] -] - ----------------------------------------------------- - -Checks for string templates. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/abap/string_feature.test b/docs/_style/prism-master/tests/languages/abap/string_feature.test deleted file mode 100644 index 808b0271..00000000 --- a/docs/_style/prism-master/tests/languages/abap/string_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -'' -'foo' -'foo\'bar' -`` -`foo` -`foo\`bar` - ----------------------------------------------------- - -[ - ["string", "''"], - ["string", "'foo'"], - ["string", "'foo\\'bar'"], - ["string", "``"], - ["string", "`foo`"], - ["string", "`foo\\`bar`"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/actionscript/keyword_feature.test b/docs/_style/prism-master/tests/languages/actionscript/keyword_feature.test deleted file mode 100644 index c41a8524..00000000 --- a/docs/_style/prism-master/tests/languages/actionscript/keyword_feature.test +++ /dev/null @@ -1,71 +0,0 @@ -as; break; case; catch; class; -const; default; delete; do; else; -extends; finally; for; function; if; -implements; import; in; instanceof; interface; -internal; is; native; new; null; -package; private; protected; public; return; -super; switch; this; throw; try; -typeof; use; var; void; while; -with; dynamic; each; final; get; -include; namespace; native; override; set; -static; - ----------------------------------------------------- - -[ - ["keyword", "as"], ["punctuation", ";"], - ["keyword", "break"], ["punctuation", ";"], - ["keyword", "case"], ["punctuation", ";"], - ["keyword", "catch"], ["punctuation", ";"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], ["punctuation", ";"], - ["keyword", "default"], ["punctuation", ";"], - ["keyword", "delete"], ["punctuation", ";"], - ["keyword", "do"], ["punctuation", ";"], - ["keyword", "else"], ["punctuation", ";"], - ["keyword", "extends"], ["punctuation", ";"], - ["keyword", "finally"], ["punctuation", ";"], - ["keyword", "for"], ["punctuation", ";"], - ["keyword", "function"], ["punctuation", ";"], - ["keyword", "if"], ["punctuation", ";"], - ["keyword", "implements"], ["punctuation", ";"], - ["keyword", "import"], ["punctuation", ";"], - ["keyword", "in"], ["punctuation", ";"], - ["keyword", "instanceof"], ["punctuation", ";"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "internal"], ["punctuation", ";"], - ["keyword", "is"], ["punctuation", ";"], - ["keyword", "native"], ["punctuation", ";"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "null"], ["punctuation", ";"], - ["keyword", "package"], ["punctuation", ";"], - ["keyword", "private"], ["punctuation", ";"], - ["keyword", "protected"], ["punctuation", ";"], - ["keyword", "public"], ["punctuation", ";"], - ["keyword", "return"], ["punctuation", ";"], - ["keyword", "super"], ["punctuation", ";"], - ["keyword", "switch"], ["punctuation", ";"], - ["keyword", "this"], ["punctuation", ";"], - ["keyword", "throw"], ["punctuation", ";"], - ["keyword", "try"], ["punctuation", ";"], - ["keyword", "typeof"], ["punctuation", ";"], - ["keyword", "use"], ["punctuation", ";"], - ["keyword", "var"], ["punctuation", ";"], - ["keyword", "void"], ["punctuation", ";"], - ["keyword", "while"], ["punctuation", ";"], - ["keyword", "with"], ["punctuation", ";"], - ["keyword", "dynamic"], ["punctuation", ";"], - ["keyword", "each"], ["punctuation", ";"], - ["keyword", "final"], ["punctuation", ";"], - ["keyword", "get"], ["punctuation", ";"], - ["keyword", "include"], ["punctuation", ";"], - ["keyword", "namespace"], ["punctuation", ";"], - ["keyword", "native"], ["punctuation", ";"], - ["keyword", "override"], ["punctuation", ";"], - ["keyword", "set"], ["punctuation", ";"], - ["keyword", "static"], ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/actionscript/operator_feature.test b/docs/_style/prism-master/tests/languages/actionscript/operator_feature.test deleted file mode 100644 index 14c007c7..00000000 --- a/docs/_style/prism-master/tests/languages/actionscript/operator_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -+ - * / % ^ -+= -= *= /= %= ^= -& && | || -&= &&= |= ||= -< << > >> >>> -<= <<= >= >>= >>>= -! != = == -!== === -~ ? @ -++ -- - ----------------------------------------------------- - -[ - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], ["operator", "%"], ["operator", "^"], - ["operator", "+="], ["operator", "-="], ["operator", "*="], ["operator", "/="], ["operator", "%="], ["operator", "^="], - ["operator", "&"], ["operator", "&&"], ["operator", "|"], ["operator", "||"], - ["operator", "&="], ["operator", "&&="], ["operator", "|="], ["operator", "||="], - ["operator", "<"], ["operator", "<<"], ["operator", ">"], ["operator", ">>"], ["operator", ">>>"], - ["operator", "<="], ["operator", "<<="], ["operator", ">="], ["operator", ">>="], ["operator", ">>>="], - ["operator", "!"], ["operator", "!="], ["operator", "="], ["operator", "=="], - ["operator", "!=="], ["operator", "==="], - ["operator", "~"], ["operator", "?"], ["operator", "@"], - ["operator", "++"], ["operator", "--"] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/attr-name_feature.test b/docs/_style/prism-master/tests/languages/ada/attr-name_feature.test deleted file mode 100644 index c30a2f57..00000000 --- a/docs/_style/prism-master/tests/languages/ada/attr-name_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -Integer'Size -Character'Val - ----------------------------------------------------- - -[ - ["variable", "Integer"], ["attr-name", "'Size"], - ["variable", "Character"], ["attr-name", "'Val"] -] - ----------------------------------------------------- - -Checks for attributes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/boolean_feature.test b/docs/_style/prism-master/tests/languages/ada/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/ada/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/char_feature.test b/docs/_style/prism-master/tests/languages/ada/char_feature.test deleted file mode 100644 index 145f2cee..00000000 --- a/docs/_style/prism-master/tests/languages/ada/char_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -'f' -'A' - ----------------------------------------------------- - -[ - ["char", "'f'"], - ["char", "'A'"] -] - ----------------------------------------------------- - -Checks for chars. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/comment_feature.test b/docs/_style/prism-master/tests/languages/ada/comment_feature.test deleted file mode 100644 index adf3e0ce..00000000 --- a/docs/_style/prism-master/tests/languages/ada/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ --- --- Foo bar - ----------------------------------------------------- - -[ - ["comment", "--"], - ["comment", "-- Foo bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/keyword_feature.test b/docs/_style/prism-master/tests/languages/ada/keyword_feature.test deleted file mode 100644 index e5c815f2..00000000 --- a/docs/_style/prism-master/tests/languages/ada/keyword_feature.test +++ /dev/null @@ -1,153 +0,0 @@ -abort -abs -abstract -accept -access -aliased -all -and -array -at -begin -body -case -constant -declare -delay -delta -digits -do -else -new -return -elsif -end -entry -exception -exit -for -function -generic -goto -if -in -interface -is -limited -loop -mod -not -null -of -others -out -overriding -package -pragma -private -procedure -protected -raise -range -record -rem -renames -requeue -reverse -select -separate -some -subtype -synchronized -tagged -task -terminate -then -type -until -use -when -while -with -xor - ----------------------------------------------------- - -[ - ["keyword", "abort"], - ["keyword", "abs"], - ["keyword", "abstract"], - ["keyword", "accept"], - ["keyword", "access"], - ["keyword", "aliased"], - ["keyword", "all"], - ["keyword", "and"], - ["keyword", "array"], - ["keyword", "at"], - ["keyword", "begin"], - ["keyword", "body"], - ["keyword", "case"], - ["keyword", "constant"], - ["keyword", "declare"], - ["keyword", "delay"], - ["keyword", "delta"], - ["keyword", "digits"], - ["keyword", "do"], - ["keyword", "else"], - ["keyword", "new"], - ["keyword", "return"], - ["keyword", "elsif"], - ["keyword", "end"], - ["keyword", "entry"], - ["keyword", "exception"], - ["keyword", "exit"], - ["keyword", "for"], - ["keyword", "function"], - ["keyword", "generic"], - ["keyword", "goto"], - ["keyword", "if"], - ["keyword", "in"], - ["keyword", "interface"], - ["keyword", "is"], - ["keyword", "limited"], - ["keyword", "loop"], - ["keyword", "mod"], - ["keyword", "not"], - ["keyword", "null"], - ["keyword", "of"], - ["keyword", "others"], - ["keyword", "out"], - ["keyword", "overriding"], - ["keyword", "package"], - ["keyword", "pragma"], - ["keyword", "private"], - ["keyword", "procedure"], - ["keyword", "protected"], - ["keyword", "raise"], - ["keyword", "range"], - ["keyword", "record"], - ["keyword", "rem"], - ["keyword", "renames"], - ["keyword", "requeue"], - ["keyword", "reverse"], - ["keyword", "select"], - ["keyword", "separate"], - ["keyword", "some"], - ["keyword", "subtype"], - ["keyword", "synchronized"], - ["keyword", "tagged"], - ["keyword", "task"], - ["keyword", "terminate"], - ["keyword", "then"], - ["keyword", "type"], - ["keyword", "until"], - ["keyword", "use"], - ["keyword", "when"], - ["keyword", "while"], - ["keyword", "with"], - ["keyword", "xor"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/number_feature.test b/docs/_style/prism-master/tests/languages/ada/number_feature.test deleted file mode 100644 index a27f1af8..00000000 --- a/docs/_style/prism-master/tests/languages/ada/number_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -42 -42_000 -3.14_15_9 -0.4E+123_456 -3.7e-7 -1_6#Bad_Face#E-32_1 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "42_000"], - ["number", "3.14_15_9"], - ["number", "0.4E+123_456"], - ["number", "3.7e-7"], - ["number", "1_6#Bad_Face#E-32_1"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/operator_feature.test b/docs/_style/prism-master/tests/languages/ada/operator_feature.test deleted file mode 100644 index 21a31374..00000000 --- a/docs/_style/prism-master/tests/languages/ada/operator_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -<> -=> := -< <= -> >= -= /= -& + - -* ** / - ----------------------------------------------------- - -[ - ["operator", "<>"], - ["operator", "=>"], ["operator", ":="], - ["operator", "<"], ["operator", "<="], - ["operator", ">"], ["operator", ">="], - ["operator", "="], ["operator", "/="], - ["operator", "&"], ["operator", "+"], ["operator", "-"], - ["operator", "*"], ["operator", "**"], ["operator", "/"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/string_feature.test b/docs/_style/prism-master/tests/languages/ada/string_feature.test deleted file mode 100644 index c44e9cd0..00000000 --- a/docs/_style/prism-master/tests/languages/ada/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"Foo""bar""" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"Foo\"\"bar\"\"\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ada/variable_feature.test b/docs/_style/prism-master/tests/languages/ada/variable_feature.test deleted file mode 100644 index f47a4fc4..00000000 --- a/docs/_style/prism-master/tests/languages/ada/variable_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -Byte -foo_bar42 - ----------------------------------------------------- - -[ - ["variable", "Byte"], - ["variable", "foo_bar42"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/comment_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/comment_feature.test deleted file mode 100644 index bde22880..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -#foo -# bar -# Redirect 301 /2006/oldfile.html http://subdomain.domain.tld/newfile.html - ----------------------------------------------------- - -[ - ["comment", "#foo"], - ["comment", "# bar"], - ["comment", "# Redirect 301 /2006/oldfile.html http://subdomain.domain.tld/newfile.html"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/directive-block_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/directive-block_feature.test deleted file mode 100644 index 1af6addd..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/directive-block_feature.test +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ----------------------------------------------------- - -[ - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "AuthnProviderAlias" - ]], - ["directive-block-parameter", [ - " file file2" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "AuthzProviderAlias" - ]], - ["directive-block-parameter", [ - " ldap-group ldap-group-alias1 ", - ["string", [ - "\"cn=my-group,o=ctx\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Directory" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"/webpages/secure\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "DirectoryMatch" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"^/www/(.+/)?[0-9]{3}\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Else" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "ElseIf" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"-R '10.0.0.0/8'\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Files" - ]], - ["directive-block-parameter", [ - " ~ ", - ["string", [ - "\"\\.(gif|jpe?g|png)$\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "FilesMatch" - ]], - ["directive-block-parameter", [ - ["string", [ - "\".+\\.(gif|jpe?g|png)$\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "If" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"-z req('Host')\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "IfDefine" - ]], - ["directive-block-parameter", [ - " !MemCache" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "IfModule" - ]], - ["directive-block-parameter", [ - " mod_rewrite.c" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "IfVersion" - ]], - ["directive-block-parameter", [ - " 2.1.0" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Limit" - ]], - ["directive-block-parameter", [ - " POST PUT DELETE" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "LimitExcept" - ]], - ["directive-block-parameter", [ - " POST GET" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Location" - ]], - ["directive-block-parameter", [ - " /private1" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "LocationMatch" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"/(extra|special)/data\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Macro" - ]], - ["directive-block-parameter", [ - " LocalAccessPolicy" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "Proxy" - ]], - ["directive-block-parameter", [ - ["string", [ - "\"*\"" - ]] - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "RequireAll" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "RequireAny" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "RequireNone" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]], - - ["directive-block", [ - ["directive-block", [ - ["punctuation", "<"], - "VirtualHost" - ]], - ["directive-block-parameter", [ - " [2001", - ["punctuation", ":"], - "db8", - ["punctuation", ":"], - ["punctuation", ":"], - "a00", - ["punctuation", ":"], - "20ff", - ["punctuation", ":"], - "fea7", - ["punctuation", ":"], - "ccea]", - ["punctuation", ":"], - "80" - ]], - ["punctuation", ">"] - ]], - ["directive-block", [ - ["directive-block", [ - ["punctuation", ""] - ]] -] - ----------------------------------------------------- - -Checks for directive blocks. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/directive-flags_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/directive-flags_feature.test deleted file mode 100644 index 0bb206ec..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/directive-flags_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -[OR] -[L,QSA] - ----------------------------------------------------- - -[ - ["directive-flags", "[OR]"], - ["directive-flags", "[L,QSA]"] -] - ----------------------------------------------------- - -Checks for directive flags. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/directive-inline_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/directive-inline_feature.test deleted file mode 100644 index 6b4df43b..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/directive-inline_feature.test +++ /dev/null @@ -1,1163 +0,0 @@ -AcceptFilter -AcceptPathInfo -AccessFileName -Action -AddAlt -AddAltByEncoding -AddAltByType -AddCharset -AddDefaultCharset -AddDescription -AddEncoding -AddHandler -AddIcon -AddIconByEncoding -AddIconByType -AddInputFilter -AddLanguage -AddModuleInfo -AddOutputFilter -AddOutputFilterByType -AddType -Alias -AliasMatch -Allow -AllowCONNECT -AllowEncodedSlashes -AllowMethods -AllowOverride -AllowOverrideList -Anonymous -Anonymous_LogEmail -Anonymous_MustGiveEmail -Anonymous_NoUserID -Anonymous_VerifyEmail -AsyncRequestWorkerFactor -AuthBasicAuthoritative -AuthBasicFake -AuthBasicProvider -AuthBasicUseDigestAlgorithm -AuthDBDUserPWQuery -AuthDBDUserRealmQuery -AuthDBMGroupFile -AuthDBMType -AuthDBMUserFile -AuthDigestAlgorithm -AuthDigestDomain -AuthDigestNonceLifetime -AuthDigestProvider -AuthDigestQop -AuthDigestShmemSize -AuthFormAuthoritative -AuthFormBody -AuthFormDisableNoStore -AuthFormFakeBasicAuth -AuthFormLocation -AuthFormLoginRequiredLocation -AuthFormLoginSuccessLocation -AuthFormLogoutLocation -AuthFormMethod -AuthFormMimetype -AuthFormPassword -AuthFormProvider -AuthFormSitePassphrase -AuthFormSize -AuthFormUsername -AuthGroupFile -AuthLDAPAuthorizePrefix -AuthLDAPBindAuthoritative -AuthLDAPBindDN -AuthLDAPBindPassword -AuthLDAPCharsetConfig -AuthLDAPCompareAsUser -AuthLDAPCompareDNOnServer -AuthLDAPDereferenceAliases -AuthLDAPGroupAttribute -AuthLDAPGroupAttributeIsDN -AuthLDAPInitialBindAsUser -AuthLDAPInitialBindPattern -AuthLDAPMaxSubGroupDepth -AuthLDAPRemoteUserAttribute -AuthLDAPRemoteUserIsDN -AuthLDAPSearchAsUser -AuthLDAPSubGroupAttribute -AuthLDAPSubGroupClass -AuthLDAPUrl -AuthMerging -AuthName -AuthnCacheContext -AuthnCacheEnable -AuthnCacheProvideFor -AuthnCacheSOCache -AuthnCacheTimeout -AuthnzFcgiCheckAuthnProvider -AuthnzFcgiDefineProvider -AuthType -AuthUserFile -AuthzDBDLoginToReferer -AuthzDBDQuery -AuthzDBDRedirectQuery -AuthzDBMType -AuthzSendForbiddenOnFailure -BalancerGrowth -BalancerInherit -BalancerMember -BalancerPersist -BrowserMatch -BrowserMatchNoCase -BufferedLogs -BufferSize -CacheDefaultExpire -CacheDetailHeader -CacheDirLength -CacheDirLevels -CacheDisable -CacheEnable -CacheFile -CacheHeader -CacheIgnoreCacheControl -CacheIgnoreHeaders -CacheIgnoreNoLastMod -CacheIgnoreQueryString -CacheIgnoreURLSessionIdentifiers -CacheKeyBaseURL -CacheLastModifiedFactor -CacheLock -CacheLockMaxAge -CacheLockPath -CacheMaxExpire -CacheMaxFileSize -CacheMinExpire -CacheMinFileSize -CacheNegotiatedDocs -CacheQuickHandler -CacheReadSize -CacheReadTime -CacheRoot -CacheSocache -CacheSocacheMaxSize -CacheSocacheMaxTime -CacheSocacheMinTime -CacheSocacheReadSize -CacheSocacheReadTime -CacheStaleOnError -CacheStoreExpired -CacheStoreNoStore -CacheStorePrivate -CGIDScriptTimeout -CGIMapExtension -CharsetDefault -CharsetOptions -CharsetSourceEnc -CheckCaseOnly -CheckSpelling -ChrootDir -ContentDigest -CookieDomain -CookieExpires -CookieName -CookieStyle -CookieTracking -CoreDumpDirectory -CustomLog -Dav -DavDepthInfinity -DavGenericLockDB -DavLockDB -DavMinTimeout -DBDExptime -DBDInitSQL -DBDKeep -DBDMax -DBDMin -DBDParams -DBDPersist -DBDPrepareSQL -DBDriver -DefaultIcon -DefaultLanguage -DefaultRuntimeDir -DefaultType -Define -DeflateBufferSize -DeflateCompressionLevel -DeflateFilterNote -DeflateInflateLimitRequestBody -DeflateInflateRatioBurst -DeflateInflateRatioLimit -DeflateMemLevel -DeflateWindowSize -Deny -DirectoryCheckHandler -DirectoryIndex -DirectoryIndexRedirect -DirectorySlash -DocumentRoot -DTracePrivileges -DumpIOInput -DumpIOOutput -EnableExceptionHook -EnableMMAP -EnableSendfile -Error -ErrorDocument -ErrorLog -ErrorLogFormat -Example -ExpiresActive -ExpiresByType -ExpiresDefault -ExtendedStatus -ExtFilterDefine -ExtFilterOptions -FallbackResource -FileETag -FilterChain -FilterDeclare -FilterProtocol -FilterProvider -FilterTrace -ForceLanguagePriority -ForceType -ForensicLog -GprofDir -GracefulShutdownTimeout -Group -Header -HeaderName -HeartbeatAddress -HeartbeatListen -HeartbeatMaxServers -HeartbeatStorage -HeartbeatStorage -HostnameLookups -IdentityCheck -IdentityCheckTimeout -ImapBase -ImapDefault -ImapMenu -Include -IncludeOptional -IndexHeadInsert -IndexIgnore -IndexIgnoreReset -IndexOptions -IndexOrderDefault -IndexStyleSheet -InputSed -ISAPIAppendLogToErrors -ISAPIAppendLogToQuery -ISAPICacheFile -ISAPIFakeAsync -ISAPILogNotSupported -ISAPIReadAheadBuffer -KeepAlive -KeepAliveTimeout -KeptBodySize -LanguagePriority -LDAPCacheEntries -LDAPCacheTTL -LDAPConnectionPoolTTL -LDAPConnectionTimeout -LDAPLibraryDebug -LDAPOpCacheEntries -LDAPOpCacheTTL -LDAPReferralHopLimit -LDAPReferrals -LDAPRetries -LDAPRetryDelay -LDAPSharedCacheFile -LDAPSharedCacheSize -LDAPTimeout -LDAPTrustedClientCert -LDAPTrustedGlobalCert -LDAPTrustedMode -LDAPVerifyServerCert -LimitInternalRecursion -LimitRequestBody -LimitRequestFields -LimitRequestFieldSize -LimitRequestLine -LimitXMLRequestBody -Listen -ListenBackLog -LoadFile -LoadModule -LogFormat -LogLevel -LogMessage -LuaAuthzProvider -LuaCodeCache -LuaHookAccessChecker -LuaHookAuthChecker -LuaHookCheckUserID -LuaHookFixups -LuaHookInsertFilter -LuaHookLog -LuaHookMapToStorage -LuaHookTranslateName -LuaHookTypeChecker -LuaInherit -LuaInputFilter -LuaMapHandler -LuaOutputFilter -LuaPackageCPath -LuaPackagePath -LuaQuickHandler -LuaRoot -LuaScope -MaxConnectionsPerChild -MaxKeepAliveRequests -MaxMemFree -MaxRangeOverlaps -MaxRangeReversals -MaxRanges -MaxRequestWorkers -MaxSpareServers -MaxSpareThreads -MaxThreads -MergeTrailers -MetaDir -MetaFiles -MetaSuffix -MimeMagicFile -MinSpareServers -MinSpareThreads -MMapFile -ModemStandard -ModMimeUsePathInfo -MultiviewsMatch -Mutex -NameVirtualHost -NoProxy -NWSSLTrustedCerts -NWSSLUpgradeable -Options -Order -OutputSed -PassEnv -PidFile -PrivilegesMode -Protocol -ProtocolEcho -ProxyAddHeaders -ProxyBadHeader -ProxyBlock -ProxyDomain -ProxyErrorOverride -ProxyExpressDBMFile -ProxyExpressDBMType -ProxyExpressEnable -ProxyFtpDirCharset -ProxyFtpEscapeWildcards -ProxyFtpListOnWildcard -ProxyHTMLBufSize -ProxyHTMLCharsetOut -ProxyHTMLDocType -ProxyHTMLEnable -ProxyHTMLEvents -ProxyHTMLExtended -ProxyHTMLFixups -ProxyHTMLInterp -ProxyHTMLLinks -ProxyHTMLMeta -ProxyHTMLStripComments -ProxyHTMLURLMap -ProxyIOBufferSize -ProxyMaxForwards -ProxyPass -ProxyPassInherit -ProxyPassInterpolateEnv -ProxyPassMatch -ProxyPassReverse -ProxyPassReverseCookieDomain -ProxyPassReverseCookiePath -ProxyPreserveHost -ProxyReceiveBufferSize -ProxyRemote -ProxyRemoteMatch -ProxyRequests -ProxySCGIInternalRedirect -ProxySCGISendfile -ProxySet -ProxySourceAddress -ProxyStatus -ProxyTimeout -ProxyVia -ReadmeName -ReceiveBufferSize -Redirect -RedirectMatch -RedirectPermanent -RedirectTemp -ReflectorHeader -RemoteIPHeader -RemoteIPInternalProxy -RemoteIPInternalProxyList -RemoteIPProxiesHeader -RemoteIPTrustedProxy -RemoteIPTrustedProxyList -RemoveCharset -RemoveEncoding -RemoveHandler -RemoveInputFilter -RemoveLanguage -RemoveOutputFilter -RemoveType -RequestHeader -RequestReadTimeout -Require -RewriteBase -RewriteCond -RewriteEngine -RewriteMap -RewriteOptions -RewriteRule -RLimitCPU -RLimitMEM -RLimitNPROC -Satisfy -ScoreBoardFile -Script -ScriptAlias -ScriptAliasMatch -ScriptInterpreterSource -ScriptLog -ScriptLogBuffer -ScriptLogLength -ScriptSock -SecureListen -SeeRequestTail -SendBufferSize -ServerAdmin -ServerAlias -ServerLimit -ServerName -ServerPath -ServerRoot -ServerSignature -ServerTokens -Session -SessionCookieName -SessionCookieName2 -SessionCookieRemove -SessionCryptoCipher -SessionCryptoDriver -SessionCryptoPassphrase -SessionCryptoPassphraseFile -SessionDBDCookieName -SessionDBDCookieName2 -SessionDBDCookieRemove -SessionDBDDeleteLabel -SessionDBDInsertLabel -SessionDBDPerUser -SessionDBDSelectLabel -SessionDBDUpdateLabel -SessionEnv -SessionExclude -SessionHeader -SessionInclude -SessionMaxAge -SetEnv -SetEnvIf -SetEnvIfExpr -SetEnvIfNoCase -SetHandler -SetInputFilter -SetOutputFilter -SSIEndTag -SSIErrorMsg -SSIETag -SSILastModified -SSILegacyExprParser -SSIStartTag -SSITimeFormat -SSIUndefinedEcho -SSLCACertificateFile -SSLCACertificatePath -SSLCADNRequestFile -SSLCADNRequestPath -SSLCARevocationCheck -SSLCARevocationFile -SSLCARevocationPath -SSLCertificateChainFile -SSLCertificateFile -SSLCertificateKeyFile -SSLCipherSuite -SSLCompression -SSLCryptoDevice -SSLEngine -SSLFIPS -SSLHonorCipherOrder -SSLInsecureRenegotiation -SSLOCSPDefaultResponder -SSLOCSPEnable -SSLOCSPOverrideResponder -SSLOCSPResponderTimeout -SSLOCSPResponseMaxAge -SSLOCSPResponseTimeSkew -SSLOCSPUseRequestNonce -SSLOpenSSLConfCmd -SSLOptions -SSLPassPhraseDialog -SSLProtocol -SSLProxyCACertificateFile -SSLProxyCACertificatePath -SSLProxyCARevocationCheck -SSLProxyCARevocationFile -SSLProxyCARevocationPath -SSLProxyCheckPeerCN -SSLProxyCheckPeerExpire -SSLProxyCheckPeerName -SSLProxyCipherSuite -SSLProxyEngine -SSLProxyMachineCertificateChainFile -SSLProxyMachineCertificateFile -SSLProxyMachineCertificatePath -SSLProxyProtocol -SSLProxyVerify -SSLProxyVerifyDepth -SSLRandomSeed -SSLRenegBufferSize -SSLRequire -SSLRequireSSL -SSLSessionCache -SSLSessionCacheTimeout -SSLSessionTicketKeyFile -SSLSRPUnknownUserSeed -SSLSRPVerifierFile -SSLStaplingCache -SSLStaplingErrorCacheTimeout -SSLStaplingFakeTryLater -SSLStaplingForceURL -SSLStaplingResponderTimeout -SSLStaplingResponseMaxAge -SSLStaplingResponseTimeSkew -SSLStaplingReturnResponderErrors -SSLStaplingStandardCacheTimeout -SSLStrictSNIVHostCheck -SSLUserName -SSLUseStapling -SSLVerifyClient -SSLVerifyDepth -StartServers -StartThreads -Substitute -Suexec -SuexecUserGroup -ThreadLimit -ThreadsPerChild -ThreadStackSize -TimeOut -TraceEnable -TransferLog -TypesConfig -UnDefine -UndefMacro -UnsetEnv -Use -UseCanonicalName -UseCanonicalPhysicalPort -User -UserDir -VHostCGIMode -VHostCGIPrivs -VHostGroup -VHostPrivs -VHostSecure -VHostUser -VirtualDocumentRoot -VirtualDocumentRootIP -VirtualScriptAlias -VirtualScriptAliasIP -WatchdogInterval -XBitHack -xml2EncAlias -xml2EncDefault -xml2StartParse - ----------------------------------------------------- - -[ - ["directive-inline", "AcceptFilter"], - ["directive-inline", "AcceptPathInfo"], - ["directive-inline", "AccessFileName"], - ["directive-inline", "Action"], - ["directive-inline", "AddAlt"], - ["directive-inline", "AddAltByEncoding"], - ["directive-inline", "AddAltByType"], - ["directive-inline", "AddCharset"], - ["directive-inline", "AddDefaultCharset"], - ["directive-inline", "AddDescription"], - ["directive-inline", "AddEncoding"], - ["directive-inline", "AddHandler"], - ["directive-inline", "AddIcon"], - ["directive-inline", "AddIconByEncoding"], - ["directive-inline", "AddIconByType"], - ["directive-inline", "AddInputFilter"], - ["directive-inline", "AddLanguage"], - ["directive-inline", "AddModuleInfo"], - ["directive-inline", "AddOutputFilter"], - ["directive-inline", "AddOutputFilterByType"], - ["directive-inline", "AddType"], - ["directive-inline", "Alias"], - ["directive-inline", "AliasMatch"], - ["directive-inline", "Allow"], - ["directive-inline", "AllowCONNECT"], - ["directive-inline", "AllowEncodedSlashes"], - ["directive-inline", "AllowMethods"], - ["directive-inline", "AllowOverride"], - ["directive-inline", "AllowOverrideList"], - ["directive-inline", "Anonymous"], - ["directive-inline", "Anonymous_LogEmail"], - ["directive-inline", "Anonymous_MustGiveEmail"], - ["directive-inline", "Anonymous_NoUserID"], - ["directive-inline", "Anonymous_VerifyEmail"], - ["directive-inline", "AsyncRequestWorkerFactor"], - ["directive-inline", "AuthBasicAuthoritative"], - ["directive-inline", "AuthBasicFake"], - ["directive-inline", "AuthBasicProvider"], - ["directive-inline", "AuthBasicUseDigestAlgorithm"], - ["directive-inline", "AuthDBDUserPWQuery"], - ["directive-inline", "AuthDBDUserRealmQuery"], - ["directive-inline", "AuthDBMGroupFile"], - ["directive-inline", "AuthDBMType"], - ["directive-inline", "AuthDBMUserFile"], - ["directive-inline", "AuthDigestAlgorithm"], - ["directive-inline", "AuthDigestDomain"], - ["directive-inline", "AuthDigestNonceLifetime"], - ["directive-inline", "AuthDigestProvider"], - ["directive-inline", "AuthDigestQop"], - ["directive-inline", "AuthDigestShmemSize"], - ["directive-inline", "AuthFormAuthoritative"], - ["directive-inline", "AuthFormBody"], - ["directive-inline", "AuthFormDisableNoStore"], - ["directive-inline", "AuthFormFakeBasicAuth"], - ["directive-inline", "AuthFormLocation"], - ["directive-inline", "AuthFormLoginRequiredLocation"], - ["directive-inline", "AuthFormLoginSuccessLocation"], - ["directive-inline", "AuthFormLogoutLocation"], - ["directive-inline", "AuthFormMethod"], - ["directive-inline", "AuthFormMimetype"], - ["directive-inline", "AuthFormPassword"], - ["directive-inline", "AuthFormProvider"], - ["directive-inline", "AuthFormSitePassphrase"], - ["directive-inline", "AuthFormSize"], - ["directive-inline", "AuthFormUsername"], - ["directive-inline", "AuthGroupFile"], - ["directive-inline", "AuthLDAPAuthorizePrefix"], - ["directive-inline", "AuthLDAPBindAuthoritative"], - ["directive-inline", "AuthLDAPBindDN"], - ["directive-inline", "AuthLDAPBindPassword"], - ["directive-inline", "AuthLDAPCharsetConfig"], - ["directive-inline", "AuthLDAPCompareAsUser"], - ["directive-inline", "AuthLDAPCompareDNOnServer"], - ["directive-inline", "AuthLDAPDereferenceAliases"], - ["directive-inline", "AuthLDAPGroupAttribute"], - ["directive-inline", "AuthLDAPGroupAttributeIsDN"], - ["directive-inline", "AuthLDAPInitialBindAsUser"], - ["directive-inline", "AuthLDAPInitialBindPattern"], - ["directive-inline", "AuthLDAPMaxSubGroupDepth"], - ["directive-inline", "AuthLDAPRemoteUserAttribute"], - ["directive-inline", "AuthLDAPRemoteUserIsDN"], - ["directive-inline", "AuthLDAPSearchAsUser"], - ["directive-inline", "AuthLDAPSubGroupAttribute"], - ["directive-inline", "AuthLDAPSubGroupClass"], - ["directive-inline", "AuthLDAPUrl"], - ["directive-inline", "AuthMerging"], - ["directive-inline", "AuthName"], - ["directive-inline", "AuthnCacheContext"], - ["directive-inline", "AuthnCacheEnable"], - ["directive-inline", "AuthnCacheProvideFor"], - ["directive-inline", "AuthnCacheSOCache"], - ["directive-inline", "AuthnCacheTimeout"], - ["directive-inline", "AuthnzFcgiCheckAuthnProvider"], - ["directive-inline", "AuthnzFcgiDefineProvider"], - ["directive-inline", "AuthType"], - ["directive-inline", "AuthUserFile"], - ["directive-inline", "AuthzDBDLoginToReferer"], - ["directive-inline", "AuthzDBDQuery"], - ["directive-inline", "AuthzDBDRedirectQuery"], - ["directive-inline", "AuthzDBMType"], - ["directive-inline", "AuthzSendForbiddenOnFailure"], - ["directive-inline", "BalancerGrowth"], - ["directive-inline", "BalancerInherit"], - ["directive-inline", "BalancerMember"], - ["directive-inline", "BalancerPersist"], - ["directive-inline", "BrowserMatch"], - ["directive-inline", "BrowserMatchNoCase"], - ["directive-inline", "BufferedLogs"], - ["directive-inline", "BufferSize"], - ["directive-inline", "CacheDefaultExpire"], - ["directive-inline", "CacheDetailHeader"], - ["directive-inline", "CacheDirLength"], - ["directive-inline", "CacheDirLevels"], - ["directive-inline", "CacheDisable"], - ["directive-inline", "CacheEnable"], - ["directive-inline", "CacheFile"], - ["directive-inline", "CacheHeader"], - ["directive-inline", "CacheIgnoreCacheControl"], - ["directive-inline", "CacheIgnoreHeaders"], - ["directive-inline", "CacheIgnoreNoLastMod"], - ["directive-inline", "CacheIgnoreQueryString"], - ["directive-inline", "CacheIgnoreURLSessionIdentifiers"], - ["directive-inline", "CacheKeyBaseURL"], - ["directive-inline", "CacheLastModifiedFactor"], - ["directive-inline", "CacheLock"], - ["directive-inline", "CacheLockMaxAge"], - ["directive-inline", "CacheLockPath"], - ["directive-inline", "CacheMaxExpire"], - ["directive-inline", "CacheMaxFileSize"], - ["directive-inline", "CacheMinExpire"], - ["directive-inline", "CacheMinFileSize"], - ["directive-inline", "CacheNegotiatedDocs"], - ["directive-inline", "CacheQuickHandler"], - ["directive-inline", "CacheReadSize"], - ["directive-inline", "CacheReadTime"], - ["directive-inline", "CacheRoot"], - ["directive-inline", "CacheSocache"], - ["directive-inline", "CacheSocacheMaxSize"], - ["directive-inline", "CacheSocacheMaxTime"], - ["directive-inline", "CacheSocacheMinTime"], - ["directive-inline", "CacheSocacheReadSize"], - ["directive-inline", "CacheSocacheReadTime"], - ["directive-inline", "CacheStaleOnError"], - ["directive-inline", "CacheStoreExpired"], - ["directive-inline", "CacheStoreNoStore"], - ["directive-inline", "CacheStorePrivate"], - ["directive-inline", "CGIDScriptTimeout"], - ["directive-inline", "CGIMapExtension"], - ["directive-inline", "CharsetDefault"], - ["directive-inline", "CharsetOptions"], - ["directive-inline", "CharsetSourceEnc"], - ["directive-inline", "CheckCaseOnly"], - ["directive-inline", "CheckSpelling"], - ["directive-inline", "ChrootDir"], - ["directive-inline", "ContentDigest"], - ["directive-inline", "CookieDomain"], - ["directive-inline", "CookieExpires"], - ["directive-inline", "CookieName"], - ["directive-inline", "CookieStyle"], - ["directive-inline", "CookieTracking"], - ["directive-inline", "CoreDumpDirectory"], - ["directive-inline", "CustomLog"], - ["directive-inline", "Dav"], - ["directive-inline", "DavDepthInfinity"], - ["directive-inline", "DavGenericLockDB"], - ["directive-inline", "DavLockDB"], - ["directive-inline", "DavMinTimeout"], - ["directive-inline", "DBDExptime"], - ["directive-inline", "DBDInitSQL"], - ["directive-inline", "DBDKeep"], - ["directive-inline", "DBDMax"], - ["directive-inline", "DBDMin"], - ["directive-inline", "DBDParams"], - ["directive-inline", "DBDPersist"], - ["directive-inline", "DBDPrepareSQL"], - ["directive-inline", "DBDriver"], - ["directive-inline", "DefaultIcon"], - ["directive-inline", "DefaultLanguage"], - ["directive-inline", "DefaultRuntimeDir"], - ["directive-inline", "DefaultType"], - ["directive-inline", "Define"], - ["directive-inline", "DeflateBufferSize"], - ["directive-inline", "DeflateCompressionLevel"], - ["directive-inline", "DeflateFilterNote"], - ["directive-inline", "DeflateInflateLimitRequestBody"], - ["directive-inline", "DeflateInflateRatioBurst"], - ["directive-inline", "DeflateInflateRatioLimit"], - ["directive-inline", "DeflateMemLevel"], - ["directive-inline", "DeflateWindowSize"], - ["directive-inline", "Deny"], - ["directive-inline", "DirectoryCheckHandler"], - ["directive-inline", "DirectoryIndex"], - ["directive-inline", "DirectoryIndexRedirect"], - ["directive-inline", "DirectorySlash"], - ["directive-inline", "DocumentRoot"], - ["directive-inline", "DTracePrivileges"], - ["directive-inline", "DumpIOInput"], - ["directive-inline", "DumpIOOutput"], - ["directive-inline", "EnableExceptionHook"], - ["directive-inline", "EnableMMAP"], - ["directive-inline", "EnableSendfile"], - ["directive-inline", "Error"], - ["directive-inline", "ErrorDocument"], - ["directive-inline", "ErrorLog"], - ["directive-inline", "ErrorLogFormat"], - ["directive-inline", "Example"], - ["directive-inline", "ExpiresActive"], - ["directive-inline", "ExpiresByType"], - ["directive-inline", "ExpiresDefault"], - ["directive-inline", "ExtendedStatus"], - ["directive-inline", "ExtFilterDefine"], - ["directive-inline", "ExtFilterOptions"], - ["directive-inline", "FallbackResource"], - ["directive-inline", "FileETag"], - ["directive-inline", "FilterChain"], - ["directive-inline", "FilterDeclare"], - ["directive-inline", "FilterProtocol"], - ["directive-inline", "FilterProvider"], - ["directive-inline", "FilterTrace"], - ["directive-inline", "ForceLanguagePriority"], - ["directive-inline", "ForceType"], - ["directive-inline", "ForensicLog"], - ["directive-inline", "GprofDir"], - ["directive-inline", "GracefulShutdownTimeout"], - ["directive-inline", "Group"], - ["directive-inline", "Header"], - ["directive-inline", "HeaderName"], - ["directive-inline", "HeartbeatAddress"], - ["directive-inline", "HeartbeatListen"], - ["directive-inline", "HeartbeatMaxServers"], - ["directive-inline", "HeartbeatStorage"], - ["directive-inline", "HeartbeatStorage"], - ["directive-inline", "HostnameLookups"], - ["directive-inline", "IdentityCheck"], - ["directive-inline", "IdentityCheckTimeout"], - ["directive-inline", "ImapBase"], - ["directive-inline", "ImapDefault"], - ["directive-inline", "ImapMenu"], - ["directive-inline", "Include"], - ["directive-inline", "IncludeOptional"], - ["directive-inline", "IndexHeadInsert"], - ["directive-inline", "IndexIgnore"], - ["directive-inline", "IndexIgnoreReset"], - ["directive-inline", "IndexOptions"], - ["directive-inline", "IndexOrderDefault"], - ["directive-inline", "IndexStyleSheet"], - ["directive-inline", "InputSed"], - ["directive-inline", "ISAPIAppendLogToErrors"], - ["directive-inline", "ISAPIAppendLogToQuery"], - ["directive-inline", "ISAPICacheFile"], - ["directive-inline", "ISAPIFakeAsync"], - ["directive-inline", "ISAPILogNotSupported"], - ["directive-inline", "ISAPIReadAheadBuffer"], - ["directive-inline", "KeepAlive"], - ["directive-inline", "KeepAliveTimeout"], - ["directive-inline", "KeptBodySize"], - ["directive-inline", "LanguagePriority"], - ["directive-inline", "LDAPCacheEntries"], - ["directive-inline", "LDAPCacheTTL"], - ["directive-inline", "LDAPConnectionPoolTTL"], - ["directive-inline", "LDAPConnectionTimeout"], - ["directive-inline", "LDAPLibraryDebug"], - ["directive-inline", "LDAPOpCacheEntries"], - ["directive-inline", "LDAPOpCacheTTL"], - ["directive-inline", "LDAPReferralHopLimit"], - ["directive-inline", "LDAPReferrals"], - ["directive-inline", "LDAPRetries"], - ["directive-inline", "LDAPRetryDelay"], - ["directive-inline", "LDAPSharedCacheFile"], - ["directive-inline", "LDAPSharedCacheSize"], - ["directive-inline", "LDAPTimeout"], - ["directive-inline", "LDAPTrustedClientCert"], - ["directive-inline", "LDAPTrustedGlobalCert"], - ["directive-inline", "LDAPTrustedMode"], - ["directive-inline", "LDAPVerifyServerCert"], - ["directive-inline", "LimitInternalRecursion"], - ["directive-inline", "LimitRequestBody"], - ["directive-inline", "LimitRequestFields"], - ["directive-inline", "LimitRequestFieldSize"], - ["directive-inline", "LimitRequestLine"], - ["directive-inline", "LimitXMLRequestBody"], - ["directive-inline", "Listen"], - ["directive-inline", "ListenBackLog"], - ["directive-inline", "LoadFile"], - ["directive-inline", "LoadModule"], - ["directive-inline", "LogFormat"], - ["directive-inline", "LogLevel"], - ["directive-inline", "LogMessage"], - ["directive-inline", "LuaAuthzProvider"], - ["directive-inline", "LuaCodeCache"], - ["directive-inline", "LuaHookAccessChecker"], - ["directive-inline", "LuaHookAuthChecker"], - ["directive-inline", "LuaHookCheckUserID"], - ["directive-inline", "LuaHookFixups"], - ["directive-inline", "LuaHookInsertFilter"], - ["directive-inline", "LuaHookLog"], - ["directive-inline", "LuaHookMapToStorage"], - ["directive-inline", "LuaHookTranslateName"], - ["directive-inline", "LuaHookTypeChecker"], - ["directive-inline", "LuaInherit"], - ["directive-inline", "LuaInputFilter"], - ["directive-inline", "LuaMapHandler"], - ["directive-inline", "LuaOutputFilter"], - ["directive-inline", "LuaPackageCPath"], - ["directive-inline", "LuaPackagePath"], - ["directive-inline", "LuaQuickHandler"], - ["directive-inline", "LuaRoot"], - ["directive-inline", "LuaScope"], - ["directive-inline", "MaxConnectionsPerChild"], - ["directive-inline", "MaxKeepAliveRequests"], - ["directive-inline", "MaxMemFree"], - ["directive-inline", "MaxRangeOverlaps"], - ["directive-inline", "MaxRangeReversals"], - ["directive-inline", "MaxRanges"], - ["directive-inline", "MaxRequestWorkers"], - ["directive-inline", "MaxSpareServers"], - ["directive-inline", "MaxSpareThreads"], - ["directive-inline", "MaxThreads"], - ["directive-inline", "MergeTrailers"], - ["directive-inline", "MetaDir"], - ["directive-inline", "MetaFiles"], - ["directive-inline", "MetaSuffix"], - ["directive-inline", "MimeMagicFile"], - ["directive-inline", "MinSpareServers"], - ["directive-inline", "MinSpareThreads"], - ["directive-inline", "MMapFile"], - ["directive-inline", "ModemStandard"], - ["directive-inline", "ModMimeUsePathInfo"], - ["directive-inline", "MultiviewsMatch"], - ["directive-inline", "Mutex"], - ["directive-inline", "NameVirtualHost"], - ["directive-inline", "NoProxy"], - ["directive-inline", "NWSSLTrustedCerts"], - ["directive-inline", "NWSSLUpgradeable"], - ["directive-inline", "Options"], - ["directive-inline", "Order"], - ["directive-inline", "OutputSed"], - ["directive-inline", "PassEnv"], - ["directive-inline", "PidFile"], - ["directive-inline", "PrivilegesMode"], - ["directive-inline", "Protocol"], - ["directive-inline", "ProtocolEcho"], - ["directive-inline", "ProxyAddHeaders"], - ["directive-inline", "ProxyBadHeader"], - ["directive-inline", "ProxyBlock"], - ["directive-inline", "ProxyDomain"], - ["directive-inline", "ProxyErrorOverride"], - ["directive-inline", "ProxyExpressDBMFile"], - ["directive-inline", "ProxyExpressDBMType"], - ["directive-inline", "ProxyExpressEnable"], - ["directive-inline", "ProxyFtpDirCharset"], - ["directive-inline", "ProxyFtpEscapeWildcards"], - ["directive-inline", "ProxyFtpListOnWildcard"], - ["directive-inline", "ProxyHTMLBufSize"], - ["directive-inline", "ProxyHTMLCharsetOut"], - ["directive-inline", "ProxyHTMLDocType"], - ["directive-inline", "ProxyHTMLEnable"], - ["directive-inline", "ProxyHTMLEvents"], - ["directive-inline", "ProxyHTMLExtended"], - ["directive-inline", "ProxyHTMLFixups"], - ["directive-inline", "ProxyHTMLInterp"], - ["directive-inline", "ProxyHTMLLinks"], - ["directive-inline", "ProxyHTMLMeta"], - ["directive-inline", "ProxyHTMLStripComments"], - ["directive-inline", "ProxyHTMLURLMap"], - ["directive-inline", "ProxyIOBufferSize"], - ["directive-inline", "ProxyMaxForwards"], - ["directive-inline", "ProxyPass"], - ["directive-inline", "ProxyPassInherit"], - ["directive-inline", "ProxyPassInterpolateEnv"], - ["directive-inline", "ProxyPassMatch"], - ["directive-inline", "ProxyPassReverse"], - ["directive-inline", "ProxyPassReverseCookieDomain"], - ["directive-inline", "ProxyPassReverseCookiePath"], - ["directive-inline", "ProxyPreserveHost"], - ["directive-inline", "ProxyReceiveBufferSize"], - ["directive-inline", "ProxyRemote"], - ["directive-inline", "ProxyRemoteMatch"], - ["directive-inline", "ProxyRequests"], - ["directive-inline", "ProxySCGIInternalRedirect"], - ["directive-inline", "ProxySCGISendfile"], - ["directive-inline", "ProxySet"], - ["directive-inline", "ProxySourceAddress"], - ["directive-inline", "ProxyStatus"], - ["directive-inline", "ProxyTimeout"], - ["directive-inline", "ProxyVia"], - ["directive-inline", "ReadmeName"], - ["directive-inline", "ReceiveBufferSize"], - ["directive-inline", "Redirect"], - ["directive-inline", "RedirectMatch"], - ["directive-inline", "RedirectPermanent"], - ["directive-inline", "RedirectTemp"], - ["directive-inline", "ReflectorHeader"], - ["directive-inline", "RemoteIPHeader"], - ["directive-inline", "RemoteIPInternalProxy"], - ["directive-inline", "RemoteIPInternalProxyList"], - ["directive-inline", "RemoteIPProxiesHeader"], - ["directive-inline", "RemoteIPTrustedProxy"], - ["directive-inline", "RemoteIPTrustedProxyList"], - ["directive-inline", "RemoveCharset"], - ["directive-inline", "RemoveEncoding"], - ["directive-inline", "RemoveHandler"], - ["directive-inline", "RemoveInputFilter"], - ["directive-inline", "RemoveLanguage"], - ["directive-inline", "RemoveOutputFilter"], - ["directive-inline", "RemoveType"], - ["directive-inline", "RequestHeader"], - ["directive-inline", "RequestReadTimeout"], - ["directive-inline", "Require"], - ["directive-inline", "RewriteBase"], - ["directive-inline", "RewriteCond"], - ["directive-inline", "RewriteEngine"], - ["directive-inline", "RewriteMap"], - ["directive-inline", "RewriteOptions"], - ["directive-inline", "RewriteRule"], - ["directive-inline", "RLimitCPU"], - ["directive-inline", "RLimitMEM"], - ["directive-inline", "RLimitNPROC"], - ["directive-inline", "Satisfy"], - ["directive-inline", "ScoreBoardFile"], - ["directive-inline", "Script"], - ["directive-inline", "ScriptAlias"], - ["directive-inline", "ScriptAliasMatch"], - ["directive-inline", "ScriptInterpreterSource"], - ["directive-inline", "ScriptLog"], - ["directive-inline", "ScriptLogBuffer"], - ["directive-inline", "ScriptLogLength"], - ["directive-inline", "ScriptSock"], - ["directive-inline", "SecureListen"], - ["directive-inline", "SeeRequestTail"], - ["directive-inline", "SendBufferSize"], - ["directive-inline", "ServerAdmin"], - ["directive-inline", "ServerAlias"], - ["directive-inline", "ServerLimit"], - ["directive-inline", "ServerName"], - ["directive-inline", "ServerPath"], - ["directive-inline", "ServerRoot"], - ["directive-inline", "ServerSignature"], - ["directive-inline", "ServerTokens"], - ["directive-inline", "Session"], - ["directive-inline", "SessionCookieName"], - ["directive-inline", "SessionCookieName2"], - ["directive-inline", "SessionCookieRemove"], - ["directive-inline", "SessionCryptoCipher"], - ["directive-inline", "SessionCryptoDriver"], - ["directive-inline", "SessionCryptoPassphrase"], - ["directive-inline", "SessionCryptoPassphraseFile"], - ["directive-inline", "SessionDBDCookieName"], - ["directive-inline", "SessionDBDCookieName2"], - ["directive-inline", "SessionDBDCookieRemove"], - ["directive-inline", "SessionDBDDeleteLabel"], - ["directive-inline", "SessionDBDInsertLabel"], - ["directive-inline", "SessionDBDPerUser"], - ["directive-inline", "SessionDBDSelectLabel"], - ["directive-inline", "SessionDBDUpdateLabel"], - ["directive-inline", "SessionEnv"], - ["directive-inline", "SessionExclude"], - ["directive-inline", "SessionHeader"], - ["directive-inline", "SessionInclude"], - ["directive-inline", "SessionMaxAge"], - ["directive-inline", "SetEnv"], - ["directive-inline", "SetEnvIf"], - ["directive-inline", "SetEnvIfExpr"], - ["directive-inline", "SetEnvIfNoCase"], - ["directive-inline", "SetHandler"], - ["directive-inline", "SetInputFilter"], - ["directive-inline", "SetOutputFilter"], - ["directive-inline", "SSIEndTag"], - ["directive-inline", "SSIErrorMsg"], - ["directive-inline", "SSIETag"], - ["directive-inline", "SSILastModified"], - ["directive-inline", "SSILegacyExprParser"], - ["directive-inline", "SSIStartTag"], - ["directive-inline", "SSITimeFormat"], - ["directive-inline", "SSIUndefinedEcho"], - ["directive-inline", "SSLCACertificateFile"], - ["directive-inline", "SSLCACertificatePath"], - ["directive-inline", "SSLCADNRequestFile"], - ["directive-inline", "SSLCADNRequestPath"], - ["directive-inline", "SSLCARevocationCheck"], - ["directive-inline", "SSLCARevocationFile"], - ["directive-inline", "SSLCARevocationPath"], - ["directive-inline", "SSLCertificateChainFile"], - ["directive-inline", "SSLCertificateFile"], - ["directive-inline", "SSLCertificateKeyFile"], - ["directive-inline", "SSLCipherSuite"], - ["directive-inline", "SSLCompression"], - ["directive-inline", "SSLCryptoDevice"], - ["directive-inline", "SSLEngine"], - ["directive-inline", "SSLFIPS"], - ["directive-inline", "SSLHonorCipherOrder"], - ["directive-inline", "SSLInsecureRenegotiation"], - ["directive-inline", "SSLOCSPDefaultResponder"], - ["directive-inline", "SSLOCSPEnable"], - ["directive-inline", "SSLOCSPOverrideResponder"], - ["directive-inline", "SSLOCSPResponderTimeout"], - ["directive-inline", "SSLOCSPResponseMaxAge"], - ["directive-inline", "SSLOCSPResponseTimeSkew"], - ["directive-inline", "SSLOCSPUseRequestNonce"], - ["directive-inline", "SSLOpenSSLConfCmd"], - ["directive-inline", "SSLOptions"], - ["directive-inline", "SSLPassPhraseDialog"], - ["directive-inline", "SSLProtocol"], - ["directive-inline", "SSLProxyCACertificateFile"], - ["directive-inline", "SSLProxyCACertificatePath"], - ["directive-inline", "SSLProxyCARevocationCheck"], - ["directive-inline", "SSLProxyCARevocationFile"], - ["directive-inline", "SSLProxyCARevocationPath"], - ["directive-inline", "SSLProxyCheckPeerCN"], - ["directive-inline", "SSLProxyCheckPeerExpire"], - ["directive-inline", "SSLProxyCheckPeerName"], - ["directive-inline", "SSLProxyCipherSuite"], - ["directive-inline", "SSLProxyEngine"], - ["directive-inline", "SSLProxyMachineCertificateChainFile"], - ["directive-inline", "SSLProxyMachineCertificateFile"], - ["directive-inline", "SSLProxyMachineCertificatePath"], - ["directive-inline", "SSLProxyProtocol"], - ["directive-inline", "SSLProxyVerify"], - ["directive-inline", "SSLProxyVerifyDepth"], - ["directive-inline", "SSLRandomSeed"], - ["directive-inline", "SSLRenegBufferSize"], - ["directive-inline", "SSLRequire"], - ["directive-inline", "SSLRequireSSL"], - ["directive-inline", "SSLSessionCache"], - ["directive-inline", "SSLSessionCacheTimeout"], - ["directive-inline", "SSLSessionTicketKeyFile"], - ["directive-inline", "SSLSRPUnknownUserSeed"], - ["directive-inline", "SSLSRPVerifierFile"], - ["directive-inline", "SSLStaplingCache"], - ["directive-inline", "SSLStaplingErrorCacheTimeout"], - ["directive-inline", "SSLStaplingFakeTryLater"], - ["directive-inline", "SSLStaplingForceURL"], - ["directive-inline", "SSLStaplingResponderTimeout"], - ["directive-inline", "SSLStaplingResponseMaxAge"], - ["directive-inline", "SSLStaplingResponseTimeSkew"], - ["directive-inline", "SSLStaplingReturnResponderErrors"], - ["directive-inline", "SSLStaplingStandardCacheTimeout"], - ["directive-inline", "SSLStrictSNIVHostCheck"], - ["directive-inline", "SSLUserName"], - ["directive-inline", "SSLUseStapling"], - ["directive-inline", "SSLVerifyClient"], - ["directive-inline", "SSLVerifyDepth"], - ["directive-inline", "StartServers"], - ["directive-inline", "StartThreads"], - ["directive-inline", "Substitute"], - ["directive-inline", "Suexec"], - ["directive-inline", "SuexecUserGroup"], - ["directive-inline", "ThreadLimit"], - ["directive-inline", "ThreadsPerChild"], - ["directive-inline", "ThreadStackSize"], - ["directive-inline", "TimeOut"], - ["directive-inline", "TraceEnable"], - ["directive-inline", "TransferLog"], - ["directive-inline", "TypesConfig"], - ["directive-inline", "UnDefine"], - ["directive-inline", "UndefMacro"], - ["directive-inline", "UnsetEnv"], - ["directive-inline", "Use"], - ["directive-inline", "UseCanonicalName"], - ["directive-inline", "UseCanonicalPhysicalPort"], - ["directive-inline", "User"], - ["directive-inline", "UserDir"], - ["directive-inline", "VHostCGIMode"], - ["directive-inline", "VHostCGIPrivs"], - ["directive-inline", "VHostGroup"], - ["directive-inline", "VHostPrivs"], - ["directive-inline", "VHostSecure"], - ["directive-inline", "VHostUser"], - ["directive-inline", "VirtualDocumentRoot"], - ["directive-inline", "VirtualDocumentRootIP"], - ["directive-inline", "VirtualScriptAlias"], - ["directive-inline", "VirtualScriptAliasIP"], - ["directive-inline", "WatchdogInterval"], - ["directive-inline", "XBitHack"], - ["directive-inline", "xml2EncAlias"], - ["directive-inline", "xml2EncDefault"], - ["directive-inline", "xml2StartParse"] -] - ----------------------------------------------------- - -Checks for all inline directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/regex_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/regex_feature.test deleted file mode 100644 index 322e59f5..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/regex_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -^(.*)$ -^foo -bar$ - ----------------------------------------------------- - -[ - ["regex", "^(.*)$"], - ["regex", "^foo"], - ["regex", "bar$"] -] - ----------------------------------------------------- - -Checks for regex. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/string_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/string_feature.test deleted file mode 100644 index d2849caf..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/string_feature.test +++ /dev/null @@ -1,24 +0,0 @@ -"foo bar" -'foo bar' -"%{REMOTE_HOST}" - ----------------------------------------------------- - -[ - ["string", [ - "\"foo bar\"" - ]], - ["string", [ - "'foo bar'" - ]], - ["string", [ - "\"", - ["variable", "%{REMOTE_HOST}"], - "\"" - ]] -] - ----------------------------------------------------- - -Checks for strings. -Also checks for variables inside strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apacheconf/variable_feature.test b/docs/_style/prism-master/tests/languages/apacheconf/variable_feature.test deleted file mode 100644 index 025fe75b..00000000 --- a/docs/_style/prism-master/tests/languages/apacheconf/variable_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -$port -${docroot} -%{REMOTE_HOST} - ----------------------------------------------------- - -[ - ["variable", "$port"], - ["variable", "${docroot}"], - ["variable", "%{REMOTE_HOST}"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/assignment_feature.test b/docs/_style/prism-master/tests/languages/apl/assignment_feature.test deleted file mode 100644 index b06cb39b..00000000 --- a/docs/_style/prism-master/tests/languages/apl/assignment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -a←1 2 3 - ----------------------------------------------------- - -[ - "a", - ["assignment", "←"], - ["number", "1"], ["number", "2"], ["number", "3"] -] - ----------------------------------------------------- - -Checks for assignment. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/comment_feature.test b/docs/_style/prism-master/tests/languages/apl/comment_feature.test deleted file mode 100644 index a3084510..00000000 --- a/docs/_style/prism-master/tests/languages/apl/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -⍝ -⍝ Foobar -#!/usr/bin/env runapl - ----------------------------------------------------- - -[ - ["comment", "⍝"], - ["comment", "⍝ Foobar"], - ["comment", "#!/usr/bin/env runapl"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/constant_feature.test b/docs/_style/prism-master/tests/languages/apl/constant_feature.test deleted file mode 100644 index 9f631626..00000000 --- a/docs/_style/prism-master/tests/languages/apl/constant_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -⍬ -⌾ -# -⎕ -⍞ - ----------------------------------------------------- - -[ - ["constant", "⍬"], - ["constant", "⌾"], - ["constant", "#"], - ["constant", "⎕"], - ["constant", "⍞"] -] - ----------------------------------------------------- - -Checks for constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/dfn_feature.test b/docs/_style/prism-master/tests/languages/apl/dfn_feature.test deleted file mode 100644 index ef4be35c..00000000 --- a/docs/_style/prism-master/tests/languages/apl/dfn_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -{0=⍴⍴⍺:'hello' ⋄ ∇¨⍵} - ----------------------------------------------------- - -[ - ["dfn", "{"], - ["number", "0"], - ["function", "="], - ["function", "⍴"], - ["function", "⍴"], - ["dfn", "⍺"], - ["dfn", ":"], - ["string", "'hello'"], - ["punctuation", "⋄"], - ["dfn", "∇"], - ["monadic-operator", "¨"], - ["dfn", "⍵"], - ["dfn", "}"] -] - ----------------------------------------------------- - -Checks for Dfns. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/dyadic-operator_feature.test b/docs/_style/prism-master/tests/languages/apl/dyadic-operator_feature.test deleted file mode 100644 index bd141ede..00000000 --- a/docs/_style/prism-master/tests/languages/apl/dyadic-operator_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -. ⍣ ⍠ -⍤ ∘ ⌸ -@ ⌺ - ----------------------------------------------------- - -[ - ["dyadic-operator", "."], ["dyadic-operator", "⍣"], ["dyadic-operator", "⍠"], - ["dyadic-operator", "⍤"], ["dyadic-operator", "∘"], ["dyadic-operator", "⌸"], - ["dyadic-operator", "@"], ["dyadic-operator", "⌺"] -] - ----------------------------------------------------- - -Checks for dyadic operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/function_feature.test b/docs/_style/prism-master/tests/languages/apl/function_feature.test deleted file mode 100644 index b926e51e..00000000 --- a/docs/_style/prism-master/tests/languages/apl/function_feature.test +++ /dev/null @@ -1,43 +0,0 @@ -- + × ÷ -⌈ ⌊ ∣ | -⍳ ⍸ ? * -⍟ ○ ! ⌹ -< ≤ = > -≥ ≠ ≡ ≢ -∊ ⍷ ∪ ∩ -~ ∨ ∧ ⍱ -⍲ ⍴ , ⍪ -⌽ ⊖ ⍉ -↑ ↓ ⊂ ⊃ -⊆ ⊇ -⌷ ⍋ ⍒ -⊤ ⊥ ⍕ ⍎ -⊣ ⊢ ⍁ ⍂ -≈ ⍯ -↗ ¤ → - ----------------------------------------------------- - -[ - ["function", "-"], ["function", "+"], ["function", "×"], ["function", "÷"], - ["function", "⌈"], ["function", "⌊"], ["function", "∣"], ["function", "|"], - ["function", "⍳"], ["function", "⍸"], ["function", "?"], ["function", "*"], - ["function", "⍟"], ["function", "○"], ["function", "!"], ["function", "⌹"], - ["function", "<"], ["function", "≤"], ["function", "="], ["function", ">"], - ["function", "≥"], ["function", "≠"], ["function", "≡"], ["function", "≢"], - ["function", "∊"], ["function", "⍷"], ["function", "∪"], ["function", "∩"], - ["function", "~"], ["function", "∨"], ["function", "∧"], ["function", "⍱"], - ["function", "⍲"], ["function", "⍴"], ["function", ","], ["function", "⍪"], - ["function", "⌽"], ["function", "⊖"], ["function", "⍉"], - ["function", "↑"], ["function", "↓"], ["function", "⊂"], ["function", "⊃"], - ["function", "⊆"], ["function", "⊇"], - ["function", "⌷"], ["function", "⍋"], ["function", "⍒"], - ["function", "⊤"], ["function", "⊥"], ["function", "⍕"], ["function", "⍎"], - ["function", "⊣"], ["function", "⊢"], ["function", "⍁"], ["function", "⍂"], - ["function", "≈"], ["function", "⍯"], - ["function", "↗"], ["function", "¤"], ["function", "→"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/monadic-operator_feature.test b/docs/_style/prism-master/tests/languages/apl/monadic-operator_feature.test deleted file mode 100644 index 0dcc9b08..00000000 --- a/docs/_style/prism-master/tests/languages/apl/monadic-operator_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -\ / ⌿ ⍀ -¨ ⍨ ⌶ -& ∥ - ----------------------------------------------------- - -[ - ["monadic-operator", "\\"], ["monadic-operator", "/"], ["monadic-operator", "⌿"], ["monadic-operator", "⍀"], - ["monadic-operator", "¨"], ["monadic-operator", "⍨"], ["monadic-operator", "⌶"], - ["monadic-operator", "&"], ["monadic-operator", "∥"] -] - ----------------------------------------------------- - -Checks for monadic operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/number_feature.test b/docs/_style/prism-master/tests/languages/apl/number_feature.test deleted file mode 100644 index 815d1ca8..00000000 --- a/docs/_style/prism-master/tests/languages/apl/number_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -42 -3.14159 -¯2 -∞ -3E12 -2.8e¯4 -0.1e+7 -2j3 -¯4.3e2J1.9e¯4 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "¯2"], - ["number", "∞"], - ["number", "3E12"], - ["number", "2.8e¯4"], - ["number", "0.1e+7"], - ["number", "2j3"], - ["number", "¯4.3e2J1.9e¯4"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/statement_feature.test b/docs/_style/prism-master/tests/languages/apl/statement_feature.test deleted file mode 100644 index 24c68744..00000000 --- a/docs/_style/prism-master/tests/languages/apl/statement_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -:Ab -:FooBar - ----------------------------------------------------- - -[ - ["statement", ":Ab"], - ["statement", ":FooBar"] -] - ----------------------------------------------------- - -Checks for statements. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/string_feature.test b/docs/_style/prism-master/tests/languages/apl/string_feature.test deleted file mode 100644 index 8bc8dd1b..00000000 --- a/docs/_style/prism-master/tests/languages/apl/string_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -'' -'foobar' -'fo''obar' - ----------------------------------------------------- - -[ - ["string", "''"], - ["string", "'foobar'"], - ["string", "'fo''obar'"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/apl/system-function_feature.test b/docs/_style/prism-master/tests/languages/apl/system-function_feature.test deleted file mode 100644 index 81cbcaa1..00000000 --- a/docs/_style/prism-master/tests/languages/apl/system-function_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -⎕IO -⎕WA -⎕CR -⎕TCNL - ----------------------------------------------------- - -[ - ["system-function", "⎕IO"], - ["system-function", "⎕WA"], - ["system-function", "⎕CR"], - ["system-function", "⎕TCNL"] -] - ----------------------------------------------------- - -Checks for system functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/class_feature.test b/docs/_style/prism-master/tests/languages/applescript/class_feature.test deleted file mode 100644 index 547906de..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/class_feature.test +++ /dev/null @@ -1,39 +0,0 @@ -alias application boolean class constant -date file integer list number -POSIX file -real record reference -RGB color -script text centimetres centimeters feet -inches kilometres kilometers metres meters -miles yards -square feet square kilometres square kilometers square metres -square meters square miles square yards -cubic centimetres cubic centimeters cubic feet cubic inches -cubic metres cubic meters cubic yards -gallons litres liters quarts grams -kilograms ounces pounds -degrees Celsius degrees Fahrenheit degrees Kelvin - ----------------------------------------------------- - -[ - ["class", "alias"], ["class", "application"], ["class", "boolean"], ["class", "class"], ["class", "constant"], - ["class", "date"], ["class", "file"], ["class", "integer"], ["class", "list"], ["class", "number"], - ["class", "POSIX file"], - ["class", "real"], ["class", "record"], ["class", "reference"], - ["class", "RGB color"], - ["class", "script"], ["class", "text"], ["class", "centimetres"], ["class", "centimeters"], ["class", "feet"], - ["class", "inches"], ["class", "kilometres"], ["class", "kilometers"], ["class", "metres"], ["class", "meters"], - ["class", "miles"], ["class", "yards"], - ["class", "square feet"], ["class", "square kilometres"], ["class", "square kilometers"], ["class", "square metres"], - ["class", "square meters"], ["class", "square miles"], ["class", "square yards"], - ["class", "cubic centimetres"], ["class", "cubic centimeters"], ["class", "cubic feet"], ["class", "cubic inches"], - ["class", "cubic metres"], ["class", "cubic meters"], ["class", "cubic yards"], - ["class", "gallons"], ["class", "litres"], ["class", "liters"], ["class", "quarts"], ["class", "grams"], - ["class", "kilograms"], ["class", "ounces"], ["class", "pounds"], - ["class", "degrees Celsius"], ["class", "degrees Fahrenheit"], ["class", "degrees Kelvin"] -] - ----------------------------------------------------- - -Checks for all classes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/comment_feature.test b/docs/_style/prism-master/tests/languages/applescript/comment_feature.test deleted file mode 100644 index d823ba42..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/comment_feature.test +++ /dev/null @@ -1,21 +0,0 @@ --- foo bar -# foo bar -(* foo -bar *) -(* foo -(* bar *) -*) - ----------------------------------------------------- - -[ - ["comment", "-- foo bar"], - ["comment", "# foo bar"], - ["comment", "(* foo\r\nbar *)"], - ["comment", "(* foo\r\n(* bar *)\r\n*)"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. -Also checks for one level of nesting. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/keyword_feature.test b/docs/_style/prism-master/tests/languages/applescript/keyword_feature.test deleted file mode 100644 index bde63a16..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/keyword_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -about above after against -apart from -around -aside from -at back before beginning behind below -beneath beside between but by -considering continue copy -does eighth else end -equal error every exit -false fifth first for fourth -from front get given global -if ignoring in -instead of -into is it its last -local me middle my -ninth of on onto -out of -over prop property put -repeat return returning -second set seventh since sixth -some tell tenth that the -then third through thru timeout -times to transaction true try -until where while whose with -without - ----------------------------------------------------- - -[ - ["keyword", "about"], ["keyword", "above"], ["keyword", "after"], ["keyword", "against"], - ["keyword", "apart from"], - ["keyword", "around"], - ["keyword", "aside from"], - ["keyword", "at"], ["keyword", "back"], ["keyword", "before"], ["keyword", "beginning"], ["keyword", "behind"], ["keyword", "below"], - ["keyword", "beneath"], ["keyword", "beside"], ["keyword", "between"], ["keyword", "but"], ["keyword", "by"], - ["keyword", "considering"], ["keyword", "continue"], ["keyword", "copy"], - ["keyword", "does"], ["keyword", "eighth"], ["keyword", "else"], ["keyword", "end"], - ["keyword", "equal"], ["keyword", "error"], ["keyword", "every"], ["keyword", "exit"], - ["keyword", "false"], ["keyword", "fifth"], ["keyword", "first"], ["keyword", "for"], ["keyword", "fourth"], - ["keyword", "from"], ["keyword", "front"], ["keyword", "get"], ["keyword", "given"], ["keyword", "global"], - ["keyword", "if"], ["keyword", "ignoring"], ["keyword", "in"], - ["keyword", "instead of"], - ["keyword", "into"], ["keyword", "is"], ["keyword", "it"], ["keyword", "its"], ["keyword", "last"], - ["keyword", "local"], ["keyword", "me"], ["keyword", "middle"], ["keyword", "my"], - ["keyword", "ninth"], ["keyword", "of"], ["keyword", "on"], ["keyword", "onto"], - ["keyword", "out of"], - ["keyword", "over"], ["keyword", "prop"], ["keyword", "property"], ["keyword", "put"], - ["keyword", "repeat"], ["keyword", "return"], ["keyword", "returning"], - ["keyword", "second"], ["keyword", "set"], ["keyword", "seventh"], ["keyword", "since"], ["keyword", "sixth"], - ["keyword", "some"], ["keyword", "tell"], ["keyword", "tenth"], ["keyword", "that"], ["keyword", "the"], - ["keyword", "then"], ["keyword", "third"], ["keyword", "through"], ["keyword", "thru"], ["keyword", "timeout"], - ["keyword", "times"], ["keyword", "to"], ["keyword", "transaction"], ["keyword", "true"], ["keyword", "try"], - ["keyword", "until"], ["keyword", "where"], ["keyword", "while"], ["keyword", "whose"], ["keyword", "with"], - ["keyword", "without"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/number_feature.test b/docs/_style/prism-master/tests/languages/applescript/number_feature.test deleted file mode 100644 index 96eb6f28..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/number_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -42 -3.14159 -3e10 -4.2E-5 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "3e10"], - ["number", "4.2E-5"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/operator_feature.test b/docs/_style/prism-master/tests/languages/applescript/operator_feature.test deleted file mode 100644 index fb4acf4d..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/operator_feature.test +++ /dev/null @@ -1,48 +0,0 @@ -& = ≠ ≤ ≥ -* + - / ÷ ^ -< <= > >= - -start with begin with end with -starts with begins with ends with -does not contain doesn't contain -contain contains -is in isn't in is not in -is contained by isn't contained by is not contained by -greater than greater than or equal greater than or equal to -less than less than or equal less than or equal to -does not come before doesn't come before comes before -does not come after doesn't come after comes after -is equal isn't equal is not equal -is equal to isn't equal to is not equal to -does not equal doesn't equal equals -isn't is not -ref a ref to a reference to -and or div mod as not - ----------------------------------------------------- - -[ - ["operator", "&"], ["operator", "="], ["operator", "≠"], ["operator", "≤"], ["operator", "≥"], - ["operator", "*"], ["operator", "+"], ["operator", "-"], ["operator", "/"], ["operator", "÷"], ["operator", "^"], - ["operator", "<"], ["operator", "<="], ["operator", ">"], ["operator", ">="], - ["operator", "start with"], ["operator", "begin with"], ["operator", "end with"], - ["operator", "starts with"], ["operator", "begins with"], ["operator", "ends with"], - ["operator", "does not contain"], ["operator", "doesn't contain"], - ["operator", "contain"], ["operator", "contains"], - ["operator", "is in"], ["operator", "isn't in"], ["operator", "is not in"], - ["operator", "is contained by"], ["operator", "isn't contained by"], ["operator", "is not contained by"], - ["operator", "greater than"], ["operator", "greater than or equal"], ["operator", "greater than or equal to"], - ["operator", "less than"], ["operator", "less than or equal"], ["operator", "less than or equal to"], - ["operator", "does not come before"], ["operator", "doesn't come before"], ["operator", "comes before"], - ["operator", "does not come after"], ["operator", "doesn't come after"], ["operator", "comes after"], - ["operator", "is equal"], ["operator", "isn't equal"], ["operator", "is not equal"], - ["operator", "is equal to"], ["operator", "isn't equal to"], ["operator", "is not equal to"], - ["operator", "does not equal"], ["operator", "doesn't equal"], ["operator", "equals"], - ["operator", "isn't"], ["operator", "is not"], - ["operator", "ref"], ["operator", "a ref to"], ["operator", "a reference to"], - ["operator", "and"], ["operator", "or"], ["operator", "div"], ["operator", "mod"], ["operator", "as"], ["operator", "not"] -] - ----------------------------------------------------- - -Checks for most of the operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/applescript/string_feature.test b/docs/_style/prism-master/tests/languages/applescript/string_feature.test deleted file mode 100644 index 42770f61..00000000 --- a/docs/_style/prism-master/tests/languages/applescript/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"foo bar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo bar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/arff/comment_feature.test b/docs/_style/prism-master/tests/languages/arff/comment_feature.test deleted file mode 100644 index 4d53a8e1..00000000 --- a/docs/_style/prism-master/tests/languages/arff/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -% -% Some comment -% Comment " with ' quotes - ----------------------------------------------------- - -[ - ["comment", "%"], - ["comment", "% Some comment"], - ["comment", "% Comment \" with ' quotes"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/arff/keyword_feature.test b/docs/_style/prism-master/tests/languages/arff/keyword_feature.test deleted file mode 100644 index b1c98169..00000000 --- a/docs/_style/prism-master/tests/languages/arff/keyword_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -@attribute -@data -@end -@relation - ----------------------------------------------------- - -[ - ["keyword", "@attribute"], - ["keyword", "@data"], - ["keyword", "@end"], - ["keyword", "@relation"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/arff/number_feature.test b/docs/_style/prism-master/tests/languages/arff/number_feature.test deleted file mode 100644 index 99969b4d..00000000 --- a/docs/_style/prism-master/tests/languages/arff/number_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -42 -0.14 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "0.14"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/arff/string_feature.test b/docs/_style/prism-master/tests/languages/arff/string_feature.test deleted file mode 100644 index c3178f17..00000000 --- a/docs/_style/prism-master/tests/languages/arff/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"double quoted\"' % string" -'single quoted"\' % string' - ----------------------------------------------------- - -[ - ["string", "\"double quoted\\\"' % string\""], - ["string", "'single quoted\"\\' % string'"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/admonition_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/admonition_feature.test deleted file mode 100644 index 6b1688f5..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/admonition_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -TIP: Foobar -NOTE: Foo bar baz -IMPORTANT: Foobar -WARNING: Foo bar baz -CAUTION: Foobar - ----------------------------------------------------- - -[ - ["admonition", "TIP:"], " Foobar\r\n", - ["admonition", "NOTE:"], " Foo bar baz\r\n", - ["admonition", "IMPORTANT:"], " Foobar\r\n", - ["admonition", "WARNING:"], " Foo bar baz\r\n", - ["admonition", "CAUTION:"], " Foobar" -] - ----------------------------------------------------- - -Checks for admonitions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/attribute-entry_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/attribute-entry_feature.test deleted file mode 100644 index a12299ab..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/attribute-entry_feature.test +++ /dev/null @@ -1,58 +0,0 @@ -:Foo bar: baz - -:Foobar: Foo + -bar + -baz - -:Foo bar!: -:Foobar!: - -===== -:Foo bar: baz - -:Foobar: Foo + -bar + -baz - -:Foo bar!: -:Foobar!: -===== - -|===== -| -:Foo bar: baz - -:Foobar: Foo + -bar + -baz -|===== - ----------------------------------------------------- - -[ - ["attribute-entry", ":Foo bar: baz"], - ["attribute-entry", ":Foobar: Foo +\r\nbar +\r\nbaz"], - ["attribute-entry", ":Foo bar!:"], - ["attribute-entry", ":Foobar!:"], - - ["other-block", [ - ["punctuation", "====="], - ["attribute-entry", ":Foo bar: baz"], - ["attribute-entry", ":Foobar: Foo +\r\nbar +\r\nbaz"], - ["attribute-entry", ":Foo bar!:"], - ["attribute-entry", ":Foobar!:"], - ["punctuation", "====="] - ]], - - ["table", [ - ["punctuation", "|====="], - ["punctuation", "|"], - ["attribute-entry", ":Foo bar: baz"], - ["attribute-entry", ":Foobar: Foo +\r\nbar +\r\nbaz"], - ["punctuation", "|====="] - ]] -] - ----------------------------------------------------- - -Checks for attribute entries. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/attributes_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/attributes_feature.test deleted file mode 100644 index a4eb9e25..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/attributes_feature.test +++ /dev/null @@ -1,403 +0,0 @@ -Foo [big red yellow-background]#obvious# - -[float] -[quote,'http://en.wikipedia.org/wiki/Samuel_Johnson[Samuel%20Johnson]'] -[quote,'"with *an* image" image:foo.png[] (TM)'] - -[NOTE] -[icon="./images/icons/wink.png"] -[icons=None, caption="My Special Note"] -[start=7] - -[cols="e,m,^,>s",width="25%"] - -===== -Foo [big red yellow-background]#obvious# - -[float] -[quote,'http://en.wikipedia.org/wiki/Samuel_Johnson[Samuel%20Johnson]'] -[quote,'"with *an* image" image:foo.png[] (TM)'] - -[NOTE] -[icon="./images/icons/wink.png"] -[icons=None, caption="My Special Note"] -[start=7] - -[cols="e,m,^,>s",width="25%"] -===== - -|===== -| -Foo [big red yellow-background]#obvious# - -[float] -[quote,'http://en.wikipedia.org/wiki/Samuel_Johnson[Samuel%20Johnson]'] -[quote,'"with *an* image" image:foo.png[] (TM)'] - -[NOTE] -[icon="./images/icons/wink.png"] -[icons=None, caption="My Special Note"] -[start=7] - -[cols="e,m,^,>s",width="25%"] -|===== - - -latexmath:[$C = \alpha + \beta Y^{\gamma} + \epsilon$] -asciimath:[`x/x={(1,if x!=0),(text{undefined},if x=0):}`] -latexmath:[$\sum_{n=1}^\infty \frac{1}{2^n}$] - ----------------------------------------------------- - -[ - "Foo ", - ["inline", [ - ["attributes", [ - ["punctuation", "["], - ["attr-value", "big red yellow-background"], - ["punctuation", "]"] - ]], - ["punctuation", "#"], "obvious", ["punctuation", "#"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "float"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//en.wikipedia.org/wiki/Samuel_Johnson", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Samuel Johnson"], - ["punctuation", "]"] - ]] - ]], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["entity", """], - "with ", - ["inline", [ - ["bold", [ - ["punctuation", "*"], "an", ["punctuation", "*"] - ]] - ]], - " image", - ["entity", """], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "foo.png", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["replacement", "(TM)"], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "NOTE"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icon"], - ["operator", "="], - ["string", "\"./images/icons/wink.png\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icons"], - ["operator", "="], - ["attr-value", "None"], - ["punctuation", ","], - ["variable", "caption"], - ["operator", "="], - ["string", "\"My Special Note\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "start"], - ["operator", "="], - ["attr-value", "7"], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], - ["variable", "cols"], - ["operator", "="], - ["string", "\"e,m,^,>s\""], - ["punctuation", ","], - ["variable", "width"], - ["operator", "="], - ["string", "\"25%\""], - ["punctuation", "]"] - ]], - - ["other-block", [ - ["punctuation", "====="], - - "\r\nFoo ", - ["inline", [ - ["attributes", [ - ["punctuation", "["], - ["attr-value", "big red yellow-background"], - ["punctuation", "]"] - ]], - ["punctuation", "#"], "obvious", ["punctuation", "#"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "float"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//en.wikipedia.org/wiki/Samuel_Johnson", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Samuel Johnson"], - ["punctuation", "]"] - ]] - ]], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["entity", """], - "with ", - ["inline", [ - ["bold", [ - ["punctuation", "*"], "an", ["punctuation", "*"] - ]] - ]], - " image", - ["entity", """], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "foo.png", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["replacement", "(TM)"], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "NOTE"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icon"], - ["operator", "="], - ["string", "\"./images/icons/wink.png\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icons"], - ["operator", "="], - ["attr-value", "None"], - ["punctuation", ","], - ["variable", "caption"], - ["operator", "="], - ["string", "\"My Special Note\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "start"], - ["operator", "="], - ["attr-value", "7"], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], - ["variable", "cols"], - ["operator", "="], - ["string", "\"e,m,^,>s\""], - ["punctuation", ","], - ["variable", "width"], - ["operator", "="], - ["string", "\"25%\""], - ["punctuation", "]"] - ]], - - ["punctuation", "====="] - ]], - - ["table", [ - ["punctuation", "|====="], - ["punctuation", "|"], - - "\r\nFoo ", - ["inline", [ - ["attributes", [ - ["punctuation", "["], - ["attr-value", "big red yellow-background"], - ["punctuation", "]"] - ]], - ["punctuation", "#"], "obvious", ["punctuation", "#"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "float"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//en.wikipedia.org/wiki/Samuel_Johnson", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Samuel Johnson"], - ["punctuation", "]"] - ]] - ]], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "quote"], ["punctuation", ","], - ["interpreted", [ - ["punctuation", "'"], - ["entity", """], - "with ", - ["inline", [ - ["bold", [ - ["punctuation", "*"], "an", ["punctuation", "*"] - ]] - ]], - " image", - ["entity", """], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "foo.png", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["replacement", "(TM)"], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], ["attr-value", "NOTE"], ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icon"], - ["operator", "="], - ["string", "\"./images/icons/wink.png\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "icons"], - ["operator", "="], - ["attr-value", "None"], - ["punctuation", ","], - ["variable", "caption"], - ["operator", "="], - ["string", "\"My Special Note\""], - ["punctuation", "]"] - ]], - ["attributes", [ - ["punctuation", "["], - ["variable", "start"], - ["operator", "="], - ["attr-value", "7"], - ["punctuation", "]"] - ]], - - ["attributes", [ - ["punctuation", "["], - ["variable", "cols"], - ["operator", "="], - ["string", "\"e,m,^,>s\""], - ["punctuation", ","], - ["variable", "width"], - ["operator", "="], - ["string", "\"25%\""], - ["punctuation", "]"] - ]], - - ["punctuation", "|====="] - ]], - - ["macro", [ - ["function", "latexmath"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["quoted", [ - ["punctuation", "$"], - "C = \\alpha + \\beta Y^{\\gamma} + \\epsilon", - ["punctuation", "$"] - ]], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "asciimath"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["quoted", [ - ["punctuation", "`"], - "x/x={(1,if x!=0),(text{undefined},if x=0):}", - ["punctuation", "`"] - ]], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "latexmath"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["quoted", [ - ["punctuation", "$"], - "\\sum_{n=1}^\\infty \\frac{1}{2^n}", - ["punctuation", "$"] - ]], - ["punctuation", "]"] - ]] - ]] -] - ----------------------------------------------------- - -Checks for attributes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/callout_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/callout_feature.test deleted file mode 100644 index b7de9b1b..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/callout_feature.test +++ /dev/null @@ -1,34 +0,0 @@ -Foobar <1> -<1> Foo -1> Bar -> Baz - -|==== -| Foobar <1> -<1> Foo -1> Bar -> Baz -|==== - ----------------------------------------------------- - -[ - "Foobar ", ["callout", "<1>"], - ["callout", "<1>"], " Foo\r\n", - ["callout", "1>"], " Bar\r\n", - ["callout", ">"], " Baz\r\n\r\n", - - ["table", [ - ["punctuation", "|===="], - ["punctuation", "|"], - " Foobar ", ["callout", "<1>"], - ["callout", "<1>"], " Foo\r\n", - ["callout", "1>"], " Bar\r\n", - ["callout", ">"], " Baz\r\n", - ["punctuation", "|===="] - ]] -] - ----------------------------------------------------- - -Checks for callouts. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/comment-block_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/comment-block_feature.test deleted file mode 100644 index 4c4248ed..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/comment-block_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -//// -//// - -//// -Foobar - -Baz -//// - ----------------------------------------------------- - -[ - ["comment-block", "////\r\n////"], - ["comment-block", "////\r\nFoobar\r\n\r\nBaz\r\n////"] -] - ----------------------------------------------------- - -Checks for comment blocks. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/comment_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/comment_feature.test deleted file mode 100644 index c2ccc51b..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/comment_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -// -// Foobar - -****** -// -// Foobar -****** - -|====== -| -// -| -// Foobar -|====== - ----------------------------------------------------- - -[ - ["comment", "//"], - ["comment", "// Foobar"], - - ["other-block", [ - ["punctuation", "******"], - ["comment", "//"], - ["comment", "// Foobar"], - ["punctuation", "******"] - ]], - - ["table", [ - ["punctuation", "|======"], - ["punctuation", "|"], - ["comment", "//"], - ["punctuation", "|"], - ["comment", "// Foobar"], - ["punctuation", "|======"] - ]] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.js b/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.js deleted file mode 100644 index 2e99cd10..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - '➊': '&#x278a;', - '¶': '&#182;' -}; \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.test deleted file mode 100644 index a3d5ce81..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/entity_feature.test +++ /dev/null @@ -1,48 +0,0 @@ -➊ ¶ - -➊ ¶ -============ - -['➊ ¶'] - --- -➊ ¶ --- - -|====== -| ➊ ¶ -|====== - ----------------------------------------------------- - -[ - ["entity", "➊"], ["entity", "¶"], - ["title", [ - ["entity", "➊"], ["entity", "¶"], - ["punctuation", "============"] - ]], - ["attributes", [ - ["punctuation", "["], - ["interpreted", [ - ["punctuation", "'"], - ["entity", "➊"], ["entity", "¶"], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - ["other-block", [ - ["punctuation", "--"], - ["entity", "➊"], ["entity", "¶"], - ["punctuation", "--"] - ]], - ["table", [ - ["punctuation", "|======"], - ["punctuation", "|"], - ["entity", "➊"], ["entity", "¶"], - ["punctuation", "|======"] - ]] -] - ----------------------------------------------------- - -Checks for entities. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/hr_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/hr_feature.test deleted file mode 100644 index 822a7dbb..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/hr_feature.test +++ /dev/null @@ -1,14 +0,0 @@ -''' - -'''''''''' - ----------------------------------------------------- - -[ - ["hr", "'''"], - ["hr", "''''''''''"] -] - ----------------------------------------------------- - -Checks for hr. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/indented-block_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/indented-block_feature.test deleted file mode 100644 index c6d66575..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/indented-block_feature.test +++ /dev/null @@ -1,28 +0,0 @@ -. - - (TM) __foobar__ - :bar: baz - - Foo *bar* baz - // Foobar - == Foobar == - - Title - ~~~~~ - ..... - ..... - ----------------------------------------------------- - -[ - ".\r\n\r\n", - ["indented-block", "\t(TM) __foobar__\r\n\t:bar: baz"], - ["indented-block", " Foo *bar* baz\r\n // Foobar\r\n == Foobar =="], - ["indented-block", " Title\r\n ~~~~~\r\n .....\r\n ....."] -] - ----------------------------------------------------- - -Checks for indented blocks. -Also checks that nothing gets highlighted inside. -The initial dot is required because tests are trimmed. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/inline_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/inline_feature.test deleted file mode 100644 index d88a5708..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/inline_feature.test +++ /dev/null @@ -1,521 +0,0 @@ -_emphasis_ -``double quotes'' -`single quotes' -`monospace` -'emphasis' -*strong* -+monospace+ -#unquoted# - -_foo _ bar baz_ -`foo ' bar baz' -`foo ` bar baz` -'foo ' bar baz' -*foo * bar baz* -+foo + bar baz+ -#foo # bar baz# - -_foo -bar_ -``foo -bar'' -`foo -bar' -`foo -bar` -'foo -bar' -*foo -bar* -+foo -bar+ -#foo -bar# - -foo__emphasis__bar -foo**strong**bar -foo++monospace++bar -foo+++passthrough+++bar -foo##unquoted##bar -foo$$passthrough$$bar -foo~subscript~bar -foo^superscript^bar -foo{attribute-reference}bar -foo[[anchor]]bar -foo[[[bibliography anchor]]]bar -foo<>bar -foo(((indexes)))bar -foo((indexes))bar - -==== -_emphasis_ -``double quotes'' -`single quotes' -`monospace` -'emphasis' -*strong* -+monospace+ -#unquoted# -__emphasis__ -**strong** -++monospace++ -+++passthrough+++ -##unquoted## -$$passthrough$$ -~subscript~ -^superscript^ -{attribute-reference} -[[anchor]] -[[[bibliography anchor]]] -<> -(((indexes))) -((indexes)) -==== - -|==== -| -_emphasis_ -``double quotes'' -`single quotes' -`monospace` -'emphasis' -*strong* -+monospace+ -#unquoted# -__emphasis__ -**strong** -++monospace++ -+++passthrough+++ -##unquoted## -$$passthrough$$ -~subscript~ -^superscript^ -{attribute-reference} -[[anchor]] -[[[bibliography anchor]]] -<> -(((indexes))) -((indexes)) -|==== - -['foo *bar* baz'] - -== foo *bar* baz == - -{names=value} -{names?value} -{names!value} -{names#value} -{names%value} -{names@regexp:value1:value2} -{names$regexp:value1:value2} -{names$regexp::value} -{foo,bar=foobar} -{foo+bar=foobar} -{counter:attrname} - ----------------------------------------------------- - -[ - ["inline", [ - ["italic", [["punctuation", "_"], "emphasis", ["punctuation", "_"]]] - ]], - ["inline", [ - ["punctuation", "``"], "double quotes", ["punctuation", "''"] - ]], - ["inline", [ - ["punctuation", "`"], "single quotes", ["punctuation", "'"] - ]], - ["inline", [ - ["punctuation", "`"], "monospace", ["punctuation", "`"] - ]], - ["inline", [ - ["italic", [["punctuation", "'"], "emphasis", ["punctuation", "'"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "*"], "strong", ["punctuation", "*"]]] - ]], - ["inline", [ - ["punctuation", "+"], "monospace", ["punctuation", "+"] - ]], - ["inline", [ - ["punctuation", "#"], "unquoted", ["punctuation", "#"] - ]], - - ["inline", [ - ["italic", [["punctuation", "_"], "foo _ bar baz", ["punctuation", "_"]]] - ]], - ["inline", [ - ["punctuation", "`"], "foo ' bar baz", ["punctuation", "'"] - ]], - ["inline", [ - ["punctuation", "`"], "foo ` bar baz", ["punctuation", "`"] - ]], - ["inline", [ - ["italic", [["punctuation", "'"], "foo ' bar baz", ["punctuation", "'"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "*"], "foo * bar baz", ["punctuation", "*"]]] - ]], - ["inline", [ - ["punctuation", "+"], "foo + bar baz", ["punctuation", "+"] - ]], - ["inline", [ - ["punctuation", "#"], "foo # bar baz", ["punctuation", "#"] - ]], - - ["inline", [ - ["italic", [["punctuation", "_"], "foo\r\nbar", ["punctuation", "_"]]] - ]], - ["inline", [ - ["punctuation", "``"], "foo\r\nbar", ["punctuation", "''"] - ]], - ["inline", [ - ["punctuation", "`"], "foo\r\nbar", ["punctuation", "'"] - ]], - ["inline", [ - ["punctuation", "`"], "foo\r\nbar", ["punctuation", "`"] - ]], - ["inline", [ - ["italic", [["punctuation", "'"], "foo\r\nbar", ["punctuation", "'"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "*"], "foo\r\nbar", ["punctuation", "*"]]] - ]], - ["inline", [ - ["punctuation", "+"], "foo\r\nbar", ["punctuation", "+"] - ]], - ["inline", [ - ["punctuation", "#"], "foo\r\nbar", ["punctuation", "#"] - ]], - - "\r\n\r\nfoo", - ["inline", [ - ["italic", [["punctuation", "__"], "emphasis", ["punctuation", "__"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["bold", [["punctuation", "**"], "strong", ["punctuation", "**"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "++"], "monospace", ["punctuation", "++"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "+++"], "passthrough", ["punctuation", "+++"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "##"], "unquoted", ["punctuation", "##"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "$$"], "passthrough", ["punctuation", "$$"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "~"], "subscript", ["punctuation", "~"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "^"], "superscript", ["punctuation", "^"] - ]], - "bar\r\nfoo", - ["inline", [ - ["attribute-ref", [["punctuation", "{"], ["variable", "attribute-reference"], ["punctuation", "}"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["url", [["punctuation", "[["], "anchor", ["punctuation", "]]"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["url", [["punctuation", "[[["], "bibliography anchor", ["punctuation", "]]]"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["url", [["punctuation", "<<"], "xref", ["punctuation", ">>"]]] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "((("], "indexes", ["punctuation", ")))"] - ]], - "bar\r\nfoo", - ["inline", [ - ["punctuation", "(("], "indexes", ["punctuation", "))"] - ]], - "bar\r\n\r\n", - - ["other-block", [ - ["punctuation", "===="], - - ["inline", [ - ["italic", [["punctuation", "_"], "emphasis", ["punctuation", "_"]]] - ]], - ["inline", [ - ["punctuation", "``"], "double quotes", ["punctuation", "''"] - ]], - ["inline", [ - ["punctuation", "`"], "single quotes", ["punctuation", "'"] - ]], - ["inline", [ - ["punctuation", "`"], "monospace", ["punctuation", "`"] - ]], - ["inline", [ - ["italic", [["punctuation", "'"], "emphasis", ["punctuation", "'"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "*"], "strong", ["punctuation", "*"]]] - ]], - ["inline", [ - ["punctuation", "+"], "monospace", ["punctuation", "+"] - ]], - ["inline", [ - ["punctuation", "#"], "unquoted", ["punctuation", "#"] - ]], - ["inline", [ - ["italic", [["punctuation", "__"], "emphasis", ["punctuation", "__"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "**"], "strong", ["punctuation", "**"]]] - ]], - ["inline", [ - ["punctuation", "++"], "monospace", ["punctuation", "++"] - ]], - ["inline", [ - ["punctuation", "+++"], "passthrough", ["punctuation", "+++"] - ]], - ["inline", [ - ["punctuation", "##"], "unquoted", ["punctuation", "##"] - ]], - ["inline", [ - ["punctuation", "$$"], "passthrough", ["punctuation", "$$"] - ]], - ["inline", [ - ["punctuation", "~"], "subscript", ["punctuation", "~"] - ]], - ["inline", [ - ["punctuation", "^"], "superscript", ["punctuation", "^"] - ]], - ["inline", [ - ["attribute-ref", [["punctuation", "{"], ["variable", "attribute-reference"], ["punctuation", "}"]]] - ]], - ["inline", [ - ["url", [["punctuation", "[["], "anchor", ["punctuation", "]]"]]] - ]], - ["inline", [ - ["url", [["punctuation", "[[["], "bibliography anchor", ["punctuation", "]]]"]]] - ]], - ["inline", [ - ["url", [["punctuation", "<<"], "xref", ["punctuation", ">>"]]] - ]], - ["inline", [ - ["punctuation", "((("], "indexes", ["punctuation", ")))"] - ]], - ["inline", [ - ["punctuation", "(("], "indexes", ["punctuation", "))"] - ]], - - ["punctuation", "===="] - ]], - - ["table", [ - ["punctuation", "|===="], - ["punctuation", "|"], - - ["inline", [ - ["italic", [["punctuation", "_"], "emphasis", ["punctuation", "_"]]] - ]], - ["inline", [ - ["punctuation", "``"], "double quotes", ["punctuation", "''"] - ]], - ["inline", [ - ["punctuation", "`"], "single quotes", ["punctuation", "'"] - ]], - ["inline", [ - ["punctuation", "`"], "monospace", ["punctuation", "`"] - ]], - ["inline", [ - ["italic", [["punctuation", "'"], "emphasis", ["punctuation", "'"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "*"], "strong", ["punctuation", "*"]]] - ]], - ["inline", [ - ["punctuation", "+"], "monospace", ["punctuation", "+"] - ]], - ["inline", [ - ["punctuation", "#"], "unquoted", ["punctuation", "#"] - ]], - ["inline", [ - ["italic", [["punctuation", "__"], "emphasis", ["punctuation", "__"]]] - ]], - ["inline", [ - ["bold", [["punctuation", "**"], "strong", ["punctuation", "**"]]] - ]], - ["inline", [ - ["punctuation", "++"], "monospace", ["punctuation", "++"] - ]], - ["inline", [ - ["punctuation", "+++"], "passthrough", ["punctuation", "+++"] - ]], - ["inline", [ - ["punctuation", "##"], "unquoted", ["punctuation", "##"] - ]], - ["inline", [ - ["punctuation", "$$"], "passthrough", ["punctuation", "$$"] - ]], - ["inline", [ - ["punctuation", "~"], "subscript", ["punctuation", "~"] - ]], - ["inline", [ - ["punctuation", "^"], "superscript", ["punctuation", "^"] - ]], - ["inline", [ - ["attribute-ref", [["punctuation", "{"], ["variable", "attribute-reference"], ["punctuation", "}"]]] - ]], - ["inline", [ - ["url", [["punctuation", "[["], "anchor", ["punctuation", "]]"]]] - ]], - ["inline", [ - ["url", [["punctuation", "[[["], "bibliography anchor", ["punctuation", "]]]"]]] - ]], - ["inline", [ - ["url", [["punctuation", "<<"], "xref", ["punctuation", ">>"]]] - ]], - ["inline", [ - ["punctuation", "((("], "indexes", ["punctuation", ")))"] - ]], - ["inline", [ - ["punctuation", "(("], "indexes", ["punctuation", "))"] - ]], - - ["punctuation", "|===="] - ]], - - ["attributes", [ - ["punctuation", "["], - ["interpreted", [ - ["punctuation", "'"], - "foo ", ["inline", [["bold", [["punctuation", "*"], "bar", ["punctuation", "*"]]]]], " baz", - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - - ["title", [ - ["punctuation", "=="], - " foo ", ["inline", [["bold", [["punctuation", "*"], "bar", ["punctuation", "*"]]]]], " baz ", - ["punctuation", "=="] - ]], - - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "="], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "?"], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "!"], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "#"], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "%"], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "@"], - "regexp", ["punctuation", ":"], - "value1", ["punctuation", ":"], - "value2", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "$"], - "regexp", ["punctuation", ":"], - "value1", ["punctuation", ":"], - "value2", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "names"], - ["operator", "$"], - "regexp", ["punctuation", "::"], - "value", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "foo,bar"], - ["operator", "="], - "foobar", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "foo+bar"], - ["operator", "="], - "foobar", - ["punctuation", "}"] - ]] - ]], - ["inline", [ - ["attribute-ref", [ - ["punctuation", "{"], - ["variable", "counter"], - ["punctuation", ":"], - "attrname", - ["punctuation", "}"] - ]] - ]] -] - ----------------------------------------------------- - -Checks for all kinds of inline quoted text. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/line-continuation_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/line-continuation_feature.test deleted file mode 100644 index 651fa81f..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/line-continuation_feature.test +++ /dev/null @@ -1,18 +0,0 @@ -Foo + -bar - -* Foo -+ -bar - ----------------------------------------------------- - -[ - "Foo ", ["line-continuation", "+"], "\r\nbar\r\n\r\n", - ["list-punctuation", "*"], " Foo\r\n", - ["line-continuation", "+"], "\r\nbar" -] - ----------------------------------------------------- - -Checks for line continuations. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/list-label_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/list-label_feature.test deleted file mode 100644 index 3e95bb6a..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/list-label_feature.test +++ /dev/null @@ -1,73 +0,0 @@ -In:: -Lorem:: - Foo bar baz -Dolor::: - Ipsum:::: - Donec;; - Foobar - -____ -In:: -Lorem:: - Foo bar baz -Dolor::: - Ipsum:::: - Donec;; - Foobar -____ - -|======== -| -In:: -Lorem:: - Foo bar baz -Dolor::: - Ipsum:::: - Donec;; - Foobar -|======== - ----------------------------------------------------- - -[ - ["list-label", "In::"], - ["list-label", "Lorem::"], - "\r\n Foo bar baz\r\n", - ["list-label", "Dolor:::"], - ["list-label", "Ipsum::::"], - ["list-label", "Donec;;"], - "\r\n Foobar\r\n\r\n", - - ["other-block", [ - ["punctuation", "____"], - - ["list-label", "In::"], - ["list-label", "Lorem::"], - "\r\n Foo bar baz\r\n", - ["list-label", "Dolor:::"], - ["list-label", "Ipsum::::"], - ["list-label", "Donec;;"], - "\r\n Foobar\r\n", - - ["punctuation", "____"] - ]], - - ["table", [ - ["punctuation", "|========"], - ["punctuation", "|"], - - ["list-label", "In::"], - ["list-label", "Lorem::"], - "\r\n Foo bar baz\r\n", - ["list-label", "Dolor:::"], - ["list-label", "Ipsum::::"], - ["list-label", "Donec;;"], - "\r\n Foobar\r\n", - - ["punctuation", "|========"] - ]] -] - ----------------------------------------------------- - -Checks for list labels. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/list-punctuation_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/list-punctuation_feature.test deleted file mode 100644 index 60dbf13c..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/list-punctuation_feature.test +++ /dev/null @@ -1,77 +0,0 @@ -- Foo -* Foo -** Foo bar -*** Foo - 1. Foo - 2. Foo bar - 42. Foo -**** Foo -***** Foo bar - -. Foo -.. Foo - a. Foo - b. Foo - z. Foo -... Foo bar -.... Foo - i) Foo - vi) Bar - xxvii) Baz -..... Foo - -____ -. 1 -.. 2 -____ - -|=== -| -xi) a -xii) b -|=== - ----------------------------------------------------- - -[ - ["list-punctuation", "-"], " Foo\r\n", - ["list-punctuation", "*"], " Foo\r\n", - ["list-punctuation", "**"], " Foo bar\r\n", - ["list-punctuation", "***"], " Foo\r\n\t", - ["list-punctuation", "1."], " Foo\r\n\t", - ["list-punctuation", "2."], " Foo bar\r\n\t", - ["list-punctuation", "42."], " Foo\r\n", - ["list-punctuation", "****"], " Foo\r\n", - ["list-punctuation", "*****"], " Foo bar\r\n\r\n", - - ["list-punctuation", "."], " Foo\r\n", - ["list-punctuation", ".."], " Foo\r\n ", - ["list-punctuation", "a."], " Foo\r\n ", - ["list-punctuation", "b."], " Foo\r\n ", - ["list-punctuation", "z."], " Foo\r\n", - ["list-punctuation", "..."], " Foo bar\r\n", - ["list-punctuation", "...."], " Foo\r\n\t", - ["list-punctuation", "i)"], " Foo\r\n\t", - ["list-punctuation", "vi)"], " Bar\r\n\t", - ["list-punctuation", "xxvii)"], " Baz\r\n", - ["list-punctuation", "....."], " Foo\r\n\r\n", - - ["other-block", [ - ["punctuation", "____"], - ["list-punctuation", "."], " 1\r\n", - ["list-punctuation", ".."], " 2\r\n", - ["punctuation", "____"] - ]], - - ["table", [ - ["punctuation", "|==="], - ["punctuation", "|"], - ["list-punctuation", "xi)"], " a\r\n", - ["list-punctuation", "xii)"], " b\r\n", - ["punctuation", "|==="] - ]] -] - ----------------------------------------------------- - -Checks for list punctuation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/literal-block_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/literal-block_feature.test deleted file mode 100644 index 9449ca06..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/literal-block_feature.test +++ /dev/null @@ -1,46 +0,0 @@ ----- -== Foobar == -Bar _baz_ (TM) <1> -* Foo <2> -<1> Foobar -2> Baz ----- - -....... -.Foo -. Foobar <1> -include::addendum.txt <2> -> Foo -> Bar -__Foo__**bar**{baz} -....... - ----------------------------------------------------- - -[ - ["literal-block", [ - ["punctuation", "----"], - "\r\n== Foobar ==\r\nBar _baz_ (TM) ", - ["callout", "<1>"], - "\r\n* Foo ", - ["callout", "<2>"], - ["callout", "<1>"], " Foobar\r\n", - ["callout", "2>"], " Baz\r\n", - ["punctuation", "----"] - ]], - ["literal-block", [ - ["punctuation", "......."], - "\r\n.Foo\r\n. Foobar ", - ["callout", "<1>"], - "\r\ninclude::addendum.txt ", - ["callout", "<2>"], - ["callout", ">"], " Foo\r\n", - ["callout", ">"], " Bar\r\n__Foo__**bar**{baz}\r\n", - ["punctuation", "......."] - ]] -] - ----------------------------------------------------- - -Checks for literal blocks and listing blocks. -Also checks that nothing gets highlighted inside expect callouts. diff --git a/docs/_style/prism-master/tests/languages/asciidoc/macro_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/macro_feature.test deleted file mode 100644 index 50ee1616..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/macro_feature.test +++ /dev/null @@ -1,250 +0,0 @@ -footnote:[An example footnote.] -indexterm:[Tigers,Big cats] - -http://www.docbook.org/[DocBook.org] -include::chapt1.txt[tabsize=2] -mailto:srackham@gmail.com[] - -image:screen-thumbnail.png[height=32,link="screen.png"] - -== Foo image:foo.jpg[] == - --- -footnote:[An example footnote.] -indexterm:[Tigers,Big cats] - -http://www.docbook.org/[DocBook.org] -include::chapt1.txt[tabsize=2] -mailto:srackham@gmail.com[] - -image:screen-thumbnail.png[height=32,link="screen.png"] --- - -|==== -| -footnote:[An example footnote.] -indexterm:[Tigers,Big cats] - -http://www.docbook.org/[DocBook.org] -include::chapt1.txt[tabsize=2] -mailto:srackham@gmail.com[] - -image:screen-thumbnail.png[height=32,link="screen.png"] -|==== - ----------------------------------------------------- - -[ - ["macro", [ - ["function", "footnote"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "An example footnote."], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "indexterm"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Tigers"], - ["punctuation", ","], - ["attr-value", "Big cats"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//www.docbook.org/", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "DocBook.org"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "include"], ["punctuation", "::"], - "chapt1.txt", - ["attributes", [ - ["punctuation", "["], - ["variable", "tabsize"], - ["operator", "="], - ["attr-value", "2"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "mailto"], ["punctuation", ":"], - "srackham@gmail.com", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "screen-thumbnail.png", - ["attributes", [ - ["punctuation", "["], - ["variable", "height"], - ["operator", "="], - ["attr-value", "32"], - ["punctuation", ","], - ["variable", "link"], - ["operator", "="], - ["string", "\"screen.png\""], - ["punctuation", "]"] - ]] - ]], - - ["title", [ - ["punctuation", "=="], - " Foo ", - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "foo.jpg", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["punctuation", "=="] - ]], - - ["other-block", [ - ["punctuation", "--"], - - ["macro", [ - ["function", "footnote"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "An example footnote."], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "indexterm"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Tigers"], - ["punctuation", ","], - ["attr-value", "Big cats"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//www.docbook.org/", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "DocBook.org"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "include"], ["punctuation", "::"], - "chapt1.txt", - ["attributes", [ - ["punctuation", "["], - ["variable", "tabsize"], - ["operator", "="], - ["attr-value", "2"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "mailto"], ["punctuation", ":"], - "srackham@gmail.com", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "screen-thumbnail.png", - ["attributes", [ - ["punctuation", "["], - ["variable", "height"], - ["operator", "="], - ["attr-value", "32"], - ["punctuation", ","], - ["variable", "link"], - ["operator", "="], - ["string", "\"screen.png\""], - ["punctuation", "]"] - ]] - ]], - - ["punctuation", "--"] - ]], - - ["table", [ - ["punctuation", "|===="], - ["punctuation", "|"], - - ["macro", [ - ["function", "footnote"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "An example footnote."], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "indexterm"], ["punctuation", ":"], - ["attributes", [ - ["punctuation", "["], - ["attr-value", "Tigers"], - ["punctuation", ","], - ["attr-value", "Big cats"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "http"], ["punctuation", ":"], - "//www.docbook.org/", - ["attributes", [ - ["punctuation", "["], - ["attr-value", "DocBook.org"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "include"], ["punctuation", "::"], - "chapt1.txt", - ["attributes", [ - ["punctuation", "["], - ["variable", "tabsize"], - ["operator", "="], - ["attr-value", "2"], - ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "mailto"], ["punctuation", ":"], - "srackham@gmail.com", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["macro", [ - ["function", "image"], ["punctuation", ":"], - "screen-thumbnail.png", - ["attributes", [ - ["punctuation", "["], - ["variable", "height"], - ["operator", "="], - ["attr-value", "32"], - ["punctuation", ","], - ["variable", "link"], - ["operator", "="], - ["string", "\"screen.png\""], - ["punctuation", "]"] - ]] - ]], - - ["punctuation", "|===="] - ]] -] - ----------------------------------------------------- - -Checks for macros. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/other-block_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/other-block_feature.test deleted file mode 100644 index fb33c207..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/other-block_feature.test +++ /dev/null @@ -1,45 +0,0 @@ -**** -Sidebar block <1> -**** - -______ -Quote block <2> -______ - -======== -Example block <3> -======== - --- -Open block <4> --- - ----------------------------------------------------- - -[ - ["other-block", [ - ["punctuation", "****"], - "\r\nSidebar block <1>\r\n", - ["punctuation", "****"] - ]], - ["other-block", [ - ["punctuation", "______"], - "\r\nQuote block <2>\r\n", - ["punctuation", "______"] - ]], - ["other-block", [ - ["punctuation", "========"], - "\r\nExample block <3>\r\n", - ["punctuation", "========"] - ]], - ["other-block", [ - ["punctuation", "--"], - "\r\nOpen block <4>\r\n", - ["punctuation", "--"] - ]] -] - ----------------------------------------------------- - -Checks for sidebar blocks, quote blocks, example blocks and open blocks. -Also checks that callouts are not highlighted. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/page-break_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/page-break_feature.test deleted file mode 100644 index 010a5901..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/page-break_feature.test +++ /dev/null @@ -1,14 +0,0 @@ -<<< - -<<<<<<<<<<<<< - ----------------------------------------------------- - -[ - ["page-break", "<<<"], - ["page-break", "<<<<<<<<<<<<<"] -] - ----------------------------------------------------- - -Checks for page breaks. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/passthrough-block_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/passthrough-block_feature.test deleted file mode 100644 index a5bb08b4..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/passthrough-block_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -++++ -.Fo__o__bar *baz* -Fo(((o)))bar baz -* Foobar baz -include::addendum.txt[] -++++ - ----------------------------------------------------- - -[ - ["passthrough-block", [ - ["punctuation", "++++"], - "\r\n.Fo__o__bar *baz*\r\nFo(((o)))bar baz\r\n* Foobar baz\r\n", - ["macro", [ - ["function", "include"], - ["punctuation", "::"], - "addendum.txt", - ["attributes", [ - ["punctuation", "["], ["punctuation", "]"] - ]] - ]], - ["punctuation", "++++"] - ]] -] - ----------------------------------------------------- - -Checks for passthrough blocks. -Also checks that nothing gets highlighted inside expect macros. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/replacement_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/replacement_feature.test deleted file mode 100644 index ca27610e..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/replacement_feature.test +++ /dev/null @@ -1,48 +0,0 @@ -(C) (TM) (R) - -(C) (TM) (R) -============ - -['(C) (TM) (R)'] - --- -(C) (TM) (R) --- - -|====== -| (C) (TM) (R) -|====== - ----------------------------------------------------- - -[ - ["replacement", "(C)"], ["replacement", "(TM)"], ["replacement", "(R)"], - ["title", [ - ["replacement", "(C)"], ["replacement", "(TM)"], ["replacement", "(R)"], - ["punctuation", "============"] - ]], - ["attributes", [ - ["punctuation", "["], - ["interpreted", [ - ["punctuation", "'"], - ["replacement", "(C)"], ["replacement", "(TM)"], ["replacement", "(R)"], - ["punctuation", "'"] - ]], - ["punctuation", "]"] - ]], - ["other-block", [ - ["punctuation", "--"], - ["replacement", "(C)"], ["replacement", "(TM)"], ["replacement", "(R)"], - ["punctuation", "--"] - ]], - ["table", [ - ["punctuation", "|======"], - ["punctuation", "|"], - ["replacement", "(C)"], ["replacement", "(TM)"], ["replacement", "(R)"], - ["punctuation", "|======"] - ]] -] - ----------------------------------------------------- - -Checks for replacements. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/table_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/table_feature.test deleted file mode 100644 index 0b4bd3c6..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/table_feature.test +++ /dev/null @@ -1,61 +0,0 @@ -|=== -|1 -|=== - -|============================ -|1 >s|2 |3 |4 -^|5 2.2+^.^|6 .3+<.>m|7 -2*^|8 -|9 2+>|10 -|============================ - -|============================================== -|Normal cell - -|Cell with nested table - -!============================================== -!Nested table cell 1 !Nested table cell 2 -!============================================== - -|============================================== - ----------------------------------------------------- - -[ - ["table", [ - ["punctuation", "|==="], - ["punctuation", "|"], "1\r\n", - ["punctuation", "|==="] - ]], - - ["table", [ - ["punctuation", "|============================"], - ["punctuation", "|"], "1 ", - ["specifiers", ">s"], ["punctuation", "|"], "2 ", - ["punctuation", "|"], "3 ", - ["punctuation", "|"], "4\r\n", - ["specifiers", "^"], ["punctuation", "|"], "5 ", - ["specifiers", "2.2+^.^"], ["punctuation", "|"], "6 ", - ["specifiers", ".3+<.>m"], ["punctuation", "|"], "7\r\n", - ["specifiers", "2*^"], ["punctuation", "|"], "8\r\n", - ["punctuation", "|"], "9 ", - ["specifiers", "2+>"], ["punctuation", "|"], "10\r\n", - ["punctuation", "|============================"] - ]], - - ["table", [ - ["punctuation", "|=============================================="], - ["punctuation", "|"], "Normal cell\r\n\r\n", - ["punctuation", "|"], "Cell with nested table\r\n\r\n", - ["punctuation", "!=============================================="], - ["punctuation", "!"], "Nested table cell 1 ", - ["punctuation", "!"], "Nested table cell 2\r\n", - ["punctuation", "!=============================================="], - ["punctuation", "|=============================================="] - ]] -] - ----------------------------------------------------- - -Checks for tables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asciidoc/title_feature.test b/docs/_style/prism-master/tests/languages/asciidoc/title_feature.test deleted file mode 100644 index a9d40474..00000000 --- a/docs/_style/prism-master/tests/languages/asciidoc/title_feature.test +++ /dev/null @@ -1,80 +0,0 @@ -Foobar -====== - -Foobar ------- - -Foobar -~~~~~~ - -Foobar -^^^^^^ - -Foo -+++ - -= Foo bar baz = -== Foo bar baz -=== Foo bar baz === -==== Foo bar baz -===== Foo bar baz ===== - -.Foo bar baz - ----------------------------------------------------- - -[ - ["title", [ - "Foobar\r\n", - ["punctuation", "======"] - ]], - ["title", [ - "Foobar\r\n", - ["punctuation", "------"] - ]], - ["title", [ - "Foobar\r\n", - ["punctuation", "~~~~~~"] - ]], - ["title", [ - "Foobar\r\n", - ["punctuation", "^^^^^^"] - ]], - ["title", [ - "Foo\r\n", - ["punctuation", "+++"] - ]], - - ["title", [ - ["punctuation", "="], - " Foo bar baz ", - ["punctuation", "="] - ]], - ["title", [ - ["punctuation", "=="], - " Foo bar baz" - ]], - ["title", [ - ["punctuation", "==="], - " Foo bar baz ", - ["punctuation", "==="] - ]], - ["title", [ - ["punctuation", "===="], - " Foo bar baz" - ]], - ["title", [ - ["punctuation", "====="], - " Foo bar baz ", - ["punctuation", "====="] - ]], - - ["title", [ - ["punctuation", "."], - "Foo bar baz" - ]] -] - ----------------------------------------------------- - -Checks for titles. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/asm6502/comment_feature.test b/docs/_style/prism-master/tests/languages/asm6502/comment_feature.test deleted file mode 100644 index d4b86016..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -; -; foo bar baz - -------------------------- - -[ - ["comment", ";"], - ["comment", "; foo bar baz"] -] - -------------------------- - -Check for comments diff --git a/docs/_style/prism-master/tests/languages/asm6502/directive_feature.test b/docs/_style/prism-master/tests/languages/asm6502/directive_feature.test deleted file mode 100644 index 1cf42c81..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/directive_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -.segment CODE - -------------------------- - -[ - ["directive", ".segment"], - " CODE" -] - -------------------------- - -Check for directives diff --git a/docs/_style/prism-master/tests/languages/asm6502/number_feature.test b/docs/_style/prism-master/tests/languages/asm6502/number_feature.test deleted file mode 100644 index 55262f67..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/number_feature.test +++ /dev/null @@ -1,18 +0,0 @@ -LDA #127 -STA $8000 -LDX #%10001010 - -------------------------- - -[ - ["opcode", "LDA"], - ["decimalnumber", "#127"], - ["opcode", "STA"], - ["hexnumber", "$8000"], - ["opcode", "LDX"], - ["binarynumber", "#%10001010"] -] - -------------------------- - -Check for numbers diff --git a/docs/_style/prism-master/tests/languages/asm6502/opcode_feature.test b/docs/_style/prism-master/tests/languages/asm6502/opcode_feature.test deleted file mode 100644 index 75e7b428..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/opcode_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -LDA -BNE -inx -clc - -------------------------------------- - -[ - ["opcode", "LDA"], - ["opcode", "BNE"], - ["opcode", "inx"], - ["opcode", "clc"] -] - -------------------------------------- - -Check for opcodes diff --git a/docs/_style/prism-master/tests/languages/asm6502/register_feature.test b/docs/_style/prism-master/tests/languages/asm6502/register_feature.test deleted file mode 100644 index 91ec7c42..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/register_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -LDA $8000,x -ASL A - -------------------------- - -[ - ["opcode", "LDA"], - ["hexnumber", "$8000"], - ",", - ["register", "x"], - ["opcode", "ASL"], - ["register", "A"] -] - -------------------------- - -Check for registers diff --git a/docs/_style/prism-master/tests/languages/asm6502/string_feature.test b/docs/_style/prism-master/tests/languages/asm6502/string_feature.test deleted file mode 100644 index 02050744..00000000 --- a/docs/_style/prism-master/tests/languages/asm6502/string_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -.include "header.asm" - -------------------------- - -[ - ["directive", ".include"], - ["string", "\"header.asm\""] -] - -------------------------- - -Check for strings diff --git a/docs/_style/prism-master/tests/languages/aspnet/comment_feature.test b/docs/_style/prism-master/tests/languages/aspnet/comment_feature.test deleted file mode 100644 index da388e5f..00000000 --- a/docs/_style/prism-master/tests/languages/aspnet/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -<%----%> -<%--foo--%> -<%-- foo -bar --%> - ----------------------------------------------------- - -[ - ["asp comment", "<%----%>"], - ["asp comment", "<%--foo--%>"], - ["asp comment", "<%-- foo\r\nbar --%>"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/aspnet/page-directive_feature.test b/docs/_style/prism-master/tests/languages/aspnet/page-directive_feature.test deleted file mode 100644 index 3d79dae9..00000000 --- a/docs/_style/prism-master/tests/languages/aspnet/page-directive_feature.test +++ /dev/null @@ -1,92 +0,0 @@ -<%@Assembly foo="bar"%> -<% @Control foo="bar" %> -<%@ Implements%> -<%@Import%> -<%@Master%> -<%@MasterType%> -<%@OutputCache%> -<%@Page%> -<%@PreviousPageType%> -<%@Reference%> -<%@Register%> - ----------------------------------------------------- - -[ - ["page-directive tag", [ - ["page-directive tag", "<%@Assembly"], - ["attr-name", [ - "foo" - ]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - "bar", - ["punctuation", "\""] - ]], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<% @Control"], - ["attr-name", [ - "foo" - ]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - "bar", - ["punctuation", "\""] - ]], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@ Implements"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@Import"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@Master"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@MasterType"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@OutputCache"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@Page"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@PreviousPageType"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@Reference"], - ["page-directive tag", "%>"] - ]], - - ["page-directive tag", [ - ["page-directive tag", "<%@Register"], - ["page-directive tag", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for all page directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/boolean_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/boolean_feature.test deleted file mode 100644 index 6d97d4bf..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/builtin_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/builtin_feature.test deleted file mode 100644 index afbe8d33..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/builtin_feature.test +++ /dev/null @@ -1,147 +0,0 @@ -abs -acos -asc -asin -atan -ceil -chr -class -cos -dllcall -exp -fileexist -Fileopen -floor -il_add -il_create -il_destroy -instr -substr -isfunc -islabel -IsObject -ln -log -lv_add -lv_delete -lv_deletecol -lv_getcount -lv_getnext -lv_gettext -lv_insert -lv_insertcol -lv_modify -lv_modifycol -lv_setimagelist -mod -onmessage -numget -numput -registercallback -regexmatch -regexreplace -round -sin -tan -sqrt -strlen -sb_seticon -sb_setparts -sb_settext -strsplit -tv_add -tv_delete -tv_getchild -tv_getcount -tv_getnext -tv_get -tv_getparent -tv_getprev -tv_getselection -tv_gettext -tv_modify -varsetcapacity -winactive -winexist -__New -__Call -__Get -__Set - ----------------------------------------------------- - -[ - ["builtin", "abs"], - ["builtin", "acos"], - ["builtin", "asc"], - ["builtin", "asin"], - ["builtin", "atan"], - ["builtin", "ceil"], - ["builtin", "chr"], - ["builtin", "class"], - ["builtin", "cos"], - ["builtin", "dllcall"], - ["builtin", "exp"], - ["builtin", "fileexist"], - ["builtin", "Fileopen"], - ["builtin", "floor"], - ["builtin", "il_add"], - ["builtin", "il_create"], - ["builtin", "il_destroy"], - ["builtin", "instr"], - ["builtin", "substr"], - ["builtin", "isfunc"], - ["builtin", "islabel"], - ["builtin", "IsObject"], - ["builtin", "ln"], - ["builtin", "log"], - ["builtin", "lv_add"], - ["builtin", "lv_delete"], - ["builtin", "lv_deletecol"], - ["builtin", "lv_getcount"], - ["builtin", "lv_getnext"], - ["builtin", "lv_gettext"], - ["builtin", "lv_insert"], - ["builtin", "lv_insertcol"], - ["builtin", "lv_modify"], - ["builtin", "lv_modifycol"], - ["builtin", "lv_setimagelist"], - ["builtin", "mod"], - ["builtin", "onmessage"], - ["builtin", "numget"], - ["builtin", "numput"], - ["builtin", "registercallback"], - ["builtin", "regexmatch"], - ["builtin", "regexreplace"], - ["builtin", "round"], - ["builtin", "sin"], - ["builtin", "tan"], - ["builtin", "sqrt"], - ["builtin", "strlen"], - ["builtin", "sb_seticon"], - ["builtin", "sb_setparts"], - ["builtin", "sb_settext"], - ["builtin", "strsplit"], - ["builtin", "tv_add"], - ["builtin", "tv_delete"], - ["builtin", "tv_getchild"], - ["builtin", "tv_getcount"], - ["builtin", "tv_getnext"], - ["builtin", "tv_get"], - ["builtin", "tv_getparent"], - ["builtin", "tv_getprev"], - ["builtin", "tv_getselection"], - ["builtin", "tv_gettext"], - ["builtin", "tv_modify"], - ["builtin", "varsetcapacity"], - ["builtin", "winactive"], - ["builtin", "winexist"], - ["builtin", "__New"], - ["builtin", "__Call"], - ["builtin", "__Get"], - ["builtin", "__Set"] -] - ----------------------------------------------------- - -Checks for all builtins. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/comment_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/comment_feature.test deleted file mode 100644 index 5b00ce20..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -;foo -; bar - ----------------------------------------------------- - -[ - ["comment", ";foo"], - ["comment", "; bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/constant_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/constant_feature.test deleted file mode 100644 index ab116734..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/constant_feature.test +++ /dev/null @@ -1,275 +0,0 @@ -a_ahkpath -a_ahkversion -a_appdata -a_appdatacommon -a_autotrim -a_batchlines -a_caretx -a_carety -a_computername -a_controldelay -a_cursor -a_dd -a_ddd -a_dddd -a_defaultmousespeed -a_desktop -a_desktopcommon -a_detecthiddentext -a_detecthiddenwindows -a_endchar -a_eventinfo -a_exitreason -a_formatfloat -a_formatinteger -a_gui -a_guievent -a_guicontrol -a_guicontrolevent -a_guiheight -a_guiwidth -a_guix -a_guiy -a_hour -a_iconfile -a_iconhidden -a_iconnumber -a_icontip -a_index -a_ipaddress1 -a_ipaddress2 -a_ipaddress3 -a_ipaddress4 -a_isadmin -a_iscompiled -a_iscritical -a_ispaused -a_issuspended -a_isunicode -a_keydelay -a_language -a_lasterror -a_linefile -a_linenumber -a_loopfield -a_loopfileattrib -a_loopfiledir -a_loopfileext -a_loopfilefullpath -a_loopfilelongpath -a_loopfilename -a_loopfileshortname -a_loopfileshortpath -a_loopfilesize -a_loopfilesizekb -a_loopfilesizemb -a_loopfiletimeaccessed -a_loopfiletimecreated -a_loopfiletimemodified -a_loopreadline -a_loopregkey -a_loopregname -a_loopregsubkey -a_loopregtimemodified -a_loopregtype -a_mday -a_min -a_mm -a_mmm -a_mmmm -a_mon -a_mousedelay -a_msec -a_mydocuments -a_now -a_nowutc -a_numbatchlines -a_ostype -a_osversion -a_priorhotkey -programfiles -a_programfiles -a_programs -a_programscommon -a_screenheight -a_screenwidth -a_scriptdir -a_scriptfullpath -a_scriptname -a_sec -a_space -a_startmenu -a_startmenucommon -a_startup -a_startupcommon -a_stringcasesense -a_tab -a_temp -a_thisfunc -a_thishotkey -a_thislabel -a_thismenu -a_thismenuitem -a_thismenuitempos -a_tickcount -a_timeidle -a_timeidlephysical -a_timesincepriorhotkey -a_timesincethishotkey -a_titlematchmode -a_titlematchmodespeed -a_username -a_wday -a_windelay -a_windir -a_workingdir -a_yday -a_year -a_yweek -a_yyyy -clipboard -clipboardall -comspec -errorlevel - ----------------------------------------------------- - -[ - ["constant", "a_ahkpath"], - ["constant", "a_ahkversion"], - ["constant", "a_appdata"], - ["constant", "a_appdatacommon"], - ["constant", "a_autotrim"], - ["constant", "a_batchlines"], - ["constant", "a_caretx"], - ["constant", "a_carety"], - ["constant", "a_computername"], - ["constant", "a_controldelay"], - ["constant", "a_cursor"], - ["constant", "a_dd"], - ["constant", "a_ddd"], - ["constant", "a_dddd"], - ["constant", "a_defaultmousespeed"], - ["constant", "a_desktop"], - ["constant", "a_desktopcommon"], - ["constant", "a_detecthiddentext"], - ["constant", "a_detecthiddenwindows"], - ["constant", "a_endchar"], - ["constant", "a_eventinfo"], - ["constant", "a_exitreason"], - ["constant", "a_formatfloat"], - ["constant", "a_formatinteger"], - ["constant", "a_gui"], - ["constant", "a_guievent"], - ["constant", "a_guicontrol"], - ["constant", "a_guicontrolevent"], - ["constant", "a_guiheight"], - ["constant", "a_guiwidth"], - ["constant", "a_guix"], - ["constant", "a_guiy"], - ["constant", "a_hour"], - ["constant", "a_iconfile"], - ["constant", "a_iconhidden"], - ["constant", "a_iconnumber"], - ["constant", "a_icontip"], - ["constant", "a_index"], - ["constant", "a_ipaddress1"], - ["constant", "a_ipaddress2"], - ["constant", "a_ipaddress3"], - ["constant", "a_ipaddress4"], - ["constant", "a_isadmin"], - ["constant", "a_iscompiled"], - ["constant", "a_iscritical"], - ["constant", "a_ispaused"], - ["constant", "a_issuspended"], - ["constant", "a_isunicode"], - ["constant", "a_keydelay"], - ["constant", "a_language"], - ["constant", "a_lasterror"], - ["constant", "a_linefile"], - ["constant", "a_linenumber"], - ["constant", "a_loopfield"], - ["constant", "a_loopfileattrib"], - ["constant", "a_loopfiledir"], - ["constant", "a_loopfileext"], - ["constant", "a_loopfilefullpath"], - ["constant", "a_loopfilelongpath"], - ["constant", "a_loopfilename"], - ["constant", "a_loopfileshortname"], - ["constant", "a_loopfileshortpath"], - ["constant", "a_loopfilesize"], - ["constant", "a_loopfilesizekb"], - ["constant", "a_loopfilesizemb"], - ["constant", "a_loopfiletimeaccessed"], - ["constant", "a_loopfiletimecreated"], - ["constant", "a_loopfiletimemodified"], - ["constant", "a_loopreadline"], - ["constant", "a_loopregkey"], - ["constant", "a_loopregname"], - ["constant", "a_loopregsubkey"], - ["constant", "a_loopregtimemodified"], - ["constant", "a_loopregtype"], - ["constant", "a_mday"], - ["constant", "a_min"], - ["constant", "a_mm"], - ["constant", "a_mmm"], - ["constant", "a_mmmm"], - ["constant", "a_mon"], - ["constant", "a_mousedelay"], - ["constant", "a_msec"], - ["constant", "a_mydocuments"], - ["constant", "a_now"], - ["constant", "a_nowutc"], - ["constant", "a_numbatchlines"], - ["constant", "a_ostype"], - ["constant", "a_osversion"], - ["constant", "a_priorhotkey"], - ["constant", "programfiles"], - ["constant", "a_programfiles"], - ["constant", "a_programs"], - ["constant", "a_programscommon"], - ["constant", "a_screenheight"], - ["constant", "a_screenwidth"], - ["constant", "a_scriptdir"], - ["constant", "a_scriptfullpath"], - ["constant", "a_scriptname"], - ["constant", "a_sec"], - ["constant", "a_space"], - ["constant", "a_startmenu"], - ["constant", "a_startmenucommon"], - ["constant", "a_startup"], - ["constant", "a_startupcommon"], - ["constant", "a_stringcasesense"], - ["constant", "a_tab"], - ["constant", "a_temp"], - ["constant", "a_thisfunc"], - ["constant", "a_thishotkey"], - ["constant", "a_thislabel"], - ["constant", "a_thismenu"], - ["constant", "a_thismenuitem"], - ["constant", "a_thismenuitempos"], - ["constant", "a_tickcount"], - ["constant", "a_timeidle"], - ["constant", "a_timeidlephysical"], - ["constant", "a_timesincepriorhotkey"], - ["constant", "a_timesincethishotkey"], - ["constant", "a_titlematchmode"], - ["constant", "a_titlematchmodespeed"], - ["constant", "a_username"], - ["constant", "a_wday"], - ["constant", "a_windelay"], - ["constant", "a_windir"], - ["constant", "a_workingdir"], - ["constant", "a_yday"], - ["constant", "a_year"], - ["constant", "a_yweek"], - ["constant", "a_yyyy"], - ["constant", "clipboard"], - ["constant", "clipboardall"], - ["constant", "comspec"], - ["constant", "errorlevel"] -] - ----------------------------------------------------- - -Checks for all constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/function_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/function_feature.test deleted file mode 100644 index f38a186f..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/function_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo( -foo_bar( - ----------------------------------------------------- - -[ - ["function", "foo"], - ["punctuation", "("], - ["function", "foo_bar"], - ["punctuation", "("] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/important_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/important_feature.test deleted file mode 100644 index aa9cbeb5..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/important_feature.test +++ /dev/null @@ -1,67 +0,0 @@ -#AllowSameLineComments -#ClipboardTimeout -#CommentFlag -#ErrorStdOut -#EscapeChar -#HotkeyInterval -#HotkeyModifierTimeout -#Hotstring -#IfWinActive -#IfWinExist -#IfWinNotActive -#IfWinNotExist -#Include -#IncludeAgain -#InstallKeybdHook -#InstallMouseHook -#KeyHistory -#LTrim -#MaxHotkeysPerInterval -#MaxMem -#MaxThreads -#MaxThreadsBuffer -#MaxThreadsPerHotkey -#NoEnv -#NoTrayIcon -#Persistent -#SingleInstance -#UseHook -#WinActivateForce - ----------------------------------------------------- - -[ - ["important", "#AllowSameLineComments"], - ["important", "#ClipboardTimeout"], - ["important", "#CommentFlag"], - ["important", "#ErrorStdOut"], - ["important", "#EscapeChar"], - ["important", "#HotkeyInterval"], - ["important", "#HotkeyModifierTimeout"], - ["important", "#Hotstring"], - ["important", "#IfWinActive"], - ["important", "#IfWinExist"], - ["important", "#IfWinNotActive"], - ["important", "#IfWinNotExist"], - ["important", "#Include"], - ["important", "#IncludeAgain"], - ["important", "#InstallKeybdHook"], - ["important", "#InstallMouseHook"], - ["important", "#KeyHistory"], - ["important", "#LTrim"], - ["important", "#MaxHotkeysPerInterval"], - ["important", "#MaxMem"], - ["important", "#MaxThreads"], - ["important", "#MaxThreadsBuffer"], - ["important", "#MaxThreadsPerHotkey"], - ["important", "#NoEnv"], - ["important", "#NoTrayIcon"], - ["important", "#Persistent"], - ["important", "#SingleInstance"], - ["important", "#UseHook"], - ["important", "#WinActivateForce"] -] - ----------------------------------------------------- - -Checks for all important keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/keyword_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/keyword_feature.test deleted file mode 100644 index 91671355..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/keyword_feature.test +++ /dev/null @@ -1,537 +0,0 @@ -Abort -AboveNormal -Add -ahk_class -ahk_group -ahk_id -ahk_pid -All -Alnum -Alpha -AltSubmit -AltTab -AltTabAndMenu -AltTabMenu -AltTabMenuDismiss -AlwaysOnTop -AutoSize -Background -BackgroundTrans -BelowNormal -between -BitAnd -BitNot -BitOr -BitShiftLeft -BitShiftRight -BitXOr -Bold -Border -Button -ByRef -Checkbox -Checked -CheckedGray -Choose -ChooseString -Close -Color -ComboBox -Contains -ControlList -Count -Date -DateTime -Days -DDL -Default -DeleteAll -Delimiter -Deref -Destroy -Digit -Disable -Disabled -DropDownList -Edit -Eject -Else -Enable -Enabled -Error -Exist -Expand -ExStyle -FileSystem -First -Flash -Float -FloatFast -Focus -Font -for -global -Grid -Group -GroupBox -GuiClose -GuiContextMenu -GuiDropFiles -GuiEscape -GuiSize -Hdr -Hidden -Hide -High -HKCC -HKCR -HKCU -HKEY_CLASSES_ROOT -HKEY_CURRENT_CONFIG -HKEY_CURRENT_USER -HKEY_LOCAL_MACHINE -HKEY_USERS -HKLM -HKU -Hours -HScroll -Icon -IconSmall -ID -IDLast -If -IfEqual -IfExist -IfGreater -IfGreaterOrEqual -IfInString -IfLess -IfLessOrEqual -IfMsgBox -IfNotEqual -IfNotExist -IfNotInString -IfWinActive -IfWinExist -IfWinNotActive -IfWinNotExist -Ignore -ImageList -in -Integer -IntegerFast -Interrupt -is -italic -Join -Label -LastFound -LastFoundExist -Limit -Lines -List -ListBox -ListView -local -Lock -Logoff -Low -Lower -Lowercase -MainWindow -Margin -Maximize -MaximizeBox -MaxSize -Minimize -MinimizeBox -MinMax -MinSize -Minutes -MonthCal -Mouse -Move -Multi -NA -No -NoActivate -NoDefault -NoHide -NoIcon -NoMainWindow -norm -Normal -NoSort -NoSortHdr -NoStandard -Not -NoTab -NoTimers -Number -Off -Ok -On -OwnDialogs -Owner -Parse -Password -Picture -Pixel -Pos -Pow -Priority -ProcessName -Radio -Range -Read -ReadOnly -Realtime -Redraw -REG_BINARY -REG_DWORD -REG_EXPAND_SZ -REG_MULTI_SZ -REG_SZ -Region -Relative -Rename -Report -Resize -Restore -Retry -RGB -Screen -Seconds -Section -Serial -SetLabel -ShiftAltTab -Show -Single -Slider -SortDesc -Standard -static -Status -StatusBar -StatusCD -strike -Style -Submit -SysMenu -Tab2 -TabStop -Text -Theme -Tile -ToggleCheck -ToggleEnable -ToolWindow -Top -Topmost -TransColor -Transparent -Tray -TreeView -TryAgain -Type -UnCheck -underline -Unicode -Unlock -UpDown -Upper -Uppercase -UseErrorLevel -Vis -VisFirst -Visible -VScroll -Wait -WaitClose -WantCtrlA -WantF2 -WantReturn -While -Wrap -Xdigit -xm -xp -xs -Yes -ym -yp -ys - ----------------------------------------------------- - -[ - ["keyword", "Abort"], - ["keyword", "AboveNormal"], - ["keyword", "Add"], - ["keyword", "ahk_class"], - ["keyword", "ahk_group"], - ["keyword", "ahk_id"], - ["keyword", "ahk_pid"], - ["keyword", "All"], - ["keyword", "Alnum"], - ["keyword", "Alpha"], - ["keyword", "AltSubmit"], - ["keyword", "AltTab"], - ["keyword", "AltTabAndMenu"], - ["keyword", "AltTabMenu"], - ["keyword", "AltTabMenuDismiss"], - ["keyword", "AlwaysOnTop"], - ["keyword", "AutoSize"], - ["keyword", "Background"], - ["keyword", "BackgroundTrans"], - ["keyword", "BelowNormal"], - ["keyword", "between"], - ["keyword", "BitAnd"], - ["keyword", "BitNot"], - ["keyword", "BitOr"], - ["keyword", "BitShiftLeft"], - ["keyword", "BitShiftRight"], - ["keyword", "BitXOr"], - ["keyword", "Bold"], - ["keyword", "Border"], - ["keyword", "Button"], - ["keyword", "ByRef"], - ["keyword", "Checkbox"], - ["keyword", "Checked"], - ["keyword", "CheckedGray"], - ["keyword", "Choose"], - ["keyword", "ChooseString"], - ["keyword", "Close"], - ["keyword", "Color"], - ["keyword", "ComboBox"], - ["keyword", "Contains"], - ["keyword", "ControlList"], - ["keyword", "Count"], - ["keyword", "Date"], - ["keyword", "DateTime"], - ["keyword", "Days"], - ["keyword", "DDL"], - ["keyword", "Default"], - ["keyword", "DeleteAll"], - ["keyword", "Delimiter"], - ["keyword", "Deref"], - ["keyword", "Destroy"], - ["keyword", "Digit"], - ["keyword", "Disable"], - ["keyword", "Disabled"], - ["keyword", "DropDownList"], - ["keyword", "Edit"], - ["keyword", "Eject"], - ["keyword", "Else"], - ["keyword", "Enable"], - ["keyword", "Enabled"], - ["keyword", "Error"], - ["keyword", "Exist"], - ["keyword", "Expand"], - ["keyword", "ExStyle"], - ["keyword", "FileSystem"], - ["keyword", "First"], - ["keyword", "Flash"], - ["keyword", "Float"], - ["keyword", "FloatFast"], - ["keyword", "Focus"], - ["keyword", "Font"], - ["keyword", "for"], - ["keyword", "global"], - ["keyword", "Grid"], - ["keyword", "Group"], - ["keyword", "GroupBox"], - ["keyword", "GuiClose"], - ["keyword", "GuiContextMenu"], - ["keyword", "GuiDropFiles"], - ["keyword", "GuiEscape"], - ["keyword", "GuiSize"], - ["keyword", "Hdr"], - ["keyword", "Hidden"], - ["keyword", "Hide"], - ["keyword", "High"], - ["keyword", "HKCC"], - ["keyword", "HKCR"], - ["keyword", "HKCU"], - ["keyword", "HKEY_CLASSES_ROOT"], - ["keyword", "HKEY_CURRENT_CONFIG"], - ["keyword", "HKEY_CURRENT_USER"], - ["keyword", "HKEY_LOCAL_MACHINE"], - ["keyword", "HKEY_USERS"], - ["keyword", "HKLM"], - ["keyword", "HKU"], - ["keyword", "Hours"], - ["keyword", "HScroll"], - ["keyword", "Icon"], - ["keyword", "IconSmall"], - ["keyword", "ID"], - ["keyword", "IDLast"], - ["keyword", "If"], - ["keyword", "IfEqual"], - ["keyword", "IfExist"], - ["keyword", "IfGreater"], - ["keyword", "IfGreaterOrEqual"], - ["keyword", "IfInString"], - ["keyword", "IfLess"], - ["keyword", "IfLessOrEqual"], - ["keyword", "IfMsgBox"], - ["keyword", "IfNotEqual"], - ["keyword", "IfNotExist"], - ["keyword", "IfNotInString"], - ["keyword", "IfWinActive"], - ["keyword", "IfWinExist"], - ["keyword", "IfWinNotActive"], - ["keyword", "IfWinNotExist"], - ["keyword", "Ignore"], - ["keyword", "ImageList"], - ["keyword", "in"], - ["keyword", "Integer"], - ["keyword", "IntegerFast"], - ["keyword", "Interrupt"], - ["keyword", "is"], - ["keyword", "italic"], - ["keyword", "Join"], - ["keyword", "Label"], - ["keyword", "LastFound"], - ["keyword", "LastFoundExist"], - ["keyword", "Limit"], - ["keyword", "Lines"], - ["keyword", "List"], - ["keyword", "ListBox"], - ["keyword", "ListView"], - ["keyword", "local"], - ["keyword", "Lock"], - ["keyword", "Logoff"], - ["keyword", "Low"], - ["keyword", "Lower"], - ["keyword", "Lowercase"], - ["keyword", "MainWindow"], - ["keyword", "Margin"], - ["keyword", "Maximize"], - ["keyword", "MaximizeBox"], - ["keyword", "MaxSize"], - ["keyword", "Minimize"], - ["keyword", "MinimizeBox"], - ["keyword", "MinMax"], - ["keyword", "MinSize"], - ["keyword", "Minutes"], - ["keyword", "MonthCal"], - ["keyword", "Mouse"], - ["keyword", "Move"], - ["keyword", "Multi"], - ["keyword", "NA"], - ["keyword", "No"], - ["keyword", "NoActivate"], - ["keyword", "NoDefault"], - ["keyword", "NoHide"], - ["keyword", "NoIcon"], - ["keyword", "NoMainWindow"], - ["keyword", "norm"], - ["keyword", "Normal"], - ["keyword", "NoSort"], - ["keyword", "NoSortHdr"], - ["keyword", "NoStandard"], - ["keyword", "Not"], - ["keyword", "NoTab"], - ["keyword", "NoTimers"], - ["keyword", "Number"], - ["keyword", "Off"], - ["keyword", "Ok"], - ["keyword", "On"], - ["keyword", "OwnDialogs"], - ["keyword", "Owner"], - ["keyword", "Parse"], - ["keyword", "Password"], - ["keyword", "Picture"], - ["keyword", "Pixel"], - ["keyword", "Pos"], - ["keyword", "Pow"], - ["keyword", "Priority"], - ["keyword", "ProcessName"], - ["keyword", "Radio"], - ["keyword", "Range"], - ["keyword", "Read"], - ["keyword", "ReadOnly"], - ["keyword", "Realtime"], - ["keyword", "Redraw"], - ["keyword", "REG_BINARY"], - ["keyword", "REG_DWORD"], - ["keyword", "REG_EXPAND_SZ"], - ["keyword", "REG_MULTI_SZ"], - ["keyword", "REG_SZ"], - ["keyword", "Region"], - ["keyword", "Relative"], - ["keyword", "Rename"], - ["keyword", "Report"], - ["keyword", "Resize"], - ["keyword", "Restore"], - ["keyword", "Retry"], - ["keyword", "RGB"], - ["keyword", "Screen"], - ["keyword", "Seconds"], - ["keyword", "Section"], - ["keyword", "Serial"], - ["keyword", "SetLabel"], - ["keyword", "ShiftAltTab"], - ["keyword", "Show"], - ["keyword", "Single"], - ["keyword", "Slider"], - ["keyword", "SortDesc"], - ["keyword", "Standard"], - ["keyword", "static"], - ["keyword", "Status"], - ["keyword", "StatusBar"], - ["keyword", "StatusCD"], - ["keyword", "strike"], - ["keyword", "Style"], - ["keyword", "Submit"], - ["keyword", "SysMenu"], - ["keyword", "Tab2"], - ["keyword", "TabStop"], - ["keyword", "Text"], - ["keyword", "Theme"], - ["keyword", "Tile"], - ["keyword", "ToggleCheck"], - ["keyword", "ToggleEnable"], - ["keyword", "ToolWindow"], - ["keyword", "Top"], - ["keyword", "Topmost"], - ["keyword", "TransColor"], - ["keyword", "Transparent"], - ["keyword", "Tray"], - ["keyword", "TreeView"], - ["keyword", "TryAgain"], - ["keyword", "Type"], - ["keyword", "UnCheck"], - ["keyword", "underline"], - ["keyword", "Unicode"], - ["keyword", "Unlock"], - ["keyword", "UpDown"], - ["keyword", "Upper"], - ["keyword", "Uppercase"], - ["keyword", "UseErrorLevel"], - ["keyword", "Vis"], - ["keyword", "VisFirst"], - ["keyword", "Visible"], - ["keyword", "VScroll"], - ["keyword", "Wait"], - ["keyword", "WaitClose"], - ["keyword", "WantCtrlA"], - ["keyword", "WantF2"], - ["keyword", "WantReturn"], - ["keyword", "While"], - ["keyword", "Wrap"], - ["keyword", "Xdigit"], - ["keyword", "xm"], - ["keyword", "xp"], - ["keyword", "xs"], - ["keyword", "Yes"], - ["keyword", "ym"], - ["keyword", "yp"], - ["keyword", "ys"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/number_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/number_feature.test deleted file mode 100644 index 35a493ae..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/number_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -42 -3.14159 -3.2e10 -2.9E-7 -0xbabe -0xBABE - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "3.2e10"], - ["number", "2.9E-7"], - ["number", "0xbabe"], - ["number", "0xBABE"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/operator_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/operator_feature.test deleted file mode 100644 index ec9e23ed..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/operator_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -+ - * / -= ? & | -< > -++ -- ** // -! ~ ^ . -<< >> <= >= -~= == <> != -NOT AND && OR || -:= += -= *= -/= //= .= -|= &= ^= ->>= <<= - ----------------------------------------------------- - -[ - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], - ["operator", "="], ["operator", "?"], ["operator", "&"], ["operator", "|"], - ["operator", "<"], ["operator", ">"], - ["operator", "++"], ["operator", "--"], ["operator", "**"], ["operator", "//"], - ["operator", "!"], ["operator", "~"], ["operator", "^"], ["operator", "."], - ["operator", "<<"], ["operator", ">>"], ["operator", "<="], ["operator", ">="], - ["operator", "~="], ["operator", "=="], ["operator", "<>"], ["operator", "!="], - ["operator", "NOT"], ["operator", "AND"], ["operator", "&&"], ["operator", "OR"], ["operator", "||"], - ["operator", ":="], ["operator", "+="], ["operator", "-="], ["operator", "*="], - ["operator", "/="], ["operator", "//="], ["operator", ".="], - ["operator", "|="], ["operator", "&="], ["operator", "^="], - ["operator", ">>="], ["operator", "<<="] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/selector_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/selector_feature.test deleted file mode 100644 index 29210c26..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/selector_feature.test +++ /dev/null @@ -1,381 +0,0 @@ -AutoTrim -BlockInput -Break -Click -ClipWait -Continue -Control -ControlClick -ControlFocus -ControlGet -ControlGetFocus -ControlGetPos -ControlGetText -ControlMove -ControlSend -ControlSendRaw -ControlSetText -CoordMode -Critical -DetectHiddenText -DetectHiddenWindows -Drive -DriveGet -DriveSpaceFree -EnvAdd -EnvDiv -EnvGet -EnvMult -EnvSet -EnvSub -EnvUpdate -Exit -ExitApp -FileAppend -FileCopy -FileCopyDir -FileCreateDir -FileCreateShortcut -FileDelete -FileEncoding -FileGetAttrib -FileGetShortcut -FileGetSize -FileGetTime -FileGetVersion -FileInstall -FileMove -FileMoveDir -FileRead -FileReadLine -FileRecycle -FileRecycleEmpty -FileRemoveDir -FileSelectFile -FileSelectFolder -FileSetAttrib -FileSetTime -FormatTime -GetKeyState -Gosub -Goto -GroupActivate -GroupAdd -GroupClose -GroupDeactivate -Gui -GuiControl -GuiControlGet -Hotkey -ImageSearch -IniDelete -IniRead -IniWrite -Input -InputBox -KeyWait -ListHotkeys -ListLines -ListVars -Loop -Menu -MouseClick -MouseClickDrag -MouseGetPos -MouseMove -MsgBox -OnExit -OutputDebug -Pause -PixelGetColor -PixelSearch -PostMessage -Process -Progress -Random -RegDelete -RegRead -RegWrite -Reload -Repeat -Return -Run -RunAs -RunWait -Send -SendEvent -SendInput -SendMessage -SendMode -SendPlay -SendRaw -SetBatchLines -SetCapslockState -SetControlDelay -SetDefaultMouseSpeed -SetEnv -SetFormat -SetKeyDelay -SetMouseDelay -SetNumlockState -SetScrollLockState -SetStoreCapslockMode -SetTimer -SetTitleMatchMode -SetWinDelay -SetWorkingDir -Shutdown -Sleep -Sort -SoundBeep -SoundGet -SoundGetWaveVolume -SoundPlay -SoundSet -SoundSetWaveVolume -SplashImage -SplashTextOff -SplashTextOn -SplitPath -StatusBarGetText -StatusBarWait -StringCaseSense -StringGetPos -StringLeft -StringLen -StringLower -StringMid -StringReplace -StringRight -StringSplit -StringTrimLeft -StringTrimRight -StringUpper -Suspend -SysGet -Thread -ToolTip -Transform -TrayTip -URLDownloadToFile -WinActivate -WinActivateBottom -WinClose -WinGet -WinGetActiveStats -WinGetActiveTitle -WinGetClass -WinGetPos -WinGetText -WinGetTitle -WinHide -WinKill -WinMaximize -WinMenuSelectItem -WinMinimize -WinMinimizeAll -WinMinimizeAllUndo -WinMove -WinRestore -WinSet -WinSetTitle -WinShow -WinWait -WinWaitActive -WinWaitClose -WinWaitNotActive - ----------------------------------------------------- - -[ - ["selector", "AutoTrim"], - ["selector", "BlockInput"], - ["selector", "Break"], - ["selector", "Click"], - ["selector", "ClipWait"], - ["selector", "Continue"], - ["selector", "Control"], - ["selector", "ControlClick"], - ["selector", "ControlFocus"], - ["selector", "ControlGet"], - ["selector", "ControlGetFocus"], - ["selector", "ControlGetPos"], - ["selector", "ControlGetText"], - ["selector", "ControlMove"], - ["selector", "ControlSend"], - ["selector", "ControlSendRaw"], - ["selector", "ControlSetText"], - ["selector", "CoordMode"], - ["selector", "Critical"], - ["selector", "DetectHiddenText"], - ["selector", "DetectHiddenWindows"], - ["selector", "Drive"], - ["selector", "DriveGet"], - ["selector", "DriveSpaceFree"], - ["selector", "EnvAdd"], - ["selector", "EnvDiv"], - ["selector", "EnvGet"], - ["selector", "EnvMult"], - ["selector", "EnvSet"], - ["selector", "EnvSub"], - ["selector", "EnvUpdate"], - ["selector", "Exit"], - ["selector", "ExitApp"], - ["selector", "FileAppend"], - ["selector", "FileCopy"], - ["selector", "FileCopyDir"], - ["selector", "FileCreateDir"], - ["selector", "FileCreateShortcut"], - ["selector", "FileDelete"], - ["selector", "FileEncoding"], - ["selector", "FileGetAttrib"], - ["selector", "FileGetShortcut"], - ["selector", "FileGetSize"], - ["selector", "FileGetTime"], - ["selector", "FileGetVersion"], - ["selector", "FileInstall"], - ["selector", "FileMove"], - ["selector", "FileMoveDir"], - ["selector", "FileRead"], - ["selector", "FileReadLine"], - ["selector", "FileRecycle"], - ["selector", "FileRecycleEmpty"], - ["selector", "FileRemoveDir"], - ["selector", "FileSelectFile"], - ["selector", "FileSelectFolder"], - ["selector", "FileSetAttrib"], - ["selector", "FileSetTime"], - ["selector", "FormatTime"], - ["selector", "GetKeyState"], - ["selector", "Gosub"], - ["selector", "Goto"], - ["selector", "GroupActivate"], - ["selector", "GroupAdd"], - ["selector", "GroupClose"], - ["selector", "GroupDeactivate"], - ["selector", "Gui"], - ["selector", "GuiControl"], - ["selector", "GuiControlGet"], - ["selector", "Hotkey"], - ["selector", "ImageSearch"], - ["selector", "IniDelete"], - ["selector", "IniRead"], - ["selector", "IniWrite"], - ["selector", "Input"], - ["selector", "InputBox"], - ["selector", "KeyWait"], - ["selector", "ListHotkeys"], - ["selector", "ListLines"], - ["selector", "ListVars"], - ["selector", "Loop"], - ["selector", "Menu"], - ["selector", "MouseClick"], - ["selector", "MouseClickDrag"], - ["selector", "MouseGetPos"], - ["selector", "MouseMove"], - ["selector", "MsgBox"], - ["selector", "OnExit"], - ["selector", "OutputDebug"], - ["selector", "Pause"], - ["selector", "PixelGetColor"], - ["selector", "PixelSearch"], - ["selector", "PostMessage"], - ["selector", "Process"], - ["selector", "Progress"], - ["selector", "Random"], - ["selector", "RegDelete"], - ["selector", "RegRead"], - ["selector", "RegWrite"], - ["selector", "Reload"], - ["selector", "Repeat"], - ["selector", "Return"], - ["selector", "Run"], - ["selector", "RunAs"], - ["selector", "RunWait"], - ["selector", "Send"], - ["selector", "SendEvent"], - ["selector", "SendInput"], - ["selector", "SendMessage"], - ["selector", "SendMode"], - ["selector", "SendPlay"], - ["selector", "SendRaw"], - ["selector", "SetBatchLines"], - ["selector", "SetCapslockState"], - ["selector", "SetControlDelay"], - ["selector", "SetDefaultMouseSpeed"], - ["selector", "SetEnv"], - ["selector", "SetFormat"], - ["selector", "SetKeyDelay"], - ["selector", "SetMouseDelay"], - ["selector", "SetNumlockState"], - ["selector", "SetScrollLockState"], - ["selector", "SetStoreCapslockMode"], - ["selector", "SetTimer"], - ["selector", "SetTitleMatchMode"], - ["selector", "SetWinDelay"], - ["selector", "SetWorkingDir"], - ["selector", "Shutdown"], - ["selector", "Sleep"], - ["selector", "Sort"], - ["selector", "SoundBeep"], - ["selector", "SoundGet"], - ["selector", "SoundGetWaveVolume"], - ["selector", "SoundPlay"], - ["selector", "SoundSet"], - ["selector", "SoundSetWaveVolume"], - ["selector", "SplashImage"], - ["selector", "SplashTextOff"], - ["selector", "SplashTextOn"], - ["selector", "SplitPath"], - ["selector", "StatusBarGetText"], - ["selector", "StatusBarWait"], - ["selector", "StringCaseSense"], - ["selector", "StringGetPos"], - ["selector", "StringLeft"], - ["selector", "StringLen"], - ["selector", "StringLower"], - ["selector", "StringMid"], - ["selector", "StringReplace"], - ["selector", "StringRight"], - ["selector", "StringSplit"], - ["selector", "StringTrimLeft"], - ["selector", "StringTrimRight"], - ["selector", "StringUpper"], - ["selector", "Suspend"], - ["selector", "SysGet"], - ["selector", "Thread"], - ["selector", "ToolTip"], - ["selector", "Transform"], - ["selector", "TrayTip"], - ["selector", "URLDownloadToFile"], - ["selector", "WinActivate"], - ["selector", "WinActivateBottom"], - ["selector", "WinClose"], - ["selector", "WinGet"], - ["selector", "WinGetActiveStats"], - ["selector", "WinGetActiveTitle"], - ["selector", "WinGetClass"], - ["selector", "WinGetPos"], - ["selector", "WinGetText"], - ["selector", "WinGetTitle"], - ["selector", "WinHide"], - ["selector", "WinKill"], - ["selector", "WinMaximize"], - ["selector", "WinMenuSelectItem"], - ["selector", "WinMinimize"], - ["selector", "WinMinimizeAll"], - ["selector", "WinMinimizeAllUndo"], - ["selector", "WinMove"], - ["selector", "WinRestore"], - ["selector", "WinSet"], - ["selector", "WinSetTitle"], - ["selector", "WinShow"], - ["selector", "WinWait"], - ["selector", "WinWaitActive"], - ["selector", "WinWaitClose"], - ["selector", "WinWaitNotActive"] -] - ----------------------------------------------------- - -Checks for all selectors. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/string_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/string_feature.test deleted file mode 100644 index 76168fd2..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/string_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -"" -"foo" -"foo""bar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo\""], - ["string", "\"foo\"\"bar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/symbol_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/symbol_feature.test deleted file mode 100644 index 2c477774..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/symbol_feature.test +++ /dev/null @@ -1,347 +0,0 @@ -alt -altdown -altup -appskey -backspace -browser_back -browser_favorites -browser_forward -browser_home -browser_refresh -browser_search -browser_stop -bs -capslock -ctrl -ctrlbreak -ctrldown -ctrlup -del -delete -down -end -enter -esc -escape -f1 -f10 -f11 -f12 -f13 -f14 -f15 -f16 -f17 -f18 -f19 -f2 -f20 -f21 -f22 -f23 -f24 -f3 -f4 -f5 -f6 -f7 -f8 -f9 -home -ins -insert -joy1 -joy10 -joy11 -joy12 -joy13 -joy14 -joy15 -joy16 -joy17 -joy18 -joy19 -joy2 -joy20 -joy21 -joy22 -joy23 -joy24 -joy25 -joy26 -joy27 -joy28 -joy29 -joy3 -joy30 -joy31 -joy32 -joy4 -joy5 -joy6 -joy7 -joy8 -joy9 -joyaxes -joybuttons -joyinfo -joyname -joypov -joyr -joyu -joyv -joyx -joyy -joyz -lalt -launch_app1 -launch_app2 -launch_mail -launch_media -lbutton -lcontrol -lctrl -left -lshift -lwin -lwindown -lwinup -mbutton -media_next -media_play_pause -media_prev -media_stop -numlock -numpad0 -numpad1 -numpad2 -numpad3 -numpad4 -numpad5 -numpad6 -numpad7 -numpad8 -numpad9 -numpadadd -numpadclear -numpaddel -numpaddiv -numpaddot -numpaddown -numpadend -numpadenter -numpadhome -numpadins -numpadleft -numpadmult -numpadpgdn -numpadpgup -numpadright -numpadsub -numpadup -pgdn -pgup -printscreen -ralt -rbutton -rcontrol -rctrl -right -rshift -rwin -rwindown -rwinup -scrolllock -shift -shiftdown -shiftup -space -tab -up -volume_down -volume_mute -volume_up -wheeldown -wheelleft -wheelright -wheelup -xbutton1 -xbutton2 - ----------------------------------------------------- - -[ - ["symbol", "alt"], - ["symbol", "altdown"], - ["symbol", "altup"], - ["symbol", "appskey"], - ["symbol", "backspace"], - ["symbol", "browser_back"], - ["symbol", "browser_favorites"], - ["symbol", "browser_forward"], - ["symbol", "browser_home"], - ["symbol", "browser_refresh"], - ["symbol", "browser_search"], - ["symbol", "browser_stop"], - ["symbol", "bs"], - ["symbol", "capslock"], - ["symbol", "ctrl"], - ["symbol", "ctrlbreak"], - ["symbol", "ctrldown"], - ["symbol", "ctrlup"], - ["symbol", "del"], - ["symbol", "delete"], - ["symbol", "down"], - ["symbol", "end"], - ["symbol", "enter"], - ["symbol", "esc"], - ["symbol", "escape"], - ["symbol", "f1"], - ["symbol", "f10"], - ["symbol", "f11"], - ["symbol", "f12"], - ["symbol", "f13"], - ["symbol", "f14"], - ["symbol", "f15"], - ["symbol", "f16"], - ["symbol", "f17"], - ["symbol", "f18"], - ["symbol", "f19"], - ["symbol", "f2"], - ["symbol", "f20"], - ["symbol", "f21"], - ["symbol", "f22"], - ["symbol", "f23"], - ["symbol", "f24"], - ["symbol", "f3"], - ["symbol", "f4"], - ["symbol", "f5"], - ["symbol", "f6"], - ["symbol", "f7"], - ["symbol", "f8"], - ["symbol", "f9"], - ["symbol", "home"], - ["symbol", "ins"], - ["symbol", "insert"], - ["symbol", "joy1"], - ["symbol", "joy10"], - ["symbol", "joy11"], - ["symbol", "joy12"], - ["symbol", "joy13"], - ["symbol", "joy14"], - ["symbol", "joy15"], - ["symbol", "joy16"], - ["symbol", "joy17"], - ["symbol", "joy18"], - ["symbol", "joy19"], - ["symbol", "joy2"], - ["symbol", "joy20"], - ["symbol", "joy21"], - ["symbol", "joy22"], - ["symbol", "joy23"], - ["symbol", "joy24"], - ["symbol", "joy25"], - ["symbol", "joy26"], - ["symbol", "joy27"], - ["symbol", "joy28"], - ["symbol", "joy29"], - ["symbol", "joy3"], - ["symbol", "joy30"], - ["symbol", "joy31"], - ["symbol", "joy32"], - ["symbol", "joy4"], - ["symbol", "joy5"], - ["symbol", "joy6"], - ["symbol", "joy7"], - ["symbol", "joy8"], - ["symbol", "joy9"], - ["symbol", "joyaxes"], - ["symbol", "joybuttons"], - ["symbol", "joyinfo"], - ["symbol", "joyname"], - ["symbol", "joypov"], - ["symbol", "joyr"], - ["symbol", "joyu"], - ["symbol", "joyv"], - ["symbol", "joyx"], - ["symbol", "joyy"], - ["symbol", "joyz"], - ["symbol", "lalt"], - ["symbol", "launch_app1"], - ["symbol", "launch_app2"], - ["symbol", "launch_mail"], - ["symbol", "launch_media"], - ["symbol", "lbutton"], - ["symbol", "lcontrol"], - ["symbol", "lctrl"], - ["symbol", "left"], - ["symbol", "lshift"], - ["symbol", "lwin"], - ["symbol", "lwindown"], - ["symbol", "lwinup"], - ["symbol", "mbutton"], - ["symbol", "media_next"], - ["symbol", "media_play_pause"], - ["symbol", "media_prev"], - ["symbol", "media_stop"], - ["symbol", "numlock"], - ["symbol", "numpad0"], - ["symbol", "numpad1"], - ["symbol", "numpad2"], - ["symbol", "numpad3"], - ["symbol", "numpad4"], - ["symbol", "numpad5"], - ["symbol", "numpad6"], - ["symbol", "numpad7"], - ["symbol", "numpad8"], - ["symbol", "numpad9"], - ["symbol", "numpadadd"], - ["symbol", "numpadclear"], - ["symbol", "numpaddel"], - ["symbol", "numpaddiv"], - ["symbol", "numpaddot"], - ["symbol", "numpaddown"], - ["symbol", "numpadend"], - ["symbol", "numpadenter"], - ["symbol", "numpadhome"], - ["symbol", "numpadins"], - ["symbol", "numpadleft"], - ["symbol", "numpadmult"], - ["symbol", "numpadpgdn"], - ["symbol", "numpadpgup"], - ["symbol", "numpadright"], - ["symbol", "numpadsub"], - ["symbol", "numpadup"], - ["symbol", "pgdn"], - ["symbol", "pgup"], - ["symbol", "printscreen"], - ["symbol", "ralt"], - ["symbol", "rbutton"], - ["symbol", "rcontrol"], - ["symbol", "rctrl"], - ["symbol", "right"], - ["symbol", "rshift"], - ["symbol", "rwin"], - ["symbol", "rwindown"], - ["symbol", "rwinup"], - ["symbol", "scrolllock"], - ["symbol", "shift"], - ["symbol", "shiftdown"], - ["symbol", "shiftup"], - ["symbol", "space"], - ["symbol", "tab"], - ["symbol", "up"], - ["symbol", "volume_down"], - ["symbol", "volume_mute"], - ["symbol", "volume_up"], - ["symbol", "wheeldown"], - ["symbol", "wheelleft"], - ["symbol", "wheelright"], - ["symbol", "wheelup"], - ["symbol", "xbutton1"], - ["symbol", "xbutton2"] -] - ----------------------------------------------------- - -Checks for all symbols. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/tag_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/tag_feature.test deleted file mode 100644 index 0ca92589..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/tag_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo: -foo_bar: - ----------------------------------------------------- - -[ - ["tag", "foo"], - ["punctuation", ":"], - ["tag", "foo_bar"], - ["punctuation", ":"] -] - ----------------------------------------------------- - -Checks for tags (labels). \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autohotkey/variable_feature.test b/docs/_style/prism-master/tests/languages/autohotkey/variable_feature.test deleted file mode 100644 index 5ed8a4c5..00000000 --- a/docs/_style/prism-master/tests/languages/autohotkey/variable_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -%foo% -%foo_bar% - ----------------------------------------------------- - -[ - ["variable", "%foo%"], - ["variable", "%foo_bar%"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/boolean_feature.test b/docs/_style/prism-master/tests/languages/autoit/boolean_feature.test deleted file mode 100644 index 5750be06..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -True -False - ----------------------------------------------------- - -[ - ["boolean", "True"], - ["boolean", "False"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/comment_feature.test b/docs/_style/prism-master/tests/languages/autoit/comment_feature.test deleted file mode 100644 index d88708f3..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/comment_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -; -; foo -#comments-start - foobar() -#comments-end -#cs - foobar() -#ce -;#comments-start - foobar() -;#comments-end -;#cs - foobar() -;#ce - ----------------------------------------------------- - -[ - ["comment", ";"], - ["comment", "; foo"], - ["comment", "#comments-start\r\n\tfoobar()\r\n#comments-end"], - ["comment", "#cs\r\n\tfoobar()\r\n#ce"], - ["comment", ";#comments-start"], - ["function", "foobar"], ["punctuation", "("], ["punctuation", ")"], - ["comment", ";#comments-end"], - ["comment", ";#cs"], - ["function", "foobar"], ["punctuation", "("], ["punctuation", ")"], - ["comment", ";#ce"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/directive_feature.test b/docs/_style/prism-master/tests/languages/autoit/directive_feature.test deleted file mode 100644 index 9219f700..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/directive_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -#NoTrayIcon -#OnAutoItStartRegister "Example" - ----------------------------------------------------- - -[ - ["directive", "#NoTrayIcon"], - ["directive", "#OnAutoItStartRegister"], ["string", ["\"Example\""]] -] - ----------------------------------------------------- - -Checks for directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/function_feature.test b/docs/_style/prism-master/tests/languages/autoit/function_feature.test deleted file mode 100644 index 13c82dda..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/function_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo() -foo_bar() -foo_bar_42() - ----------------------------------------------------- - -[ - ["function", "foo"], ["punctuation", "("], ["punctuation", ")"], - ["function", "foo_bar"], ["punctuation", "("], ["punctuation", ")"], - ["function", "foo_bar_42"], ["punctuation", "("], ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/keyword_feature.test b/docs/_style/prism-master/tests/languages/autoit/keyword_feature.test deleted file mode 100644 index 2db276cb..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/keyword_feature.test +++ /dev/null @@ -1,83 +0,0 @@ -Case -Const -ContinueCase -ContinueLoop -Default -Dim -Do -Else -ElseIf -EndFunc -EndIf -EndSelect -EndSwitch -EndWith -Enum -Exit -ExitLoop -For -Func -Global -If -In -Local -Next -Null -ReDim -Select -Static -Step -Switch -Then -To -Until -Volatile -WEnd -While -With - ----------------------------------------------------- - -[ - ["keyword", "Case"], - ["keyword", "Const"], - ["keyword", "ContinueCase"], - ["keyword", "ContinueLoop"], - ["keyword", "Default"], - ["keyword", "Dim"], - ["keyword", "Do"], - ["keyword", "Else"], - ["keyword", "ElseIf"], - ["keyword", "EndFunc"], - ["keyword", "EndIf"], - ["keyword", "EndSelect"], - ["keyword", "EndSwitch"], - ["keyword", "EndWith"], - ["keyword", "Enum"], - ["keyword", "Exit"], - ["keyword", "ExitLoop"], - ["keyword", "For"], - ["keyword", "Func"], - ["keyword", "Global"], - ["keyword", "If"], - ["keyword", "In"], - ["keyword", "Local"], - ["keyword", "Next"], - ["keyword", "Null"], - ["keyword", "ReDim"], - ["keyword", "Select"], - ["keyword", "Static"], - ["keyword", "Step"], - ["keyword", "Switch"], - ["keyword", "Then"], - ["keyword", "To"], - ["keyword", "Until"], - ["keyword", "Volatile"], - ["keyword", "WEnd"], - ["keyword", "While"], - ["keyword", "With"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/number_feature.test b/docs/_style/prism-master/tests/languages/autoit/number_feature.test deleted file mode 100644 index 5c6ac5bc..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/number_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -42 -3.14159 -4e8 -3.5E-9 -0.7e+12 -0xBadFace - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "4e8"], - ["number", "3.5E-9"], - ["number", "0.7e+12"], - ["number", "0xBadFace"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/operator_feature.test b/docs/_style/prism-master/tests/languages/autoit/operator_feature.test deleted file mode 100644 index 65f4ce4d..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/operator_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -< <= <> -> >= -+ += - -= -* *= / /= -& &= -? ^ -And Or Not - ----------------------------------------------------- - -[ - ["operator", "<"], ["operator", "<="], ["operator", "<>"], - ["operator", ">"], ["operator", ">="], - ["operator", "+"], ["operator", "+="], ["operator", "-"], ["operator", "-="], - ["operator", "*"], ["operator", "*="], ["operator", "/"], ["operator", "/="], - ["operator", "&"], ["operator", "&="], - ["operator", "?"], ["operator", "^"], - ["operator", "And"], ["operator", "Or"], ["operator", "Not"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/string_feature.test b/docs/_style/prism-master/tests/languages/autoit/string_feature.test deleted file mode 100644 index 5ae4bb70..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/string_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -"" -"foo""bar" -"foo %foo% bar $bar$ baz @baz@" -'' -'foo''bar' -'foo %foo% bar $bar$ baz @baz@' - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["\"foo\"\"bar\""]], - ["string", [ - "\"foo ", - ["variable", "%foo%"], - " bar ", - ["variable", "$bar$"], - " baz ", - ["variable", "@baz@"], - "\"" - ]], - ["string", ["''"]], - ["string", ["'foo''bar'"]], - ["string", [ - "'foo ", - ["variable", "%foo%"], - " bar ", - ["variable", "$bar$"], - " baz ", - ["variable", "@baz@"], - "'" - ]] -] - ----------------------------------------------------- - -Checks for strings and interpolation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/url_feature.test b/docs/_style/prism-master/tests/languages/autoit/url_feature.test deleted file mode 100644 index 1f6d3cd4..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/url_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "foo.au3" - ----------------------------------------------------- - -[ - ["directive", "#include"], - ["url", ""], - ["directive", "#include"], - ["url", "\"foo.au3\""] -] - ----------------------------------------------------- - -Checks for files in includes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/autoit/variable_feature.test b/docs/_style/prism-master/tests/languages/autoit/variable_feature.test deleted file mode 100644 index e36ffe32..00000000 --- a/docs/_style/prism-master/tests/languages/autoit/variable_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -$foo -$foo_bar_42 -@ComputerName -@CPUArch -@TAB - ----------------------------------------------------- - -[ - ["variable", "$foo"], - ["variable", "$foo_bar_42"], - ["variable", "@ComputerName"], - ["variable", "@CPUArch"], - ["variable", "@TAB"] -] - ----------------------------------------------------- - -Checks for variables and macros. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/arithmetic_environment_feature.test b/docs/_style/prism-master/tests/languages/bash/arithmetic_environment_feature.test deleted file mode 100644 index a9c5663d..00000000 --- a/docs/_style/prism-master/tests/languages/bash/arithmetic_environment_feature.test +++ /dev/null @@ -1,53 +0,0 @@ -(( 4 + 5 )) -$((5 * 7)) -"foo $((5 * 7)) bar" -for (( NUM=1 ; NUM<=1000 ; NUM++ )) - ----------------------------------------------------- - -[ - ["variable", [ - ["punctuation", "(("], - ["number", "4"], - ["operator", "+"], - ["number", "5"], - ["punctuation", "))"] - ]], - ["variable", [ - ["variable", "$(("], - ["number", "5"], - ["operator", "*"], - ["number", "7"], - ["variable", "))"] - ]], - ["string", [ - "\"foo ", - ["variable", [ - ["variable", "$(("], - ["number", "5"], - ["operator", "*"], - ["number", "7"], - ["variable", "))"] - ]], - " bar\"" - ]], - ["keyword", "for"], - ["variable", [ - ["punctuation", "(("], - " NUM", - ["operator", "="], - ["number", "1"], - ["punctuation", ";"], - " NUM", - ["operator", "<="], - ["number", "1000"], - ["punctuation", ";"], - " NUM", - ["operator", "++"], - ["punctuation", "))"] - ]] -] - ----------------------------------------------------- - -Checks arithmetic environments \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/command_substitution_feature.test b/docs/_style/prism-master/tests/languages/bash/command_substitution_feature.test deleted file mode 100644 index 2b78b518..00000000 --- a/docs/_style/prism-master/tests/languages/bash/command_substitution_feature.test +++ /dev/null @@ -1,45 +0,0 @@ -$(echo "foo") -`echo "foo"` -"$(echo "foo") bar" -"`echo "foo"` bar" - ----------------------------------------------------- - -[ - ["variable", [ - ["variable", "$("], - ["keyword", "echo"], - ["string", ["\"foo\""]], - ["variable", ")"] - ]], - ["variable", [ - ["variable", "`"], - ["keyword", "echo"], - ["string", ["\"foo\""]], - ["variable", "`"] - ]], - ["string", [ - "\"", - ["variable", [ - ["variable", "$("], - ["keyword", "echo"], - ["string", ["\"foo\""]], - ["variable", ")"] - ]], - " bar\"" - ]], - ["string", [ - "\"", - ["variable", [ - ["variable", "`"], - ["keyword", "echo"], - ["string", ["\"foo\""]], - ["variable", "`"] - ]], - " bar\"" - ]] -] - ----------------------------------------------------- - -Checks for command substitution. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/comment_feature.test b/docs/_style/prism-master/tests/languages/bash/comment_feature.test deleted file mode 100644 index 58249096..00000000 --- a/docs/_style/prism-master/tests/languages/bash/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -#foo -# bar - ----------------------------------------------------- - -[ - ["comment", "#foo"], - ["comment", "# bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/function_feature.test b/docs/_style/prism-master/tests/languages/bash/function_feature.test deleted file mode 100644 index f3fb7123..00000000 --- a/docs/_style/prism-master/tests/languages/bash/function_feature.test +++ /dev/null @@ -1,101 +0,0 @@ -alias apropos apt apt-cache apt-get aptitude aspell -automysqlbackup awk basename bash bc bconsole bg -builtin bzip2 cal cat cd -cfdisk chgrp chmod chown chroot -chkconfig cksum clear cmp comm -command cp cron crontab csplit -curl cut date dc dd ddrescue debootstrap -df diff diff3 dig dir dircolors -dirname dirs dmesg du -egrep eject enable env ethtool -eval exec expand expect -export expr fdformat fdisk -fg fgrep file find fmt -fold format free fsck ftp -fuser gawk getopts git gparted grep -groupadd groupdel groupmod groups grub-mkconfig -gzip halt hash head help hg history host -hostname htop iconv id ifconfig -ifdown ifup import install ip jobs -join kill killall less link ln -locate logname logout logrotate look lpc lpr -lprint lprintd lprintq lprm ls -lsof lynx make man mc mdadm mkconfig mkdir mke2fs mkfifo -mkfs mkisofs mknod mkswap more most mount -mtools mtr mutt mv mmv nano nc netstat -nice nl nohup notify-send nslookup -open parted op passwd paste pathchk ping -pkill popd pr printcap printenv -printf ps pushd pv pwd quota -quotacheck quotactl ram rar rcp -read readarray readonly reboot -rename renice remsync rev rm -rmdir rpm rsync screen scp sdiff sed sendmail -seq service sftp shift -shopt shutdown sleep slocate -sort source split ssh stat strace -su sudo sum suspend swapon sync tail tar -tee test time timeout times -touch top traceroute trap tr -tsort tty type ulimit umask -umount unalias uname unexpand uniq -units unrar unshar unzip update-grub uptime -useradd userdel usermod users uuencode -uudecode vdir vi vim virsh vmstat wait watch -wc wget whereis which who whoami write -xargs xdg-open yes zip zypper - ----------------------------------------------------- - -[ - ["function", "alias"], ["function", "apropos"], ["function", "apt"], ["function", "apt-cache"], ["function", "apt-get"], ["function", "aptitude"], ["function", "aspell"], - ["function", "automysqlbackup"], ["function", "awk"], ["function", "basename"], ["function", "bash"], ["function", "bc"], ["function", "bconsole"], ["function", "bg"], - ["function", "builtin"], ["function", "bzip2"], ["function", "cal"], ["function", "cat"], ["function", "cd"], - ["function", "cfdisk"], ["function", "chgrp"], ["function", "chmod"], ["function", "chown"], ["function", "chroot"], - ["function", "chkconfig"], ["function", "cksum"], ["function", "clear"], ["function", "cmp"], ["function", "comm"], - ["function", "command"], ["function", "cp"], ["function", "cron"], ["function", "crontab"], ["function", "csplit"], - ["function", "curl"], ["function", "cut"], ["function", "date"], ["function", "dc"], ["function", "dd"], ["function", "ddrescue"], ["function", "debootstrap"], - ["function", "df"], ["function", "diff"], ["function", "diff3"], ["function", "dig"], ["function", "dir"], ["function", "dircolors"], - ["function", "dirname"], ["function", "dirs"], ["function", "dmesg"], ["function", "du"], - ["function", "egrep"], ["function", "eject"], ["function", "enable"], ["function", "env"], ["function", "ethtool"], - ["function", "eval"], ["function", "exec"], ["function", "expand"], ["function", "expect"], - ["function", "export"], ["function", "expr"], ["function", "fdformat"], ["function", "fdisk"], - ["function", "fg"], ["function", "fgrep"], ["function", "file"], ["function", "find"], ["function", "fmt"], - ["function", "fold"], ["function", "format"], ["function", "free"], ["function", "fsck"], ["function", "ftp"], - ["function", "fuser"], ["function", "gawk"], ["function", "getopts"], ["function", "git"], ["function", "gparted"], ["function", "grep"], - ["function", "groupadd"], ["function", "groupdel"], ["function", "groupmod"], ["function", "groups"], ["function", "grub-mkconfig"], - ["function", "gzip"], ["function", "halt"], ["function", "hash"], ["function", "head"], ["function", "help"], ["function", "hg"], ["function", "history"], ["function", "host"], - ["function", "hostname"], ["function", "htop"], ["function", "iconv"], ["function", "id"], ["function", "ifconfig"], - ["function", "ifdown"], ["function", "ifup"], ["function", "import"], ["function", "install"], ["function", "ip"], ["function", "jobs"], - ["function", "join"], ["function", "kill"], ["function", "killall"], ["function", "less"], ["function", "link"], ["function", "ln"], - ["function", "locate"], ["function", "logname"], ["function", "logout"], ["function", "logrotate"], ["function", "look"], ["function", "lpc"], ["function", "lpr"], - ["function", "lprint"], ["function", "lprintd"], ["function", "lprintq"], ["function", "lprm"], ["function", "ls"], - ["function", "lsof"], ["function", "lynx"], ["function", "make"], ["function", "man"], ["function", "mc"], ["function", "mdadm"], ["function", "mkconfig"], ["function", "mkdir"], ["function", "mke2fs"], ["function", "mkfifo"], - ["function", "mkfs"], ["function", "mkisofs"], ["function", "mknod"], ["function", "mkswap"], ["function", "more"], ["function", "most"], ["function", "mount"], - ["function", "mtools"], ["function", "mtr"], ["function", "mutt"], ["function", "mv"], ["function", "mmv"], ["function", "nano"], ["function", "nc"], ["function", "netstat"], - ["function", "nice"], ["function", "nl"], ["function", "nohup"], ["function", "notify-send"], ["function", "nslookup"], - ["function", "open"], ["function", "parted"], ["function", "op"], ["function", "passwd"], ["function", "paste"], ["function", "pathchk"], ["function", "ping"], - ["function", "pkill"], ["function", "popd"], ["function", "pr"], ["function", "printcap"], ["function", "printenv"], - ["function", "printf"], ["function", "ps"], ["function", "pushd"], ["function", "pv"], ["function", "pwd"], ["function", "quota"], - ["function", "quotacheck"], ["function", "quotactl"], ["function", "ram"], ["function", "rar"], ["function", "rcp"], - ["function", "read"], ["function", "readarray"], ["function", "readonly"], ["function", "reboot"], - ["function", "rename"], ["function", "renice"], ["function", "remsync"], ["function", "rev"], ["function", "rm"], - ["function", "rmdir"], ["function", "rpm"], ["function", "rsync"], ["function", "screen"], ["function", "scp"], ["function", "sdiff"], ["function", "sed"], ["function", "sendmail"], - ["function", "seq"], ["function", "service"], ["function", "sftp"], ["function", "shift"], - ["function", "shopt"], ["function", "shutdown"], ["function", "sleep"], ["function", "slocate"], - ["function", "sort"], ["function", "source"], ["function", "split"], ["function", "ssh"], ["function", "stat"], ["function", "strace"], - ["function", "su"], ["function", "sudo"], ["function", "sum"], ["function", "suspend"], ["function", "swapon"], ["function", "sync"], ["function", "tail"], ["function", "tar"], - ["function", "tee"], ["function", "test"], ["function", "time"], ["function", "timeout"], ["function", "times"], - ["function", "touch"], ["function", "top"], ["function", "traceroute"], ["function", "trap"], ["function", "tr"], - ["function", "tsort"], ["function", "tty"], ["function", "type"], ["function", "ulimit"], ["function", "umask"], - ["function", "umount"], ["function", "unalias"], ["function", "uname"], ["function", "unexpand"], ["function", "uniq"], - ["function", "units"], ["function", "unrar"], ["function", "unshar"], ["function", "unzip"], ["function", "update-grub"], ["function", "uptime"], - ["function", "useradd"], ["function", "userdel"], ["function", "usermod"], ["function", "users"], ["function", "uuencode"], - ["function", "uudecode"], ["function", "vdir"], ["function", "vi"], ["function", "vim"], ["function", "virsh"], ["function", "vmstat"], ["function", "wait"], ["function", "watch"], - ["function", "wc"], ["function", "wget"], ["function", "whereis"], ["function", "which"], ["function", "who"], ["function", "whoami"], ["function", "write"], - ["function", "xargs"], ["function", "xdg-open"], ["function", "yes"], ["function", "zip"], ["function", "zypper"] -] - ----------------------------------------------------- - -Checks for all functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/keyword_feature.test b/docs/_style/prism-master/tests/languages/bash/keyword_feature.test deleted file mode 100644 index b3bacf55..00000000 --- a/docs/_style/prism-master/tests/languages/bash/keyword_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -if then else elif fi -for break continue while -in case function select -do done until echo exit -return set declare -. : ----------------------------------------------------- - -[ - ["keyword", "if"], ["keyword", "then"], ["keyword", "else"], ["keyword", "elif"], ["keyword", "fi"], - ["keyword", "for"], ["keyword", "break"], ["keyword", "continue"], ["keyword", "while"], - ["keyword", "in"], ["keyword", "case"], ["keyword", "function"], ["keyword", "select"], - ["keyword", "do"], ["keyword", "done"], ["keyword", "until"], ["keyword", "echo"], ["keyword", "exit"], - ["keyword", "return"], ["keyword", "set"], ["keyword", "declare"], - ["keyword", "."], ["keyword", ":"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/shebang_feature.test b/docs/_style/prism-master/tests/languages/bash/shebang_feature.test deleted file mode 100644 index c41860d0..00000000 --- a/docs/_style/prism-master/tests/languages/bash/shebang_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - ----------------------------------------------------- - -[ - ["shebang", "#!/bin/bash"] -] - ----------------------------------------------------- - -Checks for shebang. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/string_feature.test b/docs/_style/prism-master/tests/languages/bash/string_feature.test deleted file mode 100644 index fd2a3031..00000000 --- a/docs/_style/prism-master/tests/languages/bash/string_feature.test +++ /dev/null @@ -1,67 +0,0 @@ -"" -'' -"foo" -'foo' -"foo -bar" -'foo -bar' -"'foo'" -'"bar"' -"$@" -"${foo}" -<< STRING_END -foo -bar -STRING_END -<< EOF -foo $@ -bar -EOF -<< 'EOF' -'single quoted string' -"double quoted string" -EOF -<< "EOF" -foo -bar -EOF -<< STRING_END -# comment -STRING_END -" # comment " - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["''"]], - ["string", ["\"foo\""]], - ["string", ["'foo'"]], - ["string", ["\"foo\r\nbar\""]], - ["string", ["'foo\r\nbar'"]], - ["string", ["\"'foo'\""]], - ["string", ["'\"bar\"'"]], - ["string", [ - "\"", ["variable", "$@"], "\"" - ]], - ["string", [ - "\"", ["variable", "${foo}"], "\"" - ]], - ["operator", "<<"], - ["string", ["STRING_END\r\nfoo\r\nbar\r\nSTRING_END"]], - ["operator", "<<"], - ["string", ["EOF\r\nfoo ", ["variable", "$@"], "\r\nbar\r\nEOF"]], - ["operator", "<<"], - ["string", ["'EOF'\r\n'single quoted string'\r\n\"double quoted string\"\r\nEOF"]], - ["operator", "<<"], - ["string", ["\"EOF\"\r\nfoo\r\nbar\r\nEOF"]], - ["operator", "<<"], - ["string", ["STRING_END\r\n# comment\r\nSTRING_END"]], - ["string", ["\" # comment \""]] -] - ----------------------------------------------------- - -Checks for single-quoted and double-quoted strings. -Also checks for variables in strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bash/variable_feature.test b/docs/_style/prism-master/tests/languages/bash/variable_feature.test deleted file mode 100644 index 16b7448a..00000000 --- a/docs/_style/prism-master/tests/languages/bash/variable_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -$foo -$@ -${foo bar} - ----------------------------------------------------- - -[ - ["variable", "$foo"], - ["variable", "$@"], - ["variable", "${foo bar}"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/comment_feature.test b/docs/_style/prism-master/tests/languages/basic/comment_feature.test deleted file mode 100644 index d3061761..00000000 --- a/docs/_style/prism-master/tests/languages/basic/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -! Foobar -REM Foobar - ----------------------------------------------------- - -[ - ["comment", ["! Foobar"]], - ["comment", [["keyword", "REM"], " Foobar"]] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/function_feature.test b/docs/_style/prism-master/tests/languages/basic/function_feature.test deleted file mode 100644 index 5fe8d760..00000000 --- a/docs/_style/prism-master/tests/languages/basic/function_feature.test +++ /dev/null @@ -1,309 +0,0 @@ -ABS -ACCESS -ACOS -ANGLE -AREA -ARITHMETIC -ARRAY -ASIN -ASK -AT -ATN -BASE -BEGIN -BREAK -CAUSE -CEIL -CHR -CLIP -COLLATE -COLOR -CON -COS -COSH -COT -CSC -DATE -DATUM -DEBUG -DECIMAL -DEF -DEG -DEGREES -DELETE -DET -DEVICE -DISPLAY -DOT -ELAPSED -EPS -ERASABLE -EXLINE -EXP -EXTERNAL -EXTYPE -FILETYPE -FIXED -FP -GO -GRAPH -HANDLER -IDN -IMAGE -IN -INT -INTERNAL -IP -IS -KEYED -LBOUND -LCASE -LEFT -LEN -LENGTH -LET -LINE -LINES -LOG -LOG10 -LOG2 -LTRIM -MARGIN -MAT -MAX -MAXNUM -MID -MIN -MISSING -MOD -NATIVE -NUL -NUMERIC -OF -OPTION -ORD -ORGANIZATION -OUTIN -OUTPUT -PI -POINT -POINTER -POINTS -POS -PRINT -PROGRAM -PROMPT -RAD -RADIANS -RANDOMIZE -RECORD -RECSIZE -RECTYPE -RELATIVE -REMAINDER -REPEAT -REST -RETRY -REWRITE -RIGHT -RND -ROUND -RTRIM -SAME -SEC -SELECT -SEQUENTIAL -SET -SETTER -SGN -SIN -SINH -SIZE -SKIP -SQR -STANDARD -STATUS -STR -STREAM -STYLE -TAB -TAN -TANH -TEMPLATE -TEXT -THERE -TIME -TIMEOUT -TRACE -TRANSFORM -TRUNCATE -UBOUND -UCASE -USE -VAL -VARIABLE -VIEWPORT -WHEN -WINDOW -WITH -ZER -ZONEWIDTH - ----------------------------------------------------- - -[ - ["function", "ABS"], - ["function", "ACCESS"], - ["function", "ACOS"], - ["function", "ANGLE"], - ["function", "AREA"], - ["function", "ARITHMETIC"], - ["function", "ARRAY"], - ["function", "ASIN"], - ["function", "ASK"], - ["function", "AT"], - ["function", "ATN"], - ["function", "BASE"], - ["function", "BEGIN"], - ["function", "BREAK"], - ["function", "CAUSE"], - ["function", "CEIL"], - ["function", "CHR"], - ["function", "CLIP"], - ["function", "COLLATE"], - ["function", "COLOR"], - ["function", "CON"], - ["function", "COS"], - ["function", "COSH"], - ["function", "COT"], - ["function", "CSC"], - ["function", "DATE"], - ["function", "DATUM"], - ["function", "DEBUG"], - ["function", "DECIMAL"], - ["function", "DEF"], - ["function", "DEG"], - ["function", "DEGREES"], - ["function", "DELETE"], - ["function", "DET"], - ["function", "DEVICE"], - ["function", "DISPLAY"], - ["function", "DOT"], - ["function", "ELAPSED"], - ["function", "EPS"], - ["function", "ERASABLE"], - ["function", "EXLINE"], - ["function", "EXP"], - ["function", "EXTERNAL"], - ["function", "EXTYPE"], - ["function", "FILETYPE"], - ["function", "FIXED"], - ["function", "FP"], - ["function", "GO"], - ["function", "GRAPH"], - ["function", "HANDLER"], - ["function", "IDN"], - ["function", "IMAGE"], - ["function", "IN"], - ["function", "INT"], - ["function", "INTERNAL"], - ["function", "IP"], - ["function", "IS"], - ["function", "KEYED"], - ["function", "LBOUND"], - ["function", "LCASE"], - ["function", "LEFT"], - ["function", "LEN"], - ["function", "LENGTH"], - ["function", "LET"], - ["function", "LINE"], - ["function", "LINES"], - ["function", "LOG"], - ["function", "LOG10"], - ["function", "LOG2"], - ["function", "LTRIM"], - ["function", "MARGIN"], - ["function", "MAT"], - ["function", "MAX"], - ["function", "MAXNUM"], - ["function", "MID"], - ["function", "MIN"], - ["function", "MISSING"], - ["function", "MOD"], - ["function", "NATIVE"], - ["function", "NUL"], - ["function", "NUMERIC"], - ["function", "OF"], - ["function", "OPTION"], - ["function", "ORD"], - ["function", "ORGANIZATION"], - ["function", "OUTIN"], - ["function", "OUTPUT"], - ["function", "PI"], - ["function", "POINT"], - ["function", "POINTER"], - ["function", "POINTS"], - ["function", "POS"], - ["function", "PRINT"], - ["function", "PROGRAM"], - ["function", "PROMPT"], - ["function", "RAD"], - ["function", "RADIANS"], - ["function", "RANDOMIZE"], - ["function", "RECORD"], - ["function", "RECSIZE"], - ["function", "RECTYPE"], - ["function", "RELATIVE"], - ["function", "REMAINDER"], - ["function", "REPEAT"], - ["function", "REST"], - ["function", "RETRY"], - ["function", "REWRITE"], - ["function", "RIGHT"], - ["function", "RND"], - ["function", "ROUND"], - ["function", "RTRIM"], - ["function", "SAME"], - ["function", "SEC"], - ["function", "SELECT"], - ["function", "SEQUENTIAL"], - ["function", "SET"], - ["function", "SETTER"], - ["function", "SGN"], - ["function", "SIN"], - ["function", "SINH"], - ["function", "SIZE"], - ["function", "SKIP"], - ["function", "SQR"], - ["function", "STANDARD"], - ["function", "STATUS"], - ["function", "STR"], - ["function", "STREAM"], - ["function", "STYLE"], - ["function", "TAB"], - ["function", "TAN"], - ["function", "TANH"], - ["function", "TEMPLATE"], - ["function", "TEXT"], - ["function", "THERE"], - ["function", "TIME"], - ["function", "TIMEOUT"], - ["function", "TRACE"], - ["function", "TRANSFORM"], - ["function", "TRUNCATE"], - ["function", "UBOUND"], - ["function", "UCASE"], - ["function", "USE"], - ["function", "VAL"], - ["function", "VARIABLE"], - ["function", "VIEWPORT"], - ["function", "WHEN"], - ["function", "WINDOW"], - ["function", "WITH"], - ["function", "ZER"], - ["function", "ZONEWIDTH"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/keyword_feature.test b/docs/_style/prism-master/tests/languages/basic/keyword_feature.test deleted file mode 100644 index ecac6855..00000000 --- a/docs/_style/prism-master/tests/languages/basic/keyword_feature.test +++ /dev/null @@ -1,213 +0,0 @@ -AS -BEEP -BLOAD -BSAVE -CALL -CALL ABSOLUTE -CASE -CHAIN -CHDIR -CLEAR -CLOSE -CLS -COM -COMMON -CONST -DATA -DECLARE -DEF FN -DEF SEG -DEFDBL -DEFINT -DEFLNG -DEFSNG -DEFSTR -DIM -DO -DOUBLE -ELSE -ELSEIF -END -ENVIRON -ERASE -ERROR -EXIT -FIELD -FILES -FOR -FUNCTION -GET -GOSUB -GOTO -IF -INPUT -INTEGER -IOCTL -KEY -KILL -LINE INPUT -LOCATE -LOCK -LONG -LOOP -LSET -MKDIR -NAME -NEXT -OFF -ON -ON COM -ON ERROR -ON KEY -ON TIMER -OPEN -OPTION BASE -OUT -POKE -PUT -READ -REDIM -REM -RESTORE -RESUME -RETURN -RMDIR -RSET -RUN -SHARED -SINGLE -SELECT CASE -SHELL -SLEEP -STATIC -STEP -STOP -STRING -SUB -SWAP -SYSTEM -THEN -TIMER -TO -TROFF -TRON -TYPE -UNLOCK -UNTIL -USING -VIEW PRINT -WAIT -WEND -WHILE -WRITE - ----------------------------------------------------- - -[ - ["keyword", "AS"], - ["keyword", "BEEP"], - ["keyword", "BLOAD"], - ["keyword", "BSAVE"], - ["keyword", "CALL"], - ["keyword", "CALL ABSOLUTE"], - ["keyword", "CASE"], - ["keyword", "CHAIN"], - ["keyword", "CHDIR"], - ["keyword", "CLEAR"], - ["keyword", "CLOSE"], - ["keyword", "CLS"], - ["keyword", "COM"], - ["keyword", "COMMON"], - ["keyword", "CONST"], - ["keyword", "DATA"], - ["keyword", "DECLARE"], - ["keyword", "DEF FN"], - ["keyword", "DEF SEG"], - ["keyword", "DEFDBL"], - ["keyword", "DEFINT"], - ["keyword", "DEFLNG"], - ["keyword", "DEFSNG"], - ["keyword", "DEFSTR"], - ["keyword", "DIM"], - ["keyword", "DO"], - ["keyword", "DOUBLE"], - ["keyword", "ELSE"], - ["keyword", "ELSEIF"], - ["keyword", "END"], - ["keyword", "ENVIRON"], - ["keyword", "ERASE"], - ["keyword", "ERROR"], - ["keyword", "EXIT"], - ["keyword", "FIELD"], - ["keyword", "FILES"], - ["keyword", "FOR"], - ["keyword", "FUNCTION"], - ["keyword", "GET"], - ["keyword", "GOSUB"], - ["keyword", "GOTO"], - ["keyword", "IF"], - ["keyword", "INPUT"], - ["keyword", "INTEGER"], - ["keyword", "IOCTL"], - ["keyword", "KEY"], - ["keyword", "KILL"], - ["keyword", "LINE INPUT"], - ["keyword", "LOCATE"], - ["keyword", "LOCK"], - ["keyword", "LONG"], - ["keyword", "LOOP"], - ["keyword", "LSET"], - ["keyword", "MKDIR"], - ["keyword", "NAME"], - ["keyword", "NEXT"], - ["keyword", "OFF"], - ["keyword", "ON"], - ["keyword", "ON COM"], - ["keyword", "ON ERROR"], - ["keyword", "ON KEY"], - ["keyword", "ON TIMER"], - ["keyword", "OPEN"], - ["keyword", "OPTION BASE"], - ["keyword", "OUT"], - ["keyword", "POKE"], - ["keyword", "PUT"], - ["keyword", "READ"], - ["keyword", "REDIM"], - ["keyword", "REM"], - ["keyword", "RESTORE"], - ["keyword", "RESUME"], - ["keyword", "RETURN"], - ["keyword", "RMDIR"], - ["keyword", "RSET"], - ["keyword", "RUN"], - ["keyword", "SHARED"], - ["keyword", "SINGLE"], - ["keyword", "SELECT CASE"], - ["keyword", "SHELL"], - ["keyword", "SLEEP"], - ["keyword", "STATIC"], - ["keyword", "STEP"], - ["keyword", "STOP"], - ["keyword", "STRING"], - ["keyword", "SUB"], - ["keyword", "SWAP"], - ["keyword", "SYSTEM"], - ["keyword", "THEN"], - ["keyword", "TIMER"], - ["keyword", "TO"], - ["keyword", "TROFF"], - ["keyword", "TRON"], - ["keyword", "TYPE"], - ["keyword", "UNLOCK"], - ["keyword", "UNTIL"], - ["keyword", "USING"], - ["keyword", "VIEW PRINT"], - ["keyword", "WAIT"], - ["keyword", "WEND"], - ["keyword", "WHILE"], - ["keyword", "WRITE"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/number_feature.test b/docs/_style/prism-master/tests/languages/basic/number_feature.test deleted file mode 100644 index 5adeb7af..00000000 --- a/docs/_style/prism-master/tests/languages/basic/number_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -42 -3.14159 -2e8 -3.4E-9 -0.7E+12 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "2e8"], - ["number", "3.4E-9"], - ["number", "0.7E+12"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/operator_feature.test b/docs/_style/prism-master/tests/languages/basic/operator_feature.test deleted file mode 100644 index b2d93f61..00000000 --- a/docs/_style/prism-master/tests/languages/basic/operator_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -< <= <> -> >= -+ - * / -^ = & -AND EQV IMP -NOT OR XOR - ----------------------------------------------------- - -[ - ["operator", "<"], ["operator", "<="], ["operator", "<>"], - ["operator", ">"], ["operator", ">="], - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], - ["operator", "^"], ["operator", "="], ["operator", "&"], - ["operator", "AND"], ["operator", "EQV"], ["operator", "IMP"], - ["operator", "NOT"], ["operator", "OR"], ["operator", "XOR"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/basic/string_feature.test b/docs/_style/prism-master/tests/languages/basic/string_feature.test deleted file mode 100644 index 83fea35e..00000000 --- a/docs/_style/prism-master/tests/languages/basic/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"fo""obar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\"\"obar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/batch/command_feature.test b/docs/_style/prism-master/tests/languages/batch/command_feature.test deleted file mode 100644 index 350788a8..00000000 --- a/docs/_style/prism-master/tests/languages/batch/command_feature.test +++ /dev/null @@ -1,103 +0,0 @@ -FOR /l %%a in (5,-1,1) do (TITLE %title% -- closing in %%as) -SET title=%~n0 -echo.Hello World -@ECHO OFF -if not defined ProgressFormat set "ProgressFormat=[PPPP]" -EXIT /b -set /a ProgressCnt+=1 -IF "%~1" NEQ "" (SET %~1=%new%) ELSE (echo.%new%) - ----------------------------------------------------- - -[ - ["command", [ - ["keyword", "FOR"], - ["parameter", ["/l"]], - ["variable", "%%a"], - ["keyword", "in"], - ["punctuation", "("], - ["number", "5"], ["punctuation", ","], - ["number", "-1"], ["punctuation", ","], - ["number", "1"], ["punctuation", ")"], - ["keyword", "do"] - ]], - ["punctuation", "("], - ["command", [ - ["keyword", "TITLE"], - ["variable", "%title%"], - " -- closing in ", - ["variable", "%%as"] - ]], - ["punctuation", ")"], - - ["command", [ - ["keyword", "SET"], - ["variable", "title"], - ["operator", "="], - ["variable", "%~n0"] - ]], - - ["command", [ - ["keyword", "echo"], - ".Hello World" - ]], - - ["operator", "@"], - ["command", [ - ["keyword", "ECHO"], - " OFF" - ]], - - ["command", [ - ["keyword", "if"], - ["keyword", "not"], - ["keyword", "defined"], - " ProgressFormat" - ]], - ["command", [ - ["keyword", "set"], - ["string", "\"ProgressFormat=[PPPP]\""] - ]], - - ["command", [ - ["keyword", "EXIT"], - ["parameter", ["/b"]] - ]], - - ["command", [ - ["keyword", "set"], - ["parameter", ["/a"]], - ["variable", "ProgressCnt"], - ["operator", "+="], - ["number", "1"] - ]], - - ["command", [ - ["keyword", "IF"], - ["string", "\"%~1\""], - ["operator", "NEQ"], - ["string", "\"\""] - ]], - ["punctuation", "("], - ["command", [ - ["keyword", "SET"], - ["variable", "%~1"], - ["operator", "="], - ["variable", "%new%"] - ]], - ["punctuation", ")"], - ["command", [ - ["keyword", "ELSE"] - ]], - ["punctuation", "("], - ["command", [ - ["keyword", "echo"], - ".", - ["variable", "%new%"] - ]], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for commands. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/batch/comment_feature.test b/docs/_style/prism-master/tests/languages/batch/comment_feature.test deleted file mode 100644 index 83759d8e..00000000 --- a/docs/_style/prism-master/tests/languages/batch/comment_feature.test +++ /dev/null @@ -1,18 +0,0 @@ -:: -:: Foobar -REM Foobar -rem foo^ -bar - ----------------------------------------------------- - -[ - ["comment", "::"], - ["comment", ":: Foobar"], - ["comment", "REM Foobar"], - ["comment", "rem foo^\r\nbar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/batch/label_feature.test b/docs/_style/prism-master/tests/languages/batch/label_feature.test deleted file mode 100644 index a672ba0c..00000000 --- a/docs/_style/prism-master/tests/languages/batch/label_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -:foo -:Foo_Bar - ----------------------------------------------------- - -[ - ["label", ":foo"], - ["label", ":Foo_Bar"] -] - ----------------------------------------------------- - -Checks for labels. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/c_feature.test b/docs/_style/prism-master/tests/languages/bison/c_feature.test deleted file mode 100644 index 4d8be839..00000000 --- a/docs/_style/prism-master/tests/languages/bison/c_feature.test +++ /dev/null @@ -1,56 +0,0 @@ -%{ - #include -%} -%code { - if(foo) { - - } -} -%% -exp: - NUM { - $$ = f($3, $4); - @$.first_column = @1.first_column; - $result = $left + $1; - } -%% - ----------------------------------------------------- - -[ - ["bison", [ - ["c", [ - ["delimiter", "%{"], - ["macro", ["#", ["directive", "include"], ["string", ""]]], - ["delimiter", "%}"] - ]], - ["keyword", "%code"], - ["c", [ - ["delimiter", "{"], - ["keyword", "if"], ["punctuation", "("], "foo", ["punctuation", ")"], - ["punctuation", "{"], ["punctuation", "}"], - ["delimiter", "}"] - ]], - ["punctuation", "%%"], - ["property", "exp"], ["punctuation", ":"], - "\r\n\tNUM ", - ["c", [ - ["delimiter", "{"], - ["bison-variable", ["$$"]], ["operator", "="], - ["function", "f"], ["punctuation", "("], - ["bison-variable", ["$3"]], ["punctuation", ","], - ["bison-variable", ["$4"]], ["punctuation", ")"], ["punctuation", ";"], - ["bison-variable", ["@$"]], ["punctuation", "."], "first_column ", ["operator", "="], - ["bison-variable", ["@1"]], ["punctuation", "."], "first_column", ["punctuation", ";"], - ["bison-variable", ["$result"]], ["operator", "="], - ["bison-variable", ["$left"]], ["operator", "+"], - ["bison-variable", ["$", ["punctuation", "<"], "itype", ["punctuation", ">"], "1"]], ["punctuation", ";"], - ["delimiter", "}"] - ]], - ["punctuation", "%%"] - ]] -] - ----------------------------------------------------- - -Checks for C inside Bison, along with special Bison variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/comment_feature.test b/docs/_style/prism-master/tests/languages/bison/comment_feature.test deleted file mode 100644 index 15ed152e..00000000 --- a/docs/_style/prism-master/tests/languages/bison/comment_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -// Foobar -/* Foo -bar */ -%% -// Foobar -/* Foo -bar */ -%% - ----------------------------------------------------- - -[ - ["bison", [ - ["comment", "// Foobar"], - ["comment", "/* Foo\r\nbar */"], - ["punctuation", "%%"], - ["comment", "// Foobar"], - ["comment", "/* Foo\r\nbar */"], - ["punctuation", "%%"] - ]] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/keyword_feature.test b/docs/_style/prism-master/tests/languages/bison/keyword_feature.test deleted file mode 100644 index 49fb3733..00000000 --- a/docs/_style/prism-master/tests/languages/bison/keyword_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -%union -%token -%% -exp: %empty -%% - ----------------------------------------------------- - -[ - ["bison", [ - ["keyword", "%union"], - ["keyword", "%token"], - ["punctuation", "%%"], - ["property", "exp"], ["punctuation", ":"], - ["keyword", "%empty"], - ["punctuation", "%%"] - ]] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/number_feature.test b/docs/_style/prism-master/tests/languages/bison/number_feature.test deleted file mode 100644 index ddbacff9..00000000 --- a/docs/_style/prism-master/tests/languages/bison/number_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -42 -0 -0xBadFace - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "0"], - ["number", "0xBadFace"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/property_feature.test b/docs/_style/prism-master/tests/languages/bison/property_feature.test deleted file mode 100644 index c1b6cb24..00000000 --- a/docs/_style/prism-master/tests/languages/bison/property_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -%% -foo: -bar_42: -$@1: -%% - ----------------------------------------------------- - -[ - ["bison", [ - ["punctuation", "%%"], - ["property", "foo"], ["punctuation", ":"], - ["property", "bar_42"], ["punctuation", ":"], - ["property", "$@1"], ["punctuation", ":"], - ["punctuation", "%%"] - ]] -] - ----------------------------------------------------- - -Checks for properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bison/string_feature.test b/docs/_style/prism-master/tests/languages/bison/string_feature.test deleted file mode 100644 index 3f3f6c0d..00000000 --- a/docs/_style/prism-master/tests/languages/bison/string_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -%% -foo: 'foo' "foo"; -bar: '\'' "\""; -%% - ----------------------------------------------------- - -[ - ["bison", [ - ["punctuation", "%%"], - ["property", "foo"], ["punctuation", ":"], - ["string", "'foo'"], ["string", "\"foo\""], ["punctuation", ";"], - ["property", "bar"], ["punctuation", ":"], - ["string", "'\\''"], ["string", "\"\\\"\""], ["punctuation", ";"], - ["punctuation", "%%"] - ]] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/brainfuck/all_feature.test b/docs/_style/prism-master/tests/languages/brainfuck/all_feature.test deleted file mode 100644 index 284f758a..00000000 --- a/docs/_style/prism-master/tests/languages/brainfuck/all_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -++ foobar -[ - >. - <-, -] - ----------------------------------------------------- - -[ - ["increment", "+"], ["increment", "+"], ["comment", "foobar"], - ["branching", "["], - ["pointer", ">"], ["operator", "."], - ["pointer", "<"], ["decrement", "-"], ["operator", ","], - ["branching", "]"] -] - ----------------------------------------------------- - -Checks for all patterns. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/builtin_feature.test b/docs/_style/prism-master/tests/languages/bro/builtin_feature.test deleted file mode 100644 index cd5a1210..00000000 --- a/docs/_style/prism-master/tests/languages/bro/builtin_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -@load-sigs -@load-plugin -@unload -@prefixes -@ifndef -@ifdef -@else -&redef -&priority -redef - ----------------------------------------------------- - -[ - ["builtin", "@load-sigs"], - ["builtin", "@load-plugin"], - ["builtin", "@unload"], - ["builtin", "@prefixes"], - ["builtin", "@ifndef"], - ["builtin", "@ifdef"], - ["builtin", "@else"], - ["builtin", "&redef"], - ["builtin", "&priority"], - ["builtin", "redef"] -] - ----------------------------------------------------- - -Checks for the builtins \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/comment_feature.test b/docs/_style/prism-master/tests/languages/bro/comment_feature.test deleted file mode 100644 index e1eff305..00000000 --- a/docs/_style/prism-master/tests/languages/bro/comment_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -## comment -#TODO -## FIXME -# XXX - ----------------------------------------------------- - -[ - ["comment", [ "## comment"]], - ["comment", [ "#", ["italic", "TODO"]]], - ["comment", [ "## ", ["italic", "FIXME"]]], - ["comment", [ "# ", ["italic", "XXX"]]] -] - ----------------------------------------------------- - -Checks for the highlighting of comments \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/function_feature.test b/docs/_style/prism-master/tests/languages/bro/function_feature.test deleted file mode 100644 index a10d792a..00000000 --- a/docs/_style/prism-master/tests/languages/bro/function_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -function foo -hook foo -event foo -function foo::bar -hook foo::bar -event foo::bar - ----------------------------------------------------- - -[ - ["function", [["keyword", "function"], " foo"]], - ["function", [["keyword", "hook"], " foo"]], - ["function", [["keyword", "event"], " foo"]], - ["function", [["keyword", "function"], " foo::bar"]], - ["function", [["keyword", "hook"], " foo::bar"]], - ["function", [["keyword", "event"], " foo::bar"]] -] - ----------------------------------------------------- - -Checks for the function feature \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/keyword_feature.test b/docs/_style/prism-master/tests/languages/bro/keyword_feature.test deleted file mode 100644 index aa025cd4..00000000 --- a/docs/_style/prism-master/tests/languages/bro/keyword_feature.test +++ /dev/null @@ -1,87 +0,0 @@ -break -next -continue -alarm -using -of -add -delete -export -print -return -schedule -when -timeout -addr -any -bool -count -double -enum -file -int -interval -pattern -opaque -port -record -set -string -subnet -table -time -vector -for -if -else -in -module -function - ----------------------------------------------------- - -[ - ["keyword", "break"], - ["keyword", "next"], - ["keyword", "continue"], - ["keyword", "alarm"], - ["keyword", "using"], - ["keyword", "of"], - ["keyword", "add"], - ["keyword", "delete"], - ["keyword", "export"], - ["keyword", "print"], - ["keyword", "return"], - ["keyword", "schedule"], - ["keyword", "when"], - ["keyword", "timeout"], - ["keyword", "addr"], - ["keyword", "any"], - ["keyword", "bool"], - ["keyword", "count"], - ["keyword", "double"], - ["keyword", "enum"], - ["keyword", "file"], - ["keyword", "int"], - ["keyword", "interval"], - ["keyword", "pattern"], - ["keyword", "opaque"], - ["keyword", "port"], - ["keyword", "record"], - ["keyword", "set"], - ["keyword", "string"], - ["keyword", "subnet"], - ["keyword", "table"], - ["keyword", "time"], - ["keyword", "vector"], - ["keyword", "for"], - ["keyword", "if"], - ["keyword", "else"], - ["keyword", "in"], - ["keyword", "module"], - ["keyword", "function"] -] - ----------------------------------------------------- - -Checks for the builtins \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/string_feature.test b/docs/_style/prism-master/tests/languages/bro/string_feature.test deleted file mode 100644 index 68277b51..00000000 --- a/docs/_style/prism-master/tests/languages/bro/string_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -"" -'' -"foo" -'foo' -"'foo'" -'"bar"' -" # comment " - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "''"], - ["string", "\"foo\""], - ["string", "'foo'"], - ["string", "\"'foo'\""], - ["string", "'\"bar\"'"], - ["string", "\" # comment \""] -] - ----------------------------------------------------- - -Checks for single-quoted and double-quoted strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/bro/variable_feature.test b/docs/_style/prism-master/tests/languages/bro/variable_feature.test deleted file mode 100644 index daf51203..00000000 --- a/docs/_style/prism-master/tests/languages/bro/variable_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -local foo -global foo -local bool = T; -const bar -local baz = 66; - ----------------------------------------------------- - -[ - ["variable", [["keyword", "local"], " foo"]], - ["variable", [["keyword", "global"], " foo"]], - ["variable", [["keyword", "local"], " bool"]], - ["operator", "="], - ["boolean", "T"], - ["punctuation", ";"], - ["constant", [["keyword", "const"], " bar"]], - ["variable", [["keyword", "local"], " baz"]], - ["operator", "="], - ["number", "66"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for the highlighting of variables \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c+pure/c_inclusion.test b/docs/_style/prism-master/tests/languages/c+pure/c_inclusion.test deleted file mode 100644 index 312fde2c..00000000 --- a/docs/_style/prism-master/tests/languages/c+pure/c_inclusion.test +++ /dev/null @@ -1,28 +0,0 @@ -%< -asm (); -%> - -%< -*- C -*- -asm (); -%> - ----------------------------------------------------- - -[ - ["inline-lang", [ - ["delimiter", "%<"], - ["keyword", "asm"], ["punctuation", "("], ["punctuation", ")"], ["punctuation", ";"], - ["delimiter", "%>"] - ]], - - ["inline-lang-c", [ - ["delimiter", "%< "], - ["lang", "-*- C -*-"], - ["keyword", "asm"], ["punctuation", "("], ["punctuation", ")"], ["punctuation", ";"], - ["delimiter", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for C in Pure. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c/constant_feature.test b/docs/_style/prism-master/tests/languages/c/constant_feature.test deleted file mode 100644 index 449305a7..00000000 --- a/docs/_style/prism-master/tests/languages/c/constant_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -__FILE__ -__LINE__ -__DATE__ -__TIME__ -__TIMESTAMP__ -__func__ -EOF -NULL -SEEK_CUR -SEEK_END -SEEK_SET -stdin -stdout -stderr - ----------------------------------------------------- - -[ - ["constant", "__FILE__"], - ["constant", "__LINE__"], - ["constant", "__DATE__"], - ["constant", "__TIME__"], - ["constant", "__TIMESTAMP__"], - ["constant", "__func__"], - ["constant", "EOF"], - ["constant", "NULL"], - ["constant", "SEEK_CUR"], - ["constant", "SEEK_END"], - ["constant", "SEEK_SET"], - ["constant", "stdin"], - ["constant", "stdout"], - ["constant", "stderr"] -] - ----------------------------------------------------- - -Checks for all constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c/keyword_feature.test b/docs/_style/prism-master/tests/languages/c/keyword_feature.test deleted file mode 100644 index feb9ff93..00000000 --- a/docs/_style/prism-master/tests/languages/c/keyword_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -_Alignas _Alignof _Atomic _Bool -_Complex _Generic _Imaginary -_Noreturn _Static_assert _Thread_local -asm typeof inline auto break -case char const continue default -do double else enum extern -float for goto if int -long register return short signed -sizeof static struct switch typedef -union unsigned void volatile while - ----------------------------------------------------- - -[ - ["keyword", "_Alignas"], ["keyword", "_Alignof"], ["keyword", "_Atomic"], ["keyword", "_Bool"], - ["keyword", "_Complex"], ["keyword", "_Generic"], ["keyword", "_Imaginary"], - ["keyword", "_Noreturn"], ["keyword", "_Static_assert"], ["keyword", "_Thread_local"], - ["keyword", "asm"], ["keyword", "typeof"], ["keyword", "inline"], ["keyword", "auto"], ["keyword", "break"], - ["keyword", "case"], ["keyword", "char"], ["keyword", "const"], ["keyword", "continue"], ["keyword", "default"], - ["keyword", "do"], ["keyword", "double"], ["keyword", "else"], ["keyword", "enum"], ["keyword", "extern"], - ["keyword", "float"], ["keyword", "for"], ["keyword", "goto"], ["keyword", "if"], ["keyword", "int"], - ["keyword", "long"], ["keyword", "register"], ["keyword", "return"], ["keyword", "short"], ["keyword", "signed"], - ["keyword", "sizeof"], ["keyword", "static"], ["keyword", "struct"], ["keyword", "switch"], ["keyword", "typedef"], - ["keyword", "union"], ["keyword", "unsigned"], ["keyword", "void"], ["keyword", "volatile"], ["keyword", "while"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c/macro_feature.test b/docs/_style/prism-master/tests/languages/c/macro_feature.test deleted file mode 100644 index 6e65d9d5..00000000 --- a/docs/_style/prism-master/tests/languages/c/macro_feature.test +++ /dev/null @@ -1,45 +0,0 @@ -# include - #define PG_locked 0 - -#defined -#elif -#else -#endif -#error -#ifdef -#ifndef -#if -#import -#include -#line -#pragma -#undef -#using - ----------------------------------------------------- - -[ - ["macro", [ - "# ", ["directive", "include"], - ["string", ""] - ]], - ["macro", ["#", ["directive", "define"], " PG_locked 0"]], - ["macro", ["#", ["directive", "defined"]]], - ["macro", ["#", ["directive", "elif"]]], - ["macro", ["#", ["directive", "else"]]], - ["macro", ["#", ["directive", "endif"]]], - ["macro", ["#", ["directive", "error"]]], - ["macro", ["#", ["directive", "ifdef"]]], - ["macro", ["#", ["directive", "ifndef"]]], - ["macro", ["#", ["directive", "if"]]], - ["macro", ["#", ["directive", "import"]]], - ["macro", ["#", ["directive", "include"]]], - ["macro", ["#", ["directive", "line"]]], - ["macro", ["#", ["directive", "pragma"]]], - ["macro", ["#", ["directive", "undef"]]], - ["macro", ["#", ["directive", "using"]]] -] - ----------------------------------------------------- - -Checks for macros and paths inside include statements. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c/number_feature.test b/docs/_style/prism-master/tests/languages/c/number_feature.test deleted file mode 100644 index 05426834..00000000 --- a/docs/_style/prism-master/tests/languages/c/number_feature.test +++ /dev/null @@ -1,35 +0,0 @@ -42 -3.14159 -4e10 -2.1e-10 -0.4e+2 -0xbabe -0xBABE -42f -42F -42u -42U -42l -42L - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "4e10"], - ["number", "2.1e-10"], - ["number", "0.4e+2"], - ["number", "0xbabe"], - ["number", "0xBABE"], - ["number", "42f"], - ["number", "42F"], - ["number", "42u"], - ["number", "42U"], - ["number", "42l"], - ["number", "42L"] -] - ----------------------------------------------------- - -Checks for decimal numbers and hexadecimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/c/operator_feature.test b/docs/_style/prism-master/tests/languages/c/operator_feature.test deleted file mode 100644 index 95cef646..00000000 --- a/docs/_style/prism-master/tests/languages/c/operator_feature.test +++ /dev/null @@ -1,61 +0,0 @@ -+ - * / % -- ++ ->> << -~ & | ^ -+= -= *= /= %= >>= <<= &= |= ^= -! && || --> :: -? : -= == != < > <= >= - ----------------------------------------------------- - -[ - ["operator", "+"], - ["operator", "-"], - ["operator", "*"], - ["operator", "/"], - ["operator", "%"], - ["operator", "--"], - ["operator", "++"], - - ["operator", ">>"], - ["operator", "<<"], - - ["operator", "~"], - ["operator", "&"], - ["operator", "|"], - ["operator", "^"], - - ["operator", "+="], - ["operator", "-="], - ["operator", "*="], - ["operator", "/="], - ["operator", "%="], - ["operator", ">>="], - ["operator", "<<="], - ["operator", "&="], - ["operator", "|="], - ["operator", "^="], - - ["operator", "!"], - ["operator", "&&"], - ["operator", "||"], - - ["operator", "->"], - ["operator", "::"], - - ["operator", "?"], - ["operator", ":"], - - ["operator", "="], - ["operator", "=="], - ["operator", "!="], - ["operator", "<"], - ["operator", ">"], - ["operator", "<="], - ["operator", ">="] -] - ----------------------------------------------------- - -Checks for all operators diff --git a/docs/_style/prism-master/tests/languages/cil/asm_reference.test b/docs/_style/prism-master/tests/languages/cil/asm_reference.test deleted file mode 100644 index 991f998c..00000000 --- a/docs/_style/prism-master/tests/languages/cil/asm_reference.test +++ /dev/null @@ -1,11 +0,0 @@ -[mscorlib] - --------------------------------------------------------- - -[ - [ "variable", "[mscorlib]" ] -] - --------------------------------------------------------- - -Tests assembly references diff --git a/docs/_style/prism-master/tests/languages/cil/boolean.test b/docs/_style/prism-master/tests/languages/cil/boolean.test deleted file mode 100644 index c6cfe746..00000000 --- a/docs/_style/prism-master/tests/languages/cil/boolean.test +++ /dev/null @@ -1,14 +0,0 @@ - -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Tests booleans. diff --git a/docs/_style/prism-master/tests/languages/cil/comment.test b/docs/_style/prism-master/tests/languages/cil/comment.test deleted file mode 100644 index 97a30a45..00000000 --- a/docs/_style/prism-master/tests/languages/cil/comment.test +++ /dev/null @@ -1,11 +0,0 @@ -// this is a test - --------------------------------------------------------- - -[ - [ "comment", "// this is a test" ] -] - --------------------------------------------------------- - -Tests comments diff --git a/docs/_style/prism-master/tests/languages/cil/instructions.test b/docs/_style/prism-master/tests/languages/cil/instructions.test deleted file mode 100644 index fc01826c..00000000 --- a/docs/_style/prism-master/tests/languages/cil/instructions.test +++ /dev/null @@ -1,457 +0,0 @@ -add -add.ovf -add.ovf.un -and -arglist -beq -beq.s -bge -bge.s -bge.un -bge.un.s -bgt -bgt.s -bgt.un -bgt.un.s -ble -ble.s -ble.un -ble.un.s -blt -blt.s -blt.un -blt.un.s -bne.un -bne.un.s -box -br -br.s -break -brfalse -brfalse.s -brinst -brinst.s -brnull -brnull.s -brtrue -brtrue.s -brzero -brzero.s -call -calli -callvirt -castclass -ceq -cgt -cgt.un -ckfinite -clt -clt.un -conv.i -conv.i1 -conv.i2 -conv.i4 -conv.i8 -conv.ovf.i -conv.ovf.i.un -conv.ovf.i1 -conv.ovf.i1.un -conv.ovf.i2 -conv.ovf.i2.un -conv.ovf.i4 -conv.ovf.i4.un -conv.ovf.i8 -conv.ovf.i8.un -conv.ovf.u -conv.ovf.u.un -conv.ovf.u1 -conv.ovf.u1.un -conv.ovf.u2 -conv.ovf.u2.un -conv.ovf.u4 -conv.ovf.u4.un -conv.ovf.u8 -conv.ovf.u8.un -conv.r.un -conv.r4 -conv.r8 -conv.u -conv.u1 -conv.u2 -conv.u4 -conv.u8 -cpblk -cpobj -div -div.un -dup -endfault -endfilter -endfinally -initblk -initobj -isinst -jmp -ldarg -ldarg.0 -ldarg.1 -ldarg.2 -ldarg.3 -ldarg.s -ldarga -ldarga.s -ldc.i4 -ldc.i4.0 -ldc.i4.1 -ldc.i4.2 -ldc.i4.3 -ldc.i4.4 -ldc.i4.5 -ldc.i4.6 -ldc.i4.7 -ldc.i4.8 -ldc.i4.m1 -ldc.i4.M1 -ldc.i4.s -ldc.i8 -ldc.r4 -ldc.r8 -ldelem -ldelem.i -ldelem.i1 -ldelem.i2 -ldelem.i4 -ldelem.i8 -ldelem.r4 -ldelem.r8 -ldelem.ref -ldelem.u1 -ldelem.u2 -ldelem.u4 -ldelem.u8 -ldelema -ldfld -ldflda -ldftn -ldind.i -ldind.i1 -ldind.i2 -ldind.i4 -ldind.i8 -ldind.r4 -ldind.r8 -ldind.ref -ldind.u1 -ldind.u2 -ldind.u4 -ldind.u8 -ldlen -ldloc -ldloc.0 -ldloc.1 -ldloc.2 -ldloc.3 -ldloc.s -ldloca -ldloca.s -ldnull -ldobj -ldsfld -ldsflda -ldstr -ldtoken -ldvirtftn -leave -leave.s -localloc -mkrefany -mul -mul.ovf -mul.ovf.un -neg -newarr -newobj -nop -not -or -pop -refanytype -refanyval -rem -rem.un -ret -rethrow -shl -shr -shr.un -sizeof -starg -starg.s -stelem -stelem.i -stelem.i1 -stelem.i2 -stelem.i4 -stelem.i8 -stelem.r4 -stelem.r8 -stelem.ref -stfld -stind.i -stind.i1 -stind.i2 -stind.i4 -stind.i8 -stind.r4 -stind.r8 -stind.ref -stloc -stloc.0 -stloc.1 -stloc.2 -stloc.3 -stloc.s -stobj -stsfld -sub -sub.ovf -sub.ovf.un -switch -throw -alignment -unbox -unbox.any -xor - ----------------------------------------------------- - -[ - ["function", "add"], - ["function", "add.ovf"], - ["function", "add.ovf.un"], - ["function", "and"], - ["function", "arglist"], - ["function", "beq"], - ["function", "beq.s"], - ["function", "bge"], - ["function", "bge.s"], - ["function", "bge.un"], - ["function", "bge.un.s"], - ["function", "bgt"], - ["function", "bgt.s"], - ["function", "bgt.un"], - ["function", "bgt.un.s"], - ["function", "ble"], - ["function", "ble.s"], - ["function", "ble.un"], - ["function", "ble.un.s"], - ["function", "blt"], - ["function", "blt.s"], - ["function", "blt.un"], - ["function", "blt.un.s"], - ["function", "bne.un"], - ["function", "bne.un.s"], - ["function", "box"], - ["function", "br"], - ["function", "br.s"], - ["function", "break"], - ["function", "brfalse"], - ["function", "brfalse.s"], - ["function", "brinst"], - ["function", "brinst.s"], - ["function", "brnull"], - ["function", "brnull.s"], - ["function", "brtrue"], - ["function", "brtrue.s"], - ["function", "brzero"], - ["function", "brzero.s"], - ["function", "call"], - ["function", "calli"], - ["function", "callvirt"], - ["function", "castclass"], - ["function", "ceq"], - ["function", "cgt"], - ["function", "cgt.un"], - ["function", "ckfinite"], - ["function", "clt"], - ["function", "clt.un"], - ["function", "conv.i"], - ["function", "conv.i1"], - ["function", "conv.i2"], - ["function", "conv.i4"], - ["function", "conv.i8"], - ["function", "conv.ovf.i"], - ["function", "conv.ovf.i.un"], - ["function", "conv.ovf.i1"], - ["function", "conv.ovf.i1.un"], - ["function", "conv.ovf.i2"], - ["function", "conv.ovf.i2.un"], - ["function", "conv.ovf.i4"], - ["function", "conv.ovf.i4.un"], - ["function", "conv.ovf.i8"], - ["function", "conv.ovf.i8.un"], - ["function", "conv.ovf.u"], - ["function", "conv.ovf.u.un"], - ["function", "conv.ovf.u1"], - ["function", "conv.ovf.u1.un"], - ["function", "conv.ovf.u2"], - ["function", "conv.ovf.u2.un"], - ["function", "conv.ovf.u4"], - ["function", "conv.ovf.u4.un"], - ["function", "conv.ovf.u8"], - ["function", "conv.ovf.u8.un"], - ["function", "conv.r.un"], - ["function", "conv.r4"], - ["function", "conv.r8"], - ["function", "conv.u"], - ["function", "conv.u1"], - ["function", "conv.u2"], - ["function", "conv.u4"], - ["function", "conv.u8"], - ["function", "cpblk"], - ["function", "cpobj"], - ["function", "div"], - ["function", "div.un"], - ["function", "dup"], - ["function", "endfault"], - ["function", "endfilter"], - ["function", "endfinally"], - ["function", "initblk"], - ["function", "initobj"], - ["function", "isinst"], - ["function", "jmp"], - ["function", "ldarg"], - ["function", "ldarg.0"], - ["function", "ldarg.1"], - ["function", "ldarg.2"], - ["function", "ldarg.3"], - ["function", "ldarg.s"], - ["function", "ldarga"], - ["function", "ldarga.s"], - ["function", "ldc.i4"], - ["function", "ldc.i4.0"], - ["function", "ldc.i4.1"], - ["function", "ldc.i4.2"], - ["function", "ldc.i4.3"], - ["function", "ldc.i4.4"], - ["function", "ldc.i4.5"], - ["function", "ldc.i4.6"], - ["function", "ldc.i4.7"], - ["function", "ldc.i4.8"], - ["function", "ldc.i4.m1"], - ["function", "ldc.i4.M1"], - ["function", "ldc.i4.s"], - ["function", "ldc.i8"], - ["function", "ldc.r4"], - ["function", "ldc.r8"], - ["function", "ldelem"], - ["function", "ldelem.i"], - ["function", "ldelem.i1"], - ["function", "ldelem.i2"], - ["function", "ldelem.i4"], - ["function", "ldelem.i8"], - ["function", "ldelem.r4"], - ["function", "ldelem.r8"], - ["function", "ldelem.ref"], - ["function", "ldelem.u1"], - ["function", "ldelem.u2"], - ["function", "ldelem.u4"], - ["function", "ldelem.u8"], - ["function", "ldelema"], - ["function", "ldfld"], - ["function", "ldflda"], - ["function", "ldftn"], - ["function", "ldind.i"], - ["function", "ldind.i1"], - ["function", "ldind.i2"], - ["function", "ldind.i4"], - ["function", "ldind.i8"], - ["function", "ldind.r4"], - ["function", "ldind.r8"], - ["function", "ldind.ref"], - ["function", "ldind.u1"], - ["function", "ldind.u2"], - ["function", "ldind.u4"], - ["function", "ldind.u8"], - ["function", "ldlen"], - ["function", "ldloc"], - ["function", "ldloc.0"], - ["function", "ldloc.1"], - ["function", "ldloc.2"], - ["function", "ldloc.3"], - ["function", "ldloc.s"], - ["function", "ldloca"], - ["function", "ldloca.s"], - ["function", "ldnull"], - ["function", "ldobj"], - ["function", "ldsfld"], - ["function", "ldsflda"], - ["function", "ldstr"], - ["function", "ldtoken"], - ["function", "ldvirtftn"], - ["function", "leave"], - ["function", "leave.s"], - ["function", "localloc"], - ["function", "mkrefany"], - ["function", "mul"], - ["function", "mul.ovf"], - ["function", "mul.ovf.un"], - ["function", "neg"], - ["function", "newarr"], - ["function", "newobj"], - ["function", "nop"], - ["function", "not"], - ["function", "or"], - ["function", "pop"], - ["function", "refanytype"], - ["function", "refanyval"], - ["function", "rem"], - ["function", "rem.un"], - ["function", "ret"], - ["function", "rethrow"], - ["function", "shl"], - ["function", "shr"], - ["function", "shr.un"], - ["function", "sizeof"], - ["function", "starg"], - ["function", "starg.s"], - ["function", "stelem"], - ["function", "stelem.i"], - ["function", "stelem.i1"], - ["function", "stelem.i2"], - ["function", "stelem.i4"], - ["function", "stelem.i8"], - ["function", "stelem.r4"], - ["function", "stelem.r8"], - ["function", "stelem.ref"], - ["function", "stfld"], - ["function", "stind.i"], - ["function", "stind.i1"], - ["function", "stind.i2"], - ["function", "stind.i4"], - ["function", "stind.i8"], - ["function", "stind.r4"], - ["function", "stind.r8"], - ["function", "stind.ref"], - ["function", "stloc"], - ["function", "stloc.0"], - ["function", "stloc.1"], - ["function", "stloc.2"], - ["function", "stloc.3"], - ["function", "stloc.s"], - ["function", "stobj"], - ["function", "stsfld"], - ["function", "sub"], - ["function", "sub.ovf"], - ["function", "sub.ovf.un"], - ["function", "switch"], - ["function", "throw"], - ["function", "alignment"], - ["function", "unbox"], - ["function", "unbox.any"], - ["function", "xor"] -] - ----------------------------------------------------- - -Tests instruction names diff --git a/docs/_style/prism-master/tests/languages/cil/keywords.test b/docs/_style/prism-master/tests/languages/cil/keywords.test deleted file mode 100644 index 53a4645a..00000000 --- a/docs/_style/prism-master/tests/languages/cil/keywords.test +++ /dev/null @@ -1,161 +0,0 @@ -abstract -ansi -assembly -auto -autochar -beforefieldinit -bool -bstr -byvalstr -cil -char -class -currency -date -decimal -default -enum -error -explicit -extends -extern -famandassem -family -famorassem -final -float32 -float64 -hidebysig -iant -idispatch -import -initonly -instance -int -int16 -int32 -int64 -int8 -uint -uint16 -uint32 -uint64 -uint8 -interface -iunknown -lpstr -lpstruct -lptstr -lpwstr -managed -nativeType -nested -newslot -objectref -pinvokeimpl -private -privatescope -public -reqsecobj -rtspecialname -sealed -sequential -serializable -specialname -static -string -struct -syschar -tbstr -unicode -unmanagedexp -unsigned -value -variant -virtual -void - --------------------------------------------------------- - -[ - [ "keyword", "abstract" ], - [ "keyword", "ansi" ], - [ "keyword", "assembly" ], - [ "keyword", "auto" ], - [ "keyword", "autochar" ], - [ "keyword", "beforefieldinit" ], - [ "keyword", "bool" ], - [ "keyword", "bstr" ], - [ "keyword", "byvalstr" ], - [ "keyword", "cil" ], - [ "keyword", "char" ], - [ "keyword", "class" ], - [ "keyword", "currency" ], - [ "keyword", "date" ], - [ "keyword", "decimal" ], - [ "keyword", "default" ], - [ "keyword", "enum" ], - [ "keyword", "error" ], - [ "keyword", "explicit" ], - [ "keyword", "extends" ], - [ "keyword", "extern" ], - [ "keyword", "famandassem" ], - [ "keyword", "family" ], - [ "keyword", "famorassem" ], - [ "keyword", "final" ], - [ "keyword", "float32" ], - [ "keyword", "float64" ], - [ "keyword", "hidebysig" ], - [ "keyword", "iant" ], - [ "keyword", "idispatch" ], - [ "keyword", "import" ], - [ "keyword", "initonly" ], - [ "keyword", "instance" ], - [ "keyword", "int" ], - [ "keyword", "int16" ], - [ "keyword", "int32" ], - [ "keyword", "int64" ], - [ "keyword", "int8" ], - [ "keyword", "uint" ], - [ "keyword", "uint16" ], - [ "keyword", "uint32" ], - [ "keyword", "uint64" ], - [ "keyword", "uint8" ], - [ "keyword", "interface" ], - [ "keyword", "iunknown" ], - [ "keyword", "lpstr" ], - [ "keyword", "lpstruct" ], - [ "keyword", "lptstr" ], - [ "keyword", "lpwstr" ], - [ "keyword", "managed" ], - [ "keyword", "nativeType" ], - [ "keyword", "nested" ], - [ "keyword", "newslot" ], - [ "keyword", "objectref" ], - [ "keyword", "pinvokeimpl" ], - [ "keyword", "private" ], - [ "keyword", "privatescope" ], - [ "keyword", "public" ], - [ "keyword", "reqsecobj" ], - [ "keyword", "rtspecialname" ], - [ "keyword", "sealed" ], - [ "keyword", "sequential" ], - [ "keyword", "serializable" ], - [ "keyword", "specialname" ], - [ "keyword", "static" ], - [ "keyword", "string" ], - [ "keyword", "struct" ], - [ "keyword", "syschar" ], - [ "keyword", "tbstr" ], - [ "keyword", "unicode" ], - [ "keyword", "unmanagedexp" ], - [ "keyword", "unsigned" ], - [ "keyword", "value" ], - [ "keyword", "variant" ], - [ "keyword", "virtual" ], - [ "keyword", "void"] -] - --------------------------------------------------------- - -Tests keywords diff --git a/docs/_style/prism-master/tests/languages/cil/strings.test b/docs/_style/prism-master/tests/languages/cil/strings.test deleted file mode 100644 index da2de2c7..00000000 --- a/docs/_style/prism-master/tests/languages/cil/strings.test +++ /dev/null @@ -1,11 +0,0 @@ -"testing! £$%^&*" - ----------------------------------------------------- - -[ - ["string", "\"testing! £$%^&*\""] -] - ----------------------------------------------------- - -Tests strings. diff --git a/docs/_style/prism-master/tests/languages/clike/boolean_feature.test b/docs/_style/prism-master/tests/languages/clike/boolean_feature.test deleted file mode 100644 index 6a1ad0ca..00000000 --- a/docs/_style/prism-master/tests/languages/clike/boolean_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -true; false; - ----------------------------------------------------- - -[ - ["boolean", "true"], ["punctuation", ";"], - ["boolean", "false"], ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/class-name_feature.test b/docs/_style/prism-master/tests/languages/clike/class-name_feature.test deleted file mode 100644 index 04c21125..00000000 --- a/docs/_style/prism-master/tests/languages/clike/class-name_feature.test +++ /dev/null @@ -1,53 +0,0 @@ -class Foo -interface bar -extends Foo -implements bar -trait Foo -instanceof \bar -new \Foo -catch (bar) - ----------------------------------------------------- - -[ - "class ", - ["class-name", [ - "Foo" - ]], - "\r\ninterface ", - ["class-name", [ - "bar" - ]], - "\r\nextends ", - ["class-name", [ - "Foo" - ]], - "\r\nimplements ", - ["class-name", [ - "bar" - ]], - "\r\ntrait ", - ["class-name", [ - "Foo" - ]], - ["keyword", "instanceof"], - ["class-name", [ - ["punctuation", "\\"], - "bar" - ]], - ["keyword", "new"], - ["class-name", [ - ["punctuation", "\\"], - "Foo" - ]], - ["keyword", "catch"], - ["punctuation", "("], - ["class-name", [ - "bar" - ]], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for class names. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/comment_feature.test b/docs/_style/prism-master/tests/languages/clike/comment_feature.test deleted file mode 100644 index 00bb2894..00000000 --- a/docs/_style/prism-master/tests/languages/clike/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -// foobar -/**/ -/* foo -bar */ - ----------------------------------------------------- - -[ - ["comment", "// foobar"], - ["comment", "/**/"], - ["comment", "/* foo\r\nbar */"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/function_feature.test b/docs/_style/prism-master/tests/languages/clike/function_feature.test deleted file mode 100644 index 031ed1c9..00000000 --- a/docs/_style/prism-master/tests/languages/clike/function_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -foo() -foo_bar() -f42() - ----------------------------------------------------- - -[ - ["function", "foo"], - ["punctuation", "("], - ["punctuation", ")"], - - ["function", "foo_bar"], - ["punctuation", "("], - ["punctuation", ")"], - - ["function", "f42"], - ["punctuation", "("], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/issue1340.test b/docs/_style/prism-master/tests/languages/clike/issue1340.test deleted file mode 100644 index 0b5effa0..00000000 --- a/docs/_style/prism-master/tests/languages/clike/issue1340.test +++ /dev/null @@ -1,13 +0,0 @@ -/* -// -*/ - ----------------------------------------------------- - -[ - ["comment", "/*\r\n//\r\n*/"] -] - ----------------------------------------------------- - -Non-regression test for inline comments inside multiline comments. See #1340 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/keyword_feature.test b/docs/_style/prism-master/tests/languages/clike/keyword_feature.test deleted file mode 100644 index 4313f7c9..00000000 --- a/docs/_style/prism-master/tests/languages/clike/keyword_feature.test +++ /dev/null @@ -1,30 +0,0 @@ -if; else; while; do; for; -return; in; instanceof; function; new; -try; throw; catch; finally; null; -break; continue; - ----------------------------------------------------- - -[ - ["keyword", "if"], ["punctuation", ";"], - ["keyword", "else"], ["punctuation", ";"], - ["keyword", "while"], ["punctuation", ";"], - ["keyword", "do"], ["punctuation", ";"], - ["keyword", "for"], ["punctuation", ";"], - ["keyword", "return"], ["punctuation", ";"], - ["keyword", "in"], ["punctuation", ";"], - ["keyword", "instanceof"], ["punctuation", ";"], - ["keyword", "function"], ["punctuation", ";"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "try"], ["punctuation", ";"], - ["keyword", "throw"], ["punctuation", ";"], - ["keyword", "catch"], ["punctuation", ";"], - ["keyword", "finally"], ["punctuation", ";"], - ["keyword", "null"], ["punctuation", ";"], - ["keyword", "break"], ["punctuation", ";"], - ["keyword", "continue"], ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/number_feature.test b/docs/_style/prism-master/tests/languages/clike/number_feature.test deleted file mode 100644 index f6bcc0b6..00000000 --- a/docs/_style/prism-master/tests/languages/clike/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -42 -3.14159 -4e10 -2.1e-10 -0.4e+2 -0xbabe -0xBABE - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "4e10"], - ["number", "2.1e-10"], - ["number", "0.4e+2"], - ["number", "0xbabe"], - ["number", "0xBABE"] -] - ----------------------------------------------------- - -Checks for decimal numbers and hexadecimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/operator_feature.test b/docs/_style/prism-master/tests/languages/clike/operator_feature.test deleted file mode 100644 index 1a4c6495..00000000 --- a/docs/_style/prism-master/tests/languages/clike/operator_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -- + -- ++ -< <= > >= -= == === -! != !== -& && | || -? * / ~ ^ % - ----------------------------------------------------- - -[ - ["operator", "-"], ["operator", "+"], ["operator", "--"], ["operator", "++"], - ["operator", "<"], ["operator", "<="], ["operator", ">"], ["operator", ">="], - ["operator", "="], ["operator", "=="], ["operator", "==="], - ["operator", "!"], ["operator", "!="], ["operator", "!=="], - ["operator", "&"], ["operator", "&&"], ["operator", "|"], ["operator", "||"], - ["operator", "?"], ["operator", "*"], ["operator", "/"], ["operator", "~"], ["operator", "^"], ["operator", "%"] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clike/string_feature.test b/docs/_style/prism-master/tests/languages/clike/string_feature.test deleted file mode 100644 index 0e347c90..00000000 --- a/docs/_style/prism-master/tests/languages/clike/string_feature.test +++ /dev/null @@ -1,31 +0,0 @@ -"" -'' -"f\"oo" -'b\'ar' -"foo\ -bar" -'foo\ -bar' -"foo /* comment */ bar" -'foo // bar' -'foo // bar' //comment - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "''"], - ["string", "\"f\\\"oo\""], - ["string", "'b\\'ar'"], - ["string", "\"foo\\\r\nbar\""], - ["string", "'foo\\\r\nbar'"], - ["string", "\"foo /* comment */ bar\""], - ["string", "'foo // bar'"], - ["string", "'foo // bar'"], - ["comment", "//comment"] -] - ----------------------------------------------------- - -Checks for empty strings, single-line strings and -multi-line strings, both single-quoted and double-quoted. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clojure/boolean_feature.test b/docs/_style/prism-master/tests/languages/clojure/boolean_feature.test deleted file mode 100644 index 55720b4f..00000000 --- a/docs/_style/prism-master/tests/languages/clojure/boolean_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -false -true -nil - ----------------------------------------------------- - -[ - ["boolean", "false"], - ["boolean", "true"], - ["boolean", "nil"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clojure/comment_feature.test b/docs/_style/prism-master/tests/languages/clojure/comment_feature.test deleted file mode 100644 index c4f5c7d1..00000000 --- a/docs/_style/prism-master/tests/languages/clojure/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -;; this is comment -; this is another comment - ----------------------------------------------------- - -[ - ["comment", ";; this is comment"], - ["comment", "; this is another comment"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clojure/keyword_feature.test b/docs/_style/prism-master/tests/languages/clojure/keyword_feature.test deleted file mode 100644 index 0c32cd2d..00000000 --- a/docs/_style/prism-master/tests/languages/clojure/keyword_feature.test +++ /dev/null @@ -1,175 +0,0 @@ -(def) -(if) -(do) -(let) -(var) -(fn) -(quote) -(->>) -(->) -(loop) -(recur) -(throw) -(try) -(monitor-enter) -(.) -(new) -(set!) -(defn) -(defn-) -(defmacro) -(defmulti) -(defmethod) -(defstruct) -(defonce) -(declare) -(definline) -(definterface) -(defprotocol) -(defrecord) -(deftype) -(defproject) -(ns) -(*) -(+) -(-) -(/) -(<) -(<=) -(>=) -(=) -(==) -(..) -(>) -(accessor) -(agent) -(agent-errors) -(aget) -(alength) -(all-ns) -(alter) -(and) -(append-child) -(apply) -(array-map) -(aset) -(aset-boolean) -(aset-byte) -(aset-char) -(aset-double) -(aset-float) -(aset-int) -(aset-long) -(aset-short) -(assert) -(assoc) -(await) -(await-for) -(bean) -(binding) -(bit-and) -(bit-not) -(branch?) -(contains?) -(end?) -(every?) -(false?) -(identical?) -(instance?) -(keyword?) -(list*) -(not-any?) -(true?) -(zero?) - ----------------------------------------------------- - -[ - ["punctuation", "("], ["keyword", "def"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "if"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "do"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "let"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "var"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "fn"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "quote"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "->>"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "->"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "loop"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "recur"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "throw"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "try"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "monitor-enter"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "."], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "new"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "set!"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defn"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defn-"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defmacro"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defmulti"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defmethod"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defstruct"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defonce"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "declare"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "definline"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "definterface"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defprotocol"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defrecord"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "deftype"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "defproject"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "ns"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "*"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "+"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "-"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "/"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "<"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "<="], ["punctuation", ")"], - ["punctuation", "("], ["keyword", ">="], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "="], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "=="], ["punctuation", ")"], - ["punctuation", "("], ["keyword", ".."], ["punctuation", ")"], - ["punctuation", "("], ["keyword", ">"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "accessor"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "agent"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "agent-errors"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aget"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "alength"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "all-ns"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "alter"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "and"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "append-child"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "apply"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "array-map"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-boolean"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-byte"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-char"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-double"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-float"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-int"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-long"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "aset-short"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "assert"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "assoc"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "await"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "await-for"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "bean"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "binding"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "bit-and"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "bit-not"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "branch?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "contains?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "end?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "every?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "false?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "identical?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "instance?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "keyword?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "list*"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "not-any?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "true?"], ["punctuation", ")"], - ["punctuation", "("], ["keyword", "zero?"], ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for keywords. diff --git a/docs/_style/prism-master/tests/languages/clojure/operator_and_punctuation.test b/docs/_style/prism-master/tests/languages/clojure/operator_and_punctuation.test deleted file mode 100644 index e90acb63..00000000 --- a/docs/_style/prism-master/tests/languages/clojure/operator_and_punctuation.test +++ /dev/null @@ -1,20 +0,0 @@ -(::example [x y] (:kebab-case x y)) - ----------------------------------------------------- - -[ - ["punctuation", "("], - ["operator", "::example"], - ["punctuation", "["], - "x y", - ["punctuation", "]"], - ["punctuation", "("], - ["operator", ":kebab-case"], - " x y", - ["punctuation", ")"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for operators and punctuation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/clojure/string_feature.test b/docs/_style/prism-master/tests/languages/clojure/string_feature.test deleted file mode 100644 index 13a966cf..00000000 --- a/docs/_style/prism-master/tests/languages/clojure/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"Fo\"obar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"Fo\\\"obar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript+haml/coffeescript_inclusion.test b/docs/_style/prism-master/tests/languages/coffeescript+haml/coffeescript_inclusion.test deleted file mode 100644 index 7a775169..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript+haml/coffeescript_inclusion.test +++ /dev/null @@ -1,24 +0,0 @@ -:coffee - 'This is coffee script' - -~ - :coffee - 'This is coffee script' - ----------------------------------------------------- - -[ - ["filter-coffee", [ - ["filter-name", ":coffee"], - ["string", "'This is coffee script'"] - ]], - ["punctuation", "~"], - ["filter-coffee", [ - ["filter-name", ":coffee"], - ["string", "'This is coffee script'"] - ]] -] - ----------------------------------------------------- - -Checks for CoffeeScript filter in Haml. The tilde serves only as a separator. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript+pug/coffeescript_inclusion.test b/docs/_style/prism-master/tests/languages/coffeescript+pug/coffeescript_inclusion.test deleted file mode 100644 index ccf8963b..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript+pug/coffeescript_inclusion.test +++ /dev/null @@ -1,19 +0,0 @@ -:coffee - "#{foo}" - ----------------------------------------------------- - -[ - ["filter-coffee", [ - ["filter-name", ":coffee"], - ["string", [ - "\"", - ["interpolation", "#{foo}"], - "\"" - ]] - ]] -] - ----------------------------------------------------- - -Checks for coffee filter (CoffeeScript) in Jade. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/block-regex_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/block-regex_feature.test deleted file mode 100644 index b01a9a42..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/block-regex_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -///foo[bar]/// -///foo -[bar]/// -///foo -b#{ar}baz/// -///foo #bar -baz/// - ----------------------------------------------------- - -[ - ["block-regex", [ - "///foo[bar]///" - ]], - ["block-regex", [ - "///foo\r\n[bar]///" - ]], - ["block-regex", [ - "///foo\r\nb", - ["interpolation", "#{ar}"], - "baz///" - ]], - ["block-regex", [ - "///foo ", - ["comment", "#bar"], - "\r\nbaz///" - ]] -] - ----------------------------------------------------- - -Checks for block regex. -Also checks for comments and interpolations inside block regex. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/class-member_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/class-member_feature.test deleted file mode 100644 index 6fdbcf3e..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/class-member_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -@name -@foo_bar - ----------------------------------------------------- - -[ - ["class-member", "@name"], - ["class-member", "@foo_bar"] -] - ----------------------------------------------------- - -Checks for class members. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/comment_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/comment_feature.test deleted file mode 100644 index 5d034ff0..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -#foo -# foo bar -### foo bar -baz ### - ----------------------------------------------------- - -[ - ["comment", "#foo"], - ["comment", "# foo bar"], - ["multiline-comment", "### foo bar\r\nbaz ###"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/inline-javascript_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/inline-javascript_feature.test deleted file mode 100644 index cf15e26c..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/inline-javascript_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -`/* JS here */` -`/* -JS here */` - ----------------------------------------------------- - -[ - ["inline-javascript", [ - ["delimiter", "`"], - ["comment", "/* JS here */"], - ["delimiter", "`"] - ]], - ["inline-javascript", [ - ["delimiter", "`"], - ["comment", "/*\r\nJS here */"], - ["delimiter", "`"] - ]] -] - ----------------------------------------------------- - -Checks for inline JavaScript. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/keyword_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/keyword_feature.test deleted file mode 100644 index 3207e398..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/keyword_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -and break by catch -class; -continue debugger delete -do each else extend -extends; -false finally for if in -instanceof; -is isnt let -loop namespace -new; -no not null of off on -or own return super -switch then this throw -true try typeof undefined -unless until when while -window with yes yield - ----------------------------------------------------- - -[ - ["keyword", "and"], ["keyword", "break"], ["keyword", "by"], ["keyword", "catch"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "continue"], ["keyword", "debugger"], ["keyword", "delete"], - ["keyword", "do"], ["keyword", "each"], ["keyword", "else"], ["keyword", "extend"], - ["keyword", "extends"], ["punctuation", ";"], - ["keyword", "false"], ["keyword", "finally"], ["keyword", "for"], ["keyword", "if"], ["keyword", "in"], - ["keyword", "instanceof"], ["punctuation", ";"], - ["keyword", "is"], ["keyword", "isnt"], ["keyword", "let"], - ["keyword", "loop"], ["keyword", "namespace"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "no"], ["keyword", "not"], ["keyword", "null"], ["keyword", "of"], ["keyword", "off"], ["keyword", "on"], - ["keyword", "or"], ["keyword", "own"], ["keyword", "return"], ["keyword", "super"], - ["keyword", "switch"], ["keyword", "then"], ["keyword", "this"], ["keyword", "throw"], - ["keyword", "true"], ["keyword", "try"], ["keyword", "typeof"], ["keyword", "undefined"], - ["keyword", "unless"], ["keyword", "until"], ["keyword", "when"], ["keyword", "while"], - ["keyword", "window"], ["keyword", "with"], ["keyword", "yes"], ["keyword", "yield"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/property_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/property_feature.test deleted file mode 100644 index 0f256798..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/property_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo: -foo_bar : - ----------------------------------------------------- - -[ - ["property", "foo"], - ["punctuation", ":"], - ["property", "foo_bar"], - ["punctuation", ":"] -] - ----------------------------------------------------- - -Checks for object properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/coffeescript/string_feature.test b/docs/_style/prism-master/tests/languages/coffeescript/string_feature.test deleted file mode 100644 index f5a68685..00000000 --- a/docs/_style/prism-master/tests/languages/coffeescript/string_feature.test +++ /dev/null @@ -1,64 +0,0 @@ -'' -'foo' -'foo\ -bar' -"" -"foo" -"foo\ -bar" -"foo #{interpolation} bar" -'''''' -'''foo''' -'''foo -bar''' - -"""""" -"""foo""" -"""foo -bar""" -"""foo #{interpolation} bar""" -"foo # comment bar" -'foo # bar' -"""foo -#comment -bar""" -'''foo -#comment -bar''' - ----------------------------------------------------- - -[ - ["string", "''"], - ["string", "'foo'"], - ["string", "'foo\\\r\nbar'"], - ["string", ["\"\""]], - ["string", ["\"foo\""]], - ["string", ["\"foo\\\r\nbar\""]], - ["string", [ - "\"foo ", - ["interpolation", "#{interpolation}"], - " bar\"" - ]], - - ["multiline-string", "''''''"], - ["multiline-string", "'''foo'''"], - ["multiline-string", "'''foo\r\nbar'''"], - ["multiline-string", ["\"\"\"\"\"\""]], - ["multiline-string", ["\"\"\"foo\"\"\""]], - ["multiline-string", ["\"\"\"foo\r\nbar\"\"\""]], - ["multiline-string", [ - "\"\"\"foo ", - ["interpolation", "#{interpolation}"], - " bar\"\"\"" - ]], - ["string", ["\"foo # comment bar\""]], - ["string", "'foo # bar'"], - ["multiline-string", ["\"\"\"foo\r\n#comment\r\nbar\"\"\""]], - ["multiline-string", "'''foo\r\n#comment\r\nbar'''"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line strings and block strings. -Also checks for string interpolation inside double-quoted strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/cpp+pure/cpp_inclusion.test b/docs/_style/prism-master/tests/languages/cpp+pure/cpp_inclusion.test deleted file mode 100644 index 150080e4..00000000 --- a/docs/_style/prism-master/tests/languages/cpp+pure/cpp_inclusion.test +++ /dev/null @@ -1,18 +0,0 @@ -%< -*- C++ -*- -alignas -%> - ----------------------------------------------------- - -[ - ["inline-lang-cpp", [ - ["delimiter", "%< "], - ["lang", "-*- C++ -*-"], - ["keyword", "alignas"], - ["delimiter", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for C++ in Pure. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/cpp/boolean_feature.test b/docs/_style/prism-master/tests/languages/cpp/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/cpp/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/cpp/class-name_feature.test b/docs/_style/prism-master/tests/languages/cpp/class-name_feature.test deleted file mode 100644 index f3d5df5f..00000000 --- a/docs/_style/prism-master/tests/languages/cpp/class-name_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -class Foo -class Foo_bar - ----------------------------------------------------- - -[ - ["keyword", "class"], ["class-name", "Foo"], - ["keyword", "class"], ["class-name", "Foo_bar"] -] - ----------------------------------------------------- - -Checks for class names. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/cpp/keyword_feature.test b/docs/_style/prism-master/tests/languages/cpp/keyword_feature.test deleted file mode 100644 index 180f68ef..00000000 --- a/docs/_style/prism-master/tests/languages/cpp/keyword_feature.test +++ /dev/null @@ -1,49 +0,0 @@ -alignas alignof asm auto bool -break case catch char char16_t char32_t -class; -compl const constexpr -const_cast continue decltype default -delete do double dynamic_cast -else enum explicit export extern -float for friend goto if -inline int long mutable namespace -new noexcept nullptr operator -private protected public register -reinterpret_cast return short -signed sizeof static static_assert -static_cast struct switch template -this thread_local throw try -typedef typeid typename union -unsigned using virtual void -volatile wchar_t while -int8_t int16_t int32_t int64_t -uint8_t uint16_t uint32_t uint64_t - ----------------------------------------------------- - -[ - ["keyword", "alignas"], ["keyword", "alignof"], ["keyword", "asm"], ["keyword", "auto"], ["keyword", "bool"], - ["keyword", "break"], ["keyword", "case"], ["keyword", "catch"], ["keyword", "char"], ["keyword", "char16_t"], ["keyword", "char32_t"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "compl"], ["keyword", "const"], ["keyword", "constexpr"], - ["keyword", "const_cast"], ["keyword", "continue"], ["keyword", "decltype"], ["keyword", "default"], - ["keyword", "delete"], ["keyword", "do"], ["keyword", "double"], ["keyword", "dynamic_cast"], - ["keyword", "else"], ["keyword", "enum"], ["keyword", "explicit"], ["keyword", "export"], ["keyword", "extern"], - ["keyword", "float"], ["keyword", "for"], ["keyword", "friend"], ["keyword", "goto"], ["keyword", "if"], - ["keyword", "inline"], ["keyword", "int"], ["keyword", "long"], ["keyword", "mutable"], ["keyword", "namespace"], - ["keyword", "new"], ["keyword", "noexcept"], ["keyword", "nullptr"], ["keyword", "operator"], - ["keyword", "private"], ["keyword", "protected"], ["keyword", "public"], ["keyword", "register"], - ["keyword", "reinterpret_cast"], ["keyword", "return"], ["keyword", "short"], - ["keyword", "signed"], ["keyword", "sizeof"], ["keyword", "static"], ["keyword", "static_assert"], - ["keyword", "static_cast"], ["keyword", "struct"], ["keyword", "switch"], ["keyword", "template"], - ["keyword", "this"], ["keyword", "thread_local"], ["keyword", "throw"], ["keyword", "try"], - ["keyword", "typedef"], ["keyword", "typeid"], ["keyword", "typename"], ["keyword", "union"], - ["keyword", "unsigned"], ["keyword", "using"], ["keyword", "virtual"], ["keyword", "void"], - ["keyword", "volatile"], ["keyword", "wchar_t"], ["keyword", "while"], - ["keyword", "int8_t"], ["keyword", "int16_t"], ["keyword", "int32_t"], ["keyword", "int64_t"], - ["keyword", "uint8_t"], ["keyword", "uint16_t"], ["keyword", "uint32_t"], ["keyword", "uint64_t"] -] - ----------------------------------------------------- - -Checks for all keywords \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/cpp/operator_feature.test b/docs/_style/prism-master/tests/languages/cpp/operator_feature.test deleted file mode 100644 index a29f08ea..00000000 --- a/docs/_style/prism-master/tests/languages/cpp/operator_feature.test +++ /dev/null @@ -1,73 +0,0 @@ -+ - * / % -- ++ ->> << -~ & | ^ -+= -= *= /= %= >>= <<= &= |= ^= -! && || --> :: -? : -= == != < > <= >= -and and_eq bitand bitor not not_eq or or_eq xor xor_eq - ----------------------------------------------------- - -[ - ["operator", "+"], - ["operator", "-"], - ["operator", "*"], - ["operator", "/"], - ["operator", "%"], - ["operator", "--"], - ["operator", "++"], - - ["operator", ">>"], - ["operator", "<<"], - - ["operator", "~"], - ["operator", "&"], - ["operator", "|"], - ["operator", "^"], - - ["operator", "+="], - ["operator", "-="], - ["operator", "*="], - ["operator", "/="], - ["operator", "%="], - ["operator", ">>="], - ["operator", "<<="], - ["operator", "&="], - ["operator", "|="], - ["operator", "^="], - - ["operator", "!"], - ["operator", "&&"], - ["operator", "||"], - - ["operator", "->"], - ["operator", "::"], - - ["operator", "?"], - ["operator", ":"], - - ["operator", "="], - ["operator", "=="], - ["operator", "!="], - ["operator", "<"], - ["operator", ">"], - ["operator", "<="], - ["operator", ">="], - - ["operator", "and"], - ["operator", "and_eq"], - ["operator", "bitand"], - ["operator", "bitor"], - ["operator", "not"], - ["operator", "not_eq"], - ["operator", "or"], - ["operator", "or_eq"], - ["operator", "xor"], - ["operator", "xor_eq"] -] - ----------------------------------------------------- - -Checks for all operators. diff --git a/docs/_style/prism-master/tests/languages/cpp/raw_string_feature.test b/docs/_style/prism-master/tests/languages/cpp/raw_string_feature.test deleted file mode 100644 index f9a896f8..00000000 --- a/docs/_style/prism-master/tests/languages/cpp/raw_string_feature.test +++ /dev/null @@ -1,18 +0,0 @@ -R"raw( -test -)raw" -R"(no delimiter)" -R"(foo)"R"(bar)" - ----------------------------------------------------- - -[ - ["raw-string", "R\"raw(\r\ntest\r\n)raw\""], - ["raw-string", "R\"(no delimiter)\""], - ["raw-string", "R\"(foo)\""], - ["raw-string", "R\"(bar)\""] -] - ----------------------------------------------------- - -Checks for the C++11 raw string feature \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/crystal/attribute_feature.test b/docs/_style/prism-master/tests/languages/crystal/attribute_feature.test deleted file mode 100644 index 4fb0bad1..00000000 --- a/docs/_style/prism-master/tests/languages/crystal/attribute_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -@[AlwaysInline] -@[CallConvention("X86_StdCall")] - ----------------------------------------------------- - -[ - ["attribute", [ - ["delimiter", "@["], - ["constant", "AlwaysInline"], - ["delimiter", "]"] - ]], - ["attribute", [ - ["delimiter", "@["], - ["constant", "CallConvention"], ["punctuation", "("], ["string", [ "\"X86_StdCall\"" ]], ["punctuation", ")"], - ["delimiter", "]"] - ]] -] - ----------------------------------------------------- - -Checks for attributes. diff --git a/docs/_style/prism-master/tests/languages/crystal/expansion_feature.test b/docs/_style/prism-master/tests/languages/crystal/expansion_feature.test deleted file mode 100644 index b40bd1f5..00000000 --- a/docs/_style/prism-master/tests/languages/crystal/expansion_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -{{ 1_u32 }} -{% 2_u32 %} -{{ { 3_u32 } }} -{% % 4_u32 % %} - ----------------------------------------------------- - -[ - ["expansion", [ - ["delimiter", "{{"], - ["number", "1_u32"], - ["delimiter", "}}"] - ]], - ["expansion", [ - ["delimiter", "{%"], - ["number", "2_u32"], - ["delimiter", "%}"] - ]], - ["expansion", [ - ["delimiter", "{{"], - ["punctuation", "{"], - ["number", "3_u32"], - ["punctuation", "}"], - ["delimiter", "}}"] - ]], - ["expansion", [ - ["delimiter", "{%"], - ["operator", "%"], - ["number", "4_u32"], - ["operator", "%"], - ["delimiter", "%}"] - ]] -] - ----------------------------------------------------- - -Checks for macro expansions. diff --git a/docs/_style/prism-master/tests/languages/crystal/keyword_feature.test b/docs/_style/prism-master/tests/languages/crystal/keyword_feature.test deleted file mode 100644 index 962beadf..00000000 --- a/docs/_style/prism-master/tests/languages/crystal/keyword_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -abstract alias as asm begin break case -class; -def; -do else elsif -end ensure enum extend for fun -if include instance_sizeof -.is_a? -lib macro module next of out pointerof -private protected rescue -.responds_to? -return require select self sizeof struct super -then type typeof uninitialized union unless -until when while with yield __DIR__ __END_LINE__ -__FILE__ __LINE__ - ----------------------------------------------------- - -[ - ["keyword", "abstract"], ["keyword", "alias"], ["keyword", "as"], ["keyword", "asm"], ["keyword", "begin"], ["keyword", "break"], ["keyword", "case"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "def"], ["punctuation", ";"], - ["keyword", "do"], ["keyword", "else"], ["keyword", "elsif"], - ["keyword", "end"], ["keyword", "ensure"], ["keyword", "enum"], ["keyword", "extend"], ["keyword", "for"], ["keyword", "fun"], - ["keyword", "if"], ["keyword", "include"], ["keyword", "instance_sizeof"], - ["punctuation", "."], ["keyword", "is_a?"], - ["keyword", "lib"], ["keyword", "macro"], ["keyword", "module"], ["keyword", "next"], ["keyword", "of"], ["keyword", "out"], ["keyword", "pointerof"], - ["keyword", "private"], ["keyword", "protected"], ["keyword", "rescue"], - ["punctuation", "."], ["keyword", "responds_to?"], - ["keyword", "return"], ["keyword", "require"], ["keyword", "select"], ["keyword", "self"], ["keyword", "sizeof"], ["keyword", "struct"], ["keyword", "super"], - ["keyword", "then"], ["keyword", "type"], ["keyword", "typeof"], ["keyword", "uninitialized"], ["keyword", "union"], ["keyword", "unless"], - ["keyword", "until"], ["keyword", "when"], ["keyword", "while"], ["keyword", "with"], ["keyword", "yield"], ["keyword", "__DIR__"], ["keyword", "__END_LINE__"], - ["keyword", "__FILE__"], ["keyword", "__LINE__"] -] - ----------------------------------------------------- - -Checks for all keywords. diff --git a/docs/_style/prism-master/tests/languages/crystal/number_feature.test b/docs/_style/prism-master/tests/languages/crystal/number_feature.test deleted file mode 100644 index cefdc5ee..00000000 --- a/docs/_style/prism-master/tests/languages/crystal/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -1 -1_1 -1_1_1 -0b10_01 -0o1_2_3 -0x123456789abcdef -012_345.678_9e+10_f64 - ----------------------------------------------------- - -[ - ["number", "1"], - ["number", "1_1"], - ["number", "1_1_1"], - ["number", "0b10_01"], - ["number", "0o1_2_3"], - ["number", "0x123456789abcdef"], - ["number", "012_345.678_9e+10_f64"] -] - ----------------------------------------------------- - -Checks for number literals. diff --git a/docs/_style/prism-master/tests/languages/csharp+aspnet/directive_feature.test b/docs/_style/prism-master/tests/languages/csharp+aspnet/directive_feature.test deleted file mode 100644 index f19c2a82..00000000 --- a/docs/_style/prism-master/tests/languages/csharp+aspnet/directive_feature.test +++ /dev/null @@ -1,70 +0,0 @@ -<%: Page.Title %> -<%#:Item.ProductID%> - -<% if(foo) { %> - foobar -<% } %> - ----------------------------------------------------- - -[ - ["directive tag", [ - ["directive tag", "<%:"], - " Page", - ["punctuation", "."], - "Title ", - ["directive tag", "%>"] - ]], - - ["directive tag", [ - ["directive tag", "<%#:"], - "Item", - ["punctuation", "."], - "ProductID", - ["directive tag", "%>"] - ]], - - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "a" - ]], - ["attr-name", [ - "href" - ]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - "ProductDetails.aspx?productID=", - ["directive tag", [ - ["directive tag", "<%#:"], - "Item", - ["punctuation", "."], - "ProductID", - ["directive tag", "%>"] - ]], - ["punctuation", "\""] - ]], - ["punctuation", ">"] - ]], - - ["directive tag", [ - ["directive tag", "<%"], - ["keyword", "if"], - ["punctuation", "("], - "foo", - ["punctuation", ")"], - ["punctuation", "{"], - ["directive tag", "%>"] - ]], - "\r\n\tfoobar\r\n", - ["directive tag", [ - ["directive tag", "<%"], - ["punctuation", "}"], - ["directive tag", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/class-name_feature.test b/docs/_style/prism-master/tests/languages/csharp/class-name_feature.test deleted file mode 100644 index 113982b6..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/class-name_feature.test +++ /dev/null @@ -1,34 +0,0 @@ -class Foo -interface BarBaz -class Foo : Bar -[Foobar] -void Foo(Bar bar, Baz baz) - ----------------------------------------------------- - -[ - ["keyword", "class"], - ["class-name", ["Foo"]], - ["keyword", "interface"], - ["class-name", ["BarBaz"]], - ["keyword", "class"], - ["class-name", ["Foo"]], - ["punctuation", ":"], - ["class-name", ["Bar"]], - ["punctuation", "["], - ["class-name", ["Foobar"]], - ["punctuation", "]"], - ["keyword", "void"], - ["function", "Foo"], - ["punctuation", "("], - ["class-name", ["Bar"]], - " bar", - ["punctuation", ","], - ["class-name", ["Baz"]], - " baz", - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for class names. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/generic_feature.test b/docs/_style/prism-master/tests/languages/csharp/generic_feature.test deleted file mode 100644 index 05d3f589..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/generic_feature.test +++ /dev/null @@ -1,34 +0,0 @@ -void method(); -method(); - ----------------------------------------------------- - -[ - ["keyword", "void"], - ["generic-method", [ - ["function", "method"], - ["punctuation", "<"], - ["class-name", ["T"]], - ["punctuation", ","], - ["class-name", ["U"]], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["generic-method", [ - ["function", "method"], - ["punctuation", "<"], - ["keyword", "int"], - ["punctuation", ","], - ["keyword", "char"], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for generic methods \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/issue1091.test b/docs/_style/prism-master/tests/languages/csharp/issue1091.test deleted file mode 100644 index c0c264b1..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/issue1091.test +++ /dev/null @@ -1,12 +0,0 @@ -@"file:///" - ----------------------------------------------------- - -[ - ["string", "@\"file:///\""] -] - ----------------------------------------------------- - -Checks that three slashes inside a string do not break highlighting. -See #1091. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/issue1365.test b/docs/_style/prism-master/tests/languages/csharp/issue1365.test deleted file mode 100644 index 6aaf3c71..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/issue1365.test +++ /dev/null @@ -1,39 +0,0 @@ -interface ILogger { - void Init(SomeClass file); - void LogInfo(string message); -} -public class SomeClass : BaseClass {} - ----------------------------------------------------- - -[ - ["keyword", "interface"], - ["class-name", ["ILogger"]], - ["punctuation", "{"], - ["keyword", "void"], - ["function", "Init"], - ["punctuation", "("], - ["class-name", ["SomeClass"]], - " file", - ["punctuation", ")"], - ["punctuation", ";"], - ["keyword", "void"], - ["function", "LogInfo"], - ["punctuation", "("], - ["keyword", "string"], - " message", - ["punctuation", ")"], - ["punctuation", ";"], - ["punctuation", "}"], - ["keyword", "public"], - ["keyword", "class"], - ["class-name", ["SomeClass"]], - ["punctuation", ":"], - ["class-name", ["BaseClass"]], - ["punctuation", "{"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for class names. See #1365 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/issue1371.test b/docs/_style/prism-master/tests/languages/csharp/issue1371.test deleted file mode 100644 index 1fec4a05..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/issue1371.test +++ /dev/null @@ -1,144 +0,0 @@ -container.Register(); -container.Register(); -var container = new Container(f => -{ - f.For().Use(); -}); -class LandAnimal { - public void Move() => Run(); } -class Dog : LandAnimal { - public new void Move() => Run(); } -class Works : LandAnimal { - public override void Move() => Run(); } -[Required] -[RequiredAttribute()] -[Range(1, 10)] - ----------------------------------------------------- - -[ - "container", - ["punctuation", "."], - ["generic-method", [ - ["function", "Register"], - ["punctuation", "<"], - ["class-name", ["Car"]], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - "\r\ncontainer", - ["punctuation", "."], - ["generic-method", [ - ["function", "Register"], - ["punctuation", "<"], - ["class-name", ["IJuice"]], - ["punctuation", ","], - ["class-name", ["Juice"]], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["keyword", "var"], - " container ", - ["operator", "="], - ["keyword", "new"], - ["class-name", ["Container"]], - ["punctuation", "("], - "f ", - ["operator", "=>"], - ["punctuation", "{"], - "\r\n f", - ["punctuation", "."], - ["generic-method", [ - ["function", "For"], - ["punctuation", "<"], - ["class-name", ["IFoo"]], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", "."], - ["generic-method", [ - ["function", "Use"], - ["punctuation", "<"], - ["class-name", ["Foo"]], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["punctuation", "}"], - ["punctuation", ")"], - ["punctuation", ";"], - ["keyword", "class"], - ["class-name", ["LandAnimal"]], - ["punctuation", "{"], - ["keyword", "public"], - ["keyword", "void"], - ["function", "Move"], - ["punctuation", "("], - ["punctuation", ")"], - ["operator", "=>"], - ["function", "Run"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["punctuation", "}"], - ["keyword", "class"], - ["class-name", ["Dog"]], - ["punctuation", ":"], - ["class-name", ["LandAnimal"]], - ["punctuation", "{"], - ["keyword", "public"], - ["keyword", "new"], - ["keyword", "void"], - ["function", "Move"], - ["punctuation", "("], - ["punctuation", ")"], - ["operator", "=>"], - ["function", "Run"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["punctuation", "}"], - ["keyword", "class"], - ["class-name", ["Works"]], - ["punctuation", ":"], - ["class-name", ["LandAnimal"]], - ["punctuation", "{"], - ["keyword", "public"], - ["keyword", "override"], - ["keyword", "void"], - ["function", "Move"], - ["punctuation", "("], - ["punctuation", ")"], - ["operator", "=>"], - ["function", "Run"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - ["punctuation", "}"], - ["punctuation", "["], - ["class-name", ["Required"]], - ["punctuation", "]"], - ["punctuation", "["], - ["class-name", ["RequiredAttribute"]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", "]"], - ["punctuation", "["], - ["class-name", ["Range"]], - ["punctuation", "("], - ["number", "1"], - ["punctuation", ","], - ["number", "10"], - ["punctuation", ")"], - ["punctuation", "]"] -] - ----------------------------------------------------- - -Checks for various cases of class names. See #1371 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/issue806.test b/docs/_style/prism-master/tests/languages/csharp/issue806.test deleted file mode 100644 index f7a4096f..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/issue806.test +++ /dev/null @@ -1,12 +0,0 @@ -0.3f - ----------------------------------------------------- - -[ - ["number", "0.3f"] -] - ----------------------------------------------------- - -Checks that "f" prefix is properly highlighted as part of the number. -See #806. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/keyword_feature.test b/docs/_style/prism-master/tests/languages/csharp/keyword_feature.test deleted file mode 100644 index e1c37f0a..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/keyword_feature.test +++ /dev/null @@ -1,209 +0,0 @@ -abstract -add -alias -as -ascending -async -await -base -bool -break -byte -case -catch -char -checked -class; -const -continue -decimal -default -delegate -descending -do -double -dynamic -else -enum -event -explicit -extern -false -finally -fixed -float -for -foreach -from -get -global -goto -group -if -implicit -in -int -interface; -internal -into -is -join -let -lock -long -namespace -new; -null -object -operator -orderby -out -override -params -partial -private -protected -public -readonly -ref -remove -return -sbyte -sealed -select -set -short -sizeof -stackalloc -static -string -struct -switch -this -throw -true -try -typeof -uint -ulong -unchecked -unsafe -ushort -using -value -var -virtual -void -volatile -where -while -yield - ----------------------------------------------------- - -[ - ["keyword", "abstract"], - ["keyword", "add"], - ["keyword", "alias"], - ["keyword", "as"], - ["keyword", "ascending"], - ["keyword", "async"], - ["keyword", "await"], - ["keyword", "base"], - ["keyword", "bool"], - ["keyword", "break"], - ["keyword", "byte"], - ["keyword", "case"], - ["keyword", "catch"], - ["keyword", "char"], - ["keyword", "checked"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], - ["keyword", "continue"], - ["keyword", "decimal"], - ["keyword", "default"], - ["keyword", "delegate"], - ["keyword", "descending"], - ["keyword", "do"], - ["keyword", "double"], - ["keyword", "dynamic"], - ["keyword", "else"], - ["keyword", "enum"], - ["keyword", "event"], - ["keyword", "explicit"], - ["keyword", "extern"], - ["keyword", "false"], - ["keyword", "finally"], - ["keyword", "fixed"], - ["keyword", "float"], - ["keyword", "for"], - ["keyword", "foreach"], - ["keyword", "from"], - ["keyword", "get"], - ["keyword", "global"], - ["keyword", "goto"], - ["keyword", "group"], - ["keyword", "if"], - ["keyword", "implicit"], - ["keyword", "in"], - ["keyword", "int"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "internal"], - ["keyword", "into"], - ["keyword", "is"], - ["keyword", "join"], - ["keyword", "let"], - ["keyword", "lock"], - ["keyword", "long"], - ["keyword", "namespace"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "null"], - ["keyword", "object"], - ["keyword", "operator"], - ["keyword", "orderby"], - ["keyword", "out"], - ["keyword", "override"], - ["keyword", "params"], - ["keyword", "partial"], - ["keyword", "private"], - ["keyword", "protected"], - ["keyword", "public"], - ["keyword", "readonly"], - ["keyword", "ref"], - ["keyword", "remove"], - ["keyword", "return"], - ["keyword", "sbyte"], - ["keyword", "sealed"], - ["keyword", "select"], - ["keyword", "set"], - ["keyword", "short"], - ["keyword", "sizeof"], - ["keyword", "stackalloc"], - ["keyword", "static"], - ["keyword", "string"], - ["keyword", "struct"], - ["keyword", "switch"], - ["keyword", "this"], - ["keyword", "throw"], - ["keyword", "true"], - ["keyword", "try"], - ["keyword", "typeof"], - ["keyword", "uint"], - ["keyword", "ulong"], - ["keyword", "unchecked"], - ["keyword", "unsafe"], - ["keyword", "ushort"], - ["keyword", "using"], - ["keyword", "value"], - ["keyword", "var"], - ["keyword", "virtual"], - ["keyword", "void"], - ["keyword", "volatile"], - ["keyword", "where"], - ["keyword", "while"], - ["keyword", "yield"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/number_feature.test b/docs/_style/prism-master/tests/languages/csharp/number_feature.test deleted file mode 100644 index 12680803..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/number_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -42 -3.14159 -0xbabe -0XBABE - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "0xbabe"], - ["number", "0XBABE"] -] - ----------------------------------------------------- - -Checks for decimal and hexadecimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/operator_feature.test b/docs/_style/prism-master/tests/languages/csharp/operator_feature.test deleted file mode 100644 index 4749c38c..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/operator_feature.test +++ /dev/null @@ -1,60 +0,0 @@ -+ - * / % -- ++ ->> << -~ & | ^ -+= -= *= /= %= >>= <<= &= |= ^= -! && || --> => -= == != < > <= >= -?? - ----------------------------------------------------- - -[ - ["operator", "+"], - ["operator", "-"], - ["operator", "*"], - ["operator", "/"], - ["operator", "%"], - ["operator", "--"], - ["operator", "++"], - - ["operator", ">>"], - ["operator", "<<"], - - ["operator", "~"], - ["operator", "&"], - ["operator", "|"], - ["operator", "^"], - - ["operator", "+="], - ["operator", "-="], - ["operator", "*="], - ["operator", "/="], - ["operator", "%="], - ["operator", ">>="], - ["operator", "<<="], - ["operator", "&="], - ["operator", "|="], - ["operator", "^="], - - ["operator", "!"], - ["operator", "&&"], - ["operator", "||"], - - ["operator", "->"], - ["operator", "=>"], - - ["operator", "="], - ["operator", "=="], - ["operator", "!="], - ["operator", "<"], - ["operator", ">"], - ["operator", "<="], - ["operator", ">="], - - ["operator", "??"] -] - ----------------------------------------------------- - -Checks for all operators. diff --git a/docs/_style/prism-master/tests/languages/csharp/preprocessor_feature.test b/docs/_style/prism-master/tests/languages/csharp/preprocessor_feature.test deleted file mode 100644 index 93ad4903..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/preprocessor_feature.test +++ /dev/null @@ -1,35 +0,0 @@ -#define DEBUG -#if DEBUG -#endif - -#elif -#else -#endregion -#error -#line -#pragma -#region -#undef -#warning - ----------------------------------------------------- - -[ - ["preprocessor", ["#", ["directive", "define"], " DEBUG"]], - ["preprocessor", ["#", ["directive", "if"], " DEBUG"]], - ["preprocessor", ["#", ["directive", "endif"]]], - - ["preprocessor", ["#", ["directive", "elif"]]], - ["preprocessor", ["#", ["directive", "else"]]], - ["preprocessor", ["#", ["directive", "endregion"]]], - ["preprocessor", ["#", ["directive", "error"]]], - ["preprocessor", ["#", ["directive", "line"]]], - ["preprocessor", ["#", ["directive", "pragma"]]], - ["preprocessor", ["#", ["directive", "region"]]], - ["preprocessor", ["#", ["directive", "undef"]]], - ["preprocessor", ["#", ["directive", "warning"]]] -] - ----------------------------------------------------- - -Checks for preprocessor directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csharp/punctuation_feature.test b/docs/_style/prism-master/tests/languages/csharp/punctuation_feature.test deleted file mode 100644 index b9a7bf14..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/punctuation_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -. , ; : :: -? ?. -[ ] { } ( ) - ----------------------------------------------------- - -[ - ["punctuation", "."], - ["punctuation", ","], - ["punctuation", ";"], - ["punctuation", ":"], - ["punctuation", "::"], - - ["punctuation", "?"], - ["punctuation", "?."], - - ["punctuation", "["], - ["punctuation", "]"], - ["punctuation", "{"], - ["punctuation", "}"], - ["punctuation", "("], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for punctuation. diff --git a/docs/_style/prism-master/tests/languages/csharp/string_feature.test b/docs/_style/prism-master/tests/languages/csharp/string_feature.test deleted file mode 100644 index 2814cb56..00000000 --- a/docs/_style/prism-master/tests/languages/csharp/string_feature.test +++ /dev/null @@ -1,32 +0,0 @@ -"" -"fo\"o" - -@"" -@"foo" -@"fo""o" -@"foo -bar" - -'a' -'\'' -'\\' - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\\\"o\""], - - ["string", "@\"\""], - ["string", "@\"foo\""], - ["string", "@\"fo\"\"o\""], - ["string", "@\"foo\r\nbar\""], - ["string", "'a'"], - ["string", "'\\''"], - ["string", "'\\\\'"] -] - ----------------------------------------------------- - -Checks for normal and verbatim strings. -Also checks for single quoted characters. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/csp/directive_no_value_feature.test b/docs/_style/prism-master/tests/languages/csp/directive_no_value_feature.test deleted file mode 100644 index 5ae927df..00000000 --- a/docs/_style/prism-master/tests/languages/csp/directive_no_value_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -upgrade-insecure-requests; - ----------------------------------------------------- - -[ - ["directive", "upgrade-insecure-requests;"] -] - ----------------------------------------------------- - -Checks for a "void" CSP directive followed by semicolon. diff --git a/docs/_style/prism-master/tests/languages/csp/directive_with_source_expression_feature.test b/docs/_style/prism-master/tests/languages/csp/directive_with_source_expression_feature.test deleted file mode 100644 index fc059f42..00000000 --- a/docs/_style/prism-master/tests/languages/csp/directive_with_source_expression_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -script-src example.com; - ----------------------------------------------------- - -[ - ["directive", "script-src "], - "example.com;" -] - ----------------------------------------------------- - -Checks for CSP directive followed by a source expression. diff --git a/docs/_style/prism-master/tests/languages/csp/safe_feature.test b/docs/_style/prism-master/tests/languages/csp/safe_feature.test deleted file mode 100644 index af31d1ac..00000000 --- a/docs/_style/prism-master/tests/languages/csp/safe_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -default-src 'none'; style-src 'self' 'strict-dynamic' 'nonce-yeah' 'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4='; - ----------------------------------------------------- - -[ - ["directive", "default-src "], - ["safe", "'none'"], - "; ", - ["directive", "style-src "], - ["safe", "'self'"], - ["safe", "'strict-dynamic'"], - ["safe", "'nonce-yeah'"], - ["safe", "'sha256-EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4='"], - ";" -] - ----------------------------------------------------- - -Checks for source expressions classified as safe. diff --git a/docs/_style/prism-master/tests/languages/csp/unsafe_feature.test b/docs/_style/prism-master/tests/languages/csp/unsafe_feature.test deleted file mode 100644 index 1fe7e478..00000000 --- a/docs/_style/prism-master/tests/languages/csp/unsafe_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -script-src 'unsafe-inline' 'unsafe-eval' 'unsafe-hashed-attributes'; - ----------------------------------------------------- - -[ - ["directive", "script-src "], - ["unsafe", "'unsafe-inline'"], - ["unsafe", "'unsafe-eval'"], - ["unsafe", "'unsafe-hashed-attributes'"], - ";" -] - ----------------------------------------------------- - -Checks for source expressions classified as unsafe. diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/entity_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/entity_feature.test deleted file mode 100644 index c1c91a29..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/entity_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -\0022 -\20B9 - ----------------------------------------------------- - -[ - ["entity", "\\0022"], - ["entity", "\\20B9"] -] - ----------------------------------------------------- - -Checks for entities. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/hexcode_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/hexcode_feature.test deleted file mode 100644 index cac0b44b..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/hexcode_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -#ff0000 -#BADA55 -#4dd -#D0C - ----------------------------------------------------- - -[ - ["hexcode", "#ff0000"], - ["hexcode", "#BADA55"], - ["hexcode", "#4dd"], - ["hexcode", "#D0C"] -] - ----------------------------------------------------- - -Checks for hexadecimal colors. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/number_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/number_feature.test deleted file mode 100644 index ef58e620..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/number_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -42 -3.14159 --42 --3.14 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "-42"], - ["number", "-3.14"] -] - ----------------------------------------------------- - -Checks for numbers. diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/operator_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/operator_feature.test deleted file mode 100644 index 0df7612b..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/operator_feature.test +++ /dev/null @@ -1,71 +0,0 @@ -width: calc(100% + 20px); -width: calc(100% - 20px); -width: calc(5px * 2); -width: calc(10px / 2); -height: -20px; -content: 'this - is not an operator'; - ----------------------------------------------------- - -[ - ["property", "width"], - ["punctuation", ":"], - ["function", "calc"], - ["punctuation", "("], - ["number", "100"], - ["unit", "%"], - ["operator", "+"], - ["number", "20"], - ["unit", "px"], - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "width"], - ["punctuation", ":"], - ["function", "calc"], - ["punctuation", "("], - ["number", "100"], - ["unit", "%"], - ["operator", "-"], - ["number", "20"], - ["unit", "px"], - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "width"], - ["punctuation", ":"], - ["function", "calc"], - ["punctuation", "("], - ["number", "5"], - ["unit", "px"], - ["operator", "*"], - ["number", "2"], - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "width"], - ["punctuation", ":"], - ["function", "calc"], - ["punctuation", "("], - ["number", "10"], - ["unit", "px"], - ["operator", "/"], - ["number", "2"], - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "height"], - ["punctuation", ":"], - ["number", "-20"], - ["unit", "px"], - ["punctuation", ";"], - - ["property", "content"], - ["punctuation", ":"], - ["string", "'this - is not an operator'"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for operators. diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/selector_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/selector_feature.test deleted file mode 100644 index 3931a663..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/selector_feature.test +++ /dev/null @@ -1,58 +0,0 @@ -foo:after { -foo::first-letter { - -foo:nth-child(2n+1) { - -foo.bar { - -foo#bar { - -#foo > .bar:not(baz):after { - -div[foo="bar"] { - ----------------------------------------------------- - -[ - ["selector", [ - "foo", - ["pseudo-element", ":after"] - ]], ["punctuation", "{"], - - ["selector", [ - "foo", - ["pseudo-element", "::first-letter"] - ]], ["punctuation", "{"], - - ["selector", [ - "foo", - ["pseudo-class", ":nth-child(2n+1)"] - ]], ["punctuation", "{"], - - ["selector", [ - "foo", - ["class", ".bar"] - ]], ["punctuation", "{"], - - ["selector", [ - "foo", - ["id", "#bar"] - ]], ["punctuation", "{"], - - ["selector", [ - ["id", "#foo"], - " > ", - ["class", ".bar"], - ["pseudo-class", ":not(baz)"], - ["pseudo-element", ":after"] - ]], ["punctuation", "{"], - - ["selector", [ - "div", - ["attribute", "[foo=\"bar\"]"] - ]], ["punctuation", "{"] -] - ----------------------------------------------------- - -Checks for pseudo-elements, pseudo-classes, classes and ids inside selectors. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/unit_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/unit_feature.test deleted file mode 100644 index 6d5c15c8..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/unit_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -100% -1rem -500ms -.5s --3px --0.618vw - ----------------------------------------------------- - -[ - ["number", "100"], ["unit", "%"], - ["number", "1"], ["unit", "rem"], - ["number", "500"], ["unit", "ms"], - ["number", ".5"], ["unit", "s"], - ["number", "-3"], ["unit", "px"], - ["number", "-0.618"], ["unit", "vw"] -] - ----------------------------------------------------- - -Checks for units. diff --git a/docs/_style/prism-master/tests/languages/css!+css-extras/variable_feature.test b/docs/_style/prism-master/tests/languages/css!+css-extras/variable_feature.test deleted file mode 100644 index dc089b5f..00000000 --- a/docs/_style/prism-master/tests/languages/css!+css-extras/variable_feature.test +++ /dev/null @@ -1,54 +0,0 @@ -element { - --foo: green; -} - -var(--color-primary) -var(--level-3) -var(--foo, red) -calc(100% - var(--margin-size) * 2) - ----------------------------------------------------- - -[ - ["selector", ["element"]], - ["punctuation", "{"], - ["variable", "--foo"], - ["punctuation", ":"], - " green", - ["punctuation", ";"], - ["punctuation", "}"], - - ["function", "var"], - ["punctuation", "("], - ["variable", "--color-primary"], - ["punctuation", ")"], - - ["function", "var"], - ["punctuation", "("], - ["variable", "--level-3"], - ["punctuation", ")"], - - ["function", "var"], - ["punctuation", "("], - ["variable", "--foo"], - ["punctuation", ","], - " red", - ["punctuation", ")"], - - ["function", "calc"], - ["punctuation", "("], - ["number", "100"], - ["unit", "%"], - ["operator", "-"], - ["function", "var"], - ["punctuation", "("], - ["variable", "--margin-size"], - ["punctuation", ")"], - ["operator", "*"], - ["number", "2"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for variables. diff --git a/docs/_style/prism-master/tests/languages/css+haml/css+haml_usage.test b/docs/_style/prism-master/tests/languages/css+haml/css+haml_usage.test deleted file mode 100644 index c90047f2..00000000 --- a/docs/_style/prism-master/tests/languages/css+haml/css+haml_usage.test +++ /dev/null @@ -1,28 +0,0 @@ -:css - .test {} - -~ - :css - .test {} - ----------------------------------------------------- - -[ - ["filter-css", [ - ["filter-name", ":css"], - ["selector", ".test"], - ["punctuation", "{"], - ["punctuation", "}"] - ]], - ["punctuation", "~"], - ["filter-css", [ - ["filter-name", ":css"], - ["selector", ".test"], - ["punctuation", "{"], - ["punctuation", "}"] - ]] -] - ----------------------------------------------------- - -Checks for CSS filter in Haml. The tilde serves only as a separator. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css+http/css_inclusion.test b/docs/_style/prism-master/tests/languages/css+http/css_inclusion.test deleted file mode 100644 index f805d6d4..00000000 --- a/docs/_style/prism-master/tests/languages/css+http/css_inclusion.test +++ /dev/null @@ -1,25 +0,0 @@ -Content-type: text/css - -a.link:hover { - color: red; -} - ----------------------------------------------------- - -[ - ["header-name", "Content-type:"], - " text/css", - ["text/css", [ - ["selector", "a.link:hover"], - ["punctuation", "{"], - ["property", "color"], - ["punctuation", ":"], - " red", - ["punctuation", ";"], - ["punctuation", "}"] - ]] -] - ----------------------------------------------------- - -Checks for JavaScript content in HTTP. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css+textile/css_inclusion.test b/docs/_style/prism-master/tests/languages/css+textile/css_inclusion.test deleted file mode 100644 index 8b6f6052..00000000 --- a/docs/_style/prism-master/tests/languages/css+textile/css_inclusion.test +++ /dev/null @@ -1,117 +0,0 @@ -h3{color: red}. Baz - -h4[fr]{text-decoration:underline;}(#bar). Foobar - -*{color:red}bold* -"(foo)[en]{color:blue;}Foo":bar - -#[fr](#foo){background:pink} Foobar - -!(foo)[en]{border:1px solid #ccc}foo.png! - -{color: blue}.|{font-weight:bold}.Baz| -(foo#bar){font-style:italic}[fr].|{background:red;}(bar#baz)[en].Baz| - ----------------------------------------------------- - -[ - ["phrase", [ - ["block-tag", [ - ["tag", "h3"], - ["modifier", [ - ["css", [["punctuation", "{"], ["property", "color"], ["punctuation", ":"], " red", ["punctuation", "}"]]] - ]], - ["punctuation", "."] - ]], - " Baz" - ]], - ["phrase", [ - ["block-tag", [ - ["tag", "h4"], - ["modifier", [ - ["punctuation", "["], ["lang", "fr"], ["punctuation", "]"], - ["css", [["punctuation", "{"], ["property", "text-decoration"], ["punctuation", ":"], "underline", ["punctuation", ";"], ["punctuation", "}"]]], - ["punctuation", "("], ["class-id", "#bar"], ["punctuation", ")"] - ]], - ["punctuation", "."] - ]], - " Foobar" - ]], - - ["phrase", [ - ["inline", [ - ["punctuation", "*"], - ["modifier", [ - ["css", [["punctuation", "{"], ["property", "color"], ["punctuation", ":"], "red", ["punctuation", "}"]]] - ]], - ["bold", ["bold"]], - ["punctuation", "*"] - ]], - ["link", [ - ["punctuation", "\""], - ["modifier", [ - ["punctuation", "("], ["class-id", "foo"], ["punctuation", ")"], - ["punctuation", "["], ["lang", "en"], ["punctuation", "]"], - ["css", [["punctuation", "{"], ["property", "color"], ["punctuation", ":"], "blue", ["punctuation", ";"], ["punctuation", "}"]]] - ]], - ["text", "Foo"], - ["punctuation", "\""], ["punctuation", ":"], - ["url", "bar"] - ]] - ]], - - ["phrase", [ - ["list", [ - ["punctuation", "#"], - ["modifier", [ - ["punctuation", "["], ["lang", "fr"], ["punctuation", "]"], - ["punctuation", "("], ["class-id", "#foo"], ["punctuation", ")"], - ["css", [["punctuation", "{"], ["property", "background"], ["punctuation", ":"], "pink", ["punctuation", "}"]]] - ]], - " Foobar" - ]] - ]], - - ["phrase", [ - ["image", [ - ["punctuation", "!"], - ["modifier", [ - ["punctuation", "("], ["class-id", "foo"], ["punctuation", ")"], - ["punctuation", "["], ["lang", "en"], ["punctuation", "]"], - ["css", [["punctuation", "{"], ["property", "border"], ["punctuation", ":"], "1px solid #ccc", ["punctuation", "}"]]] - ]], - ["source", "foo.png"], - ["punctuation", "!"] - ]] - ]], - - ["phrase", [ - ["table", [ - ["modifier", [ - ["css", [["punctuation", "{"], ["property", "color"], ["punctuation", ":"], " blue", ["punctuation", "}"]]] - ]], - ["punctuation", "."], ["punctuation", "|"], - ["modifier", [ - ["css", [["punctuation", "{"], ["property", "font-weight"], ["punctuation", ":"], "bold", ["punctuation", "}"]]] - ]], - ["punctuation", "."], "Baz", ["punctuation", "|"], - - ["modifier", [ - ["punctuation", "("], ["class-id", "foo#bar"], ["punctuation", ")"], - ["css", [["punctuation", "{"], ["property", "font-style"], ["punctuation", ":"], "italic", ["punctuation", "}"]]], - ["punctuation", "["], ["lang", "fr"], ["punctuation", "]"] - ]], - ["punctuation", "."], ["punctuation", "|"], - ["modifier", [ - ["css", [["punctuation", "{"], ["property", "background"], ["punctuation", ":"], "red", ["punctuation", ";"], ["punctuation", "}"]]], - ["punctuation", "("], ["class-id", "bar#baz"], ["punctuation", ")"], - ["punctuation", "["], ["lang", "en"], ["punctuation", "]"] - ]], - ["punctuation", "."], "Baz", ["punctuation", "|"] - ]] - ]] -] - ----------------------------------------------------- - -Checks for CSS modifier. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/atrule_feature.test b/docs/_style/prism-master/tests/languages/css/atrule_feature.test deleted file mode 100644 index abc75def..00000000 --- a/docs/_style/prism-master/tests/languages/css/atrule_feature.test +++ /dev/null @@ -1,47 +0,0 @@ -@import url(http://github.com/foo.css); -@media print {} -@media (min-width: 640px) and (min-height: 1000px) {} -@main-color: red; - ----------------------------------------------------- - -[ - ["atrule", [ - ["rule", "@import"], - ["url", "url(http://github.com/foo.css)"], - ["punctuation", ";"] - ]], - ["atrule", [ - ["rule", "@media"], - " print" - ]], - ["punctuation", "{"], - ["punctuation", "}"], - ["atrule", [ - ["rule", "@media"], - ["punctuation", "("], - ["property", "min-width"], - ["punctuation", ":"], - " 640px", - ["punctuation", ")"], - " and ", - ["punctuation", "("], - ["property", "min-height"], - ["punctuation", ":"], - " 1000px", - ["punctuation", ")"] - ]], - ["punctuation", "{"], - ["punctuation", "}"], - ["atrule", [ - ["rule", "@main-color"], - ["punctuation", ":"], - " red", - ["punctuation", ";"] - ]] -] - ----------------------------------------------------- - -Checks for at-rules. -Also checks for LESS variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/comment_feature.test b/docs/_style/prism-master/tests/languages/css/comment_feature.test deleted file mode 100644 index a3899edd..00000000 --- a/docs/_style/prism-master/tests/languages/css/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -/**/ -/* foo */ -/* foo -bar */ - ----------------------------------------------------- - -[ - ["comment", "/**/"], - ["comment", "/* foo */"], - ["comment", "/* foo\r\nbar */"] -] - ----------------------------------------------------- - -Checks for empty comment, single-line comment and multi-line comment. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/function_feature.test b/docs/_style/prism-master/tests/languages/css/function_feature.test deleted file mode 100644 index 0af02a0c..00000000 --- a/docs/_style/prism-master/tests/languages/css/function_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -transform: translate(-50%); -background: rgba(0, 0, 0, 0.2); -filter: opacity(alpha=0); - ----------------------------------------------------- - -[ - ["property", "transform"], - ["punctuation", ":"], - ["function", "translate"], - ["punctuation", "("], - "-50%", - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "background"], - ["punctuation", ":"], - ["function", "rgba"], - ["punctuation", "("], - "0", - ["punctuation", ","], - " 0", - ["punctuation", ","], - " 0", - ["punctuation", ","], - " 0.2", - ["punctuation", ")"], - ["punctuation", ";"], - - ["property", "filter"], - ["punctuation", ":"], - ["function", "opacity"], - ["punctuation", "("], - "alpha=0", - ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/important_feature.test b/docs/_style/prism-master/tests/languages/css/important_feature.test deleted file mode 100644 index 0f5c1409..00000000 --- a/docs/_style/prism-master/tests/languages/css/important_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -color: red !important; -padding: 10px 20px 30px !important; -position:absolute!important; - ----------------------------------------------------- - -[ - ["property", "color"], - ["punctuation", ":"], - " red ", - ["important", "!important"], - ["punctuation", ";"], - ["property", "padding"], - ["punctuation", ":"], - " 10px 20px 30px ", - ["important", "!important"], - ["punctuation", ";"], - ["property", "position"], - ["punctuation", ":"], - "absolute", - ["important", "!important"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for !important rule. diff --git a/docs/_style/prism-master/tests/languages/css/property_feature.test b/docs/_style/prism-master/tests/languages/css/property_feature.test deleted file mode 100644 index d01e2d1d..00000000 --- a/docs/_style/prism-master/tests/languages/css/property_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -color: red; -background-color: blue; --webkit-transform: none; ---ötökkä: 2; - ----------------------------------------------------- - -[ - ["property", "color"], - ["punctuation", ":"], - " red", - ["punctuation", ";"], - ["property", "background-color"], - ["punctuation", ":"], - " blue", - ["punctuation", ";"], - ["property", "-webkit-transform"], - ["punctuation", ":"], - " none", - ["punctuation", ";"], - ["property", "--ötökkä"], - ["punctuation", ":"], - " 2", - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/selector_feature.test b/docs/_style/prism-master/tests/languages/css/selector_feature.test deleted file mode 100644 index 5a597201..00000000 --- a/docs/_style/prism-master/tests/languages/css/selector_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -foo{ -foo + bar { -foo:first-child:hover { -* { -foo, -bar{ - ----------------------------------------------------- - -[ - ["selector", "foo"], - ["punctuation", "{"], - ["selector", "foo + bar"], - ["punctuation", "{"], - ["selector", "foo:first-child:hover"], - ["punctuation", "{"], - ["selector", "*"], - ["punctuation", "{"], - ["selector", "foo,\r\nbar"], - ["punctuation", "{"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line selectors. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/string_feature.test b/docs/_style/prism-master/tests/languages/css/string_feature.test deleted file mode 100644 index c849d604..00000000 --- a/docs/_style/prism-master/tests/languages/css/string_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -"f\"oo" -'f\'oo' -"foo\ -bar" -'foo\ -bar' -"foo /* bar" /* and out */ - ----------------------------------------------------- - -[ - ["string", "\"f\\\"oo\""], - ["string", "'f\\'oo'"], - ["string", "\"foo\\\r\nbar\""], - ["string", "'foo\\\r\nbar'"], - ["string", "\"foo /* bar\""], - ["comment", "/* and out */"] -] - ----------------------------------------------------- - -Checks for single-quoted and double-quoted strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/css/url_feature.test b/docs/_style/prism-master/tests/languages/css/url_feature.test deleted file mode 100644 index 05ce8f9f..00000000 --- a/docs/_style/prism-master/tests/languages/css/url_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -url(http://github.com/foo.png) -url('http://github.com/foo.png') -url("http://github.com/foo.png") -url('foo\ -bar.png') -url("foo\ -bar.png") - ----------------------------------------------------- - -[ - ["url", "url(http://github.com/foo.png)"], - ["url", "url('http://github.com/foo.png')"], - ["url", "url(http://github.com/%22foo.png/")"], - ["url", "url('http://github.com/foo///r/nbar.png')"], - ["url", "url(http://github.com/%22foo///r/nbar.png/")"] -] - ----------------------------------------------------- - -Checks for url(), unquoted, single-quoted and double-quoted. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/comment_feature.test b/docs/_style/prism-master/tests/languages/d/comment_feature.test deleted file mode 100644 index ed1f8603..00000000 --- a/docs/_style/prism-master/tests/languages/d/comment_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env rdmd - -/++/ -/+ foo -bar +/ -/+ foo - /+ - bar +/ -baz +/ -// This q{is} a comment -// This /* is a */ comment - ----------------------------------------------------- - -[ - ["comment", "#!/usr/bin/env rdmd"], - ["comment", "/++/"], - ["comment", "/+ foo\r\nbar +/"], - ["comment", "/+ foo\r\n\t/+\r\n\tbar +/\r\nbaz +/"], - ["comment", "// This q{is} a comment"], - ["comment", "// This /* is a */ comment"] -] - ----------------------------------------------------- - -Checks for shebang and nestable multi-line comments. -Other comments are tested in clike. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/keyword_feature.test b/docs/_style/prism-master/tests/languages/d/keyword_feature.test deleted file mode 100644 index a5a03688..00000000 --- a/docs/_style/prism-master/tests/languages/d/keyword_feature.test +++ /dev/null @@ -1,251 +0,0 @@ -$ -abstract -alias -align -asm -assert -auto -body -bool -break -byte -case -cast -catch -cdouble -cent -cfloat -char -class; -const -continue -creal -dchar -debug -default -delegate -delete -deprecated -do -double -else -enum -export -extern -false -final -finally -float -for -foreach -foreach_reverse -function -goto -idouble -if -ifloat -immutable -import -inout -int -interface; -invariant -ireal -lazy -long -macro -mixin -module -new; -nothrow -null -out -override -package -pragma -private -protected -public -pure -real -ref -return -scope -shared -short -static -struct -super -switch -synchronized -template -this -throw -true -try -typedef -typeid -typeof -ubyte -ucent -uint -ulong -union -unittest -ushort -version -void -volatile -wchar -while -with -__FILE__ -__MODULE__ -__LINE__ -__FUNCTION__ -__PRETTY_FUNCTION__ -__DATE__ -__EOF__ -__TIME__ -__TIMESTAMP__ -__VENDOR__ -__VERSION__ -__gshared -__traits -__vector -__parameters -string -wstring -dstring -size_t -ptrdiff_t - ----------------------------------------------------- - -[ - ["keyword", "$"], - ["keyword", "abstract"], - ["keyword", "alias"], - ["keyword", "align"], - ["keyword", "asm"], - ["keyword", "assert"], - ["keyword", "auto"], - ["keyword", "body"], - ["keyword", "bool"], - ["keyword", "break"], - ["keyword", "byte"], - ["keyword", "case"], - ["keyword", "cast"], - ["keyword", "catch"], - ["keyword", "cdouble"], - ["keyword", "cent"], - ["keyword", "cfloat"], - ["keyword", "char"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], - ["keyword", "continue"], - ["keyword", "creal"], - ["keyword", "dchar"], - ["keyword", "debug"], - ["keyword", "default"], - ["keyword", "delegate"], - ["keyword", "delete"], - ["keyword", "deprecated"], - ["keyword", "do"], - ["keyword", "double"], - ["keyword", "else"], - ["keyword", "enum"], - ["keyword", "export"], - ["keyword", "extern"], - ["keyword", "false"], - ["keyword", "final"], - ["keyword", "finally"], - ["keyword", "float"], - ["keyword", "for"], - ["keyword", "foreach"], - ["keyword", "foreach_reverse"], - ["keyword", "function"], - ["keyword", "goto"], - ["keyword", "idouble"], - ["keyword", "if"], - ["keyword", "ifloat"], - ["keyword", "immutable"], - ["keyword", "import"], - ["keyword", "inout"], - ["keyword", "int"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "invariant"], - ["keyword", "ireal"], - ["keyword", "lazy"], - ["keyword", "long"], - ["keyword", "macro"], - ["keyword", "mixin"], - ["keyword", "module"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "nothrow"], - ["keyword", "null"], - ["keyword", "out"], - ["keyword", "override"], - ["keyword", "package"], - ["keyword", "pragma"], - ["keyword", "private"], - ["keyword", "protected"], - ["keyword", "public"], - ["keyword", "pure"], - ["keyword", "real"], - ["keyword", "ref"], - ["keyword", "return"], - ["keyword", "scope"], - ["keyword", "shared"], - ["keyword", "short"], - ["keyword", "static"], - ["keyword", "struct"], - ["keyword", "super"], - ["keyword", "switch"], - ["keyword", "synchronized"], - ["keyword", "template"], - ["keyword", "this"], - ["keyword", "throw"], - ["keyword", "true"], - ["keyword", "try"], - ["keyword", "typedef"], - ["keyword", "typeid"], - ["keyword", "typeof"], - ["keyword", "ubyte"], - ["keyword", "ucent"], - ["keyword", "uint"], - ["keyword", "ulong"], - ["keyword", "union"], - ["keyword", "unittest"], - ["keyword", "ushort"], - ["keyword", "version"], - ["keyword", "void"], - ["keyword", "volatile"], - ["keyword", "wchar"], - ["keyword", "while"], - ["keyword", "with"], - ["keyword", "__FILE__"], - ["keyword", "__MODULE__"], - ["keyword", "__LINE__"], - ["keyword", "__FUNCTION__"], - ["keyword", "__PRETTY_FUNCTION__"], - ["keyword", "__DATE__"], - ["keyword", "__EOF__"], - ["keyword", "__TIME__"], - ["keyword", "__TIMESTAMP__"], - ["keyword", "__VENDOR__"], - ["keyword", "__VERSION__"], - ["keyword", "__gshared"], - ["keyword", "__traits"], - ["keyword", "__vector"], - ["keyword", "__parameters"], - ["keyword", "string"], - ["keyword", "wstring"], - ["keyword", "dstring"], - ["keyword", "size_t"], - ["keyword", "ptrdiff_t"] -] - ----------------------------------------------------- - -Checks for $, keywords, special tokens and globally defined symbols. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/number_feature.test b/docs/_style/prism-master/tests/languages/d/number_feature.test deleted file mode 100644 index 817bd7e3..00000000 --- a/docs/_style/prism-master/tests/languages/d/number_feature.test +++ /dev/null @@ -1,55 +0,0 @@ -42 -42_000 -42L -42U -42UL -3.14_15_9 -3.2e8 -0.4e-7 -62.14e+4 - -0xBAD_FACE -0xFFU -0xfaL -0x42UL -0x2.AFp4 -0xFp-3 -0xFBp+9 - -0b0000_1111 - -6.3i -6.3fi -6.3Li - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "42_000"], - ["number", "42L"], - ["number", "42U"], - ["number", "42UL"], - ["number", "3.14_15_9"], - ["number", "3.2e8"], - ["number", "0.4e-7"], - ["number", "62.14e+4"], - - ["number", "0xBAD_FACE"], - ["number", "0xFFU"], - ["number", "0xfaL"], - ["number", "0x42UL"], - ["number", "0x2.AFp4"], - ["number", "0xFp-3"], - ["number", "0xFBp+9"], - - ["number", "0b0000_1111"], - - ["number", "6.3i"], - ["number", "6.3fi"], - ["number", "6.3Li"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/operator_feature.test b/docs/_style/prism-master/tests/languages/d/operator_feature.test deleted file mode 100644 index d7e291da..00000000 --- a/docs/_style/prism-master/tests/languages/d/operator_feature.test +++ /dev/null @@ -1,63 +0,0 @@ -|| -&& -++ --- -.. ... -=> -in is -!in !is -<< <<= ->> >>> >>= >>>= -^^ ^^= -<> !<> <>= !<>= -!< !<= -!> !>= -+ += -- -= -* *= -/ /= -% %= -& &= -| |= -^ ^= -~ ~= -= == -! != -< <= -> >= - ----------------------------------------------------- - -[ - ["operator", "||"], - ["operator", "&&"], - ["operator", "++"], - ["operator", "--"], - ["operator", ".."], ["operator", "..."], - ["operator", "=>"], - ["operator", "in"], ["operator", "is"], - ["operator", "!in"], ["operator", "!is"], - ["operator", "<<"], ["operator", "<<="], - ["operator", ">>"], ["operator", ">>>"], ["operator", ">>="], ["operator", ">>>="], - ["operator", "^^"], ["operator", "^^="], - ["operator", "<>"], ["operator", "!<>"], ["operator", "<>="], ["operator", "!<>="], - ["operator", "!<"], ["operator", "!<="], - ["operator", "!>"], ["operator", "!>="], - ["operator", "+"], ["operator", "+="], - ["operator", "-"], ["operator", "-="], - ["operator", "*"], ["operator", "*="], - ["operator", "/"], ["operator", "/="], - ["operator", "%"], ["operator", "%="], - ["operator", "&"], ["operator", "&="], - ["operator", "|"], ["operator", "|="], - ["operator", "^"], ["operator", "^="], - ["operator", "~"], ["operator", "~="], - ["operator", "="], ["operator", "=="], - ["operator", "!"], ["operator", "!="], - ["operator", "<"], ["operator", "<="], - ["operator", ">"], ["operator", ">="] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/property_feature.test b/docs/_style/prism-master/tests/languages/d/property_feature.test deleted file mode 100644 index 9e61b062..00000000 --- a/docs/_style/prism-master/tests/languages/d/property_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -@property int data() -@disable this(); - ----------------------------------------------------- - -[ - ["property", "@property"], - ["keyword", "int"], - ["function", "data"], ["punctuation", "("], ["punctuation", ")"], - ["property", "@disable"], - ["keyword", "this"], ["punctuation", "("], ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Check for properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/register_feature.test b/docs/_style/prism-master/tests/languages/d/register_feature.test deleted file mode 100644 index 3eb57c67..00000000 --- a/docs/_style/prism-master/tests/languages/d/register_feature.test +++ /dev/null @@ -1,75 +0,0 @@ -AL AH AX EAX -BL BH BX EBX -CL CH CX ECX -DL DH DX EDX -BP EBP -SP ESP -DI EDI -SI ESI -ES CS SS DS GS FS -CR0 CR2 CR3 CR4 -DR0 DR1 DR2 DR3 DR6 DR7 -TR3 TR4 TR5 TR6 TR7 -ST -ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7) -MM0 MM1 MM2 MM3 MM4 MM5 MM6 MM7 -XMM0 XMM1 XMM2 XMM3 XMM4 XMM5 XMM6 XMM7 - -RAX RBX RCX RDX -BPL RBP -SPL RSP -DIL RDI -SIL RSI -R8B R8W R8D R8 -R9B R9W R9D R9 -R10B R10W R10D R10 -R11B R11W R11D R11 -R12B R12W R12D R12 -R13B R13W R13D R13 -R14B R14W R14D R14 -R15B R15W R15D R15 -XMM8 XMM9 XMM10 XMM11 XMM12 XMM13 XMM14 XMM15 -YMM0 YMM1 YMM2 YMM3 YMM4 YMM5 YMM6 YMM7 -YMM8 YMM9 YMM10 YMM11 YMM12 YMM13 YMM14 YMM15 - ----------------------------------------------------- - -[ - ["register", "AL"], ["register", "AH"], ["register", "AX"], ["register", "EAX"], - ["register", "BL"], ["register", "BH"], ["register", "BX"], ["register", "EBX"], - ["register", "CL"], ["register", "CH"], ["register", "CX"], ["register", "ECX"], - ["register", "DL"], ["register", "DH"], ["register", "DX"], ["register", "EDX"], - ["register", "BP"], ["register", "EBP"], - ["register", "SP"], ["register", "ESP"], - ["register", "DI"], ["register", "EDI"], - ["register", "SI"], ["register", "ESI"], - ["register", "ES"], ["register", "CS"], ["register", "SS"], ["register", "DS"], ["register", "GS"], ["register", "FS"], - ["register", "CR0"], ["register", "CR2"], ["register", "CR3"], ["register", "CR4"], - ["register", "DR0"], ["register", "DR1"], ["register", "DR2"], ["register", "DR3"], ["register", "DR6"], ["register", "DR7"], - ["register", "TR3"], ["register", "TR4"], ["register", "TR5"], ["register", "TR6"], ["register", "TR7"], - ["register", "ST"], - ["register", "ST(0)"], ["register", "ST(1)"], ["register", "ST(2)"], ["register", "ST(3)"], ["register", "ST(4)"], ["register", "ST(5)"], ["register", "ST(6)"], ["register", "ST(7)"], - ["register", "MM0"], ["register", "MM1"], ["register", "MM2"], ["register", "MM3"], ["register", "MM4"], ["register", "MM5"], ["register", "MM6"], ["register", "MM7"], - ["register", "XMM0"], ["register", "XMM1"], ["register", "XMM2"], ["register", "XMM3"], ["register", "XMM4"], ["register", "XMM5"], ["register", "XMM6"], ["register", "XMM7"], - - ["register", "RAX"], ["register", "RBX"], ["register", "RCX"], ["register", "RDX"], - ["register", "BPL"], ["register", "RBP"], - ["register", "SPL"], ["register", "RSP"], - ["register", "DIL"], ["register", "RDI"], - ["register", "SIL"], ["register", "RSI"], - ["register", "R8B"], ["register", "R8W"], ["register", "R8D"], ["register", "R8"], - ["register", "R9B"], ["register", "R9W"], ["register", "R9D"], ["register", "R9"], - ["register", "R10B"], ["register", "R10W"], ["register", "R10D"], ["register", "R10"], - ["register", "R11B"], ["register", "R11W"], ["register", "R11D"], ["register", "R11"], - ["register", "R12B"], ["register", "R12W"], ["register", "R12D"], ["register", "R12"], - ["register", "R13B"], ["register", "R13W"], ["register", "R13D"], ["register", "R13"], - ["register", "R14B"], ["register", "R14W"], ["register", "R14D"], ["register", "R14"], - ["register", "R15B"], ["register", "R15W"], ["register", "R15D"], ["register", "R15"], - ["register", "XMM8"], ["register", "XMM9"], ["register", "XMM10"], ["register", "XMM11"], ["register", "XMM12"], ["register", "XMM13"], ["register", "XMM14"], ["register", "XMM15"], - ["register", "YMM0"], ["register", "YMM1"], ["register", "YMM2"], ["register", "YMM3"], ["register", "YMM4"], ["register", "YMM5"], ["register", "YMM6"], ["register", "YMM7"], - ["register", "YMM8"], ["register", "YMM9"], ["register", "YMM10"], ["register", "YMM11"], ["register", "YMM12"], ["register", "YMM13"], ["register", "YMM14"], ["register", "YMM15"] -] - ----------------------------------------------------- - -Checks for Iasm registers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/string_feature.test b/docs/_style/prism-master/tests/languages/d/string_feature.test deleted file mode 100644 index fc501b11..00000000 --- a/docs/_style/prism-master/tests/languages/d/string_feature.test +++ /dev/null @@ -1,56 +0,0 @@ -r"" x"" -r"fo\"o"c x"00 FBCD"w r"baz"d - -q"[fo"o -bar]" -q"(fo"o -bar)" -q"" -q"{fo"o -bar}" - -q"FOO -Bar "baz" -FOO" - -q"/fo"o -bar/" -q"|fo"o -bar|" - -'a' '\'' '\u000A' - -"" -"foo"c "bar"w "baz"d -"fo\"o -bar" -`foo` - ----------------------------------------------------- - -[ - ["string", "r\"\""], ["string", "x\"\""], - ["string", "r\"fo\\\"o\"c"], ["string", "x\"00 FBCD\"w"], ["string", "r\"baz\"d"], - - ["string", "q\"[fo\"o\r\nbar]\""], - ["string", "q\"(fo\"o\r\nbar)\""], - ["string", "q\"\""], - ["string", "q\"{fo\"o\r\nbar}\""], - - ["string", "q\"FOO\r\nBar \"baz\"\r\nFOO\""], - - ["string", "q\"/fo\"o\r\nbar/\""], - ["string", "q\"|fo\"o\r\nbar|\""], - - ["string", "'a'"], ["string", "'\\''"], ["string", "'\\u000A'"], - - ["string", "\"\""], - ["string", "\"foo\"c"], ["string", "\"bar\"w"], ["string", "\"baz\"d"], - ["string", "\"fo\\\"o\r\nbar\""], - ["string", "`foo`"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/d/token-string_feature.test b/docs/_style/prism-master/tests/languages/d/token-string_feature.test deleted file mode 100644 index 01627c4a..00000000 --- a/docs/_style/prism-master/tests/languages/d/token-string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -q{foo} -q{ q{bar} } - ----------------------------------------------------- - -[ - ["token-string", "q{foo}"], - ["token-string", "q{ q{bar} }"] -] - ----------------------------------------------------- - -Checks for token strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/dart/keyword_feature.test b/docs/_style/prism-master/tests/languages/dart/keyword_feature.test deleted file mode 100644 index a5e3c6c2..00000000 --- a/docs/_style/prism-master/tests/languages/dart/keyword_feature.test +++ /dev/null @@ -1,49 +0,0 @@ -async* sync* yield* -abstract assert async await -break case catch -class; -const -continue default deferred -do dynamic else enum -export external -extends; -factory final finally for -get if -implements; -import -in library -new; -null -operator part rethrow return -set static super switch this -throw try typedef var -void while with yield - ----------------------------------------------------- - -[ - ["keyword", "async*"], ["keyword", "sync*"], ["keyword", "yield*"], - ["keyword", "abstract"], ["keyword", "assert"], ["keyword", "async"], ["keyword", "await"], - ["keyword", "break"], ["keyword", "case"], ["keyword", "catch"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], - ["keyword", "continue"], ["keyword", "default"], ["keyword", "deferred"], - ["keyword", "do"], ["keyword", "dynamic"], ["keyword", "else"], ["keyword", "enum"], - ["keyword", "export"], ["keyword", "external"], - ["keyword", "extends"], ["punctuation", ";"], - ["keyword", "factory"], ["keyword", "final"], ["keyword", "finally"], ["keyword", "for"], - ["keyword", "get"], ["keyword", "if"], - ["keyword", "implements"], ["punctuation", ";"], - ["keyword", "import"], - ["keyword", "in"], ["keyword", "library"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "null"], - ["keyword", "operator"], ["keyword", "part"], ["keyword", "rethrow"], ["keyword", "return"], - ["keyword", "set"], ["keyword", "static"], ["keyword", "super"], ["keyword", "switch"], ["keyword", "this"], - ["keyword", "throw"], ["keyword", "try"], ["keyword", "typedef"], ["keyword", "var"], - ["keyword", "void"], ["keyword", "while"], ["keyword", "with"], ["keyword", "yield"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/dart/metadata_feature.test b/docs/_style/prism-master/tests/languages/dart/metadata_feature.test deleted file mode 100644 index e88e7ba1..00000000 --- a/docs/_style/prism-master/tests/languages/dart/metadata_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -@deprecated -@override -@todo('seth', 'make this do something') - ----------------------------------------------------- - -[ - ["metadata", "@deprecated"], - ["metadata", "@override"], - ["metadata", "@todo"], - ["punctuation", "("], - ["string", "'seth'"], - ["punctuation", ","], - ["string", "'make this do something'"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for metadata. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/dart/operator_feature.test b/docs/_style/prism-master/tests/languages/dart/operator_feature.test deleted file mode 100644 index 5d631fd0..00000000 --- a/docs/_style/prism-master/tests/languages/dart/operator_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -++ -- -* / % ~/ -+ - ! ~ -<< >> ? -& ^ | ->= > <= < -as is is! -== != && || -= *= /= ~/= -%= += -= -<<= >>= -&= ^= |= - ----------------------------------------------------- - -[ - ["operator", "++"], ["operator", "--"], - ["operator", "*"], ["operator", "/"], ["operator", "%"], ["operator", "~/"], - ["operator", "+"], ["operator", "-"], ["operator", "!"], ["operator", "~"], - ["operator", "<<"], ["operator", ">>"], ["operator", "?"], - ["operator", "&"], ["operator", "^"], ["operator", "|"], - ["operator", ">="], ["operator", ">"], ["operator", "<="], ["operator", "<"], - ["operator", "as"], ["operator", "is"], ["operator", "is!"], - ["operator", "=="], ["operator", "!="], ["operator", "&&"], ["operator", "||"], - ["operator", "="], ["operator", "*="], ["operator", "/="], ["operator", "~/="], - ["operator", "%="], ["operator", "+="], ["operator", "-="], - ["operator", "<<="], ["operator", ">>="], - ["operator", "&="], ["operator", "^="], ["operator", "|="] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/dart/string_feature.test b/docs/_style/prism-master/tests/languages/dart/string_feature.test deleted file mode 100644 index feffdbce..00000000 --- a/docs/_style/prism-master/tests/languages/dart/string_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -"" '' -r"" r'' -"""""" '''''' -r"""""" r'''''' -"fo\"o" 'fo\'o' -"""foo -bar""" -'''foo -bar''' - ----------------------------------------------------- - -[ - ["string", "\"\""], ["string", "''"], - ["string", "r\"\""], ["string", "r''"], - ["string", "\"\"\"\"\"\""], ["string", "''''''"], - ["string", "r\"\"\"\"\"\""], ["string", "r''''''"], - ["string", "\"fo\\\"o\""], ["string", "'fo\\'o'"], - ["string", "\"\"\"foo\r\nbar\"\"\""], ["string", "'''foo\r\nbar'''"] -] - ----------------------------------------------------- - -Checks for single quoted and double quoted strings, -multi-line strings and "raw" strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/diff/coord_feature.test b/docs/_style/prism-master/tests/languages/diff/coord_feature.test deleted file mode 100644 index c082693f..00000000 --- a/docs/_style/prism-master/tests/languages/diff/coord_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -7c7 - -*** 4,8 **** ---- 4,8 ---- - -@@ -4,5 +4,5 @@ - ----------------------------------------------------- - -[ - ["coord", "7c7"], - - ["coord", "*** 4,8 ****"], - ["coord", "--- 4,8 ----"], - - ["coord", "@@ -4,5 +4,5 @@"] -] - ----------------------------------------------------- - -Checks for coords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/diff/diff_feature.test b/docs/_style/prism-master/tests/languages/diff/diff_feature.test deleted file mode 100644 index 0e534900..00000000 --- a/docs/_style/prism-master/tests/languages/diff/diff_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -! qt: core - -- qt: core -+ qt: core gui - -< qt: core -> qt: core quick - ----------------------------------------------------- - -[ - ["diff", "! qt: core"], - ["deleted", "- qt: core"], - ["inserted", "+ qt: core gui"], - ["deleted", "< qt: core"], - ["inserted", "> qt: core quick"] -] - ----------------------------------------------------- - -Checks for deleted, inserted and different lines. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/django/comment_feature.test b/docs/_style/prism-master/tests/languages/django/comment_feature.test deleted file mode 100644 index d41c4489..00000000 --- a/docs/_style/prism-master/tests/languages/django/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -{##} -{# This a Django template example #} -{# Multi-line -comment #} - ----------------------------------------------------- - -[ - ["comment", "{##}"], - ["comment", "{# This a Django template example #}"], - ["comment", "{# Multi-line\r\ncomment #}"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/django/property_feature.test b/docs/_style/prism-master/tests/languages/django/property_feature.test deleted file mode 100644 index adbe3c4b..00000000 --- a/docs/_style/prism-master/tests/languages/django/property_feature.test +++ /dev/null @@ -1,64 +0,0 @@ -{% extends "base_generic.html" %} -{% block body %} - -{{ story.headline|upper }} - -{% endblock %} - ----------------------------------------------------- - -[ - ["property", [ - "{% ", - ["keyword", "extends"], - ["string", "\"base_generic.html\""], - " %}" - ]], - ["property", [ - "{% ", - ["keyword", "block"], - ["variable", "body"], - " %}" - ]], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "a" - ]], - ["attr-name", ["href=\""]], - ["property", [ - "{{ ", - ["variable", "story"], - ["punctuation", "."], - ["variable", "get_absolute_url"], - " }}" - ]], - ["attr-name", ["\""]], - ["punctuation", ">"] - ]], - ["property", [ - "{{ ", - ["variable", "story"], - ["punctuation", "."], - ["variable", "headline"], - ["keyword", "|"], - ["function", "upper"], - " }}" - ]], - ["tag", [ - ["tag", [ - ["punctuation", ""] - ]], - ["property", [ - "{% ", - ["keyword", "endblock"], - " %}" - ]] -] - ----------------------------------------------------- - -Checks for properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/docker/comment_feature.test b/docs/_style/prism-master/tests/languages/docker/comment_feature.test deleted file mode 100644 index 054d6023..00000000 --- a/docs/_style/prism-master/tests/languages/docker/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -# -# foobar - ----------------------------------------------------- - -[ - ["comment", "#"], - ["comment", "# foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/docker/keyword_feature.test b/docs/_style/prism-master/tests/languages/docker/keyword_feature.test deleted file mode 100644 index a36a7e00..00000000 --- a/docs/_style/prism-master/tests/languages/docker/keyword_feature.test +++ /dev/null @@ -1,45 +0,0 @@ -ONBUILD ADD . /app/src -FROM ubuntu -MAINTAINER SvenDowideit@home.org.au -RUN cd /tmp -EXPOSE 5900 -ENV myName John Doe -COPY hom* /mydir/ -VOLUME /myvol -USER daemon -WORKDIR /a -HEALTHCHECK CMD echo "foo" -LABEL version="1.0" -ENTRYPOINT ["top", "-b"] -ARG user1 -SHELL ["powershell", "-command"] -STOPSIGNAL signal - ----------------------------------------------------- - -[ - ["keyword", "ONBUILD"], ["keyword", "ADD"], " . /app/src\r\n", - ["keyword", "FROM"], " ubuntu\r\n", - ["keyword", "MAINTAINER"], " SvenDowideit@home.org.au\r\n", - ["keyword", "RUN"], " cd /tmp\r\n", - ["keyword", "EXPOSE"], " 5900\r\n", - ["keyword", "ENV"], " myName John Doe\r\n", - ["keyword", "COPY"], " hom* /mydir/\r\n", - ["keyword", "VOLUME"], " /myvol\r\n", - ["keyword", "USER"], " daemon\r\n", - ["keyword", "WORKDIR"], " /a\r\n", - ["keyword", "HEALTHCHECK"], ["keyword", "CMD"], " echo ", ["string", "\"foo\""], - ["keyword", "LABEL"], " version=", ["string", "\"1.0\""], - ["keyword", "ENTRYPOINT"], - ["punctuation", "["], ["string", "\"top\""], ["punctuation", ","], - ["string", "\"-b\""], ["punctuation", "]"], - ["keyword", "ARG"], " user1\r\n", - ["keyword", "SHELL"], - ["punctuation", "["], ["string", "\"powershell\""], ["punctuation", ","], - ["string", "\"-command\""], ["punctuation", "]"], - ["keyword", "STOPSIGNAL"], " signal" -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/docker/string_feature.test b/docs/_style/prism-master/tests/languages/docker/string_feature.test deleted file mode 100644 index e131f021..00000000 --- a/docs/_style/prism-master/tests/languages/docker/string_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -"" -"fo\"obar" -"foo\ -bar" -'' -'fo\'obar' -'foo\ -bar' - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\\\"obar\""], - ["string", "\"foo\\\r\nbar\""], - ["string", "''"], - ["string", "'fo\\'obar'"], - ["string", "'foo\\\r\nbar'"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/boolean_feature.test b/docs/_style/prism-master/tests/languages/eiffel/boolean_feature.test deleted file mode 100644 index 5750be06..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -True -False - ----------------------------------------------------- - -[ - ["boolean", "True"], - ["boolean", "False"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/char_feature.test b/docs/_style/prism-master/tests/languages/eiffel/char_feature.test deleted file mode 100644 index 6a9b1248..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/char_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -'a' -'%'' -'%/123/' - ----------------------------------------------------- - -[ - ["char", "'a'"], - ["char", "'%''"], - ["char", "'%/123/'"] -] - ----------------------------------------------------- - -Checks for chars. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/class-name_feature.test b/docs/_style/prism-master/tests/languages/eiffel/class-name_feature.test deleted file mode 100644 index a7e15a6b..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/class-name_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -A -FOO -B4D_F4C3 - ----------------------------------------------------- - -[ - ["class-name", "A"], - ["class-name", "FOO"], - ["class-name", "B4D_F4C3"] -] - ----------------------------------------------------- - -Checks for class names. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/comment_feature.test b/docs/_style/prism-master/tests/languages/eiffel/comment_feature.test deleted file mode 100644 index 8a6b93e3..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ --- --- foo bar --- "foo" bar - ----------------------------------------------------- - -[ - ["comment", "--"], - ["comment", "-- foo bar"], - ["comment", "-- \"foo\" bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/keyword_feature.test b/docs/_style/prism-master/tests/languages/eiffel/keyword_feature.test deleted file mode 100644 index cb6ae298..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/keyword_feature.test +++ /dev/null @@ -1,39 +0,0 @@ -across agent alias all and -attached as assign attribute -check class convert create -Current debug deferred detachable -do else elseif end ensure -expanded export external -feature from frozen if -implies inherit inspect -invariant like local loop -not note obsolete old -once or Precursor redefine -rename require rescue Result -retry select separate some -then undefine until variant -Void when xor - ----------------------------------------------------- - -[ - ["keyword", "across"], ["keyword", "agent"], ["keyword", "alias"], ["keyword", "all"], ["keyword", "and"], - ["keyword", "attached"], ["keyword", "as"], ["keyword", "assign"], ["keyword", "attribute"], - ["keyword", "check"], ["keyword", "class"], ["keyword", "convert"], ["keyword", "create"], - ["keyword", "Current"], ["keyword", "debug"], ["keyword", "deferred"], ["keyword", "detachable"], - ["keyword", "do"], ["keyword", "else"], ["keyword", "elseif"], ["keyword", "end"], ["keyword", "ensure"], - ["keyword", "expanded"], ["keyword", "export"], ["keyword", "external"], - ["keyword", "feature"], ["keyword", "from"], ["keyword", "frozen"], ["keyword", "if"], - ["keyword", "implies"], ["keyword", "inherit"], ["keyword", "inspect"], - ["keyword", "invariant"], ["keyword", "like"], ["keyword", "local"], ["keyword", "loop"], - ["keyword", "not"], ["keyword", "note"], ["keyword", "obsolete"], ["keyword", "old"], - ["keyword", "once"], ["keyword", "or"], ["keyword", "Precursor"], ["keyword", "redefine"], - ["keyword", "rename"], ["keyword", "require"], ["keyword", "rescue"], ["keyword", "Result"], - ["keyword", "retry"], ["keyword", "select"], ["keyword", "separate"], ["keyword", "some"], - ["keyword", "then"], ["keyword", "undefine"], ["keyword", "until"], ["keyword", "variant"], - ["keyword", "Void"], ["keyword", "when"], ["keyword", "xor"] -] - ----------------------------------------------------- - -Checks for all keywords \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/number_feature.test b/docs/_style/prism-master/tests/languages/eiffel/number_feature.test deleted file mode 100644 index beb3cc7c..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/number_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -0xbabe -0xBAD_A55 -0c7654_1234 -0b1111_0000_0101 -42 -42. -.42 -1_845.123_456 -3.14e+4 -4_2.5_7e-1_0 - ----------------------------------------------------- - -[ - ["number", "0xbabe"], - ["number", "0xBAD_A55"], - ["number", "0c7654_1234"], - ["number", "0b1111_0000_0101"], - ["number", "42"], - ["number", "42."], - ["number", ".42"], - ["number", "1_845.123_456"], - ["number", "3.14e+4"], - ["number", "4_2.5_7e-1_0"] -] - ----------------------------------------------------- - -Checks for hexadecimal, octal, binary and decimal numbers. diff --git a/docs/_style/prism-master/tests/languages/eiffel/operator_feature.test b/docs/_style/prism-master/tests/languages/eiffel/operator_feature.test deleted file mode 100644 index fcba4b34..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/operator_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -\\ |..| .. // -/~ / /= -< <= > >= -+ - * -^ = ~ - ----------------------------------------------------- - -[ - ["operator", "\\\\"], ["operator", "|..|"], ["operator", ".."], ["operator", "//"], - ["operator", "/~"], ["operator", "/"], ["operator", "/="], - ["operator", "<"], ["operator", "<="], ["operator", ">"], ["operator", ">="], - ["operator", "+"], ["operator", "-"], ["operator", "*"], - ["operator", "^"], ["operator", "="], ["operator", "~"] -] - ----------------------------------------------------- - -Checks for all operators \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/eiffel/string_feature.test b/docs/_style/prism-master/tests/languages/eiffel/string_feature.test deleted file mode 100644 index 4b6692f8..00000000 --- a/docs/_style/prism-master/tests/languages/eiffel/string_feature.test +++ /dev/null @@ -1,34 +0,0 @@ -"" -"fo%"o" -"foo% -%bar" - -"[]" -"[fo"o -bar]" -"!-[fo"o[] -bar]!-" -"{}" -"{fo"o -bar}" -"*?{fo"o{} -bar}*?" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo%\"o\""], - ["string", "\"foo%\r\n%bar\""], - - ["string", "\"[]\""], - ["string", "\"[fo\"o\r\nbar]\""], - ["string", "\"!-[fo\"o[]\r\nbar]!-\""], - ["string", "\"{}\""], - ["string", "\"{fo\"o\r\nbar}\""], - ["string", "\"*?{fo\"o{}\r\nbar}*?\""] -] - ----------------------------------------------------- - -Checks for strings, multi-line strings, and aligned and non aligned verbatim strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/atom_feature.test b/docs/_style/prism-master/tests/languages/elixir/atom_feature.test deleted file mode 100644 index 5b1c47d2..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/atom_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -:true -:false -:FooBar42 - ----------------------------------------------------- - -[ - ["atom", ":true"], - ["atom", ":false"], - ["atom", ":FooBar42"] -] - ----------------------------------------------------- - -Checks for atoms. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/attr-name_feature.test b/docs/_style/prism-master/tests/languages/elixir/attr-name_feature.test deleted file mode 100644 index de7cadc8..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/attr-name_feature.test +++ /dev/null @@ -1,24 +0,0 @@ -[a: 1, b: 2] -do: :this, else: :that -where: foo, -select: bar - ----------------------------------------------------- - -[ - ["punctuation", "["], - ["attr-name", "a:"], - ["number", "1"], ["punctuation", ","], - ["attr-name", "b:"], - ["number", "2"], ["punctuation", "]"], - - ["attr-name", "do:"], ["atom", ":this"], - ["punctuation", ","], - ["attr-name", "else:"], ["atom", ":that"], - ["attr-name", "where:"], " foo", ["punctuation", ","], - ["attr-name", "select:"], " bar" -] - ----------------------------------------------------- - -Checks for keyword list keys. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/attribute_feature.test b/docs/_style/prism-master/tests/languages/elixir/attribute_feature.test deleted file mode 100644 index 273c2a40..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/attribute_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -@vsn 2 -@moduledoc """ -foobar -""" -@tag :external - ----------------------------------------------------- - -[ - ["attribute", "@vsn"], ["number", "2"], - ["attribute", "@moduledoc"], ["string", [ - "\"\"\"\r\nfoobar\r\n\"\"\"" - ]], - ["attribute", "@tag"], ["atom", ":external"] -] - ----------------------------------------------------- - -Checks for module attributes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/boolean_feature.test b/docs/_style/prism-master/tests/languages/elixir/boolean_feature.test deleted file mode 100644 index aa949797..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/boolean_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -true -false -nil - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"], - ["boolean", "nil"] -] - ----------------------------------------------------- - -Checks for booleans and nil. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/capture_feature.test b/docs/_style/prism-master/tests/languages/elixir/capture_feature.test deleted file mode 100644 index 8a64b661..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/capture_feature.test +++ /dev/null @@ -1,28 +0,0 @@ -fun = &Math.zero?/1 -(&is_function/1).(fun) -fun = &(&1 + 1) -&List.flatten(&1, &2) - ----------------------------------------------------- - -[ - "fun ", ["operator", "="], - ["capture", "&Math.zero?/1"], - ["punctuation", "("], - ["capture", "&is_function/1"], - ["punctuation", ")"], - ["punctuation", "."], - ["punctuation", "("], "fun", ["punctuation", ")"], - "\r\nfun ", ["operator", "="], - ["capture", "&"], - ["punctuation", "("], ["argument", "&1"], - ["operator", "+"], ["number", "1"], ["punctuation", ")"], - ["capture", "&List.flatten"], - ["punctuation", "("], ["argument", "&1"], - ["punctuation", ","], ["argument", "&2"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for function capturing and arguments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/comment_feature.test b/docs/_style/prism-master/tests/languages/elixir/comment_feature.test deleted file mode 100644 index 7a7981d9..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -# -# Foobar -#{ This is a comment - ----------------------------------------------------- - -[ - ["comment", "#"], - ["comment", "# Foobar"], - ["comment", "#{ This is a comment"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/issue1392.test b/docs/_style/prism-master/tests/languages/elixir/issue1392.test deleted file mode 100644 index c7d20a83..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/issue1392.test +++ /dev/null @@ -1,16 +0,0 @@ -String.upcase(@fixed) - ----------------------------------------------------- - -[ - "String", - ["punctuation", "."], - "upcase", - ["punctuation", "("], - ["attribute", "@fixed"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Ensure module attributes don't consume punctuation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/issue775.test b/docs/_style/prism-master/tests/languages/elixir/issue775.test deleted file mode 100644 index d3243f80..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/issue775.test +++ /dev/null @@ -1,17 +0,0 @@ -@doc """ -## Parameters -""" - ----------------------------------------------------- - -[ - ["attribute", "@doc"], - ["string", [ - "\"\"\"\r\n## Parameters\r\n\"\"\"" - ]] -] - ----------------------------------------------------- - -Ensures that markdown headers are not highlighted as comments inside strings. -See #775 for details. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/keyword_feature.test b/docs/_style/prism-master/tests/languages/elixir/keyword_feature.test deleted file mode 100644 index e1b02a1a..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/keyword_feature.test +++ /dev/null @@ -1,31 +0,0 @@ -after alias and case -catch cond def -defcallback -defexception -defimpl defmodule -defp defprotocol -defstruct do else -end fn for if -import not or -require rescue try -unless use when - ----------------------------------------------------- - -[ - ["keyword", "after"], ["keyword", "alias"], ["keyword", "and"], ["keyword", "case"], - ["keyword", "catch"], ["keyword", "cond"], ["keyword", "def"], - ["keyword", "defcallback"], - ["keyword", "defexception"], - ["keyword", "defimpl"], ["keyword", "defmodule"], - ["keyword", "defp"], ["keyword", "defprotocol"], - ["keyword", "defstruct"], ["keyword", "do"], ["keyword", "else"], - ["keyword", "end"], ["keyword", "fn"], ["keyword", "for"], ["keyword", "if"], - ["keyword", "import"], ["keyword", "not"], ["keyword", "or"], - ["keyword", "require"], ["keyword", "rescue"], ["keyword", "try"], - ["keyword", "unless"], ["keyword", "use"], ["keyword", "when"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/number_feature.test b/docs/_style/prism-master/tests/languages/elixir/number_feature.test deleted file mode 100644 index ae06684b..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/number_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -0b1111_0000 -0o754_123 -0xBadFace -42 -42_000 -3.14159 -2e6 -3.241_753E-7 -0.7e+15 - ----------------------------------------------------- - -[ - ["number", "0b1111_0000"], - ["number", "0o754_123"], - ["number", "0xBadFace"], - ["number", "42"], - ["number", "42_000"], - ["number", "3.14159"], - ["number", "2e6"], - ["number", "3.241_753E-7"], - ["number", "0.7e+15"] -] - ----------------------------------------------------- - -Checks for binary, octal, hexadecimal and decimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/operator_feature.test b/docs/_style/prism-master/tests/languages/elixir/operator_feature.test deleted file mode 100644 index c97dcce5..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/operator_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -in -\\ -:: -.. -... -+ ++ -- -- -> -* -/ -^ -& && -| || |> -< <= <> <- -> >= -! != !== -= =~ == === => - ----------------------------------------------------- - -[ - ["operator", "in"], - ["operator", "\\\\"], - ["operator", "::"], - ["operator", ".."], - ["operator", "..."], - ["operator", "+"], ["operator", "++"], - ["operator", "-"], ["operator", "--"], ["operator", "->"], - ["operator", "*"], - ["operator", "/"], - ["operator", "^"], - ["operator", "&"], ["operator", "&&"], - ["operator", "|"], ["operator", "||"], ["operator", "|>"], - ["operator", "<"], ["operator", "<="], ["operator", "<>"], ["operator", "<-"], - ["operator", ">"], ["operator", ">="], - ["operator", "!"], ["operator", "!="], ["operator", "!=="], - ["operator", "="], ["operator", "=~"], ["operator", "=="], ["operator", "==="], ["operator", "=>"] -] - ----------------------------------------------------- - -Checks for all operators. diff --git a/docs/_style/prism-master/tests/languages/elixir/regex_feature.test b/docs/_style/prism-master/tests/languages/elixir/regex_feature.test deleted file mode 100644 index 81eb31a6..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/regex_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -~r"""foobar"""im -~R'''foobar'''ux -~r/fo\/obar/smfr -~R|fo\|obar|uismxfr -~r"fo\"obar"x -~R'fo\'obar's -~r(fo\)obar) -~R[fo\]obar] -~r{fo\}obar} -~Robar> - ----------------------------------------------------- - -[ - ["regex", "~r\"\"\"foobar\"\"\"im"], - ["regex", "~R'''foobar'''ux"], - ["regex", "~r/fo\\/obar/smfr"], - ["regex", "~R|fo\\|obar|uismxfr"], - ["regex", "~r\"fo\\\"obar\"x"], - ["regex", "~R'fo\\'obar's"], - ["regex", "~r(fo\\)obar)"], - ["regex", "~R[fo\\]obar]"], - ["regex", "~r{fo\\}obar}"], - ["regex", "~Robar>"] -] - ----------------------------------------------------- - -Checks for regexes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elixir/string_feature.test b/docs/_style/prism-master/tests/languages/elixir/string_feature.test deleted file mode 100644 index ca54f4be..00000000 --- a/docs/_style/prism-master/tests/languages/elixir/string_feature.test +++ /dev/null @@ -1,115 +0,0 @@ -~s/foobar/ -~s/foo#{42}bar/ -~S|foobar| -~c"foobar" -~c"foo#{42}bar" -~C'foobar' -~w(foobar)c -~w(foo#{42}bar)s -~W[foobar]a -~s{foobar} -~s{foo#{42}bar} -~S - -""" -Foo bar -""" - -''' -Foo bar -''' - -~S""" -Foo bar -""" - -~c""" -Foo bar -""" - -~w""" -Foo bar -""" - -"" -"foo" -"fo\"o\ -#{42}bar" -'' -'foo' -'fo\'o\ -bar' - ----------------------------------------------------- - -[ - ["string", ["~s/foobar/"]], - ["string", [ - "~s/foo", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "bar/" - ]], - ["string", ["~S|foobar|"]], - ["string", ["~c\"foobar\""]], - ["string", [ - "~c\"foo", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "bar\"" - ]], - ["string", ["~C'foobar'"]], - ["string", ["~w(foobar)c"]], - ["string", [ - "~w(foo", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "bar)s" - ]], - ["string", ["~W[foobar]a"]], - ["string", ["~s{foobar}"]], - ["string", [ - "~s{foo", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "bar}" - ]], - ["string", ["~S"]], - - ["string", ["\"\"\"\r\nFoo bar\r\n\"\"\""]], - ["string", ["'''\r\nFoo bar\r\n'''"]], - ["string", ["~S\"\"\"\r\nFoo bar\r\n\"\"\""]], - ["string", ["~c\"\"\"\r\nFoo bar\r\n\"\"\""]], - ["string", ["~w\"\"\"\r\nFoo bar\r\n\"\"\""]], - - ["string", ["\"\""]], - ["string", ["\"foo\""]], - ["string", [ - "\"fo\\\"o\\\r\n", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "bar\"" - ]], - ["string", ["''"]], - ["string", ["'foo'"]], - ["string", ["'fo\\'o\\\r\nbar'"]] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elm/builtin_feature.test b/docs/_style/prism-master/tests/languages/elm/builtin_feature.test deleted file mode 100644 index da96c1dd..00000000 --- a/docs/_style/prism-master/tests/languages/elm/builtin_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -abs acos always asin atan atan2 -ceiling clamp compare cos curry -degrees e flip floor fromPolar -identity isInfinite isNaN -logBase max min negate never -not pi radians rem round sin -sqrt tan toFloat toPolar toString -truncate turns uncurry xor - ----------------------------------------------------- - -[ - ["builtin", "abs"], ["builtin", "acos"], ["builtin", "always"], ["builtin", "asin"], ["builtin", "atan"], ["builtin", "atan2"], - ["builtin", "ceiling"], ["builtin", "clamp"], ["builtin", "compare"], ["builtin", "cos"], ["builtin", "curry"], - ["builtin", "degrees"], ["builtin", "e"], ["builtin", "flip"], ["builtin", "floor"], ["builtin", "fromPolar"], - ["builtin", "identity"], ["builtin", "isInfinite"], ["builtin", "isNaN"], - ["builtin", "logBase"], ["builtin", "max"], ["builtin", "min"], ["builtin", "negate"], ["builtin", "never"], - ["builtin", "not"], ["builtin", "pi"], ["builtin", "radians"], ["builtin", "rem"], ["builtin", "round"], ["builtin", "sin"], - ["builtin", "sqrt"], ["builtin", "tan"], ["builtin", "toFloat"], ["builtin", "toPolar"], ["builtin", "toString"], - ["builtin", "truncate"], ["builtin", "turns"], ["builtin", "uncurry"], ["builtin", "xor"] -] - ----------------------------------------------------- - -Checks for all builtin. diff --git a/docs/_style/prism-master/tests/languages/elm/char_feature.test b/docs/_style/prism-master/tests/languages/elm/char_feature.test deleted file mode 100644 index c4d33fcd..00000000 --- a/docs/_style/prism-master/tests/languages/elm/char_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -'a' -'\'' -'\n' -'\23' -'\xFE' - ----------------------------------------------------- - -[ - ["char", "'a'"], - ["char", "'\\''"], - ["char", "'\\n'"], - ["char", "'\\23'"], - ["char", "'\\xFE'"] -] - ----------------------------------------------------- - -Checks for chars. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elm/comment_feature.test b/docs/_style/prism-master/tests/languages/elm/comment_feature.test deleted file mode 100644 index d8c8cdda..00000000 --- a/docs/_style/prism-master/tests/languages/elm/comment_feature.test +++ /dev/null @@ -1,14 +0,0 @@ --- foo -{- foo -bar -} - ----------------------------------------------------- - -[ - ["comment", "-- foo"], - ["comment", "{- foo\r\nbar -}"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elm/constant_feature.test b/docs/_style/prism-master/tests/languages/elm/constant_feature.test deleted file mode 100644 index 06f25f10..00000000 --- a/docs/_style/prism-master/tests/languages/elm/constant_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -Foo -Foo.Bar -Baz.Foobar_42 - ----------------------------------------------------- - -[ - ["constant", "Foo"], - ["constant", "Foo.Bar"], - ["constant", "Baz.Foobar_42"] -] - ----------------------------------------------------- - -Checks for constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elm/hvariable_feature.test b/docs/_style/prism-master/tests/languages/elm/hvariable_feature.test deleted file mode 100644 index defa3ba7..00000000 --- a/docs/_style/prism-master/tests/languages/elm/hvariable_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo -Foo.bar -Baz.foobar_42 - ----------------------------------------------------- - -[ - ["hvariable", "foo"], - ["hvariable", "Foo.bar"], - ["hvariable", "Baz.foobar_42"] -] - ----------------------------------------------------- - -Checks for hvariables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/elm/import_statement_feature.test b/docs/_style/prism-master/tests/languages/elm/import_statement_feature.test deleted file mode 100644 index c2940362..00000000 --- a/docs/_style/prism-master/tests/languages/elm/import_statement_feature.test +++ /dev/null @@ -1,48 +0,0 @@ -import Foo -import Foo_42.Bar as Foobar -import Foo.Bar as Foo.Baz -import List exposing (map) -import Json.Decode as Json exposing (Decoder) - ----------------------------------------------------- - -[ - ["import_statement", [ - ["keyword", "import"], - " Foo" - ]], - ["import_statement", [ - ["keyword", "import"], - " Foo_42.Bar ", - ["keyword", "as"], - " Foobar" - ]], - ["import_statement", [ - ["keyword", "import"], - " Foo.Bar ", - ["keyword", "as"], - " Foo.Baz" - ]], - ["import_statement", [ - ["keyword", "import"], - " List ", - ["keyword", "exposing"] - ]], - ["punctuation", "("], - ["hvariable", "map"], - ["punctuation", ")"], - ["import_statement", [ - ["keyword", "import"], - " Json.Decode ", - ["keyword", "as"], - " Json ", - ["keyword", "exposing"] - ]], - ["punctuation", "("], - ["constant", "Decoder"], - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for import statement. diff --git a/docs/_style/prism-master/tests/languages/elm/keyword_feature.test b/docs/_style/prism-master/tests/languages/elm/keyword_feature.test deleted file mode 100644 index 6f1132a4..00000000 --- a/docs/_style/prism-master/tests/languages/elm/keyword_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -alias as case else -exposing if in -infixl infixr let -module of then -type - ----------------------------------------------------- - -[ - ["keyword", "alias"], ["keyword", "as"], ["keyword", "case"], ["keyword", "else"], - ["keyword", "exposing"], ["keyword", "if"], ["keyword", "in"], - ["keyword", "infixl"], ["keyword", "infixr"], ["keyword", "let"], - ["keyword", "module"], ["keyword", "of"], ["keyword", "then"], - ["keyword", "type"] -] - ----------------------------------------------------- - -Checks for all keywords. diff --git a/docs/_style/prism-master/tests/languages/elm/number_feature.test b/docs/_style/prism-master/tests/languages/elm/number_feature.test deleted file mode 100644 index c27dae9a..00000000 --- a/docs/_style/prism-master/tests/languages/elm/number_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -42 -3.14159 -2E3 -1.2e-4 -0.9e+1 -0xBadFace - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "2E3"], - ["number", "1.2e-4"], - ["number", "0.9e+1"], - ["number", "0xBadFace"] -] - ----------------------------------------------------- - -Checks for decimal and hexadecimal numbers. diff --git a/docs/_style/prism-master/tests/languages/elm/operator_feature.test b/docs/_style/prism-master/tests/languages/elm/operator_feature.test deleted file mode 100644 index ce120ec6..00000000 --- a/docs/_style/prism-master/tests/languages/elm/operator_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -.. -reverse . sort -+ - * / -^ ^^ ** -&& || -< <= == /= ->= > | -++ : !! -<- -> -= :: => ->> >>= >@> -~ ! @ - ----------------------------------------------------- - -[ - ["operator", ".."], - ["hvariable", "reverse"], ["operator", " . "], ["hvariable", "sort"], - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], - ["operator", "^"], ["operator", "^^"], ["operator", "**"], - ["operator", "&&"], ["operator", "||"], - ["operator", "<"], ["operator", "<="], ["operator", "=="], ["operator", "/="], - ["operator", ">="], ["operator", ">"], ["operator", "|"], - ["operator", "++"], ["operator", ":"], ["operator", "!!"], - ["operator", "<-"], ["operator", "->"], - ["operator", "="], ["operator", "::"], ["operator", "=>"], - ["operator", ">>"], ["operator", ">>="], ["operator", ">@>"], - ["operator", "~"], ["operator", "!"], ["operator", "@"] -] - ----------------------------------------------------- - -Checks for operators. diff --git a/docs/_style/prism-master/tests/languages/elm/string_feature.test b/docs/_style/prism-master/tests/languages/elm/string_feature.test deleted file mode 100644 index 6b5ea8dd..00000000 --- a/docs/_style/prism-master/tests/languages/elm/string_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -"" -"regular string" -"fo\"o" -"""foo - bar""" -"""foo -- comment - bar""" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"regular string\""], - ["string", "\"fo\\\"o\""], - ["string", "\"\"\"foo\r\n bar\"\"\""], - ["string", "\"\"\"foo -- comment\r\n bar\"\"\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erb/erb_feature.test b/docs/_style/prism-master/tests/languages/erb/erb_feature.test deleted file mode 100644 index cb86983d..00000000 --- a/docs/_style/prism-master/tests/languages/erb/erb_feature.test +++ /dev/null @@ -1,36 +0,0 @@ -<%# comment %> -<%= render @products || "empty_list" %> -<% @books.each do |book| %> - ----------------------------------------------------- - -[ - ["erb", [ - ["delimiter", "<%"], - ["comment", "# comment "], - ["delimiter", "%>"] - ]], - ["erb", [ - ["delimiter", "<%="], - " render ", - ["variable", "@products"], - ["operator", "||"], - ["string", ["\"empty_list\""]], - ["delimiter", "%>"] - ]], - ["erb", [ - ["delimiter", "<%"], - ["variable", "@books"], - ["punctuation", "."], - ["keyword", "each"], - ["keyword", "do"], - ["operator", "|"], - "book", - ["operator", "|"], - ["delimiter", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for ERB tags. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erb/erb_in_markup_feature.test b/docs/_style/prism-master/tests/languages/erb/erb_in_markup_feature.test deleted file mode 100644 index 20719ea8..00000000 --- a/docs/_style/prism-master/tests/languages/erb/erb_in_markup_feature.test +++ /dev/null @@ -1,48 +0,0 @@ -
        -___ERB1___<%= 1 %>___ERB2___<%= 2 %> - ----------------------------------------------------- - -[ - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "div" - ]], - ["attr-name", ["class"]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - ["erb", [ - ["delimiter", "<%="], - ["builtin", "Time"], - ["punctuation", "."], - "now", - ["punctuation", "."], - "strftime", - ["punctuation", "("], - ["string", ["'%A'"]], - ["punctuation", ")"], - ["delimiter", "%>"] - ]], - ["punctuation", "\""] - ]], - ["punctuation", ">"] - ]], - "\r\n___ERB1___", - ["erb", [ - ["delimiter", "<%="], - ["number", "1"], - ["delimiter", "%>"] - ]], - "___ERB2___", - ["erb", [ - ["delimiter", "<%="], - ["number", "2"], - ["delimiter", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for ERB inside Markup diff --git a/docs/_style/prism-master/tests/languages/erlang/atom_feature.test b/docs/_style/prism-master/tests/languages/erlang/atom_feature.test deleted file mode 100644 index 1761a8bb..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/atom_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -foo -foo@_bar -'foo bar' -'\'\\' - ----------------------------------------------------- - -[ - ["atom", "foo"], - ["atom", "foo@_bar"], - ["quoted-atom", "'foo bar'"], - ["quoted-atom", "'\\'\\\\'"] -] - ----------------------------------------------------- - -Checks for atoms and quoted atoms. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/boolean_feature.test b/docs/_style/prism-master/tests/languages/erlang/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/comment_feature.test b/docs/_style/prism-master/tests/languages/erlang/comment_feature.test deleted file mode 100644 index b879c5e5..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/comment_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -% foo bar - ----------------------------------------------------- - -[ - ["comment", "% foo bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/function_feature.test b/docs/_style/prism-master/tests/languages/erlang/function_feature.test deleted file mode 100644 index 72d8857d..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/function_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -spawn( -foo@_bar( -'foo bar'( -'\'\\'( - ----------------------------------------------------- - -[ - ["function", "spawn"], ["punctuation", "("], - ["function", "foo@_bar"], ["punctuation", "("], - ["quoted-function", "'foo bar'"], ["punctuation", "("], - ["quoted-function", "'\\'\\\\'"], ["punctuation", "("] -] - ----------------------------------------------------- - -Checks for functions and quoted functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/keyword_feature.test b/docs/_style/prism-master/tests/languages/erlang/keyword_feature.test deleted file mode 100644 index 3af1da3d..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/keyword_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -fun when case of -end if receive -after try catch - ----------------------------------------------------- - -[ - ["keyword", "fun"], ["keyword", "when"], ["keyword", "case"], ["keyword", "of"], - ["keyword", "end"], ["keyword", "if"], ["keyword", "receive"], - ["keyword", "after"], ["keyword", "try"], ["keyword", "catch"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/number_feature.test b/docs/_style/prism-master/tests/languages/erlang/number_feature.test deleted file mode 100644 index 0e5890f3..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/number_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -42 -2#101 -16#1f -2.3 -2.3e3 -2.3e-3 -$A -$\n - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "2#101"], - ["number", "16#1f"], - ["number", "2.3"], - ["number", "2.3e3"], - ["number", "2.3e-3"], - ["number", "$A"], - ["number", "$\\n"] -] - ----------------------------------------------------- - -Checks for numbers and character codes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/operator_feature.test b/docs/_style/prism-master/tests/languages/erlang/operator_feature.test deleted file mode 100644 index addd5d3d..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/operator_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -== /= >= := -=:= =/= -+ ++ - -- -= * / ! -<= < > -bnot div rem band -bor bxor bsl bsr -not and or xor -orelse andalso - ----------------------------------------------------- - -[ - ["operator", "=="], ["operator", "/="], ["operator", ">="], ["operator", ":="], - ["operator", "=:="], ["operator", "=/="], - ["operator", "+"], ["operator", "++"], ["operator", "-"], ["operator", "--"], - ["operator", "="], ["operator", "*"], ["operator", "/"], ["operator", "!"], - ["operator", "<="], ["operator", "<"], ["operator", ">"], - ["operator", "bnot"], ["operator", "div"], ["operator", "rem"], ["operator", "band"], - ["operator", "bor"], ["operator", "bxor"], ["operator", "bsl"], ["operator", "bsr"], - ["operator", "not"], ["operator", "and"], ["operator", "or"], ["operator", "xor"], - ["operator", "orelse"], ["operator", "andalso"] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/string_feature.test b/docs/_style/prism-master/tests/languages/erlang/string_feature.test deleted file mode 100644 index 42770f61..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"foo bar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo bar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/erlang/variable_feature.test b/docs/_style/prism-master/tests/languages/erlang/variable_feature.test deleted file mode 100644 index f7492e44..00000000 --- a/docs/_style/prism-master/tests/languages/erlang/variable_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -Foo -?Bar -_ -Foo@_bar - ----------------------------------------------------- - -[ - ["variable", "Foo"], - ["variable", "?Bar"], - ["variable", "_"], - ["variable", "Foo@_bar"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/flow/flow-punctuation_feature.test b/docs/_style/prism-master/tests/languages/flow/flow-punctuation_feature.test deleted file mode 100644 index ba0c9005..00000000 --- a/docs/_style/prism-master/tests/languages/flow/flow-punctuation_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -{| foo : string |} - ----------------------------------------------------- - -[ - ["flow-punctuation", "{|"], " foo ", ["punctuation", ":"], - ["type", "string"], ["flow-punctuation", "|}"] -] - ----------------------------------------------------- - -Checks for Flow specific punctuation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/flow/function-variable_feature.test b/docs/_style/prism-master/tests/languages/flow/function-variable_feature.test deleted file mode 100644 index cda25c97..00000000 --- a/docs/_style/prism-master/tests/languages/flow/function-variable_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -foo = (a: number) : number => {} -bar = () : string => {} - ----------------------------------------------------- - -[ - ["function-variable", "foo"], ["operator", "="], - ["punctuation", "("], "a", ["punctuation", ":"], - ["type", "number"], ["punctuation", ")"], - ["punctuation", ":"], ["type", "number"], - ["operator", "=>"], ["punctuation", "{"], ["punctuation", "}"], - ["function-variable", "bar"], ["operator", "="], - ["punctuation", "("], ["punctuation", ")"], - ["punctuation", ":"], ["type", "string"], - ["operator", "=>"], ["punctuation", "{"], ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for function variables containing types. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/flow/keyword_feature.test b/docs/_style/prism-master/tests/languages/flow/keyword_feature.test deleted file mode 100644 index fc1ca2ac..00000000 --- a/docs/_style/prism-master/tests/languages/flow/keyword_feature.test +++ /dev/null @@ -1,39 +0,0 @@ -type -opaque -declare -Class -$await -$Diff -$Exact -$Keys -$ObjMap -$PropertyType -$Shape -$Record -$Supertype -$Subtype -$Enum - ----------------------------------------------------- - -[ - ["keyword", "type"], - ["keyword", "opaque"], - ["keyword", "declare"], - ["keyword", "Class"], - ["keyword", "$await"], - ["keyword", "$Diff"], - ["keyword", "$Exact"], - ["keyword", "$Keys"], - ["keyword", "$ObjMap"], - ["keyword", "$PropertyType"], - ["keyword", "$Shape"], - ["keyword", "$Record"], - ["keyword", "$Supertype"], - ["keyword", "$Subtype"], - ["keyword", "$Enum"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/flow/type_feature.test b/docs/_style/prism-master/tests/languages/flow/type_feature.test deleted file mode 100644 index 53827c35..00000000 --- a/docs/_style/prism-master/tests/languages/flow/type_feature.test +++ /dev/null @@ -1,31 +0,0 @@ -Number -number -String -string -Boolean -boolean -Function -any -mixed -null -void - ----------------------------------------------------- - -[ - ["type", "Number"], - ["type", "number"], - ["type", "String"], - ["type", "string"], - ["type", "Boolean"], - ["type", "boolean"], - ["type", "Function"], - ["type", "any"], - ["type", "mixed"], - ["type", "null"], - ["type", "void"] -] - ----------------------------------------------------- - -Checks for types. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran+pure/fortran_inclusion.test b/docs/_style/prism-master/tests/languages/fortran+pure/fortran_inclusion.test deleted file mode 100644 index 176cad9e..00000000 --- a/docs/_style/prism-master/tests/languages/fortran+pure/fortran_inclusion.test +++ /dev/null @@ -1,18 +0,0 @@ -%< -*- Fortran90 -*- -21_SHORT -%> - ----------------------------------------------------- - -[ - ["inline-lang-fortran", [ - ["delimiter", "%< "], - ["lang", "-*- Fortran90 -*-"], - ["number", "21_SHORT"], - ["delimiter", "%>"] - ]] -] - ----------------------------------------------------- - -Checks for Fortran in Pure. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/boolean_feature.test b/docs/_style/prism-master/tests/languages/fortran/boolean_feature.test deleted file mode 100644 index 88bdb6bd..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -.TRUE. -.false. - ----------------------------------------------------- - -[ - ["boolean", ".TRUE."], - ["boolean", ".false."] -] - ----------------------------------------------------- - -Checks for boolean. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/comment_feature.test b/docs/_style/prism-master/tests/languages/fortran/comment_feature.test deleted file mode 100644 index 51b66e88..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -! -! foobar -! This "string" should not be highlighted - ----------------------------------------------------- - -[ - ["comment", "!"], - ["comment", "! foobar"], - ["comment", "! This \"string\" should not be highlighted"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/keyword_feature.test b/docs/_style/prism-master/tests/languages/fortran/keyword_feature.test deleted file mode 100644 index e3bb493b..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/keyword_feature.test +++ /dev/null @@ -1,199 +0,0 @@ -INTEGER -REAL -DOUBLE PRECISION -DOUBLEPRECISION -COMPLEX -CHARACTER -LOGICAL - -ALLOCATABLE -ALLOCATE -BACKSPACE -CALL -CASE -CLOSE -COMMON -CONTAINS -CONTINUE -CYCLE -DATA -DEALLOCATE -DIMENSION -DO -END -EQUIVALENCE -EXIT -EXTERNAL -FORMAT -GO TO -GOTO -IMPLICIT -IMPLICIT NONE -INQUIRE -INTENT -INTRINSIC -MODULE PROCEDURE -NAMELIST -NULLIFY -OPEN -OPTIONAL -PARAMETER -POINTER -PRINT -PRIVATE -PUBLIC -READ -RETURN -REWIND -SAVE -SELECT -STOP -TARGET -WHILE -WRITE - -BLOCK DATA -BLOCKDATA -END BLOCK DATA -ENDBLOCKDATA -DO -ENDDO -FILE -FORALL -FUNCTION -IF -END IF -INTERFACE -MODULE -PROGRAM -SELECT -SUBROUTINE -TYPE -WHERE - -ASSIGNMENT -DEFAULT -ELEMENTAL -ELSE -ELSEWHERE -ELSEIF -ENTRY -IN -INCLUDE -INOUT -KIND -NULL -ONLY -OPERATOR -OUT -PURE -RECURSIVE -RESULT -SEQUENCE -STAT -THEN -USE - ----------------------------------------------------- - -[ - ["keyword", "INTEGER"], - ["keyword", "REAL"], - ["keyword", "DOUBLE PRECISION"], - ["keyword", "DOUBLEPRECISION"], - ["keyword", "COMPLEX"], - ["keyword", "CHARACTER"], - ["keyword", "LOGICAL"], - - ["keyword", "ALLOCATABLE"], - ["keyword", "ALLOCATE"], - ["keyword", "BACKSPACE"], - ["keyword", "CALL"], - ["keyword", "CASE"], - ["keyword", "CLOSE"], - ["keyword", "COMMON"], - ["keyword", "CONTAINS"], - ["keyword", "CONTINUE"], - ["keyword", "CYCLE"], - ["keyword", "DATA"], - ["keyword", "DEALLOCATE"], - ["keyword", "DIMENSION"], - ["keyword", "DO"], - ["keyword", "END"], - ["keyword", "EQUIVALENCE"], - ["keyword", "EXIT"], - ["keyword", "EXTERNAL"], - ["keyword", "FORMAT"], - ["keyword", "GO TO"], - ["keyword", "GOTO"], - ["keyword", "IMPLICIT"], - ["keyword", "IMPLICIT NONE"], - ["keyword", "INQUIRE"], - ["keyword", "INTENT"], - ["keyword", "INTRINSIC"], - ["keyword", "MODULE PROCEDURE"], - ["keyword", "NAMELIST"], - ["keyword", "NULLIFY"], - ["keyword", "OPEN"], - ["keyword", "OPTIONAL"], - ["keyword", "PARAMETER"], - ["keyword", "POINTER"], - ["keyword", "PRINT"], - ["keyword", "PRIVATE"], - ["keyword", "PUBLIC"], - ["keyword", "READ"], - ["keyword", "RETURN"], - ["keyword", "REWIND"], - ["keyword", "SAVE"], - ["keyword", "SELECT"], - ["keyword", "STOP"], - ["keyword", "TARGET"], - ["keyword", "WHILE"], - ["keyword", "WRITE"], - - ["keyword", "BLOCK DATA"], - ["keyword", "BLOCKDATA"], - ["keyword", "END BLOCK DATA"], - ["keyword", "ENDBLOCKDATA"], - ["keyword", "DO"], - ["keyword", "ENDDO"], - ["keyword", "FILE"], - ["keyword", "FORALL"], - ["keyword", "FUNCTION"], - ["keyword", "IF"], - ["keyword", "END IF"], - ["keyword", "INTERFACE"], - ["keyword", "MODULE"], - ["keyword", "PROGRAM"], - ["keyword", "SELECT"], - ["keyword", "SUBROUTINE"], - ["keyword", "TYPE"], - ["keyword", "WHERE"], - - ["keyword", "ASSIGNMENT"], - ["keyword", "DEFAULT"], - ["keyword", "ELEMENTAL"], - ["keyword", "ELSE"], - ["keyword", "ELSEWHERE"], - ["keyword", "ELSEIF"], - ["keyword", "ENTRY"], - ["keyword", "IN"], - ["keyword", "INCLUDE"], - ["keyword", "INOUT"], - ["keyword", "KIND"], - ["keyword", "NULL"], - ["keyword", "ONLY"], - ["keyword", "OPERATOR"], - ["keyword", "OUT"], - ["keyword", "PURE"], - ["keyword", "RECURSIVE"], - ["keyword", "RESULT"], - ["keyword", "SEQUENCE"], - ["keyword", "STAT"], - ["keyword", "THEN"], - ["keyword", "USE"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/number_feature.test b/docs/_style/prism-master/tests/languages/fortran/number_feature.test deleted file mode 100644 index c7b4a4cf..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/number_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -473 -21_2 -21_SHORT -1976354279568241_8 -1.6E3 -0.45E-4 -10.93E7_QUAD -3E4 -B'01110' -B"010" -O'047' -O"642" -Z'F41A' -Z"00BC" - ----------------------------------------------------- - -[ - ["number", "473"], - ["number", "21_2"], - ["number", "21_SHORT"], - ["number", "1976354279568241_8"], - ["number", "1.6E3"], - ["number", "0.45E-4"], - ["number", "10.93E7_QUAD"], - ["number", "3E4"], - ["quoted-number", "B'01110'"], - ["quoted-number", "B\"010\""], - ["quoted-number", "O'047'"], - ["quoted-number", "O\"642\""], - ["quoted-number", "Z'F41A'"], - ["quoted-number", "Z\"00BC\""] -] - ----------------------------------------------------- - -Checks for numbers and quoted numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/operator_feature.test b/docs/_style/prism-master/tests/languages/fortran/operator_feature.test deleted file mode 100644 index d2d3003f..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/operator_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -** // => -== /= :: -< <= > >= -+ - * / = % -.EQ. .NE. .LT. .LE. -.GT. .GE. .NOT. .AND. -.OR. .EQV. .NEQV. -.foobar. - ----------------------------------------------------- - -[ - ["operator", "**"], ["operator", "//"], ["operator", "=>"], - ["operator", "=="], ["operator", "/="], ["operator", "::"], - ["operator", "<"], ["operator", "<="], ["operator", ">"], ["operator", ">="], - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], ["operator", "="], ["operator", "%"], - ["operator", ".EQ."], ["operator", ".NE."], ["operator", ".LT."], ["operator", ".LE."], - ["operator", ".GT."], ["operator", ".GE."], ["operator", ".NOT."], ["operator", ".AND."], - ["operator", ".OR."], ["operator", ".EQV."], ["operator", ".NEQV."], - ["operator", ".foobar."] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fortran/string_feature.test b/docs/_style/prism-master/tests/languages/fortran/string_feature.test deleted file mode 100644 index 3fa6f74a..00000000 --- a/docs/_style/prism-master/tests/languages/fortran/string_feature.test +++ /dev/null @@ -1,31 +0,0 @@ -"" -'' -"foo ""bar"" baz" -'foo ''bar'' baz' -ITALICS_'foobar' -"foo & - &bar" -"foo & - ! Comment - &bar" - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["''"]], - ["string", ["\"foo \"\"bar\"\" baz\""]], - ["string", ["'foo ''bar'' baz'"]], - ["string", ["ITALICS_'foobar'"]], - ["string", ["\"foo &\r\n\t&bar\""]], - ["string", [ - "\"foo &\r\n\t", - ["comment", "! Comment"], - "\r\n\t&bar\"" - ]] -] - ----------------------------------------------------- - -Checks for strings and line continuation. -Also checks for comments inside strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fsharp/annotation_feature.test b/docs/_style/prism-master/tests/languages/fsharp/annotation_feature.test deleted file mode 100644 index 3445236d..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/annotation_feature.test +++ /dev/null @@ -1,35 +0,0 @@ -[] -[] - ----------------------------------------------------- - -[ - ["annotation", [ - ["punctuation", "[<"], - ["class-name", "Foo"], - ["punctuation", ">]"] - ]], - ["annotation", [ - ["punctuation", "[<"], - ["class-name", "Bar"], - ["annotation-content", [ - ["punctuation", "("], - ["string", "\"bar\""], - ["punctuation", ")"], - ["punctuation", ";"] - ]], - ["class-name", "Foo"], - ["annotation-content", [ - ["punctuation", "("], - ["number", "1"], - ["punctuation", ","], - ["number", "2"], - ["punctuation", ")"] - ]], - ["punctuation", ">]"] - ]] -] - ----------------------------------------------------- - -Checks for annotations. diff --git a/docs/_style/prism-master/tests/languages/fsharp/class-name_feature.test b/docs/_style/prism-master/tests/languages/fsharp/class-name_feature.test deleted file mode 100644 index 79399642..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/class-name_feature.test +++ /dev/null @@ -1,86 +0,0 @@ -let func : HttpFunc = handler (Some >> Task.FromResult) - -type Base1() = - abstract member F : unit -> unit - default u.F() = - printfn "F Base1" - -type Derived1() = - inherit Base1() - override u.F() = - printfn "F Derived1" - -let d1 : Derived1 = Derived1() - -let base1 = d1 :> Base1 -let derived1 = base1 :?> Derived1 - -type PersonName = - | FirstOnly of string - | LastOnly of string - | FirstLast of string * string - -type Shape = - | Rectangle of height : float * width : float - | Circle of radius : float - -type MyInterface = - abstract member Add: int -> int -> int - abstract member Pi : float - -exception Error1 of string -exception Error2 of string * int - ----------------------------------------------------- - -[ - ["keyword", "let"], " func ", - ["punctuation", ":"], ["class-name", ["HttpFunc"]], - ["operator", "="], " handler ", ["punctuation", "("], - "Some ", ["operator", ">>"], " Task", ["punctuation", "."], "FromResult", - ["punctuation", ")"], - - ["keyword", "type"], ["class-name", ["Base1"]], ["punctuation", "("], ["punctuation", ")"], ["operator", "="], - ["keyword", "abstract"], ["keyword", "member"], " F ", ["punctuation", ":"], - ["class-name", [ - "unit ", ["operator", "->"], " unit"] - ], - ["keyword", "default"], " u", ["punctuation", "."], ["function", "F"], ["punctuation", "("], ["punctuation", ")"], - ["operator", "="], "\n printfn ", ["string", "\"F Base1\""], - - ["keyword", "type"], ["class-name", ["Derived1"]], ["punctuation", "("], ["punctuation", ")"], ["operator", "="], - ["keyword", "inherit"], ["class-name", ["Base1"]], ["punctuation", "("], ["punctuation", ")"], - ["keyword", "override"], " u", ["punctuation", "."], ["function", "F"], ["punctuation", "("], ["punctuation", ")"], ["operator", "="], - "\n printfn ", ["string", "\"F Derived1\""], - - ["keyword", "let"], " d1 ", ["punctuation", ":"], ["class-name", ["Derived1"]], ["operator", "="], - ["function", "Derived1"], ["punctuation", "("], ["punctuation", ")"], - - ["keyword", "let"], " base1 ", ["operator", "="], " d1 ", ["operator", ":>"], ["class-name", ["Base1"]], - - ["keyword", "let"], " derived1 ", ["operator", "="], " base1 ", ["operator", ":?>"], ["class-name", ["Derived1"]], - - ["keyword", "type"], ["class-name", ["PersonName"]], ["operator", "="], - ["operator", "|"], " FirstOnly ", ["keyword", "of"], ["class-name", ["string"]], - ["operator", "|"], " LastOnly ", ["keyword", "of"], ["class-name", ["string"]], - ["operator", "|"], " FirstLast ", ["keyword", "of"], ["class-name", ["string ", ["operator", "*"], " string"]], - - ["keyword", "type"], ["class-name", ["Shape"]], ["operator", "="], - ["operator", "|"], " Rectangle ", ["keyword", "of"], - " height ", ["punctuation", ":"], ["class-name", ["float"]], ["operator", "*"], - " width ", ["punctuation", ":"], ["class-name", ["float"]], - ["operator", "|"], " Circle ", ["keyword", "of"], " radius ", ["punctuation", ":"], ["class-name", ["float"]], - - ["keyword", "type"], ["class-name", ["MyInterface"]], ["operator", "="], - ["keyword", "abstract"], ["keyword", "member"], " Add", ["punctuation", ":"], - ["class-name", ["int ", ["operator", "->"], " int ", ["operator", "->"], " int"]], - ["keyword", "abstract"], ["keyword", "member"], " Pi ", ["punctuation", ":"], ["class-name", ["float"]], - - ["keyword", "exception"], ["class-name", ["Error1"]], ["keyword", "of"], ["class-name", ["string"]], - - ["keyword", "exception"], ["class-name", ["Error2"]], ["keyword", "of"], ["class-name", ["string ", ["operator", "*"], " int"]] -] - ----------------------------------------------------- - -Checks for class-names. diff --git a/docs/_style/prism-master/tests/languages/fsharp/comment_feature.test b/docs/_style/prism-master/tests/languages/fsharp/comment_feature.test deleted file mode 100644 index ff9170b6..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/comment_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -// foobar -(**) -(* foo -bar *) - ----------------------------------------------------- - -[ - ["comment", "// foobar"], - ["comment", "(**)"], - ["comment", "(* foo\r\nbar *)"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fsharp/computation-expression_feature.test b/docs/_style/prism-master/tests/languages/fsharp/computation-expression_feature.test deleted file mode 100644 index a49c6e35..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/computation-expression_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -async {} -task {} -seq {} -foo {} - ----------------------------------------------------- - -[ - ["computation-expression", "async"], ["punctuation", "{"], ["punctuation", "}"], - ["computation-expression", "task"], ["punctuation", "{"], ["punctuation", "}"], - ["computation-expression", "seq"], ["punctuation", "{"], ["punctuation", "}"], - ["computation-expression", "foo"], ["punctuation", "{"], ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for computation expressions. diff --git a/docs/_style/prism-master/tests/languages/fsharp/issue1480.test b/docs/_style/prism-master/tests/languages/fsharp/issue1480.test deleted file mode 100644 index 2c692a33..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/issue1480.test +++ /dev/null @@ -1,36 +0,0 @@ -let foo' = failWith "foo" - -let bar' = failWith "bar" - -let map (f: 'a -> 'b) (xs: 'a list): 'b list = failWith "not implemented" - ----------------------------------------------------- - -[ - ["keyword", "let"], " foo' ", ["operator", "="], " failWith ", ["string", "\"foo\""], - ["keyword", "let"], " bar' ", ["operator", "="], " failWith ", ["string", "\"bar\""], - - ["keyword", "let"], - " map ", - ["punctuation", "("], - "f", - ["punctuation", ":"], - " 'a ", - ["operator", "->"], - " 'b", - ["punctuation", ")"], - ["punctuation", "("], - "xs", - ["punctuation", ":"], - " 'a list", - ["punctuation", ")"], - ["punctuation", ":"], - " 'b list ", - ["operator", "="], - " failWith ", - ["string", "\"not implemented\""] -] - ----------------------------------------------------- - -Checks for apostrophes in names. See #1480. diff --git a/docs/_style/prism-master/tests/languages/fsharp/keyword_feature.test b/docs/_style/prism-master/tests/languages/fsharp/keyword_feature.test deleted file mode 100644 index 17f058a5..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/keyword_feature.test +++ /dev/null @@ -1,73 +0,0 @@ -abstract and as assert -base begin -class; -default -delegate do done downcast -downto elif else end -exception; -extern false finally -for fun function global -if in inherit; inline -interface; -internal lazy let -let! match member module -mutable namespace -new; -not -null of; open or override -private public rec return -return! select static struct -then to true try type; -upcast use use! val void -when while with yield -yield! asr land lor lsl -lsr lxor mod sig atomic -break checked component -const constraint constructor -continue eager event external -fixed functor include method -mixin object parallel process -protected pure sealed tailcall -trait; -virtual volatile - ----------------------------------------------------- - -[ - ["keyword", "abstract"], ["keyword", "and"], ["keyword", "as"], ["keyword", "assert"], - ["keyword", "base"], ["keyword", "begin"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "default"], - ["keyword", "delegate"], ["keyword", "do"], ["keyword", "done"], ["keyword", "downcast"], - ["keyword", "downto"], ["keyword", "elif"], ["keyword", "else"], ["keyword", "end"], - ["keyword", "exception"], ["punctuation", ";"], - ["keyword", "extern"], ["keyword", "false"], ["keyword", "finally"], - ["keyword", "for"], ["keyword", "fun"], ["keyword", "function"], ["keyword", "global"], - ["keyword", "if"], ["keyword", "in"], ["keyword", "inherit"], ["punctuation", ";"], ["keyword", "inline"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "internal"], ["keyword", "lazy"], ["keyword", "let"], - ["keyword", "let!"], ["keyword", "match"], ["keyword", "member"], ["keyword", "module"], - ["keyword", "mutable"], ["keyword", "namespace"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "not"], - ["keyword", "null"], ["keyword", "of"], ["punctuation", ";"], ["keyword", "open"], ["keyword", "or"], ["keyword", "override"], - ["keyword", "private"], ["keyword", "public"], ["keyword", "rec"], ["keyword", "return"], - ["keyword", "return!"], ["keyword", "select"], ["keyword", "static"], ["keyword", "struct"], - ["keyword", "then"], ["keyword", "to"], ["keyword", "true"], ["keyword", "try"], ["keyword", "type"], ["punctuation", ";"], - ["keyword", "upcast"], ["keyword", "use"], ["keyword", "use!"], ["keyword", "val"], ["keyword", "void"], - ["keyword", "when"], ["keyword", "while"], ["keyword", "with"], ["keyword", "yield"], - ["keyword", "yield!"], ["keyword", "asr"], ["keyword", "land"], ["keyword", "lor"], ["keyword", "lsl"], - ["keyword", "lsr"], ["keyword", "lxor"], ["keyword", "mod"], ["keyword", "sig"], ["keyword", "atomic"], - ["keyword", "break"], ["keyword", "checked"], ["keyword", "component"], - ["keyword", "const"], ["keyword", "constraint"], ["keyword", "constructor"], - ["keyword", "continue"], ["keyword", "eager"], ["keyword", "event"], ["keyword", "external"], - ["keyword", "fixed"], ["keyword", "functor"], ["keyword", "include"], ["keyword", "method"], - ["keyword", "mixin"], ["keyword", "object"], ["keyword", "parallel"], ["keyword", "process"], - ["keyword", "protected"], ["keyword", "pure"], ["keyword", "sealed"], ["keyword", "tailcall"], - ["keyword", "trait"], ["punctuation", ";"], - ["keyword", "virtual"], ["keyword", "volatile"] -] - ----------------------------------------------------- - -Checks for all keywords. diff --git a/docs/_style/prism-master/tests/languages/fsharp/number_feature.test b/docs/_style/prism-master/tests/languages/fsharp/number_feature.test deleted file mode 100644 index f2f86ed2..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/number_feature.test +++ /dev/null @@ -1,65 +0,0 @@ -0xbabe -0xBABEun -0xflf -0xfLF - -0b1001 -0b1001y -0b1001uy - -42 -1.5 -2.3E+32 -2.3e-32 -4.14F -4.14f -0.7833M -0.7833m - -86y -86uy -86s -86us -86l -86u -86ul -86L -86UL -9999999999999999999999999999I - ----------------------------------------------------- - -[ - ["number", "0xbabe"], - ["number", "0xBABEun"], - ["number", "0xflf"], - ["number", "0xfLF"], - - ["number", "0b1001"], - ["number", "0b1001y"], - ["number", "0b1001uy"], - - ["number", "42"], - ["number", "1.5"], - ["number", "2.3E+32"], - ["number", "2.3e-32"], - ["number", "4.14F"], - ["number", "4.14f"], - ["number", "0.7833M"], - ["number", "0.7833m"], - - ["number", "86y"], - ["number", "86uy"], - ["number", "86s"], - ["number", "86us"], - ["number", "86l"], - ["number", "86u"], - ["number", "86ul"], - ["number", "86L"], - ["number", "86UL"], - ["number", "9999999999999999999999999999I"] -] - ----------------------------------------------------- - -Checks for decimal, hexadecimal and binary numbers, with all possible suffixes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fsharp/operator_feature.test b/docs/_style/prism-master/tests/languages/fsharp/operator_feature.test deleted file mode 100644 index 7e15afc4..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/operator_feature.test +++ /dev/null @@ -1,53 +0,0 @@ - >= <= <> > < = + - * / % - >=? <=? <>? >? =? ?<=? ?<>? ?>? ?= ?<= ?<> ?> ?< ?= ?+ ?- ?* ?/ ?% - -** - -<- -> -.. -:: -:= -:> :? :?> -<< >> -<<< >>> ~~~ ^^^ &&& ||| -| || -<| <|| <||| -|> ||> |||> -~~ ~- ~+ - -? ^ ! -!= == -& && - ----------------------------------------------------- - -[ - ["operator", ">="], ["operator", "<="], ["operator", "<>"], ["operator", ">"], ["operator", "<"], ["operator", "="], ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], ["operator", "%"], - ["operator", ">=?"], ["operator", "<=?"], ["operator", "<>?"], ["operator", ">?"], ["operator", "=?"], ["operator", "?<=?"], ["operator", "?<>?"], ["operator", "?>?"], ["operator", "?="], ["operator", "?<="], ["operator", "?<>"], ["operator", "?>"], ["operator", "?<"], ["operator", "?="], ["operator", "?+"], ["operator", "?-"], ["operator", "?*"], ["operator", "?/"], ["operator", "?%"], - - ["operator", "**"], - - ["operator", "<-"], ["operator", "->"], - ["operator", ".."], - ["operator", "::"], - ["operator", ":="], - ["operator", ":>"], ["operator", ":?"], ["operator", ":?>"], - ["operator", "<<"], ["operator", ">>"], - ["operator", "<<<"], ["operator", ">>>"], ["operator", "~~~"], ["operator", "^^^"], ["operator", "&&&"], ["operator", "|||"], - ["operator", "|"], ["operator", "||"], - ["operator", "<|"], ["operator", "<||"], ["operator", "<|||"], - ["operator", "|>"], ["operator", "||>"], ["operator", "|||>"], - ["operator", "~~"], ["operator", "~-"], ["operator", "~+"], - - ["operator", "?"], ["operator", "^"], ["operator", "!"], - ["operator", "!="], ["operator", "=="], - ["operator", "&"], ["operator", "&&"] -] - ----------------------------------------------------- - -Checks for operators. diff --git a/docs/_style/prism-master/tests/languages/fsharp/preprocessor_feature.test b/docs/_style/prism-master/tests/languages/fsharp/preprocessor_feature.test deleted file mode 100644 index 4ee06395..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/preprocessor_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -#if foo -#else -#endif - -#light -#line -#nowarn - ----------------------------------------------------- - -[ - ["preprocessor", ["#", ["directive", "if"], " foo"]], - ["preprocessor", ["#", ["directive", "else"]]], - ["preprocessor", ["#", ["directive", "endif"]]], - ["preprocessor", ["#", ["directive", "light"]]], - ["preprocessor", ["#", ["directive", "line"]]], - ["preprocessor", ["#", ["directive", "nowarn"]]] -] - ----------------------------------------------------- - -Checks for preprocessor directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/fsharp/string_feature.test b/docs/_style/prism-master/tests/languages/fsharp/string_feature.test deleted file mode 100644 index 3c984a8c..00000000 --- a/docs/_style/prism-master/tests/languages/fsharp/string_feature.test +++ /dev/null @@ -1,47 +0,0 @@ -"" -"fo\"o" -"foo\ -bar" -"foo"B - -@"" -@"foo" -@"fo""o" -@"foo"B - -"""""" -"""fo""o" -bar""" -"""foo"""B - -'a' -'a'B -'\'' -'\\' - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\\\"o\""], - ["string", "\"foo\\\r\nbar\""], - ["string", "\"foo\"B"], - - ["string", "@\"\""], - ["string", "@\"foo\""], - ["string", "@\"fo\"\"o\""], - ["string", "@\"foo\"B"], - - ["string", "\"\"\"\"\"\""], - ["string", "\"\"\"fo\"\"o\"\r\nbar\"\"\""], - ["string", "\"\"\"foo\"\"\"B"], - - ["string", "'a'"], - ["string", "'a'B"], - ["string", "'\\''"], - ["string", "'\\\\'"] -] - ----------------------------------------------------- - -Checks for normal strings, verbatim strings, triple-quoted strings and character literals. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gcode/checksum_feature.test b/docs/_style/prism-master/tests/languages/gcode/checksum_feature.test deleted file mode 100644 index d3ea4561..00000000 --- a/docs/_style/prism-master/tests/languages/gcode/checksum_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -G28*22 - ----------------------------------------------------- - -[ - ["keyword", "G28"], - ["checksum", "*22"] -] - ----------------------------------------------------- - -Checks for checksums. diff --git a/docs/_style/prism-master/tests/languages/gcode/comment_feature.test b/docs/_style/prism-master/tests/languages/gcode/comment_feature.test deleted file mode 100644 index cb2533f8..00000000 --- a/docs/_style/prism-master/tests/languages/gcode/comment_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -; foo -(Home some axes) -G28 (here come the axes to be homed) X - ----------------------------------------------------- - -[ - ["comment", "; foo"], - - ["comment", "(Home some axes)"], - - ["keyword", "G28"], - ["comment", "(here come the axes to be homed)"], - ["property", "X"] - -] - ----------------------------------------------------- - -Checks for comments. diff --git a/docs/_style/prism-master/tests/languages/gcode/keyword_feature.test b/docs/_style/prism-master/tests/languages/gcode/keyword_feature.test deleted file mode 100644 index d72d0760..00000000 --- a/docs/_style/prism-master/tests/languages/gcode/keyword_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -G00 -G200 -G84.1 - -M00 -M123 -M52.4 - ----------------------------------------------------- - -[ - ["keyword", "G00"], - ["keyword", "G200"], - ["keyword", "G84.1"], - - ["keyword", "M00"], - ["keyword", "M123"], - ["keyword", "M52.4"] -] - ----------------------------------------------------- - -Checks for G- and M-codes. diff --git a/docs/_style/prism-master/tests/languages/gcode/property_feature.test b/docs/_style/prism-master/tests/languages/gcode/property_feature.test deleted file mode 100644 index 9dee8868..00000000 --- a/docs/_style/prism-master/tests/languages/gcode/property_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -X123 -Y0.2 -Z-3.1415 -E420:420 - ----------------------------------------------------- - -[ - ["property", "X"], "123\n", - ["property", "Y"], "0.2\n", - ["property", "Z"], "-3.1415\n", - ["property", "E"], "420", ["punctuation", ":"], "420" -] - ----------------------------------------------------- - -Checks for all other codes except G- and M-codes. diff --git a/docs/_style/prism-master/tests/languages/gcode/string_feature.test b/docs/_style/prism-master/tests/languages/gcode/string_feature.test deleted file mode 100644 index d05306d7..00000000 --- a/docs/_style/prism-master/tests/languages/gcode/string_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -M587 S"MYROUTER" P"ABCxyz;"" 123" - ----------------------------------------------------- - -[ - ["keyword", "M587"], - - ["property", "S"], - ["string", "\"MYROUTER\""], - - ["property", "P"], - ["string", "\"ABCxyz;\"\" 123\""] -] - ----------------------------------------------------- - -Checks for strings. diff --git a/docs/_style/prism-master/tests/languages/gedcom/level_feature.test b/docs/_style/prism-master/tests/languages/gedcom/level_feature.test deleted file mode 100644 index 08c3963d..00000000 --- a/docs/_style/prism-master/tests/languages/gedcom/level_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -0 HEAD -1 CHAR ASCII -99 FOO bar - ----------------------------------------------------- - -[ - ["level", "0"], - ["tag", "HEAD"], - ["level", "1"], - ["tag", "CHAR"], - ["line-value", ["ASCII"]], - ["level", "99"], - ["tag", "FOO"], - ["line-value", ["bar"]] -] - ----------------------------------------------------- - -Checks for levels. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gedcom/line-value_feature.test b/docs/_style/prism-master/tests/languages/gedcom/line-value_feature.test deleted file mode 100644 index 4ab43e72..00000000 --- a/docs/_style/prism-master/tests/languages/gedcom/line-value_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -1 CHIL @CHILD1@ -2 PLAC ÁĆÉǴÍ,ḰĹḾŃÓ,ṔŔŚÚẂ,ÝŹáćé,ǵíḱĺḿ,ńóṕŕś,úẃýź -1 NAME code: 0313/COMMA ABOVE/ -2 DATE @#DGREGORIAN@ 31 DEC 1997 -2 CONT Copyright gedcom@@gedcom.org - ----------------------------------------------------- - -[ - ["level", "1"], - ["tag", "CHIL"], - ["line-value", [["pointer", "@CHILD1@"]]], - ["level", "2"], - ["tag", "PLAC"], - ["line-value", ["ÁĆÉǴÍ,ḰĹḾŃÓ,ṔŔŚÚẂ,ÝŹáćé,ǵíḱĺḿ,ńóṕŕś,úẃýź"]], - ["level", "1"], - ["tag", "NAME"], - ["line-value", ["code: 0313/COMMA ABOVE/"]], - ["level", "2"], - ["tag", "DATE"], - ["line-value", ["@#DGREGORIAN@ 31 DEC 1997"]], - ["level", "2"], - ["tag", "CONT"], - ["line-value", ["Copyright gedcom@@gedcom.org"]] -] - ----------------------------------------------------- - -Checks for line values. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gedcom/pointer_feature.test b/docs/_style/prism-master/tests/languages/gedcom/pointer_feature.test deleted file mode 100644 index f39ffb77..00000000 --- a/docs/_style/prism-master/tests/languages/gedcom/pointer_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -@f!@ -@foo_Bar@ -@_$!"$%&'()*+,-./:;<=>?[\]^`{|}~ #foobar@ -0 @SUBMITTER@ SUBM -1 FAMS @FAMILY@ - ----------------------------------------------------- - -[ - ["pointer", "@f!@"], - ["pointer", "@foo_Bar@"], - ["pointer", "@_$!\"$%&'()*+,-./:;<=>?[\\]^`{|}~ #foobar@"], - ["level", "0"], - ["pointer", "@SUBMITTER@"], - ["tag", "SUBM"], - ["level", "1"], - ["tag", "FAMS"], - ["line-value", [["pointer", "@FAMILY@"]]] -] - ----------------------------------------------------- - -Checks for pointers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gedcom/tag_feature.test b/docs/_style/prism-master/tests/languages/gedcom/tag_feature.test deleted file mode 100644 index 2166f644..00000000 --- a/docs/_style/prism-master/tests/languages/gedcom/tag_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -0 HEAD -1 foo_bar -2 _ -3 @pointer@ _Sometag42 -4 247 - ----------------------------------------------------- - -[ - ["level", "0"], - ["tag", "HEAD"], - ["level", "1"], - ["tag", "foo_bar"], - ["level", "2"], - ["tag", "_"], - ["level", "3"], - ["pointer", "@pointer@"], - ["tag", "_Sometag42"], - ["level", "4"], - ["tag", "247"] -] - ----------------------------------------------------- - -Checks for tags. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/atrule_feature.test b/docs/_style/prism-master/tests/languages/gherkin/atrule_feature.test deleted file mode 100644 index 23bc5408..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/atrule_feature.test +++ /dev/null @@ -1,1211 +0,0 @@ -Scenario: foo - 'ach foobar - 'a foobar - 'ej foobar - 7 foobar - a foobar - A také foobar - A taktiež foobar - A tiež foobar - A zároveň foobar - Aber foobar - Ac foobar - Adott foobar - Akkor foobar - Ak foobar - Aleshores foobar - Ale foobar - Ali foobar - Allora foobar - Alors foobar - Als foobar - Ama foobar - Amennyiben foobar - Amikor foobar - Ampak foobar - an foobar - AN foobar - Ananging foobar - And y'all foobar - And foobar - Angenommen foobar - Anrhegedig a foobar - An foobar - Apabila foobar - Atès foobar - Atesa foobar - Atunci foobar - Avast! foobar - Aye foobar - A foobar - awer foobar - Bagi foobar - Banjur foobar - Bet foobar - Biết foobar - Blimey! foobar - Buh foobar - But at the end of the day I reckon foobar - But y'all foobar - But foobar - BUT foobar - Cal foobar - Când foobar - Cando foobar - Cand foobar - Ce foobar - Cuando foobar - Če foobar - Ða ðe foobar - Ða foobar - Dadas foobar - Dada foobar - Dados foobar - Dado foobar - DaH ghu' bejlu' foobar - dann foobar - Dann foobar - Dano foobar - Dan foobar - Dar foobar - Dat fiind foobar - Data foobar - Date fiind foobar - Date foobar - Dati fiind foobar - Dati foobar - Daţi fiind foobar - Dați fiind foobar - Dato foobar - DEN foobar - Den youse gotta foobar - Dengan foobar - De foobar - Diberi foobar - Diyelim ki foobar - Donada foobar - Donat foobar - Donitaĵo foobar - Do foobar - Dun foobar - Duota foobar - Ðurh foobar - Eeldades foobar - Ef foobar - Eğer ki foobar - Entao foobar - Então foobar - Entón foobar - Entonces foobar - En foobar - Epi foobar - E foobar - És foobar - Etant donnée foobar - Etant donné foobar - Et foobar - Étant données foobar - Étant donnée foobar - Étant donné foobar - Etant données foobar - Etant donnés foobar - Étant donnés foobar - Fakat foobar - Gangway! foobar - Gdy foobar - Gegeben seien foobar - Gegeben sei foobar - Gegeven foobar - Gegewe foobar - ghu' noblu' foobar - Gitt foobar - Given y'all foobar - Given foobar - Givet foobar - Givun foobar - Ha foobar - Cho foobar - I CAN HAZ foobar - In foobar - Ir foobar - It's just unbelievable foobar - I foobar - Ja foobar - Jeśli foobar - Jeżeli foobar - Kadar foobar - Kada foobar - Kad foobar - Kai foobar - Kaj foobar - Když foobar - Keď foobar - Kemudian foobar - Ketika foobar - Khi foobar - Kiedy foobar - Ko foobar - Kuid foobar - Kui foobar - Kun foobar - Lan foobar - latlh foobar - Le sa a foobar - Let go and haul foobar - Le foobar - Lè sa a foobar - Lè foobar - Logo foobar - Lorsqu'< foobar - Lorsque foobar - mä foobar - Maar foobar - Mais foobar - Mając foobar - Majd foobar - Maka foobar - Manawa foobar - Mas foobar - Ma foobar - Menawa foobar - Men foobar - Mutta foobar - Nalikaning foobar - Nalika foobar - Nanging foobar - Når foobar - När foobar - Nato foobar - Nhưng foobar - Niin foobar - Njuk foobar - O zaman foobar - Og foobar - Och foobar - Oletetaan foobar - Onda foobar - Ond foobar - Oraz foobar - Pak foobar - Pero foobar - Però foobar - Podano foobar - Pokiaľ foobar - Pokud foobar - Potem foobar - Potom foobar - Privzeto foobar - Pryd foobar - qaSDI' foobar - Quando foobar - Quand foobar - Quan foobar - Så foobar - Sed foobar - Se foobar - Siis foobar - Sipoze ke foobar - Sipoze Ke foobar - Sipoze foobar - Si foobar - Şi foobar - Și foobar - Soit foobar - Stel foobar - Tada foobar - Tad foobar - Takrat foobar - Tak foobar - Tapi foobar - Ter foobar - Tetapi foobar - Tha the foobar - Tha foobar - Then y'all foobar - Then foobar - Thì foobar - Thurh foobar - Toda foobar - Too right foobar - ugeholl foobar - Und foobar - Un foobar - Và foobar - vaj foobar - Vendar foobar - Ve foobar - wann foobar - Wanneer foobar - WEN foobar - Wenn foobar - When y'all foobar - When foobar - Wtedy foobar - Wun foobar - Y'know foobar - Yeah nah foobar - Yna foobar - Youse know like when foobar - Youse know when youse got foobar - Y foobar - Za predpokladu foobar - Za předpokladu foobar - Zadani foobar - Zadano foobar - Zadan foobar - Zadate foobar - Zadato foobar - Zakładając foobar - Zaradi foobar - Zatati foobar - Þa foobar - Þá foobar - Þa þe foobar - Þegar foobar - Þurh foobar - Αλλά foobar - Δεδομένου foobar - Και foobar - Όταν foobar - Τότε foobar - А також foobar - Агар foobar - Але foobar - Али foobar - Аммо foobar - А foobar - Әгәр foobar - Әйтик foobar - Әмма foobar - Бирок foobar - Ва foobar - Вә foobar - Дадено foobar - Дано foobar - Допустим foobar - Если foobar - Задате foobar - Задати foobar - Задато foobar - И foobar - І foobar - К тому же foobar - Када foobar - Кад foobar - Когато foobar - Когда foobar - Коли foobar - Ләкин foobar - Лекин foobar - Нәтиҗәдә foobar - Нехай foobar - Но foobar - Онда foobar - Припустимо, що foobar - Припустимо foobar - Пусть foobar - Также foobar - Та foobar - Тогда foobar - Тоді foobar - То foobar - Унда foobar - Һәм foobar - Якщо foobar - אבל foobar - אזי foobar - אז foobar - בהינתן foobar - וגם foobar - כאשר foobar - آنگاه foobar - اذاً foobar - اگر foobar - اما foobar - اور foobar - با فرض foobar - بالفرض foobar - بفرض foobar - پھر foobar - تب foobar - ثم foobar - جب foobar - عندما foobar - فرض کیا foobar - لكن foobar - لیکن foobar - متى foobar - هنگامی foobar - و foobar - अगर foobar - और foobar - कदा foobar - किन्तु foobar - चूंकि foobar - जब foobar - तथा foobar - तदा foobar - तब foobar - परन्तु foobar - पर foobar - यदि foobar - ਅਤੇ foobar - ਜਦੋਂ foobar - ਜਿਵੇਂ ਕਿ foobar - ਜੇਕਰ foobar - ਤਦ foobar - ਪਰ foobar - అప్పుడు foobar - ఈ పరిస్థితిలో foobar - కాని foobar - చెప్పబడినది foobar - మరియు foobar - ಆದರೆ foobar - ನಂತರ foobar - ನೀಡಿದ foobar - ಮತ್ತು foobar - ಸ್ಥಿತಿಯನ್ನು foobar - กำหนดให้ foobar - ดังนั้น foobar - แต่ foobar - เมื่อ foobar - และ foobar - 그러면< foobar - 그리고< foobar - 단< foobar - 만약< foobar - 만일< foobar - 먼저< foobar - 조건< foobar - 하지만< foobar - かつ< foobar - しかし< foobar - ただし< foobar - ならば< foobar - もし< foobar - 並且< foobar - 但し< foobar - 但是< foobar - 假如< foobar - 假定< foobar - 假設< foobar - 假设< foobar - 前提< foobar - 同时< foobar - 同時< foobar - 并且< foobar - 当< foobar - 當< foobar - 而且< foobar - 那么< foobar - 那麼< foobar - ----------------------------------------------------- - -[ - ["scenario", [["keyword", "Scenario:"], ["important", " foo"]]], - ["atrule", "'ach"], - " foobar\r\n\t", - ["atrule", "'a"], - " foobar\r\n\t", - ["atrule", "'ej"], - " foobar\r\n\t", - ["atrule", "7"], - " foobar\r\n\t", - ["atrule", "a"], - " foobar\r\n\t", - ["atrule", "A také"], - " foobar\r\n\t", - ["atrule", "A taktiež"], - " foobar\r\n\t", - ["atrule", "A tiež"], - " foobar\r\n\t", - ["atrule", "A zároveň"], - " foobar\r\n\t", - ["atrule", "Aber"], - " foobar\r\n\t", - ["atrule", "Ac"], - " foobar\r\n\t", - ["atrule", "Adott"], - " foobar\r\n\t", - ["atrule", "Akkor"], - " foobar\r\n\t", - ["atrule", "Ak"], - " foobar\r\n\t", - ["atrule", "Aleshores"], - " foobar\r\n\t", - ["atrule", "Ale"], - " foobar\r\n\t", - ["atrule", "Ali"], - " foobar\r\n\t", - ["atrule", "Allora"], - " foobar\r\n\t", - ["atrule", "Alors"], - " foobar\r\n\t", - ["atrule", "Als"], - " foobar\r\n\t", - ["atrule", "Ama"], - " foobar\r\n\t", - ["atrule", "Amennyiben"], - " foobar\r\n\t", - ["atrule", "Amikor"], - " foobar\r\n\t", - ["atrule", "Ampak"], - " foobar\r\n\t", - ["atrule", "an"], - " foobar\r\n\t", - ["atrule", "AN"], - " foobar\r\n\t", - ["atrule", "Ananging"], - " foobar\r\n\t", - ["atrule", "And y'all"], - " foobar\r\n\t", - ["atrule", "And"], - " foobar\r\n\t", - ["atrule", "Angenommen"], - " foobar\r\n\t", - ["atrule", "Anrhegedig a"], - " foobar\r\n\t", - ["atrule", "An"], - " foobar\r\n\t", - ["atrule", "Apabila"], - " foobar\r\n\t", - ["atrule", "Atès"], - " foobar\r\n\t", - ["atrule", "Atesa"], - " foobar\r\n\t", - ["atrule", "Atunci"], - " foobar\r\n\t", - ["atrule", "Avast!"], - " foobar\r\n\t", - ["atrule", "Aye"], - " foobar\r\n\t", - ["atrule", "A"], - " foobar\r\n\t", - ["atrule", "awer"], - " foobar\r\n\t", - ["atrule", "Bagi"], - " foobar\r\n\t", - ["atrule", "Banjur"], - " foobar\r\n\t", - ["atrule", "Bet"], - " foobar\r\n\t", - ["atrule", "Biết"], - " foobar\r\n\t", - ["atrule", "Blimey!"], - " foobar\r\n\t", - ["atrule", "Buh"], - " foobar\r\n\t", - ["atrule", "But at the end of the day I reckon"], - " foobar\r\n\t", - ["atrule", "But y'all"], - " foobar\r\n\t", - ["atrule", "But"], - " foobar\r\n\t", - ["atrule", "BUT"], - " foobar\r\n\t", - ["atrule", "Cal"], - " foobar\r\n\t", - ["atrule", "Când"], - " foobar\r\n\t", - ["atrule", "Cando"], - " foobar\r\n\t", - ["atrule", "Cand"], - " foobar\r\n\t", - ["atrule", "Ce"], - " foobar\r\n\t", - ["atrule", "Cuando"], - " foobar\r\n\t", - ["atrule", "Če"], - " foobar\r\n\t", - ["atrule", "Ða ðe"], - " foobar\r\n\t", - ["atrule", "Ða"], - " foobar\r\n\t", - ["atrule", "Dadas"], - " foobar\r\n\t", - ["atrule", "Dada"], - " foobar\r\n\t", - ["atrule", "Dados"], - " foobar\r\n\t", - ["atrule", "Dado"], - " foobar\r\n\t", - ["atrule", "DaH ghu' bejlu'"], - " foobar\r\n\t", - ["atrule", "dann"], - " foobar\r\n\t", - ["atrule", "Dann"], - " foobar\r\n\t", - ["atrule", "Dano"], - " foobar\r\n\t", - ["atrule", "Dan"], - " foobar\r\n\t", - ["atrule", "Dar"], - " foobar\r\n\t", - ["atrule", "Dat fiind"], - " foobar\r\n\t", - ["atrule", "Data"], - " foobar\r\n\t", - ["atrule", "Date fiind"], - " foobar\r\n\t", - ["atrule", "Date"], - " foobar\r\n\t", - ["atrule", "Dati fiind"], - " foobar\r\n\t", - ["atrule", "Dati"], - " foobar\r\n\t", - ["atrule", "Daţi fiind"], - " foobar\r\n\t", - ["atrule", "Dați fiind"], - " foobar\r\n\t", - ["atrule", "Dato"], - " foobar\r\n\t", - ["atrule", "DEN"], - " foobar\r\n\t", - ["atrule", "Den youse gotta"], - " foobar\r\n\t", - ["atrule", "Dengan"], - " foobar\r\n\t", - ["atrule", "De"], - " foobar\r\n\t", - ["atrule", "Diberi"], - " foobar\r\n\t", - ["atrule", "Diyelim ki"], - " foobar\r\n\t", - ["atrule", "Donada"], - " foobar\r\n\t", - ["atrule", "Donat"], - " foobar\r\n\t", - ["atrule", "Donitaĵo"], - " foobar\r\n\t", - ["atrule", "Do"], - " foobar\r\n\t", - ["atrule", "Dun"], - " foobar\r\n\t", - ["atrule", "Duota"], - " foobar\r\n\t", - ["atrule", "Ðurh"], - " foobar\r\n\t", - ["atrule", "Eeldades"], - " foobar\r\n\t", - ["atrule", "Ef"], - " foobar\r\n\t", - ["atrule", "Eğer ki"], - " foobar\r\n\t", - ["atrule", "Entao"], - " foobar\r\n\t", - ["atrule", "Então"], - " foobar\r\n\t", - ["atrule", "Entón"], - " foobar\r\n\t", - ["atrule", "Entonces"], - " foobar\r\n\t", - ["atrule", "En"], - " foobar\r\n\t", - ["atrule", "Epi"], - " foobar\r\n\t", - ["atrule", "E"], - " foobar\r\n\t", - ["atrule", "És"], - " foobar\r\n\t", - ["atrule", "Etant donnée"], - " foobar\r\n\t", - ["atrule", "Etant donné"], - " foobar\r\n\t", - ["atrule", "Et"], - " foobar\r\n\t", - ["atrule", "Étant données"], - " foobar\r\n\t", - ["atrule", "Étant donnée"], - " foobar\r\n\t", - ["atrule", "Étant donné"], - " foobar\r\n\t", - ["atrule", "Etant données"], - " foobar\r\n\t", - ["atrule", "Etant donnés"], - " foobar\r\n\t", - ["atrule", "Étant donnés"], - " foobar\r\n\t", - ["atrule", "Fakat"], - " foobar\r\n\t", - ["atrule", "Gangway!"], - " foobar\r\n\t", - ["atrule", "Gdy"], - " foobar\r\n\t", - ["atrule", "Gegeben seien"], - " foobar\r\n\t", - ["atrule", "Gegeben sei"], - " foobar\r\n\t", - ["atrule", "Gegeven"], - " foobar\r\n\t", - ["atrule", "Gegewe"], - " foobar\r\n\t", - ["atrule", "ghu' noblu'"], - " foobar\r\n\t", - ["atrule", "Gitt"], - " foobar\r\n\t", - ["atrule", "Given y'all"], - " foobar\r\n\t", - ["atrule", "Given"], - " foobar\r\n\t", - ["atrule", "Givet"], - " foobar\r\n\t", - ["atrule", "Givun"], - " foobar\r\n\t", - ["atrule", "Ha"], - " foobar\r\n\t", - ["atrule", "Cho"], - " foobar\r\n\t", - ["atrule", "I CAN HAZ"], - " foobar\r\n\t", - ["atrule", "In"], - " foobar\r\n\t", - ["atrule", "Ir"], - " foobar\r\n\t", - ["atrule", "It's just unbelievable"], - " foobar\r\n\t", - ["atrule", "I"], - " foobar\r\n\t", - ["atrule", "Ja"], - " foobar\r\n\t", - ["atrule", "Jeśli"], - " foobar\r\n\t", - ["atrule", "Jeżeli"], - " foobar\r\n\t", - ["atrule", "Kadar"], - " foobar\r\n\t", - ["atrule", "Kada"], - " foobar\r\n\t", - ["atrule", "Kad"], - " foobar\r\n\t", - ["atrule", "Kai"], - " foobar\r\n\t", - ["atrule", "Kaj"], - " foobar\r\n\t", - ["atrule", "Když"], - " foobar\r\n\t", - ["atrule", "Keď"], - " foobar\r\n\t", - ["atrule", "Kemudian"], - " foobar\r\n\t", - ["atrule", "Ketika"], - " foobar\r\n\t", - ["atrule", "Khi"], - " foobar\r\n\t", - ["atrule", "Kiedy"], - " foobar\r\n\t", - ["atrule", "Ko"], - " foobar\r\n\t", - ["atrule", "Kuid"], - " foobar\r\n\t", - ["atrule", "Kui"], - " foobar\r\n\t", - ["atrule", "Kun"], - " foobar\r\n\t", - ["atrule", "Lan"], - " foobar\r\n\t", - ["atrule", "latlh"], - " foobar\r\n\t", - ["atrule", "Le sa a"], - " foobar\r\n\t", - ["atrule", "Let go and haul"], - " foobar\r\n\t", - ["atrule", "Le"], - " foobar\r\n\t", - ["atrule", "Lè sa a"], - " foobar\r\n\t", - ["atrule", "Lè"], - " foobar\r\n\t", - ["atrule", "Logo"], - " foobar\r\n\t", - ["atrule", "Lorsqu'<"], - " foobar\r\n\t", - ["atrule", "Lorsque"], - " foobar\r\n\t", - ["atrule", "mä"], - " foobar\r\n\t", - ["atrule", "Maar"], - " foobar\r\n\t", - ["atrule", "Mais"], - " foobar\r\n\t", - ["atrule", "Mając"], - " foobar\r\n\t", - ["atrule", "Majd"], - " foobar\r\n\t", - ["atrule", "Maka"], - " foobar\r\n\t", - ["atrule", "Manawa"], - " foobar\r\n\t", - ["atrule", "Mas"], - " foobar\r\n\t", - ["atrule", "Ma"], - " foobar\r\n\t", - ["atrule", "Menawa"], - " foobar\r\n\t", - ["atrule", "Men"], - " foobar\r\n\t", - ["atrule", "Mutta"], - " foobar\r\n\t", - ["atrule", "Nalikaning"], - " foobar\r\n\t", - ["atrule", "Nalika"], - " foobar\r\n\t", - ["atrule", "Nanging"], - " foobar\r\n\t", - ["atrule", "Når"], - " foobar\r\n\t", - ["atrule", "När"], - " foobar\r\n\t", - ["atrule", "Nato"], - " foobar\r\n\t", - ["atrule", "Nhưng"], - " foobar\r\n\t", - ["atrule", "Niin"], - " foobar\r\n\t", - ["atrule", "Njuk"], - " foobar\r\n\t", - ["atrule", "O zaman"], - " foobar\r\n\t", - ["atrule", "Og"], - " foobar\r\n\t", - ["atrule", "Och"], - " foobar\r\n\t", - ["atrule", "Oletetaan"], - " foobar\r\n\t", - ["atrule", "Onda"], - " foobar\r\n\t", - ["atrule", "Ond"], - " foobar\r\n\t", - ["atrule", "Oraz"], - " foobar\r\n\t", - ["atrule", "Pak"], - " foobar\r\n\t", - ["atrule", "Pero"], - " foobar\r\n\t", - ["atrule", "Però"], - " foobar\r\n\t", - ["atrule", "Podano"], - " foobar\r\n\t", - ["atrule", "Pokiaľ"], - " foobar\r\n\t", - ["atrule", "Pokud"], - " foobar\r\n\t", - ["atrule", "Potem"], - " foobar\r\n\t", - ["atrule", "Potom"], - " foobar\r\n\t", - ["atrule", "Privzeto"], - " foobar\r\n\t", - ["atrule", "Pryd"], - " foobar\r\n\t", - ["atrule", "qaSDI'"], - " foobar\r\n\t", - ["atrule", "Quando"], - " foobar\r\n\t", - ["atrule", "Quand"], - " foobar\r\n\t", - ["atrule", "Quan"], - " foobar\r\n\t", - ["atrule", "Så"], - " foobar\r\n\t", - ["atrule", "Sed"], - " foobar\r\n\t", - ["atrule", "Se"], - " foobar\r\n\t", - ["atrule", "Siis"], - " foobar\r\n\t", - ["atrule", "Sipoze ke"], - " foobar\r\n\t", - ["atrule", "Sipoze Ke"], - " foobar\r\n\t", - ["atrule", "Sipoze"], - " foobar\r\n\t", - ["atrule", "Si"], - " foobar\r\n\t", - ["atrule", "Şi"], - " foobar\r\n\t", - ["atrule", "Și"], - " foobar\r\n\t", - ["atrule", "Soit"], - " foobar\r\n\t", - ["atrule", "Stel"], - " foobar\r\n\t", - ["atrule", "Tada"], - " foobar\r\n\t", - ["atrule", "Tad"], - " foobar\r\n\t", - ["atrule", "Takrat"], - " foobar\r\n\t", - ["atrule", "Tak"], - " foobar\r\n\t", - ["atrule", "Tapi"], - " foobar\r\n\t", - ["atrule", "Ter"], - " foobar\r\n\t", - ["atrule", "Tetapi"], - " foobar\r\n\t", - ["atrule", "Tha the"], - " foobar\r\n\t", - ["atrule", "Tha"], - " foobar\r\n\t", - ["atrule", "Then y'all"], - " foobar\r\n\t", - ["atrule", "Then"], - " foobar\r\n\t", - ["atrule", "Thì"], - " foobar\r\n\t", - ["atrule", "Thurh"], - " foobar\r\n\t", - ["atrule", "Toda"], - " foobar\r\n\t", - ["atrule", "Too right"], - " foobar\r\n\t", - ["atrule", "ugeholl"], - " foobar\r\n\t", - ["atrule", "Und"], - " foobar\r\n\t", - ["atrule", "Un"], - " foobar\r\n\t", - ["atrule", "Và"], - " foobar\r\n\t", - ["atrule", "vaj"], - " foobar\r\n\t", - ["atrule", "Vendar"], - " foobar\r\n\t", - ["atrule", "Ve"], - " foobar\r\n\t", - ["atrule", "wann"], - " foobar\r\n\t", - ["atrule", "Wanneer"], - " foobar\r\n\t", - ["atrule", "WEN"], - " foobar\r\n\t", - ["atrule", "Wenn"], - " foobar\r\n\t", - ["atrule", "When y'all"], - " foobar\r\n\t", - ["atrule", "When"], - " foobar\r\n\t", - ["atrule", "Wtedy"], - " foobar\r\n\t", - ["atrule", "Wun"], - " foobar\r\n\t", - ["atrule", "Y'know"], - " foobar\r\n\t", - ["atrule", "Yeah nah"], - " foobar\r\n\t", - ["atrule", "Yna"], - " foobar\r\n\t", - ["atrule", "Youse know like when"], - " foobar\r\n\t", - ["atrule", "Youse know when youse got"], - " foobar\r\n\t", - ["atrule", "Y"], - " foobar\r\n\t", - ["atrule", "Za predpokladu"], - " foobar\r\n\t", - ["atrule", "Za předpokladu"], - " foobar\r\n\t", - ["atrule", "Zadani"], - " foobar\r\n\t", - ["atrule", "Zadano"], - " foobar\r\n\t", - ["atrule", "Zadan"], - " foobar\r\n\t", - ["atrule", "Zadate"], - " foobar\r\n\t", - ["atrule", "Zadato"], - " foobar\r\n\t", - ["atrule", "Zakładając"], - " foobar\r\n\t", - ["atrule", "Zaradi"], - " foobar\r\n\t", - ["atrule", "Zatati"], - " foobar\r\n\t", - ["atrule", "Þa"], - " foobar\r\n\t", - ["atrule", "Þá"], - " foobar\r\n\t", - ["atrule", "Þa þe"], - " foobar\r\n\t", - ["atrule", "Þegar"], - " foobar\r\n\t", - ["atrule", "Þurh"], - " foobar\r\n\t", - ["atrule", "Αλλά"], - " foobar\r\n\t", - ["atrule", "Δεδομένου"], - " foobar\r\n\t", - ["atrule", "Και"], - " foobar\r\n\t", - ["atrule", "Όταν"], - " foobar\r\n\t", - ["atrule", "Τότε"], - " foobar\r\n\t", - ["atrule", "А також"], - " foobar\r\n\t", - ["atrule", "Агар"], - " foobar\r\n\t", - ["atrule", "Але"], - " foobar\r\n\t", - ["atrule", "Али"], - " foobar\r\n\t", - ["atrule", "Аммо"], - " foobar\r\n\t", - ["atrule", "А"], - " foobar\r\n\t", - ["atrule", "Әгәр"], - " foobar\r\n\t", - ["atrule", "Әйтик"], - " foobar\r\n\t", - ["atrule", "Әмма"], - " foobar\r\n\t", - ["atrule", "Бирок"], - " foobar\r\n\t", - ["atrule", "Ва"], - " foobar\r\n\t", - ["atrule", "Вә"], - " foobar\r\n\t", - ["atrule", "Дадено"], - " foobar\r\n\t", - ["atrule", "Дано"], - " foobar\r\n\t", - ["atrule", "Допустим"], - " foobar\r\n\t", - ["atrule", "Если"], - " foobar\r\n\t", - ["atrule", "Задате"], - " foobar\r\n\t", - ["atrule", "Задати"], - " foobar\r\n\t", - ["atrule", "Задато"], - " foobar\r\n\t", - ["atrule", "И"], - " foobar\r\n\t", - ["atrule", "І"], - " foobar\r\n\t", - ["atrule", "К тому же"], - " foobar\r\n\t", - ["atrule", "Када"], - " foobar\r\n\t", - ["atrule", "Кад"], - " foobar\r\n\t", - ["atrule", "Когато"], - " foobar\r\n\t", - ["atrule", "Когда"], - " foobar\r\n\t", - ["atrule", "Коли"], - " foobar\r\n\t", - ["atrule", "Ләкин"], - " foobar\r\n\t", - ["atrule", "Лекин"], - " foobar\r\n\t", - ["atrule", "Нәтиҗәдә"], - " foobar\r\n\t", - ["atrule", "Нехай"], - " foobar\r\n\t", - ["atrule", "Но"], - " foobar\r\n\t", - ["atrule", "Онда"], - " foobar\r\n\t", - ["atrule", "Припустимо, що"], - " foobar\r\n\t", - ["atrule", "Припустимо"], - " foobar\r\n\t", - ["atrule", "Пусть"], - " foobar\r\n\t", - ["atrule", "Также"], - " foobar\r\n\t", - ["atrule", "Та"], - " foobar\r\n\t", - ["atrule", "Тогда"], - " foobar\r\n\t", - ["atrule", "Тоді"], - " foobar\r\n\t", - ["atrule", "То"], - " foobar\r\n\t", - ["atrule", "Унда"], - " foobar\r\n\t", - ["atrule", "Һәм"], - " foobar\r\n\t", - ["atrule", "Якщо"], - " foobar\r\n\t", - ["atrule", "אבל"], - " foobar\r\n\t", - ["atrule", "אזי"], - " foobar\r\n\t", - ["atrule", "אז"], - " foobar\r\n\t", - ["atrule", "בהינתן"], - " foobar\r\n\t", - ["atrule", "וגם"], - " foobar\r\n\t", - ["atrule", "כאשר"], - " foobar\r\n\t", - ["atrule", "آنگاه"], - " foobar\r\n\t", - ["atrule", "اذاً"], - " foobar\r\n\t", - ["atrule", "اگر"], - " foobar\r\n\t", - ["atrule", "اما"], - " foobar\r\n\t", - ["atrule", "اور"], - " foobar\r\n\t", - ["atrule", "با فرض"], - " foobar\r\n\t", - ["atrule", "بالفرض"], - " foobar\r\n\t", - ["atrule", "بفرض"], - " foobar\r\n\t", - ["atrule", "پھر"], - " foobar\r\n\t", - ["atrule", "تب"], - " foobar\r\n\t", - ["atrule", "ثم"], - " foobar\r\n\t", - ["atrule", "جب"], - " foobar\r\n\t", - ["atrule", "عندما"], - " foobar\r\n\t", - ["atrule", "فرض کیا"], - " foobar\r\n\t", - ["atrule", "لكن"], - " foobar\r\n\t", - ["atrule", "لیکن"], - " foobar\r\n\t", - ["atrule", "متى"], - " foobar\r\n\t", - ["atrule", "هنگامی"], - " foobar\r\n\t", - ["atrule", "و"], - " foobar\r\n\t", - ["atrule", "अगर"], - " foobar\r\n\t", - ["atrule", "और"], - " foobar\r\n\t", - ["atrule", "कदा"], - " foobar\r\n\t", - ["atrule", "किन्तु"], - " foobar\r\n\t", - ["atrule", "चूंकि"], - " foobar\r\n\t", - ["atrule", "जब"], - " foobar\r\n\t", - ["atrule", "तथा"], - " foobar\r\n\t", - ["atrule", "तदा"], - " foobar\r\n\t", - ["atrule", "तब"], - " foobar\r\n\t", - ["atrule", "परन्तु"], - " foobar\r\n\t", - ["atrule", "पर"], - " foobar\r\n\t", - ["atrule", "यदि"], - " foobar\r\n\t", - ["atrule", "ਅਤੇ"], - " foobar\r\n\t", - ["atrule", "ਜਦੋਂ"], - " foobar\r\n\t", - ["atrule", "ਜਿਵੇਂ ਕਿ"], - " foobar\r\n\t", - ["atrule", "ਜੇਕਰ"], - " foobar\r\n\t", - ["atrule", "ਤਦ"], - " foobar\r\n\t", - ["atrule", "ਪਰ"], - " foobar\r\n\t", - ["atrule", "అప్పుడు"], - " foobar\r\n\t", - ["atrule", "ఈ పరిస్థితిలో"], - " foobar\r\n\t", - ["atrule", "కాని"], - " foobar\r\n\t", - ["atrule", "చెప్పబడినది"], - " foobar\r\n\t", - ["atrule", "మరియు"], - " foobar\r\n\t", - ["atrule", "ಆದರೆ"], - " foobar\r\n\t", - ["atrule", "ನಂತರ"], - " foobar\r\n\t", - ["atrule", "ನೀಡಿದ"], - " foobar\r\n\t", - ["atrule", "ಮತ್ತು"], - " foobar\r\n\t", - ["atrule", "ಸ್ಥಿತಿಯನ್ನು"], - " foobar\r\n\t", - ["atrule", "กำหนดให้"], - " foobar\r\n\t", - ["atrule", "ดังนั้น"], - " foobar\r\n\t", - ["atrule", "แต่"], - " foobar\r\n\t", - ["atrule", "เมื่อ"], - " foobar\r\n\t", - ["atrule", "และ"], - " foobar\r\n\t", - ["atrule", "그러면<"], - " foobar\r\n\t", - ["atrule", "그리고<"], - " foobar\r\n\t", - ["atrule", "단<"], - " foobar\r\n\t", - ["atrule", "만약<"], - " foobar\r\n\t", - ["atrule", "만일<"], - " foobar\r\n\t", - ["atrule", "먼저<"], - " foobar\r\n\t", - ["atrule", "조건<"], - " foobar\r\n\t", - ["atrule", "하지만<"], - " foobar\r\n\t", - ["atrule", "かつ<"], - " foobar\r\n\t", - ["atrule", "しかし<"], - " foobar\r\n\t", - ["atrule", "ただし<"], - " foobar\r\n\t", - ["atrule", "ならば<"], - " foobar\r\n\t", - ["atrule", "もし<"], - " foobar\r\n\t", - ["atrule", "並且<"], - " foobar\r\n\t", - ["atrule", "但し<"], - " foobar\r\n\t", - ["atrule", "但是<"], - " foobar\r\n\t", - ["atrule", "假如<"], - " foobar\r\n\t", - ["atrule", "假定<"], - " foobar\r\n\t", - ["atrule", "假設<"], - " foobar\r\n\t", - ["atrule", "假设<"], - " foobar\r\n\t", - ["atrule", "前提<"], - " foobar\r\n\t", - ["atrule", "同时<"], - " foobar\r\n\t", - ["atrule", "同時<"], - " foobar\r\n\t", - ["atrule", "并且<"], - " foobar\r\n\t", - ["atrule", "当<"], - " foobar\r\n\t", - ["atrule", "當<"], - " foobar\r\n\t", - ["atrule", "而且<"], - " foobar\r\n\t", - ["atrule", "那么<"], - " foobar\r\n\t", - ["atrule", "那麼<"], - " foobar" -] - ----------------------------------------------------- - -Checks for at-rules in all languages. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/comment_feature.test b/docs/_style/prism-master/tests/languages/gherkin/comment_feature.test deleted file mode 100644 index ce88cc95..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -# -# foo bar - # foobar - ----------------------------------------------------- - -[ - ["comment", "#"], - ["comment", "# foo bar"], - ["comment", "# foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/feature_feature.test b/docs/_style/prism-master/tests/languages/gherkin/feature_feature.test deleted file mode 100644 index 774dd363..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/feature_feature.test +++ /dev/null @@ -1,196 +0,0 @@ -Ability: foobar -baz -Ahoy matey!: -Arwedd: foobar -Aspekt: foobar -Besigheid Behoefte: foobar -Business Need: foobar -Caracteristica: foobar -Característica: foobar -Egenskab: foobar -Egenskap: foobar -Eiginleiki: foobar -Feature: foobar -Fīča: foobar -Fitur: foobar -Fonctionnalité: foobar -Fonksyonalite: foobar -Funcionalidade: foobar -Funcionalitat: foobar -Functionalitate: foobar -Funcţionalitate: foobar -Funcționalitate: foobar -Functionaliteit: foobar -Fungsi: foobar -Funkcia: foobar -Funkcija: foobar -Funkcionalitāte: foobar -Funkcionalnost: foobar -Funkcja: foobar -Funksie: foobar -Funktionalität: foobar -Funktionalitéit: foobar -Funzionalità: foobar -Hwaet: foobar -Hwæt: foobar -Jellemző: foobar -Karakteristik: foobar -laH: foobar -Lastnost: foobar -Mak: foobar -Mogucnost: foobar -Mogućnost: foobar -Moznosti: foobar -Možnosti: foobar -OH HAI: foobar -Omadus: foobar -Ominaisuus: foobar -Osobina: foobar -Özellik: foobar -perbogh: foobar -poQbogh malja': foobar -Potrzeba biznesowa: foobar -Požadavek: foobar -Požiadavka: foobar -Pretty much: foobar -Qap: foobar -Qu'meH 'ut: foobar -Savybė: foobar -Tính năng: foobar -Trajto: foobar -Vermoë: foobar -Vlastnosť: foobar -Właściwość: foobar -Značilnost: foobar -Δυνατότητα: foobar -Λειτουργία: foobar -Могућност: foobar -Мөмкинлек: foobar -Особина: foobar -Свойство: foobar -Үзенчәлеклелек: foobar -Функционал: foobar -Функционалност: foobar -Функция: foobar -Функціонал: foobar -תכונה: foobar -خاصية: foobar -خصوصیت: foobar -صلاحیت: foobar -کاروبار کی ضرورت: foobar -وِیژگی: foobar -रूप लेख: foobar -ਖਾਸੀਅਤ: foobar -ਨਕਸ਼ ਨੁਹਾਰ: foobar -ਮੁਹਾਂਦਰਾ: foobar -గుణము: foobar -ಹೆಚ್ಚಳ: foobar -ความต้องการทางธุรกิจ: foobar -ความสามารถ: foobar -โครงหลัก: foobar -기능: foobar -フィーチャ: foobar -功能: foobar -機能: foobar - ----------------------------------------------------- - -[ - ["feature", [["keyword", "Ability:"], ["important", " foobar"], "\r\nbaz\r\n"]], - ["feature", [["keyword", "Ahoy matey!:"]]], - ["feature", [["keyword", "Arwedd:"], ["important", " foobar"]]], - ["feature", [["keyword", "Aspekt:"], ["important", " foobar"]]], - ["feature", [["keyword", "Besigheid Behoefte:"], ["important", " foobar"]]], - ["feature", [["keyword", "Business Need:"], ["important", " foobar"]]], - ["feature", [["keyword", "Caracteristica:"], ["important", " foobar"]]], - ["feature", [["keyword", "Característica:"], ["important", " foobar"]]], - ["feature", [["keyword", "Egenskab:"], ["important", " foobar"]]], - ["feature", [["keyword", "Egenskap:"], ["important", " foobar"]]], - ["feature", [["keyword", "Eiginleiki:"], ["important", " foobar"]]], - ["feature", [["keyword", "Feature:"], ["important", " foobar"]]], - ["feature", [["keyword", "Fīča:"], ["important", " foobar"]]], - ["feature", [["keyword", "Fitur:"], ["important", " foobar"]]], - ["feature", [["keyword", "Fonctionnalité:"], ["important", " foobar"]]], - ["feature", [["keyword", "Fonksyonalite:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funcionalidade:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funcionalitat:"], ["important", " foobar"]]], - ["feature", [["keyword", "Functionalitate:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funcţionalitate:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funcționalitate:"], ["important", " foobar"]]], - ["feature", [["keyword", "Functionaliteit:"], ["important", " foobar"]]], - ["feature", [["keyword", "Fungsi:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funkcia:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funkcija:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funkcionalitāte:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funkcionalnost:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funkcja:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funksie:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funktionalität:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funktionalitéit:"], ["important", " foobar"]]], - ["feature", [["keyword", "Funzionalità:"], ["important", " foobar"]]], - ["feature", [["keyword", "Hwaet:"], ["important", " foobar"]]], - ["feature", [["keyword", "Hwæt:"], ["important", " foobar"]]], - ["feature", [["keyword", "Jellemző:"], ["important", " foobar"]]], - ["feature", [["keyword", "Karakteristik:"], ["important", " foobar"]]], - ["feature", [["keyword", "laH:"], ["important", " foobar"]]], - ["feature", [["keyword", "Lastnost:"], ["important", " foobar"]]], - ["feature", [["keyword", "Mak:"], ["important", " foobar"]]], - ["feature", [["keyword", "Mogucnost:"], ["important", " foobar"]]], - ["feature", [["keyword", "Mogućnost:"], ["important", " foobar"]]], - ["feature", [["keyword", "Moznosti:"], ["important", " foobar"]]], - ["feature", [["keyword", "Možnosti:"], ["important", " foobar"]]], - ["feature", [["keyword", "OH HAI:"], ["important", " foobar"]]], - ["feature", [["keyword", "Omadus:"], ["important", " foobar"]]], - ["feature", [["keyword", "Ominaisuus:"], ["important", " foobar"]]], - ["feature", [["keyword", "Osobina:"], ["important", " foobar"]]], - ["feature", [["keyword", "Özellik:"], ["important", " foobar"]]], - ["feature", [["keyword", "perbogh:"], ["important", " foobar"]]], - ["feature", [["keyword", "poQbogh malja':"], ["important", " foobar"]]], - ["feature", [["keyword", "Potrzeba biznesowa:"], ["important", " foobar"]]], - ["feature", [["keyword", "Požadavek:"], ["important", " foobar"]]], - ["feature", [["keyword", "Požiadavka:"], ["important", " foobar"]]], - ["feature", [["keyword", "Pretty much:"], ["important", " foobar"]]], - ["feature", [["keyword", "Qap:"], ["important", " foobar"]]], - ["feature", [["keyword", "Qu'meH 'ut:"], ["important", " foobar"]]], - ["feature", [["keyword", "Savybė:"], ["important", " foobar"]]], - ["feature", [["keyword", "Tính năng:"], ["important", " foobar"]]], - ["feature", [["keyword", "Trajto:"], ["important", " foobar"]]], - ["feature", [["keyword", "Vermoë:"], ["important", " foobar"]]], - ["feature", [["keyword", "Vlastnosť:"], ["important", " foobar"]]], - ["feature", [["keyword", "Właściwość:"], ["important", " foobar"]]], - ["feature", [["keyword", "Značilnost:"], ["important", " foobar"]]], - ["feature", [["keyword", "Δυνατότητα:"], ["important", " foobar"]]], - ["feature", [["keyword", "Λειτουργία:"], ["important", " foobar"]]], - ["feature", [["keyword", "Могућност:"], ["important", " foobar"]]], - ["feature", [["keyword", "Мөмкинлек:"], ["important", " foobar"]]], - ["feature", [["keyword", "Особина:"], ["important", " foobar"]]], - ["feature", [["keyword", "Свойство:"], ["important", " foobar"]]], - ["feature", [["keyword", "Үзенчәлеклелек:"], ["important", " foobar"]]], - ["feature", [["keyword", "Функционал:"], ["important", " foobar"]]], - ["feature", [["keyword", "Функционалност:"], ["important", " foobar"]]], - ["feature", [["keyword", "Функция:"], ["important", " foobar"]]], - ["feature", [["keyword", "Функціонал:"], ["important", " foobar"]]], - ["feature", [["keyword", "תכונה:"], ["important", " foobar"]]], - ["feature", [["keyword", "خاصية:"], ["important", " foobar"]]], - ["feature", [["keyword", "خصوصیت:"], ["important", " foobar"]]], - ["feature", [["keyword", "صلاحیت:"], ["important", " foobar"]]], - ["feature", [["keyword", "کاروبار کی ضرورت:"], ["important", " foobar"]]], - ["feature", [["keyword", "وِیژگی:"], ["important", " foobar"]]], - ["feature", [["keyword", "रूप लेख:"], ["important", " foobar"]]], - ["feature", [["keyword", "ਖਾਸੀਅਤ:"], ["important", " foobar"]]], - ["feature", [["keyword", "ਨਕਸ਼ ਨੁਹਾਰ:"], ["important", " foobar"]]], - ["feature", [["keyword", "ਮੁਹਾਂਦਰਾ:"], ["important", " foobar"]]], - ["feature", [["keyword", "గుణము:"], ["important", " foobar"]]], - ["feature", [["keyword", "ಹೆಚ್ಚಳ:"], ["important", " foobar"]]], - ["feature", [["keyword", "ความต้องการทางธุรกิจ:"], ["important", " foobar"]]], - ["feature", [["keyword", "ความสามารถ:"], ["important", " foobar"]]], - ["feature", [["keyword", "โครงหลัก:"], ["important", " foobar"]]], - ["feature", [["keyword", "기능:"], ["important", " foobar"]]], - ["feature", [["keyword", "フィーチャ:"], ["important", " foobar"]]], - ["feature", [["keyword", "功能:"], ["important", " foobar"]]], - ["feature", [["keyword", "機能:"], ["important", " foobar"]]] -] - ----------------------------------------------------- - -Checks for features in all languages. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/outline_feature.test b/docs/_style/prism-master/tests/languages/gherkin/outline_feature.test deleted file mode 100644 index 19fc54f9..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/outline_feature.test +++ /dev/null @@ -1,11 +0,0 @@ - - ----------------------------------------------------- - -[ - ["outline", ""] -] - ----------------------------------------------------- - -Checks for single outlines. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/pystring_feature.test b/docs/_style/prism-master/tests/languages/gherkin/pystring_feature.test deleted file mode 100644 index 17fd2b4d..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/pystring_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -""" -foo -bar -""" - -''' -foo -bar -''' - ----------------------------------------------------- - -[ - ["pystring", "\"\"\"\r\nfoo\r\nbar\r\n\"\"\""], - ["pystring", "'''\r\nfoo\r\nbar\r\n'''"] -] - ----------------------------------------------------- - -Checks for pystrings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/scenario_feature.test b/docs/_style/prism-master/tests/languages/gherkin/scenario_feature.test deleted file mode 100644 index 0979c698..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/scenario_feature.test +++ /dev/null @@ -1,581 +0,0 @@ -Abstract Scenario: foobar -Abstrakt Scenario: foobar -Achtergrond: foobar -Aer: foobar -Ær: foobar -Agtergrond: foobar -All y'all: foobar -Antecedentes: foobar -Antecedents: foobar -Atburðarás: foobar -Atburðarásir: foobar -Awww, look mate: foobar -B4: foobar -Background: foobar -Baggrund: foobar -Bakgrund: foobar -Bakgrunn: foobar -Bakgrunnur: foobar -Beispiele: foobar -Beispiller: foobar -Bối cảnh: foobar -Cefndir: foobar -Cenario: foobar -Cenário: foobar -Cenario de Fundo: foobar -Cenário de Fundo: foobar -Cenarios: foobar -Cenários: foobar -Contesto: foobar -Context: foobar -Contexte: foobar -Contexto: foobar -Conto: foobar -Contoh: foobar -Contone: foobar -Dæmi: foobar -Dasar: foobar -Dead men tell no tales: foobar -Delineacao do Cenario: foobar -Delineação do Cenário: foobar -Dis is what went down: foobar -Dữ liệu: foobar -Dyagram senaryo: foobar -Dyagram Senaryo: foobar -Egzanp: foobar -Ejemplos: foobar -Eksempler: foobar -Ekzemploj: foobar -Enghreifftiau: foobar -Esbozo do escenario: foobar -Escenari: foobar -Escenario: foobar -Esempi: foobar -Esquema de l'escenari: foobar -Esquema del escenario: foobar -Esquema do Cenario: foobar -Esquema do Cenário: foobar -Examples: foobar -EXAMPLZ: foobar -Exempel: foobar -Exemple: foobar -Exemples: foobar -Exemplos: foobar -First off: foobar -Fono: foobar -Forgatókönyv: foobar -Forgatókönyv vázlat: foobar -Fundo: foobar -Geçmiş: foobar -ghantoH: foobar -Grundlage: foobar -Hannergrond: foobar -Háttér: foobar -Heave to: foobar -Istorik: foobar -Juhtumid: foobar -Keadaan: foobar -Khung kịch bản: foobar -Khung tình huống: foobar -Kịch bản: foobar -Koncept: foobar -Konsep skenario: foobar -Kontèks: foobar -Kontekst: foobar -Kontekstas: foobar -Konteksts: foobar -Kontext: foobar -Konturo de la scenaro: foobar -Latar Belakang: foobar -lut: foobar -lut chovnatlh: foobar -lutmey: foobar -Lýsing Atburðarásar: foobar -Lýsing Dæma: foobar -Menggariskan Senario: foobar -MISHUN: foobar -MISHUN SRSLY: foobar -mo': foobar -Náčrt Scenára: foobar -Náčrt Scénáře: foobar -Náčrt Scenáru: foobar -Oris scenarija: foobar -Örnekler: foobar -Osnova: foobar -Osnova Scenára: foobar -Osnova scénáře: foobar -Osnutek: foobar -Ozadje: foobar -Paraugs: foobar -Pavyzdžiai: foobar -Példák: foobar -Piemēri: foobar -Plan du scénario: foobar -Plan du Scénario: foobar -Plan senaryo: foobar -Plan Senaryo: foobar -Plang vum Szenario: foobar -Pozadí: foobar -Pozadie: foobar -Pozadina: foobar -Príklady: foobar -Příklady: foobar -Primer: foobar -Primeri: foobar -Primjeri: foobar -Przykłady: foobar -Raamstsenaarium: foobar -Reckon it's like: foobar -Rerefons: foobar -Scenár: foobar -Scénář: foobar -Scenarie: foobar -Scenarij: foobar -Scenarijai: foobar -Scenarijaus šablonas: foobar -Scenariji: foobar -Scenārijs: foobar -Scenārijs pēc parauga: foobar -Scenarijus: foobar -Scenario: foobar -Scénario: foobar -Scenario Amlinellol: foobar -Scenario Outline: foobar -Scenario Template: foobar -Scenariomal: foobar -Scenariomall: foobar -Scenarios: foobar -Scenariu: foobar -Scenariusz: foobar -Scenaro: foobar -Schema dello scenario: foobar -Se ðe: foobar -Se the: foobar -Se þe: foobar -Senario: foobar -Senaryo: foobar -Senaryo deskripsyon: foobar -Senaryo Deskripsyon: foobar -Senaryo taslağı: foobar -Shiver me timbers: foobar -Situācija: foobar -Situai: foobar -Situasie: foobar -Situasie Uiteensetting: foobar -Skenario: foobar -Skenario konsep: foobar -Skica: foobar -Structura scenariu: foobar -Structură scenariu: foobar -Struktura scenarija: foobar -Stsenaarium: foobar -Swa: foobar -Swa hwaer swa: foobar -Swa hwær swa: foobar -Szablon scenariusza: foobar -Szenario: foobar -Szenariogrundriss: foobar -Tapaukset: foobar -Tapaus: foobar -Tapausaihio: foobar -Taust: foobar -Tausta: foobar -Template Keadaan: foobar -Template Senario: foobar -Template Situai: foobar -The thing of it is: foobar -Tình huống: foobar -Variantai: foobar -Voorbeelde: foobar -Voorbeelden: foobar -Wharrimean is: foobar -Yo-ho-ho: foobar -You'll wanna: foobar -Założenia: foobar -Παραδείγματα: foobar -Περιγραφή Σεναρίου: foobar -Σενάρια: foobar -Σενάριο: foobar -Υπόβαθρο: foobar -Кереш: foobar -Контекст: foobar -Концепт: foobar -Мисаллар: foobar -Мисоллар: foobar -Основа: foobar -Передумова: foobar -Позадина: foobar -Предистория: foobar -Предыстория: foobar -Приклади: foobar -Пример: foobar -Примери: foobar -Примеры: foobar -Рамка на сценарий: foobar -Скица: foobar -Структура сценарија: foobar -Структура сценария: foobar -Структура сценарію: foobar -Сценарий: foobar -Сценарий структураси: foobar -Сценарийның төзелеше: foobar -Сценарији: foobar -Сценарио: foobar -Сценарій: foobar -Тарих: foobar -Үрнәкләр: foobar -דוגמאות: foobar -רקע: foobar -תבנית תרחיש: foobar -תרחיש: foobar -الخلفية: foobar -الگوی سناریو: foobar -امثلة: foobar -پس منظر: foobar -زمینه: foobar -سناریو: foobar -سيناريو: foobar -سيناريو مخطط: foobar -مثالیں: foobar -منظر نامے کا خاکہ: foobar -منظرنامہ: foobar -نمونه ها: foobar -उदाहरण: foobar -परिदृश्य: foobar -परिदृश्य रूपरेखा: foobar -पृष्ठभूमि: foobar -ਉਦਾਹਰਨਾਂ: foobar -ਪਟਕਥਾ: foobar -ਪਟਕਥਾ ਢਾਂਚਾ: foobar -ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ: foobar -ਪਿਛੋਕੜ: foobar -ఉదాహరణలు: foobar -కథనం: foobar -నేపథ్యం: foobar -సన్నివేశం: foobar -ಉದಾಹರಣೆಗಳು: foobar -ಕಥಾಸಾರಾಂಶ: foobar -ವಿವರಣೆ: foobar -ಹಿನ್ನೆಲೆ: foobar -โครงสร้างของเหตุการณ์: foobar -ชุดของตัวอย่าง: foobar -ชุดของเหตุการณ์: foobar -แนวคิด: foobar -สรุปเหตุการณ์: foobar -เหตุการณ์: foobar -배경: foobar -시나리오: foobar -시나리오 개요: foobar -예: foobar -サンプル: foobar -シナリオ: foobar -シナリオアウトライン: foobar -シナリオテンプレ: foobar -シナリオテンプレート: foobar -テンプレ: foobar -例: foobar -例子: foobar -剧本: foobar -剧本大纲: foobar -劇本: foobar -劇本大綱: foobar -场景: foobar -场景大纲: foobar -場景: foobar -場景大綱: foobar -背景: foobar - ----------------------------------------------------- - -[ - ["scenario", [["keyword", "Abstract Scenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Abstrakt Scenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Achtergrond:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Aer:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Ær:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Agtergrond:"], ["important", " foobar"]]], - ["scenario", [["keyword", "All y'all:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Antecedentes:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Antecedents:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Atburðarás:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Atburðarásir:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Awww, look mate:"], ["important", " foobar"]]], - ["scenario", [["keyword", "B4:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Background:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Baggrund:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Bakgrund:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Bakgrunn:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Bakgrunnur:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Beispiele:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Beispiller:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Bối cảnh:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cefndir:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenário:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenario de Fundo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenário de Fundo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenarios:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Cenários:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Contesto:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Context:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Contexte:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Contexto:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Conto:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Contoh:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Contone:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dæmi:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dasar:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dead men tell no tales:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Delineacao do Cenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Delineação do Cenário:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dis is what went down:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dữ liệu:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dyagram senaryo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Dyagram Senaryo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Egzanp:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Ejemplos:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Eksempler:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Ekzemploj:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Enghreifftiau:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esbozo do escenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Escenari:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Escenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esempi:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esquema de l'escenari:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esquema del escenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esquema do Cenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Esquema do Cenário:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Examples:"], ["important", " foobar"]]], - ["scenario", [["keyword", "EXAMPLZ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Exempel:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Exemple:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Exemples:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Exemplos:"], ["important", " foobar"]]], - ["scenario", [["keyword", "First off:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Fono:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Forgatókönyv:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Forgatókönyv vázlat:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Fundo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Geçmiş:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ghantoH:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Grundlage:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Hannergrond:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Háttér:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Heave to:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Istorik:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Juhtumid:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Keadaan:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Khung kịch bản:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Khung tình huống:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Kịch bản:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Koncept:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Konsep skenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Kontèks:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Kontekst:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Kontekstas:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Konteksts:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Kontext:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Konturo de la scenaro:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Latar Belakang:"], ["important", " foobar"]]], - ["scenario", [["keyword", "lut:"], ["important", " foobar"]]], - ["scenario", [["keyword", "lut chovnatlh:"], ["important", " foobar"]]], - ["scenario", [["keyword", "lutmey:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Lýsing Atburðarásar:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Lýsing Dæma:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Menggariskan Senario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "MISHUN:"], ["important", " foobar"]]], - ["scenario", [["keyword", "MISHUN SRSLY:"], ["important", " foobar"]]], - ["scenario", [["keyword", "mo':"], ["important", " foobar"]]], - ["scenario", [["keyword", "Náčrt Scenára:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Náčrt Scénáře:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Náčrt Scenáru:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Oris scenarija:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Örnekler:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Osnova:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Osnova Scenára:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Osnova scénáře:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Osnutek:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Ozadje:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Paraugs:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Pavyzdžiai:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Példák:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Piemēri:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Plan du scénario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Plan du Scénario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Plan senaryo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Plan Senaryo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Plang vum Szenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Pozadí:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Pozadie:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Pozadina:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Príklady:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Příklady:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Primer:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Primeri:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Primjeri:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Przykłady:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Raamstsenaarium:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Reckon it's like:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Rerefons:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenár:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scénář:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarie:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarij:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarijai:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarijaus šablonas:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenariji:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenārijs:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenārijs pēc parauga:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarijus:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scénario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenario Amlinellol:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenario Outline:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenario Template:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenariomal:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenariomall:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenarios:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenariu:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenariusz:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Scenaro:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Schema dello scenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Se ðe:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Se the:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Se þe:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Senario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Senaryo:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Senaryo deskripsyon:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Senaryo Deskripsyon:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Senaryo taslağı:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Shiver me timbers:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Situācija:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Situai:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Situasie:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Situasie Uiteensetting:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Skenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Skenario konsep:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Skica:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Structura scenariu:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Structură scenariu:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Struktura scenarija:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Stsenaarium:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Swa:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Swa hwaer swa:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Swa hwær swa:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Szablon scenariusza:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Szenario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Szenariogrundriss:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Tapaukset:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Tapaus:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Tapausaihio:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Taust:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Tausta:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Template Keadaan:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Template Senario:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Template Situai:"], ["important", " foobar"]]], - ["scenario", [["keyword", "The thing of it is:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Tình huống:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Variantai:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Voorbeelde:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Voorbeelden:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Wharrimean is:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Yo-ho-ho:"], ["important", " foobar"]]], - ["scenario", [["keyword", "You'll wanna:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Założenia:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Παραδείγματα:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Περιγραφή Σεναρίου:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Σενάρια:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Σενάριο:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Υπόβαθρο:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Кереш:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Контекст:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Концепт:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Мисаллар:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Мисоллар:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Основа:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Передумова:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Позадина:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Предистория:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Предыстория:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Приклади:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Пример:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Примери:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Примеры:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Рамка на сценарий:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Скица:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Структура сценарија:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Структура сценария:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Структура сценарію:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарий:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарий структураси:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарийның төзелеше:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарији:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарио:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Сценарій:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Тарих:"], ["important", " foobar"]]], - ["scenario", [["keyword", "Үрнәкләр:"], ["important", " foobar"]]], - ["scenario", [["keyword", "דוגמאות:"], ["important", " foobar"]]], - ["scenario", [["keyword", "רקע:"], ["important", " foobar"]]], - ["scenario", [["keyword", "תבנית תרחיש:"], ["important", " foobar"]]], - ["scenario", [["keyword", "תרחיש:"], ["important", " foobar"]]], - ["scenario", [["keyword", "الخلفية:"], ["important", " foobar"]]], - ["scenario", [["keyword", "الگوی سناریو:"], ["important", " foobar"]]], - ["scenario", [["keyword", "امثلة:"], ["important", " foobar"]]], - ["scenario", [["keyword", "پس منظر:"], ["important", " foobar"]]], - ["scenario", [["keyword", "زمینه:"], ["important", " foobar"]]], - ["scenario", [["keyword", "سناریو:"], ["important", " foobar"]]], - ["scenario", [["keyword", "سيناريو:"], ["important", " foobar"]]], - ["scenario", [["keyword", "سيناريو مخطط:"], ["important", " foobar"]]], - ["scenario", [["keyword", "مثالیں:"], ["important", " foobar"]]], - ["scenario", [["keyword", "منظر نامے کا خاکہ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "منظرنامہ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "نمونه ها:"], ["important", " foobar"]]], - ["scenario", [["keyword", "उदाहरण:"], ["important", " foobar"]]], - ["scenario", [["keyword", "परिदृश्य:"], ["important", " foobar"]]], - ["scenario", [["keyword", "परिदृश्य रूपरेखा:"], ["important", " foobar"]]], - ["scenario", [["keyword", "पृष्ठभूमि:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ਉਦਾਹਰਨਾਂ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ਪਟਕਥਾ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ਪਟਕਥਾ ਢਾਂਚਾ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ਪਿਛੋਕੜ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ఉదాహరణలు:"], ["important", " foobar"]]], - ["scenario", [["keyword", "కథనం:"], ["important", " foobar"]]], - ["scenario", [["keyword", "నేపథ్యం:"], ["important", " foobar"]]], - ["scenario", [["keyword", "సన్నివేశం:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ಉದಾಹರಣೆಗಳು:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ಕಥಾಸಾರಾಂಶ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ವಿವರಣೆ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ಹಿನ್ನೆಲೆ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "โครงสร้างของเหตุการณ์:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ชุดของตัวอย่าง:"], ["important", " foobar"]]], - ["scenario", [["keyword", "ชุดของเหตุการณ์:"], ["important", " foobar"]]], - ["scenario", [["keyword", "แนวคิด:"], ["important", " foobar"]]], - ["scenario", [["keyword", "สรุปเหตุการณ์:"], ["important", " foobar"]]], - ["scenario", [["keyword", "เหตุการณ์:"], ["important", " foobar"]]], - ["scenario", [["keyword", "배경:"], ["important", " foobar"]]], - ["scenario", [["keyword", "시나리오:"], ["important", " foobar"]]], - ["scenario", [["keyword", "시나리오 개요:"], ["important", " foobar"]]], - ["scenario", [["keyword", "예:"], ["important", " foobar"]]], - ["scenario", [["keyword", "サンプル:"], ["important", " foobar"]]], - ["scenario", [["keyword", "シナリオ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "シナリオアウトライン:"], ["important", " foobar"]]], - ["scenario", [["keyword", "シナリオテンプレ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "シナリオテンプレート:"], ["important", " foobar"]]], - ["scenario", [["keyword", "テンプレ:"], ["important", " foobar"]]], - ["scenario", [["keyword", "例:"], ["important", " foobar"]]], - ["scenario", [["keyword", "例子:"], ["important", " foobar"]]], - ["scenario", [["keyword", "剧本:"], ["important", " foobar"]]], - ["scenario", [["keyword", "剧本大纲:"], ["important", " foobar"]]], - ["scenario", [["keyword", "劇本:"], ["important", " foobar"]]], - ["scenario", [["keyword", "劇本大綱:"], ["important", " foobar"]]], - ["scenario", [["keyword", "场景:"], ["important", " foobar"]]], - ["scenario", [["keyword", "场景大纲:"], ["important", " foobar"]]], - ["scenario", [["keyword", "場景:"], ["important", " foobar"]]], - ["scenario", [["keyword", "場景大綱:"], ["important", " foobar"]]], - ["scenario", [["keyword", "背景:"], ["important", " foobar"]]] -] - ----------------------------------------------------- - -Checks for scenarios in all languages. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/string_feature.test b/docs/_style/prism-master/tests/languages/gherkin/string_feature.test deleted file mode 100644 index bcb152f7..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/string_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -"" -"foobar" -"foobaz" -'' -'foobar' -'foobaz' - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["\"foobar\""]], - ["string", [ - "\"foo", - ["outline", ""], - "baz\"" - ]], - ["string", ["''"]], - ["string", ["'foobar'"]], - ["string", [ - "'foo", - ["outline", ""], - "baz'" - ]] -] - ----------------------------------------------------- - -Checks for double-quoted and single-quoted strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/table_feature.test b/docs/_style/prism-master/tests/languages/gherkin/table_feature.test deleted file mode 100644 index 00eaef85..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/table_feature.test +++ /dev/null @@ -1,40 +0,0 @@ -Examples: foo -| start | eat | left | -| | 5 | 7 | - | 20 | 5 | 15 | - ----------------------------------------------------- - -[ - ["scenario", [["keyword", "Examples:"], ["important", " foo"]]], - ["table-head", [ - ["punctuation", "|"], - ["th", " start "], - ["punctuation", "|"], - ["th", " eat "], - ["punctuation", "|"], - ["th", " left "], - ["punctuation", "|"] - ]], - ["table-body", [ - ["punctuation", "|"], - ["outline", ""], - ["punctuation", "|"], - ["td", " 5 "], - ["punctuation", "|"], - ["td", " 7 "], - ["punctuation", "|"], - - ["punctuation", "|"], - ["td", " 20 "], - ["punctuation", "|"], - ["td", " 5 "], - ["punctuation", "|"], - ["td", " 15 "], - ["punctuation", "|"] - ]] -] - ----------------------------------------------------- - -Checks for table heads and table bodies. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/gherkin/tag_feature.test b/docs/_style/prism-master/tests/languages/gherkin/tag_feature.test deleted file mode 100644 index 51026ea1..00000000 --- a/docs/_style/prism-master/tests/languages/gherkin/tag_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -@important -@billing @bicker @annoy - @foo - ----------------------------------------------------- - -[ - ["tag", "@important"], - ["tag", "@billing"], - ["tag", "@bicker"], - ["tag", "@annoy"], - ["tag", "@foo"] -] - ----------------------------------------------------- - -Checks for tags. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/command_feature.test b/docs/_style/prism-master/tests/languages/git/command_feature.test deleted file mode 100644 index 488f0be3..00000000 --- a/docs/_style/prism-master/tests/languages/git/command_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -$ git add file.txt -foo@foobar ~ $ git diff --cached -$ git log -p -i - ----------------------------------------------------- - -[ - ["command", ["$ git add file.txt"]], - ["command", ["foo@foobar ~ $ git diff", ["parameter", " --cached"]]], - ["command", ["$ git log", ["parameter", " -p"], ["parameter", " -i"]]] -] - ----------------------------------------------------- - -Checks for git commands, with and without parameters. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/comment_feature.test b/docs/_style/prism-master/tests/languages/git/comment_feature.test deleted file mode 100644 index c3d0358e..00000000 --- a/docs/_style/prism-master/tests/languages/git/comment_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -# On branch gh-pages -# Changes to be committed: -# (use "git reset HEAD ..." to unstage) -# - ----------------------------------------------------- - -[ - ["comment", "# On branch gh-pages"], - ["comment", "# Changes to be committed:"], - ["comment", "# (use \"git reset HEAD ...\" to unstage)"], - ["comment", "#"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/commit_sha1_feature.test b/docs/_style/prism-master/tests/languages/git/commit_sha1_feature.test deleted file mode 100644 index fbc58ed2..00000000 --- a/docs/_style/prism-master/tests/languages/git/commit_sha1_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -commit a11a14ef7e26f2ca62d4b35eac455ce636d0dc09 -commit 87edc4ad8c71b95f6e46f736eb98b742859abd95 -commit 3102416a90c431400d2e2a14e707fb7fd6d9e06d - ----------------------------------------------------- - -[ - ["commit_sha1", "commit a11a14ef7e26f2ca62d4b35eac455ce636d0dc09"], - ["commit_sha1", "commit 87edc4ad8c71b95f6e46f736eb98b742859abd95"], - ["commit_sha1", "commit 3102416a90c431400d2e2a14e707fb7fd6d9e06d"] -] - ----------------------------------------------------- - -Checks for commit SHA1. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/coord_feature.test b/docs/_style/prism-master/tests/languages/git/coord_feature.test deleted file mode 100644 index 8d8c5e2b..00000000 --- a/docs/_style/prism-master/tests/languages/git/coord_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -@@ -1 +1,2 @@ -@@@ -98,20 -98,12 +98,20 @@@ - ----------------------------------------------------- - -[ - ["coord", "@@ -1 +1,2 @@"], - ["coord", "@@@ -98,20 -98,12 +98,20 @@@"] -] - ----------------------------------------------------- - -Checks for coords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/diff_feature.test b/docs/_style/prism-master/tests/languages/git/diff_feature.test deleted file mode 100644 index b93fb181..00000000 --- a/docs/_style/prism-master/tests/languages/git/diff_feature.test +++ /dev/null @@ -1,31 +0,0 @@ --Here's my tetx file -+Here's my text file -+And this is the second line - -––– a/web/js/lazy.js -+++ b/web/js/lazy.js - -- if (url !== null && url !== '' && typeof url !== 'undefined') { -+ if (url === null || url === '' || typeof url === 'undefined') { -+ return; -+ } -+ - ----------------------------------------------------- - -[ - ["deleted", "-Here's my tetx file"], - ["inserted", "+Here's my text file"], - ["inserted", "+And this is the second line"], - ["deleted", "––– a/web/js/lazy.js"], - ["inserted", "+++ b/web/js/lazy.js"], - ["deleted", "- if (url !== null && url !== '' && typeof url !== 'undefined') {"], - ["inserted", "+ if (url === null || url === '' || typeof url === 'undefined') {"], - ["inserted", "+ return;"], - ["inserted", "+ }"], - ["inserted", "+"] -] - ----------------------------------------------------- - -Checks for inserted and deleted lines in git diff output. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/git/string_feature.test b/docs/_style/prism-master/tests/languages/git/string_feature.test deleted file mode 100644 index ebff3bf5..00000000 --- a/docs/_style/prism-master/tests/languages/git/string_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -"" -"foo" -'' -'bar' - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo\""], - ["string", "''"], - ["string", "'bar'"] -] - ----------------------------------------------------- - -Checks for double-quoted and single-quoted strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/glsl/comment_feature.test b/docs/_style/prism-master/tests/languages/glsl/comment_feature.test deleted file mode 100644 index b45a6bd5..00000000 --- a/docs/_style/prism-master/tests/languages/glsl/comment_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -/**/ -/* foo -bar */ -// -// foo -// foo\ -bar - ----------------------------------------------------- - -[ - ["comment", "/**/"], - ["comment", "/* foo\r\nbar */"], - ["comment", "//"], - ["comment", "// foo"], - ["comment", "// foo\\\r\nbar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/glsl/keyword_feature.test b/docs/_style/prism-master/tests/languages/glsl/keyword_feature.test deleted file mode 100644 index e07768ca..00000000 --- a/docs/_style/prism-master/tests/languages/glsl/keyword_feature.test +++ /dev/null @@ -1,263 +0,0 @@ -attribute -const -uniform -varying -buffer -shared -coherent -volatile -restrict -readonly -writeonly -atomic_uint -layout -centroid -flat -smooth -noperspective -patch -sample -break -continue -do -for -while -switch -case -default -if -else -subroutine -in -out -inout -float -double -int -void -bool -true -false -invariant -precise -discard -return -mat2 mat3 mat4 -mat2x2 mat2x3 mat2x4 -mat3x2 mat3x3 mat3x4 -mat4x2 mat4x3 mat4x4 -dmat2 dmat3 dmat4 -dmat2x2 dmat2x3 dmat2x4 -dmat3x2 dmat3x3 dmat3x4 -dmat4x2 dmat4x3 dmat4x4 -vec2 vec3 vec4 -ivec2 ivec3 ivec4 -bvec2 bvec3 bvec4 -dvec2 dvec3 dvec4 -uvec2 uvec3 uvec4 -uint -lowp -mediump -highp -precision -sampler1D sampler2D sampler3D -isampler1D isampler2D isampler3D -usampler1D usampler2D usampler3D -samplerCube isamplerCube usamplerCube -sampler1DShadow sampler2DShadow -samplerCubeShadow -sampler1DArray sampler2DArray -isampler1DArray isampler2DArray -usampler1DArray usampler2DArray -sampler1DArrayShadow sampler2DArrayShadow -sampler2DRect isampler2DRect usampler2DRect -sampler2DRectShadow -samplerBuffer isamplerBuffer usamplerBuffer -sampler2DMS isampler2DMS usampler2DMS -sampler2DMSArray isampler2DMSArray usampler2DMSArray -samplerCubeArray isamplerCubeArray usamplerCubeArray -samplerCubeArrayShadow -image1D image2D image3D -iimage1D iimage2D iimage3D -uimage1D uimage2D uimage3D -image2DRect iimage2DRect uimage2DRect -imageCube iimageCube uimageCube -imageBuffer iimageBuffer uimageBuffer -image1DArray image2DArray -iimage1DArray iimage2DArray -uimage1DArray uimage2DArray -imageCubeArray iimageCubeArray uimageCubeArray -image2DMS iimage2DMS uimage2DMS -image2DMSArray iimage2DMSArray uimage2DMSArray -struct -common -partition -active -asm -class; -union -enum -typedef -template -this -resource -goto -inline -noinline -public -static -extern -external -interface; -long -short -half -fixed -unsigned -superp -input -output -hvec2 hvec3 hvec4 -fvec2 fvec3 fvec4 -sampler3DRect -filter -sizeof -cast -namespace -using - ----------------------------------------------------- - -[ - ["keyword", "attribute"], - ["keyword", "const"], - ["keyword", "uniform"], - ["keyword", "varying"], - ["keyword", "buffer"], - ["keyword", "shared"], - ["keyword", "coherent"], - ["keyword", "volatile"], - ["keyword", "restrict"], - ["keyword", "readonly"], - ["keyword", "writeonly"], - ["keyword", "atomic_uint"], - ["keyword", "layout"], - ["keyword", "centroid"], - ["keyword", "flat"], - ["keyword", "smooth"], - ["keyword", "noperspective"], - ["keyword", "patch"], - ["keyword", "sample"], - ["keyword", "break"], - ["keyword", "continue"], - ["keyword", "do"], - ["keyword", "for"], - ["keyword", "while"], - ["keyword", "switch"], - ["keyword", "case"], - ["keyword", "default"], - ["keyword", "if"], - ["keyword", "else"], - ["keyword", "subroutine"], - ["keyword", "in"], - ["keyword", "out"], - ["keyword", "inout"], - ["keyword", "float"], - ["keyword", "double"], - ["keyword", "int"], - ["keyword", "void"], - ["keyword", "bool"], - ["keyword", "true"], - ["keyword", "false"], - ["keyword", "invariant"], - ["keyword", "precise"], - ["keyword", "discard"], - ["keyword", "return"], - ["keyword", "mat2"], ["keyword", "mat3"], ["keyword", "mat4"], - ["keyword", "mat2x2"], ["keyword", "mat2x3"], ["keyword", "mat2x4"], - ["keyword", "mat3x2"], ["keyword", "mat3x3"], ["keyword", "mat3x4"], - ["keyword", "mat4x2"], ["keyword", "mat4x3"], ["keyword", "mat4x4"], - ["keyword", "dmat2"], ["keyword", "dmat3"], ["keyword", "dmat4"], - ["keyword", "dmat2x2"], ["keyword", "dmat2x3"], ["keyword", "dmat2x4"], - ["keyword", "dmat3x2"], ["keyword", "dmat3x3"], ["keyword", "dmat3x4"], - ["keyword", "dmat4x2"], ["keyword", "dmat4x3"], ["keyword", "dmat4x4"], - ["keyword", "vec2"], ["keyword", "vec3"], ["keyword", "vec4"], - ["keyword", "ivec2"], ["keyword", "ivec3"], ["keyword", "ivec4"], - ["keyword", "bvec2"], ["keyword", "bvec3"], ["keyword", "bvec4"], - ["keyword", "dvec2"], ["keyword", "dvec3"], ["keyword", "dvec4"], - ["keyword", "uvec2"], ["keyword", "uvec3"], ["keyword", "uvec4"], - ["keyword", "uint"], - ["keyword", "lowp"], - ["keyword", "mediump"], - ["keyword", "highp"], - ["keyword", "precision"], - ["keyword", "sampler1D"], ["keyword", "sampler2D"], ["keyword", "sampler3D"], - ["keyword", "isampler1D"], ["keyword", "isampler2D"], ["keyword", "isampler3D"], - ["keyword", "usampler1D"], ["keyword", "usampler2D"], ["keyword", "usampler3D"], - ["keyword", "samplerCube"], ["keyword", "isamplerCube"], ["keyword", "usamplerCube"], - ["keyword", "sampler1DShadow"], ["keyword", "sampler2DShadow"], - ["keyword", "samplerCubeShadow"], - ["keyword", "sampler1DArray"], ["keyword", "sampler2DArray"], - ["keyword", "isampler1DArray"], ["keyword", "isampler2DArray"], - ["keyword", "usampler1DArray"], ["keyword", "usampler2DArray"], - ["keyword", "sampler1DArrayShadow"], ["keyword", "sampler2DArrayShadow"], - ["keyword", "sampler2DRect"], ["keyword", "isampler2DRect"], ["keyword", "usampler2DRect"], - ["keyword", "sampler2DRectShadow"], - ["keyword", "samplerBuffer"], ["keyword", "isamplerBuffer"], ["keyword", "usamplerBuffer"], - ["keyword", "sampler2DMS"], ["keyword", "isampler2DMS"], ["keyword", "usampler2DMS"], - ["keyword", "sampler2DMSArray"], ["keyword", "isampler2DMSArray"], ["keyword", "usampler2DMSArray"], - ["keyword", "samplerCubeArray"], ["keyword", "isamplerCubeArray"], ["keyword", "usamplerCubeArray"], - ["keyword", "samplerCubeArrayShadow"], - ["keyword", "image1D"], ["keyword", "image2D"], ["keyword", "image3D"], - ["keyword", "iimage1D"], ["keyword", "iimage2D"], ["keyword", "iimage3D"], - ["keyword", "uimage1D"], ["keyword", "uimage2D"], ["keyword", "uimage3D"], - ["keyword", "image2DRect"], ["keyword", "iimage2DRect"], ["keyword", "uimage2DRect"], - ["keyword", "imageCube"], ["keyword", "iimageCube"], ["keyword", "uimageCube"], - ["keyword", "imageBuffer"], ["keyword", "iimageBuffer"], ["keyword", "uimageBuffer"], - ["keyword", "image1DArray"], ["keyword", "image2DArray"], - ["keyword", "iimage1DArray"], ["keyword", "iimage2DArray"], - ["keyword", "uimage1DArray"], ["keyword", "uimage2DArray"], - ["keyword", "imageCubeArray"], ["keyword", "iimageCubeArray"], ["keyword", "uimageCubeArray"], - ["keyword", "image2DMS"], ["keyword", "iimage2DMS"], ["keyword", "uimage2DMS"], - ["keyword", "image2DMSArray"], ["keyword", "iimage2DMSArray"], ["keyword", "uimage2DMSArray"], - ["keyword", "struct"], - ["keyword", "common"], - ["keyword", "partition"], - ["keyword", "active"], - ["keyword", "asm"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "union"], - ["keyword", "enum"], - ["keyword", "typedef"], - ["keyword", "template"], - ["keyword", "this"], - ["keyword", "resource"], - ["keyword", "goto"], - ["keyword", "inline"], - ["keyword", "noinline"], - ["keyword", "public"], - ["keyword", "static"], - ["keyword", "extern"], - ["keyword", "external"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "long"], - ["keyword", "short"], - ["keyword", "half"], - ["keyword", "fixed"], - ["keyword", "unsigned"], - ["keyword", "superp"], - ["keyword", "input"], - ["keyword", "output"], - ["keyword", "hvec2"], ["keyword", "hvec3"], ["keyword", "hvec4"], - ["keyword", "fvec2"], ["keyword", "fvec3"], ["keyword", "fvec4"], - ["keyword", "sampler3DRect"], - ["keyword", "filter"], - ["keyword", "sizeof"], - ["keyword", "cast"], - ["keyword", "namespace"], - ["keyword", "using"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/glsl/number_feature.test b/docs/_style/prism-master/tests/languages/glsl/number_feature.test deleted file mode 100644 index 20ac4d11..00000000 --- a/docs/_style/prism-master/tests/languages/glsl/number_feature.test +++ /dev/null @@ -1,31 +0,0 @@ -0xBadFace -42 -3.14159 -3e8 -3.6e-7 -4.7E+12 -4u -42U -3.1l -42f -2.0LF - ----------------------------------------------------- - -[ - ["number", "0xBadFace"], - ["number", "42"], - ["number", "3.14159"], - ["number", "3e8"], - ["number", "3.6e-7"], - ["number", "4.7E+12"], - ["number", "4u"], - ["number", "42U"], - ["number", "3.1l"], - ["number", "42f"], - ["number", "2.0LF"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/glsl/preprocessor_feature.test b/docs/_style/prism-master/tests/languages/glsl/preprocessor_feature.test deleted file mode 100644 index a720d350..00000000 --- a/docs/_style/prism-master/tests/languages/glsl/preprocessor_feature.test +++ /dev/null @@ -1,35 +0,0 @@ -#define -#undef -#if -#ifdef -#ifndef -#else -#elif -#endif -#error -#pragma -#extension -#version -#line - ----------------------------------------------------- - -[ - ["preprocessor", "#define"], - ["preprocessor", "#undef"], - ["preprocessor", "#if"], - ["preprocessor", "#ifdef"], - ["preprocessor", "#ifndef"], - ["preprocessor", "#else"], - ["preprocessor", "#elif"], - ["preprocessor", "#endif"], - ["preprocessor", "#error"], - ["preprocessor", "#pragma"], - ["preprocessor", "#extension"], - ["preprocessor", "#version"], - ["preprocessor", "#line"] -] - ----------------------------------------------------- - -Checks for preprocessor instructions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/boolean_feature.test b/docs/_style/prism-master/tests/languages/go/boolean_feature.test deleted file mode 100644 index 1ee11926..00000000 --- a/docs/_style/prism-master/tests/languages/go/boolean_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -_ -iota -nil -true -false - ----------------------------------------------------- - -[ - ["boolean", "_"], - ["boolean", "iota"], - ["boolean", "nil"], - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for all boolean values. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/builtin_feature.test b/docs/_style/prism-master/tests/languages/go/builtin_feature.test deleted file mode 100644 index 414018fe..00000000 --- a/docs/_style/prism-master/tests/languages/go/builtin_feature.test +++ /dev/null @@ -1,79 +0,0 @@ -bool -byte -complex64 -complex128 -error -float32 -float64 -rune -string -int -int8 -int16 -int32 -int64 -uint -uint8 -uint16 -uint32 -uint64 -uintptr -append -cap -close -complex -copy -delete -imag -len -make -new -panic -print -println -real -recover - ----------------------------------------------------- - -[ - ["builtin", "bool"], - ["builtin", "byte"], - ["builtin", "complex64"], - ["builtin", "complex128"], - ["builtin", "error"], - ["builtin", "float32"], - ["builtin", "float64"], - ["builtin", "rune"], - ["builtin", "string"], - ["builtin", "int"], - ["builtin", "int8"], - ["builtin", "int16"], - ["builtin", "int32"], - ["builtin", "int64"], - ["builtin", "uint"], - ["builtin", "uint8"], - ["builtin", "uint16"], - ["builtin", "uint32"], - ["builtin", "uint64"], - ["builtin", "uintptr"], - ["builtin", "append"], - ["builtin", "cap"], - ["builtin", "close"], - ["builtin", "complex"], - ["builtin", "copy"], - ["builtin", "delete"], - ["builtin", "imag"], - ["builtin", "len"], - ["builtin", "make"], - ["builtin", "new"], - ["builtin", "panic"], - ["builtin", "print"], - ["builtin", "println"], - ["builtin", "real"], - ["builtin", "recover"] -] - ----------------------------------------------------- - -Checks for all builtins. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/keyword_feature.test b/docs/_style/prism-master/tests/languages/go/keyword_feature.test deleted file mode 100644 index a2799f73..00000000 --- a/docs/_style/prism-master/tests/languages/go/keyword_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -break -case -chan -const -continue -default -defer -else -fallthrough -for -func -go -goto -if -import -interface -map -package -range -return -select -struct -switch -type -var - ----------------------------------------------------- - -[ - ["keyword", "break"], - ["keyword", "case"], - ["keyword", "chan"], - ["keyword", "const"], - ["keyword", "continue"], - ["keyword", "default"], - ["keyword", "defer"], - ["keyword", "else"], - ["keyword", "fallthrough"], - ["keyword", "for"], - ["keyword", "func"], - ["keyword", "go"], - ["keyword", "goto"], - ["keyword", "if"], - ["keyword", "import"], - ["keyword", "interface"], - ["keyword", "map"], - ["keyword", "package"], - ["keyword", "range"], - ["keyword", "return"], - ["keyword", "select"], - ["keyword", "struct"], - ["keyword", "switch"], - ["keyword", "type"], - ["keyword", "var"] -] - ----------------------------------------------------- - -Checks for all keywords \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/number_feature.test b/docs/_style/prism-master/tests/languages/go/number_feature.test deleted file mode 100644 index f21b3e87..00000000 --- a/docs/_style/prism-master/tests/languages/go/number_feature.test +++ /dev/null @@ -1,43 +0,0 @@ -42 -0600 -0xBadFace -170141183460469231731687303715884105727 -72.40 -072.40 -2.71828 -1.e+0 -6.67428e-11 -1E6 -0i -011i -0.i -2.71828i -1.e+0i -6.67428e-11i -1E6i - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "0600"], - ["number", "0xBadFace"], - ["number", "170141183460469231731687303715884105727"], - ["number", "72.40"], - ["number", "072.40"], - ["number", "2.71828"], - ["number", "1.e+0"], - ["number", "6.67428e-11"], - ["number", "1E6"], - ["number", "0i"], - ["number", "011i"], - ["number", "0.i"], - ["number", "2.71828i"], - ["number", "1.e+0i"], - ["number", "6.67428e-11i"], - ["number", "1E6i"] -] - ----------------------------------------------------- - -Checks for integers, floats and imaginary numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/operator_feature.test b/docs/_style/prism-master/tests/languages/go/operator_feature.test deleted file mode 100644 index cda53861..00000000 --- a/docs/_style/prism-master/tests/languages/go/operator_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -* / % ^ ! = -*= /= %= ^= != == -+ += ++ -- -= -- -| |= || -& &= && &^ &^= -> >> >>= >= -< << <<= <= <- -:= ... - ----------------------------------------------------- - -[ - ["operator", "*"], ["operator", "/"], ["operator", "%"], ["operator", "^"], ["operator", "!"], ["operator", "="], - ["operator", "*="], ["operator", "/="], ["operator", "%="], ["operator", "^="], ["operator", "!="], ["operator", "=="], - ["operator", "+"], ["operator", "+="], ["operator", "++"], - ["operator", "-"], ["operator", "-="], ["operator", "--"], - ["operator", "|"], ["operator", "|="], ["operator", "||"], - ["operator", "&"], ["operator", "&="], ["operator", "&&"], ["operator", "&^"], ["operator", "&^="], - ["operator", ">"], ["operator", ">>"], ["operator", ">>="], ["operator", ">="], - ["operator", "<"], ["operator", "<<"], ["operator", "<<="], ["operator", "<="], ["operator", "<-"], - ["operator", ":="], ["operator", "..."] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/go/string_feature.test b/docs/_style/prism-master/tests/languages/go/string_feature.test deleted file mode 100644 index 97e3960b..00000000 --- a/docs/_style/prism-master/tests/languages/go/string_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -'a' -'ä' -'本' -'\t' -'\xff' -'\u12e4' - -`abc` -`\n -\n` -"\n" -"\"" -"Hello, world!\n" -"日本語" -"\xff\u00FF" - ----------------------------------------------------- - -[ - ["string", "'a'"], - ["string", "'ä'"], - ["string", "'本'"], - ["string", "'\\t'"], - ["string", "'\\xff'"], - ["string", "'\\u12e4'"], - ["string", "`abc`"], - ["string", "`\\n\r\n\\n`"], - ["string", "\"\\n\""], - ["string", "\"\\\"\""], - ["string", "\"Hello, world!\\n\""], - ["string", "\"日本語\""], - ["string", "\"\\xff\\u00FF\""] -] - ----------------------------------------------------- - -Checks for runes and strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/attr-name_feature.test b/docs/_style/prism-master/tests/languages/graphql/attr-name_feature.test deleted file mode 100644 index 7f976e6d..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/attr-name_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -{ - zuck: user(id: 4) { - name - } -} - ----------------------------------------------------- - -[ - ["punctuation", "{"], - ["attr-name", "zuck"], - ["punctuation", ":"], - " user", - ["punctuation", "("], - ["attr-name", "id"], - ["punctuation", ":"], - ["number", "4"], - ["punctuation", ")"], - ["punctuation", "{"], - "\r\n\t\tname\r\n\t", - ["punctuation", "}"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for aliases, parameter names, etc. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/boolean_feature.test b/docs/_style/prism-master/tests/languages/graphql/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/comment_feature.test b/docs/_style/prism-master/tests/languages/graphql/comment_feature.test deleted file mode 100644 index 054d6023..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -# -# foobar - ----------------------------------------------------- - -[ - ["comment", "#"], - ["comment", "# foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/directive_feature.test b/docs/_style/prism-master/tests/languages/graphql/directive_feature.test deleted file mode 100644 index ca9b7004..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/directive_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -@skip -@include - ----------------------------------------------------- - -[ - ["directive", "@skip"], - ["directive", "@include"] -] - ----------------------------------------------------- - -Checks for directives \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/keyword_feature.test b/docs/_style/prism-master/tests/languages/graphql/keyword_feature.test deleted file mode 100644 index 06d16f2a..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/keyword_feature.test +++ /dev/null @@ -1,24 +0,0 @@ -query -fragment -mutation -fragment foo on Bar -... on Foo - ----------------------------------------------------- - -[ - ["keyword", "query"], - ["keyword", "fragment"], - ["keyword", "mutation"], - ["keyword", "fragment"], - " foo ", - ["keyword", "on"], - " Bar\r\n", - ["operator", "..."], - ["keyword", "on"], - " Foo" -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/number_feature.test b/docs/_style/prism-master/tests/languages/graphql/number_feature.test deleted file mode 100644 index c58cc24d..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -0 -42 --5 -3.14159 -5e4 -6E-78 -0.3e+1 - ----------------------------------------------------- - -[ - ["number", "0"], - ["number", "42"], - ["number", "-5"], - ["number", "3.14159"], - ["number", "5e4"], - ["number", "6E-78"], - ["number", "0.3e+1"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/string_feature.test b/docs/_style/prism-master/tests/languages/graphql/string_feature.test deleted file mode 100644 index a3d26be3..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/string_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -"" -"foo bar" -"foo\"bar\\baz" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo bar\""], - ["string", "\"foo\\\"bar\\\\baz\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/graphql/variable_feature.test b/docs/_style/prism-master/tests/languages/graphql/variable_feature.test deleted file mode 100644 index 642d173e..00000000 --- a/docs/_style/prism-master/tests/languages/graphql/variable_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -$foo -$Foo_bar42 - ----------------------------------------------------- - -[ - ["variable", "$foo"], - ["variable", "$Foo_bar42"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/annotation_feature.test b/docs/_style/prism-master/tests/languages/groovy/annotation_feature.test deleted file mode 100644 index f956c527..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/annotation_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -@BaseScript MyBaseClass baseScript -@DelegatesTo(EmailSpec) - ----------------------------------------------------- - -[ - ["annotation", "@BaseScript"], - " MyBaseClass baseScript\r\n", - ["annotation", "@DelegatesTo"], - ["punctuation", "("], - "EmailSpec", - ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for annotations. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/issue1049.js b/docs/_style/prism-master/tests/languages/groovy/issue1049.js deleted file mode 100644 index 2ce81537..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/issue1049.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - '"&"': '"&amp;"', - '"&&"': '"&amp;&amp;"', - '"<"': '"&lt;"', - '"<<"': '"&lt;&lt;"', - '"&lt;"': '"&amp;lt;"', - '">"': '"&gt;"', -}; diff --git a/docs/_style/prism-master/tests/languages/groovy/keyword_feature.test b/docs/_style/prism-master/tests/languages/groovy/keyword_feature.test deleted file mode 100644 index 57b7e164..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/keyword_feature.test +++ /dev/null @@ -1,61 +0,0 @@ -as def in abstract -assert boolean break -byte case catch char -class; -const continue -default do double else -enum -extends; -final -finally float for goto -if -implements; -import -instanceof; -int -interface; -long native -new; -package private protected -public return short -static strictfp super -switch synchronized this -throw throws -trait; -transient -try void volatile while - ----------------------------------------------------- - -[ - ["keyword", "as"], ["keyword", "def"], ["keyword", "in"], ["keyword", "abstract"], - ["keyword", "assert"], ["keyword", "boolean"], ["keyword", "break"], - ["keyword", "byte"], ["keyword", "case"], ["keyword", "catch"], ["keyword", "char"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], ["keyword", "continue"], - ["keyword", "default"], ["keyword", "do"], ["keyword", "double"], ["keyword", "else"], - ["keyword", "enum"], - ["keyword", "extends"], ["punctuation", ";"], - ["keyword", "final"], - ["keyword", "finally"], ["keyword", "float"], ["keyword", "for"], ["keyword", "goto"], - ["keyword", "if"], - ["keyword", "implements"], ["punctuation", ";"], - ["keyword", "import"], - ["keyword", "instanceof"], ["punctuation", ";"], - ["keyword", "int"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "long"], ["keyword", "native"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "package"], ["keyword", "private"], ["keyword", "protected"], - ["keyword", "public"], ["keyword", "return"], ["keyword", "short"], - ["keyword", "static"], ["keyword", "strictfp"], ["keyword", "super"], - ["keyword", "switch"], ["keyword", "synchronized"], ["keyword", "this"], - ["keyword", "throw"], ["keyword", "throws"], - ["keyword", "trait"], ["punctuation", ";"], - ["keyword", "transient"], - ["keyword", "try"], ["keyword", "void"], ["keyword", "volatile"], ["keyword", "while"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/number_feature.test b/docs/_style/prism-master/tests/languages/groovy/number_feature.test deleted file mode 100644 index bd12ff9d..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/number_feature.test +++ /dev/null @@ -1,43 +0,0 @@ -0b0110 -0b0110_1111_0000 -0b01G 0b01L 0b01I -0b01D 0b01F - -0xBABE -0xBad_Face -0x1.8p1 -0xa.fp-2 - -42_000 -3.14_15_9 -1.2e3 -3E+1 -4E-2 -42g 42l 42i -42d 42f - ----------------------------------------------------- - -[ - ["number", "0b0110"], - ["number", "0b0110_1111_0000"], - ["number", "0b01G"], ["number", "0b01L"], ["number", "0b01I"], - ["number", "0b01D"], ["number", "0b01F"], - - ["number", "0xBABE"], - ["number", "0xBad_Face"], - ["number", "0x1.8p1"], - ["number", "0xa.fp-2"], - - ["number", "42_000"], - ["number", "3.14_15_9"], - ["number", "1.2e3"], - ["number", "3E+1"], - ["number", "4E-2"], - ["number", "42g"], ["number", "42l"], ["number", "42i"], - ["number", "42d"], ["number", "42f"] -] - ----------------------------------------------------- - -Checks for binary, hexadecimal and decimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/operator_feature.test b/docs/_style/prism-master/tests/languages/groovy/operator_feature.test deleted file mode 100644 index cce07a99..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/operator_feature.test +++ /dev/null @@ -1,45 +0,0 @@ -~ -= == =~ ==~ -? ?. ?: -* ** *. *= **= -. .@ .& -5..8 -5..<8 -- -- -= -> -+ ++ += -! != -< << <<= <= <=> -> >> >>> >>= >>>= >= -& && &= -| || |= -/ -/= -^ ^= -% %= - ----------------------------------------------------- - -[ - ["operator", "~"], - ["operator", "="], ["operator", "=="], ["operator", "=~"], ["operator", "==~"], - ["operator", "?"], ["operator", "?."], ["operator", "?:"], - ["operator", "*"], ["operator", "**"], ["operator", "*."], ["operator", "*="], ["operator", "**="], - ["operator", "."], ["operator", ".@"], ["operator", ".&"], - ["number", "5"], ["operator", ".."], ["number", "8"], - ["number", "5"], ["operator", "..<"], ["number", "8"], - ["operator", "-"], ["operator", "--"], ["operator", "-="], ["operator", "->"], - ["operator", "+"], ["operator", "++"], ["operator", "+="], - ["operator", "!"], ["operator", "!="], - ["operator", "<"], ["operator", "<<"], ["operator", "<<="], ["operator", "<="], ["operator", "<=>"], - ["operator", ">"], ["operator", ">>"], ["operator", ">>>"], ["operator", ">>="], ["operator", ">>>="], ["operator", ">="], - ["operator", "&"], ["operator", "&&"], ["operator", "&="], - ["operator", "|"], ["operator", "||"], ["operator", "|="], - ["operator", "/"], - ["operator", "/="], - ["operator", "^"], ["operator", "^="], - ["operator", "%"], ["operator", "%="] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/shebang_feature.test b/docs/_style/prism-master/tests/languages/groovy/shebang_feature.test deleted file mode 100644 index c5806ebb..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/shebang_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -#!foobar -#!/usr/bin/env groovy - ----------------------------------------------------- - -[ - ["shebang", "#!foobar"], - ["shebang", "#!/usr/bin/env groovy"] -] - ----------------------------------------------------- - -Check for shebang comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/spock-block_feature.test b/docs/_style/prism-master/tests/languages/groovy/spock-block_feature.test deleted file mode 100644 index 5cc72fe8..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/spock-block_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -setup: -given: -when: -then: -and: -cleanup: -expect: -where: - ----------------------------------------------------- - -[ - ["spock-block", "setup:"], - ["spock-block", "given:"], - ["spock-block", "when:"], - ["spock-block", "then:"], - ["spock-block", "and:"], - ["spock-block", "cleanup:"], - ["spock-block", "expect:"], - ["spock-block", "where:"] -] - ----------------------------------------------------- - -Checks for all spock blocks. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/string-interpolation_feature.js b/docs/_style/prism-master/tests/languages/groovy/string-interpolation_feature.js deleted file mode 100644 index ddecdfd7..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/string-interpolation_feature.js +++ /dev/null @@ -1,28 +0,0 @@ -module.exports = { - // Double quoted: interpolation - '"$foo"': '"$foo"', - '"${42}"': '"${42}"', - // Triple double quoted: interpolation - '"""$foo"""': '"""$foo"""', - '"""${42}"""': '"""${42}"""', - // Slashy string: interpolation - '/$foo/': '/$foo/', - '/${42}/': '/${42}/', - // Dollar slashy string: interpolation - '$/$foo/$': '$/$foo/$', - '$/${42}/$': '$/${42}/$', - - // Double quoted: no interpolation (escaped) - '"\\$foo \\${42}"': '"\\$foo \\${42}"', - // Triple double quoted: no interpolation (escaped) - '"""\\$foo \\${42}"""': '"""\\$foo \\${42}"""', - // Slashy string: no interpolation (escaped) - '/\\$foo \\${42}/': '/\\$foo \\${42}/', - // Dollar slashy string: no interpolation (escaped) - '$/$$foo $${42}/$': '$/$$foo $${42}/$', - - // Single quoted string: no interpolation - '\'$foo ${42}\'': '\'$foo ${42}\'', - // Triple single quoted string: no interpolation - '\'\'\'$foo ${42}\'\'\'': '\'\'\'$foo ${42}\'\'\'' -}; \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/groovy/string_feature.test b/docs/_style/prism-master/tests/languages/groovy/string_feature.test deleted file mode 100644 index 639db825..00000000 --- a/docs/_style/prism-master/tests/languages/groovy/string_feature.test +++ /dev/null @@ -1,60 +0,0 @@ -"""""" -"""foo""" -"""foo -bar""" - -'''''' -'''foo''' -'''foo -bar''' - -"" -"fo\"o" -'' -'fo\'o' - -/foo/ -/fo\/o/ - -$/fo$/$o/$ -$/foo -bar/$ -"foo /* comment */ bar" -'foo // bar' -'''foo -/* comment */ -bar''' -"""foo -// comment -bar""" - ----------------------------------------------------- - -[ - ["string", "\"\"\"\"\"\""], - ["string", "\"\"\"foo\"\"\""], - ["string", "\"\"\"foo\r\nbar\"\"\""], - ["string", "''''''"], - ["string", "'''foo'''"], - ["string", "'''foo\r\nbar'''"], - - ["string", "\"\""], - ["string", "\"fo\\\"o\""], - ["string", "''"], - ["string", "'fo\\'o'"], - - ["string", "/foo/"], - ["string", "/fo\\/o/"], - - ["string", "$/fo$/$o/$"], - ["string", "$/foo\r\nbar/$"], - ["string", "\"foo /* comment */ bar\""], - ["string", "'foo // bar'"], - ["string", "'''foo\r\n/* comment */\r\nbar'''"], - ["string", "\"\"\"foo\r\n// comment\r\nbar\"\"\""] -] - ----------------------------------------------------- - -Checks for single quoted, triple single quoted, double quoted, -triple double quoted, slashy and dollar slashy strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haml/code_feature.test b/docs/_style/prism-master/tests/languages/haml/code_feature.test deleted file mode 100644 index 51315e62..00000000 --- a/docs/_style/prism-master/tests/languages/haml/code_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -~ 42 -- 42 -= 42 -&= 42 -!= 42 - ----------------------------------------------------- - -[ - ["punctuation", "~"], ["code", [["number", "42"]]], - ["punctuation", "-"], ["code", [["number", "42"]]], - ["punctuation", "="], ["code", [["number", "42"]]], - ["punctuation", "&="], ["code", [["number", "42"]]], - ["punctuation", "!="], ["code", [["number", "42"]]] -] - ----------------------------------------------------- - -Checks for single-line code. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haml/doctype_feature.test b/docs/_style/prism-master/tests/languages/haml/doctype_feature.test deleted file mode 100644 index 5f5a731b..00000000 --- a/docs/_style/prism-master/tests/languages/haml/doctype_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -!!! -!!! 5 -!!! Strict - ----------------------------------------------------- - -[ - ["doctype", "!!!"], - ["doctype", "!!! 5"], - ["doctype", "!!! Strict"] -] - ----------------------------------------------------- - -Checks for doctypes. diff --git a/docs/_style/prism-master/tests/languages/haml/interpolation_feature.test b/docs/_style/prism-master/tests/languages/haml/interpolation_feature.test deleted file mode 100644 index ed13c87a..00000000 --- a/docs/_style/prism-master/tests/languages/haml/interpolation_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -#{ 42 } -#{ "foobar" } - ----------------------------------------------------- - -[ - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - ["interpolation", [ - ["delimiter", "#{"], - ["string", ["\"foobar\""]], - ["delimiter", "}"] - ]] -] - ----------------------------------------------------- - -Checks for interpolation in plain text. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haml/multiline-code_feature.test b/docs/_style/prism-master/tests/languages/haml/multiline-code_feature.test deleted file mode 100644 index 06cb2b43..00000000 --- a/docs/_style/prism-master/tests/languages/haml/multiline-code_feature.test +++ /dev/null @@ -1,58 +0,0 @@ - ~ 1, - 1, - 1 -~ 2 | - 2 | - 2 | - -- 3, - 3, - 3 - - 4 | - 4 | - 4 | - - = 5, - 5, - 5 -= 6 | - 6 | - 6 | - -&= 7, - 7, - 7 - &= 8 | - 8 | - 8 | - -!= 9, - 9, - 9 -!= 10 | - 10 | - 10 | - ----------------------------------------------------- - -[ - ["punctuation", "~"], ["multiline-code", [["number", "1"], ["punctuation", ","], ["number", "1"], ["punctuation", ","], ["number", "1"]]], - ["punctuation", "~"], ["multiline-code", [["number", "2"], ["operator", "|"], ["number", "2"], ["operator", "|"], ["number", "2"], ["operator", "|"]]], - - ["punctuation", "-"], ["multiline-code", [["number", "3"], ["punctuation", ","], ["number", "3"], ["punctuation", ","], ["number", "3"]]], - ["punctuation", "-"], ["multiline-code", [["number", "4"], ["operator", "|"], ["number", "4"], ["operator", "|"], ["number", "4"], ["operator", "|"]]], - - ["punctuation", "="], ["multiline-code", [["number", "5"], ["punctuation", ","], ["number", "5"], ["punctuation", ","], ["number", "5"]]], - ["punctuation", "="], ["multiline-code", [["number", "6"], ["operator", "|"], ["number", "6"], ["operator", "|"], ["number", "6"], ["operator", "|"]]], - - ["punctuation", "&="], ["multiline-code", [["number", "7"], ["punctuation", ","], ["number", "7"], ["punctuation", ","], ["number", "7"]]], - ["punctuation", "&="], ["multiline-code", [["number", "8"], ["operator", "|"], ["number", "8"], ["operator", "|"], ["number", "8"], ["operator", "|"]]], - - ["punctuation", "!="], ["multiline-code", [["number", "9"], ["punctuation", ","], ["number", "9"], ["punctuation", ","], ["number", "9"]]], - ["punctuation", "!="], ["multiline-code", [["number", "10"], ["operator", "|"], ["number", "10"], ["operator", "|"], ["number", "10"], ["operator", "|"]]] -] - ----------------------------------------------------- - -Checks for inline code, with all possible prefixes, some not indented, some -indented with two spaces, some indented with one tab. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haml/multiline-comment_feature.test b/docs/_style/prism-master/tests/languages/haml/multiline-comment_feature.test deleted file mode 100644 index 809aaffa..00000000 --- a/docs/_style/prism-master/tests/languages/haml/multiline-comment_feature.test +++ /dev/null @@ -1,46 +0,0 @@ -/ - -/ foo - -/foo - bar - - /foo - bar - - /foo - bar - --# - --# foo - --#foo - bar - - -#foo - bar - - -#foo - bar - ----------------------------------------------------- - -[ - ["multiline-comment", "/"], - ["multiline-comment", "/ foo"], - ["multiline-comment", "/foo\r\n bar"], - ["multiline-comment", "/foo\r\n bar"], - ["multiline-comment", "/foo\r\n\t bar"], - - ["multiline-comment", "-#"], - ["multiline-comment", "-# foo"], - ["multiline-comment", "-#foo\r\n bar"], - ["multiline-comment", "-#foo\r\n bar"], - ["multiline-comment", "-#foo\r\n\t bar"] -] - ----------------------------------------------------- - -Checks for multi-line comments, not indented, indented with two spaces and -indented with one tab. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haml/tag_feature.test b/docs/_style/prism-master/tests/languages/haml/tag_feature.test deleted file mode 100644 index a1e0c0de..00000000 --- a/docs/_style/prism-master/tests/languages/haml/tag_feature.test +++ /dev/null @@ -1,161 +0,0 @@ -%one - %two - -%div#things -%div.articles -%div.article.title - -%script{:type => "text/javascript", - :src => "javascripts/script_#{42}"} - -%div{:id => [@item.type, @item.number]} - -%a(title=@title href=href) -%input(selected) - -%html{html_attrs('fr-fr')} - -%div[@user, :greeting] - %bar[290]/ - -%div#Article.article.entry{:id => @article.number} - -#collection - .item - -%br/ -%blockquote< -%img> - ----------------------------------------------------- - -[ - ["tag", ["%one"]], - ["tag", ["%two"]], - - ["tag", ["%div#things"]], - ["tag", ["%div.articles"]], - ["tag", ["%div.article.title"]], - - ["tag", [ - "%script", - ["attributes", [ - ["punctuation", "{"], - ["symbol", ":type"], - ["operator", "="], ["operator", ">"], - ["string", ["\"text/javascript\""]], - ["punctuation", ","], - ["symbol", ":src"], - ["operator", "="], ["operator", ">"], - ["string", [ - "\"javascripts/script_", - ["interpolation", [ - ["delimiter", "#{"], - ["number", "42"], - ["delimiter", "}"] - ]], - "\"" - ]], - ["punctuation", "}"] - ]] - ]], - - ["tag", [ - "%div", - ["attributes", [ - ["punctuation", "{"], - ["symbol", ":id"], - ["operator", "="], ["operator", ">"], - ["punctuation", "["], - ["variable", "@item"], - ["punctuation", "."], - "type", - ["punctuation", ","], - ["variable", "@item"], - ["punctuation", "."], - "number", - ["punctuation", "]"], - ["punctuation", "}"] - ]] - ]], - - ["tag", [ - "%a", - ["attributes", [ - ["punctuation", "("], - ["attr-name", "title"], - ["punctuation", "="], - ["attr-value", "@title"], - ["attr-name", "href"], - ["punctuation", "="], - ["attr-value", "href"], - ["punctuation", ")"] - ]] - ]], - ["tag", [ - "%input", - ["attributes", [ - ["punctuation", "("], - ["attr-name", "selected"], - ["punctuation", ")"] - ]] - ]], - - ["tag", [ - "%html", - ["attributes", [ - ["punctuation", "{"], - "html_attrs", - ["punctuation", "("], - ["string", ["'fr-fr'"]], - ["punctuation", ")"], - ["punctuation", "}"] - ]] - ]], - - ["tag", [ - "%div", - ["attributes", [ - ["punctuation", "["], - ["variable", "@user"], - ["punctuation", ","], - ["symbol", ":greeting"], - ["punctuation", "]"] - ]] - ]], - ["tag", [ - "%bar", - ["attributes", [ - ["punctuation", "["], - ["number", "290"], - ["punctuation", "]"] - ]], - "/" - ]], - - ["tag", [ - "%div#Article.article.entry", - ["attributes", [ - ["punctuation", "{"], - ["symbol", ":id"], - ["operator", "="], ["operator", ">"], - ["variable", "@article"], - ["punctuation", "."], - "number", - ["punctuation", "}"] - ]] - ]], - - ["tag", ["#collection"]], - ["tag", [".item"]], - - ["tag", ["%br/"]], - ["tag", ["%blockquote", ["punctuation", "<"]]], - ["tag", ["%img", ["punctuation", ">"]]] -] - ----------------------------------------------------- - -Checks for tags: basic element names, attributes, html-style attributes, -attribute methods, boolean attributes, class and id shortcuts, -implicit div elements, empty tags and whitespace removal. diff --git a/docs/_style/prism-master/tests/languages/handlebars+pug/handlebars_inclusion.test b/docs/_style/prism-master/tests/languages/handlebars+pug/handlebars_inclusion.test deleted file mode 100644 index c69751fd..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars+pug/handlebars_inclusion.test +++ /dev/null @@ -1,15 +0,0 @@ -:handlebars - {{!comment}} - ----------------------------------------------------- - -[ - ["filter-handlebars", [ - ["filter-name", ":handlebars"], - ["comment", "{{!comment}}"] - ]] -] - ----------------------------------------------------- - -Checks for handlebars filter in Jade. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/block_feature.test b/docs/_style/prism-master/tests/languages/handlebars/block_feature.test deleted file mode 100644 index dd2c74e9..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/block_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -{{#each comments}}{{/each}} -{{~#if isActive~}}{{~/if~}} - ----------------------------------------------------- - -[ - ["handlebars", [ - ["delimiter", "{{"], ["block", "#each"], ["variable", "comments"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["block", "/each"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["punctuation", "~"], ["block", "#if"], ["variable", "isActive"], ["punctuation", "~"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["punctuation", "~"], ["block", "/if"], ["punctuation", "~"], ["delimiter", "}}"] - ]] -] - ----------------------------------------------------- - -Checks for block helpers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/boolean_feature.test b/docs/_style/prism-master/tests/languages/handlebars/boolean_feature.test deleted file mode 100644 index 1f252a14..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/boolean_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -{{ true }} -{{{ false }}} - ----------------------------------------------------- - -[ - ["handlebars", [ - ["delimiter", "{{"], ["boolean", "true"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], ["boolean", "false"], ["delimiter", "}}}"] - ]] -] - ----------------------------------------------------- - -Checks for all booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/comment_feature.test b/docs/_style/prism-master/tests/languages/handlebars/comment_feature.test deleted file mode 100644 index c0fffb9f..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/comment_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -{{! foobar}} -{{!-- foo bar baz --}} -{{! foo -bar }} -{{!-- foo -bar --}} - ----------------------------------------------------- - -[ - ["handlebars", [["comment", "{{! foobar}}"]]], - ["handlebars", [["comment", "{{!-- foo bar baz --}}"]]], - ["handlebars", [["comment", "{{! foo\r\nbar }}"]]], - ["handlebars", [["comment", "{{!-- foo\r\nbar --}}"]]] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/handlebars_in_markup_feature.test b/docs/_style/prism-master/tests/languages/handlebars/handlebars_in_markup_feature.test deleted file mode 100644 index d9591ba4..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/handlebars_in_markup_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -
        {{{intro}}}
        -
        -___HANDLEBARS1___{{{intro}}} -{{!
        }} - ----------------------------------------------------- - -[ - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "div" - ]], - ["punctuation", ">"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], - ["variable", "intro"], - ["delimiter", "}}}"] - ]], - ["tag", [ - ["tag", [ - ["punctuation", ""] - ]], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "div" - ]], - ["attr-name", ["class"]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - ["handlebars", [ - ["delimiter", "{{"], - ["variable", "foo"], - ["delimiter", "}}"] - ]], - ["punctuation", "\""] - ]], - ["punctuation", ">"] - ]], - "\r\n___HANDLEBARS1___", - ["handlebars", [ - ["delimiter", "{{{"], - ["variable", "intro"], - ["delimiter", "}}}"] - ]], - ["handlebars", [ - ["comment", "{{!
        }}"] - ]] -] - ----------------------------------------------------- - -Checks for Handlebars in Markup. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/number_feature.test b/docs/_style/prism-master/tests/languages/handlebars/number_feature.test deleted file mode 100644 index aa894ec5..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/number_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -{{ 0xBadFace }} -{{{ 42 }}} -{{{ 4e2 }}} -{{ 3.5e+1 }} -{{ 0.15e-9 }} - ----------------------------------------------------- - -[ - ["handlebars", [ - ["delimiter", "{{"], ["number", "0xBadFace"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], ["number", "42"], ["delimiter", "}}}"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], ["number", "4e2"], ["delimiter", "}}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["number", "3.5e+1"], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["number", "0.15e-9"], ["delimiter", "}}"] - ]] -] - ----------------------------------------------------- - -Checks for decimal and hexadecimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/handlebars/string_feature.test b/docs/_style/prism-master/tests/languages/handlebars/string_feature.test deleted file mode 100644 index f9f9d707..00000000 --- a/docs/_style/prism-master/tests/languages/handlebars/string_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -{{ "" }} -{{{''}}} -{{{"foobar"}}} -{{ 'foobar' }} - ----------------------------------------------------- - -[ - ["handlebars", [ - ["delimiter", "{{"], ["string", "\"\""], ["delimiter", "}}"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], ["string", "''"], ["delimiter", "}}}"] - ]], - ["handlebars", [ - ["delimiter", "{{{"], ["string", "\"foobar\""], ["delimiter", "}}}"] - ]], - ["handlebars", [ - ["delimiter", "{{"], ["string", "'foobar'"], ["delimiter", "}}"] - ]] -] - ----------------------------------------------------- - -Checks for strings \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/builtin_feature.test b/docs/_style/prism-master/tests/languages/haskell/builtin_feature.test deleted file mode 100644 index c50ca40b..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/builtin_feature.test +++ /dev/null @@ -1,137 +0,0 @@ -abs acos acosh all and -any appendFile approxRational -asTypeOf asin asinh atan -atan2 atanh basicIORun break -catch ceiling chr compare -concat concatMap const -cos cosh curry cycle -decodeFloat denominator -digitToInt div divMod -drop dropWhile either -elem encodeFloat enumFrom -enumFromThen enumFromThenTo -enumFromTo error even exp -exponent fail filter flip -floatDigits floatRadix -floatRange floor fmap -foldl foldl1 foldr foldr1 -fromDouble fromEnum fromInt -fromInteger fromIntegral -fromRational fst gcd -getChar getContents getLine -group head id inRange index -init intToDigit interact -ioError isAlpha isAlphaNum -isAscii isControl isDenormalized -isDigit isHexDigit isIEEE -isInfinite isLower isNaN -isNegativeZero isOctDigit -isPrint isSpace isUpper iterate -last lcm length lex -lexDigits lexLitChar lines -log logBase lookup map -mapM mapM_ max maxBound -maximum maybe min minBound -minimum mod negate not -notElem null numerator odd -or ord otherwise pack pi -pred primExitWith print -product properFraction -putChar putStr putStrLn quot -quotRem range rangeSize read -readDec readFile readFloat readHex -readIO readInt readList -readLitChar readLn readOct -readParen readSigned reads -readsPrec realToFrac recip -rem repeat replicate return -reverse round scaleFloat -scanl scanl1 scanr scanr1 -seq sequence sequence_ show -showChar showInt showList -showLitChar showParen showSigned -showString shows showsPrec -significand signum sin sinh -snd sort span splitAt sqrt -subtract succ sum tail take -takeWhile tan tanh threadToIOResult -toEnum toInt toInteger -toLower toRational toUpper -truncate uncurry undefined -unlines until unwords -unzip unzip3 userError words -writeFile zip zip3 zipWith -zipWith3 - ----------------------------------------------------- - -[ - ["builtin", "abs"], ["builtin", "acos"], ["builtin", "acosh"], ["builtin", "all"], ["builtin", "and"], - ["builtin", "any"], ["builtin", "appendFile"], ["builtin", "approxRational"], - ["builtin", "asTypeOf"], ["builtin", "asin"], ["builtin", "asinh"], ["builtin", "atan"], - ["builtin", "atan2"], ["builtin", "atanh"], ["builtin", "basicIORun"], ["builtin", "break"], - ["builtin", "catch"], ["builtin", "ceiling"], ["builtin", "chr"], ["builtin", "compare"], - ["builtin", "concat"], ["builtin", "concatMap"], ["builtin", "const"], - ["builtin", "cos"], ["builtin", "cosh"], ["builtin", "curry"], ["builtin", "cycle"], - ["builtin", "decodeFloat"], ["builtin", "denominator"], - ["builtin", "digitToInt"], ["builtin", "div"], ["builtin", "divMod"], - ["builtin", "drop"], ["builtin", "dropWhile"], ["builtin", "either"], - ["builtin", "elem"], ["builtin", "encodeFloat"], ["builtin", "enumFrom"], - ["builtin", "enumFromThen"], ["builtin", "enumFromThenTo"], - ["builtin", "enumFromTo"], ["builtin", "error"], ["builtin", "even"], ["builtin", "exp"], - ["builtin", "exponent"], ["builtin", "fail"], ["builtin", "filter"], ["builtin", "flip"], - ["builtin", "floatDigits"], ["builtin", "floatRadix"], - ["builtin", "floatRange"], ["builtin", "floor"], ["builtin", "fmap"], - ["builtin", "foldl"], ["builtin", "foldl1"], ["builtin", "foldr"], ["builtin", "foldr1"], - ["builtin", "fromDouble"], ["builtin", "fromEnum"], ["builtin", "fromInt"], - ["builtin", "fromInteger"], ["builtin", "fromIntegral"], - ["builtin", "fromRational"], ["builtin", "fst"], ["builtin", "gcd"], - ["builtin", "getChar"], ["builtin", "getContents"], ["builtin", "getLine"], - ["builtin", "group"], ["builtin", "head"], ["builtin", "id"], ["builtin", "inRange"], ["builtin", "index"], - ["builtin", "init"], ["builtin", "intToDigit"], ["builtin", "interact"], - ["builtin", "ioError"], ["builtin", "isAlpha"], ["builtin", "isAlphaNum"], - ["builtin", "isAscii"], ["builtin", "isControl"], ["builtin", "isDenormalized"], - ["builtin", "isDigit"], ["builtin", "isHexDigit"], ["builtin", "isIEEE"], - ["builtin", "isInfinite"], ["builtin", "isLower"], ["builtin", "isNaN"], - ["builtin", "isNegativeZero"], ["builtin", "isOctDigit"], - ["builtin", "isPrint"], ["builtin", "isSpace"], ["builtin", "isUpper"], ["builtin", "iterate"], - ["builtin", "last"], ["builtin", "lcm"], ["builtin", "length"], ["builtin", "lex"], - ["builtin", "lexDigits"], ["builtin", "lexLitChar"], ["builtin", "lines"], - ["builtin", "log"], ["builtin", "logBase"], ["builtin", "lookup"], ["builtin", "map"], - ["builtin", "mapM"], ["builtin", "mapM_"], ["builtin", "max"], ["builtin", "maxBound"], - ["builtin", "maximum"], ["builtin", "maybe"], ["builtin", "min"], ["builtin", "minBound"], - ["builtin", "minimum"], ["builtin", "mod"], ["builtin", "negate"], ["builtin", "not"], - ["builtin", "notElem"], ["builtin", "null"], ["builtin", "numerator"], ["builtin", "odd"], - ["builtin", "or"], ["builtin", "ord"], ["builtin", "otherwise"], ["builtin", "pack"], ["builtin", "pi"], - ["builtin", "pred"], ["builtin", "primExitWith"], ["builtin", "print"], - ["builtin", "product"], ["builtin", "properFraction"], - ["builtin", "putChar"], ["builtin", "putStr"], ["builtin", "putStrLn"], ["builtin", "quot"], - ["builtin", "quotRem"], ["builtin", "range"], ["builtin", "rangeSize"], ["builtin", "read"], - ["builtin", "readDec"], ["builtin", "readFile"], ["builtin", "readFloat"], ["builtin", "readHex"], - ["builtin", "readIO"], ["builtin", "readInt"], ["builtin", "readList"], - ["builtin", "readLitChar"], ["builtin", "readLn"], ["builtin", "readOct"], - ["builtin", "readParen"], ["builtin", "readSigned"], ["builtin", "reads"], - ["builtin", "readsPrec"], ["builtin", "realToFrac"], ["builtin", "recip"], - ["builtin", "rem"], ["builtin", "repeat"], ["builtin", "replicate"], ["builtin", "return"], - ["builtin", "reverse"], ["builtin", "round"], ["builtin", "scaleFloat"], - ["builtin", "scanl"], ["builtin", "scanl1"], ["builtin", "scanr"], ["builtin", "scanr1"], - ["builtin", "seq"], ["builtin", "sequence"], ["builtin", "sequence_"], ["builtin", "show"], - ["builtin", "showChar"], ["builtin", "showInt"], ["builtin", "showList"], - ["builtin", "showLitChar"], ["builtin", "showParen"], ["builtin", "showSigned"], - ["builtin", "showString"], ["builtin", "shows"], ["builtin", "showsPrec"], - ["builtin", "significand"], ["builtin", "signum"], ["builtin", "sin"], ["builtin", "sinh"], - ["builtin", "snd"], ["builtin", "sort"], ["builtin", "span"], ["builtin", "splitAt"], ["builtin", "sqrt"], - ["builtin", "subtract"], ["builtin", "succ"], ["builtin", "sum"], ["builtin", "tail"], ["builtin", "take"], - ["builtin", "takeWhile"], ["builtin", "tan"], ["builtin", "tanh"], ["builtin", "threadToIOResult"], - ["builtin", "toEnum"], ["builtin", "toInt"], ["builtin", "toInteger"], - ["builtin", "toLower"], ["builtin", "toRational"], ["builtin", "toUpper"], - ["builtin", "truncate"], ["builtin", "uncurry"], ["builtin", "undefined"], - ["builtin", "unlines"], ["builtin", "until"], ["builtin", "unwords"], - ["builtin", "unzip"], ["builtin", "unzip3"], ["builtin", "userError"], ["builtin", "words"], - ["builtin", "writeFile"], ["builtin", "zip"], ["builtin", "zip3"], ["builtin", "zipWith"], - ["builtin", "zipWith3"] -] - ----------------------------------------------------- - -Checks for all builtin. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/char_feature.test b/docs/_style/prism-master/tests/languages/haskell/char_feature.test deleted file mode 100644 index e4dedc44..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/char_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -'a' -'\n' -'\23' -'\xFE' - ----------------------------------------------------- - -[ - ["char", "'a'"], - ["char", "'\\n'"], - ["char", "'\\23'"], - ["char", "'\\xFE'"] -] - ----------------------------------------------------- - -Checks for chars. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/comment_feature.test b/docs/_style/prism-master/tests/languages/haskell/comment_feature.test deleted file mode 100644 index d8c8cdda..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/comment_feature.test +++ /dev/null @@ -1,14 +0,0 @@ --- foo -{- foo -bar -} - ----------------------------------------------------- - -[ - ["comment", "-- foo"], - ["comment", "{- foo\r\nbar -}"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/constant_feature.test b/docs/_style/prism-master/tests/languages/haskell/constant_feature.test deleted file mode 100644 index 06f25f10..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/constant_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -Foo -Foo.Bar -Baz.Foobar_42 - ----------------------------------------------------- - -[ - ["constant", "Foo"], - ["constant", "Foo.Bar"], - ["constant", "Baz.Foobar_42"] -] - ----------------------------------------------------- - -Checks for constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/hvariable_feature.test b/docs/_style/prism-master/tests/languages/haskell/hvariable_feature.test deleted file mode 100644 index defa3ba7..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/hvariable_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo -Foo.bar -Baz.foobar_42 - ----------------------------------------------------- - -[ - ["hvariable", "foo"], - ["hvariable", "Foo.bar"], - ["hvariable", "Baz.foobar_42"] -] - ----------------------------------------------------- - -Checks for hvariables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/import_statement_feature.test b/docs/_style/prism-master/tests/languages/haskell/import_statement_feature.test deleted file mode 100644 index 120fbe75..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/import_statement_feature.test +++ /dev/null @@ -1,35 +0,0 @@ -import Foo -import qualified Foobar -import Foo_42.Bar as Foobar -import Foo.Bar as Foo.Baz hiding - ----------------------------------------------------- - -[ - ["import_statement", [ - ["keyword", "import"], - " Foo" - ]], - ["import_statement", [ - ["keyword", "import"], - ["keyword", "qualified"], - " Foobar" - ]], - ["import_statement", [ - ["keyword", "import"], - " Foo_42.Bar ", - ["keyword", "as"], - " Foobar" - ]], - ["import_statement", [ - ["keyword", "import"], - " Foo.Bar ", - ["keyword", "as"], - " Foo.Baz ", - ["keyword", "hiding"] - ]] -] - ----------------------------------------------------- - -Checks for import statement. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/keyword_feature.test b/docs/_style/prism-master/tests/languages/haskell/keyword_feature.test deleted file mode 100644 index 0fb36579..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/keyword_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -case class data deriving -do else if in infixl -infixr instance let -module newtype of -primitive then type where - ----------------------------------------------------- - -[ - ["keyword", "case"], ["keyword", "class"], ["keyword", "data"], ["keyword", "deriving"], - ["keyword", "do"], ["keyword", "else"], ["keyword", "if"], ["keyword", "in"], ["keyword", "infixl"], - ["keyword", "infixr"], ["keyword", "instance"], ["keyword", "let"], - ["keyword", "module"], ["keyword", "newtype"], ["keyword", "of"], - ["keyword", "primitive"], ["keyword", "then"], ["keyword", "type"], ["keyword", "where"] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/number_feature.test b/docs/_style/prism-master/tests/languages/haskell/number_feature.test deleted file mode 100644 index f45a0f28..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -42 -3.14159 -2E3 -1.2e-4 -0.9e+1 -0o47 -0xBadFace - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "2E3"], - ["number", "1.2e-4"], - ["number", "0.9e+1"], - ["number", "0o47"], - ["number", "0xBadFace"] -] - ----------------------------------------------------- - -Checks for decimal, octal and hexadecimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/operator_feature.test b/docs/_style/prism-master/tests/languages/haskell/operator_feature.test deleted file mode 100644 index 9cd8339f..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/operator_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -.. -reverse . sort -`foo` -`Foo.bar` -+ - * / -^ ^^ ** -&& || -< <= == /= ->= > \ | -++ : !! -\\ <- -> -= :: => ->> >>= >@> -~ ! @ - ----------------------------------------------------- - -[ - ["operator", ".."], - ["builtin", "reverse"], ["operator", " . "], ["builtin", "sort"], - ["operator", "`foo`"], - ["operator", "`Foo.bar`"], - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], - ["operator", "^"], ["operator", "^^"], ["operator", "**"], - ["operator", "&&"], ["operator", "||"], - ["operator", "<"], ["operator", "<="], ["operator", "=="], ["operator", "/="], - ["operator", ">="], ["operator", ">"], ["operator", "\\"], ["operator", "|"], - ["operator", "++"], ["operator", ":"], ["operator", "!!"], - ["operator", "\\\\"], ["operator", "<-"], ["operator", "->"], - ["operator", "="], ["operator", "::"], ["operator", "=>"], - ["operator", ">>"], ["operator", ">>="], ["operator", ">@>"], - ["operator", "~"], ["operator", "!"], ["operator", "@"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haskell/string_feature.test b/docs/_style/prism-master/tests/languages/haskell/string_feature.test deleted file mode 100644 index 49c2cfbf..00000000 --- a/docs/_style/prism-master/tests/languages/haskell/string_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -"" -"fo\"o" -"foo \ - \ bar" -"foo -- comment \ - \ bar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\\\"o\""], - ["string", "\"foo \\\r\n \\ bar\""], - ["string", "\"foo -- comment \\\r\n \\ bar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/keyword_feature.test b/docs/_style/prism-master/tests/languages/haxe/keyword_feature.test deleted file mode 100644 index 6a26bd09..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/keyword_feature.test +++ /dev/null @@ -1,93 +0,0 @@ -this -abstract -as -break -case -cast -catch -class -continue -default -do -dynamic -else -enum -extends -extern -from -for -function -if -implements -import -in -inline -interface -macro -new -null -override -public -private -return -static -super -switch -throw -to -try -typedef -using -var -while - ----------------------------------------------------- - -[ - ["keyword", "this"], - ["keyword", "abstract"], - ["keyword", "as"], - ["keyword", "break"], - ["keyword", "case"], - ["keyword", "cast"], - ["keyword", "catch"], - ["keyword", "class"], - ["keyword", "continue"], - ["keyword", "default"], - ["keyword", "do"], - ["keyword", "dynamic"], - ["keyword", "else"], - ["keyword", "enum"], - ["keyword", "extends"], - ["keyword", "extern"], - ["keyword", "from"], - ["keyword", "for"], - ["keyword", "function"], - ["keyword", "if"], - ["keyword", "implements"], - ["keyword", "import"], - ["keyword", "in"], - ["keyword", "inline"], - ["keyword", "interface"], - ["keyword", "macro"], - ["keyword", "new"], - ["keyword", "null"], - ["keyword", "override"], - ["keyword", "public"], - ["keyword", "private"], - ["keyword", "return"], - ["keyword", "static"], - ["keyword", "super"], - ["keyword", "switch"], - ["keyword", "throw"], - ["keyword", "to"], - ["keyword", "try"], - ["keyword", "typedef"], - ["keyword", "using"], - ["keyword", "var"], - ["keyword", "while"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/metadata_feature.test b/docs/_style/prism-master/tests/languages/haxe/metadata_feature.test deleted file mode 100644 index 312114d9..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/metadata_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -@author("Nicolas") -@debug -@:noCompletion - ----------------------------------------------------- - -[ - ["metadata", "@author"], ["punctuation", "("], ["string", ["\"Nicolas\""]], ["punctuation", ")"], - ["metadata", "@debug"], - ["metadata", "@:noCompletion"] -] - ----------------------------------------------------- - -Checks for metadata. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/operator_feature.test b/docs/_style/prism-master/tests/languages/haxe/operator_feature.test deleted file mode 100644 index 6963f800..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/operator_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -... -+ ++ -- -- -> -= == -! != -& && -| || -< <= << -> >= >> -* / % ~ ^ - ----------------------------------------------------- - -[ - ["operator", "..."], - ["operator", "+"], ["operator", "++"], - ["operator", "-"], ["operator", "--"], ["operator", "->"], - ["operator", "="], ["operator", "=="], - ["operator", "!"], ["operator", "!="], - ["operator", "&"], ["operator", "&&"], - ["operator", "|"], ["operator", "||"], - ["operator", "<"], ["operator", "<="], ["operator", "<<"], - ["operator", ">"], ["operator", ">="], ["operator", ">>"], - ["operator", "*"], ["operator", "/"], ["operator", "%"], ["operator", "~"], ["operator", "^"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/preprocessor_feature.test b/docs/_style/prism-master/tests/languages/haxe/preprocessor_feature.test deleted file mode 100644 index 3cadacf6..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/preprocessor_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -#if -#elseif -#else -#end - ----------------------------------------------------- - -[ - ["preprocessor", "#if"], - ["preprocessor", "#elseif"], - ["preprocessor", "#else"], - ["preprocessor", "#end"] -] - ----------------------------------------------------- - -Checks for preprocessor directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/regex_feature.test b/docs/_style/prism-master/tests/languages/haxe/regex_feature.test deleted file mode 100644 index 577cab7b..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/regex_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -~/ha\/xe/i -~/[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z][A-Z][A-Z]?/i -~/(dog|fox)/igmsu - ----------------------------------------------------- - -[ - ["regex", "~/ha\\/xe/i"], - ["regex", "~/[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z][A-Z][A-Z]?/i"], - ["regex", "~/(dog|fox)/igmsu"] -] - ----------------------------------------------------- - -Checks for regexes. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/reification_feature.test b/docs/_style/prism-master/tests/languages/haxe/reification_feature.test deleted file mode 100644 index 6d382b7c..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/reification_feature.test +++ /dev/null @@ -1,16 +0,0 @@ -$e -${4+2} - ----------------------------------------------------- - -[ - ["reification", "$e"], - ["reification", "$"], - ["punctuation", "{"], - ["number", "4"], ["operator", "+"], ["number", "2"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for reification. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/haxe/string_feature.test b/docs/_style/prism-master/tests/languages/haxe/string_feature.test deleted file mode 100644 index 61f1985f..00000000 --- a/docs/_style/prism-master/tests/languages/haxe/string_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -"" -"Foo -\"bar\" -baz" -"$bar ${4+2}" -'' -'Foo -\'bar\' -baz' - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["\"Foo\r\n\\\"bar\\\"\r\nbaz\""]], - ["string", [ - "\"", - ["interpolation", [ - ["interpolation", "$bar"] - ]], - ["interpolation", [ - ["interpolation", "$"], - ["punctuation", "{"], - ["number", "4"], - ["operator", "+"], - ["number", "2"], - ["punctuation", "}"] - ]], - "\"" - ]], - ["string", ["''"]], - ["string", ["'Foo\r\n\\'bar\\'\r\nbaz'"]] -] - ----------------------------------------------------- - -Checks for strings and string interpolation. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/hpkp/safe_maxage_feature.test b/docs/_style/prism-master/tests/languages/hpkp/safe_maxage_feature.test deleted file mode 100644 index 169f23cd..00000000 --- a/docs/_style/prism-master/tests/languages/hpkp/safe_maxage_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -max-age=31536000 - ----------------------------------------------------- - -[ - ["directive", "max-age="], - ["safe", "31536000"] -] - ----------------------------------------------------- - -Checks for HPKP with a "safe" max-age. diff --git a/docs/_style/prism-master/tests/languages/hpkp/sha256_pin_feature.test b/docs/_style/prism-master/tests/languages/hpkp/sha256_pin_feature.test deleted file mode 100644 index 45adf1c8..00000000 --- a/docs/_style/prism-master/tests/languages/hpkp/sha256_pin_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -pin-sha256="EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4=" - ----------------------------------------------------- - -[ - ["directive", "pin-sha256=\"EpOpN/ahUF6jhWShDUdy+NvvtaGcu5F7qM6+x2mfkh4=\""] -] - ----------------------------------------------------- - -Checks for HPKP with a sha256 pin. diff --git a/docs/_style/prism-master/tests/languages/hpkp/unsafe_maxage_feature.test b/docs/_style/prism-master/tests/languages/hpkp/unsafe_maxage_feature.test deleted file mode 100644 index 4f1ff961..00000000 --- a/docs/_style/prism-master/tests/languages/hpkp/unsafe_maxage_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -max-age=123 - ----------------------------------------------------- - -[ - ["directive", "max-age="], - ["unsafe", "123"] -] - ----------------------------------------------------- - -Checks for HPKP with an "unsafe" max-age. diff --git a/docs/_style/prism-master/tests/languages/hsts/include_subdomains_feature.test b/docs/_style/prism-master/tests/languages/hsts/include_subdomains_feature.test deleted file mode 100644 index e889e84c..00000000 --- a/docs/_style/prism-master/tests/languages/hsts/include_subdomains_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -includeSubDomains - ----------------------------------------------------- - -[ - ["directive", "includeSubDomains"] -] - ----------------------------------------------------- - -Checks for HSTS with the includeSubDomains directive. diff --git a/docs/_style/prism-master/tests/languages/hsts/preload_feature.test b/docs/_style/prism-master/tests/languages/hsts/preload_feature.test deleted file mode 100644 index 2a390a03..00000000 --- a/docs/_style/prism-master/tests/languages/hsts/preload_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -preload - ----------------------------------------------------- - -[ - ["directive", "preload"] -] - ----------------------------------------------------- - -Checks for HSTS with the preload directive. diff --git a/docs/_style/prism-master/tests/languages/hsts/safe_maxage_feature.test b/docs/_style/prism-master/tests/languages/hsts/safe_maxage_feature.test deleted file mode 100644 index 6797fff3..00000000 --- a/docs/_style/prism-master/tests/languages/hsts/safe_maxage_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -max-age=31536000 - ----------------------------------------------------- - -[ - ["directive", "max-age="], - ["safe", "31536000"] -] - ----------------------------------------------------- - -Checks for HSTS with a "safe" max-age. diff --git a/docs/_style/prism-master/tests/languages/hsts/unsafe_maxage_feature.test b/docs/_style/prism-master/tests/languages/hsts/unsafe_maxage_feature.test deleted file mode 100644 index dba69dc3..00000000 --- a/docs/_style/prism-master/tests/languages/hsts/unsafe_maxage_feature.test +++ /dev/null @@ -1,12 +0,0 @@ -max-age=123 - ----------------------------------------------------- - -[ - ["directive", "max-age="], - ["unsafe", "123"] -] - ----------------------------------------------------- - -Checks for HSTS with an "unsafe" max-age. diff --git a/docs/_style/prism-master/tests/languages/http/header-name_feature.test b/docs/_style/prism-master/tests/languages/http/header-name_feature.test deleted file mode 100644 index cd2612fa..00000000 --- a/docs/_style/prism-master/tests/languages/http/header-name_feature.test +++ /dev/null @@ -1,24 +0,0 @@ -Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3 -Accept-Encoding: gzip, deflate -Server: GitHub.com -Date: Mon, 22 Dec 2014 18:25:30 GMT -Content-Type: text/html; charset=utf-8 - ----------------------------------------------------- - -[ - ["header-name", "Accept-Language:"], - " fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3\r\n", - ["header-name", "Accept-Encoding:"], - " gzip, deflate\r\n", - ["header-name", "Server:"], - " GitHub.com\r\n", - ["header-name", "Date:"], - " Mon, 22 Dec 2014 18:25:30 GMT\r\n", - ["header-name", "Content-Type:"], - " text/html; charset=utf-8" -] - ----------------------------------------------------- - -Checks for header names. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/http/request-line_feature.test b/docs/_style/prism-master/tests/languages/http/request-line_feature.test deleted file mode 100644 index 70db1a9f..00000000 --- a/docs/_style/prism-master/tests/languages/http/request-line_feature.test +++ /dev/null @@ -1,56 +0,0 @@ -POST http://example.com HTTP/1.0 -GET http://localhost:9999/foo.html HTTP/1.1 -PUT http://www.example.com HTTP/2.0 -DELETE https://example.com HTTP/1.1 -OPTIONS https://www.example.com HTTP/1.1 -PATCH http://example.com HTTP/1.0 -TRACE http://example.com HTTP/1.0 -CONNECT http://example.com HTTP/1.0 -GET /path/to/foo.html HTTP/1.1 - ----------------------------------------------------- - -[ - ["request-line", [ - ["property", "POST"], - " http://example.com HTTP/1.0" - ]], - ["request-line", [ - ["property", "GET"], - " http://localhost", - ["attr-name", ":9999"], - "/foo.html HTTP/1.1" - ]], - ["request-line", [ - ["property", "PUT"], - " http://www.example.com HTTP/2.0" - ]], - ["request-line", [ - ["property", "DELETE"], - " https://example.com HTTP/1.1" - ]], - ["request-line", [ - ["property", "OPTIONS"], - " https://www.example.com HTTP/1.1" - ]], - ["request-line", [ - ["property", "PATCH"], - " http://example.com HTTP/1.0" - ]], - ["request-line", [ - ["property", "TRACE"], - " http://example.com HTTP/1.0" - ]], - ["request-line", [ - ["property", "CONNECT"], - " http://example.com HTTP/1.0" - ]], - ["request-line", [ - ["property", "GET"], - " /path/to/foo.html HTTP/1.1" - ]] -] - ----------------------------------------------------- - -Checks for request lines. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/http/response-status_feature.test b/docs/_style/prism-master/tests/languages/http/response-status_feature.test deleted file mode 100644 index 7384ff2a..00000000 --- a/docs/_style/prism-master/tests/languages/http/response-status_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -HTTP/1.0 200 OK -HTTP/1.1 403 Forbidden -HTTP/1.1 404 Not Found -HTTP/1.0 418 I'm a teapot - ----------------------------------------------------- - -[ - ["response-status", [ - "HTTP/1.0 ", - ["property", "200 OK"] - ]], - ["response-status", [ - "HTTP/1.1 ", - ["property", "403 Forbidden"] - ]], - ["response-status", [ - "HTTP/1.1 ", - ["property", "404 Not Found"] - ]], - ["response-status", [ - "HTTP/1.0 ", - ["property", "418 I'm a teapot"] - ]] -] - ----------------------------------------------------- - -Checks for response statuses. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/comment_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/comment_feature.test deleted file mode 100644 index 65211e27..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/comment_feature.test +++ /dev/null @@ -1,17 +0,0 @@ -'Foobar -' Foobar -REMFoobar -REM Foobar - ----------------------------------------------------- - -[ - ["comment", "'Foobar"], - ["comment", "' Foobar"], - ["comment", "REMFoobar"], - ["comment", "REM Foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/function_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/function_feature.test deleted file mode 100644 index 6cc325b6..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/function_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -ABS -ANA -ASC -BIN -BTN -DEC -FREE -HELP -HEX -I2CR -I2CW -IN -INKEY -LEN -LINE -PEEK -RND -SCR -SOUND -STR -TICK -USR -VER -VPEEK -ZER - ----------------------------------------------------- - -[ - ["function", "ABS"], - ["function", "ANA"], - ["function", "ASC"], - ["function", "BIN"], - ["function", "BTN"], - ["function", "DEC"], - ["function", "FREE"], - ["function", "HELP"], - ["function", "HEX"], - ["function", "I2CR"], - ["function", "I2CW"], - ["function", "IN"], - ["function", "INKEY"], - ["function", "LEN"], - ["function", "LINE"], - ["function", "PEEK"], - ["function", "RND"], - ["function", "SCR"], - ["function", "SOUND"], - ["function", "STR"], - ["function", "TICK"], - ["function", "USR"], - ["function", "VER"], - ["function", "VPEEK"], - ["function", "ZER"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/keyword_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/keyword_feature.test deleted file mode 100644 index 71d83a48..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/keyword_feature.test +++ /dev/null @@ -1,119 +0,0 @@ -BEEP -BPS -CASE -CLEAR -CLK -CLO -CLP -CLS -CLT -CLV -CONT -COPY -ELSE -END -FILE -FILES -FOR -GOSUB -GSB -GOTO -IF -INPUT -KBD -LED -LET -LIST -LOAD -LOCATE -LRUN -NEW -NEXT -OUT -RIGHT -PLAY -POKE -PRINT -PWM -RENUM -RESET -RETURN -RTN -RUN -SAVE -SCROLL -SLEEP -SRND -STEP -STOP -SUB -TEMPO -THEN -TO -UART -VIDEO -WAIT - ----------------------------------------------------- - -[ - ["keyword", "BEEP"], - ["keyword", "BPS"], - ["keyword", "CASE"], - ["keyword", "CLEAR"], - ["keyword", "CLK"], - ["keyword", "CLO"], - ["keyword", "CLP"], - ["keyword", "CLS"], - ["keyword", "CLT"], - ["keyword", "CLV"], - ["keyword", "CONT"], - ["keyword", "COPY"], - ["keyword", "ELSE"], - ["keyword", "END"], - ["keyword", "FILE"], - ["keyword", "FILES"], - ["keyword", "FOR"], - ["keyword", "GOSUB"], - ["keyword", "GSB"], - ["keyword", "GOTO"], - ["keyword", "IF"], - ["keyword", "INPUT"], - ["keyword", "KBD"], - ["keyword", "LED"], - ["keyword", "LET"], - ["keyword", "LIST"], - ["keyword", "LOAD"], - ["keyword", "LOCATE"], - ["keyword", "LRUN"], - ["keyword", "NEW"], - ["keyword", "NEXT"], - ["keyword", "OUT"], - ["keyword", "RIGHT"], - ["keyword", "PLAY"], - ["keyword", "POKE"], - ["keyword", "PRINT"], - ["keyword", "PWM"], - ["keyword", "RENUM"], - ["keyword", "RESET"], - ["keyword", "RETURN"], - ["keyword", "RTN"], - ["keyword", "RUN"], - ["keyword", "SAVE"], - ["keyword", "SCROLL"], - ["keyword", "SLEEP"], - ["keyword", "SRND"], - ["keyword", "STEP"], - ["keyword", "STOP"], - ["keyword", "SUB"], - ["keyword", "TEMPO"], - ["keyword", "THEN"], - ["keyword", "TO"], - ["keyword", "UART"], - ["keyword", "VIDEO"], - ["keyword", "WAIT"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/label_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/label_feature.test deleted file mode 100644 index 42ad2e85..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/label_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -@PAPERNEKO -@SUKI - ----------------------------------------------------- - -[ - ["label", "@PAPERNEKO"], - ["label", "@SUKI"] -] - ----------------------------------------------------- - -Checks for labels. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/number_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/number_feature.test deleted file mode 100644 index 89bfcf2c..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -42 -3.14159 -2e8 -3.4E-9 -0.7E+12 -#496F726953756B69 -`11100010 - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "2e8"], - ["number", "3.4E-9"], - ["number", "0.7E+12"], - ["number", "#496F726953756B69"], - ["number", "`11100010"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/operator_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/operator_feature.test deleted file mode 100644 index 59e79944..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/operator_feature.test +++ /dev/null @@ -1,36 +0,0 @@ -< -<= -<> -> ->= -+ -- -* -/ -^ -= -& -~ -! -| -AND -NOT -OR -|| -&& - ----------------------------------------------------- - -[ - ["operator", "<"], ["operator", "<="], ["operator", "<>"], - ["operator", ">"], ["operator", ">="], - ["operator", "+"], ["operator", "-"], ["operator", "*"], ["operator", "/"], - ["operator", "^"], ["operator", "="], - ["operator", "&"], ["operator", "~"], ["operator", "!"], ["operator", "|"], - ["operator", "AND"], ["operator", "NOT"], ["operator", "OR"], - ["operator", "||"], ["operator", "&&"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ichigojam/string_feature.test b/docs/_style/prism-master/tests/languages/ichigojam/string_feature.test deleted file mode 100644 index 83fea35e..00000000 --- a/docs/_style/prism-master/tests/languages/ichigojam/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -"" -"fo""obar" - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"fo\"\"obar\""] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/builtin-keyword_feature.test b/docs/_style/prism-master/tests/languages/icon/builtin-keyword_feature.test deleted file mode 100644 index 1d257642..00000000 --- a/docs/_style/prism-master/tests/languages/icon/builtin-keyword_feature.test +++ /dev/null @@ -1,91 +0,0 @@ -&allocated -&ascii -&clock -&collections -&cset -¤t -&date -&dateline -&digits -&dump -&e -&error -&errornumber -&errortext -&errorvalue -&errout -&fail -&features -&file -&host -&input -&lcase -&letters -&level -&line -&main -&null -&output -&phi -&pi -&pos -&progname -&random -®ions -&source -&storage -&subject -&time -&trace -&ucase -&version - ----------------------------------------------------- - -[ - ["builtin-keyword", "&allocated"], - ["builtin-keyword", "&ascii"], - ["builtin-keyword", "&clock"], - ["builtin-keyword", "&collections"], - ["builtin-keyword", "&cset"], - ["builtin-keyword", "¤t"], - ["builtin-keyword", "&date"], - ["builtin-keyword", "&dateline"], - ["builtin-keyword", "&digits"], - ["builtin-keyword", "&dump"], - ["builtin-keyword", "&e"], - ["builtin-keyword", "&error"], - ["builtin-keyword", "&errornumber"], - ["builtin-keyword", "&errortext"], - ["builtin-keyword", "&errorvalue"], - ["builtin-keyword", "&errout"], - ["builtin-keyword", "&fail"], - ["builtin-keyword", "&features"], - ["builtin-keyword", "&file"], - ["builtin-keyword", "&host"], - ["builtin-keyword", "&input"], - ["builtin-keyword", "&lcase"], - ["builtin-keyword", "&letters"], - ["builtin-keyword", "&level"], - ["builtin-keyword", "&line"], - ["builtin-keyword", "&main"], - ["builtin-keyword", "&null"], - ["builtin-keyword", "&output"], - ["builtin-keyword", "&phi"], - ["builtin-keyword", "&pi"], - ["builtin-keyword", "&pos"], - ["builtin-keyword", "&progname"], - ["builtin-keyword", "&random"], - ["builtin-keyword", "®ions"], - ["builtin-keyword", "&source"], - ["builtin-keyword", "&storage"], - ["builtin-keyword", "&subject"], - ["builtin-keyword", "&time"], - ["builtin-keyword", "&trace"], - ["builtin-keyword", "&ucase"], - ["builtin-keyword", "&version"] -] - ----------------------------------------------------- - -Checks for builtin keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/comment_feature.test b/docs/_style/prism-master/tests/languages/icon/comment_feature.test deleted file mode 100644 index 09493ab5..00000000 --- a/docs/_style/prism-master/tests/languages/icon/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -# -# Foobar - ----------------------------------------------------- - -[ - ["comment", "#"], - ["comment", "# Foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/directive_feature.test b/docs/_style/prism-master/tests/languages/icon/directive_feature.test deleted file mode 100644 index ede5ebc8..00000000 --- a/docs/_style/prism-master/tests/languages/icon/directive_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -$include -$line -$define -$undef -$ifdef -$ifndef - ----------------------------------------------------- - -[ - ["directive", "$include"], - ["directive", "$line"], - ["directive", "$define"], - ["directive", "$undef"], - ["directive", "$ifdef"], - ["directive", "$ifndef"] -] - ----------------------------------------------------- - -Checks for preprocessor directives. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/function_feature.test b/docs/_style/prism-master/tests/languages/icon/function_feature.test deleted file mode 100644 index 715452f5..00000000 --- a/docs/_style/prism-master/tests/languages/icon/function_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -foo() -Foobar_42{} -Foo_Bar ! [] - ----------------------------------------------------- - -[ - ["function", "foo"], ["punctuation", "("], ["punctuation", ")"], - ["function", "Foobar_42"], ["punctuation", "{"], ["punctuation", "}"], - ["function", "Foo_Bar"], ["operator", "!"], ["punctuation", "["], ["punctuation", "]"] -] - ----------------------------------------------------- - -Checks for functions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/keyword_feature.test b/docs/_style/prism-master/tests/languages/icon/keyword_feature.test deleted file mode 100644 index 08169110..00000000 --- a/docs/_style/prism-master/tests/languages/icon/keyword_feature.test +++ /dev/null @@ -1,67 +0,0 @@ -break -by -case -create -default -do -else -end -every -fail -global -if -initial -invocable -link -local -next -not -of -procedure -record -repeat -return -static -suspend -then -to -until -while - ----------------------------------------------------- - -[ - ["keyword", "break"], - ["keyword", "by"], - ["keyword", "case"], - ["keyword", "create"], - ["keyword", "default"], - ["keyword", "do"], - ["keyword", "else"], - ["keyword", "end"], - ["keyword", "every"], - ["keyword", "fail"], - ["keyword", "global"], - ["keyword", "if"], - ["keyword", "initial"], - ["keyword", "invocable"], - ["keyword", "link"], - ["keyword", "local"], - ["keyword", "next"], - ["keyword", "not"], - ["keyword", "of"], - ["keyword", "procedure"], - ["keyword", "record"], - ["keyword", "repeat"], - ["keyword", "return"], - ["keyword", "static"], - ["keyword", "suspend"], - ["keyword", "then"], - ["keyword", "to"], - ["keyword", "until"], - ["keyword", "while"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/number_feature.test b/docs/_style/prism-master/tests/languages/icon/number_feature.test deleted file mode 100644 index fc8efc84..00000000 --- a/docs/_style/prism-master/tests/languages/icon/number_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -0 -.42 -42 -3.14159 -2e8 -3.2E-7 -47E+19 - -2r11 -8R751 -16rbadface -36razerty - ----------------------------------------------------- - -[ - ["number", "0"], - ["number", ".42"], - ["number", "42"], - ["number", "3.14159"], - ["number", "2e8"], - ["number", "3.2E-7"], - ["number", "47E+19"], - - ["number", "2r11"], - ["number", "8R751"], - ["number", "16rbadface"], - ["number", "36razerty"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/operator_feature.test b/docs/_style/prism-master/tests/languages/icon/operator_feature.test deleted file mode 100644 index a2e29ffa..00000000 --- a/docs/_style/prism-master/tests/languages/icon/operator_feature.test +++ /dev/null @@ -1,69 +0,0 @@ -! -% %:= -& &:= -* *:= ** **:= -+ ++ +:= ++:= -- -:= -- --:= -. -/ /:= -:= :=: -< <- <-> <:= -<< <<:= -<<= <<=:= -<= <=:= -= =:= -== ==:= -=== ===:= -> >:= ->= >=:= ->> >>:= ->>= >>=:= -? ?:= -@ @:= -\ -^ ^:= -| || ||:= -||| |||:= -~ ~= ~=:= -~== ~==:= -~=== ~===:= -: +: -: - ----------------------------------------------------- - -[ - ["operator", "!"], - ["operator", "%"], ["operator", "%:="], - ["operator", "&"], ["operator", "&:="], - ["operator", "*"], ["operator", "*:="], ["operator", "**"], ["operator", "**:="], - ["operator", "+"], ["operator", "++"], ["operator", "+:="], ["operator", "++:="], - ["operator", "-"], ["operator", "-:="], ["operator", "--"], ["operator", "--:="], - ["operator", "."], - ["operator", "/"], ["operator", "/:="], - ["operator", ":="], ["operator", ":=:"], - ["operator", "<"], ["operator", "<-"], ["operator", "<->"], ["operator", "<:="], - ["operator", "<<"], ["operator", "<<:="], - ["operator", "<<="], ["operator", "<<=:="], - ["operator", "<="], ["operator", "<=:="], - ["operator", "="], ["operator", "=:="], - ["operator", "=="], ["operator", "==:="], - ["operator", "==="], ["operator", "===:="], - ["operator", ">"], ["operator", ">:="], - ["operator", ">="], ["operator", ">=:="], - ["operator", ">>"], ["operator", ">>:="], - ["operator", ">>="], ["operator", ">>=:="], - ["operator", "?"], ["operator", "?:="], - ["operator", "@"], ["operator", "@:="], - ["operator", "\\"], - ["operator", "^"], ["operator", "^:="], - ["operator", "|"], ["operator", "||"], ["operator", "||:="], - ["operator", "|||"], ["operator", "|||:="], - ["operator", "~"], ["operator", "~="], ["operator", "~=:="], - ["operator", "~=="], ["operator", "~==:="], - ["operator", "~==="], ["operator", "~===:="], - ["operator", ":"], ["operator", "+:"], ["operator", "-:"] -] - ----------------------------------------------------- - -Checks for operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/icon/string_feature.test b/docs/_style/prism-master/tests/languages/icon/string_feature.test deleted file mode 100644 index 78bc0349..00000000 --- a/docs/_style/prism-master/tests/languages/icon/string_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -"" -"Fo\"obar" -"Foo_ -bar_ -baz" - -'' -'a\'zerty' - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"Fo\\\"obar\""], - ["string", "\"Foo_\r\nbar_\r\nbaz\""], - ["string", "''"], - ["string", "'a\\'zerty'"] -] - ----------------------------------------------------- - -Checks for strings and csets. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/comment_feature.test b/docs/_style/prism-master/tests/languages/inform7/comment_feature.test deleted file mode 100644 index f0aedfe0..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/comment_feature.test +++ /dev/null @@ -1,15 +0,0 @@ -[Foobar] -[Foo -bar -baz] - ----------------------------------------------------- - -[ - ["comment", "[Foobar]"], - ["comment", "[Foo\r\nbar\r\nbaz]"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/keyword_feature.test b/docs/_style/prism-master/tests/languages/inform7/keyword_feature.test deleted file mode 100644 index b2497b4a..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/keyword_feature.test +++ /dev/null @@ -1,85 +0,0 @@ -after -before -carry out -check -continue the action -definition : -do nothing -else -end if -end unless -end the story -every turn -if -include -instead -instead of -let -move -no -now -otherwise -repeat -report -resume the story -rule for -running through -say -saying -stop the action -test -try -trying -understand -unless -use -when -while -yes - ----------------------------------------------------- - -[ - ["keyword", "after"], - ["keyword", "before"], - ["keyword", "carry out"], - ["keyword", "check"], - ["keyword", "continue the action"], - ["keyword", "definition"], ["punctuation", ":"], - ["keyword", "do nothing"], - ["keyword", "else"], - ["keyword", "end if"], - ["keyword", "end unless"], - ["keyword", "end the story"], - ["keyword", "every turn"], - ["keyword", "if"], - ["keyword", "include"], - ["keyword", "instead"], - ["keyword", "instead of"], - ["keyword", "let"], - ["keyword", "move"], - ["keyword", "no"], - ["keyword", "now"], - ["keyword", "otherwise"], - ["keyword", "repeat"], - ["keyword", "report"], - ["keyword", "resume the story"], - ["keyword", "rule for"], - ["keyword", "running through"], - ["keyword", "say"], - ["keyword", "saying"], - ["keyword", "stop the action"], - ["keyword", "test"], - ["keyword", "try"], - ["keyword", "trying"], - ["keyword", "understand"], - ["keyword", "unless"], - ["keyword", "use"], - ["keyword", "when"], - ["keyword", "while"], - ["keyword", "yes"] -] - ----------------------------------------------------- - -Checks for keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/number_feature.test b/docs/_style/prism-master/tests/languages/inform7/number_feature.test deleted file mode 100644 index cb5ca7ff..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/number_feature.test +++ /dev/null @@ -1,25 +0,0 @@ -42 -3.14159 -50kg -100m -one two three -four five six -seven eight nine -ten eleven twelve - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "50kg"], - ["number", "100m"], - ["number", "one"], ["number", "two"], ["number", "three"], - ["number", "four"], ["number", "five"], ["number", "six"], - ["number", "seven"], ["number", "eight"], ["number", "nine"], - ["number", "ten"], ["number", "eleven"], ["number", "twelve"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/position_feature.test b/docs/_style/prism-master/tests/languages/inform7/position_feature.test deleted file mode 100644 index a2982179..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/position_feature.test +++ /dev/null @@ -1,73 +0,0 @@ -above -adjacent to -back side of -below -between -down -east -everywhere -front side -here -in -inside -inside from -north -northeast -northwest -nowhere -on -on top of -other side -outside -outside from -part of -parts of -regionally in -south -southeast -southwest -through -up -west -within - ----------------------------------------------------- - -[ - ["position", "above"], - ["position", "adjacent to"], - ["position", "back side of"], - ["position", "below"], - ["position", "between"], - ["position", "down"], - ["position", "east"], - ["position", "everywhere"], - ["position", "front side"], - ["position", "here"], - ["position", "in"], - ["position", "inside"], - ["position", "inside from"], - ["position", "north"], - ["position", "northeast"], - ["position", "northwest"], - ["position", "nowhere"], - ["position", "on"], - ["position", "on top of"], - ["position", "other side"], - ["position", "outside"], - ["position", "outside from"], - ["position", "part of"], - ["position", "parts of"], - ["position", "regionally in"], - ["position", "south"], - ["position", "southeast"], - ["position", "southwest"], - ["position", "through"], - ["position", "up"], - ["position", "west"], - ["position", "within"] -] - ----------------------------------------------------- - -Checks for positions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/property_feature.test b/docs/_style/prism-master/tests/languages/inform7/property_feature.test deleted file mode 100644 index 4954b9e9..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/property_feature.test +++ /dev/null @@ -1,157 +0,0 @@ -adjacent -carried -closed -concealed -contained -dark -described -edible -empty -enclosed -enterable -even -female -fixed in place -full -handled -held -improper-named -incorporated -inedible -invisible -lighted -lit -lockable -locked -male -marked for listing -mentioned -negative -neuter -non-empty -non-full -non-recurring -odd -opaque -open -openable -plural-named -portable -positive -privately-named -proper-named -provided -publically-named -pushable between rooms -recurring -related -rubbing -scenery -seen -singular-named -supported -swinging -switchable -switched -switched on -switched off -touchable -touched -transparent -unconcealed -undescribed -unlit -unlocked -unmarked for listing -unmentioned -unopenable -untouchable -unvisited -variable -visible -visited -wearable -worn - ----------------------------------------------------- - -[ - ["property", "adjacent"], - ["property", "carried"], - ["property", "closed"], - ["property", "concealed"], - ["property", "contained"], - ["property", "dark"], - ["property", "described"], - ["property", "edible"], - ["property", "empty"], - ["property", "enclosed"], - ["property", "enterable"], - ["property", "even"], - ["property", "female"], - ["property", "fixed in place"], - ["property", "full"], - ["property", "handled"], - ["property", "held"], - ["property", "improper-named"], - ["property", "incorporated"], - ["property", "inedible"], - ["property", "invisible"], - ["property", "lighted"], - ["property", "lit"], - ["property", "lockable"], - ["property", "locked"], - ["property", "male"], - ["property", "marked for listing"], - ["property", "mentioned"], - ["property", "negative"], - ["property", "neuter"], - ["property", "non-empty"], - ["property", "non-full"], - ["property", "non-recurring"], - ["property", "odd"], - ["property", "opaque"], - ["property", "open"], - ["property", "openable"], - ["property", "plural-named"], - ["property", "portable"], - ["property", "positive"], - ["property", "privately-named"], - ["property", "proper-named"], - ["property", "provided"], - ["property", "publically-named"], - ["property", "pushable between rooms"], - ["property", "recurring"], - ["property", "related"], - ["property", "rubbing"], - ["property", "scenery"], - ["property", "seen"], - ["property", "singular-named"], - ["property", "supported"], - ["property", "swinging"], - ["property", "switchable"], - ["property", "switched"], - ["property", "switched on"], - ["property", "switched off"], - ["property", "touchable"], - ["property", "touched"], - ["property", "transparent"], - ["property", "unconcealed"], - ["property", "undescribed"], - ["property", "unlit"], - ["property", "unlocked"], - ["property", "unmarked for listing"], - ["property", "unmentioned"], - ["property", "unopenable"], - ["property", "untouchable"], - ["property", "unvisited"], - ["property", "variable"], - ["property", "visible"], - ["property", "visited"], - ["property", "wearable"], - ["property", "worn"] -] - ----------------------------------------------------- - -Checks for properties. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/string_feature.test b/docs/_style/prism-master/tests/languages/inform7/string_feature.test deleted file mode 100644 index 56a880e1..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/string_feature.test +++ /dev/null @@ -1,49 +0,0 @@ -"" -"foo" -"foo -bar" -"[if the player is in Center Ring]A magician's booth stands in the corner, painted dark blue with glittering gold stars.[otherwise if the magician's booth is closed]A crack of light indicates the way back out to the center ring.[otherwise]The door stands open to the outside.[end if]" - ----------------------------------------------------- - -[ - ["string", ["\"\""]], - ["string", ["\"foo\""]], - ["string", ["\"foo\r\nbar\""]], - ["string", [ - "\"", - ["substitution", [ - ["delimiter", "["], - ["keyword", "if"], ["text", "the"], - ["type", "player"], ["verb", "is"], - ["position", "in"], ["text", "Center Ring"], - ["delimiter", "]"] - ]], - "A magician's booth stands in the corner, painted dark blue with glittering gold stars.", - ["substitution", [ - ["delimiter", "["], - ["keyword", "otherwise"], ["keyword", "if"], - ["text", "the magician's booth"], - ["verb", "is"], - ["property", "closed"], - ["delimiter", "]"] - ]], - "A crack of light indicates the way back out to the center ring.", - ["substitution", [ - ["delimiter", "["], - ["keyword", "otherwise"], - ["delimiter", "]"] - ]], - "The door stands open to the outside.", - ["substitution", [ - ["delimiter", "["], - ["keyword", "end if"], - ["delimiter", "]"] - ]], - "\"" - ]] -] - ----------------------------------------------------- - -Checks for strings and text substitution. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/title_feature.test b/docs/_style/prism-master/tests/languages/inform7/title_feature.test deleted file mode 100644 index ec3cdbfb..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/title_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -Volume 1 - Foobar -Book 2 - Foobar -Part 3 - Foobar -Chapter 4 - Foobar -Section 5 - Foobar -Table 6 - Foobar - ----------------------------------------------------- - -[ - ["title", "Volume 1 - Foobar"], - ["title", "Book 2 - Foobar"], - ["title", "Part 3 - Foobar"], - ["title", "Chapter 4 - Foobar"], - ["title", "Section 5 - Foobar"], - ["title", "Table 6 - Foobar"] -] - ----------------------------------------------------- - -Checks for titles. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/variable_feature.test b/docs/_style/prism-master/tests/languages/inform7/variable_feature.test deleted file mode 100644 index 25c2f740..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/variable_feature.test +++ /dev/null @@ -1,141 +0,0 @@ -action -actions -activity -activities -actor -actors -animal -animals -backdrop -backdrops -container -containers -device -devices -direction -directions -door -doors -holder -holders -kind -kinds -list -lists -man -men -nobody -nothing -noun -nouns -number -numbers -object -objects -people -person -persons -player -player's holdall -region -regions -relation -relations -room -rooms -rule -rules -rulebook -rulebooks -scene -scenes -someone -something -supporter -supporters -table -tables -text -texts -thing -things -time -vehicle -vehicles -woman -women - ----------------------------------------------------- - -[ - ["type", "action"], - ["type", "actions"], - ["type", "activity"], - ["type", "activities"], - ["type", "actor"], - ["type", "actors"], - ["type", "animal"], - ["type", "animals"], - ["type", "backdrop"], - ["type", "backdrops"], - ["type", "container"], - ["type", "containers"], - ["type", "device"], - ["type", "devices"], - ["type", "direction"], - ["type", "directions"], - ["type", "door"], - ["type", "doors"], - ["type", "holder"], - ["type", "holders"], - ["type", "kind"], - ["type", "kinds"], - ["type", "list"], - ["type", "lists"], - ["type", "man"], - ["type", "men"], - ["type", "nobody"], - ["type", "nothing"], - ["type", "noun"], - ["type", "nouns"], - ["type", "number"], - ["type", "numbers"], - ["type", "object"], - ["type", "objects"], - ["type", "people"], - ["type", "person"], - ["type", "persons"], - ["type", "player"], - ["type", "player's holdall"], - ["type", "region"], - ["type", "regions"], - ["type", "relation"], - ["type", "relations"], - ["type", "room"], - ["type", "rooms"], - ["type", "rule"], - ["type", "rules"], - ["type", "rulebook"], - ["type", "rulebooks"], - ["type", "scene"], - ["type", "scenes"], - ["type", "someone"], - ["type", "something"], - ["type", "supporter"], - ["type", "supporters"], - ["type", "table"], - ["type", "tables"], - ["type", "text"], - ["type", "texts"], - ["type", "thing"], - ["type", "things"], - ["type", "time"], - ["type", "vehicle"], - ["type", "vehicles"], - ["type", "woman"], - ["type", "women"] -] - ----------------------------------------------------- - -Checks for variables. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/inform7/verb_feature.test b/docs/_style/prism-master/tests/languages/inform7/verb_feature.test deleted file mode 100644 index 3da285d3..00000000 --- a/docs/_style/prism-master/tests/languages/inform7/verb_feature.test +++ /dev/null @@ -1,213 +0,0 @@ -applying to -are -attacking -answering -asking -be -being -burning -buying -called -carries -carry -carrying -climbing -closing -conceal -conceals -concealing -consulting -contain -contains -containing -cutting -drinking -dropping -eating -enclose -encloses -enclosing -entering -examining -exiting -getting -giving -going -have -has -having -hold -holds -holding -imply -implies -incorporate -incorporates -incorporating -inserting -is -jumping -kissing -listening -locking -looking -mean -means -meaning -opening -provide -provides -providing -pulling -pushing -putting -relate -relates -relating -removing -searching -see -sees -seeing -setting -showing -singing -sleeping -smelling -squeezing -switching -support -supports -supporting -swearing -taking -tasting -telling -thinking -throwing -touching -turning -tying -unlock -unlocks -unlocking -vary -varies -varying -waiting -waking -waving -wear -wears -wearing - ----------------------------------------------------- - -[ - ["verb", "applying to"], - ["verb", "are"], - ["verb", "attacking"], - ["verb", "answering"], - ["verb", "asking"], - ["verb", "be"], - ["verb", "being"], - ["verb", "burning"], - ["verb", "buying"], - ["verb", "called"], - ["verb", "carries"], - ["verb", "carry"], - ["verb", "carrying"], - ["verb", "climbing"], - ["verb", "closing"], - ["verb", "conceal"], - ["verb", "conceals"], - ["verb", "concealing"], - ["verb", "consulting"], - ["verb", "contain"], - ["verb", "contains"], - ["verb", "containing"], - ["verb", "cutting"], - ["verb", "drinking"], - ["verb", "dropping"], - ["verb", "eating"], - ["verb", "enclose"], - ["verb", "encloses"], - ["verb", "enclosing"], - ["verb", "entering"], - ["verb", "examining"], - ["verb", "exiting"], - ["verb", "getting"], - ["verb", "giving"], - ["verb", "going"], - ["verb", "have"], - ["verb", "has"], - ["verb", "having"], - ["verb", "hold"], - ["verb", "holds"], - ["verb", "holding"], - ["verb", "imply"], - ["verb", "implies"], - ["verb", "incorporate"], - ["verb", "incorporates"], - ["verb", "incorporating"], - ["verb", "inserting"], - ["verb", "is"], - ["verb", "jumping"], - ["verb", "kissing"], - ["verb", "listening"], - ["verb", "locking"], - ["verb", "looking"], - ["verb", "mean"], - ["verb", "means"], - ["verb", "meaning"], - ["verb", "opening"], - ["verb", "provide"], - ["verb", "provides"], - ["verb", "providing"], - ["verb", "pulling"], - ["verb", "pushing"], - ["verb", "putting"], - ["verb", "relate"], - ["verb", "relates"], - ["verb", "relating"], - ["verb", "removing"], - ["verb", "searching"], - ["verb", "see"], - ["verb", "sees"], - ["verb", "seeing"], - ["verb", "setting"], - ["verb", "showing"], - ["verb", "singing"], - ["verb", "sleeping"], - ["verb", "smelling"], - ["verb", "squeezing"], - ["verb", "switching"], - ["verb", "support"], - ["verb", "supports"], - ["verb", "supporting"], - ["verb", "swearing"], - ["verb", "taking"], - ["verb", "tasting"], - ["verb", "telling"], - ["verb", "thinking"], - ["verb", "throwing"], - ["verb", "touching"], - ["verb", "turning"], - ["verb", "tying"], - ["verb", "unlock"], - ["verb", "unlocks"], - ["verb", "unlocking"], - ["verb", "vary"], - ["verb", "varies"], - ["verb", "varying"], - ["verb", "waiting"], - ["verb", "waking"], - ["verb", "waving"], - ["verb", "wear"], - ["verb", "wears"], - ["verb", "wearing"] -] - ----------------------------------------------------- - -Checks for verbs. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ini/comment_feature.test b/docs/_style/prism-master/tests/languages/ini/comment_feature.test deleted file mode 100644 index 85faefe7..00000000 --- a/docs/_style/prism-master/tests/languages/ini/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -; -; foobar - ----------------------------------------------------- - -[ - ["comment", ";"], - ["comment", "; foobar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ini/key_value_feature.test b/docs/_style/prism-master/tests/languages/ini/key_value_feature.test deleted file mode 100644 index 5d25d9d3..00000000 --- a/docs/_style/prism-master/tests/languages/ini/key_value_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -foo=Bar Baz -foobar=42 - ----------------------------------------------------- - -[ - ["constant", "foo"], - ["attr-value", [ - ["punctuation", "="], - "Bar Baz" - ]], - ["constant", "foobar"], - ["attr-value", [ - ["punctuation", "="], - "42" - ]] -] - ----------------------------------------------------- - -Checks for key/value pairs. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/ini/selector_feature.test b/docs/_style/prism-master/tests/languages/ini/selector_feature.test deleted file mode 100644 index 3158a665..00000000 --- a/docs/_style/prism-master/tests/languages/ini/selector_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -[owner] -[foobar] - ----------------------------------------------------- - -[ - ["selector", "[owner]"], - ["selector", "[foobar]"] -] - ----------------------------------------------------- - -Checks for section titles. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/io/comment_feature.test b/docs/_style/prism-master/tests/languages/io/comment_feature.test deleted file mode 100644 index fb67d07b..00000000 --- a/docs/_style/prism-master/tests/languages/io/comment_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -// -// Foobar -#!/usr/bin/env io -/* multiline -comment -*/ - ----------------------------------------------------- - -[ - ["comment", "//"], - ["comment", "// Foobar"], - ["comment", "#!/usr/bin/env io"], - ["comment", "/* multiline\ncomment\n*/"] -] - ----------------------------------------------------- - -Checks for comments. diff --git a/docs/_style/prism-master/tests/languages/io/number_feature.test b/docs/_style/prism-master/tests/languages/io/number_feature.test deleted file mode 100644 index 371be063..00000000 --- a/docs/_style/prism-master/tests/languages/io/number_feature.test +++ /dev/null @@ -1,23 +0,0 @@ -123 -123.456 -0.456 -123e-4 -123e4 -123.456e-7 -123.456e2 - ------------------------------- - -[ - ["number", "123"], - ["number", "123.456"], - ["number", "0.456"], - ["number", "123e-4"], - ["number", "123e4"], - ["number", "123.456e-7"], - ["number", "123.456e2"] -] - ------------------------------- - -Check numbers. diff --git a/docs/_style/prism-master/tests/languages/io/operator_feature.test b/docs/_style/prism-master/tests/languages/io/operator_feature.test deleted file mode 100644 index 5378bc52..00000000 --- a/docs/_style/prism-master/tests/languages/io/operator_feature.test +++ /dev/null @@ -1,26 +0,0 @@ -::= := = -== != >= <= -&& and || or not -.. -+ - / * ** -%= &= *= += -= /= <<= >>= ^= |= -? ?? @ @@ -return - -------------------------------------------------------------------------------------------------------------------------- - -[ - ["operator", "::=" ] , ["operator", ":=" ] , ["operator", "=" ] , - ["operator", "==" ] , ["operator", "!=" ] , ["operator", ">=" ] , ["operator", "<=" ] , - ["operator", "&&" ] , ["operator", "and" ] , ["operator", "||" ] , ["operator", "or" ] , ["operator", "not" ] , - ["operator", ".." ] , - ["operator", "+" ] , ["operator", "-" ] , ["operator", "/" ] , ["operator", "*" ] , ["operator", "**" ] , - ["operator", "%=" ] , ["operator", "&=" ] , ["operator", "*=" ] , ["operator", "+=" ] , ["operator", "-=" ] , - ["operator", "/=" ] , ["operator", "<<=" ] , ["operator", ">>=" ] , ["operator", "^=" ] , ["operator", "|=" ] , - ["operator", "?" ] , ["operator", "??" ] , ["operator", "@" ] , ["operator", "@@" ] , - ["operator", "return" ] -] - -------------------------------------------------------------------------------------------------------------------------- - -Check operators. diff --git a/docs/_style/prism-master/tests/languages/io/string_feature.test b/docs/_style/prism-master/tests/languages/io/string_feature.test deleted file mode 100644 index 5b4f003e..00000000 --- a/docs/_style/prism-master/tests/languages/io/string_feature.test +++ /dev/null @@ -1,18 +0,0 @@ -"" -"""""" -"this is a \"test\".\nThis is only a test." -"""this is a "test". -This is only a test.""" - -------------------------------------------------------------------------- - -[ - ["string", "\"\""], - ["triple-quoted-string", "\"\"\"\"\"\""], - ["string", "\"this is a \\\"test\\\".\\nThis is only a test.\""], - ["triple-quoted-string", "\"\"\"this is a \"test\".\nThis is only a test.\"\"\""] -] - -------------------------------------------------------------------------- - -Check strings. diff --git a/docs/_style/prism-master/tests/languages/j/adverb_feature.test b/docs/_style/prism-master/tests/languages/j/adverb_feature.test deleted file mode 100644 index 2374d72f..00000000 --- a/docs/_style/prism-master/tests/languages/j/adverb_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -~ } -/ /. -\ \. -b. f. M. -t. t: - ----------------------------------------------------- - -[ - ["adverb", "~"], ["adverb", "}"], - ["adverb", "/"], ["adverb", "/."], - ["adverb", "\\"], ["adverb", "\\."], - ["adverb", "b."], ["adverb", "f."], ["adverb", "M."], - ["adverb", "t."], ["adverb", "t:"] -] - ----------------------------------------------------- - -Checks for adverbs. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/comment_feature.test b/docs/_style/prism-master/tests/languages/j/comment_feature.test deleted file mode 100644 index c816bf19..00000000 --- a/docs/_style/prism-master/tests/languages/j/comment_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -NB. -NB. Foo bar - ----------------------------------------------------- - -[ - ["comment", "NB."], - ["comment", "NB. Foo bar"] -] - ----------------------------------------------------- - -Checks for comments. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/conjunction_feature.test b/docs/_style/prism-master/tests/languages/j/conjunction_feature.test deleted file mode 100644 index 0fcc2e89..00000000 --- a/docs/_style/prism-master/tests/languages/j/conjunction_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -& &. &.: &: - -. .. .: -: :. :: -@ @. @: - -!. !: -D. D: - -;. d. H. T. - -` `: - -^: L: S: - -" - ----------------------------------------------------- - -[ - ["conjunction", "&"], ["conjunction", "&."], ["conjunction", "&.:"], ["conjunction", "&:"], - - ["conjunction", "."], ["conjunction", ".."], ["conjunction", ".:"], - ["conjunction", ":"], ["conjunction", ":."], ["conjunction", "::"], - ["conjunction", "@"], ["conjunction", "@."], ["conjunction", "@:"], - - ["conjunction", "!."], ["conjunction", "!:"], - ["conjunction", "D."], ["conjunction", "D:"], - - ["conjunction", ";."], ["conjunction", "d."], ["conjunction", "H."], ["conjunction", "T."], - - ["conjunction", "`"], ["conjunction", "`:"], - - ["conjunction", "^:"], ["conjunction", "L:"], ["conjunction", "S:"], - - ["conjunction", "\""] -] - ----------------------------------------------------- - -Checks for conjunctions. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/keyword_feature.test b/docs/_style/prism-master/tests/languages/j/keyword_feature.test deleted file mode 100644 index 38f982d8..00000000 --- a/docs/_style/prism-master/tests/languages/j/keyword_feature.test +++ /dev/null @@ -1,77 +0,0 @@ -adverb -conjunction -CR -def -define -dyad -LF -monad -noun -verb - -assert. -break. -case. -catch. -catchd. -catcht. -continue. -do. -else. -elseif. -end. -fcase. -for. -for_foobar. -goto_foobar. -if. -label_foobar. -return. -select. -throw. -try. -while. -whilst. - ----------------------------------------------------- - -[ - ["keyword", "adverb"], - ["keyword", "conjunction"], - ["keyword", "CR"], - ["keyword", "def"], - ["keyword", "define"], - ["keyword", "dyad"], - ["keyword", "LF"], - ["keyword", "monad"], - ["keyword", "noun"], - ["keyword", "verb"], - - ["keyword", "assert."], - ["keyword", "break."], - ["keyword", "case."], - ["keyword", "catch."], - ["keyword", "catchd."], - ["keyword", "catcht."], - ["keyword", "continue."], - ["keyword", "do."], - ["keyword", "else."], - ["keyword", "elseif."], - ["keyword", "end."], - ["keyword", "fcase."], - ["keyword", "for."], - ["keyword", "for_foobar."], - ["keyword", "goto_foobar."], - ["keyword", "if."], - ["keyword", "label_foobar."], - ["keyword", "return."], - ["keyword", "select."], - ["keyword", "throw."], - ["keyword", "try."], - ["keyword", "while."], - ["keyword", "whilst."] -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/number_feature.test b/docs/_style/prism-master/tests/languages/j/number_feature.test deleted file mode 100644 index ecd2dc2a..00000000 --- a/docs/_style/prism-master/tests/languages/j/number_feature.test +++ /dev/null @@ -1,41 +0,0 @@ -2.3e2 2.3e_2 2j3 -230 0.023 2j3 - -2p1 1p_1 -6.28319 0.31831 - -1x2 2x1 1x_1 -7.38906 5.43656 0.367879 - -2e2j_2e2 2e2j2p1 2ad45 2ar0.785398 -200j_200 628.319j6.28319 1.41421j1.41421 1.41421j1.41421 - -16b1f 10b23 _10b23 1e2b23 2b111.111 -31 23 _17 203 7.875 - -_ __ - ----------------------------------------------------- - -[ - ["number", "2.3e2"], ["number", "2.3e_2"], ["number", "2j3"], - ["number", "230"], ["number", "0.023"], ["number", "2j3"], - - ["number", "2p1"], ["number", "1p_1"], - ["number", "6.28319"], ["number", "0.31831"], - - ["number", "1x2"], ["number", "2x1"], ["number", "1x_1"], - ["number", "7.38906"], ["number", "5.43656"], ["number", "0.367879"], - - ["number", "2e2j_2e2"], ["number", "2e2j2p1"], ["number", "2ad45"], ["number", "2ar0.785398"], - ["number", "200j_200"], ["number", "628.319j6.28319"], ["number", "1.41421j1.41421"], ["number", "1.41421j1.41421"], - - ["number", "16b1f"], ["number", "10b23"], ["number", "_10b23"], ["number", "1e2b23"], ["number", "2b111.111"], - ["number", "31"], ["number", "23"], ["number", "_17"], ["number", "203"], ["number", "7.875"], - - ["number", "_"], ["number", "__"] -] - ----------------------------------------------------- - -Checks for numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/string_feature.test b/docs/_style/prism-master/tests/languages/j/string_feature.test deleted file mode 100644 index 8cfa97fc..00000000 --- a/docs/_style/prism-master/tests/languages/j/string_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -'' -'fo''obar' - ----------------------------------------------------- - -[ - ["string", "''"], - ["string", "'fo''obar'"] -] - ----------------------------------------------------- - -Checks for strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/j/verb_feature.test b/docs/_style/prism-master/tests/languages/j/verb_feature.test deleted file mode 100644 index 55f90866..00000000 --- a/docs/_style/prism-master/tests/languages/j/verb_feature.test +++ /dev/null @@ -1,93 +0,0 @@ -{ {. {: {:: - -p. p.. p: - -= -! -] - -< <. <: -> >. >: -+ +. +: -* *. *: -- -. -: -% %. %: -$ $. $: -| |. |: -, ,. ,: -# #. #: - -^ ^. -? ?. - -; ;: -[ [: - -~. ~: -}. }: -". ": -i. i: - -A. C. e. -E. I. j. -L. o. r. - -_: /: \: -q: s: u: x: -_9: _8: _7: -_6: _5: _4: -_3: _2: _1: -1: 2: 3: -4: 5: 6: -7: 8: 9: - ----------------------------------------------------- - -[ - ["verb", "{"], ["verb", "{."], ["verb", "{:"], ["verb", "{::"], - - ["verb", "p."], ["verb", "p.."], ["verb", "p:"], - - ["verb", "="], - ["verb", "!"], - ["verb", "]"], - - ["verb", "<"], ["verb", "<."], ["verb", "<:"], - ["verb", ">"], ["verb", ">."], ["verb", ">:"], - ["verb", "+"], ["verb", "+."], ["verb", "+:"], - ["verb", "*"], ["verb", "*."], ["verb", "*:"], - ["verb", "-"], ["verb", "-."], ["verb", "-:"], - ["verb", "%"], ["verb", "%."], ["verb", "%:"], - ["verb", "$"], ["verb", "$."], ["verb", "$:"], - ["verb", "|"], ["verb", "|."], ["verb", "|:"], - ["verb", ","], ["verb", ",."], ["verb", ",:"], - ["verb", "#"], ["verb", "#."], ["verb", "#:"], - - ["verb", "^"], ["verb", "^."], - ["verb", "?"], ["verb", "?."], - - ["verb", ";"], ["verb", ";:"], - ["verb", "["], ["verb", "[:"], - - ["verb", "~."], ["verb", "~:"], - ["verb", "}."], ["verb", "}:"], - ["verb", "\"."], ["verb", "\":"], - ["verb", "i."], ["verb", "i:"], - - ["verb", "A."], ["verb", "C."], ["verb", "e."], - ["verb", "E."], ["verb", "I."], ["verb", "j."], - ["verb", "L."], ["verb", "o."], ["verb", "r."], - - ["verb", "_:"], ["verb", "/:"], ["verb", "\\:"], - ["verb", "q:"], ["verb", "s:"], ["verb", "u:"], ["verb", "x:"], - ["verb", "_9:"], ["verb", "_8:"], ["verb", "_7:"], - ["verb", "_6:"], ["verb", "_5:"], ["verb", "_4:"], - ["verb", "_3:"], ["verb", "_2:"], ["verb", "_1:"], - ["verb", "1:"], ["verb", "2:"], ["verb", "3:"], - ["verb", "4:"], ["verb", "5:"], ["verb", "6:"], - ["verb", "7:"], ["verb", "8:"], ["verb", "9:"] -] - ----------------------------------------------------- - -Checks for verbs. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/java/function_featrue.test b/docs/_style/prism-master/tests/languages/java/function_featrue.test deleted file mode 100644 index 2ca6169f..00000000 --- a/docs/_style/prism-master/tests/languages/java/function_featrue.test +++ /dev/null @@ -1,32 +0,0 @@ -void foo(int a) {} -foo(0); -Bar::foo; - ----------------------------------------------------- - -[ - ["keyword", "void"], - ["function", "foo"], - ["punctuation", "("], - ["keyword", "int"], - " a", - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["function", "foo"], - ["punctuation", "("], - ["number", "0"], - ["punctuation", ")"], - ["punctuation", ";"], - - ["class-name", "Bar"], - ["operator", ":"], - ["operator", ":"], - ["function", "foo"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for functions. diff --git a/docs/_style/prism-master/tests/languages/java/generics_feature.test b/docs/_style/prism-master/tests/languages/java/generics_feature.test deleted file mode 100644 index d1d5e536..00000000 --- a/docs/_style/prism-master/tests/languages/java/generics_feature.test +++ /dev/null @@ -1,67 +0,0 @@ -public class Solo {} -Solo val = new Solo(); -Duo dual = new Duo(12.2585, 'C'); - ----------------------------------------------------- - -[ - ["keyword", "public"], - ["keyword", "class"], - ["class-name", "Solo"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "T"], - ["punctuation", ">"] - ]], - ["punctuation", "{"], - ["punctuation", "}"], - - ["class-name", "Solo"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "Integer"], - ["punctuation", ">"] - ]], - " val ", - ["operator", "="], - ["keyword", "new"], - ["class-name", "Solo"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "Integer"], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ";"], - - ["class-name", "Duo"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "Double"], - ["punctuation", ","], - ["class-name", "Character"], - ["punctuation", ">"] - ]], - " dual ", - ["operator", "="], - ["keyword", "new"], - ["class-name", "Duo"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "Double"], - ["punctuation", ","], - ["class-name", "Character"], - ["punctuation", ">"] - ]], - ["punctuation", "("], - ["number", "12.2585"], - ["punctuation", ","], - ["string", "'C'"], - ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for generics. diff --git a/docs/_style/prism-master/tests/languages/java/issue1351.test b/docs/_style/prism-master/tests/languages/java/issue1351.test deleted file mode 100644 index 3034e3e8..00000000 --- a/docs/_style/prism-master/tests/languages/java/issue1351.test +++ /dev/null @@ -1,27 +0,0 @@ -public class AllChangesIndexer extends SiteIndexer { - ----------------------------------------------------- - -[ - ["keyword", "public"], - ["keyword", "class"], - ["class-name", "AllChangesIndexer"], - ["keyword", "extends"], - ["class-name", "SiteIndexer"], - ["generics", [ - ["punctuation", "<"], - ["class-name", "Change"], - ["punctuation", "."], - ["class-name", "Id"], - ["punctuation", ","], - ["class-name", "ChangeData"], - ["punctuation", ","], - ["class-name", "ChangeIndex"], - ["punctuation", ">"] - ]], - ["punctuation", "{"] -] - ----------------------------------------------------- - -Checks for generics. See #1351 diff --git a/docs/_style/prism-master/tests/languages/java/keyword_feature.test b/docs/_style/prism-master/tests/languages/java/keyword_feature.test deleted file mode 100644 index 5e5604d1..00000000 --- a/docs/_style/prism-master/tests/languages/java/keyword_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -abstract continue for -new -switch assert default -goto package synchronized -boolean do if private -this break double -implements -protected throw byte else -import public throws case -enum -instanceof -return transient catch -extends -int short try char -final -interface -static void -class -finally long -strictfp volatile const -float native super while -var null -module requires transitive -exports uses open -opens with to provides - ----------------------------------------------------- - -[ - ["keyword", "abstract"], ["keyword", "continue"], ["keyword", "for"], - ["keyword", "new"], - ["keyword", "switch"], ["keyword", "assert"], ["keyword", "default"], - ["keyword", "goto"], ["keyword", "package"], ["keyword", "synchronized"], - ["keyword", "boolean"], ["keyword", "do"], ["keyword", "if"], ["keyword", "private"], - ["keyword", "this"], ["keyword", "break"], ["keyword", "double"], - ["keyword", "implements"], - ["keyword", "protected"], ["keyword", "throw"], ["keyword", "byte"], ["keyword", "else"], - ["keyword", "import"], ["keyword", "public"], ["keyword", "throws"], ["keyword", "case"], - ["keyword", "enum"], - ["keyword", "instanceof"], - ["keyword", "return"], ["keyword", "transient"], ["keyword", "catch"], - ["keyword", "extends"], - ["keyword", "int"], ["keyword", "short"], ["keyword", "try"], ["keyword", "char"], - ["keyword", "final"], - ["keyword", "interface"], - ["keyword", "static"], ["keyword", "void"], - ["keyword", "class"], - ["keyword", "finally"], ["keyword", "long"], - ["keyword", "strictfp"], ["keyword", "volatile"], ["keyword", "const"], - ["keyword", "float"], ["keyword", "native"], ["keyword", "super"], ["keyword", "while"], - ["keyword", "var"], ["keyword", "null"], - ["keyword", "module"], ["keyword", "requires"], ["keyword", "transitive"], - ["keyword", "exports"], ["keyword", "uses"], ["keyword", "open"], - ["keyword", "opens"], ["keyword", "with"], ["keyword", "to"], ["keyword","provides"] -] - ----------------------------------------------------- - -Checks for all keywords. diff --git a/docs/_style/prism-master/tests/languages/java/module_feature.test b/docs/_style/prism-master/tests/languages/java/module_feature.test deleted file mode 100644 index 983f8ece..00000000 --- a/docs/_style/prism-master/tests/languages/java/module_feature.test +++ /dev/null @@ -1,158 +0,0 @@ -module com.js.prism { - exports java.net.http; - exports jdk.internal.editor.spi to jdk.jshell; - - requires java.base; - requires transitive java.xml; - - uses java.net.ContentHandlerFactory; - - opens java.time.DateTime; - opens java.time.LocalDateTime to java.logging; - - provides com.modules.hello.HelloInterface with com.modules.hello.HelloModules; - -} - - ----------------------------------------------------- - [ - ["keyword", "module"], - ["namespace", - ["com", - ["punctuation", "."], - "js", - ["punctuation", "."], - "prism" - ] - ], - ["punctuation", "{"], - - ["keyword", "exports"], - ["namespace", - [ - "java", - ["punctuation", "."], - "net", - ["punctuation", "."], - "http" - ] - ], - ["punctuation", ";"], - - ["keyword", "exports"], - ["namespace", - [ - "jdk", - ["punctuation", "."], - "internal", - ["punctuation", "."], - "editor", - ["punctuation", "."], - "spi" - ] - ], - ["keyword", "to"], - ["namespace", - [ - "jdk", - ["punctuation", "."], - "jshell" - ] - ], - ["punctuation", ";"], - - ["keyword", "requires"], - ["namespace", - [ - "java", - ["punctuation", "."], - "base" - ] - ], - ["punctuation", ";"], - - ["keyword", "requires"], - ["keyword", "transitive"], - ["namespace", - [ - "java", - ["punctuation", "."], - "xml" - ] - ], - ["punctuation", ";"], - - ["keyword", "uses"], - ["namespace", - [ - "java", - ["punctuation", "."], - "net" - ] - ], - ["punctuation", "."], - ["class-name", "ContentHandlerFactory"], - ["punctuation", ";"], - - ["keyword", "opens"], - ["namespace", - [ - "java", - ["punctuation", "."], - "time" - ] - ], - ["punctuation", "."], - ["class-name", "DateTime"], - ["punctuation", ";"], - ["keyword", "opens"], - ["namespace", - [ - "java", - ["punctuation", "."], - "time" - ] - ], - ["punctuation", "."], - ["class-name", "LocalDateTime"], - ["keyword", "to"], - ["namespace", - [ - "java", - ["punctuation", "."], - "logging" - ] - ], - ["punctuation", ";"], - ["keyword", "provides"], - ["namespace", - [ - "com", - ["punctuation", "."], - "modules", - ["punctuation", "."], - "hello" - ] - ], - ["punctuation", "."], - ["class-name", "HelloInterface"], - ["keyword", "with"], - ["namespace", - [ - "com", - ["punctuation", "."], - "modules", - ["punctuation", "."], - "hello" - ] - ], - ["punctuation", "."], - ["class-name", "HelloModules"], - ["punctuation", ";"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for module definition. diff --git a/docs/_style/prism-master/tests/languages/java/number_feature.test b/docs/_style/prism-master/tests/languages/java/number_feature.test deleted file mode 100644 index 014ed37f..00000000 --- a/docs/_style/prism-master/tests/languages/java/number_feature.test +++ /dev/null @@ -1,60 +0,0 @@ -42 -42d -42L - -1.2e3f -0.1E-4f -0.2e+1f - -0xBadFace - -0x1.8p1 -0xa.fp-2 -0xa.fp+2 -0xa.p+3f -0x.fp+3f - -0b11110000 - -1_2_3 -1_2.3_4e-5_6 - -0x1_2 -0x0_1__2_3 - -0b1_1_1_1__0_0_0_0 - - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "42d"], - ["number", "42L"], - - ["number", "1.2e3f"], - ["number", "0.1E-4f"], - ["number", "0.2e+1f"], - - ["number", "0xBadFace"], - - ["number", "0x1.8p1"], - ["number", "0xa.fp-2"], - ["number", "0xa.fp+2"], - ["number", "0xa.p+3f"], - ["number", "0x.fp+3f"], - - ["number", "0b11110000"], - - ["number", "1_2_3"], - ["number", "1_2.3_4e-5_6"], - - ["number", "0x1_2"], - ["number", "0x0_1__2_3"], - - ["number", "0b1_1_1_1__0_0_0_0"] -] - ----------------------------------------------------- - -Checks for binary, hexadecimal and decimal numbers. diff --git a/docs/_style/prism-master/tests/languages/java/operator_feature.test b/docs/_style/prism-master/tests/languages/java/operator_feature.test deleted file mode 100644 index 8c8eb393..00000000 --- a/docs/_style/prism-master/tests/languages/java/operator_feature.test +++ /dev/null @@ -1,37 +0,0 @@ -+ ++ += -- -- -= -! != -< << <= <<= -> >> >>> >= >>= >>>= -= == -& && &= -| || |= -? : ~ -^ ^= -* *= -/ /= -% %= --> - ----------------------------------------------------- - -[ - ["operator", "+"], ["operator", "++"], ["operator", "+="], - ["operator", "-"], ["operator", "--"], ["operator", "-="], - ["operator", "!"], ["operator", "!="], - ["operator", "<"], ["operator", "<<"], ["operator", "<="], ["operator", "<<="], - ["operator", ">"], ["operator", ">>"], ["operator", ">>>"], ["operator", ">="], ["operator", ">>="], ["operator", ">>>="], - ["operator", "="], ["operator", "=="], - ["operator", "&"], ["operator", "&&"], ["operator", "&="], - ["operator", "|"], ["operator", "||"], ["operator", "|="], - ["operator", "?"], ["operator", ":"], ["operator", "~"], - ["operator", "^"], ["operator", "^="], - ["operator", "*"], ["operator", "*="], - ["operator", "/"], ["operator", "/="], - ["operator", "%"], ["operator", "%="], - ["operator", "->"] -] - ----------------------------------------------------- - -Checks for all operators. diff --git a/docs/_style/prism-master/tests/languages/java/package_feature.test b/docs/_style/prism-master/tests/languages/java/package_feature.test deleted file mode 100644 index 9fe6f1f3..00000000 --- a/docs/_style/prism-master/tests/languages/java/package_feature.test +++ /dev/null @@ -1,80 +0,0 @@ -package java.lang; - -import java.lang.Math; -import java.lang.*; - -import static java.lang.Math.PI; -import static java.lang.Math.sin; -import static java.lang.Math.*; - ----------------------------------------------------- - -[ - ["keyword", "package"], - ["namespace", [ - "java", - ["punctuation", "."], - "lang" - ]], - ["punctuation", ";"], - - ["keyword", "import"], - ["namespace", [ - "java", - ["punctuation", "."], - "lang" - ]], - ["punctuation", "."], - ["class-name", "Math"], - ["punctuation", ";"], - - ["keyword", "import"], - ["namespace", [ - "java", - ["punctuation", "."], - "lang" - ]], - ["punctuation", "."], - "*", - ["punctuation", ";"], - - ["keyword", "import"], - ["keyword", "static"], - ["namespace", [ - "java", - ["punctuation", "."], - "lang" - ]], - ["punctuation", "."], - ["class-name", "Math"], - ["punctuation", "."], - "PI", - ["punctuation", ";"], - - ["keyword", "import"], - ["keyword", "static"], - ["namespace", [ - "java", - ["punctuation", "."], - "lang" - ]], - ["punctuation", "."], - ["class-name", "Math"], - ["punctuation", "."], - "sin", - ["punctuation", ";"], - ["keyword", "import"], - ["keyword", "static"], - ["namespace", [ - "java", - ["punctuation", "."], "lang" - ]], ["punctuation", "."], - ["class-name", "Math"], - ["punctuation", "."], - "*", - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for packages. diff --git a/docs/_style/prism-master/tests/languages/javascript+haml/javascript_inclusion.test b/docs/_style/prism-master/tests/languages/javascript+haml/javascript_inclusion.test deleted file mode 100644 index 2420e60f..00000000 --- a/docs/_style/prism-master/tests/languages/javascript+haml/javascript_inclusion.test +++ /dev/null @@ -1,24 +0,0 @@ -:javascript - 0xBadFace - -~ - :javascript - 0xBadFace - ----------------------------------------------------- - -[ - ["filter-javascript", [ - ["filter-name", ":javascript"], - ["number", "0xBadFace"] - ]], - ["punctuation", "~"], - ["filter-javascript", [ - ["filter-name", ":javascript"], - ["number", "0xBadFace"] - ]] -] - ----------------------------------------------------- - -Checks for JavaScript filter in Haml. The tilde serves only as a separator. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript+http/javascript_inclusion.test b/docs/_style/prism-master/tests/languages/javascript+http/javascript_inclusion.test deleted file mode 100644 index f7977c55..00000000 --- a/docs/_style/prism-master/tests/languages/javascript+http/javascript_inclusion.test +++ /dev/null @@ -1,21 +0,0 @@ -Content-type: application/javascript - -var a = true; - ----------------------------------------------------- - -[ - ["header-name", "Content-type:"], - " application/javascript", - ["application/javascript", [ - ["keyword", "var"], - " a ", - ["operator", "="], - ["boolean", "true"], - ["punctuation", ";"] - ]] -] - ----------------------------------------------------- - -Checks for JavaScript content in HTTP. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/boolean_feature.test b/docs/_style/prism-master/tests/languages/javascript/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/class-method_feature.test b/docs/_style/prism-master/tests/languages/javascript/class-method_feature.test deleted file mode 100644 index 303f481f..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/class-method_feature.test +++ /dev/null @@ -1,59 +0,0 @@ -class Test { - foo( x, y = 0) {} - async bar(x, y = 0 ) {} - $ ( ) {} - awaitFoo(){} -} - ----------------------------------------------------- - -[ - ["keyword", "class"], - ["class-name", ["Test"]], - ["punctuation", "{"], - - ["function", "foo"], - ["punctuation", "("], - ["parameter", [ - "x", - ["punctuation", ","], - " y ", - ["operator", "="], - ["number", "0"] - ]], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["keyword", "async"], - ["function", "bar"], - ["punctuation", "("], - ["parameter", [ - "x", - ["punctuation", ","], - " y ", - ["operator", "="], - ["number", "0"] - ]], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["function", "$"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["function", "awaitFoo"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for class methods. diff --git a/docs/_style/prism-master/tests/languages/javascript/constant_feature.test b/docs/_style/prism-master/tests/languages/javascript/constant_feature.test deleted file mode 100644 index e17f2855..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/constant_feature.test +++ /dev/null @@ -1,21 +0,0 @@ -var FOO; -const FOO_BAR; -const BAZ42; - ----------------------------------------------------- - -[ - ["keyword", "var"], - ["constant", "FOO"], - ["punctuation", ";"], - ["keyword", "const"], - ["constant", "FOO_BAR"], - ["punctuation", ";"], - ["keyword", "const"], - ["constant", "BAZ42"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for constants. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/function-variable_feature.test b/docs/_style/prism-master/tests/languages/javascript/function-variable_feature.test deleted file mode 100644 index f3916f71..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/function-variable_feature.test +++ /dev/null @@ -1,104 +0,0 @@ -foo = function ( x, y) {} -{foo: function () {}} -bar = async function baz (x ) {} -baz = async(x) => x -fooBar = x => x -fooBar = ( x, y ) => x -ಠ_ಠ = () => {} -Ƞȡ_҇ = async (ಠ, Ƞ = 2) => {} - ----------------------------------------------------- - -[ - ["function-variable", "foo"], - ["operator", "="], - ["keyword", "function"], - ["punctuation", "("], - ["parameter", [ - "x", - ["punctuation", ","], - " y" - ]], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["punctuation", "{"], - ["function-variable", "foo"], - ["punctuation", ":"], - ["keyword", "function"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation","}"], - ["punctuation","}"], - - ["function-variable", "bar"], - ["operator", "="], - ["keyword", "async"], - ["keyword", "function"], - ["function", "baz"], - ["punctuation", "("], - ["parameter", [ - "x" - ]], - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["function-variable", "baz"], - ["operator", "="], - ["keyword", "async"], - ["punctuation", "("], - ["parameter", [ - "x" - ]], - ["punctuation", ")"], - ["operator", "=>"], " x\r\n", - - ["function-variable", "fooBar"], - ["operator", "="], - ["parameter", [ - "x" - ]], - ["operator", "=>"], " x\r\n", - - ["function-variable", "fooBar"], - ["operator", "="], - ["punctuation", "("], - ["parameter", [ - "x", - ["punctuation", ","], - " y" - ]], - ["punctuation", ")"], - ["operator", "=>"], " x\r\n", - - ["function-variable", "ಠ_ಠ"], - ["operator", "="], - ["punctuation", "("], - ["punctuation", ")"], - ["operator", "=>"], - ["punctuation", "{"], - ["punctuation", "}"], - - ["function-variable", "Ƞȡ_҇"], - ["operator", "="], - ["keyword", "async"], - ["punctuation", "("], - ["parameter", [ - "ಠ", - ["punctuation", ","], - " Ƞ ", - ["operator", "="], - ["number", "2"] - ]], - ["punctuation", ")"], - ["operator", "=>"], - ["punctuation", "{"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for variables obviously containing functions. diff --git a/docs/_style/prism-master/tests/languages/javascript/function_feature.test b/docs/_style/prism-master/tests/languages/javascript/function_feature.test deleted file mode 100644 index 35fa9e92..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/function_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -foo() -foo () -foo_bar() -foo_bar ( ) -f42() -_() -$() -ಠ_ಠ() -Ƞȡ_҇() -if(notAFunction) - ----------------------------------------------------- - -[ - ["function", "foo"], ["punctuation", "("], ["punctuation", ")"], - ["function", "foo"], ["punctuation", "("], ["punctuation", ")"], - ["function", "foo_bar"], ["punctuation", "("], ["punctuation", ")"], - ["function", "foo_bar"], ["punctuation", "("], ["punctuation", ")"], - ["function", "f42"], ["punctuation", "("], ["punctuation", ")"], - ["function", "_"], ["punctuation", "("], ["punctuation", ")"], - ["function", "$"], ["punctuation", "("], ["punctuation", ")"], - ["function", "ಠ_ಠ"], ["punctuation", "("], ["punctuation", ")"], - ["function", "Ƞȡ_҇"], ["punctuation", "("], ["punctuation", ")"], - ["keyword", "if"], ["punctuation", "("], "notAFunction", ["punctuation", ")"] -] - ----------------------------------------------------- - -Checks for functions. Also checks for unicode characters in identifiers. diff --git a/docs/_style/prism-master/tests/languages/javascript/issue1337.test b/docs/_style/prism-master/tests/languages/javascript/issue1337.test deleted file mode 100644 index e49f78d1..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/issue1337.test +++ /dev/null @@ -1,11 +0,0 @@ -// gulp.watch('./src/**/*.js', ['move']); - ----------------------------------------------------- - -[ - ["comment", "// gulp.watch('./src/**/*.js', ['move']);"] -] - ----------------------------------------------------- - -Checks for multi-line comment inside single-line comment. See #1337 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/issue1340.test b/docs/_style/prism-master/tests/languages/javascript/issue1340.test deleted file mode 100644 index 3e340344..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/issue1340.test +++ /dev/null @@ -1,15 +0,0 @@ -/* - * ([{}]) - * // <= double slash comment - * ([{}]) <= punctuation - */ - ----------------------------------------------------- - -[ - ["comment", "/*\r\n * ([{}])\r\n * // <= double slash comment\r\n * ([{}]) <= punctuation\r\n */"] -] - ----------------------------------------------------- - -Checks for single-line comment inside multi-line comment. See #1340 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/issue1397.test b/docs/_style/prism-master/tests/languages/javascript/issue1397.test deleted file mode 100644 index 6f9b5fb2..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/issue1397.test +++ /dev/null @@ -1,21 +0,0 @@ -`${`a string`}` - ----------------------------------------------------- - -[ - ["template-string", [ - ["string", "`"], - ["interpolation", [ - ["interpolation-punctuation", "${"], - ["template-string", [ - ["string", "`a string`"] - ]], - ["interpolation-punctuation", "}"] - ]], - ["string", "`"] - ]] -] - ----------------------------------------------------- - -Checks for nested template strings. See #1397 \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/issue1526.test b/docs/_style/prism-master/tests/languages/javascript/issue1526.test deleted file mode 100644 index 320219cb..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/issue1526.test +++ /dev/null @@ -1,35 +0,0 @@ -fetch('http://github.com/some-resource.json') - .then(response => response.json()) - .catch(console.error); - ----------------------------------------------------- - -[ - ["function", "fetch"], - ["punctuation", "("], - ["string", "'some-resource.json'"], - ["punctuation", ")"], - ["punctuation", "."], - ["function", "then"], - ["punctuation", "("], - ["parameter", ["response"]], - ["operator", "=>"], - " response", - ["punctuation", "."], - ["function", "json"], - ["punctuation", "("], - ["punctuation", ")"], - ["punctuation", ")"], - ["punctuation", "."], - ["function", "catch"], - ["punctuation", "("], - "console", - ["punctuation", "."], - "error", - ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for catch function which is not a keyword. See #1526 diff --git a/docs/_style/prism-master/tests/languages/javascript/keyword_feature.test b/docs/_style/prism-master/tests/languages/javascript/keyword_feature.test deleted file mode 100644 index 8053f2b1..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/keyword_feature.test +++ /dev/null @@ -1,73 +0,0 @@ -catch finally; - -as; async; await; break; case; -class; const; continue; debugger; -default; delete; do; else; enum; -export; extends; for; -from; function; get; if; implements; -import; in; instanceof; interface; let; -new; null; of; package; private; -protected; public; return; set; static; -super; switch; this; throw; try; -typeof; var; void; while; -with; yield; - ----------------------------------------------------- - -[ - ["keyword", "catch"], - ["keyword", "finally"], ["punctuation", ";"], - - ["keyword", "as"], ["punctuation", ";"], - ["keyword", "async"], ["punctuation", ";"], - ["keyword", "await"], ["punctuation", ";"], - ["keyword", "break"], ["punctuation", ";"], - ["keyword", "case"], ["punctuation", ";"], - ["keyword", "class"], ["punctuation", ";"], - ["keyword", "const"], ["punctuation", ";"], - ["keyword", "continue"], ["punctuation", ";"], - ["keyword", "debugger"], ["punctuation", ";"], - ["keyword", "default"], ["punctuation", ";"], - ["keyword", "delete"], ["punctuation", ";"], - ["keyword", "do"], ["punctuation", ";"], - ["keyword", "else"], ["punctuation", ";"], - ["keyword", "enum"], ["punctuation", ";"], - ["keyword", "export"], ["punctuation", ";"], - ["keyword", "extends"], ["punctuation", ";"], - ["keyword", "for"], ["punctuation", ";"], - ["keyword", "from"], ["punctuation", ";"], - ["keyword", "function"], ["punctuation", ";"], - ["keyword", "get"], ["punctuation", ";"], - ["keyword", "if"], ["punctuation", ";"], - ["keyword", "implements"], ["punctuation", ";"], - ["keyword", "import"], ["punctuation", ";"], - ["keyword", "in"], ["punctuation", ";"], - ["keyword", "instanceof"], ["punctuation", ";"], - ["keyword", "interface"], ["punctuation", ";"], - ["keyword", "let"], ["punctuation", ";"], - ["keyword", "new"], ["punctuation", ";"], - ["keyword", "null"], ["punctuation", ";"], - ["keyword", "of"], ["punctuation", ";"], - ["keyword", "package"], ["punctuation", ";"], - ["keyword", "private"], ["punctuation", ";"], - ["keyword", "protected"], ["punctuation", ";"], - ["keyword", "public"], ["punctuation", ";"], - ["keyword", "return"], ["punctuation", ";"], - ["keyword", "set"], ["punctuation", ";"], - ["keyword", "static"], ["punctuation", ";"], - ["keyword", "super"], ["punctuation", ";"], - ["keyword", "switch"], ["punctuation", ";"], - ["keyword", "this"], ["punctuation", ";"], - ["keyword", "throw"], ["punctuation", ";"], - ["keyword", "try"], ["punctuation", ";"], - ["keyword", "typeof"], ["punctuation", ";"], - ["keyword", "var"], ["punctuation", ";"], - ["keyword", "void"], ["punctuation", ";"], - ["keyword", "while"], ["punctuation", ";"], - ["keyword", "with"], ["punctuation", ";"], - ["keyword", "yield"], ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for all keywords. diff --git a/docs/_style/prism-master/tests/languages/javascript/number_feature.test b/docs/_style/prism-master/tests/languages/javascript/number_feature.test deleted file mode 100644 index 16f0e5fd..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/number_feature.test +++ /dev/null @@ -1,36 +0,0 @@ -42 -3.14159 -4e10 -3.2E+6 -2.1e-10 -0b1101 -0o571 -0xbabe -0xBABE -NaN -Infinity -123n -0x123n - ----------------------------------------------------- - -[ - ["number", "42"], - ["number", "3.14159"], - ["number", "4e10"], - ["number", "3.2E+6"], - ["number", "2.1e-10"], - ["number", "0b1101"], - ["number", "0o571"], - ["number", "0xbabe"], - ["number", "0xBABE"], - ["number", "NaN"], - ["number", "Infinity"], - ["number", "123n"], - ["number", "0x123n"] -] - ----------------------------------------------------- - -Checks for decimal numbers, binary numbers, octal numbers, hexadecimal numbers. -Also checks for keywords representing numbers. diff --git a/docs/_style/prism-master/tests/languages/javascript/operator_feature.test b/docs/_style/prism-master/tests/languages/javascript/operator_feature.test deleted file mode 100644 index eb46bc49..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/operator_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -- -- -= -+ ++ += -< <= << <<= -> >= >> >>= >>> >>>= -= == === => -! != !== -& && &= -| || |= -* ** *= **= -/ /= ~ -^ ^= % %= -? ... - ----------------------------------------------------- - -[ - ["operator", "-"], ["operator", "--"], ["operator", "-="], - ["operator", "+"], ["operator", "++"], ["operator", "+="], - ["operator", "<"], ["operator", "<="], ["operator", "<<"], ["operator", "<<="], - ["operator", ">"], ["operator", ">="], ["operator", ">>"], ["operator", ">>="], ["operator", ">>>"], ["operator", ">>>="], - ["operator", "="], ["operator", "=="], ["operator", "==="], ["operator", "=>"], - ["operator", "!"], ["operator", "!="], ["operator", "!=="], - ["operator", "&"], ["operator", "&&"], ["operator", "&="], - ["operator", "|"], ["operator", "||"], ["operator", "|="], - ["operator", "*"], ["operator", "**"], ["operator", "*="], ["operator", "**="], - ["operator", "/"], ["operator", "/="], ["operator", "~"], - ["operator", "^"], ["operator", "^="], ["operator", "%"], ["operator", "%="], - ["operator", "?"], ["operator", "..."] -] - ----------------------------------------------------- - -Checks for all operators. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/regex_feature.test b/docs/_style/prism-master/tests/languages/javascript/regex_feature.test deleted file mode 100644 index e08db8a2..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/regex_feature.test +++ /dev/null @@ -1,29 +0,0 @@ -/foo bar/; -/foo/gimyu, -/[\[\]]{2,4}(?:foo)*/; -/foo"test"bar/; -/foo\//; -/[]/; -/[\]/]/; -1 / 4 + "/, not a regex"; -/ '1' '2' '3' '4' '5' / -[/foo/] - ----------------------------------------------------- - -[ - ["regex", "/foo bar/"], ["punctuation", ";"], - ["regex", "/foo/gimyu"], ["punctuation", ","], - ["regex", "/[\\[\\]]{2,4}(?:foo)*/"], ["punctuation", ";"], - ["regex", "/foo\"test\"bar/"], ["punctuation", ";"], - ["regex", "/foo\\//"], ["punctuation", ";"], - ["regex", "/[]/"], ["punctuation", ";"], - ["regex", "/[\\]/]/"], ["punctuation", ";"], - ["number", "1"], ["operator", "/"], ["number", "4"], ["operator", "+"], ["string", "\"/, not a regex\""], ["punctuation", ";"], - ["regex", "/ '1' '2' '3' '4' '5' /"], - ["punctuation", "["], ["regex", "/foo/"], ["punctuation", "]"] -] - ----------------------------------------------------- - -Checks for regex. diff --git a/docs/_style/prism-master/tests/languages/javascript/supposed-classes_feature.test b/docs/_style/prism-master/tests/languages/javascript/supposed-classes_feature.test deleted file mode 100644 index df85fd4f..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/supposed-classes_feature.test +++ /dev/null @@ -1,30 +0,0 @@ -Foo.prototype.bar; -Bar.constructor; -fooBar.prototype.bar; - ----------------------------------------------------- - -[ - ["class-name", "Foo"], - ["punctuation", "."], - "prototype", - ["punctuation", "."], - "bar", - ["punctuation", ";"], - - ["class-name", "Bar"], - ["punctuation", "."], - "constructor", - ["punctuation", ";"], - - "\nfooBar", - ["punctuation", "."], - "prototype", - ["punctuation", "."], - "bar", - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for supposed function variables based on standard function invocations or modifications. diff --git a/docs/_style/prism-master/tests/languages/javascript/supposed-function_feature.test b/docs/_style/prism-master/tests/languages/javascript/supposed-function_feature.test deleted file mode 100644 index 7568de9b..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/supposed-function_feature.test +++ /dev/null @@ -1,38 +0,0 @@ -foo.apply(thisArg, args); -bar.call(...args); -fooBar.bind(thisArg); - ----------------------------------------------------- - -[ - ["function", "foo"], - ["punctuation", "."], - ["function", "apply"], - ["punctuation", "("], - "thisArg", - ["punctuation", ","], - " args", - ["punctuation", ")"], - ["punctuation", ";"], - - ["function", "bar"], - ["punctuation", "."], - ["function", "call"], - ["punctuation", "("], - ["operator", "..."], - "args", - ["punctuation", ")"], - ["punctuation", ";"], - - ["function", "fooBar"], - ["punctuation", "."], - ["function", "bind"], - ["punctuation", "("], - "thisArg", - ["punctuation", ")"], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks for supposed function variables based on standard function invocations or modifications. diff --git a/docs/_style/prism-master/tests/languages/javascript/template-string_feature.test b/docs/_style/prism-master/tests/languages/javascript/template-string_feature.test deleted file mode 100644 index 664c056d..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/template-string_feature.test +++ /dev/null @@ -1,49 +0,0 @@ -`foo bar` -`foo -bar` -`40+2=${40+2}` -`${foo()}` -"foo `a` `b` `c` `d` bar" -"test // test" `template` - ----------------------------------------------------- - -[ - ["template-string", [ - ["string", "`foo bar`"] - ]], - ["template-string", [ - ["string", "`foo\r\nbar`"] - ]], - ["template-string", [ - ["string", "`40+2="], - ["interpolation", [ - ["interpolation-punctuation", "${"], - ["number", "40"], - ["operator", "+"], - ["number", "2"], - ["interpolation-punctuation", "}"] - ]], - ["string", "`"] - ]], - ["template-string", [ - ["string", "`"], - ["interpolation", [ - ["interpolation-punctuation", "${"], - ["function", "foo"], - ["punctuation", "("], - ["punctuation", ")"], - ["interpolation-punctuation", "}"] - ]], - ["string", "`"] - ]], - ["string", "\"foo `a` `b` `c` `d` bar\""], - ["string", "\"test // test\""], - ["template-string", [ - ["string", "`template`"] - ]] -] - ----------------------------------------------------- - -Checks for single-line and multi-line template strings. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javascript/try-catch_feature.test b/docs/_style/prism-master/tests/languages/javascript/try-catch_feature.test deleted file mode 100644 index c4bbd8b2..00000000 --- a/docs/_style/prism-master/tests/languages/javascript/try-catch_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -try { } catch (e) { } finally { } - ----------------------------------------------------- - -[ - ["keyword", "try"], - ["punctuation", "{"], - ["punctuation", "}"], - ["keyword", "catch"], - ["punctuation", "("], - "e", - ["punctuation", ")"], - ["punctuation", "{"], - ["punctuation", "}"], - ["keyword", "finally"], - ["punctuation", "{"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for try statements. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/javastacktrace/more_feature.test b/docs/_style/prism-master/tests/languages/javastacktrace/more_feature.test deleted file mode 100644 index 8f9dc6ae..00000000 --- a/docs/_style/prism-master/tests/languages/javastacktrace/more_feature.test +++ /dev/null @@ -1,22 +0,0 @@ -... 6 more - ... 6 common frames omitted - ----------------------------------------------------- - -[ - ["more", [ - ["punctuation", "..."], - ["number", "6"], - ["keyword", "more"] - ]], - - ["more", [ - ["punctuation", "..."], - ["number", "6"], - ["keyword", "common frames omitted"] - ]] -] - ----------------------------------------------------- - -Checks for the message that some frames were omitted. diff --git a/docs/_style/prism-master/tests/languages/javastacktrace/stack-frame_feature.test b/docs/_style/prism-master/tests/languages/javastacktrace/stack-frame_feature.test deleted file mode 100644 index b4c400e4..00000000 --- a/docs/_style/prism-master/tests/languages/javastacktrace/stack-frame_feature.test +++ /dev/null @@ -1,71 +0,0 @@ -at Main.main(Main.java:13) -at Main.main(Main.java:13) Same but with some additional notes -at com.foo.bar.Main$FooBar.main(Native Method) -at Main$FooBar.(Unknown Source) - ----------------------------------------------------- - -[ - ["stack-frame", [ - ["keyword", "at"], - ["class-name", "Main"], - ["punctuation", "."], - ["function", "main"], - ["punctuation", "("], - ["source", [ - ["file", "Main.java"], - ["punctuation", ":"], - ["line-number", "13"] - ]], - ["punctuation", ")"] - ]], - - ["stack-frame", [ - ["keyword", "at"], - ["class-name", "Main"], - ["punctuation", "."], - ["function", "main"], - ["punctuation", "("], - ["source", [ - ["file", "Main.java"], - ["punctuation", ":"], - ["line-number", "13"] - ]], - ["punctuation", ")"] - ]], - " Same but with some additional notes\n", - - ["stack-frame", [ - ["keyword", "at"], - ["namespace", "com"], - ["punctuation", "."], - ["namespace", "foo"], - ["punctuation", "."], - ["namespace", "bar"], - ["punctuation", "."], - ["class-name", "Main$FooBar"], - ["punctuation", "."], - ["function", "main"], - ["punctuation", "("], - ["source", [ - ["keyword", "Native Method"] - ]], - ["punctuation", ")"] - ]], - - ["stack-frame", [ - ["keyword", "at"], - ["class-name", "Main$FooBar"], - ["punctuation", "."], - ["function", ""], - ["punctuation", "("], - ["source", [ - ["keyword", "Unknown Source"] - ]], - ["punctuation", ")"] - ]] -] - ----------------------------------------------------- - -Checks for stack frames. diff --git a/docs/_style/prism-master/tests/languages/javastacktrace/summary_feature.test b/docs/_style/prism-master/tests/languages/javastacktrace/summary_feature.test deleted file mode 100644 index 01497f85..00000000 --- a/docs/_style/prism-master/tests/languages/javastacktrace/summary_feature.test +++ /dev/null @@ -1,155 +0,0 @@ -java.lang.NullPointerException -java.lang.NullPointerException: This is bad -HighLevelException: MidLevelException: LowLevelException -HighLevelException: MidLevelException: LowLevelException: This is bad - -Exception in thread "main" java.lang.RuntimeException: A test exception -Exception in thread "main" HighLevelException: MidLevelException: LowLevelException: This is bad - -Caused by: Exception: This also includes causes and suppressed exceptions -Caused by: MidLevelException: LowLevelException -Caused by: com.example.myproject.MyProjectServletException - -Suppressed: java.lang.RuntimeException: could not insert: [com.example.myproject.MyEntity] - ----------------------------------------------------- - -[ - ["summary", [ - ["exceptions", [ - ["namespace", "java"], - ["punctuation", "."], - ["namespace", "lang"], - ["punctuation", "."], - ["class-name", "NullPointerException"] - ]] - ]], - - ["summary", [ - ["exceptions", [ - ["namespace", "java"], - ["punctuation", "."], - ["namespace", "lang"], - ["punctuation", "."], - ["class-name", "NullPointerException"] - ]], - ["punctuation", ":"], - ["message", "This is bad"] - ]], - - ["summary", [ - ["exceptions", [ - ["class-name", "HighLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "MidLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "LowLevelException"] - ]] - ]], - - ["summary", [ - ["exceptions", [ - ["class-name", "HighLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "MidLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "LowLevelException"] - ]], - ["punctuation", ":"], - ["message", "This is bad"] - ]], - - ["summary", [ - ["keyword", "Exception in thread"], - ["string", "\"main\""], - ["exceptions", [ - ["namespace", "java"], - ["punctuation", "."], - ["namespace", "lang"], - ["punctuation", "."], - ["class-name", "RuntimeException"] - ]], - ["punctuation", ":"], - ["message", "A test exception"] - ]], - - ["summary", [ - ["keyword", "Exception in thread"], - ["string", "\"main\""], - ["exceptions", [ - ["class-name", "HighLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "MidLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "LowLevelException"] - ]], - ["punctuation", ":"], - ["message", "This is bad"] - ]], - - ["summary", [ - ["keyword", "Caused by"], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "Exception"] - ]], - ["punctuation", ":"], - ["message", "This also includes causes and suppressed exceptions"] - ]], - - ["summary", [ - ["keyword", "Caused by"], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "MidLevelException"] - ]], - ["punctuation", ":"], - ["exceptions", [ - ["class-name", "LowLevelException"] - ]] - ]], - - ["summary", [ - ["keyword", "Caused by"], - ["punctuation", ":"], - ["exceptions", [ - ["namespace", "com"], - ["punctuation", "."], - ["namespace", "example"], - ["punctuation", "."], - ["namespace", "myproject"], - ["punctuation", "."], - ["class-name", "MyProjectServletException"] - ]] - ]], - - ["summary", [ - ["keyword", "Suppressed"], - ["punctuation", ":"], - ["exceptions", [ - ["namespace", "java"], - ["punctuation", "."], - ["namespace", "lang"], - ["punctuation", "."], - ["class-name", "RuntimeException"] - ]], - ["punctuation", ":"], - ["message", "could not insert: [com.example.myproject.MyEntity]"] - ]] -] - ----------------------------------------------------- - -Checks for exception summaries. diff --git a/docs/_style/prism-master/tests/languages/jolie/deployment_features.test b/docs/_style/prism-master/tests/languages/jolie/deployment_features.test deleted file mode 100644 index ebcd86a7..00000000 --- a/docs/_style/prism-master/tests/languages/jolie/deployment_features.test +++ /dev/null @@ -1,43 +0,0 @@ -Aggregates: First, Second with Third -Redirects: First => Second, Third => Fourth -Jolie: "logger.ol" in LoggerService -log@LoggerService( new )(); -println @ Console( "none" )() ----------------------------------------------------- - [ - [ "keyword", "Aggregates" ], - [ "operator", ":" ], - [ "aggregates", [ - [ "function", "First" ], [ "punctuation", ","], - [ "function", "Second" ], - [ "withExtension", [ - [ "keyword", "with" ], " Third" ] - ] - ] - ], - [ "keyword", "Redirects" ], - [ "operator", ":" ], - [ "redirects", - [ - [ "function", "First" ], - [ "symbol", "=>" ], - [ "function", "Second" ], [ "punctuation", ","], - [ "function", "Third" ], - [ "symbol", "=>" ], - [ "function", "Fourth" ] - ] - ], - [ "keyword", "Jolie" ], - [ "operator", ":" ], - [ "string", "\"logger.ol\"" ], - [ "keyword", "in" ], - [ "function", "LoggerService" ], - "\nlog", [ "symbol", "@" ], [ "function", "LoggerService" ], - "( ", [ "keyword", "new" ], " )()", [ "symbol", ";" ], - "\nprintln ", [ "symbol", "@" ], [ "function", "Console" ], - "( ", [ "string", "\"none\"" ], " )()" -] - ----------------------------------------------------- - -Checks for outputPorts and Aggregates and Redirect constructs. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/jolie/keyword_feature.test b/docs/_style/prism-master/tests/languages/jolie/keyword_feature.test deleted file mode 100644 index 55a95cab..00000000 --- a/docs/_style/prism-master/tests/languages/jolie/keyword_feature.test +++ /dev/null @@ -1,129 +0,0 @@ -include -define -is_defined -undef -main -init -outputPort ; -inputPort ; -Location -Protocol -Interfaces -RequestResponse -OneWay -type -interface -extender -throws -cset -csets -forward -courier ; -Aggregates -Redirects -embedded -extender -execution -sequential -concurrent -single -scope -install -throw -comp -cH -default -global -linkIn -linkOut -synchronized -this -new -for -if -else -while -in ; -Jolie -Java -Javascript -nullProcess -spawn -constants -with -provide -until -exit -foreach -instanceof -over -service - ----------------------------------------------------- -[ - ["keyword", "include" ], - ["keyword", "define" ], - ["keyword", "is_defined" ], - ["keyword", "undef" ], - ["keyword", "main" ], - ["keyword", "init" ], - ["keyword", "outputPort" ], [ "symbol", ";" ], - ["keyword", "inputPort" ], [ "symbol", ";" ], - ["keyword", "Location" ], - ["keyword", "Protocol" ], - ["keyword", "Interfaces" ], - ["keyword", "RequestResponse" ], - ["keyword", "OneWay" ], - ["keyword", "type" ], - ["keyword", "interface" ], - ["keyword", "extender" ], - ["keyword", "throws" ], - ["keyword", "cset" ], - ["keyword", "csets" ], - ["keyword", "forward" ], - ["keyword", "courier" ], [ "symbol", ";" ], - ["keyword", "Aggregates" ], - ["keyword", "Redirects" ], - ["keyword", "embedded" ], - ["keyword", "extender" ], - ["keyword", "execution" ], - ["keyword", "sequential" ], - ["keyword", "concurrent" ], - ["keyword", "single" ], - ["keyword", "scope" ], - ["keyword", "install" ], - ["keyword", "throw" ], - ["keyword", "comp" ], - ["keyword", "cH" ], - ["keyword", "default" ], - ["keyword", "global" ], - ["keyword", "linkIn" ], - ["keyword", "linkOut" ], - ["keyword", "synchronized" ], - ["keyword", "this" ], - ["keyword", "new" ], - ["keyword", "for" ], - ["keyword", "if" ], - ["keyword", "else" ], - ["keyword", "while" ], - ["keyword", "in" ], [ "symbol", ";" ], - ["keyword", "Jolie" ], - ["keyword", "Java" ], - ["keyword", "Javascript" ], - ["keyword", "nullProcess" ], - ["keyword", "spawn" ], - ["keyword", "constants" ], - ["keyword", "with" ], - ["keyword", "provide" ], - ["keyword", "until" ], - ["keyword", "exit" ], - ["keyword", "foreach" ], - ["keyword", "instanceof" ], - ["keyword", "over" ], - ["keyword", "service" ] - -] - ----------------------------------------------------- - -Checks for all keywords. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/jolie/number_feature.test b/docs/_style/prism-master/tests/languages/jolie/number_feature.test deleted file mode 100644 index 90d5c96f..00000000 --- a/docs/_style/prism-master/tests/languages/jolie/number_feature.test +++ /dev/null @@ -1,19 +0,0 @@ -42 -42L -1.2e3 -0.1E-4 -0.2e+1 - ----------------------------------------------------- - -[ - ["number", "42" ], - ["number", "42L" ], - ["number", "1.2e3" ], - ["number", "0.1E-4" ], - ["number", "0.2e+1" ] -] - ----------------------------------------------------- - -Checks for decimal numbers. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/jolie/operator_feature.test b/docs/_style/prism-master/tests/languages/jolie/operator_feature.test deleted file mode 100644 index 963acc2f..00000000 --- a/docs/_style/prism-master/tests/languages/jolie/operator_feature.test +++ /dev/null @@ -1,42 +0,0 @@ -+ ++ += -- -- -= -! != -< <= > >= -> << -= == -&& -? * / % ; : | @ - ----------------------------------------------------- - -[ - ["operator", "+"], - ["operator", "++"], - ["operator", "+="], - ["operator", "-"], - ["operator", "--"], - ["operator", "-="], - ["operator", "!"], - ["operator", "!="], - ["operator", "<" ], - ["operator", "<=" ], - ["operator", ">" ], - ["operator", ">=" ], - ["operator", "->" ], - ["operator", "<<" ], - ["operator", "=" ], - ["operator", "==" ], - ["operator", "&&" ], - [ "operator", "?" ], - [ "operator", "*" ], - [ "operator", "/" ], - [ "operator", "%" ], - [ "symbol", ";" ], - [ "operator", ":" ], - [ "symbol", "|" ], - [ "symbol", "@" ] - -] - ----------------------------------------------------- - -Checks for all operators and symbols. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json+http/json-suffix_inclusion.test b/docs/_style/prism-master/tests/languages/json+http/json-suffix_inclusion.test deleted file mode 100644 index b2400978..00000000 --- a/docs/_style/prism-master/tests/languages/json+http/json-suffix_inclusion.test +++ /dev/null @@ -1,21 +0,0 @@ -Content-type: application/x.foo+bar+json - -{"foo":"bar"} - ----------------------------------------------------- - -[ - ["header-name", "Content-type:"], - " application/x.foo+bar+json", - ["application/json", [ - ["punctuation", "{"], - ["property", "\"foo\""], - ["operator", ":"], - ["string", "\"bar\""], - ["punctuation", "}"] - ]] -] - ----------------------------------------------------- - -Checks for content with JSON suffix in HTTP. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json+http/json_inclusion.test b/docs/_style/prism-master/tests/languages/json+http/json_inclusion.test deleted file mode 100644 index 2282637f..00000000 --- a/docs/_style/prism-master/tests/languages/json+http/json_inclusion.test +++ /dev/null @@ -1,21 +0,0 @@ -Content-type: application/json - -{"foo":"bar"} - ----------------------------------------------------- - -[ - ["header-name", "Content-type:"], - " application/json", - ["application/json", [ - ["punctuation", "{"], - ["property", "\"foo\""], - ["operator", ":"], - ["string", "\"bar\""], - ["punctuation", "}"] - ]] -] - ----------------------------------------------------- - -Checks for JSON content in HTTP. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json/boolean_feature.test b/docs/_style/prism-master/tests/languages/json/boolean_feature.test deleted file mode 100644 index 4019c444..00000000 --- a/docs/_style/prism-master/tests/languages/json/boolean_feature.test +++ /dev/null @@ -1,13 +0,0 @@ -true -false - ----------------------------------------------------- - -[ - ["boolean", "true"], - ["boolean", "false"] -] - ----------------------------------------------------- - -Checks for booleans. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json/comment_feature.test b/docs/_style/prism-master/tests/languages/json/comment_feature.test deleted file mode 100644 index 50341d39..00000000 --- a/docs/_style/prism-master/tests/languages/json/comment_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -{ - // Line comment - "//": "//", - - /* Block comment */ - "/*": "*/" -} - ----------------------------------------------------- - -[ - ["punctuation", "{"], - ["comment", "// Line comment"], - ["property", "\"//\""], - ["operator", ":"], - ["string", "\"//\""], - ["punctuation", ","], - ["comment", "/* Block comment */"], - ["property", "\"/*\""], - ["operator", ":"], - ["string", "\"*/\""], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for single-line and multi-line comments. diff --git a/docs/_style/prism-master/tests/languages/json/null_feature.test b/docs/_style/prism-master/tests/languages/json/null_feature.test deleted file mode 100644 index 1283944a..00000000 --- a/docs/_style/prism-master/tests/languages/json/null_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -null - ----------------------------------------------------- - -[ - ["null", "null"] -] - ----------------------------------------------------- - -Checks for null. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json/number_feature.test b/docs/_style/prism-master/tests/languages/json/number_feature.test deleted file mode 100644 index ca87a480..00000000 --- a/docs/_style/prism-master/tests/languages/json/number_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -0 -123 -3.14159 -5.0e8 -0.2E+2 -47e-5 --1.23 --2.34E33 --4.34E-33 - ----------------------------------------------------- - -[ - ["number", "0"], - ["number", "123"], - ["number", "3.14159"], - ["number", "5.0e8"], - ["number", "0.2E+2"], - ["number", "47e-5"], - ["number", "-1.23"], - ["number", "-2.34E33"], - ["number", "-4.34E-33"] -] - ----------------------------------------------------- - -Checks for numbers. diff --git a/docs/_style/prism-master/tests/languages/json/operator_feature.test b/docs/_style/prism-master/tests/languages/json/operator_feature.test deleted file mode 100644 index 9d30b2e5..00000000 --- a/docs/_style/prism-master/tests/languages/json/operator_feature.test +++ /dev/null @@ -1,11 +0,0 @@ -: - ----------------------------------------------------- - -[ - ["operator", ":"] -] - ----------------------------------------------------- - -Checks for all operators. diff --git a/docs/_style/prism-master/tests/languages/json/property_feature.test b/docs/_style/prism-master/tests/languages/json/property_feature.test deleted file mode 100644 index 3880ad73..00000000 --- a/docs/_style/prism-master/tests/languages/json/property_feature.test +++ /dev/null @@ -1,33 +0,0 @@ -{"foo\"bar\"baz":1,"foo":2} -{ - "foo": 1, - "b\"ar": 2 -} - ----------------------------------------------------- - -[ - ["punctuation", "{"], - ["property", "\"foo\\\"bar\\\"baz\""], - ["operator", ":"], - ["number", "1"], - ["punctuation", ","], - ["property", "\"foo\""], - ["operator", ":"], - ["number", "2"], - ["punctuation", "}"], - - ["punctuation", "{"], - ["property", "\"foo\""], - ["operator", ":"], - ["number", "1"], - ["punctuation", ","], - ["property", "\"b\\\"ar\""], - ["operator", ":"], - ["number", "2"], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for features. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/json/punctuation_feature.test b/docs/_style/prism-master/tests/languages/json/punctuation_feature.test deleted file mode 100644 index b8e351cd..00000000 --- a/docs/_style/prism-master/tests/languages/json/punctuation_feature.test +++ /dev/null @@ -1,20 +0,0 @@ -{} -{ } -[] -], -}, -, - ----------------------------------------------------- - -[ - ["punctuation", "{"], ["punctuation", "}"], - ["punctuation", "{"], ["punctuation", "}"], - ["punctuation", "["], ["punctuation", "]"], - ["punctuation", "]"], ["punctuation", ","], - ["punctuation", "}"], ["punctuation", ","], - ["punctuation", ","] -] ----------------------------------------------------- - -Checks for punctuation. diff --git a/docs/_style/prism-master/tests/languages/json/string_feature.test b/docs/_style/prism-master/tests/languages/json/string_feature.test deleted file mode 100644 index 8dddae58..00000000 --- a/docs/_style/prism-master/tests/languages/json/string_feature.test +++ /dev/null @@ -1,27 +0,0 @@ -"" -"foo" -"foo\"bar\"baz" -"\u2642\\ " -{"foo":"bar","baz":"\""} - ----------------------------------------------------- - -[ - ["string", "\"\""], - ["string", "\"foo\""], - ["string", "\"foo\\\"bar\\\"baz\""], - ["string", "\"\\u2642\\\\ \""], - ["punctuation", "{"], - ["property", "\"foo\""], - ["operator", ":"], - ["string", "\"bar\""], - ["punctuation", ","], - ["property", "\"baz\""], - ["operator", ":"], - ["string", "\"\\\"\""], - ["punctuation", "}"] -] - ----------------------------------------------------- - -Checks for strings. diff --git a/docs/_style/prism-master/tests/languages/jsx/issue1103.test b/docs/_style/prism-master/tests/languages/jsx/issue1103.test deleted file mode 100644 index fdb4030c..00000000 --- a/docs/_style/prism-master/tests/languages/jsx/issue1103.test +++ /dev/null @@ -1,29 +0,0 @@ -var myDivElement =
        ; - ----------------------------------------------------- - -[ - ["keyword", "var"], - " myDivElement ", - ["operator", "="], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "div" - ]], - ["attr-name", ["foo"]], - ["attr-value", [ - ["punctuation", "="], - ["punctuation", "\""], - "bar baz", - ["punctuation", "\""] - ]], - ["punctuation", "/>"] - ]], - ["punctuation", ";"] -] - ----------------------------------------------------- - -Checks that quoted attribute values can contain spaces. -See #1103. \ No newline at end of file diff --git a/docs/_style/prism-master/tests/languages/jsx/issue1235.test b/docs/_style/prism-master/tests/languages/jsx/issue1235.test deleted file mode 100644 index d28d61ff..00000000 --- a/docs/_style/prism-master/tests/languages/jsx/issue1235.test +++ /dev/null @@ -1,29 +0,0 @@ - - ----------------------------------------------------- - -[ - ["tag", [ - ["tag", [ - ["punctuation", "<"], - ["class-name", "Child"] - ]], - ["attr-name", ["options"]], - ["script", [ - ["script-punctuation", "="], - ["punctuation", "{"], - ["punctuation", "{"], - " track", - ["punctuation", ":"], - ["boolean", "true"], - ["punctuation", "}"], - ["punctuation", "}"] - ]], - ["punctuation", ">"] - ]] -] - ----------------------------------------------------- - -Checks that attribute can contain an simple object. -See #1235. diff --git a/docs/_style/prism-master/tests/languages/jsx/issue1236.test b/docs/_style/prism-master/tests/languages/jsx/issue1236.test deleted file mode 100644 index 3cc3ae2b..00000000 --- a/docs/_style/prism-master/tests/languages/jsx/issue1236.test +++ /dev/null @@ -1,26 +0,0 @@ - - ----------------------------------------------------- - -[ - ["tag", [ - ["tag", [ - ["punctuation", "<"], - ["class-name", "Child"] - ]], - ["spread", [ - ["punctuation", "{"], - ["punctuation", "..."], - ["attr-value", "this"], - ["punctuation", "."], - ["attr-value", "props"], - ["punctuation", "}"] - ]], - ["punctuation", ">"] - ]] -] - ----------------------------------------------------- - -Checks that spread can contain an object property. -See #1236. diff --git a/docs/_style/prism-master/tests/languages/jsx/issue1294.test b/docs/_style/prism-master/tests/languages/jsx/issue1294.test deleted file mode 100644 index 4d167f14..00000000 --- a/docs/_style/prism-master/tests/languages/jsx/issue1294.test +++ /dev/null @@ -1,71 +0,0 @@ -export default () => ( -
        -

        Hi! I'm building a fake Gatsby site as part of a tutorial!

        -

        - What do I like to do? Lots of course but definitely enjoy building - websites. -

        -
        -); - ----------------------------------------------------- - -[ - ["keyword", "export"], - ["keyword", "default"], - ["punctuation", "("], - ["punctuation", ")"], - ["operator", "=>"], ["punctuation", "("], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "div" - ]], - ["punctuation", ">"] - ]], - ["plain-text", "\r\n\t\t"], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "h1" - ]], - ["punctuation", ">"] - ]], - ["plain-text", "Hi! I'm building a fake Gatsby site as part of a tutorial!"], - ["tag", [ - ["tag", [ - ["punctuation", ""] - ]], - ["plain-text", "\r\n\t\t"], - ["tag", [ - ["tag", [ - ["punctuation", "<"], - "p" - ]], - ["punctuation", ">"] - ]], - ["plain-text", "\r\n\t\t\tWhat do I like to do? Lots of course but definitely enjoy building\r\n\t\t\twebsites.\r\n\t\t"], - ["tag", [ - ["tag", [ - ["punctuation", ""] - ]], - ["plain-text", "\r\n\t"], - ["tag", [ - ["tag", [ - ["punctuation", ""] - ]], - ["punctuation", ")"], ["punctuation", ";"] -] - ----------------------------------------------------- - -See #1294. diff --git a/docs/_style/prism-master/tests/languages/jsx/issue1335.test b/docs/_style/prism-master/tests/languages/jsx/issue1335.test deleted file mode 100644 index 35c2704e..00000000 --- a/docs/_style/prism-master/tests/languages/jsx/issue1335.test +++ /dev/null @@ -1,126 +0,0 @@ -
      • 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 literal 23533 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 diff --git a/docs/images/1562409366637.png b/docs/images/1562409366637.png index 3c266d524596be21c22bc5a136a4346c02e5ab66..8bf7a42003ebe5591b27171f7855a00aa835e1ee 100644 GIT binary patch literal 85411 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 TE2VM)1ASyAl_cuLj6?n(o21@b literal 18426 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 diff --git a/docs/images/1562409366638.png b/docs/images/1562409366638.png new file mode 100644 index 0000000000000000000000000000000000000000..5996bf9e0c7551914d1a22d993c406e557221e80 GIT binary patch literal 30168 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 !*9iIX9J+C&cUoFC$b}qHv&eqj$co;Bs~u*) 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 BopJyG literal 0 HcmV?d00001 diff --git a/docs/images/1562409926765.png b/docs/images/1562409926765.png index 563632d364b28b7e23c7b82e36b44ee2998148ec..b79a4b1bffa85bbe2505cd7723f8cc576799e266 100644 GIT binary patch literal 21837 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 Date: Mon, 25 May 2020 00:33:52 +0800 Subject: [PATCH 241/371] fix #444 --- docs/book/24-Concurrent-Programming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/24-Concurrent-Programming.md b/docs/book/24-Concurrent-Programming.md index 7418d54b..684ef810 100644 --- a/docs/book/24-Concurrent-Programming.md +++ b/docs/book/24-Concurrent-Programming.md @@ -190,7 +190,7 @@ Java采用了更传统的方法[^2],即在顺序语言之上添加对线程的 - 你通常不能编写有用的测试,因此你必须依靠代码检查结合深入的并发知识来发现错误。 - 即使是有效的程序也只能在其设计参数下工作。当超出这些设计参数时,大多数并发程序会以某种方式失败。 -在其他Java主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“你知道的越多,你认为你知道得越多。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 +在其他 Java 主题中,我们培养了一种感觉-决定论。一切都按照语言的承诺(或隐含)进行,这是令人欣慰和期待的 - 毕竟,编程语言的目的是让机器做我们想要的。从确定性编程的世界进入并发编程领域,我们遇到了一种称为[Dunning-Kruger](https://en.wikipedia.org/wiki/Dunning%E2%80%93Kruger_effect)效应的认知偏差,可以概括为“无知者无畏。”这意味着“......相对不熟练的人拥有着虚幻的优越感,错误地评估他们的能力远高于实际。 我自己的经验是,无论你是多么确定你的代码是线程安全的,它可能已经无效了。你可以很容易地了解所有的问题,然后几个月或几年后你会发现一些概念让你意识到你编写的大多数内容实际上都容易受到并发错误的影响。当某些内容不正确时,编译器不会告诉你。为了使它正确,你必须在研究代码时掌握前脑的所有并发问题。 From 659df0150da9dd6125a25b5c42ab144b0676cd70 Mon Sep 17 00:00:00 2001 From: LingCoder Date: Mon, 25 May 2020 00:45:59 +0800 Subject: [PATCH 242/371] =?UTF-8?q?=E7=BB=99=E9=9D=A2=E5=90=91=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E7=BC=96=E7=A8=8B=E5=B8=A6=E6=9D=A5=E6=9E=81=E5=A4=A7?= =?UTF-8?q?=E7=9A=84=E4=BE=BF=E5=88=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/01-What-is-an-Object.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/book/01-What-is-an-Object.md b/docs/book/01-What-is-an-Object.md index 0f3fe968..b434eaa5 100644 --- a/docs/book/01-What-is-an-Object.md +++ b/docs/book/01-What-is-an-Object.md @@ -106,7 +106,7 @@ Java 有三个显式关键字来设置类中的访问权限:`public`(公开 ## 继承 -“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。 +“对象”的概念给编程带来便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。 通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。 From 2c4185761e112905032885aa409d4d12915ff23f Mon Sep 17 00:00:00 2001 From: LingCoder Date: Fri, 29 May 2020 04:06:26 +0800 Subject: [PATCH 243/371] =?UTF-8?q?=E7=A7=BB=E9=99=A4jupyter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../01-What-is-an-Object-checkpoint.ipynb | 408 - .../04-Operators-checkpoint.ipynb | 2214 ----- jupyter/00-Introduction.ipynb | 111 - jupyter/00-On-Java-8.ipynb | 76 - jupyter/00-Preface.ipynb | 112 - jupyter/01-What-is-an-Object.ipynb | 422 - ...nstalling-Java-and-the-Book-Examples.ipynb | 305 - jupyter/03-Objects-Everywhere.ipynb | 1386 --- jupyter/04-Operators.ipynb | 2247 ----- jupyter/05-Control-Flow.ipynb | 1436 ---- jupyter/06-Housekeeping.ipynb | 3097 ------- jupyter/07-Implementation-Hiding.ipynb | 1325 --- jupyter/08-Reuse.ipynb | 1661 ---- jupyter/09-Polymorphism.ipynb | 1809 ---- jupyter/10-Interfaces.ipynb | 2640 ------ jupyter/11-Inner-Classes.ipynb | 2189 ----- jupyter/12-Collections.ipynb | 2589 ------ jupyter/13-Functional-Programming.ipynb | 2395 ------ jupyter/14-Streams.ipynb | 3492 -------- jupyter/15-Exceptions.ipynb | 3518 -------- jupyter/16-Validating-Your-Code.ipynb | 2480 ------ jupyter/17-Files.ipynb | 1123 --- jupyter/18-Strings.ipynb | 2481 ------ jupyter/19-Type-Information.ipynb | 3701 -------- jupyter/20-Generics.ipynb | 7433 ----------------- jupyter/21-Arrays.ipynb | 3498 -------- jupyter/22-Enumerations.ipynb | 3076 ------- jupyter/23-Annotations.ipynb | 2737 ------ jupyter/24-Concurrent-Programming.ipynb | 4536 ---------- jupyter/25-Patterns.ipynb | 1674 ---- jupyter/Appendix-Becoming-a-Programmer.ipynb | 132 - ...ts-and-Costs-of-Static-Type-Checking.ipynb | 42 - jupyter/Appendix-Collection-Topics.ipynb | 3861 --------- jupyter/Appendix-Data-Compression.ipynb | 417 - jupyter/Appendix-IO-Streams.ipynb | 716 -- jupyter/Appendix-Javadoc.ipynb | 400 - jupyter/Appendix-Low-Level-Concurrency.ipynb | 2446 ------ jupyter/Appendix-New-IO.ipynb | 1446 ---- jupyter/Appendix-Object-Serialization.ipynb | 1374 --- ...pendix-Passing-and-Returning-Objects.ipynb | 88 - jupyter/Appendix-Programming-Guidelines.ipynb | 187 - jupyter/Appendix-Standard-IO.ipynb | 335 - jupyter/Appendix-Supplements.ipynb | 39 - ...itive-Legacy-of-C-plus-plus-and-Java.ipynb | 44 - ...ix-Understanding-equals-and-hashCode.ipynb | 1340 --- jupyter/GLOSSARY.ipynb | 23 - 46 files changed, 79061 deletions(-) delete mode 100644 jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb delete mode 100644 jupyter/.ipynb_checkpoints/04-Operators-checkpoint.ipynb delete mode 100644 jupyter/00-Introduction.ipynb delete mode 100644 jupyter/00-On-Java-8.ipynb delete mode 100644 jupyter/00-Preface.ipynb delete mode 100644 jupyter/01-What-is-an-Object.ipynb delete mode 100644 jupyter/02-Installing-Java-and-the-Book-Examples.ipynb delete mode 100644 jupyter/03-Objects-Everywhere.ipynb delete mode 100644 jupyter/04-Operators.ipynb delete mode 100644 jupyter/05-Control-Flow.ipynb delete mode 100644 jupyter/06-Housekeeping.ipynb delete mode 100644 jupyter/07-Implementation-Hiding.ipynb delete mode 100644 jupyter/08-Reuse.ipynb delete mode 100644 jupyter/09-Polymorphism.ipynb delete mode 100644 jupyter/10-Interfaces.ipynb delete mode 100644 jupyter/11-Inner-Classes.ipynb delete mode 100644 jupyter/12-Collections.ipynb delete mode 100644 jupyter/13-Functional-Programming.ipynb delete mode 100644 jupyter/14-Streams.ipynb delete mode 100644 jupyter/15-Exceptions.ipynb delete mode 100644 jupyter/16-Validating-Your-Code.ipynb delete mode 100644 jupyter/17-Files.ipynb delete mode 100644 jupyter/18-Strings.ipynb delete mode 100644 jupyter/19-Type-Information.ipynb delete mode 100644 jupyter/20-Generics.ipynb delete mode 100644 jupyter/21-Arrays.ipynb delete mode 100644 jupyter/22-Enumerations.ipynb delete mode 100644 jupyter/23-Annotations.ipynb delete mode 100644 jupyter/24-Concurrent-Programming.ipynb delete mode 100644 jupyter/25-Patterns.ipynb delete mode 100644 jupyter/Appendix-Becoming-a-Programmer.ipynb delete mode 100644 jupyter/Appendix-Benefits-and-Costs-of-Static-Type-Checking.ipynb delete mode 100644 jupyter/Appendix-Collection-Topics.ipynb delete mode 100644 jupyter/Appendix-Data-Compression.ipynb delete mode 100644 jupyter/Appendix-IO-Streams.ipynb delete mode 100644 jupyter/Appendix-Javadoc.ipynb delete mode 100644 jupyter/Appendix-Low-Level-Concurrency.ipynb delete mode 100644 jupyter/Appendix-New-IO.ipynb delete mode 100644 jupyter/Appendix-Object-Serialization.ipynb delete mode 100644 jupyter/Appendix-Passing-and-Returning-Objects.ipynb delete mode 100644 jupyter/Appendix-Programming-Guidelines.ipynb delete mode 100644 jupyter/Appendix-Standard-IO.ipynb delete mode 100644 jupyter/Appendix-Supplements.ipynb delete mode 100644 jupyter/Appendix-The-Positive-Legacy-of-C-plus-plus-and-Java.ipynb delete mode 100644 jupyter/Appendix-Understanding-equals-and-hashCode.ipynb delete mode 100644 jupyter/GLOSSARY.ipynb diff --git a/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb b/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb deleted file mode 100644 index 897dcd4d..00000000 --- a/jupyter/.ipynb_checkpoints/01-What-is-an-Object-checkpoint.ipynb +++ /dev/null @@ -1,408 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 第一章 对象的概念\n", - "\n", - "> “我们没有意识到惯用语言的结构有多大的力量。可以毫不夸张地说,它通过语义反应机制奴役我们。语言表现出来并在无意识中给我们留下深刻印象的结构会自动投射到我们周围的世界。” -- Alfred Korzybski (1930)\n", - "\n", - "计算机革命的起源来自机器。编程语言就像是那台机器。它不仅是我们思维放大的工具与另一种表达媒介,更像是我们思想的一部分。语言的灵感来自其他形式的表达,如写作,绘画,雕塑,动画和电影制作。编程语言就是创建应用程序的思想结构。\n", - "\n", - "面向对象编程(Object-Oriented Programming OOP)是一种编程思维方式和编码架构。本章讲述 OOP 的基本概述。如果读者对此不太理解,可先行跳过本章。等你具备一定编程基础后,请务必再回头看。只有这样你才能深刻理解面向对象编程的重要性及设计方式。\n", - "\n", - "## 抽象\n", - "\n", - "所有编程语言都提供抽象机制。从某种程度上来说,问题的复杂度直接取决于抽象的类型和质量。这里的“类型”意思是:抽象的内容是什么?汇编语言是对底层机器的轻微抽象。接着出现的“命令式”语言(如 FORTRAN,BASIC 和 C)是对汇编语言的抽象。与汇编相比,这类语言已有了长足的改进,但它们的抽象原理依然要求我们着重考虑计算机的结构,而非问题本身的结构。\n", - "\n", - "程序员必须要在机器模型(“解决方案空间”)和实际解决的问题模型(“问题空间”)之间建立起一种关联。这个过程既费精力,又脱离编程语言本身的范畴。这使得程序代码很难编写,维护代价高昂。同时还造就了一个副产业“编程方法”学科。\n", - "\n", - "为机器建模的另一个方法是为要解决的问题制作模型。对一些早期语言来说,如 LISP 和 APL,它们的做法是“从不同的角度观察世界”——“所有问题都归纳为列表”或“所有问题都归纳为算法”。PROLOG 则将所有\n", - "问题都归纳为决策链。对于这些语言,我们认为它们一部分是“基于约束”的编程,另一部分则是专为\n", - "处理图形符号设计的(后者被证明限制性太强)。每种方法都有自己特殊的用途,适合解决某一类的问题。只要超出了它们力所能及的范围,就会显得非常笨拙。\n", - "\n", - "面向对象的程序设计在此基础上跨出了一大步,程序员可利用一些工具表达“问题空间”内的元素。由于这种表达非常具有普遍性,所以不必受限于特定类型的问题。我们将问题空间中的元素以及它们在解决方案空间的表示称作“对象”(**Object**)。当然,还有一些在问题空间没有对应的对象体。通过添加新的对象类型,程序可进行灵活的调整,以便与特定的问题配合。所以当你在阅读描述解决方案的代码时,也是在阅读问题的表述。与我们以前见过的相比,这无疑是一种更加灵活、更加强大的语言抽象方法。总之,OOP 允许我们根据问题来描述问题,而不是根据运行解决方案的计算机。然而,它仍然与计算机有联系,每个对象都类似一台小计算机:它们有自己的状态并且可以进行特定的操作。这与现实世界的“对象”或者“物体”相似:它们都有自己的特征和行为。\n", - "\n", - "Smalltalk 作为第一个成功的面向对象并影响了 Java 的程序设计语言 ,*Alan Kay* 总结了其五大基本特征。通过这些特征,我们可理解“纯粹”的面向对象程序设计方法是什么样的:\n", - "\n", - "> 1. **万物皆对象**。你可以将对象想象成一种特殊的变量。它存储数据,但可以在你对其“发出请求”时执行本身的操作。理论上讲,你总是可以从要解决的问题身上抽象出概念性的组件,然后在程序中将其表示为一个对象。\n", - "> 2. **程序是一组对象,通过消息传递来告知彼此该做什么**。要请求调用一个对象的方法,你需要向该对象发送消息。\n", - "> 3. **每个对象都有自己的存储空间,可容纳其他对象**。或者说,通过封装现有对象,可制作出新型对象。所以,尽管对象的概念非常简单,但在程序中却可达到任意高的复杂程度。\n", - "> 4. **每个对象都有一种类型**。根据语法,每个对象都是某个“类”的一个“实例”。其中,“类”(Class)是“类型”(Type)的同义词。一个类最重要的特征就是“能将什么消息发给它?”。\n", - "> 5. **同一类所有对象都能接收相同的消息**。这实际是别有含义的一种说法,大家不久便能理解。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收发送给\"形状”的消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。\n", - "\n", - "*Grady Booch* 提供了对对象更简洁的描述:一个对象具有自己的状态,行为和标识。这意味着对象有自己的内部数据(提供状态)、方法 (产生行为),并彼此区分(每个对象在内存中都有唯一的地址)。\n", - "\n", - "## 接口\n", - "\n", - "亚里士多德(*Aristotle*)大概是第一个认真研究“类型”的哲学家,他曾提出过“鱼类和鸟类”这样的概念。所有对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分。这种思想被首次应用于第一个面向对象编程语言 Simula-67,它在程序中使用基本关键字 **class** 来引入新的类型(class 和 type 通常可互换使用,有些人对它们进行了进一步区分,他们强调 type 决定了接口,而 class 是那个接口的一种特殊实现方式)。\n", - "\n", - "Simula 是一个很好的例子。正如这个名字所暗示的,它的作用是“模拟”(Simulate)类似“银行出纳员”这样的经典问题。在这个例子里,我们有一系列出纳员、客户、帐号、交易和货币单位等许多\"对象”。每类成员(元素)都具有一些通用的特征:每个帐号都有一定的余额;每名出纳都能接收客户的存款;等等。与此同时,每个成员都有自己的状态;每个帐号都有不同的余额;每名出纳都有一个名字。所以在计算机程序中,能用独一无二的实体分别表示出纳员、客户、帐号以及交易。这个实体便是“对象”,而且每个对象都隶属一个特定的“类”,那个类具有自己的通用特征与行为。\n", - "\n", - "因此,在面向对象的程序设计中,尽管我们真正要做的是新建各种各样的数据“类型”(Type),但几乎所有面向对象的程序设计语言都采用了 `class` 关键字。当你看到 “type” 这个词的时候,请同时想到 `class`;反之亦然。\n", - "\n", - "创建好一个类后,可根据情况生成许多对象。随后,可将那些对象作为要解决问题中存在的元素进行处理。事实上,当我们进行面向对象的程序设计时,面临的最大一项挑战是:如何在“问题空间”(问题实际存在的地方)的元素与“方案空间”(对实际问题进行建模的地方,如计算机)的元素之间建立理想的“一对一”的映射关系。\n", - "\n", - "那么如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其解决一些实际的问题,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。“类型”与“接口”的对应关系是面向对象程序设计的基础。\n", - "\n", - "下面让我们以电灯泡为例:\n", - "\n", - "![reader](../images/reader.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "Light lt = new Light();\n", - "lt.on();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "在这个例子中,类型/类的名称是 **Light**,可向 **Light** 对象发出的请求包括打开 `on`、关闭 `off`、变得更明亮 `brighten` 或者变得更暗淡 `dim`。通过声明一个引用,如 `lt` 和 `new` 关键字,我们创建了一个 **Light** 类型的对象,再用等号将其赋给引用。\n", - "\n", - "为了向对象发送消息,我们使用句点符号 `.` 将 `lt` 和消息名称 `on` 连接起来。可以看出,使用一些预先定义好的类时,我们在程序里采用的代码是非常简单直观的。\n", - "\n", - "上图遵循 **UML**(Unified Modeling Language,统一建模语言)的格式。每个类由一个框表示,框的顶部有类型名称,框中间部分是要描述的任何数据成员,方法(属于此对象的方法,它们接收任何发送到该对象的消息)在框的底部。通常,只有类的名称和公共方法在 **UML** 设计图中显示,因此中间部分未显示,如本例所示。如果你只对类名感兴趣,则也不需要显示方法信息。\n", - "\n", - "## 服务提供\n", - "\n", - "在开发或理解程序设计时,我们可以将对象看成是“服务提供者”。你的程序本身将为用户提供服务,并且它能通过调用其他对象提供的服务来实现这一点。我们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题。\n", - "\n", - "那么问题来了:我们该选择哪个对象来解决问题呢?例如,你正在开发一个记事本程序。*你可能会想到在屏幕输入默认的记事本对象*,一个用于检测不同类型打印机并执行打印的对象。这些对象中的某些已经有了。那对于还没有的对象,我们该设计成啥样呢?这些对象需要提供哪些服务,以及还需要调用其他哪些对象?\n", - "\n", - "我们可以将这些问题一一分解,抽象成一组服务。软件设计的基本原则是高内聚:每个组件的内部作用明确,功能紧密相关。然而经常有人将太多功能塞进一个对象中。例如:在支票打印模块中,你需要设计一个可以同时读取文本格式又能正确识别不同打印机型号的对象。正确的做法是提供三个或更多对象:一个对象检查所有排版布局的目录;一个或一组可以识别不同打印机型号的对象展示通用的打印界面;第三个对象组合上述两个服务来完成任务。这样,每个对象都提供了一组紧密的服务。在良好的面向对象设计中,每个对象功能单一且高效。这样的程序设计可以提高我们代码的复用性,同时也方便别人阅读和理解我们的代码。只有让人知道你提供什么服务,别人才能更好地将其应用到其他模块或程序中。\n", - "\n", - "## 封装\n", - "\n", - "我们可以把编程的侧重领域划分为研发和应用。应用程序员调用研发程序员构建的基础工具类来做快速开发。研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节。这样可以有效地避免该工具类被错误的使用和更改,从而减少程序出错的可能。彼此职责划分清晰,相互协作。当应用程序员调用研发程序员开发的工具类时,双方建立了关系。应用程序员通过使用现成的工具类组装应用程序或者构建更大的工具库。如果工具类的创建者将类的内部所有信息都公开给调用者,那么有些使用规则就不容易被遵守。因为前者无法保证后者是否会按照正确的规则来使用,甚至是改变该工具类。只有设定访问控制,才能从根本上阻止这种情况的发生。\n", - "\n", - "因此,使用访问控制的原因有以下两点:\n", - "\n", - "1. 让应用程序员不要触摸他们不应该触摸的部分。(请注意,这也是一个哲学决策。部分编程语言认为如果程序员有需要,则应该让他们访问细节部分。);\n", - "\n", - "2. 使类库的创建者(研发程序员)在不影响后者使用的情况下完善更新工具库。例如,我们开发了一个功能简单的工具类,后来发现可以通过优化代码来提高执行速度。假如工具类的接口和实现部分明确分开并受到保护,那我们就可以轻松地完成改造。\n", - "\n", - "Java 有三个显式关键字来设置类中的访问权限:`public`(公开),`private`(私有)和`protected`(受保护)。这些访问修饰符决定了谁能使用它们修饰的方法、变量或类。\n", - "\n", - " 1. `public`(公开)表示任何人都可以访问和使用该元素;\n", - "\n", - " 2. `private`(私有)除了类本身和类内部的方法,外界无法直接访问该元素。`private` 是类和调用者之间的屏障。任何试图访问私有成员的行为都会报编译时错误;\n", - "\n", - " 3. `protected`(受保护)类似于 `private`,区别是子类(下一节就会引入继承的概念)可以访问 `protected` 的成员,但不能访问 `private` 成员;\n", - "\n", - " 4. `default`(默认)如果你不使用前面的三者,默认就是 `default` 访问权限。`default` 被称为包访问,因为该权限下的资源可以被同一包(库组件)中其他类的成员访问。\n", - "\n", - "## 复用\n", - "\n", - "一个类经创建和测试后,理应是可复用的。然而很多时候,由于程序员没有足够的编程经验和远见,我们的代码复用性并不强。\n", - "\n", - "代码和设计方案的复用性是面向对象程序设计的优点之一。我们可以通过重复使用某个类的对象来达到这种复用性。同时,我们也可以将一个类的对象作为另一个类的成员变量使用。新的类可以是由任意数量和任意类型的其他对象构成。这里涉及到“组合”和“聚合”的概念:\n", - "\n", - "* **组合**(Composition)经常用来表示“拥有”关系(has-a relationship)。例如,“汽车拥有引擎”。\n", - "\n", - "* **聚合**(Aggregation)动态的**组合**。\n", - "\n", - "![UML-example](../images/1545758268350.png)\n", - "\n", - "上图中实心三角形指向“ **Car** ”表示 **组合** 的关系;如果是 **聚合** 关系,可以使用空心三角形。\n", - "\n", - "(**译者注**:组合和聚合都属于关联关系的一种,只是额外具有整体-部分的意义。至于是聚合还是组合,需要根据实际的业务需求来判断。可能相同超类和子类,在不同的业务场景,关联关系会发生变化。只看代码是无法区分聚合和组合的,具体是哪一种关系,只能从语义级别来区分。聚合关系中,整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件可以共享同一个部件。组合关系中,整件拥有部件的生命周期,所以整件删除时,部件一定会跟着删除。而且,多个整件不可以同时共享同一个部件。这个区别可以用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步,则是聚合关系,生命周期同步就是组合关系。)\n", - "\n", - "使用“组合”关系给我们的程序带来极大的灵活性。通常新建的类中,成员对象会使用 `private` 访问权限,这样应用程序员则无法对其直接访问。我们就可以在不影响客户代码的前提下,从容地修改那些成员。我们也可以在“运行时\"改变成员对象从而动态地改变程序的行为,这进一步增大了灵活性。下面一节要讲到的“继承”并不具备这种灵活性,因为编译器对通过继承创建的类进行了限制。\n", - "\n", - "在面向对象编程中经常重点强调“继承”。在新手程序员的印象里,或许先入为主地认为“继承应当随处可见”。沿着这种思路产生的程序设计通常拙劣又复杂。相反,在创建新类时首先要考虑“组合”,因为它更简单灵活,而且设计更加清晰。等我们有一些编程经验后,一旦需要用到继承,就会明显意识到这一点。\n", - "\n", - "## 继承\n", - "\n", - "“继承”给面向对象编程带来极大的便利。它在概念上允许我们将各式各样的数据和功能封装到一起,这样便可恰当表达“问题空间”的概念,而不用受制于必须使用底层机器语言。\n", - "\n", - "通过使用 `class` 关键字,这些概念形成了编程语言中的基本单元。遗憾的是,这么做还是有很多麻烦:在创建了一个类之后,即使另一个新类与其具有相似的功能,你还是得重新创建一个新类。但我们若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。\n", - "\n", - "![Inheritance-example](../images/1545763399825.png)\n", - "\n", - "这个图中的箭头从派生类指向基类。正如你将看到的,通常有多个派生类。类型不仅仅描述一组对象的约束,它还涉及其他类型。两种类型可以具有共同的特征和行为,但是一种类型可能包含比另一种类型更多的特征,并且还可以处理更多的消息(或者以不同的方式处理它们)。继承通过基类和派生类的概念来表达这种相似性。基类包含派生自它的类型之间共享的所有特征和行为。创建基类以表示思想的核心。从基类中派生出其他类型来表示实现该核心的不同方式。\n", - "\n", - "![1545764724202](../images/1545764724202.png)\n", - "\n", - "例如,垃圾回收机对垃圾进行分类。基类是“垃圾”。每块垃圾都有重量、价值等特性,它们可以被切碎、熔化或分解。在此基础上,可以通过添加额外的特性(瓶子有颜色,钢罐有磁性)或行为(铝罐可以被压碎)派生出更具体的垃圾类型。此外,一些行为可以不同(纸张的价值取决于它的类型和状态)。使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。第二个例子是常见的“形状”例子,可能用于计算机辅助设计系统或游戏模拟。基类是“形状”,每个形状都有大小、颜色、位置等等。每个形状可以绘制、擦除、移动、着色等。由此,可以派生出(继承出)具体类型的形状——圆形、正方形、三角形等等——每个形状可以具有附加的特征和行为。\n", - "\n", - "![1545764780795](../images/1545764780795.png)\n", - "\n", - "例如,某些形状可以翻转。有些行为可能不同,比如计算形状的面积。类型层次结构体现了形状之间的相似性和差异性。以相同的术语将解决方案转换成问题是有用的,因为你不需要在问题描述和解决方案描述之间建立许多中间模型。通过使用对象,类型层次结构成为了主要模型,因此你可以直接从真实世界中对系统的描述过渡到用代码对系统进行描述。事实上,有时候,那些善于寻找复杂解决方案的人会被面向对象设计的简单性难倒。从现有类型继承创建新类型。这种新类型不仅包含现有类型的所有成员(尽管私有成员被隐藏起来并且不可访问),而且更重要的是它复制了基类的接口。也就是说,基类对象接收的所有消息也能被派生类对象接收。根据类接收的消息,我们知道类的类型,因此派生类与基类是相同的类型。\n", - "\n", - "在前面的例子中,“圆是形状”。这种通过继承的类型等价性是理解面向对象编程含义的基本门槛之一。因为基类和派生类都具有相同的基本接口,所以伴随此接口的必定有某些具体实现。也就是说,当对象接收到特定消息时,必须有可执行代码。如果继承一个类而不做其他任何事,则来自基类接口的方法直接进入派生类。这意味着派生类和基类不仅具有相同的类型,而且具有相同的行为,这么做没什么特别意义。\n", - "\n", - "有两种方法可以区分新的派生类与原始的基类。第一种方法很简单:在派生类中添加新方法。这些新方法不是基类接口的一部分。这意味着基类不能满足你的所有需求,所以你添加了更多的方法。继承的这种简单而原始的用途有时是解决问题的完美解决方案。然而,还是要仔细考虑是否在基类中也要有这些额外的方法。这种设计的发现与迭代过程在面向对象程序设计中会经常发生。\n", - "\n", - "尽管继承有时意味着你要在接口中添加新方法(尤其是在以 **extends** 关键字表示继承的 Java 中),但并非总需如此。第二种也是更重要地区分派生类和基类的方法是改变现有基类方法的行为,这被称为覆盖 (overriding)。要想覆盖一个方法,只需要在派生类中重新定义这个方法即可。\n", - "\n", - "### \"是一个\"与\"像是一个\"的关系\n", - "\n", - "对于继承可能会引发争论:继承应该只覆盖基类的方法(不应该添加基类中没有的方法)吗?如果这样的话,基类和派生类就是相同的类型了,因为它们具有相同的接口。这会造成,你可以用一个派生类对象完全替代基类对象,这叫作\"纯粹替代\",也经常被称作\"替代原则\"。在某种意义上,这是一种处理继承的理想方式。我们经常把这种基类和派生类的关系称为是一个(is-a)关系,因为可以说\"圆是一个形状\"。判断是否继承,就看在你的类之间有无这种 is-a 关系。\n", - "\n", - "有时你在派生类添加了新的接口元素,从而扩展接口。虽然新类型仍然可以替代基类,但是这种替代不完美,原因在于基类无法访问新添加的方法。这种关系称为像是一个(is-like-a)关系。新类型不但拥有旧类型的接口,而且包含其他方法,所以不能说新旧类型完全相同。\n", - "\n", - "![1545764820176](../images/1545764820176.png)\n", - "\n", - "以空调为例,假设房间里已经安装好了制冷设备的控制器,即你有了控制制冷设备的接口。想象一下,现在空调坏了,你重新安装了一个既制冷又制热的热力泵。热力泵就像是一个(is-like-a)空调,但它可以做更多。因为当初房间的控制系统被设计成只能控制制冷设备,所以它只能与新对象(热力泵)的制冷部分通信。新对象的接口已经扩展了,现有控制系统却只知道原来的接口,一旦看到这个设计,你就会发现,作为基类的制冷系统不够一般化,应该被重新命名为\"温度控制系统\",也应该包含制热功能,这样的话,我们就可以使用替代原则了。上图反映了在现实世界中进行设计时可能会发生的事情。\n", - "\n", - "当你看到替代原则时,很容易会认为纯粹替代是唯一可行的方式,并且使用纯粹替代的设计是很好的。但有些时候,你会发现必须得在派生(扩展)类中添加新方法(提供新的接口)。只要仔细审视,你可以很明显地区分两种设计方式的使用场合。\n", - "\n", - "## 多态\n", - "\n", - "我们在处理类的层次结构时,通常把一个对象看成是它所属的基类,而不是把它当成具体类。通过这种方式,我们可以编写出不局限于特定类型的代码。在上个“形状”的例子中,“方法”(method)操纵的是通用“形状”,而不关心它们是“圆”、“正方形”、“三角形”还是某种尚未定义的形状。所有的形状都可以被绘制、擦除和移动,因此“方法”向其中的任何代表“形状”的对象发送消息都不必担心对象如何处理信息。\n", - "\n", - "这样的代码不会受添加的新类型影响,并且添加新类型是扩展面向对象程序以处理新情况的常用方法。 例如,你可以通过通用的“形状”基类派生出新的“五角形”形状的子类,而不需要修改通用\"形状\"基类的方法。通过派生新的子类来扩展设计的这种能力是封装变化的基本方法之一。\n", - "\n", - "这种能力改善了我们的设计,且减少了软件的维护代价。如果我们把派生的对象类型统一看成是它本身的基类(“圆”当作“形状”,“自行车”当作“车”,“鸬鹚”当作“鸟”等等),编译器(compiler)在编译时期就无法准确地知道什么“形状”被擦除,哪一种“车”在行驶,或者是哪种“鸟”在飞行。这就是关键所在:当程序接收这种消息时,程序员并不想知道哪段代码会被执行。“绘图”的方法可以平等地应用到每种可能的“形状”上,形状会依据自身的具体类型执行恰当的代码。\n", - "\n", - "如果不需要知道执行了哪部分代码,那我们就能添加一个新的不同执行方式的子类而不需要更改调用它的方法。那么编译器在不确定该执行哪部分代码时是怎么做的呢?举个例子,下图的 **BirdController** 对象和通用 **Bird** 对象中,**BirdController** 不知道 **Bird** 的确切类型却还能一起工作。从 **BirdController** 的角度来看,这是很方便的,因为它不需要编写特别的代码来确定 **Bird** 对象的确切类型或行为。那么,在调用 **move()** 方法时是如何保证发生正确的行为(鹅走路、飞或游泳、企鹅走路或游泳)的呢?\n", - "\n", - "![Bird-example](../images/1545839316314.png)\n", - "\n", - "这个问题的答案,是面向对象程序设计的妙诀:在传统意义上,编译器不能进行函数调用。由非 OOP 编译器产生的函数调用会引起所谓的**早期绑定**,这个术语你可能从未听说过,不会想过其他的函数调用方式。这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。\n", - "\n", - "通过继承,程序直到运行时才能确定代码的地址,因此发送消息给对象时,还需要其他一些方案。为了解决这个问题,面向对象语言使用**后期绑定**的概念。当向对象发送信息时,被调用的代码直到运行时才确定。编译器确保方法存在,并对参数和返回值执行类型检查,但是它不知道要执行的确切代码。\n", - "\n", - "为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用。这段代码使用对象中存储的信息来计算方法主体的地址(此过程在多态性章节中有详细介绍)。因此,每个对象的行为根据特定代码位的内容而不同。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言中,必须显式地授予方法后期绑定属性的灵活性。例如,C++ 使用 **virtual** 关键字。在这些语言中,默认情况下方法不是动态绑定的。在 Java 中,动态绑定是默认行为,不需要额外的关键字来实现多态性。\n", - "\n", - "为了演示多态性,我们编写了一段代码,它忽略了类型的具体细节,只与基类对话。该代码与具体类型信息分离,因此更易于编写和理解。而且,如果通过继承添加了一个新类型(例如,一个六边形),那么代码对于新类型的 Shape 就像对现有类型一样有效。因此,该程序是可扩展的。\n", - "\n", - "代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - "void doSomething(Shape shape) {\n", - " shape.erase();\n", - " // ...\n", - " shape.draw();\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "此方法与任何 **Shape** 对话,因此它与所绘制和擦除的对象的具体类型无关。如果程序的其他部分使用 `doSomething()` 方法:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " Circle circle = new Circle();\n", - " Triangle triangle = new Triangle();\n", - " Line line = new Line();\n", - " doSomething(circle);\n", - " doSomething(triangle);\n", - " doSomething(line);\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "可以看到无论传入的“形状”是什么,程序都正确的执行了。\n", - "\n", - "![shape-example](../images/1545841270997.png)\n", - "\n", - "这是一个非常令人惊奇的编程技巧。分析下面这行代码:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " doSomething(circle);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "当预期接收 **Shape** 的方法被传入了 **Circle**,会发生什么。由于 **Circle** 也是一种 **Shape**,所\n", - "以 `doSomething(circle)` 能正确地执行。也就是说,`doSomething()` 能接收任意发送给 **Shape** 的消息。这是完全安全和合乎逻辑的事情。\n", - "\n", - "这种把子类当成其基类来处理的过程叫做“向上转型”(**upcasting**)。在面向对象的编程里,经常利用这种方法来给程序解耦。再看下面的 `doSomething()` 代码示例:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " shape.erase();\n", - " // ...\n", - " shape.draw();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "我们可以看到程序并未这样表达:“如果你是一个 Circle ,就这样做;如果你是一个 Square,就那样做...”。若那样编写代码,就需检查 Shape 所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的 Shape 类型后,都要相应地进行修改。在这里,我们只需说:“你是一种几何形状,我知道你能删掉 `erase()` 和绘制 `draw()`,你自己去做吧,注意细节。”\n", - "\n", - "尽管我们没作出任何特殊指示,程序的操作也是完全正确和恰当的。我们知道,为 Circle 调用`draw()` 时执行的代码与为一个 Square 或 Line 调用 `draw()` 时执行的代码是不同的。但在将 `draw()` 信息发给一个匿名 Shape 时,根据 Shape 句柄当时连接的实际类型,会相应地采取正确的操作。这非常神奇,因为当 Java 编译器为 `doSomething()` 编译代码时,它并不知道自己要操作的准确类型是什么。\n", - "\n", - "尽管我们确实可以保证最终会为 Shape 调用 `erase()` 和 `draw()`,但并不能确定特定的 Circle,Square 或者 Line 调用什么。最后,程序执行的操作却依然是正确的,这是怎么做到的呢?\n", - "\n", - "发送消息给对象时,如果程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的“多态性”(Polymorphism)。面向对象的程序设计语言是通过“动态绑定”的方式来实现对象的多态性的。编译器和运行时系统会负责对所有细节的控制;我们只需知道要做什么,以及如何利用多态性来更好地设计程序。\n", - "\n", - "## 单继承结构\n", - "\n", - "自从 C++ 引入以来,一个 OOP 问题变得尤为突出:是否所有的类都应该默认从一个基类继承呢?这个答案在 Java 中是肯定的(实际上,除 C++ 以外的几乎所有OOP语言中也是这样)。在 Java 中,这个最终基类的名字就是 `Object`。\n", - "\n", - "Java 的单继承结构有很多好处。由于所有对象都具有一个公共接口,因此它们最终都属于同一个基类。相反的,对于 C++ 所使用的多继承的方案则是不保证所有的对象都属于同一个基类。从向后兼容的角度看,多继承的方案更符合 C 的模型,而且受限较少。\n", - "\n", - "对于完全面向对象编程,我们必须要构建自己的层次结构,以提供与其他 OOP 语言同样的便利。我们经常会使用到新的类库和不兼容的接口。为了整合它们而花费大气力(有可能还要用上多继承)以获得 C++ 样的“灵活性”值得吗?如果从零开始,Java 这样的替代方案会是更好的选择。\n", - "\n", - "另外,单继承的结构使得垃圾收集器的实现更为容易。这也是 Java 在 C++ 基础上的根本改进之一。\n", - "\n", - "由于运行期的类型信息会存在于所有对象中,所以我们永远不会遇到判断不了对象类型的情况。这对于系统级操作尤其重要,例如[异常处理](#异常处理)。同时,这也让我们的编程具有更大的灵活性。\n", - "\n", - "## 集合\n", - "\n", - "通常,我们并不知道解决某个具体问题需要的对象数量和持续时间,以及对象的存储方式。那么我们如何知悉程序在运行时需要分配的内存空间呢?\n", - "\n", - "在面向对象的设计中,问题的解决方案有些过于轻率:创建一个新类型的对象来引用、容纳其他的对象。当然,我们也可以使用多数编程语言都支持的“数组”(array)。在 Java 中“集合”(Collection)的使用率更高。(也可称之为“容器”,但“集合”这个称呼更通用。)\n", - "\n", - "“集合”这种类型的对象可以存储任意类型、数量的其他对象。它能根据需要自动扩容,我们不用关心过程是如何实现的。\n", - "\n", - "还好,一般优秀的 OOP 语言都会将“集合”作为其基础包。在 C++ 中,“集合”是其标准库的一部分,通常被称为 STL(Standard Template Library,标准模板库)。SmallTalk 有一套非常完整的集合库。同样,Java 的标准库中也提供许多现成的集合类。\n", - "\n", - "在一些库中,一两个泛型集合就能满足我们所有的需求了,而在其他一些类库(Java)中,不同类型的集合对应不同的需求:常见的有 List,常用于保存序列;Map,也称为关联数组,常用于将对象与其他对象关联;Set,只能保存非重复的值;其他还包括如队列(Queue)、树(Tree)、栈(Stack)、堆(Heap)等等。从设计的角度来看,我们真正想要的是一个能够解决某个问题的集合。如果一种集合就满足所有需求,那么我们就不需要剩下的了。之所以选择集合有以下两个原因:\n", - "\n", - "1. 集合可以提供不同类型的接口和外部行为。堆栈、队列的应用场景和集合、列表不同,它们中的一种提供的解决方案可能比其他灵活得多。\n", - "\n", - "2. 不同的集合对某些操作有不同的效率。例如,List 的两种基本类型:ArrayList 和 LinkedList。虽然两者具有相同接口和外部行为,但是在某些操作中它们的效率差别很大。在 ArrayList 中随机查找元素是很高效的,而 LinkedList 随机查找效率低下。反之,在 LinkedList 中插入元素的效率要比在 ArrayList 中高。由于底层数据结构的不同,每种集合类型在执行相同的操作时会表现出效率上的差异。\n", - "\n", - "我们可以一开始使用 LinkedList 构建程序,在优化系统性能时改用 ArrayList。通过对 List 接口的抽象,我们可以很容易地将 LinkedList 改为 ArrayList。\n", - "\n", - "在 Java 5 泛型出来之前,集合中保存的是通用类型 `Object`。Java 单继承的结构意味着所有元素都基于 `Object` 类,所以在集合中可以保存任何类型的数据,易于重用。要使用这样的集合,我们先要往集合添加元素。由于 Java 5 版本前的集合只保存 `Object`,当我们往集合中添加元素时,元素便向上转型成了 `Object`,从而丢失自己原有的类型特性。这时我们再从集合中取出该元素时,元素的类型变成了 `Object`。那么我们该怎么将其转回原先具体的类型呢?这里,我们使用了强制类型转换将其转为更具体的类型,这个过程称为对象的“向下转型”。通过“向上转型”,我们知道“圆形”也是一种“形状”,这个过程是安全的。可是我们不能从“Object”看出其就是“圆形”或“形状”,所以除非我们能确定元素的具体类型信息,否则“向下转型”就是不安全的。也不能说这样的错误就是完全危险的,因为一旦我们转化了错误的类型,程序就会运行出错,抛出“运行时异常”(RuntimeException)。(后面的章节会提到) 无论如何,我们要寻找一种在取出集合元素时确定其具体类型的方法。另外,每次取出元素都要做额外的“向下转型”对程序和程序员都是一种开销。以某种方式创建集合,以确认保存元素的具体类型,减少集合元素“向下转型”的开销和可能出现的错误难道不好吗?这种解决方案就是:参数化类型机制(Parameterized Type Mechanism)。\n", - "\n", - "参数化类型机制可以使得编译器能够自动识别某个 `class` 的具体类型并正确地执行。举个例子,对集合的参数化类型机制可以让集合仅接受“形状”这种类型的元素,并以“形状”类型取出元素。Java 5 版本支持了参数化类型机制,称之为“泛型”(Generic)。泛型是 Java 5 的主要特性之一。你可以按以下方式向 ArrayList 中添加 Shape(形状):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "attributes": { - "classes": [ - "java" - ], - "id": "" - } - }, - "outputs": [], - "source": [ - " ArrayList shapes = new ArrayList<>();" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "泛型的应用,让 Java 的许多标准库和组件都发生了改变。在本书的代码示例中,你也会经常看到泛型的身影。\n", - "\n", - "## 对象创建与生命周期\n", - "\n", - "我们在使用对象时要注意的一个关键问题就是对象的创建和销毁方式。每个对象的生存都需要资源,尤其是内存。为了资源的重复利用,当对象不再被使用时,我们应该及时释放资源,清理内存。\n", - "\n", - "在简单的编程场景下,对象的清理并不是问题。我们创建对象,按需使用,最后销毁它。然而,情况往往要比这更复杂:\n", - "\n", - "假设,我们正在为机场设计一个空中交通管制的系统(该例也适用于仓库货柜管理、影带出租或者宠物寄养仓库系统)。第一步比较简单:创建一个用来保存飞机的集合,每当有飞机进入交通管制区域时,我们就创建一个“飞机”对象并将其加入到集合中,等到飞机离开时将其从这个集合中清除。与此同时,我们还需要一个记录飞机信息的系统,也许这些数据不像主要控制功能那样引人注意。比如,我们要记录所有飞机中的小型飞机的的信息(比如飞行计划)。此时,我们又创建了第二个集合来记录所有小型飞机。 每当创建一个“飞机”对象的时候,将其放入第一个集合;若它属于小型飞机,也必须同时将其放入第二个集合里。\n", - "\n", - "现在问题开始棘手了:我们怎么知道何时该清理这些对象呢?当某一个系统处理完成,而其他系统可能还没有处理完成。这样的问题在其他的场景下也可能发生。在 C++ 程序设计中,当使用完一个对象后,必须明确将其删除,这就让问题变复杂了。\n", - "\n", - "对象的数据在哪?它的生命周期是怎么被控制的? 在 C++ 设计中采用的观点是效率第一,因此它将选择权交给了程序员。为了获得最大的运行时速度,程序员可以在编写程序时,通过将对象放在栈(Stack,有时称为自动变量或作用域变量)或静态存储区域(static storage area)中来确定内存占用和生存时间。这些区域的对象会被优先分配内存和释放。这种控制在某些情况下非常有用。\n", - "\n", - "然而相对的,我们也牺牲了程序的灵活性。因为在编写代码时,我们必须要弄清楚对象的数量、生存时间还有类型。如果我们要用它来解决一个相当普遍的问题时(如计算机辅助设计、仓库管理或空中交通管制等),限制就太大了。\n", - "\n", - "第二种方法是在堆内存(Heap)中动态地创建对象。在这种方式下,直到程序运行我们才能确定需要创建的对象数量、生存时间和类型。什么时候需要,什么时候在堆内存中创建。 因为内存的占用是动态管理的,所以在运行时,在堆内存上开辟空间所需的时间可能比在栈内存上要长(但也不一定)。在栈内存开辟和释放空间通常是一条将栈指针向下移动和一条将栈指针向上移动的汇编指令。开辟堆内存空间的时间取决于内存机制的设计。\n", - "\n", - "动态方法有这样一个合理假设:对象通常是复杂的,相比于对象创建的整体开销,寻找和释放内存空间的开销微不足道。(原文:*The dynamic approach makes the generally logical assumption that objects tend to be complicated, so the extra overhead of finding storage and releasing that storage will not have an important impact on the creation of an object.*)此外,更好的灵活性对于问题的解决至关重要。\n", - "\n", - "Java 使用动态内存分配。每次创建对象时,使用 `new` 关键字构建该对象的动态实例。这又带来另一个问题:对象的生命周期。较之堆内存,在栈内存中创建对象,编译器能够确定该对象的生命周期并自动销毁它;然而如果你在堆内存创建对象的话,编译器是不知道它的生命周期的。在 C++ 中你必须以编程方式确定何时销毁对象,否则可能导致内存泄漏。Java 的内存管理是建立在垃圾收集器上的,它能自动发现对象不再被使用并释放内存。垃圾收集器的存在带来了极大的便利,它减少了我们之前必须要跟踪的问题和编写相关代码的数量。因此,垃圾收集器提供了更高级别的保险,以防止潜在的内存泄漏问题,这个问题使得许多 C++ 项目没落。\n", - "\n", - "Java 的垃圾收集器被设计用来解决内存释放的问题(虽然这不包括对象清理的其他方面)。垃圾收集器知道对象什么时候不再被使用并且自动释放内存。结合单继承和仅可在堆中创建对象的机制,Java 的编码过程比用 C++ 要简单得多。我们所要做的决定和要克服的障碍也会少很多!\n", - "\n", - "## 异常处理\n", - "\n", - "自编程语言被发明以来,程序的错误处理一直都是个难题。因为很难设计出一个好的错误处理方案,所以许多编程语言都忽略了这个问题,把这个问题丢给了程序类库的设计者。他们提出了在许多情况下都可以工作但很容易被规避的半途而废的措施,通常只需忽略错误。多数错误处理方案的主要问题是:它们依赖程序员之间的约定俗成而不是语言层面的限制。换句话说,如果程序员赶时间或没想起来,这些方案就很容易被忘记。\n", - "\n", - "异常处理机制将程序错误直接交给编程语言甚至是操作系统。“异常”(Exception)是一个从出错点“抛出”(thrown)后能被特定类型的异常处理程序捕获(catch)的一个对象。它不会干扰程序的正常运行,仅当程序出错的时候才被执行。这让我们的编码更简单:不用再反复检查错误了。另外,异常不像方法返回的错误值和方法设置用来表示发生错误的标志位那样可以被忽略。异常的发生是不会被忽略的,它终究会在某一时刻被处理。\n", - "\n", - "最后,“异常机制”提供了一种可靠地从错误状况中恢复的方法,使得我们可以编写出更健壮的程序。有时你只要处理好抛出的异常情况并恢复程序的运行即可,无需退出。\n", - "\n", - "Java 的异常处理机制在编程语言中脱颖而出。Java 从一开始就内置了异常处理,因此你不得不使用它。这是 Java 语言唯一接受的错误报告方法。如果没有编写适当的异常处理代码,你将会收到一条编译时错误消息。这种有保障的一致性有时会让程序的错误处理变得更容易。值得注意的是,异常处理并不是面向对象的特性。尽管在面向对象的语言中异常通常由对象表示,但是在面向对象语言之前也存在异常处理。\n", - "\n", - "## 本章小结\n", - "\n", - "面向过程程序包含数据定义和函数调用。要找到程序的意图,你必须要在脑中建立一个模型,弄清函数调用和更底层的概念。这些程序令人困扰,因为它们的表示更多地面向计算机而不是我们要解决的问题,这就是我们在设计程序时需要中间表示的原因。OOP 在面向过程编程的基础上增加了许多新的概念,所以有人会认为使用 Java 来编程会比同等的面向过程编程要更复杂。在这里,我想给大家一个惊喜:通常按照 Java 规范编写的程序会比面向过程程序更容易被理解。\n", - "\n", - "你看到的是对象的概念,这些概念是站在“问题空间”的(而不是站在计算机角度的“解决方案空间”),以及发送消息给对象以指示该空间中的活动。面向对象编程的一个优点是:设计良好的 Java 程序代码更容易被人阅读理解。由于 Java 类库的复用性,通常程序要写的代码也会少得多。\n", - "\n", - "OOP 和 Java 不一定适合每个人。评估自己的需求以及与现有方案作比较是很重要的。请充分考虑后再决定是不是选择 Java。如果在可预见的未来,Java 并不能很好的满足你的特定需求,那么你应该去寻找其他替代方案(特别是,我推荐看 Python)。如果你依然选择 Java 作为你的开发语言,我希望你至少应该清楚你选择的是什么,以及为什么选择这个方向。\n", - "\n", - "\n", - "