- 写在前面的话
- 引言
- 第 1 章 对象入门
- 第 2 章 一切都是对象
- 第 3 章 控制程序流程
- 第 4 章 初始化和清除
- 第 5 章 隐藏实施过程
- 第 6 章 类再生
- 第 7 章 多形性
- 第 8 章 对象的容纳
- 第 9 章 违例差错控制
- 第 10 章 Java IO 系统
- 第 11 章 运行期类型鉴定
- 第 12 章 传递和返回对象
- 第 十三 章 创建窗口和程序片
- 第 14 章 多线程
- 第 15 章 网络编程
- 第 16 章 设计范式
- 第 17 章 项目
- 附录 A 使用非 JAVA 代码
- 附录 B 对比 C++和 Java
- 附录 C Java 编程规则
- 附录 D 性能
- 附录 E 关于垃圾收集的一些话
- 附录 F 推荐读物
8.7 新集合
对我来说,集合类属于最强大的一种工具,特别适合在原创编程中使用。大家可能已感觉到我对 Java 1.1 提供的集合多少有点儿失望。因此,看到 Java 1.2 对集合重新引起了正确的注意后,确实令人非常愉快。这个版本的集合也得到了完全的重新设计(由 Sun 公司的 Joshua Bloch)。我认为新设计的集合是 Java 1.2 中两项最主要的特性之一(另一项是 Swing 库,将在第 13 章叙述),因为它们极大方便了我们的编程,也使 Java 变成一种更成熟的编程系统。
有些设计使得元素间的结合变得更紧密,也更容易让人理解。例如,许多名字都变得更短、更明确了,而且更易使用;类型同样如此。有些名字进行了修改,更接近于通俗:我感觉特别好的一个是用“反复器”(Inerator)代替了“枚举”(Enumeration)。
此次重新设计也加强了集合库的功能。现在新增的行为包括链接列表、队列以及撤消组队(即“双终点队列”)。
集合库的设计是相当困难的(会遇到大量库设计问题)。在 C++中,STL 用多个不同的类来覆盖基础。这种做法比起 STL 以前是个很大的进步,那时根本没做这方面的考虑。但仍然没有很好地转换到 Java 里面。结果就是一大堆特别容易混淆的类。在另一个极端,我曾发现一个集合库由单个类构成:colleciton,它同时作为 Vector 和 Hashtable 使用。新集合库的设计者则希望达到一种新的平衡:实现人们希望从一个成熟集合库上获得的完整功能,同时又要比 STL 和其他类似的集合库更易学习和使用。这样得到的结果在某些场合显得有些古怪。但和早期 Java 库的一些决策不同,这些古怪之处并非偶然出现的,而是以复杂性作为代价,在进行仔细权衡之后得到的结果。这样做也许会延长人们掌握一些库概念的时间,但很快就会发现自己很乐于使用那些新工具,而且变得越来越离不了它。
新的集合库考虑到了“容纳自己对象”的问题,并将其分割成两个明确的概念:
(1) 集合(Collection):一组单独的元素,通常应用了某种规则。在这里,一个 List(列表)必须按特定的顺序容纳元素,而一个 Set(集)不可包含任何重复的元素。相反,“包”(Bag)的概念未在新的集合库中实现,因为“列表”已提供了类似的功能。
(2) 映射(Map):一系列“键-值”对(这已在散列表身上得到了充分的体现)。从表面看,这似乎应该成为一个“键-值”对的“集合”,但假若试图按那种方式实现它,就会发现实现过程相当笨拙。这进一步证明了应该分离成单独的概念。另一方面,可以方便地查看 Map 的某个部分。只需创建一个集合,然后用它表示那一部分即可。这样一来,Map 就可以返回自己键的一个 Set、一个包含自己值的 List 或者包含自己“键-值”对的一个 List。和数组相似,Map 可方便扩充到多个“维”,毋需涉及任何新概念。只需简单地在一个 Map 里包含其他 Map(后者又可以包含更多的 Map,以此类推)。
Collection 和 Map 可通过多种形式实现,具体由编程要求决定。下面列出的是一个帮助大家理解的新集合示意图:
这张图刚开始的时候可能让人有点儿摸不着头脑,但在通读了本章以后,相信大家会真正理解它实际只有三个集合组件:Map,List 和 Set。而且每个组件实际只有两、三种实现方式(注释⑥),而且通常都只有一种特别好的方式。只要看出了这一点,集合就不会再令人生畏。
⑥:写作本章时,Java 1.2 尚处于β测试阶段,所以这张示意图没有包括以后会加入的 TreeSet。
虚线框代表“接口”,点线框代表“抽象”类,而实线框代表普通(实际)类。点线箭头表示一个特定的类准备实现一个接口(在抽象类的情况下,则是“部分”实现一个接口)。双线箭头表示一个类可生成箭头指向的那个类的对象。例如,任何集合都可以生成一个反复器(Iterator),而一个列表可以生成一个 ListIterator(以及原始的反复器,因为列表是从集合继承的)。
致力于容纳对象的接口是 Collection,List,Set 和 Map。在传统情况下,我们需要写大量代码才能同这些接口打交道。而且为了指定自己想使用的准确类型,必须在创建之初进行设置。所以可能创建下面这样的一个 List:
List x = new LinkedList();
当然,也可以决定将 x 作为一个 LinkedList 使用(而不是一个普通的 List),并用 x 负载准确的类型信息。使用接口的好处就是一旦决定改变自己的实施细节,要做的全部事情就是在创建的时候改变它,就象下面这样:
List x = new ArrayList();
其余代码可以保持原封不动。
在类的分级结构中,可看到大量以“Abstract”(抽象)开头的类,这刚开始可能会使人感觉迷惑。它们实际上是一些工具,用于“部分”实现一个特定的接口。举个例子来说,假如想生成自己的 Set,就不是从 Set 接口开始,然后自行实现所有方法。相反,我们可以从 AbstractSet 继承,只需极少的工作即可得到自己的新类。尽管如此,新集合库仍然包含了足够的功能,可满足我们的几乎所有需求。所以考虑到我们的目的,可忽略所有以“Abstract”开头的类。
因此,在观看这张示意图时,真正需要关心的只有位于最顶部的“接口”以及普通(实际)类——均用实线方框包围。通常需要生成实际类的一个对象,将其上溯造型为对应的接口。以后即可在代码的任何地方使用那个接口。下面是一个简单的例子,它用 String 对象填充一个集合,然后打印出集合内的每一个元素:
//: SimpleCollection.java // A simple example using the new Collections package c08.newcollections; import java.util.*; public class SimpleCollection { public static void main(String[] args) { Collection c = new ArrayList(); for(int i = 0; i < 10; i++) c.add(Integer.toString(i)); Iterator it = c.iterator(); while(it.hasNext()) System.out.println(it.next()); } } ///:~
新集合库的所有代码示例都置于子目录 newcollections 下,这样便可提醒自己这些工作只对于 Java 1.2 有效。这样一来,我们必须用下述代码来调用程序:
java c08.newcollections.SimpleCollection
采用的语法与其他程序是差不多的。
大家可以看到新集合属于 java.util 库的一部分,所以在使用时不需要再添加任何额外的 import 语句。
main() 的第一行创建了一个 ArrayList 对象,然后将其上溯造型成为一个集合。由于这个例子只使用了 Collection 方法,所以从 Collection 继承的一个类的任何对象都可以正常工作。但 ArrayList 是一个典型的 Collection,它代替了 Vector 的位置。
显然,add() 方法的作用是将一个新元素置入集合里。然而,用户文档谨慎地指出 add()“保证这个集合包含了指定的元素”。这一点是为 Set 作铺垫的,后者只有在元素不存在的前提下才会真的加入那个元素。对于 ArrayList 以及其他任何形式的 List,add() 肯定意味着“直接加入”。
利用 iterator() 方法,所有集合都能生成一个“反复器”(Iterator)。反复器其实就象一个“枚举”(Enumeration),是后者的一个替代物,只是:
(1) 它采用了一个历史上默认、而且早在 OOP 中得到广泛采纳的名字(反复器)。
(2) 采用了比 Enumeration 更短的名字:hasNext() 代替了 hasMoreElement(),而 next() 代替了 nextElement()。
(3) 添加了一个名为 remove() 的新方法,可删除由 Iterator 生成的上一个元素。所以每次调用 next() 的时候,只需调用 remove() 一次。
在 SimpleCollection.java 中,大家可看到创建了一个反复器,并用它在集合里遍历,打印出每个元素。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论