Thinking in Java 3Edition 11:对象的集合 如果程序的对象数量有限,且寿命可知,那么这个程序是相当 简单的。 一般来说,程序都是根据具体情况在不断地创建新的对象,而这些情况又 只有在程序运行的时候才能确定。不到运行时你是不会知道你到底需要多 少对象,甚至是什么类型的对象。为了解决这种常见的编程问题,你得有 办法能在任何时间,任何地点,创建任何数量的对象。所以你不能指望用 命名的 reference来持有每个对象 Myobject 原因就在于,你不可能知道究竟需要多少这样的对象 针对这个相当关键的问题,绝大多数语言都提供了某种解决办法。Java 也提供了好几种持有对象(或者更准确的说,是对象的 reference)的方 法。我们前面讨论的数组是语言内置的数据类型。此外,Java的工具类 库还包括一套比较完整的容器类( container classes也被称为 co∥ lection classes,但是由于 Collection被Java2用来命名类库的 某个子集,所以我还是用概括性更强的术语" container")。它提供了复杂 而精致的方法来持有甚至是操控你的对象。 数组 我们已经在第4章的最后部分,对数组的绝大多数内容作了必要的介绍, 此外我们还演示了如何定义和初始化一个数组。本章的所关注的问题是 “持有对象”,而数组只是其中的一个方法。那么数组又是凭什么要我们 如此重视的呢? 数组与其它容器的区别体现在三个方面:效率,类型识别以及可以持有 primitives。数组是Java提供的,能随机存储和访问 reference序列的 诸多方法中的,最高效的一种。数组是一个简单的线性序列,所以它可以 快速的访问其中的元素。但是速度是有代价的;当你创建了一个数组之 后,它的容量就固定了,而且在其生命周期里不能改变。也许你会提议先 创建一个数组,等到快不够用的时候,再创建一个新的,然后将旧数组里 的 reference全部导到新的里面。其实(我们以后会讲的) Array List就 是这么做的。但是这种灵活性所带来的开销,使得 Array List的效率比 起数组有了明显下降 C++的 vector容器类确实能知道它到底持有了什么类型的对象,但是 与Java的数组相比,它又有另一个缺点:C++ vector的[]运算符不 第1页共106页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 1 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 11:对象的集合 如果程序的对象数量有限,且寿命可知,那么这个程序是相当 简单的。 一般来说,程序都是根据具体情况在不断地创建新的对象,而这些情况又 只有在程序运行的时候才能确定。不到运行时你是不会知道你到底需要多 少对象,甚至是什么类型的对象。为了解决这种常见的编程问题,你得有 办法能在任何时间,任何地点,创建任何数量的对象。所以你不能指望用 命名的 reference 来持有每个对象: MyObject myReference; 原因就在于,你不可能知道究竟需要多少这样的对象。 针对这个相当关键的问题,绝大多数语言都提供了某种解决办法。Java 也提供了好几种持有对象(或者更准确的说,是对象的 reference) 的方 法。我们前面讨论的数组是语言内置的数据类型。此外,Java 的工具类 库还包括一套比较完整的容器类(container classes 也被称为 collection classes,但是由于 Collection 被 Java 2 用来命名类库的 某个子集,所以我还是用概括性更强的术语"container")。它提供了复杂 而精致的方法来持有甚至是操控你的对象。 数组 我们已经在第 4 章的最后部分,对数组的绝大多数内容作了必要的介绍, 此外我们还演示了如何定义和初始化一个数组。本章的所关注的问题是 “持有对象”,而数组只是其中的一个方法。那么数组又是凭什么要我们 如此重视的呢? 数组与其它容器的区别体现在三个方面:效率,类型识别以及可以持有 primitives。数组是 Java 提供的,能随机存储和访问 reference 序列的 诸多方法中的,最高效的一种。数组是一个简单的线性序列,所以它可以 快速的访问其中的元素。但是速度是有代价的;当你创建了一个数组之 后,它的容量就固定了,而且在其生命周期里不能改变。也许你会提议先 创建一个数组,等到快不够用的时候,再创建一个新的,然后将旧数组里 的 reference 全部导到新的里面。其实(我们以后会讲的)ArrayList 就 是这么做的。但是这种灵活性所带来的开销,使得 ArrayList 的效率比 起数组有了明显下降。 C++的 vector 容器类确实能知道它到底持有了什么类型的对象,但是 与 Java 的数组相比,它又有另一个缺点:C++ vector 的[]运算符不
Chapter 11: Collections of Objects 作边界检查,所以你可能会不知不觉就过了界1。而Java对数组和容器都 做边界检查:如果过了界,它就会给一个 RuntimeException。这种 异常表明这个错误是由程序员造成的,这样你就用不着再在程序里面检查 了(译者注:这里的原文有些模棱两可,我想他要表达的意思可能是以下 两种中的一个。一是指,你不用再作边界检查了;二是指,通过异常信 息,你就可以知道错误是由过界造成的,因而不用查源代码了)。顺便说 下,C++的 vector不作边界检查是为了速度;而在Java,不论是数 组或是用容器,你都得面对边界检查所带来的固定的性能下降。 本章所探讨的其它泛型容器类还包括List,Set和Map。它们处理对象 的时候就好像这些对象都没有自己的具体类型一样。也就是说,容器将它 所含的元素都看成是(ava中所有类的根类) object的。这样你只需创 建一种容器,就能把所有类型的对象全都放进去。从这个角度来看,这种 做法很不错(只是苦了 primitive。如果是常量,你还可以用Java的 primitive的 wrapper类;如果是变量,那就只能放到你自己的类里 了)。与其他泛型容器相比,这里体现出数组的第二个优势:创建数组的 时候,你也同时指明了它所持有的对象的类型(这又引出了第三点——数 组可以持有 primitives,而容器却不行)。也就是说,它会在编译的时候 作类型检査,从而防止你插入错误类型的对象,或者是在提取对象的时候 把对象的类型给搞错了。Java在编译和运行时都能阻止你将一个不恰当 的消息传给对象。所以这并不是说使用容器就有什么危险,只是如果编译 器能够帮你指定,那么程序运行会更快,最终用户也会较少受到程序运行 异常的骚扰。 从效率和类型检查的角度来看,使用数组总是没错的。但是,如果你在解 决一个更为一般的问题,那数组就会显得功能太弱了点。本章先讲数组, 然后集中精力讨论Java的容器类。 数组是第一流的对象 不管你用的是那种类型的数组,数组的标识符实际上都是一个“创建在堆 (heap)里的实实在在的对象的” reference。实际上是那个对象持有其他 对象的 reference。你既可以用数组的初始化语句,隐含地创建这个对 象,也可以用new表达式,明确地创建这个对象。只读的 length属性 能告诉你数组能存储多少元素。它是数组对象的一部分(实际上也是你唯 能访问的属性或方法)。‘[]’语法是另一条访问数组对象的途径。 下面这段程序演示了几种初始化数组的办法,以及如何将数组的 reference赋给不同的数组对象。此外,它还显示了,对象数组和 primitives数组在使用方法上几乎是完全相同。唯一的不同是,对象数组 持有 reference,而 primitive数组则直接持有值。 7/: c11: Arraysize. java 不过真的想知道 vector的容量有多大,还是有办法的,更何况at)方法确实做边界检查 第2页共106页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Chapter 11: Collections of Objects 第 2 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 作边界检查,所以你可能会不知不觉就过了界1。而 Java 对数组和容器都 做边界检查;如果过了界,它就会给一个 RuntimeException。这种 异常表明这个错误是由程序员造成的,这样你就用不着再在程序里面检查 了(译者注:这里的原文有些模棱两可,我想他要表达的意思可能是以下 两种中的一个。一是指,你不用再作边界检查了;二是指,通过异常信 息,你就可以知道错误是由过界造成的,因而不用查源代码了)。顺便说 一下,C++的 vector 不作边界检查是为了速度;而在 Java,不论是数 组或是用容器,你都得面对边界检查所带来的固定的性能下降。 本章所探讨的其它泛型容器类还包括 List,Set 和 Map。它们处理对象 的时候就好像这些对象都没有自己的具体类型一样。也就是说,容器将它 所含的元素都看成是(Java 中所有类的根类)Object 的。这样你只需创 建一种容器,就能把所有类型的对象全都放进去。从这个角度来看,这种 做法很不错(只是苦了 primitive。如果是常量,你还可以用 Java 的 primitive 的 wrapper 类;如果是变量,那就只能放到你自己的类里 了)。与其他泛型容器相比,这里体现出数组的第二个优势:创建数组的 时候,你也同时指明了它所持有的对象的类型(这又引出了第三点 —— 数 组可以持有 primitives,而容器却不行)。也就是说,它会在编译的时候 作类型检查,从而防止你插入错误类型的对象,或者是在提取对象的时候 把对象的类型给搞错了。Java 在编译和运行时都能阻止你将一个不恰当 的消息传给对象。所以这并不是说使用容器就有什么危险,只是如果编译 器能够帮你指定,那么程序运行会更快,最终用户也会较少受到程序运行 异常的骚扰。 从效率和类型检查的角度来看,使用数组总是没错的。但是,如果你在解 决一个更为一般的问题,那数组就会显得功能太弱了点。本章先讲数组, 然后集中精力讨论 Java 的容器类。 数组是第一流的对象 不管你用的是那种类型的数组,数组的标识符实际上都是一个“创建在堆 (heap)里的实实在在的对象的”reference。实际上是那个对象持有其他 对象的 reference。你既可以用数组的初始化语句,隐含地创建这个对 象,也可以用 new 表达式,明确地创建这个对象。只读的 length 属性 能告诉你数组能存储多少元素。它是数组对象的一部分(实际上也是你唯 一能访问的属性或方法)。‘[]’语法是另一条访问数组对象的途径。 下面这段程序演示了几种初始化数组的办法,以及如何将数组的 reference 赋给不同的数组对象。此外,它还显示了,对象数组和 primitives 数组在使用方法上几乎是完全相同。唯一的不同是,对象数组 持有 reference,而 primitive 数组则直接持有值。 //: c11:ArraySize.java 1不过真的想知道 vector 的容量有多大,还是有办法的,更何况 at( )方法确实做边界检查
Thinking in Java 3Edition Initialization re-assignment of arrays import com. bruceeckel. simpletest. class Weeble [) //A small mythical creature ublic class Arraysize private static Test monitor new Test( public static void main(String[] args)f // Arrays of objects Weeble[] a; / Local uninitialized variable Weeble[ b= new Weeble[5]; // Null references Weeble[ c= new Weeble[4]i for(int i =0; i< c. length; i++) if(c[il = null)// Can test for null referenc / Aggregate initialization Weeble[ d=i new Weeble(), new Weeble(), new Weeble( // Dynamic aggregate initialization a= new Weeble[ i new Weeble(), new Weeble( System. out. println("alength="+ alength)i System. out. println("blength ="+blength)i / The references inside the array are // automatically initialized to null for(int i =0; i< blength; i++) System. out. println(" c. length =+ clength)i System. out. println("d.lengt tln("a length t a length) // Arrays of primitives: int[l e; //Null t[]f t[5] t[ g w int [41 for(int i =0 g[i] 11,47,93} // Compile error: variable e not initialized //! System. out. println("e length="+ elength) Syst t println("f length =" f length)i / The primitives inside the array are automatically initialized to zero for (int 0; i<f length; i++) System. out. println("f["+ i +]=" f[i])i ngt g length) System. out.pr System. out System. out. println("elength ="+elength) monitor. expect(new String[] b lenath "b[1]=nu11", 第3页共106页 www.wgqqh.com/shhgs/tij.html
Thinking in Java 3rd Edition 第 3 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com // Initialization & re-assignment of arrays. import com.bruceeckel.simpletest.*; class Weeble {} // A small mythical creature public class ArraySize { private static Test monitor = new Test( ); public static void main(String[] args) { // Arrays of objects: Weeble[] a; // Local uninitialized variable Weeble[] b = new Weeble[5]; // Null references Weeble[] c = new Weeble[4]; for(int i = 0; i < c.length; i++) if(c[i] == null) // Can test for null reference c[i] = new Weeble( ); // Aggregate initialization: Weeble[] d = { new Weeble( ), new Weeble( ), new Weeble( ) }; // Dynamic aggregate initialization: a = new Weeble[] { new Weeble( ), new Weeble( ) }; System.out.println("a.length=" + a.length); System.out.println("b.length = " + b.length); // The references inside the array are // automatically initialized to null: for(int i = 0; i < b.length; i++) System.out.println("b[" + i + "]=" + b[i]); System.out.println("c.length = " + c.length); System.out.println("d.length = " + d.length); a = d; System.out.println("a.length = " + a.length); // Arrays of primitives: int[] e; // Null reference int[] f = new int[5]; int[] g = new int[4]; for(int i = 0; i < g.length; i++) g[i] = i*i; int[] h = { 11, 47, 93 }; // Compile error: variable e not initialized: //!System.out.println("e.length=" + e.length); System.out.println("f.length = " + f.length); // The primitives inside the array are // automatically initialized to zero: for(int i = 0; i < f.length; i++) System.out.println("f[" + i + "]=" + f[i]); System.out.println("g.length = " + g.length); System.out.println("h.length = " + h.length); e = h; System.out.println("e.length = " + e.length); e = new int[] { 1, 2 }; System.out.println("e.length = " + e.length); monitor.expect(new String[] { "a.length=2", "b.length = 5", "b[0]=null", "b[1]=null
Chapter 11: Collections of Objects "b[2]=nu11 b[4]=nu11 d. length 3 h=3 "f length =5 f[1]=0 f[2]= "f[3]=0 th h length =3 数组a是一个尚未初始化的局部变量,在你将其正确地初始化之前,编译 器禁止你对这个 reference作任何事情。数组b是一个已经进行了初始 化的数组,它被连到了一个“ Weeble对象的 reference”的数组,只 是这个数组里面还没有真正放上 Weeble对象。但是由于b指向的是 个合法的对象,所以你已经可以査询其容量大小了。这就带来一个小问 题:你没法知道数组里面究竟放了多少元素,因为 length只是告诉你数 组能放多少元素,也就是说是数组对象的容量,而不是它真正已经持有的 元素的数量。但是,创建数组对象的时候,它所持有的 reference都会 被自动地初始化为nu,所以你可以通过检查数组的某个“槽位”是否 为nu,来判断它是否持有对象。以此类推, primitive的数组,会自动 将数字初始化为零,字符初始化为(char)0, boolean初始化为 数组c演示了数组对象的创建,随后它直接用 Weeble对象对数组各个 “槽位”进行赋值。数组d就是所谓“总体初始化( aggregate initialization)”的语法,它只用一条语句,就创建了数组对象(隐含地使 用了new,就像对数组c),并且用 Weeble对象进行了初始化 下一个数组的初始化可以被理解为“动态的总体初始化( dynamic aggregate initialization)”。d所使用的“总体初始化”语句,只能在 定义d的时候用。但是用这种语法,你就可以在任何地方创建和初始化数 组对象。比方说,假设hde()是一个使用 Weeble对象的数组做参数 的方法。那么,你可以这样调用: 第4页共106页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Chapter 11: Collections of Objects 第 4 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com "b[2]=null", "b[3]=null", "b[4]=null", "c.length = 4", "d.length = 3", "a.length = 3", "f.length = 5", "f[0]=0", "f[1]=0", "f[2]=0", "f[3]=0", "f[4]=0", "g.length = 4", "h.length = 3", "e.length = 3", "e.length = 2" }); } } ///:~ 数组 a 是一个尚未初始化的局部变量,在你将其正确地初始化之前,编译 器禁止你对这个 reference 作任何事情。数组 b 是一个已经进行了初始 化的数组,它被连到了一个“Weeble 对象的 reference”的数组,只 是这个数组里面还没有真正放上 Weeble 对象。但是由于 b 指向的是一 个合法的对象,所以你已经可以查询其容量大小了。这就带来一个小问 题:你没法知道数组里面究竟放了多少元素,因为 length 只是告诉你数 组能放多少元素,也就是说是数组对象的容量,而不是它真正已经持有的 元素的数量。但是,创建数组对象的时候,它所持有的 reference 都会 被自动地初始化为 null,所以你可以通过检查数组的某个“槽位”是否 为 null,来判断它是否持有对象。以此类推,primitive 的数组,会自动 将数字初始化为零,字符初始化为(char)0,boolean 初始化为 false。 数组 c 演示了数组对象的创建,随后它直接用 Weeble 对象对数组各个 “槽位”进行赋值。数组 d 就是所谓“总体初始化(aggregate initialization)”的语法,它只用一条语句,就创建了数组对象(隐含地使 用了 new,就像对数组 c),并且用 Weeble 对象进行了初始化。 下一个数组的初始化可以被理解为“动态的总体初始化(dynamic aggregate initialization)”。d 所使用的“总体初始化”语句,只能在 定义 d 的时候用。但是用这种语法,你就可以在任何地方创建和初始化数 组对象。比方说,假设 hide( )是一个使用 Weeble 对象的数组做参数 的方法。那么,你可以这样调用: hide(d);
Thinking in Java 3Edition 但是你也可以动态地创建一个数组,把它当作参数传给hide() hide(new Weeble[]i new Weeble(), new Weeble()))i 在很多情况下,这能给你的编程带来便利。 表达式 演示了如何将一个 reference指向另一个数组对象。这么做跟使用其他 对象的 refernce没什么两样。现在a和d都指向堆中的同一个数组对 象 Arraysize.java的第二部分应征了 primitive数组的工作方式和对象 数组的几乎一摸一样。只是它能直接持有 primitive的值 primitive的容器 容器类只能持有 Object对象的 reference。而数组除了能持有 Objects的 reference之外,还可以直接持有 primitive。当然可以使 用诸如 Integer, Double之类的 wrapper类,把 primitive的值放到 容器中,但这样总有点怪怪的。此外, primitive数组的效率要比 wrapper类容器的高出许多。 当然,如果你使用 primitive的时候,还需要那种“能随需要自动扩展 的”容器类的灵活性,那就不能用数组了。你只能用容器来存储 primitive的 wrapper类。也许你会想,应该为每种 primitive都提供 个 Array List,但是遗憾的是Java没为你准备。2 返回一个数组 假设你写了一个方法,它返回的不是一个而是一组东西。在C和C++之 类的语言里,这件事就有些难办了。因为你不能返回一个数组,你只能返 回一个指向数组的指针。由于要处理“控制数组生命周期”之类的麻烦 事,这么做很容易会出错,最后导致内存泄漏。 Java采取了类似的解决方案,但是不同之处在于,它返回的“就是一个 数组”。与C++不同,你永远也不必为Java的数组操心—一只要你还 需要它,它就还在;一旦你用完了,垃圾回收器会帮你把它打扫干净。 2这就是C+明显比Ja强的地方了,因为它的 template关键词支持参数化类型( parameterized type) 第5页共106页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 5 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 但是你也可以动态地创建一个数组,把它当作参数传给 hide( ): hide(new Weeble[] { new Weeble( ), new Weeble( ) }); 在很多情况下,这能给你的编程带来便利。 表达式: a = d; 演示了如何将一个 reference 指向另一个数组对象。这么做跟使用其他 对象的 refernce 没什么两样。现在 a 和 d 都指向堆中的同一个数组对 象。 ArraySize.java 的第二部分应征了 primitive 数组的工作方式和对象 数组的几乎一摸一样。只是它能直接持有 primitive 的值。 primitive 的容器 容器类只能持有 Object 对象的 reference。而数组除了能持有 Objects 的 reference 之外,还可以直接持有 primitive。当然可以使 用诸如 Integer,Double 之类的 wrapper 类,把 primitive 的值放到 容器中,但这样总有点怪怪的。此外,primitive 数组的效率要比 wrapper 类容器的高出许多。 当然,如果你使用 primitive 的时候,还需要那种“能随需要自动扩展 的”容器类的灵活性,那就不能用数组了。你只能用容器来存储 primitive 的 wrapper 类。也许你会想,应该为每种 primitive 都提供一 个 ArrayList,但是遗憾的是 Java 没为你准备。2 返回一个数组 假设你写了一个方法,它返回的不是一个而是一组东西。在 C 和 C++之 类的语言里,这件事就有些难办了。因为你不能返回一个数组,你只能返 回一个指向数组的指针。由于要处理“控制数组生命周期”之类的麻烦 事,这么做很容易会出错,最后导致内存泄漏。 Java 采取了类似的解决方案,但是不同之处在于,它返回的“就是一个 数组”。与 C++不同,你永远也不必为 Java 的数组操心——只要你还 需要它,它就还在;一旦你用完了,垃圾回收器会帮你把它打扫干净。 2这就是 C++明显比 Java 强的地方了,因为它的 template 关键词支持参数化类型(parameterized type)
Chapter 11: Collections of Objects 下面的例子演示了如何返回一个 String数组: //: c11: IceCream. java Returning arrays from methods import com. bruceeckel. simpletest. import java.util.*i ublic class IceCream private static Test monitor new Test( private static Random rand new Random( public static final String[] flavors = f "Chocolate","Strawberry","Vanilla Fudge Swirl" "Mint Chip","Mocha Almond Fudge","Rum Raisin Praline Cream"Mud Pie" public static String[] flavorset (int n)( String[] results new String [n] boolean[] picked new boolean[flavors.length]i nt 0;i i++){ int t t = rand. nextInt(flavors. length while (picked[t])i results [i] s[t]; picked[t] true; return results public static void main (String[] args) t System. out. println "flavorset("+ 1+)=") String[] fl = flavorSet(flavors. length)i for (int 3=0;3< fl length 3++) System. out. println("\t"+ fl[jl)i monitor. expect (new object[] 号f1 avorset\(\\d+) \\t(chocolate l Strawberry l +"Vanilla Fudge SwirllMint Chip Mocha Almond # +"Fudge I Rum Raisin l Praline Cream I Mud flavorset()创建了一个名为 results的 String数组。这个数组的 容量是由传给它的参数n所决定的。接下来它从 flavors数组里面随机 选择 flavors(译者注:表示冰激凌的口味),放到 results中,最后返回 results。返回数组跟返回对象没什么两样——都是一个 reference。因 此数组是不是在 flavorset()或是其他什么地方创建的并不重要。只要 第6页共106页 www.wgqqh.com/shhgs/tij.html
Chapter 11: Collections of Objects 第 6 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 下面的例子演示了如何返回一个 String 数组: //: c11:IceCream.java // Returning arrays from methods. import com.bruceeckel.simpletest.*; import java.util.*; public class IceCream { private static Test monitor = new Test( ); private static Random rand = new Random( ); public 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) { 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 < 20; i++) { System.out.println( "flavorSet(" + i + ") = "); String[] fl = flavorSet(flavors.length); for(int j = 0; j < fl.length; j++) System.out.println("\t" + fl[j]); monitor.expect(new Object[] { "%% flavorSet\\(\\d+\\) = ", new TestExpression("%% \\t(Chocolate|Strawberry|" + "Vanilla Fudge Swirl|Mint Chip|Mocha Almond " + "Fudge|Rum Raisin|Praline Cream|Mud Pie)", 8) }); } } } ///:~ flavorSet( ) 创建了一个名为 results 的 String 数组。这个数组的 容量是由传给它的参数 n 所决定的。接下来它从 flavors 数组里面随机 选择 flavors(译者注:表示冰激凌的口味),放到 results 中,最后返回 results。返回数组跟返回对象没什么两样——都是一个 reference。因 此数组是不是在 flavorSet( )或是其他什么地方创建的并不重要。只要
Thinking in Java 3Edition 你还需要,它就不会消失:一旦你用完了,垃圾回收器负责帮你收拾干 再附带说一下, flayorset()随机选择 flavors的时候,会检查那个 flavor以前是不是没被选中过。这是由do循环做的。它不断的作随机选 择,直到找到一个在 picked数组中还没有的。(当然,也可以用“比较 String”的方式来检查随机选出的 flavor是不是已经在 results数组中 了。)如果成功,它会添加这条记录,然后寻找下一个(递增ⅱ)。 main()会打印出20套 flavors,这样你就能看到,每次 flayorset() 都是随机选择 flavors的。如果把输出导到文件你就能看得更清楚。只是 记着看文件的时候,你只是想挑一个而不是真的想吃冰激凌。 Arrays类 java.util里面有一个 Arrays类,它包括了一组可用于数组的 static 方法,这些方法都是一些实用工具。其中有四个基本方法:用来比较两个 数组是否相等的 equals():用来填充数组的f():用来对数组进行 排序的sort():以及用于在一个已排序的数组中查找元素的 binarysearch()。所有这些方法都对 primitive和 Object进行了重 载。此外还有一个 aslist()方法,它接受一个数组,然后把它转成一个 List容器。后面你会学到。 虽然Aays还是有用的,但它的功能并不完整。举例来说,如果它能让 我们不用写for循环就能直接打印数组,那就好了。此外,正如你所看到 的,fl()只能用一个值填数组。所以,如果如果你想把随机生成的数字 填进数组的话,f)是无能为力的 因此为 Arrays类提供一些额外的功能还是有意义的。为方便起鉴,我把 它放到 package com. bruceeckelutil里。它可以打印任何类型的 数组;并且用“你定义的 generator对象生成的”值或对象填充一个数 组 由于要为各种 primitive以及 Object服务,程序里有大量的几乎是重复 的代码。3比如,next()必须根据不同的情况返回不同的类型,所以每 种类型都需要一个“ generator”接口。 //: com: bruceeckel: util: Generator. java package com. bruceeckel util; ublic interface Generator object next( )i)///: C++的程序员会觉得,如果能用默认参数和模板的话,可以少写很多代码。而 Python的程序员则会认 为,这个类库本身就是多余的 第7页共106页 www.wgqqh.com/shhgs/tij.html emailshhgsasohu.com
Thinking in Java 3rd Edition 第 7 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 你还需要,它就不会消失;一旦你用完了,垃圾回收器负责帮你收拾干 净。 再附带说一下,flavorSet( )随机选择 flavors 的时候,会检查那个 flavor 以前是不是没被选中过。这是由 do 循环做的。它不断的作随机选 择,直到找到一个在 picked 数组中还没有的。(当然,也可以用“比较 String”的方式来检查随机选出的 flavor 是不是已经在 results 数组中 了。)如果成功,它会添加这条记录,然后寻找下一个(递增 i)。 main( )会打印出 20 套 flavors,这样你就能看到,每次 flavorSet( ) 都是随机选择 flavors 的。如果把输出导到文件你就能看得更清楚。只是 记着看文件的时候,你只是想挑一个而不是真的想吃冰激凌。 Arrays 类 java.util 里面有一个 Arrays 类,它包括了一组可用于数组的 static 方法,这些方法都是一些实用工具。其中有四个基本方法:用来比较两个 数组是否相等的 equals( );用来填充数组的 fill( );用来对数组进行 排序的 sort( );以及用于在一个已排序的数组中查找元素的 binarySearch( )。所有这些方法都对 primitive 和 Object 进行了重 载。此外还有一个 asList( )方法,它接受一个数组,然后把它转成一个 List 容器。后面你会学到。 虽然 Arrays 还是有用的,但它的功能并不完整。举例来说,如果它能让 我们不用写 for 循环就能直接打印数组,那就好了。此外,正如你所看到 的,fill( )只能用一个值填数组。所以,如果如果你想把随机生成的数字 填进数组的话,fill( )是无能为力的。 因此为 Arrays 类提供一些额外的功能还是有意义的。为方便起鉴,我把 它放到 package com.bruceeckel.util 里。它可以打印任何类型的 数组;并且用“你定义的 generator 对象生成的”值或对象填充一个数 组。 由于要为各种 primitive 以及 Object 服务,程序里有大量的几乎是重复 的代码。3比如,next( )必须根据不同的情况返回不同的类型,所以每 种类型都需要一个“generator” 接口。 //: com:bruceeckel:util:Generator.java package com.bruceeckel.util; public interface Generator { Object next( ); } ///:~ 3 C++的程序员会觉得,如果能用默认参数和模板的话,可以少写很多代码。而 Python 的程序员则会认 为,这个类库本身就是多余的
Chapter 11: Collections of Objects //: com: bruceeckel: util: BooleanGenerator java package com. bruceeckel util; public interface BooleanGenerator i boolean next();)/// //: com: bruceeckel: util: ByteGenerator java package com. bruceeckelutili public interface ByteGenerator i byte next( )i) ///: //: com: bruceeckel: util: CharGenerator java package com. bruceeckel.util; public interface CharGenerator i char next( )i ///:~ //: com: bruceeckel: util: ShortGenerator java package com. bruceeckelut public interface ShortGenerator short next( )i 1 ///:~ 77: com: bruceeckel: util: IntGenerator java package com. bruceeckel util public interface IntGenerator i int next( );)/// //: com: bruceeckel: util: LongGenerator java package com. bruceeckel util blic inter //: com: bruceeckel: util: FloatGenerator java package bruceeckel utili oublic interface FloatGenerator float next()i ///:~ //: com: bruceeckel: util: DoubleGenerator java package com. bruceeckeluti public interface DoubleGenerator i double next()i j 第8页共106页 www.wgqqh.com/shhgs/tij.html
Chapter 11: Collections of Objects 第 8 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com //: com:bruceeckel:util:BooleanGenerator.java package com.bruceeckel.util; public interface BooleanGenerator { boolean next( ); } ///:~ //: com:bruceeckel:util:ByteGenerator.java package com.bruceeckel.util; public interface ByteGenerator { byte next( ); } ///:~ //: com:bruceeckel:util:CharGenerator.java package com.bruceeckel.util; public interface CharGenerator { char next( ); } ///:~ //: com:bruceeckel:util:ShortGenerator.java package com.bruceeckel.util; public interface ShortGenerator { short next( ); } ///:~ //: com:bruceeckel:util:IntGenerator.java package com.bruceeckel.util; public interface IntGenerator { int next( ); } ///:~ //: com:bruceeckel:util:LongGenerator.java package com.bruceeckel.util; public interface LongGenerator { long next( ); } ///:~ //: com:bruceeckel:util:FloatGenerator.java package com.bruceeckel.util; public interface FloatGenerator { float next( ); } ///:~ //: com:bruceeckel:util:DoubleGenerator.java package com.bruceeckel.util; public interface DoubleGenerator { double next( ); } ///:~
Thinking in Java 3Edition Arrays2包含了很多 tostring()方法以重载各种类型。这些方法能让 你很方便地打印出一个数组。 tostring()方法用了 String Buffer而 不是 String对象。这是出于运行效率的考虑;当你需要重复调用一个方 法以组装字符串的时候,较为明智的选择还是使用效率更高的 String Buffer,而不是更方便的 String。这里,创建 String Buffer 的时候用了一个初始值,然后再它后面接 String。最后,把 result转 换成 String再返回 //: com: bruceeckel:util: Arrays2 java /A supplement to java. util. Arrays, to provide additional // useful functionality when working with arrays Allows // any array to be converted to a String, and to be filled // package com. bruceeckel util import java. util.*i public class Arrays public static string tostring(boolean[] a) String Buffer result new String Buffer("[")i for (int i =0; i<alength; 1++) result. append(a[i])i if(i a length-1 y result. append(",) ult append("]") urn result. tostring()i public static string tostring(byte[] a) StringBuffer result new StringBuffer("[") 0; i <a length; i++)t result. append(a[i])i if(i a length -1) result. append(",")i result public static string tostring(char [ a)( StringBuffer result new StringBuffer("[")i for (int i =0 result. append(a[])i if (i alength -1) appe return result. tostring( public static String tostring(short[ a)i ew stringBu for (int i =0; i<alength; 1++) result. append(a[i])i if(i a length -1) result. append(",")i 第9页共106页 www.wgqqh.com/shhgs/tij.html
Thinking in Java 3rd Edition 第 9 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com Arrays2 包含了很多 toString( )方法以重载各种类型。这些方法能让 你很方便地打印出一个数组。toString( )方法用了 StringBuffer 而 不是 String 对象。这是出于运行效率的考虑;当你需要重复调用一个方 法以组装字符串的时候,较为明智的选择还是使用效率更高的 StringBuffer,而不是更方便的 String。这里,创建 StringBuffer 的时候用了一个初始值,然后再它后面接 String。最后,把 result 转 换成 String 再返回: //: com:bruceeckel:util:Arrays2.java // A supplement to java.util.Arrays, to provide additional // useful functionality when working with arrays. Allows // any array to be converted to a String, and to be filled // via a user-defined "generator" object. package com.bruceeckel.util; import java.util.*; public class Arrays2 { public static String toString(boolean[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(byte[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(char[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(short[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); }
Chapter 11: Collections of Objects result. append( "]")i eturn result. tostring( public static string tostring (int[] a)i StringBuffer result new StringBuffer("[")i for (int i = 0; i<alength; i++) result. append(a [i])i if (i a length -1 result. append(,)i result. append("]") return result. tostring( )i public static String tostring (long[] a)( StringBuffer result new StringBuffer("[") for(int i =0; i<a length; i++)t result. append(a[i]) if(i a length -1) result. append(",")i result return result. tostring( )i public static String tostring(float[ a) StringBuffer result new StringBuffer("[") for(int i =0; i<a length; 1++) (a[i]); if (i alength -1 result. append(,)i result. append("]")i public static string tostring(double [] a) StringBuffer result new String Buffer("[")i for (int i =0; i<alength; 1++) result. append(a[i])i f(i< al 1) (","); result. append("]") // Fill an array using a generator: public static void fill(object a.⊥eng public static void fill(object[] a, int from, int to, Generator gen for (int i = from; i< a lil public static void fill(boolean[ a, BooleanGenerator gen) t blic static void 第10页共106页 wgqqh. com/shhgs/tij. html
Chapter 11: Collections of Objects 第 10 页 共 106 页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com result.append("]"); return result.toString( ); } public static String toString(int[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(long[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(float[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } public static String toString(double[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString( ); } // Fill an array using a generator: public static void fill(Object[] a, Generator gen) { fill(a, 0, a.length, gen); } public static void fill(Object[] a, int from, int to, Generator gen) { for(int i = from; i < to; i++) a[i] = gen.next( ); } public static void fill(boolean[] a, BooleanGenerator gen) { fill(a, 0, a.length, gen); } public static void