对java.util的总结

 java.util包中包含了一些在Java 2中新增加的最令人兴奋的增强功能:类集。一个类集
(collection)是一组对象。类集的增加使得许多java.util中的成员在结构和体系结构上发生
根本的改变。它也扩展了包可以被应用的任务范围。类集是被所有Java程序员紧密关注的
最新型的技术。
除了类集,java.util还包含了支持范围广泛的函数的各种各样的类和接口。这些类和接
口被核心的Java包广泛使用,同时当然也可以被你编写的程序所使用。对它们的应用包括
产生伪随机数,对日期和时间的操作,观测事件,对位集的操作以及标记字符串。由于
java.util具有许多特性,因此它是Java中最被广泛使用的一个包。
java.util中包含的类如下。在Java 2中新增加的一些也被列出:
AbstractCollection (Java 2) EventObject Random
AbstractList (Java 2) GregorianCalendar ResourceBundle
AbstractMap (Java 2) HashMap (Java 2) SimpleTimeZone
AbstractSequentialList (Java 2) HashSet (Java 2) Stack
AbstractSet (Java 2) Hashtable StringTokenizer
ArrayList (Java 2) LinkedList (Java 2) Timer (Java 2, v1.3)
Arrays (Java 2) ListResourceBundle TimerTask (Java 2, v1.3)
BitSet Locale TimeZone
Calendar Observable TreeMap (Java 2)
Collections (Java 2) Properties TreeSet (Java 2)
Date PropertyPermission (Java 2) Vector
Dictionary PropertyResourceBundle WeakHashMap (Java 2)
java.util定义了如下的接口。注意其中大多数是在Java 2中新增加的。
Collection (Java 2) List (Java 2) Observer
Comparator (Java 2) ListIterator (Java 2) Set (Java 2)
Enumeration Map (Java 2) SortedMap (Java 2)
EventListener Map.Entry (Java 2) SortedSet (Java 2)
Iterator (Java 2)
ResourceBundle类,ListResourceBundle类和PropertyResourceBundle类帮助具有特定地
区资源的大型程序国际化。关于这些类的讨论,在这里从略。授权对系统属性进行读/写的
PropertyPermission类也超过了本书的讨论范围。EventObject和EventListener类将在第20章讨
论。下面将对剩下的类和接口做详细的讨论。

15.1 类集概述
Java的类集(Collection)框架使你的程序处理对象组的方法标准化。在Java 2出现之前,
Java提供了一些专门的类如Dictionary,Vector,Stack和Properties去存储和操作对象组。尽
管这些类非常有用,它们却缺少一个集中,统一的主题。因此例如说使用Vector的方法就
会与使用Properties的方法不同。以前的专门的方法也没有被设计成易于扩展和能适应新的
环境的形式。而类集解决了这些(以及其他的一些)问题。
类集框架被设计用于适应几个目的。首先,这种框架是高性能的。对基本类集(动态
数组,链接表,树和散列表)的实现是高效率的。一般很少需要人工去对这些“数据引擎”
编写代码(如果有的话)。第二点,框架必须允许不同类型的类集以相同的方式和高度互
操作方式工作。第三点,类集必须是容易扩展和/或修改的。为了实现这一目标,类集框架
被设计成包含一组标准的接口。对这些接口,提供了几个标准的实现工具(例如LinkedList,
HashSet和TreeSet),通常就是这样使用的。如果你愿意的话,也可以实现你自己的类集。
为了方便起见,创建用于各种特殊目的的实现工具。一部分工具可以使你自己的类集实现
更加容易。最后,增加了允许将标准数组融合到类集框架中的机制。
算法(Algorithms)是类集机制的另一个重要部分。算法操作类集,它在Collections类
中被定义为静态方法。因此它们可以被所有的类集所利用。每一个类集类不必实现它自己
的方案,算法提供了一个处理类集的标准方法。
由类集框架创建的另一项是Iterator接口。一个迭代程序(iterator)提供了一个多用途
的,标准化的方法,用于每次访问类集的一个元素。因此迭代程序提供了一种枚举类集内
容(enumerating the contents of a collection)的方法。因为每一个类集都实现Iterator,所以
通过由Iterator定义的方法,任一类集类的元素都能被访问到。因此,稍作修改,循环通过
集合的程序代码也可以被用来循环通过列表。
除了类集之外,框架定义了几个映射接口和类。映射(Maps)存储键/值对。尽管映射
在对项的正确使用上不是“类集”,但它们完全用类集集成。在类集框架的语言中,可以
获得映射的类集“视图”(collection-view)。这个“视图”包含了从存储在类集中的映射
得到的元素。因此,如果选择了一个映射,就可以将其当做一个类集来处理。
对于由java.util定义的原始类,类集机制被更新以便它们也能够集成到新的系统里。所
以理解下面的说法是很重要的:尽管类集的增加改变了许多原始工具类的结构,但它却不
会导致被抛弃。类集仅仅是提供了处理事情的一个更好的方法。
最后的一点:如果你对C++比较熟悉的话,那么你可以发现Java的类集技术与在C++中
定义的标准模板库(STL)相似。在C++中叫做容器(container),而在Java中叫做类集。

15.2 类集接口
类集框架定义了几个接口。本节对每一个接口都进行了概述。首先讨论类集接口是因
为它们决定了collection类的基本特性。不同的是,具体类仅仅是提供了标准接口的不同实
现。支持类集的接口总结在如下的表中:
接口描述
Collection 能使你操作对象组,它位于类集层次结构的顶层
List 扩展Collection去处理序列(对象的列表)
Set 扩展Collection去处理集合,集合必须包含唯一元素
SortedSet 扩展Set去处理排序集合
除了类集接口之外,类集也使用Comparator,Iterator和ListIterator接口。关于这些接口
将在本章后面做更深入的描述。简单地说,Comparator接口定义了两个对象如何比较;
Iterator和ListIterator接口枚举类集中的对象。
为了在它们的使用中提供最大的灵活性,类集接口允许对一些方法进行选择。可选择
的方法使得使用者可以更改类集的内容。支持这些方法的类集被称为可修改的
(modifiable)。不允许修改其内容的类集被称为不可修改的(unmodifiable)。如果对一
个不可修改的类集使用这些方法,将引发一个UnsupportedOperationException异常。所有内
置的类集都是可修改的。
下面讨论类集接口。
15.2.1 类集接口
Collection接口是构造类集框架的基础。它声明所有类集都将拥有的核心方法。这些方
法被总结在表15-1中。因为所有类集实现Collection,所以熟悉它的方法对于清楚地理解框
架是必要的。其中几种方法可能会引发一个UnsupportedOperationException异常。正如上面
解释的那样,这些发生在当类集不能被修改时。当一个对象与另一个对象不兼容,例如当
企图增加一个不兼容的对象到一个类集中时。将产生一个ClassCastException异常。
表15-1 由Collection 定义的方法
方法描述
boolean add(Object obj) 将obj加入到调用类集中。如果obj被加入到类集中了,则返
回true;如果obj已经是类集中的一个成员或类集不能被复制
时,则返回false
boolean addAll(Collection c) 将c中的所有元素都加入到调用类集中,如果操作成功(也
就是说元素被加入了),则返回true;否则返回false
void clear( ) 从调用类集中删除所有元素
boolean contains(Object obj) 如果obj是调用类集的一个元素,则返回true,否则,返回false

boolean containsAll(Collection c) 如果调用类集包含了c中的所有元素,则返回true;否则,返
回false
boolean equals(Object obj) 如果调用类集与obj相等,则返回true;否则返回false
int hashCode( ) 返回调用类集的散列码
boolean isEmpty( ) 如果调用类集是空的,则返回true;否则返回false
Iterator iterator( ) 返回调用类集的迭代程序
Boolean remove(Object obj) 从调用类集中删除obj的一个实例。如果这个元素被删除了,
则返回true;否则返回false
Boolean removeAll(Collection c) 从调用类集中删除c的所有元素。如果类集被改变了(也就
是说元素被删除了),则返回true;否则返回false
Boolean retainAll(Collection c) 删除调用类集中除了包含在c中的元素之外的全部元素。如
果类集被改变了(也就是说元素被删除了),则返回true,
否则返回false
int size( ) 返回调用类集中元素的个数
Object[ ] toArray( ) 返回一个数组,该数组包含了所有存储在调用类集中的元
素。数组元素是类集元素的拷贝
Object[ ] toArray(Object array[ ]) 返回一个数组,该数组仅仅包含了那些类型与数组元素类型
匹配的类集元素。数组元素是类集元素的拷贝。如果array
的大小与匹配元素的个数相等,它们被返回到array。如果
array的大小比匹配元素的个数小,将分配并返回一个所需大
小的新数组,如果array的大小比匹配元素的个数大,在数组
中,在类集元素之后的单元被置为null。如果任一类集元素
的类型都不是array 的子类型, 则引发一个
ArrayStoreException异常
调用add( )方法可以将对象加入类集。注意add( )带一个Object类型的参数。因为Object
是所有类的超类,所以任何类型的对象可以被存储在一个类集中。然而原始类型可能不行。
例如,一个类集不能直接存储类型int,char,double等的值。当然如果想存储这些对象,也
可以使用在第14章中介绍的原始类型包装器之一。可以通过调用addAll( )方法将一个类集的
全部内容增加到另一个类集中。
可以通过调用remove( )方法将一个对象删除。为了删除一组对象,可以调用removeAll( )
方法。调用retainAll( )方法可以将除了一组指定的元素之外的所有元素删除。为了清空类集,
可以调用clear( )方法。
通过调用contains( )方法,可以确定一个类集是否包含了一个指定的对象。为了确定一
个类集是否包含了另一个类集的全部元素,可以调用containsAll( )方法。当一个类集是空的
时候,可以通过调用isEmpty( )方法来予以确认。调用size( )方法可以获得类集中当前元素
的个数。

toArray( )方法返回一个数组,这个数组包含了存储在调用类集中的元素。这个方法比
它初看上去的能力要更重要。经常使用类数组语法来处理类集的内容是有优势的。通过在
类集和数组之间提供一条路径,可以充分利用这两者的优点。
调用equals( )方法可以比较两个类集是否相等。“相等”的精确含义可以不同于从类集
到类集。例如,可以执行equals( )方法以便用于比较存储在类集中的元素的值,换句话说,
equals( )方法能比较对元素的引用。
一个更加重要的方法是iterator( ),该方法对类集返回一个迭代程序。正如你将看到的
那样,当使用一个类集框架时,迭代程序对于成功的编程来说是至关重要的。
15.2.2 List接口
List接口扩展了Collection并声明存储一系列元素的类集的特性。使用一个基于零的下
标,元素可以通过它们在列表中的位置被插入和访问。一个列表可以包含复制元素。
除了由Collection定义的方法之外,List还定义了一些它自己的方法,这些方法总结在
表15-2中。再次注意当类集不能被修改时,其中的几种方法引发UnsupportedOperation
Exception异常。当一个对象与另一个不兼容,例如当企图将一个不兼容的对象加入一个类
集中时,将产生ClassCastException异常。
表15-2 由List 定义的方法
方法描述
void add(int index, Object obj) 将obj插入调用列表,插入位置的下标由index传递。任何已存
在的,在插入点以及插入点之后的元素将前移。因此,没有元
素被覆盖
boolean addAll(int index, Collection c) 将c中的所有元素插入到调用列表中,插入点的下标由index传
递。在插入点以及插入点之后的元素将前移。因此,没有元素
被覆盖。如果调用列表改变了,则返回true;否则返回false
Object get(int index) 返回存储在调用类集内指定下标处的对象
int indexOf(Object obj) 返回调用列表中obj的第一个实例的下标。如果obj不是列表中
的元素,则返回-1
int lastIndexOf(Object obj) 返回调用列表中obj的最后一个实例的下标。如果obj不是列表
中的元素,则返回-1
ListIterator listIterator( ) 返回调用列表开始的迭代程序
ListIterator listIterator(int index) 返回调用列表在指定下标处开始的迭代程序
Object remove(int index) 删除调用列表中index位置的元素并返回删除的元素。删除后,
列表被压缩。也就是说,被删除元素后面的元素的下标减一
Object set(int index, Object obj) 用obj对调用列表内由index指定的位置进行赋值
List subList(int start, int end) 返回一个列表,该列表包括了调用列表中从start到end?1的元
素。返回列表中的元素也被调用对象引用
对于由Collection定义的add( ) 和addAll( )方法,List增加了方法add(int, Object) 和

addAll(int, Collection)。这些方法在指定的下标处插入元素。由Collection定义的add(Object)
和addAll(Collection)的语义也被List改变了,以便它们在列表的尾部增加元素。
为了获得在指定位置存储的对象,可以用对象的下标调用get( )方法。为了给类表中的
一个元素赋值,可以调用set( )方法,指定被改变的对象的下标。调用indexOf( )或lastIndexOf( )
可以得到一个对象的下标。
通过调用subList( )方法,可以获得列表的一个指定了开始下标和结束下标的子列表。
正如你能想象到的,subList( )方法使得列表处理十分方便。
15.2.3 集合接口
集合接口定义了一个集合。它扩展了Collection并说明了不允许复制元素的类集的特
性。因此,如果试图将复制元素加到集合中时,add( )方法将返回false。它本身并没有定义
任何附加的方法。
15.2.4 SortedSet接口
SortedSet接口扩展了Set并说明了按升序排列的集合的特性。除了那些由Set定义的方法
之外,由SortedSet接口说明的方法列在表15-3中。当没有项包含在调用集合中时,其中的
几种方法引发NoSuchElementException异常。当对象与调用集合中的元素不兼容时,引发
ClassCastException 异常。如果试图使用null 对象, 而集合不允许null 时, 引发
NullPointerException异常。
表15-3 由SortedSet 定义的方法
方法描述
Comparator comparator( ) 返回调用被排序集合的比较函数,如果对该集合使用自然顺
序,则返回null
Object first( ) 返回调用被排序集合的第一个元素
SortedSet headSet(Object end) 返回一个包含那些小于end的元素的SortedSet,那些元素包
含在调用被排序集合中。返回被排序集合中的元素也被调用
被排序集合所引用
Object last( ) 返回调用被排序集合的最后一个元素
SortedSet subSet(Object start, Object end) 返回一个SortedSet,它包括了从start到end?1的元素。返回类
集中的元素也被调用对象所引用
SortedSet tailSet(Object start) 返回一个SortedSet,它包含了那些包含在分类集合中的大于
等于start的元素。返回集合中的元素也被调用对象所引用
SortedSet定义了几种方法,使得对集合的处理更加方便。调用first( )方法,可以获得集
合中的第一个对象。调用last( )方法,可以获得集合中的最后一个元素。调用subSet( )方法,
可以获得排序集合的一个指定了第一个和最后一个对象的子集合。如果需要得到从集合的
第一个元素开始的一个子集合,可以使用headSet( )方法。如果需要获得集合尾部的一个子
集合,可以使用tailSet( )方法。

15.3 Collection类
现在,你已经熟悉了类集接口,下面开始讨论实现它们的标准类。一些类提供了完整
的可以被使用的工具。另一些类是抽象的,提供主框架工具,作为创建具体类集的起始点。
没有Collection类是同步的,但正如你将在本章后面看到的那样,有可能获得同步版本。
标准的Collection类总结在下面的表中。
类描述
AbstractCollection 实现大多数Collection接口
AbstractList 扩展AbstractCollection并实现大多数List接口
AbstractSequentialList 为了被类集使用而扩展AbstractList,该类集使用连续而不是随机方
式访问其元素
LinkedList 通过扩展AbstractSequentialList来实现链接表
ArrayList 通过扩展AbstractList来实现动态数组
AbstractSet 扩展AbstractCollection并实现大多数Set接口
HashSet 为了使用散列表而扩展AbstractSet
TreeSet 实现存储在树中的一个集合。扩展AbstractSet
注意:除了Collection类外,还有几个从以前版本遗留下来的类,如Vector,Stack
和Hashtable均被重新设计成支持类集的形式。这些内容将在本章后面讨论。
下面讨论具体的Collection类,举例说明它们的用法。
15.3.1 ArrayList类
ArrayList类扩展AbstractList并执行List接口。ArrayList支持可随需要而增长的动态数
组。在Java中,标准数组是定长的。在数组创建之后,它们不能被加长或缩短,这也就意
味着你必须事先知道数组可以容纳多少元素。但是,你直到运行时才能知道需要多大的数
组。为了解决这个问题,类集框架定义了ArrayList。本质上,ArrayList是对象引用的一个
变长数组。也就是说,ArrayList能够动态地增加或减小其大小。数组列表以一个原始大小
被创建。当超过了它的大小,类集自动增大。当对象被删除后,数组就可以缩小。
注意:动态数组也被从以前版本遗留下来的类Vector所支持。关于这一点,将在
本章后面介绍。
ArrayList有如下的构造函数:
ArrayList( )
ArrayList(Collection c)
ArrayList(int capacity)
其中第一个构造函数建立一个空的数组列表。第二个构造函数建立一个数组列表,该
306 第2 部分Java 库
数组列表由类集c中的元素初始化。第三个构造函数建立一个数组列表,该数组有指定的初
始容量(capacity)。容量是用于存储元素的基本数组的大小。当元素被追加到数组列表上
时,容量会自动增加。
下面的程序展示了ArrayList的一个简单应用。首先创建一个数组列表,接着添加类型
String的对象(回想一个引用字符串被转化成一个字符串(String)对象)。接着列表被显
示出来。将其中的一些元素删除后,再一次显示列表。
// Demonstrate ArrayList.
import java.util.*;
class ArrayListDemo {
public static void main(String args[]) {
// create an array list
ArrayList al = new ArrayList();
System.out.println("Initial size of al: " +
al.size());
// add elements to the array list
al.add("C");
al.add("A");
al.add("E");
al.add("B");
al.add("D");
al.add("F");
al.add(1, "A2");
System.out.println("Size of al after additions: " +
al.size());
// display the array list
System.out.println("Contents of al: " + al);
// Remove elements from the array list
al.remove("F");
al.remove(2);
System.out.println("Size of al after deletions: " +
al.size());
System.out.println("Contents of al: " + al);
}
}
该程序的输出如下所示:
Initial size of al: 0
Size of al after additions: 7
Contents of al: [C, A2, A, E, B, D, F]
Size of al after deletions: 5
Contents of al: [C, A2, E, B, D]
注意a1开始时是空的,当添加元素后,它的大小增加了。当有元素被删除后,它的大小又会变小。


在前面的例子中,使用由toString( )方法提供的默认的转换显示类集的内容,toString( )
方法是从AbstractCollection继承下来的。尽管它对简短的例子程序来说是足够了,然而很少
使用这种方法去显示实际中的类集的内容。通常编程者会提供自己的输出程序。但在下面
的几个例子中,仍将采用由toString( )方法创建的默认输出。
尽管当对象被存储在ArrayList对象中时,其容量会自动增加。仍可以通过调用
ensureCapacity( )方法来人工地增加ArrayList的容量。如果事先知道将在当前能够容纳的类
集中存储许许多多的项时,你可能会想这样做。在开始时,通过一次性地增加它的容量,
就能避免后面的再分配。因为再分配是很花时间的,避免不必要的处理可以改善性能。
ensureCapacity( )方法的特征如下所示:
void ensureCapacity(int cap)
这里,cap是新的容量。
相反地,如果想要减小在ArrayList对象之下的数组的大小,以便它有正好容纳当前项
的大小,可以调用trimToSize( )方法。该方法说明如下:
void trimToSize( )
从数组列表(ArrayList)获得数组(Array)
当使用ArrayList时,有时想要获得一个实际的数组,这个数组包含了列表的内容。正
如前面解释的那样,可以通过调用方法toArray( )来实现它。下面是几个为什么可能想将类
集转换成为数组的原因:
? 对于特定的操作,可以获得更快的处理时间。
? 为了给方法传递数组,而方法不必重载去接收类集。
? 为了将新的基于类集的程序与不认识类集的老程序集成。
无论何种原因,如下面的例子程序所示,将ArrayList转换成数组是一件繁琐的事情。
// Convert an ArrayList into an array.
import // get array

Object ia[] = al.toArray();
int sum = 0;
// sum the array
for(int i=0; isum += ((Integer) ia[i]).intValue();
System.out.println("Sum is: " + sum);
}
}
该程序的输出如下所示:
Contents of al: [1, 2, 3, 4]
Sum is: 10
程序开始时创建一个整数的类集。正如上面做出的解释那样,由于不能将原始类型存
储在类集中,因此类型Integer的对象被创建并被保存。接下来,toArray( )方法被调用,它
获得了一个Objects数组。这个数组的内容被置为整型(Integer),接下来对这些值进行求
和。
15.3.2 LinkedList类
LinkedList类扩展AbstractSequentialList并执行List接口。它提供了一个链接列表数据结
构。它具有如下的两个构造函数,说明如下:
LinkedList( )
LinkedList(Collection c)
第一个构造函数建立一个空的链接列表。第二个构造函数建立一个链接列表,该链接
列表由类集c中的元素初始化。
除了它继承的方法之外,LinkedList类本身还定义了一些有用的方法,这些方法主要用
于操作和访问列表。使用addFirst( )方法可以在列表头增加元素;使用addLast( )方法可以在
列表的尾部增加元素。它们的形式如下所示:
void addFirst(Object obj)
void addLast(Object obj)
这里,obj是被增加的项。
调用getFirst( )方法可以获得第一个元素。调用getLast( )方法可以得到最后一个元素。
它们的形式如下所示:
Object getFirst( )
Object getLast( )
为了删除第一个元素,可以使用removeFirst( )方法;为了删除最后一个元素,可以调
用removeLast( )方法。它们的形式如下所示:
Object removeFirst( )
Object removeLast( )

下面的程序举例说明了几个LinkedList支持的方法。
// Demonstrate LinkedList.
import ll.add("B");
ll.add("D");
ll.add("E");
ll.add("C");
ll.addLast("Z");
ll.addFirst("A");
ll.add(1, "A2");
System.out.println("Original contents of ll: " + ll);
// remove elements from the linked list
ll.remove("F");
ll.remove(2);
System.out.println("Contents of ll after deletion: "
+ ll);
// remove first and last elements
ll.removeFirst();
ll.removeLast();
System.out.println("ll after deleting first and last: "
+ ll);
// get and set a value
Object val = ll.get(2);
ll.set(2, (String) val + " Changed");
System.out.println("ll after change: " + ll);
}
}
该程序的输出如下所示:
Original contents of ll: [A, A2, F, B, D, E, C, Z]
Contents of ll after deletion: [A, A2, D, E, C, Z]
ll after deleting first and last: [A2, D, E, C]
ll after change: [A2, D, E Changed, C]
因为LinkedList实现List接口,调用add(Object)将项目追加到列表的尾部,如同addLast( )
方法所做的那样。使用add( )方法的add(int, Object)形式,插入项目到指定的位置,如例子

程序中调用add(1,“A2”)的举例。
注意如何通过调用get( )和set( )方法而使得ll中的第三个元素发生了改变。为了获得一
个元素的当前值,通过get( )方法传递存储该元素的下标值。为了对这个下标位置赋一个新
值,通过set( )方法传递下标和对应的新值。

15.3.3 HashSet类
HashSet扩展AbstractSet并且实现Set接口。它创建一个类集,该类集使用散列表进行存
储。正像大多数读者很可能知道的那样,散列表通过使用称之为散列法的机制来存储信息。
在散列(hashing)中,一个关键字的信息内容被用来确定唯一的一个值,称为散列码(hash
code)。而散列码被用来当做与关键字相连的数据的存储下标。关键字到其散列码的转换
是自动执行的??你看不到散列码本身。你的程序代码也不能直接索引散列表。散列法的
优点在于即使对于大的集合,它允许一些基本操作如add( ),contains( ),remove( )和size( )
方法的运行时间保持不变。
下面的构造函数定义为:
HashSet( )
HashSet(Collection c)
HashSet(int capacity)
HashSet(int capacity, float fillRatio)
第一种形式构造一个默认的散列集合。第二种形式用c中的元素初始化散列集合。第三
种形式用capacity初始化散列集合的容量。第四种形式用它的参数初始化散列集合的容量和
填充比(也称为加载容量)。填充比必须介于0.0与1.0之间,它决定在散列集合向上调整大
小之前,有多少能被充满。具体的说,就是当元素的个数大于散列集合容量乘以它的填充
比时,散列集合被扩大。对于没有获得填充比的构造函数,默认使用0.75。
HashSet没有定义任何超过它的超类和接口提供的其他方法。
重要的是,注意散列集合并没有确保其元素的顺序,因为散列法的处理通常不让自己
参与创建排序集合。如果需要排序存储,另一种类集??TreeSet将是一个更好的选择。
这里是一个说明HashSet的例子。
// Demonstrate HashSet.
import java.util.*;
class HashSetDemo {
public static void main(String args[]) {
// create a hash set
HashSet hs = new HashSet();
// add elements to the hash set
hs.add("B");
hs.add("A");
hs.add("D");
hs.add("E");
hs.add("C");
hs.add("F");

System.out.println(hs);
}
}
下面是该程序的输出:
[A, F, E, D, C, B]
如上面解释的那样,元素并没有按顺序进行存储。
15.3.4 TreeSet类
TreeSet为使用树来进行存储的Set接口提供了一个工具,对象按升序存储。访问和检索
是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet是一个很好的
选择。
下面的构造函数定义为:
TreeSet( )
TreeSet(Collection c)
TreeSet(Comparator comp)
TreeSet(SortedSet ss)
第一种形式构造一个空的树集合,该树集合将根据其元素的自然顺序按升序排序。第
二种形式构造一个包含了c的元素的树集合。第三种形式构造一个空的树集合,它按照由
comp指定的比较函数进行排序(比较函数将在本章后面介绍)。第四种形式构造一个包含
了ss的元素的树集合
这里是一个说明TreeSet的例子。
// Demonstrate TreeSet.
import java.util.*;
class TreeSetDemo {
public static void main(String args[]) {
// Create a tree set
TreeSet ts = new TreeSet();
// Add elements to the tree set
ts.add("C");
ts.add("A");
ts.add("B");
ts.add("E");
ts.add("F");
ts.add("D");
System.out.println(ts);
}
}
这个程序的输出如下所示:
[A, B, C, D, E, F]
正如上面解释的那样,因为TreeSet按树存储其元素,它们被按照排序次序自动安排,

如程序输出所示。
15.4 通过迭代函数访问类集
通常希望循环通过类集中的元素。例如,可能会希望显示每一个元素。到目前为止,
处理这个问题的最简单方法是使用iterator,iterator是一个或者实现Iterator或者实现
ListIterator接口的对象。Iterator可以完成循环通过类集,从而获得或删除元素。ListIterator
扩展Iterator,允许双向遍历列表,并可以修改单元。Iterator接口说明的方法总结在表15-4
中。ListIterator接口说明的方法总结在表15-5中。
表15-4 由Iterator 定义的方法
方法描述
boolean hasNext( ) 如果存在更多的元素,则返回true,否则返回false
Object next( ) 返回下一个元素。如果没有下一个元素,则引发NoSuchElementException异常
void remove( ) 删除当前元素,如果试图在调用next( )方法之后,调用remove( )方法,则引发
IllegalStateException异常
表15-5 由ListIterator 定义的方法
方法描述
void add(Object obj) 将obj插入列表中的一个元素之前,该元素在下一次调用next( )方法时,被返

boolean hasNext( ) 如果存在下一个元素,则返回true;否则返回false
boolean hasPrevious( ) 如果存在前一个元素,则返回true;否则返回false
Object next( ) 返回下一个元素,如果不存在下一个元素,则引发一个NoSuchElement
Exception异常
int nextIndex( ) 返回下一个元素的下标,如果不存在下一个元素,则返回列表的大小
Object previous( ) 返回前一个元素,如果前一个元素不存在,则引发一个NoSuchElement
Exception异常
int previousIndex( ) 返回前一个元素的下标,如果前一个元素不存在,则返回-1
void remove( ) 从列表中删除当前元素。如果remove( )方法在next( )方法或previous( )方法调
用之前被调用,则引发一个IllegalStateException异常
void set(Object obj) 将obj赋给当前元素。这是上一次调用next( )方法或previous( )方法最后返回的
元素
15.4.1 使用迭代函数
在通过迭代函数访问类集之前,必须得到一个迭代函数。每一个Collection类都提供一
个iterator( )函数,该函数返回一个对类集头的迭代函数。通过使用这个迭代函数对象,可
以访问类集中的每一个元素,一次一个元素。通常,使用迭代函数循环通过类集的内容,

步骤如下:
1. 通过调用类集的iterator( )方法获得对类集头的迭代函数。
2. 建立一个调用hasNext( )方法的循环,只要hasNext( )返回true,就进行循环迭代。
3. 在循环内部,通过调用next( )方法来得到每一个元素。
对于执行List的类集,也可以通过调用ListIterator来获得迭代函数。正如上面解释的那
样,列表迭代函数提供了前向或后向访问类集的能力,并可让你修改元素。否则,ListIterator
如同Iterator功能一样。
这里是一个实现这些步骤的例子,说明了Iterator和ListIterator。它使用ArrayList对象,
但是总的原则适用于任何类型的类集。当然,ListIterator只适用于那些实现List接口的类集。
// Demonstrate iterators.
import java.util.*;
class IteratorDemo {
public static void main(String args[]) {
// create an array list
ArrayList al = new ArrayList();
// add elements to the array list
al.add("C");
al.add("A");
al.add("E");
al.add("B");
al.add("D");
al.add("F");
// use iterator to display contents of al
System.out.print("Original contents of al: ");
Iterator itr = al.iterator();
while(itr.hasNext()) {
Object element = itr.next();
System.out.print(element + " ");
}
System.out.println();
// modify objects being iterated
ListIterator litr = al.listIterator();
while(litr.hasNext()) {
Object element = litr.next();
litr.set(element + "+");
}
System.out.print("Modified contents of al: ");
itr = al.iterator();
while(itr.hasNext()) {
Object element = itr.next();
System.out.print(element + " ");
}
System.out.println();
314 第2 部分Java 库
// now, display the list backwards
System.out.print("Modified list backwards: ");
while(litr.hasPrevious()) {
Object element = litr.previous();
System.out.print(element + " ");
}
System.out.println();
}
}
程序的输出如下所示:
Original contents of al: C A E B D F
Modified contents of al: C+ A+ E+ B+ D+ F+
Modified list backwards: F+ D+ B+ E+ A+ C+
特别值得注意的是:列表是如何被反向显示的。在列表被修改之后,litr指向列表的末
端(记住,当到达列表末端时,litr.hasNext( )方法返回false)。为了以反向遍历列表,程序
继续使用litr,但这一次,程序检测它是否有前一个元素。只要它有前一个元素,该元素就
被获得并被显示出来。


15.5 将用户定义的类存储于Collection中
为了简单,前面的例子在类集中存储内置的对象,如String或Integer。当然,类集并没
有被限制为只能存储内置的对象。完全相反的是,类集的能力是它能存储任何类型的对象,
包括你所创建的类的对象。例如,考虑下面的例子,在这个例子中使用LinkedList存储信箱
地址。
// A simple mailing list example.
import java.util.*;
class Address {
private String name;
private String street;
private String city;
private String state;
private String code;
Address(String n, String s, String c,
String st, String cd) {
name = n;
street = s;
city = c;
state = st;
code = cd;
}
public String toString() {
return name + "n" + street + "n" +
city + " " + state + " " + code;
}

}
class MailList {
public static void main(String args[]) {
LinkedList ml = new LinkedList();
// add elements to the linked list
ml.add(new Address("J.W. West", "11 Oak Ave",
"Urbana", "IL", "61801"));
ml.add(new Address("Ralph Baker", "1142 Maple Lane",
"Mahomet", "IL", "61853"));
ml.add(new Address("Tom Carlton", "867 Elm St",
"Champaign", "IL", "61820"));
Iterator itr = ml.iterator();
while(itr.hasNext()) {
Object element = itr.next();
System.out.println(element + "n");
}
System.out.println();
}
}
程序的输出如下所示:
J.W. West
11 Oak Ave
Urbana IL 61801
Ralph Baker
1142 Maple Lane
Mahomet IL 61853
Tom Carlton
867 Elm St
Champaign IL 61820
除了在类集中存储用户定义的类之外,关于上面程序的另一个重要的,值得注意的事
情是它是非常短的。当考虑用50行代码建立一个能够实现存储,检索,以及处理信箱地址
的链表时,类集框架的能力就变得显而易见了。正如大多数读者知道的那样,如果所有这
些功能都必须用人工编写代码的话,程序将比现在的长好几倍。类集对许多不同的编程问
题提供了现成的解决方案。每当情况出现时,就可以用它们。
15.6 处理映射
正如在本章开始时所谈到的,除了类集,Java 2还在java.util中增加了映射。映射(map)
是一个存储关键字和值的关联或者说是关键字/值对的对象。给定一个关键字,可以得到它
的值。关键字和值都是对象。关键字必须是唯一的。但值是可以被复制的。有些映射可以
接收null关键字和null值。而有的则不行。

15.6.1 映射接口
因为映射接口定义了映射的特征和本质,因此关于映射的讨论从这里开始。下面的接
口支持映射:
接口描述
Map 映射唯一关键字给值
Map.Entry 描述映射中的元素(关键字/值对)。这是Map的一个内部类
SortedMap 扩展Map以便关键字按升序保持
下面对每个接口依次进行讨论。
Map 接口
Map接口映射唯一关键字到值。关键字(key)是以后用于检索值的对象。给定一个关
键字和一个值,可以存储这个值到一个Map对象中。当这个值被存储以后,就可以使用它
的关键字来检索它。由Map说明的方法总结在表15-6中。当调用的映射中没有项存在时,
其中的几种方法会引发一个NoSuchElementException异常。而当对象与映射中的元素不兼容
时,引发一个ClassCastException异常。如果试图使用映射不允许使用的null对象时,则引发
一个NullPointerException异常。当试图改变一个不允许修改的映射时,则引发一个
UnsupportedOperationException异常。
表15-6 由Map 定义的方法
方法描述
void clear( ) 从调用映射中删除所有的关键字/值对
boolean containsKey(Object k) 如果调用映射中包含了作为关键字的k,则返回true;否则返回
false
boolean containsValue(Object v) 如果映射中包含了作为值的v,则返回true;否则返回false
Set entrySet( ) 返回包含了映射中的项的集合(Set)。该集合包含了类型
Map.Entry的对象。这个方法为调用映射提供了一个集合“视图”
Boolean equals(Object obj) 如果obj是一个Map并包含相同的输入,则返回true;否则返回false
Object get(Object k) 返回与关键字k相关联的值
int hashCode( ) 返回调用映射的散列码
boolean isEmpty( ) 如果调用映射是空的,则返回true;否则返回false
Set keySet( ) 返回一个包含调用映射中关键字的集合(Set)。这个方法为调用
映射的关键字提供了一个集合“视图”
Object put(Object k, Object v) 将一个输入加入调用映射,覆盖原先与该关键字相关联的值。关
键字和值分别为k和v。如果关键字已经不存在了,则返回null;
否则,返回原先与关键字相关联的值
void putAll(Map m) 将所有来自m的输入加入调用映射
Object remove(Object k) 删除关键字等于k的输入

续表
方法描述
int size( ) 返回映射中关键字/值对的个数
Collection values( ) 返回一个包含了映射中的值的类集。这个方法为映射中的值提供
了一个类集“视图”
映射循环使用两个基本操作:get( )和put( )。使用put( )方法可以将一个指定了关键字和
值的值加入映射。为了得到值,可以通过将关键字作为参数来调用get( )方法。调用返回该
值。
正如前面谈到的,映射不是类集,但可以获得映射的类集“视图”。为了实现这种功
能,可以使用entrySet( )方法,它返回一个包含了映射中元素的集合(Set)。为了得到关键
字的类集“视图”,可以使用keySet( )方法。为了得到值的类集“视图”,可以使用values( )
方法。类集“视图”是将映射集成到类集框架内的手段。
SortedMap 接口
SortedMap接口扩展了Map,它确保了各项按关键字升序排序。由SortedMap说明的方
法总结在表15-7中。当调用映射中没有的项时,其中的几种方法引发一个NoSuchElement
Exception异常。当对象与映射中的元素不兼容时,则引发一个ClassCastException异常。当
试图使用映射不允许使用的null对象时,则引发一个NullPointerException异常。
表15-7 由SortedMap 定义的方法
方法描述
Comparator comparator( ) 返回调用排序映射的比较函数。如果调用映射使用的是
自然顺序的话,则返回null
Object firstKey( ) 返回调用映射的第一个关键字
SortedMap headMap(Object end) 返回一个排序映射,该映射包含了那些关键字小于end
的映射输入
Object lastKey( ) 返回调用映射的最后一个关键字
SortedMap subMap(Object start, Object end) 返回一个映射,该映射包含了那些关键字大于等于start
同时小于end的输入
SortedMap tailMap(Object start) 返回一个映射,该映射包含了那些关键字大于等于start
的输入
排序映射允许对子映射(换句话说,就是映射的子集)进行高效的处理。使用
headMap( ),tailMap( )或subMap( )方法可以获得子映射。调用firstKey( )方法可以获得集合
的第一个关键字。而调用lastKey( )方法可以获得集合的最后一个关键字。
Map.Entry 接口
Map.Entry接口使得可以操作映射的输入。回想由Map接口说明的entrySet( )方法,调用
该方法返回一个包含映射输入的集合(Set)。这些集合元素的每一个都是一个Map.Entry

对象。表15-8总结了由该接口说明的方法。
表15-8 由Map.Entry 定义的方法
方法描述
boolean equals(Object obj) 如果obj是一个关键字和值都与调用对象相等的Map.Entry,则返回true
Object getKey( ) 返回该映射项的关键字
Object getValue( ) 返回该映射项的值
int hashCode( ) 返回该映射项的散列值
Object setValue(Object v) 将这个映射输入的值赋给v。如果v不是映射的正确类型,则引发一个
ClassCastException 异常。如果v 存在问题, 则引发一个
IllegalArgumentException异常。如果v是null而映射又不允许null关键字,
则引发一个NullPointerException异常。如果映射不能被改变,则引发一
个UnsupportedOperationException异常。
15.6.2 映射类
有几个类提供了映射接口的实现。可以被用做映射的类总结如下:
类描述
AbstractMap 实现大多数的Map接口
HashMap 将AbstractMap扩展到使用散列表
TreeMap 将AbstractMap扩展到使用树
WeakHashMap 将AbstractMap扩展到使用弱关键字散列表
注意AbstractMap对三个具体的映射实现来说,是一个超类。WeakHashMap实现一个使
用“弱关键字”的映射,它允许映射中的元素,当该映射的关键字不再被使用时,被放入
回收站。关于这个类,在这里不做更深入的讨论。其他的类将在下面介绍。
HashMap 类
HashMap类使用散列表实现Map接口。这允许一些基本操作如get( )和put( )的运行时间
保持恒定,即便对大型集合,也是这样的。
下面的构造函数定义为:
HashMap( )
HashMap(Map m)
HashMap(int capacity)
HashMap(int capacity, float fillRatio)
第一种形式构造一个默认的散列映射。第二种形式用m的元素初始化散列映射。第三
种形式将散列映射的容量初始化为capacity。第四种形式用它的参数同时初始化散列映射的
容量和填充比。容量和填充比的含义与前面介绍的HashSet中的容量和填充比相同。
HashMap实现Map并扩展AbstractMap。它本身并没有增加任何新的方法。
应该注意的是散列映射并不保证它的元素的顺序。因此,元素加入散列映射的顺序并

不一定是它们被迭代函数读出的顺序。
下面的程序举例说明了HashMap。它将名字映射到账目资产平衡表。注意集合“视图”
是如何获得和被使用的。
import java.util.*;
class HashMapDemo {
public static void main(String args[]) {
// Create a hash map
HashMap hm = new HashMap();
// Put elements to the map
hm.put("John Doe", new Double(3434.34));
hm.put("Tom Smith", new Double(123.22));
hm.put("Jane Baker", new Double(1378.00));
hm.put("Todd Hall", new Double(99.22));
hm.put("Ralph Smith", new Double(-19.08));
// Get a set of the entries
Set set = hm.entrySet();
// Get an iterator
Iterator i = set.iterator();
// Display elements
while(i.hasNext()) {
Map.Entry me = (Map.Entry)i.next();
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());
}
System.out.println();
// Deposit 1000 into John Doe's account
double balance = ((Double)hm.get("John Doe")).doubleValue();
hm.put("John Doe", new Double(balance + 1000));
System.out.println("John Doe's new balance: " +
hm.get("John Doe"));
}
}
该程序的输出如下所示:
Todd Hall: 99.22
Ralph Smith: -19.08
John Doe: 3434.34
Jane Baker: 1378.0
Tom Smith: 123.22
John Doe’s current balance: 4434.34
程序开始创建一个散列映射,然后将名字的映射增加到平衡表中。接下来,映射的内
容通过使用由调用函数entrySet( )而获得的集合“视图”而显示出来。关键字和值通过调用

由Map.Entry定义的getKey( )和getValue( )方法而显示。注意存款是如何被制成John Doe的账
目的。put( )方法自动用新值替换与指定关键字相关联的原先的值。因此,在John Doe的账
目被更新后,散列映射将仍然仅仅保留一个“John Doe”账目。
TreeMap 类
TreeMap类通过使用树实现Map接口。TreeMap提供了按排序顺序存储关键字/值对的有
效手段,同时允许快速检索。应该注意的是,不像散列映射,树映射保证它的元素按照关
键字升序排序。
下面的TreeMap构造函数定义为:
TreeMap( )
TreeMap(Comparator comp)
TreeMap(Map m)
TreeMap(SortedMap sm)
第一种形式构造一个空树的映射,该映射使用其关键字的自然顺序来排序。第二种形
式构造一个空的基于树的映射,该映射通过使用Comparator comp来排序(比较函数
Comparators将在本章后面进行讨论)。第三种形式用从m的输入初始化树映射,该映射使
用关键字的自然顺序来排序。第四种形式用从sm的输入来初始化一个树映射,该映射将按
与sm相同的顺序来排序。
TreeMap实现SortedMap并且扩展AbstractMap。而它本身并没有另外定义其他方法。
下面的程序重新使前面的例子运转,以便在其中使用TreeMap:
import java.util.*;
class TreeMapDemo {
public static void main(String args[]) {
// Create a tree map
TreeMap tm = new TreeMap();
// Put elements to the map
tm.put("John Doe", new Double(3434.34));
tm.put("Tom Smith", new Double(123.22));
tm.put("Jane Baker", new Double(1378.00));
tm.put("Todd Hall", new Double(99.22));
tm.put("Ralph Smith", new Double(-19.08));
// Get a set of the entries
Set set = tm.entrySet();
// Get an iterator
Iterator i = set.iterator();
// Display elements
while(i.hasNext()) {
Map.Entry me = (Map.Entry)i.next();
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());

}
System.out.println();
// Deposit 1000 into John Doe's account
double balance = ((Double)tm.get("John Doe")).doubleValue();
tm.put("John Doe", new Double(balance + 1000));
System.out.println("John Doe's new balance: " +
tm.get("John Doe"));
}
}
下面是该程序的输出结果:
Jane Baker: 1378.0
John Doe: 3434.34
Ralph Smith: -19.08
Todd Hall: 99.22
Tom Smith: 123.22
John Doe’s current balance: 4434.34
注意对关键字进行了排序。然而,在这种情况下,它们用名字而不是用姓进行了排序。
可以通过在创建映射时,指定一个比较函数来改变这种排序。在下一节将介绍如何做。
15.7 比较函数
TreeSet和TreeMap都按排序顺序存储元素。然而,精确定义采用何种“排序顺序”的
是比较函数。通常在默认的情况下,这些类通过使用被Java称之为“自然顺序”的顺序存
储它们的元素,而这种顺序通常也是你所需要的(A在B的前面,1在2的前面,等等)。如
果需要用不同的方法对元素进行排序,可以在构造集合或映射时,指定一个Comparator对
象。这样做为你提供了一种精确控制如何将元素储存到排序类集和映射中的能力。
Comparator接口定义了两个方法:compare( )和equals( )。这里给出的compare( )方法按
顺序比较了两个元素:
int compare(Object obj1, Object obj2)
obj1和obj2是被比较的两个对象。当两个对象相等时,该方法返回0;当obj1大于obj2
时,返回一个正值;否则,返回一个负值。如果用于比较的对象的类型不兼容的话,该方
法引发一个ClassCastException异常。通过覆盖compare( ),可以改变对象排序的方式。例如,
通过创建一个颠倒比较输出的比较函数,可以实现按逆向排序。
这里给出的equals( )方法,测试一个对象是否与调用比较函数相等:
boolean equals(Object obj)
obj是被用来进行相等测试的对象。如果obj和调用对象都是Comparator的对象并且使用
相同的排序。该方法返回true。否则返回false。重载equals( )方法是没有必要的,大多数简
单的比较函数都不这样做。

15.7.1 使用比较函数
下面是一个说明定制的比较函数能力的例子。该例子实现compare( )方法以便它按正常
顺序的逆向进行操作。因此,它使得一个树集合按逆向的顺序进行存储。
// Use a custom comparator.
import java.util.*;
// A reverse comparator for strings.
class MyComp implements Comparator {
public int compare(Object a, Object b) {
String aStr, bStr;
aStr = (String) a;
bStr = (String) b;
// reverse the comparison
return bStr.compareTo(aStr);
}
// no need to override equals
}
class CompDemo {
public static void main(String args[]) {
// Create a tree set
TreeSet ts = new TreeSet(new MyComp());
// Add elements to the tree set
ts.add("C");
ts.add("A");
ts.add("B");
ts.add("E");
ts.add("F");
ts.add("D");
// Get an iterator
Iterator i = ts.iterator();
// Display elements
while(i.hasNext()) {
Object element = i.next();
System.out.print(element + " ");
}
System.out.println();
}
}
正如下面的输出所示,树按照逆向顺序进行存储:
F E D C B A
仔细观察实现Comparator并覆盖compare( )方法的MyComp类(正如前面所解释的那样,
覆盖equals( )方法既不是必须的,也不是常用的)。在compare( )方法内部,String方法

compareTo( )比较两个字符串。然而由bStr??不是aStr??调用compareTo( )方法,这导致
比较的结果被逆向。
对应一个更实际的例子,下面的例子是用TreeMap程序实现前面介绍的存储账目资产
平衡表例子的程序。在前面介绍的程序中,账目是按名进行排序的,但程序是以按照名字
进行排序开始的。下面的程序按姓对账目进行排序。为了实现这种功能,程序使用了比较
函数来比较每一个账目下姓的排序。得到的映射是按姓进行排序的。
// Use a comparator to sort accounts by last name.
import java.util.*;
// Compare last whole words in two strings.
class TComp implements Comparator {
public int compare(Object a, Object b) {
int i, j, k;
String aStr, bStr;
aStr = (String) a;
bStr = (String) b;
// find index of beginning of last name
i = aStr.lastIndexOf(' ');
j = bStr.lastIndexOf(' ');
k = aStr.substring(i).compareTo(bStr.substring(j));
if(k==0) // last names match, check entire name
return aStr.compareTo(bStr);
else
return k;
}
// no need to override equals
}
class TreeMapDemo2 {
public static void main(String args[]) {
// Create a tree map
TreeMap tm = new TreeMap(new TComp());
// Put elements to the map
tm.put("John Doe", new Double(3434.34));
tm.put("Tom Smith", new Double(123.22));
tm.put("Jane Baker", new Double(1378.00));
tm.put("Todd Hall", new Double(99.22));
tm.put("Ralph Smith", new Double(-19.08));
// Get a set of the entries
Set set = tm.entrySet();
// Get an iterator
Iterator itr = set.iterator();
// Display elements
while(itr.hasNext()) {

Map.Entry me = (Map.Entry)itr.next();
System.out.print(me.getKey() + ": ");
System.out.println(me.getValue());
}
System.out.println();
// Deposit 1000 into John Doe's account
double balance = ((Double)tm.get("John Doe")).doubleValue();
tm.put("John Doe", new Double(balance + 1000));
System.out.println("John Doe's new balance: " +
tm.get("John Doe"));
}
}
这里是程序的输出结果,注意此时的账目是按姓进行排序的:
Jane Baker: 1378.0
John Doe: 3434.34
Todd Hall: 99.22
Ralph Smith: -19.08
Tom Smith: 123.22
John Doe’s new balance: 4434.34
比较函数类TComp比较两个包含姓和名的字符串。它首先比较姓。具体是这样做的,
它首先寻找每一个字符串中最后一个空格的下标,然后比较从这个位置开始的每一个元素
的子字符串。当两个字符串中姓完全相等时,它再比较两个名。这样就形成了一个先按姓
进行排序,在姓相同的情况下,再按名字进行排序的树型映射。通过程序的输出中Ralph
Smith出现在Tom Smith之前的结果可以看到这一点。
15.8 类集算法
类集框架定义了几种能用于类集和映射的算法。在Collections类中,这些算法被定义为
静态方法。表15-9中列出了这些算法。当试图比较不兼容的类型时,其中的一些算法引发
一个ClassCastException 异常; 而当试图改变一个不可改变的类集时, 则引发一个
UnsupportedOperationException异常。
表15-9 由Collections 定义的算法
方法描述
static int binarySearch(List list, Object
value,Comparator c)
按照c的次序在list中搜寻value。如果value在list内,则
返回value在list的位置。如果在list中没有发现value,
则返回-1
static int binarySearch(List list, Object value) 在list中搜寻value,列表(list)必须被排序。如果value
在list内,则返回value的位置。如果在list中没有发现
value,则返回-1
static void copy(List list1, List list2) 将list2中的元素复制给list1

续表
方法描述
static Enumeration enumeration(Collection c) 返回c的一个枚举(参看本章后面的“枚举接口”)。
static void fill(List list, Object obj) 将obj赋给list中的每一个元素
Static Object max(Collection c,Comparator comp) 返回由comp确定的c中的最大元素
static Object max(Collection c) 返回按自然顺序确定的c中的最大元素。类集不必被排

static Object min(Collection c,Comparator comp) 返回由comp确定的c中的最小元素。类集不必被排序
static Object min(Collection c) 返回按自然顺序确定的c中的最小元素
static List nCopies(int num, Object obj) 返回包含在不可改变的列表中的obj的num个拷贝。
num必须大于等于0
static void reverse(List list) 将list中的序列逆向
static Comparator reverseOrder( ) 返回一个逆向比较函数(即将两个元素比较的结果进
行逆向的比较函数)
static void shuffle(List list, Random r) 用r作为随机数的源,对list中的元素进行混淆(也即
随机化)
static void shuffle(List list) 对list中的元素进行混淆(也即随机化)
static Set singleton(Object obj) 返回一个不可改变的集合obj。这是一个实现将单个对
象变成集合的简单办法
static List singletonList(Object obj) 返回一个不可改变的列表obj。这是一个实现将单个对
象变成列表的简单办法(在Java 2的1.3版中新增加的)
static Map singletonMap(Object k, Object v) 返回一个不可改变的关键字/值对映射k/v。这是一个
实现将单个关键字/值对变成映射的简单办法(在Java
2的1.3版中新增加的)
static void sort(List list, Comparator comp) 按comp对list中的元素进行排序
static void sort(List list) 按自然顺序对list中的元素进行排序
static Collection
synchronizedCollection(Collection c)
返回一个被c支持的安全线程类集
static List synchronizedList(List list) 返回一个被list支持的安全线程列表
static Map synchronizedMap(Map m) 返回一个被m支持的安全线程映射
static Set synchronizedSet(Set s) 返回一个被s支持的安全线程集合
static SortedMap
synchronizedSortedMap(SortedMap sm)
返回一个被sm支持的安全线程排序集合
static SortedSet
synchronizedSortedSet(SortedSet ss)
返回一个被ss支持的安全线程集合
static Collection
unmodifiableCollection(Collection c)
返回一个被c支持的不可变类集
Static List unmodifiableList(List list) 返回一个被list支持的不可变列表

续表
方法描述
static Map unmodifiableMap(Map m) 返回一个被m支持的不可变映射
static Set unmodifiableSet(Set s) 返回一个被s支持的不可变集合
Static SortedMap
unmodifiable SortedMap(SortedMap sm)
返回一个被sm支持的不可变排序映射
static SortedSet
unmodifiableSortedSet(SortedSet ss)
返回一个被ss支持的不可变排序集合
注意其中的几种方法,如synchronizedList( )和synchronizedSet( )被用来获得各种类集的
同步(安全线程)拷贝。正如前面解释的那样,没有任何一个标准类集实现是同步的。必
须使用同步算法来为其提供同步。另一种观点:同步类集的迭代函数必须在synchronized块
内使用。
以unmodifiable开头的一组方法返回不能被改变的各种类集“视图”。这些方法当将一
些进程对类集设为只读形式时很有用的。
Collections定义了三个静态变量:EMPTY_SET,EMPTY_LIST和EMPTY_MAP。它们
都是不可改变的。EMPTY_MAP是在Java 2的1.3版中新增加的。
下面的程序说明了其中的一些算法。该程序创建和初始化了一个链表。reverseOrder( )
方法返回一个对Integer对象的比较进行逆向的Comparator函数。列表中的元素按照这个比较
函数进行排序并被显示出来。接下来,调用shuffle( )方法对列表进行随机排列。然后显示
列表的最大和最小值。
// Demonstrate various algorithms.
import java.util.*;
class AlgorithmsDemo {
public static void main(String args[]) {
// Create and initialize linked list
LinkedList ll = new LinkedList();
ll.add(new Integer(-8));
ll.add(new Integer(20));
ll.add(new Integer(-20));
ll.add(new Integer(8));
// Create a reverse order comparator
Comparator r = Collections.reverseOrder();
// Sort list by using the comparator
Collections.sort(ll, r);
// Get iterator
Iterator li = ll.iterator();
System.out.print("List sorted in reverse: ");
while(li.hasNext())
System.out.print(li.next() + " ");

System.out.println();
Collections.shuffle(ll);
// display randomized list
li = ll.iterator();
System.out.print("List shuffled: ");
while(li.hasNext())
System.out.print(li.next() + " ");
System.out.println();
System.out.println("Minimum: " + Collections.min(ll));
System.out.println("Maximum: " + Collections.max(ll));
}
}
该程序的输出如下所示:
List sorted in reverse: 20 8 -8 -20
List shuffled: 20 -20 8 -8
Minimum: -20
Maximum: 20
注意min( )和max( )方法是在列表被混淆之后,对其进行操作的。两者在运行时,都不
需要排序的列表。
15.9 Arrays (数组)
Java 2在java.util中新增加了一个叫做Arrays的类。这个类提供了各种在进行数组运算时
很有用的方法。尽管这些方法在技术上不属于类集框架,但它们提供了跨越类集和数组的
桥梁。在这一节中,分析由Arrays定义的每一种方法。
asList( )方法返回一个被指定数组支持的List。换句话说,列表和数组访问的是同一个
单元。它具有如下的形式:
static List asList(Object[ ] array)
这里array是包含了数据的数组。
binarySearch( )方法使用二进制搜索寻找指定的值。该方法必须应用于排序数组。它具
有如下的形式:
static int binarySearch(byte[ ] array, byte value)
static int binarySearch(char[ ] array, char value)
static int binarySearch(double[ ] array, double value)
static int binarySearch(float[ ] array, float value)
static int binarySearch(int[ ] array, int value)
static int binarySearch(long[ ] array, long value)
static int binarySearch(short[ ] array, short value)
static int binarySearch(Object[ ] array, Object value)
static int binarySearch(Object[ ] array, Object value, Comparator c)

这里,array是被搜索的数组,而value是被查找的值。当array中包含的元素是不可比较
的(例如Double和StringBuffer)或者当value与array中的类型不兼容时,后两种形式引发一
个ClassCastException异常。在最后一种形式中,比较函数(Comparator)c用于确定array中
的元素的顺序。在所有的形式中,如果array中含有value,则返回该元素的下标。否则,返
回一个负值。
当两个数组相等时,equals( )方法返回true;否则返回false。equals( )方法具有下面的一
些形式:
static boolean equals(boolean array1[ ], boolean array2[ ])
static boolean equals(byte array1[ ], byte array2[ ])
static boolean equals(char array1[ ], char array2[ ])
static boolean equals(double array1[ ], double array2[ ])
static boolean equals(float array1[ ], float array2[ ])
static boolean equals(int array1[ ], int array2[ ])
static boolean equals(long array1[ ], long array2[ ])
static boolean equals(short array1[ ], short array2[ ])
static boolean equals(Object array1[ ], Object array2[ ])
这里array1和array2是两个用来比较看是否相等的数组。
fill( )方法将一个值赋给数组中的所有元素。换句话说,它用一个指定的值填充数组。
fill( )方法有两种形式。第一种形式具有下面的一些形式,填充整个数组:
static void fill(boolean array[ ], boolean value)
static void fill(byte array[ ], byte value)
static void fill(char array[ ], char value)
static void fill(double array[ ], double value)
static void fill(float array[ ], float value)
static void fill(int array[ ], int value)
static void fill(long array[ ], long value)
static void fill(short array[ ], short value)
static void fill(Object array[ ], Object value)
这里value被赋给数组array中的每一个元素。
fill( )方法的第二种形式将一个值赋给数组的一个子集。它的几种形式如下:
static void fill(boolean array[ ], int start, int end, boolean value)
static void fill(byte array[ ], int start, int end, byte value)
static void fill(char array[ ], int start, int end, char value)
static void fill(double array[ ], int start, int end, double value)
static void fill(float array[ ], int start, int end, float value)
static void fill(int array[ ], int start, int end, int value)
static void fill(long array[ ], int start, int end, long value)
static void fill(short array[ ], int start, int end, short value)
static void fill(Object array[ ], int start, int end, Object value)
这里,value是赋给数组array中从start开始到end?1结束的子集的值。这些方法当start大
于end时,都能引发一个IllegalArgumentException异常;而当start或end出界时,都能引发一
个ArrayIndexOutOfBoundsException异常。
sort( )方法对数组进行排序,以便数组能够按升序进行排列。sort( )方法有两种形式。
下面给出的第一种形式对整个数组进行排序:

static void sort(byte array[ ])
static void sort(char array[ ])
static void sort(double array[ ])
static void sort(float array[ ])
static void sort(int array[ ])
static void sort(long array[ ])
static void sort(short array[ ])
static void sort(Object array[ ])
static void sort(Object array[ ], Comparator c)
这里,array是被排序的数组。在最后的一种形式中,c是一个用来规定array中元素顺序
的比较函数(Comparator)。当用于排序的数组中的元素不可比较时,这些对Object的数组进
行排序的sort( )方法将引发一个ClassCastException异常。
sort( )方法的第二种形式允许在一个数组内,指定一个想要进行排序的范围。它的具体
形式如下:
static void sort(byte array[ ], int start, int end)
static void sort(char array[ ], int start, int end)
static void sort(double array[ ], int start, int end)
static void sort(float array[ ], int start, int end)
static void sort(int array[ ], int start, int end)
static void sort(long array[ ], int start, int end)
static void sort(short array[ ], int start, int end)
static void sort(Object array[ ], int start, int end)
static void sort(Object array[ ], int start, int end, Comparator c)
这里,数组中想要进行排序的范围从start到end?1。在最后一种形式中,c是一个用来规
定array中元素顺序的Comparator。如果start大于end ,所有这些方法都能引发一个
IllegalArgumentException 异常; 而当start 或end 出界时, 又都能引发一个
ArrayIndexOutOfBoundsException异常。当用于排序的数组中的元素不可比较时,最后两种
形式也能引发一个ClassCastException异常。
下面的程序举例说明了如何使用Arrays类中的一些方法:
// Demonstrate Arrays
import java.util.*;
class ArraysDemo {
public static void main(String args[]) {
// allocate and initialize array
int array[] = new int[10];
for(int i = 0; i < 10; i++)
array[i] = -3 * i;
// display, sort, display
System.out.print("Original contents: ");
display(array);
Arrays.sort(array);
System.out.print("Sorted: ");
display(array);

// fill and display
Arrays.fill(array, 2, 6, -1);
System.out.print("After fill(): ");
display(array);
// sort and display
Arrays.sort(array);
System.out.print("After sorting again: ");
display(array);
// binary search for -9
System.out.print("The value -9 is at location ");
int index =
Arrays.binarySearch(array, -9);
System.out.println(index);
}
static void display(int array[]) {
for(int i = 0; i < array.length; i++)
System.out.print(array[i] + " ");
System.out.println("");
}
}
下面是该程序的输出结果:
Original contents: 0 -3 -6 -9 -12 -15 -18 -21 -24 -27
Sorted: -27 -24 -21 -18 -15 -12 -9 -6 -3 0
After fill(): -27 -24 -1 -1 -1 -1 -9 -6 -3 0
After sorting again: -27 -24 -9 -6 -3 -1 -1 -1 -1 0
The value -9 is at location 2
15.10 从以前版本遗留下来的类和接口
正如本章开始时介绍的那样,java.util的最初版本中不包括类集框架。取而代之,它定
义了几个类和接口提供专门的方法用于存储对象。随着在Java 2中引入类集,有几种最初的
类被重新设计成支持类集接口。因此它们与框架完全兼容。尽管实际上没有类被摈弃,但
其中某些仍被认为是过时的。当然,在那些重复从以前版本遗留下来的类的功能性的地方,
通常都愿意用类集编写新的代码程序。一般地,对从以前版本遗留下来的类的支持是因为
仍然存在大量使用它们的基本代码。包括现在仍在被Java 2的应用编程接口(API)使用的
程序。
另一点,没有一个类集类是同步的。但是所有的从以前版本遗留下来的类都是同步的。
这一区别在有些情况下是很重要的。当然,通过使用由Collections提供的算法也很容易实现
类集同步。
由java.util定义的从以前版本遗留下来的类说明如下:
Dictionary Hashtable Properties Stack Vector

有一个枚举(Enumeration)接口是从以前版本遗留下来。在下面依次介绍Enumeration
和每一种从以前版本遗留下来的类。
15.10.1 Enumeration接口
Enumeration接口定义了可以对一个对象的类集中的元素进行枚举(一次获得一个)的
方法。这个接口尽管没有被摈弃,但已经被Iterator所替代。Enumeration对新程序来说是过
时的。然而它仍被几种从以前版本遗留下来的类(例如Vector和Properties)所定义的方法
使用,被几种其他的API类所使用以及被目前广泛使用的应用程序所使用。
Enumeration指定下面的两个方法:
boolean hasMoreElements( )
Object nextElement( )
执行后,当仍有更多的元素可提取时,hasMoreElements( )方法一定返回true。当所有
元素都被枚举了,则返回false。nextElement( )方法将枚举中的下一个对象做为一个类属
Object的引用而返回。也就是每次调用nextElement( )方法获得枚举中的下一个对象。调用例
程必须将那个对象置为包含在枚举内的对象类型。
15.10.2 Vector
Vector实现动态数组。这与ArrayList相似,但两者不同的是:Vector是同步的,并且它
包含了许多不属于类集框架的从以前版本遗留下来的方法。随着Java 2的公布,Vector被重
新设计来扩展AbstractList和实现List接口,因此现在它与类集是完全兼容的。
这里是Vector的构造函数:
Vector( )
Vector(int size)
Vector(int size, int incr)
Vector(Collection c)
第一种形式创建一个原始大小为10的默认矢量。第二种形式创建一个其原始容量由size
指定的矢量。第三种形式创建一个其原始容量由size指定,并且它的增量由incr指定的矢量。
增量指定了矢量每次允许向上改变大小的元素的个数。第四种形式创建一个包含了类集c
中元素的矢量。这个构造函数是在Java 2中新增加的。
所有的矢量开始都有一个原始的容量。在这个原始容量达到以后,下一次再试图向矢
量中存储对象时,矢量自动为那个对象分配空间同时为别的对象增加额外的空间。通过分
配超过需要的内存,矢量减小了可能产生的分配的次数。这种次数的减少是很重要的,因
为分配内存是很花时间的。在每次再分配中,分配的额外空间的总数由在创建矢量时指定
的增量来确定。如果没有指定增量,在每个分配周期,矢量的大小增一倍。
Vector定义了下面的保护数据成员:
int capacityIncrement;
int elementCount;
Object elementData[ ];

增量值被存储在capacityIncrement中。矢量中的当前元素的个数被存储在elementCount
中。保存矢量的数组被存储在elementData中。
除了由List定义的类集方法之外,Vector还定义了几个从以前版本遗留下来的方法,这
些方法列在表15-10中。
表15-10 由Vector 定义的方法
方法描述
final void addElement(Object element) 将由element指定的对象加入矢量
int capacity( ) 返回矢量的容量
Object clone( ) 返回调用矢量的一个拷贝
Boolean contains(Object element) 如果element被包含在矢量中,则返回true;如果不包含于其
中,则返回false
void copyInto(Object array[ ]) 将包含在调用矢量中的元素复制到由array指定的数组中
Object elementAt(int index) 返回由index指定位置的元素
Enumeration elements( ) 返回矢量中元素的一个枚举
Object firstElement( ) 返回矢量的第一个元素
int indexOf(Object element) 返回element首次出现的位置下标。如果对象不在矢量中,
则返回-1
int indexOf(Object element, int start) 返回element在矢量中在start及其之后第一次出现的位置下
标。如果该对象不属于矢量的这一部分,则返回-1
void insertElementAt(Object element,
int index)
在矢量中,在由index指定的位置处加入element
boolean isEmpty( ) 如果矢量是空的,则返回true。如果它包含了一个或更多个
元素,则返回false
Object lastElement( ) 返回矢量中的最后一个元素
int lastIndexOf(Object element) 返回element在矢量中最后一次出现的位置下标。如果对象
不包含在矢量中,则返回-1
int lastIndexOf(Object element,int start) 返回element在矢量中,在start之前最后一次出现的位置下
标。如果该对象不属于矢量的这一部分,则返回-1
void removeAllElements( ) 清空矢量,在这个方法执行以后,矢量的大小为0
boolean removeElement(Object element) 从矢量中删除element。对于指定的对象,矢量中如果有其
多个实例,则其中第一个实例被删除。如果成功删除,则
返回true;如果没有发现对象,则返回false
void removeElementAt(int index) 删除由index指定位置处的元素
void setElementAt(Object element,
int index)
将由index指定的位置分配给element
void setSize(int size) 将矢量中元素的个数设为size。如果新的长度小于老的长
度,元素将丢失;如果新的长度大于老的长度,则在其后
增加null元素
int size( ) 返回矢量中当前元素的个数

续表
方法描述
String toString( ) 返回矢量的字符串等价形式
void trimToSize( ) 将矢量的容量设为与其当前拥有的元素的个数相等
因为Vector实现List,所以可以像使用ArrayList的一个实例那样使用矢量。也可以使用
它的从以前版本遗留下来的方法来操作它。例如,在后面实例化Vector,可以通过调用
addElement( )方法而为其增加一个元素。调用elementAt( )方法可以获得指定位置处的元素。
调用firstElement( )方法可以得到矢量的第一个元素。调用lastElement( )方法可以检索到矢量
的最后一个元素。使用indexOf( ) 和lastIndexOf( ) 方法可以获得元素的下标。调用
removeElement( )或removeElementAt( )方法可以删除元素。
下面的程序使用矢量存储不同类型的数值对象。程序说明了几种由Vector定义的从以
前版本遗留下来的方法,同时它也说明了枚举(Enumeration)接口。
// Demonstrate various Vector operations.
import java.util.*;
class VectorDemo {
public static void main(String args[]) {
// initial size is 3, increment is 2
Vector v = new Vector(3, 2);
System.out.println("Initial size: " + v.size());
System.out.println("Initial capacity: " +
v.capacity());
v.addElement(new Integer(1));
v.addElement(new Integer(2));
v.addElement(new Integer(3));
v.addElement(new Integer(4));
System.out.println("Capacity after four additions: " +
v.capacity());
v.addElement(new Double(5.45));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Double(6.08));
v.addElement(new Integer(7));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Float(9.4));
v.addElement(new Integer(10));
System.out.println("Current capacity: " +
v.capacity());
v.addElement(new Integer(11));
v.addElement(new Integer(12));

System.out.println("First element: " +
(Integer)v.firstElement());
System.out.println("Last element: " +
(Integer)v.lastElement());
if(v.contains(new Integer(3)))
System.out.println("Vector contains 3.");
// enumerate the elements in the vector.
Enumeration vEnum = v.elements();
System.out.println("nElements in vector:");
while(vEnum.hasMoreElements())
System.out.print(vEnum.nextElement() + " ");
System.out.println();
}
}
该程序的输出如下所示:
Initial size: 0
Initial capacity: 3
Capacity after four additions: 5
Current capacity: 5
Current capacity: 7
Current capacity: 9
First element: 1
Last element: 12
Vector contains 3.
Elements in vector:
1 2 3 4 5.45 6.08 7 9.4 10 11 12
随着Java 2的公布,Vector增加了对迭代函数的支持。现在可以使用迭代函数来替代枚
举去遍历对象(正如前面的程序所做的那样)。例如,下面的基于迭代函数的程序代码可
以被替换到上面的程序中:
// use an iterator to display contents
Iterator vItr = v.iterator();
System.out.println("nElements in vector:");
while(vItr.hasNext())
System.out.print(vItr.next() + " ");
System.out.println();
因为建议不要使编写枚举新的程序代码,所以通常可以使用迭代函数来对矢量的内容
进行枚举。当然,业已存在的大量的老程序采用了枚举。不过幸运的是,枚举和迭代函数
的工作方式几乎相同。
15.10.3 Stack
Stack是Vector的一个子类,它实现标准的后进先出堆栈。Stack仅仅定义了创建空堆栈

的默认构造函数。Stack包括了由Vector定义的所有方法,同时增加了几种它自己定义的方
法,具体总结在表15-11中。
表15-11 由Stack 定义的方法
方法描述
boolean empty( ) 如果堆栈是空的,则返回true,当堆栈包含有元素时,返回false
Object peek( ) 返回位于栈顶的元素,但是并不在堆栈中删除它
Object pop( ) 返回位于栈顶的元素,并在进程中删除它
Object push(Object element) 将element压入堆栈,同时也返回element
int search(Object element) 在堆栈中搜索element,如果发现了,则返回它相对于栈顶的偏移
量。否则,返回-1
调用push( )方法可将一个对象压入栈顶。调用pop( )方法可以删除和返回栈顶的元素。
当调用堆栈是空的时,如果调用pop( )方法,将引发一个EmptyStackException异常。调用
peek( )方法返回但不删除栈顶的对象。调用empty( )方法,当堆栈中没有元素时,返回true。
search( )方法确定一个对象是否存在于堆栈,并且返回将其指向栈顶所需的弹出次数。下面
是一个创建堆栈的例子,在例子中,将几个整型(Integer)对象压入堆栈,然后再将它们
弹出。
// Demonstrate the Stack class.
import java.util.*;
class StackDemo {
static void showpush(Stack st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
}
static void showpop(Stack st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack: " + st);
}
public static void main(String args[]) {
Stack st = new Stack();
System.out.println("stack: " + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
try {
showpop(st);

} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}
下面是由该程序产生的输出。注意对于EmptyStackException的异常处理程序是如何被
捕获以便于能够从容地处理堆栈的下溢:
stack: [ ]
push(42)
stack: [42]
push(66)
stack: [42, 66]
push(99)
stack: [42, 66, 99]
pop -> 99
stack: [42, 66]
pop -> 66
stack: [42]
pop -> 42
stack: [ ]
pop -> empty stack
15.10.4 Dictionary
字典(Dictionary)是一个表示关键字/值存储库的抽象类,同时它的操作也很像映射
(Map)。给定一个关键字和值,可以将值存储到字典(Dictionary)对象中。一旦这个值
被存储了,就能够用它的关键字来检索它。因此,与映射一样,字典可以被当做关键字/值
对列表来考虑。尽管在Java 2中并没有摈弃字典(Dictionary),由于它被映射(Map)所取
代,从而被认为是过时的。然而由于目前Dictionary被广泛地使用,因此这里仍对它进行详
细的讨论。
由Dictionary定义的抽象方法在表15-12中列出。
表15-12 由Dictionary 定义的抽象方法
方法描述
Enumeration elements( ) 返回对包含在字典中的值的枚举
Object get(Object key) 返回一个包含与key相连的值的对象。如果key不在字典中,
则返回一个空对象
boolean isEmpty( ) 如果字典是空的,则返回true;如果字典中至少包含一个关
键字,则返回false
Enumeration keys( ) 返回包含在字典中的关键字的枚举
Object put(Object key, Object value) 将一个关键字和它的值插入字典中。如果key已经不在字典
中了,则返回null;如果key已经在字典中了,则返回与key
相关联的前一个值

续表
方法描述
Object remove(Object key) 删除key和它的值。返回与key相关联的值。如果key不在字
典中,则返回null
int size( ) 返回字典中的项数
使用put( )方法在字典中增加关键字和值。使用get( )方法检索给定关键字的值。当分别
使用keys( )和elements( )方法进行枚举(Enumeration)时,关键字和值可以分别逐个地返回。
size( )方法返回存储在字典中的关键字/值对的个数。当字典是空的时候,isEmpty( )返回true。
使用remove( )方法可以删除关键字/值对。
注意:Dictionary类是过时的。应该执行Map接口去获得关键字/值存储的功能。
15.10.5 Hashtable
散列表(Hashtable)是原始java.util中的一部分同时也是Dictionary的一个具体实现。然
而,Java 2重新设计了散列表(Hashtable)以便它也能实现映射(Map)接口。因此现在
Hashtable也被集成到类集框架中。它与HashMap相似,但它是同步的。
和HashMap一样,Hashtable将关键字/值对存储到散列表中。使用Hashtable时,指定一
个对象作为关键字,同时指定与该关键字相关联的值。接着该关键字被散列,而把得到的
散列值作为存储在表中的值的下标。
散列表仅仅可以存储重载由Object定义的hashCode( )和equals( )方法的对象。hashCode( )
方法计算和返回对象的散列码。当然,equals( )方法比较两个对象。幸运的是,许多Java内
置的类已经实现了hashCode( )方法。例如,大多数常见的Hashtable类型使用字符串(String)
对象作为关键字。String实现hashCode( )和equals( )方法。
Hashtable的构造函数如下所示:
Hashtable( )
Hashtable(int size)
Hashtable(int size, float fillRatio)
Hashtable(Map m)
第一种形式是默认的构造函数。第二种形式创建一个散列表,该散列表具有由size指定
的原始大小。第三种形式创建一个散列表,该散列表具有由size指定的原始大小和由fillRatio
指定的填充比。填充比必须介于0.0和1.0之间,它决定了在散列表向上调整大小之前散列表
的充满度。具体地说,当元素的个数大于散列表的容量乘以它的填充比时,散列表被扩展。
如果没有指定填充比,默认使用0.75。最后,第四种形式创建一个散列表,该散列表用m中
的元素初始化。散列表的容量被设为m中元素的个数的两倍。默认的填充因子设为0.75。第
四种构造函数是在Java 2中新增加的。
除了Hashtable目前实现的,由Map接口定义的方法之外,Hashtable定义的从以前版本
遗留下来的方法列在表15-13中。

表15-13 由Hashtable 定义的从以前版本遗留下来的方法
方法描述
void clear( ) 复位并清空散列表
Object clone( ) 返回调用对象的复制
boolean contains(Object value) 如果一些值与存在于散列表中的value相等的话,则返回true;
如果这个值不存在,则返回false
boolean containsKey(Object key) 如果一些关键字与存在于散列表中的key相等的话,则返回true;
如果这个关键字不存在,则返回false
boolean containsValue(Object value) 如果一些值与散列表中存在的value相等的话,返回true;如果
这个值没有找到,则返回false(是一种为了保持一致性而在Java
2中新增加的非Map方法)
Enumeration elements( ) 返回包含在散列表中的值的枚举
Object get(Object key) 返回包含与key相关联的值的对象。如果key不在散列表中,则
返回一个空对象
boolean isEmpty( ) 如果散列表是空的,则返回true;如果散列表中至少包含一个关
键字,则返回false
Enumeration keys( ) 返回包含在散列表中的关键字的枚举
Object put(Object key, Object value) 将关键字和值插入散列表中。如果key已经不在散列表中,返回
null。如果key已经存在于散列表中,则返回与key相连的前一个

void rehash( ) 增大散列表的大小并且对其关键字进行再散列。
Object remove(Object key) 删除key及其对应的值。返回与key相关联的值。如果key不在散
列表中,则返回一个空对象
int size( ) 返回散列表中的项数
String toString( ) 返回散列表的等价字符串形式
下面的例子重写前面介绍的关于银行账目的程序。在重写的程序中,使用Hashtable储
存银行存款人的名字和他们当前的资产平衡表:
// Demonstrate a Hashtable
import java.util.*;
class HTDemo {
public static void main(String args[]) {
Hashtable balance = new Hashtable();
Enumeration names;
String str;
double bal;
balance.put("John Doe", new Double(3434.34));
balance.put("Tom Smith", new Double(123.22));
balance.put("Jane Baker", new Double(1378.00));
balance.put("Todd Hall", new Double(99.22));
balance.put("Ralph Smith", new Double(-19.08));

// Show all balances in hash table.
names = balance.keys();
while(names.hasMoreElements()) {
str = (String) names.nextElement();
System.out.println(str + ": " +
balance.get(str));
}
System.out.println();
// Deposit 1,000 into John Doe's account
bal = ((Double)balance.get("John Doe")).doubleValue();
balance.put("John Doe", new Double(bal+1000));
System.out.println("John Doe's new balance: " +
balance.get("John Doe"));
}
}
该程序的输出如下所示:
Todd Hall: 99.22
Ralph Smith: -19.08
John Doe: 3434.34
Jane Baker: 1378.0
Tom Smith: 123.22
John Doe’s new balance: 4434.34
重要的一点是:和映射类一样,Hashtable不直接支持迭代函数。因此,上面的程序使
用枚举来显示balance的内容。然而,我们可以获得允许使用迭代函数的散列表的集合视图。
为了实现它,可以简单地使用由Map定义的一个类集“视图”方法,如entrySet( )或keySet( )
方法。例如,可以获得关键字的一个集合“视图”,并遍历这些关键字。下面是采用这种
技术后重新编写的程序:
// Use iterators with a Hashtable.
import java.util.*;
class HTDemo2 {
public static void main(String args[]) {
Hashtable balance = new Hashtable();
String str;
double bal;
balance.put("John Doe", new Double(3434.34));
balance.put("Tom Smith", new Double(123.22));
balance.put("Jane Baker", new Double(1378.00));
balance.put("Todd Hall", new Double(99.22));
balance.put("Ralph Smith", new Double(-19.08));
// show all balances in hashtable
Set set = balance.keySet(); // get set-view of keys
// get iterator

Iterator itr = set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
System.out.println(str + ": " +
balance.get(str));
}
System.out.println();
// Deposit 1,000 into John Doe's account
bal = ((Double)balance.get("John Doe")).doubleValue();
balance.put("John Doe", new Double(bal+1000));
System.out.println("John Doe's new balance: " +
balance.get("John Doe"));
}
}
15.10.6 Properties
属性(Properties)是Hashtable的一个子类。它用来保持值的列表,在其中关键字和值
都是字符串(String)。Properties类被许多其他的Java类所使用。例如,当获得系统环境值
时,System.getProperties( )返回对象的类型。
Properties定义了下面的实例变量:
Properties defaults;
这个变量包含了一个与属性(Properties)对象相关联的默认属性列表。Properties定义
了如下的构造函数:
Properties( )
Properties(Properties propDefault)
第一种形式创建一个没有默认值的属性(Properties)对象。第二种形式创建一个将
propDefault作为其默认值的对象。在这两种情况下,属性列表都是空的。
除了Properties从Hashtable中继承下来的方法之外,Properties自己定义的方法列在表
15-14中。Properties也包含了一个不被赞成使用的方法:save( )。它被store( )方法所取代,
因为它不能正确地处理错误。
表15-14 由Properties 定义的从以前版本遗留下来的方法
方法描述
String getProperty(String key) 返回与key相关联的值。如果key既不在列表中,也不在默
认属性列表中,则返回一个null对象
String getProperty(String key,
String defaultProperty)
返回与key相关联的值。如果key既不在列表中,也不在默
认属性列表中,则返回defaultProperty
void list(PrintStream streamOut) 将属性列表发送给与streamOut相链接的输出流
void list(PrintWriter streamOut) 将属性列表发送给与streamOut相链接的输出流

续表
方法描述
void load(InputStream streamIn)
throws IOException
从与streamIn相链接的输入数据流输入一个属性列表
Enumeration propertyNames( ) 返回关键字的枚举,也包括那些在默认属性列表中找到的
关键字
Object setProperty(String key, String value) 将value与key关联。返回与key关联的前一个值,如果不存
在这样的关联,则返回null(为了保持一致性,在Java 2中
新增加的)
void store(OutputStream streamOut,
String description)
在写入由description指定的字符串之后,属性列表被写入与
streamOut相链接的输出流(在Java 2中新增加的)
Properties类的一个有用的功能是可以指定一个默认属性,如果没有值与特定的关键字
相关联,则返回这个默认属性。例如,默认值可以与关键字一起在getProperty( )方法中被指
定??如getProperty(“name”,“default value”)。如果“name”值没有找到,则返回“default
value”。当构造一个Properties对象时,可以传递Properties的另一个实例做为新实例的默认
值。在这种情况下,如果对一个给定的Properties对象调用getProperty(“foo”),而“foo”并
不存在时,Java在默认Properties对象中寻找“foo”。它允许默认属性的任意层嵌套。
下面的例子说明了Properties。该程序创建一个属性列表,在其中关键字是美国的州名,
值是这些州的首府的名字。注意试图寻找包括默认值的Florida的首府时的情况。
// Demonstrate a Property list.
import java.util.*;
class PropDemo {
public static void main(String args[]) {
Properties capitals = new Properties();
Set states;
String str;
capitals.put("Illinois", "Springfield");
capitals.put("Missouri", "Jefferson City");
capitals.put("Washington", "Olympia");
capitals.put("California", "Sacramento");
capitals.put("Indiana", "Indianapolis");
// Show all states and capitals in hashtable.
states = capitals.keySet(); // get set-view of keys
Iterator itr = states.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
System.out.println("The capital of " +
str + " is " +
capitals.getProperty(str)
+ ".");
}
 
System.out.println();
// look for state not in list -- specify default
str = capitals.getProperty("Florida", "Not Found");
System.out.println("The capital of Florida is "
+ str + ".");
}
}
该程序的输出如下所示:
The capital of Missouri is Jefferson City.
The capital of Illinois is Springfield.
The capital of Indiana is Indianapolis.
The capital of California is Sacramento.
The capital of Washington is Olympia.
The capital of Florida is Not Found.
由于Florida不在列表中,所以使用了默认值。
尽管当调用getProperty( )方法时,使用默认值是十分有效的,正如上面的程序所展示的
那样,对大多数属性列表的应用来说,有更好的方法去处理默认值。为了更大的灵活性,
当构造一个属性(Properties)对象时,指定一个默认的属性列表。如果在主列表中没有发
现期望的关键字,将会搜索默认列表。例如,下面是对前面程序稍作修改的程序。在该程
序中,有一个指定州的默认列表。在这种情况下,当搜索Florida时,将在默认列表中找到
它。
// Use a default property list.
import java.util.*;
class PropDemoDef {
public static void main(String args[]) {
Properties defList = new Properties();
defList.put("Florida", "Tallahassee");
defList.put("Wisconsin", "Madison");
Properties capitals = new Properties(defList);
Set states;
String str;
capitals.put("Illinois", "Springfield");
capitals.put("Missouri", "Jefferson City");
capitals.put("Washington", "Olympia");
capitals.put("California", "Sacramento");
capitals.put("Indiana", "Indianapolis");
// Show all states and capitals in hashtable.
states = capitals.keySet(); // get set-view of keys
Iterator itr = states.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
System.out.println("The capital of " +
str + " is " +

capitals.getProperty(str)
+ ".");
}
System.out.println();
// Florida will now be found in the default list.
str = capitals.getProperty("Florida");
System.out.println("The capital of Florida is "
+ str + ".");
}
}


15.10.7 使用store( )和load( )
Properties的一个最有用的方面是可以利用store( )和load( )方法方便地对包含在属性
(Properties)对象中的信息进行存储或从盘中装入信息。在任何时候,都可以将一个属性
(Properties)对象写入流或从中将其读出。这使得属性列表特别方便实现简单的数据库。
例如,下面的程序使用属性列表创建一个简单的用计算机处理的存储着姓名和电话号码的
电话本。为了寻找某人的电话号码,可输入他或者她的名字。程序使用store( )和load( )方法
来存储和检索列表。当程序执行时,它首先试着从一个叫做phonebook.dat的文件中装入列
表。如果这个文件存在,列表就被装入。然后就可以增加列表。如果这样做了,当终止程
序时,新列表就会被保存。注意:实现一个小且实用的计算机化的电话号码本只需要很少
的程序代码。
/* A simple telephone number database that uses
a property list. */
import java.io.*;
import java.util.*;
class Phonebook {
public static void main(String args[])
throws IOException
{
Properties ht = new Properties();
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String name, number;
FileInputStream fin = null;
boolean changed = false;
// Try to open phonebook.dat file.
try {
fin = new FileInputStream("phonebook.dat");
} catch(FileNotFoundException e) {
// ignore missing file
}
/* If phonebook file already exists,
load existing telephone numbers. */
try {
if(fin != null) {
ht.load(fin);

fin.close();
}
} catch(IOException e) {
System.out.println("Error reading file.");
}
// Let user enter new names and numbers.
do {
System.out.println("Enter new name" +
" ('quit' to stop): ");
name = br.readLine();
if(name.equals("quit")) continue;
System.out.println("Enter number: ");
number = br.readLine();
ht.put(name, number);
changed = true;
} while(!name.equals("quit"));
// If phone book data has changed, save it.
if(changed) {
FileOutputStream fout = new FileOutputStream("phonebook.dat");
ht.store(fout, "Telephone Book");
fout.close();
}
// Look up numbers given a name.
do {
System.out.println("Enter name to find" +
" ('quit' to quit): ");
name = br.readLine();
if(name.equals("quit")) continue;
number = (String) ht.get(name);
System.out.println(number);
} while(!name.equals("quit"));
}
}
15.11 类集总结
类集框架为程序员提供了一个功能强大的设计方案以解决编程过程中面临的大多数任
务。下一次当你需要存储和检索信息时,可考虑使用类集。记住,类集不仅仅是专为那些
“大型作业”,例如联合数据库,邮件列表或产品清单系统等所专用的。它们对于一些小
型作业也是很有效的。例如,TreeMap可以给出一个很好的类集以保留一组文件的字典结
构。TreeSet在存储工程管理信息时是十分有用的。坦白地说,对于采用基于类集的解决方
案而受益的问题种类只受限于你的想象力。

你可能感兴趣的:(Java)