From 928d84715371d1488ffe1f93381a8223511afa7d Mon Sep 17 00:00:00 2001 From: WangHengwei Date: Thu, 15 Aug 2019 17:07:51 +0800 Subject: [PATCH 1/3] [hotfix] fix typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原文是“produce an exception”,我理解“产生”与“报出”有区别,产生的异常可以捕获处理,不知妥否。 --- 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 c3a612b..4de9af7 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -253,7 +253,7 @@ public class RecursiveFactorial { 3628800 ``` -这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则无限递归并报异常。 +这里,`fact` 是一个静态变量。 注意使用三元 **if-else**。 递归函数将一直调用自己,直到 `i == 0`。所有递归函数都有“停止条件”,否则将无限递归并产生异常。 我们可以将 `Fibonacci` 序列改为使用递归 Lambda 表达式来实现,这次使用实例变量: From 9807b4b03629afa93060b835940ea2d55bb37ff5 Mon Sep 17 00:00:00 2001 From: cogitates Date: Thu, 15 Aug 2019 17:17:35 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Fix=20issue=20#117=20=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=20=20=E7=AC=AC=E4=B8=89=EF=BC=8C=E7=AC=AC?= =?UTF-8?q?=E5=9B=9B=E5=B0=8F=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 | 260 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/docs/book/21-Arrays.md b/docs/book/21-Arrays.md index 9c40c66..caeb116 100644 --- a/docs/book/21-Arrays.md +++ b/docs/book/21-Arrays.md @@ -306,10 +306,270 @@ a = d; ## 返回数组 +假设你写了一个方法,这个方法不是返回一个元素,而是返回多个元素。对C++/C这样的语言来说这是很困难的,因为你无法返回一个数组,只能是返回一个指向数组的指针。这会带来一些问题,因为对数组生存期的控制变得很混乱,这会导致内存泄露。 + +而在Java中,你只需返回数组,你永远不用为数组担心,只要你需要它,它就可用,垃圾收集器会在你用完后把它清理干净。 + +下面,我们返回一个 **字符串** 数组: + +```Java +// arrays/IceCreamFlavors.java +// Returning arrays from methods +import java.util.*; +import static onjava.ArrayShow.*; + +public class IceCreamFlavors { + private static SplittableRandom rand = + new SplittableRandom(47); + static final String[] FLAVORS = { + "Chocolate", "Strawberry", "Vanilla Fudge Swirl", + "Mint Chip", "Mocha Almond Fudge", "Rum Raisin", + "Praline Cream", "Mud Pie" + }; + public static String[] flavorSet(int n) { + if(n > FLAVORS.length) + throw new IllegalArgumentException("Set too big"); + String[] results = new String[n]; + boolean[] picked = new boolean[FLAVORS.length]; + for(int i = 0; i < n; i++) { + int t; + do + t = rand.nextInt(FLAVORS.length); + while(picked[t]); + results[i] = FLAVORS[t]; + picked[t] = true; + } + return results; + } + public static void main(String[] args) { + for(int i = 0; i < 7; i++) + show(flavorSet(3)); + } +} +/* Output: +[Praline Cream, Mint Chip, Vanilla Fudge Swirl] +[Strawberry, Vanilla Fudge Swirl, Mud Pie] +[Chocolate, Strawberry, Vanilla Fudge Swirl] +[Rum Raisin, Praline Cream, Chocolate] +[Mint Chip, Rum Raisin, Mocha Almond Fudge] +[Mocha Almond Fudge, Mud Pie, Vanilla Fudge Swirl] +[Mocha Almond Fudge, Mud Pie, Mint Chip] +*/ +``` + + + +**flaverset()** 创建名为 **results** 的 **String** 类型的数组。 这个数组的大小 **n** 取决于你传进方法的参数。然后选择从数组 **FLAVORS** 中随机选择flavors并且把它们放进 **results** 里并返回。返回一个数组就像返回其他任何的对象一样,实际上返回的是引用。数组是在 **flavorSet()** 中或者在其他的什么地方创建的并不重要。垃圾收集器会清理你用完的数组,你需要的数组则会保留。 + +如果你必须要返回一系列不同类型的元素,你可以使用 [泛型](book/generics) 中介绍的 **元组** 。 + +注意,当 **flavorSet()** 随机选择 flavors,它应该确保某个特定的选项被选中。这在一个 **do** 循环中执行,它将一直做出随机选择直到它发现一个元素不在 **picked** 数组中。(一个字符串 + +比较将显示出随机选中的元素是不是已经存在于 **results** 数组中)。如果成功了,它将添加条目并且寻找下一个( **i** 递增)。输出结果显示 **flvorSet()** 每一次都是按照随机顺序选择 flavors。 + +直到书中的这个点,随机数通过 **java.util.Random** 类生成的,这个类从Java 1.0就有,甚至被更新以提供Java 8 流。现在我们可以介绍Java 8中的 **SplittableRandom** ,它不只是以线性操作工作(你最终会学到),还提供了一个高质量的随机数。我们将在这本书的后面部分使用 **SplittableRandom** 。 ## 多维数组 +要创建多维的基元数组,你要用大括号来界定数组中的向量: + +```Java +// arrays/MultidimensionalPrimitiveArray.java +import java.util.*; + +public class MultidimensionalPrimitiveArray { + public static void main(String[] args) { + int[][] a = { + { 1, 2, 3, }, + { 4, 5, 6, }, + }; + System.out.println(Arrays.deepToString(a)); + } +} +/* Output: +[[1, 2, 3], [4, 5, 6]] +*/。 +``` + +每个嵌套的大括号都代表了数组的一个维度。 + +这个例子使用 **array.deepToString()** 方法,这将多维数组转换成 **String** 类型,就像在输出中显示的那样。 + +你也可以使用 **new** 分配数组。 这是一个使用 **new** 表达式分配的三维数组: + +```Java +// arrays/ThreeDWithNew.java +import java.util.*; + +public class ThreeDWithNew { + public static void main(String[] args) { + // 3-D array with fixed length: + int[][][] a = new int[2][2][4]; + System.out.println(Arrays.deepToString(a)); + } +} +/* Output: +[[[0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, +0]]] +*/ +``` + +倘若你不对基元数组进行显式的初始化,它的值会自动初始化。而对象数组将被初始化为 **null** 。 + +组成矩阵的数组中每一个向量都可以是任意长度的(这叫做不规则数组): + +```Java +// arrays/RaggedArray.java +import java.util.*; + +public class RaggedArray { + static int val = 1; + public static void main(String[] args) { + SplittableRandom rand = new SplittableRandom(47); + // 3-D array with varied-length vectors: + int[][][] a = new int[rand.nextInt(7)][][]; + for(int i = 0; i < a.length; i++) { + a[i] = new int[rand.nextInt(5)][]; + for(int j = 0; j < a[i].length; j++) { + a[i][j] = new int[rand.nextInt(5)]; + Arrays.setAll(a[i][j], n -> val++); // [1] + } + } + System.out.println(Arrays.deepToString(a)); + } +} +/* Output: +[[[1], []], [[2, 3, 4, 5], [6]], [[7, 8, 9], [10, 11, +12], []]] +*/ +``` + +第一个 **new** 创建了一个数组,这个数组首元素长度随机,其余的则不确定。第二个在for循环中的 **new** 给数组填充了元素,第三个 **new** 为数组的最后一个索引填充元素。 + +* **[1]** Java 8 增加了 **Arrays.setAll()** 方法,其使用生成器来生成插入数组中的值。此生成器符合函数接口 **IntunaryOperator** ,只使用一个非 **默认** 的方法 **ApplyAsint(int操作数)** 。 **Arrays.setAll()** 传递当前数组索引作为操作数,因此一个选项是提供 **n -> n** 的lambda表达式来显示数组的索引(在上面的代码中很容易尝试)。这里,我们忽略索引,只是插入递增计数器的值。 + +非基元的对象数组也可以定义为不规则数组。这里,我们收集了许多使用大括号的 **new** 表达式: + +```Java +// arrays/MultidimensionalObjectArrays.java +import java.util.*; + +public class MultidimensionalObjectArrays { + public static void main(String[] args) { + BerylliumSphere[][] spheres = { + { new BerylliumSphere(), new BerylliumSphere() }, + { new BerylliumSphere(), new BerylliumSphere(), + new BerylliumSphere(), new BerylliumSphere() }, + { new BerylliumSphere(), new BerylliumSphere(), + new BerylliumSphere(), new BerylliumSphere(), + new BerylliumSphere(), new BerylliumSphere(), + new BerylliumSphere(), new BerylliumSphere() }, + }; + System.out.println(Arrays.deepToString(spheres)); + } +} +/* Output: +[[Sphere 0, Sphere 1], [Sphere 2, Sphere 3, Sphere 4, +Sphere 5], [Sphere 6, Sphere 7, Sphere 8, Sphere 9, +Sphere 10, Sphere 11, Sphere 12, Sphere 13]] +*/ +``` + +数组初始化时使用自动装箱技术: + +```Java +// arrays/AutoboxingArrays.java +import java.util.*; + +public class AutoboxingArrays { + public static void main(String[] args) { + Integer[][] a = { // Autoboxing: + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }, + { 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }, + { 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 }, + { 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 }, + }; + System.out.println(Arrays.deepToString(a)); + } +} +/* Output: +[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [21, 22, 23, 24, 25, +26, 27, 28, 29, 30], [51, 52, 53, 54, 55, 56, 57, 58, +59, 60], [71, 72, 73, 74, 75, 76, 77, 78, 79, 80]] +*/ +``` + +以下是如何逐个构建非基元的对象数组: + +```Java +// arrays/AssemblingMultidimensionalArrays.java +// Creating multidimensional arrays +import java.util.*; + +public class AssemblingMultidimensionalArrays { + public static void main(String[] args) { + Integer[][] a; + a = new Integer[3][]; + for(int i = 0; i < a.length; i++) { + a[i] = new Integer[3]; + for(int j = 0; j < a[i].length; j++) + a[i][j] = i * j; // Autoboxing + } + System.out.println(Arrays.deepToString(a)); + } +} +/* Output: +[[0, 0, 0], [0, 1, 2], [0, 2, 4]] +*/ +``` + +**i * j** 在这里只是为了向 **Integer** 中添加有趣的值。 + +**Arrays.deepToString()** 方法同时适用于基元数组和对象数组: + +```JAVA +// arrays/MultiDimWrapperArray.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. +// Multidimensional arrays of "wrapper" objects +import java.util.*; + +public class MultiDimWrapperArray { + public static void main(String[] args) { + Integer[][] a1 = { // Autoboxing + { 1, 2, 3, }, + { 4, 5, 6, }, + }; + Double[][][] a2 = { // Autoboxing + { { 1.1, 2.2 }, { 3.3, 4.4 } }, + { { 5.5, 6.6 }, { 7.7, 8.8 } }, + { { 9.9, 1.2 }, { 2.3, 3.4 } }, + }; + String[][] a3 = { + { "The", "Quick", "Sly", "Fox" }, + { "Jumped", "Over" }, + { "The", "Lazy", "Brown", "Dog", "&", "friend" }, + }; + System.out.println( + "a1: " + Arrays.deepToString(a1)); + System.out.println( + "a2: " + Arrays.deepToString(a2)); + System.out.println( + "a3: " + Arrays.deepToString(a3)); + } +} +/* Output: +a1: [[1, 2, 3], [4, 5, 6]] +a2: [[[1.1, 2.2], [3.3, 4.4]], [[5.5, 6.6], [7.7, +8.8]], [[9.9, 1.2], [2.3, 3.4]]] +a3: [[The, Quick, Sly, Fox], [Jumped, Over], [The, +Lazy, Brown, Dog, &, friend]] +*/ +``` + +同样的,在 **Integer** 和 **Double** 数组中,自动装箱为可为你创建包装器对象。 ## 泛型数组 From b2c4b990b71b61be377f9dca25987d3ce388e02e Mon Sep 17 00:00:00 2001 From: xiangflight Date: Thu, 15 Aug 2019 22:31:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?revision[13]=20=E4=BD=9C=E4=B8=BA=E9=97=AD?= =?UTF-8?q?=E5=8C=85=E7=9A=84=E5=86=85=E9=83=A8=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/13-Functional-Programming.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/book/13-Functional-Programming.md b/docs/book/13-Functional-Programming.md index c3a612b..95d622d 100644 --- a/docs/book/13-Functional-Programming.md +++ b/docs/book/13-Functional-Programming.md @@ -1048,7 +1048,7 @@ O **闭包**(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。 -考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语变量捕获 )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 +考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为**支持闭包**,或者叫作在词法上限定范围( 也使用术语*变量捕获* )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。 首先,下例函数中,方法返回访问对象字段和方法参数。代码示例: @@ -1094,7 +1094,7 @@ public class SharedStorage { 每次调用 `getAsInt()` 都会增加 `i`,表明存储是共享的。 -如果 `i` 是 `makeFun()` 的本地怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍然编译: +如果 `i` 是 `makeFun()` 的局部变量怎么办? 在正常情况下,当 `makeFun()` 完成时 `i` 就消失。 但它仍可以编译: ```java // functional/Closure2.java @@ -1126,7 +1126,7 @@ public class Closure3 { } ``` -`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者实 `final` 效果的。 +`x` 和 `i` 的操作都犯了同样的错误:从 Lambda 表达式引用的局部变量必须是 `final` 或者是等同 `final` 效果的。 如果使用 `final` 修饰 `x`和 `i`,就不能再递增它们的值了。代码示例: @@ -1165,7 +1165,7 @@ public class Closure5 { } ``` -我们可以通过将 `final` 关键字应用于变量声明来实现**等同 final 效果**, 不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。 +**等同 final 效果**意味着可以在变量声明前加上 **final** 关键字而不用更改任何其余代码。 实际上它就是具备 `final` 效果的,只是没有明确说明。 通过在闭包中使用 `final` 关键字提前修饰变量 `x` 和 `i` , 我们解决了 `Closure5.java` 中的问题。代码示例: @@ -1206,7 +1206,7 @@ public class Closure7 { } ``` -编译器非常智能,它能识别变量 `i` 的值正在被更改。 对于包装类型的处理可能比较特殊,所以让我们使用下面的 List 的例子。代码示例: +编译器非常智能,它能识别变量 `i` 的值被更改过了。 对于包装类型的处理可能比较特殊,因此我们尝试下 **List**: ```java // functional/Closure8.java @@ -1244,7 +1244,7 @@ public class Closure8 { [1, 96] ``` -可以看到,这次一切正常。我们改变了 List 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList` 他们不能互相干扰和共享。 +可以看到,这次一切正常。我们改变了 **List** 的值却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 `makeFun()` 时,其实都会创建并返回一个全新的 `ArrayList`。 也就是说,每个闭包都有自己独立的 `ArrayList`, 它们之间互不干扰。 请**注意**我已经声明 `ai` 是 `final` 的了。尽管在这个例子中你可以去掉 `final` 并得到相同的结果(试试吧!)。 应用于对象引用的 `final` 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。 @@ -1271,6 +1271,7 @@ public class Closure9 { 让我们回顾一下 `Closure1.java`。那么现在问题来了:为什么变量 `i` 被修改编译器却没有报错呢。 它既不是 `final` 的,也不是**等同 final 效果**的。因为 `i` 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。是的,你可以辩称在这种情况下不会发生变量捕获(Variable Capture)。但可以肯定的是,`Closure3.java` 的错误消息是专门针对局部变量的。因此,规则并非只是“在 Lambda 之外定义的任何变量必须是 `final` 的或**等同 final 效果**那么简单。相反,你必须考虑捕获的变量是否是**等同 final 效果**的。 如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。 + ### 作为闭包的内部类 我们可以复制我们的例子使用匿名内部类: