Java集合类

2018-9-15

对我来说,集合类属于最强大的一种工具,特别适合在原创编程中使用。大家可能已感觉到我对java1.1提供的集合多少有点儿失望。因此,看到Java 1.2对集合重新引起了正确的注意后,确实令人非常愉快。这个版本的集合也得到了完全的重新设计(由Sun公司的Joshua Bloch)。我认为新设计的集合是Java 1.2中两项最主要的特性之一(另一项是Swing库),因为它们极大方便了我们的编程,也使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中,大家可看到创建了一个反复器,并用它在集合里遍历,打印出每个元素。






暗色背景
手机扫码阅读本页