1.类集框架 

java.util 包中包含了一些在 Java 2 中新增加的最令人兴奋的增强功能:类集。一个类集

(collection)是一组对象。类集的增加使得许多 java.util 中的成员在结构和体系结构上发生 根本的改变。它也扩展了包可以被应用的任务范围。类集是被所有 Java 程序员紧密关注的 最新型的技术。

除 了类集,java.util 还包含了支持范围广泛的函数的各种各样的类和接口。这些类和接 口被核心的 Java 包广泛使用,同时当然也可以被你编写的程序所使用。对它们的应用包括 产生伪随机数,对日期和时间的操作,观测事件,对位集的操作以及标记字符串。由于 java.util 具有许多特性,因此它是 Java 中最被广泛使用的一个包。

2.类集概述

Java 的类集(Collection)框架使你的程序处理对象组的方法标准化。在 Java 2 出现之前,

Java 提供了一些专门的类如 Dictionary,Vector,Stack 和 Properties 去存储和操作对象组。 尽管这些类非常有用,它们却缺少一个集中,统一的主题。因此例如说使用 Vector 的方法 就会与使用 Properties 的方法不同。以前的专门的方法也没有被设计成易于扩展和能适应新 的环境的形式。而类集解决了这些(以及其他的一些)问题。

2.1.设计类集的目的

类集框架被设计用于适应几个目的。首先,这种框架是高性能的。对基本类集(动态数

组, 链接表,树和散列表)的实现是高效率的。一般很少需要人工去对这些“数据引擎”编写 代码(如果有的话)。第二点,框架必须允许不同类型的类集以相同的方式和高度互操作方 式工作。第三点,类集必须是容易扩展和/或修改的。为了实现这一目标,类集框架被设计 成包含一组标准的接口。对这些接口,提供了几个标准的实现工具(例如 LinkedList,HashSet 和 TreeSet),通常就是这样使用的。如果你愿意的话,也可以实现你自己的类集。为了方 便起见,创建用于各种特殊目的的实现工具。一部分工具可以使你自己的类集实现更加容易。 最后,增加了允许将标准数组融合到类集框架中的机制。

2.2.Algorithms

算法(Algorithms)是类集机制的另一个重要部分。算法操作类集,它在 Collections 类 中被定义为静态方法。因此它们可以被所有的类集所利用。每一个类集类不必实现它自己的 方案,算法提供了一个处理类集的标准方法。

2.3.Iterator

由类集框架创建的另一项是 Iterator 接口。一个迭代程序(iterator)提供了一个多用途 的,标准化的方法,用于每次访问类集的一个元素。因此迭代程序提供了一种枚举类集内容(enumerating the contents of a collection)的方法。因为每一个类集都实现 Iterator,所以通 过由 Iterator 定义的方法,任一类集类的元素都能被访问到。除 了类集之外,框架定义了几个映射接口和类。映射(Maps)存储键/值对。尽管映射 在对项的正确使用上不是“类集”,但它们完全用类集集成。在类集框架的语言中,可以获得 映射的类集“视图”(collection-view)。这个“视图”包含了从存储在类集中的映射得到的元素。 因此,如果选择了一个映射,就可以将其当做一个类集来处理。对于由 java.util 定义的原始类,类集机制被更新以便它们也能够集成到新的系统里。所 以理解下面的说法是很重要的:尽管类集的增加改变了许多原始工具类的结构,但它却不会 导致被抛弃。类集仅仅是提供了处理事情的一个更好的方法。

最后的一点:如果你对 C++比较熟悉的话,那么你可以发现 Java 的类集技术与在 C++中 定义的标准模板库(STL)相似。在 C++中叫做容器(container),而在 Java 中叫做类集。

3 复合框架中的接口


所谓框架就是一个类库的集合。集合框架就是一个用来表示和操作集合的统一的架构, 包含了实现集合的接口与类。除 了类集接口之外,类集也使用 Comparator,Iterator 和 ListIterator 接口。简单地说, Comparator 接口定义了两个对象如何比较;Iterator 和 ListIterator 接口枚举类集中的对象。 为了在它们的使用中提供最大的灵活性,类集接口允许对一些方法进行选择。可选择的 方法使得使用者可以更改类集的内容。支持这些方法的类集被称为可修改的(modifiable)。 不允许修改其内容的类集被称为不可修改的(unmodifiable)。如果对一个不可修改的类集 使用这些方法,将引发一个 UnsupportedOperationException 异常。所有内置的类集都是可修改的。Collection 接口是构造类集框架的基础。它声明所有类集都将拥有的核心方法。因为所 有类集实现 Collection,所以熟悉它的方法对于清楚地理解框架是必要的。其中几种方法可 能会引发一个 UnsupportedOperationException 异常。正如上面解释的那样,这些发生在当类 集不能被修改时。当一个对象与另一个对象不兼容,例如当企图增加一个不兼容的对象到一个 类集中时。将产生一个 ClassCastException 异常。调用 add()方法可以将对象加入类集。注 意 add()带一个 Object 类型的参数。因为 Object 是所有类的超类,所以任何类型的对象可以 被存储在一个类集中。然而原始类型不行。例如,一个类集不能直接存储类型 int,char, double 等的值。当然如果想存储这些对象,也可以使用原始类型包装器。可以通过调用 addAll() 方法将一个类集的全部内容增加到另一个类集中。可以通过调用 remove()方法将一个对象删 除。为了删除一组对象,可以调用 removeAll()方法。调用 retainAll()方法可以将除了一组指 定的元素之外的所有元素删除。为了清空类集,可以调用 clear()方法。通过调用 contains() 方法,可以确定一个类集是否包含了一个指定的对象。为了确定一个类集是否包含了另一个 类集的全部元素,可以调用 containsAll()方法当一个类集是空的时候,可以通过调用 isEmpty() 方法来予以确认。调用 size()方法可以获得类集中当前元素的个数。toArray()方法返回一个数 组,这个数组包含了存储在调用类集中的元素。通过在类集和数组之间提供一条路径,可以 充分利用这两者的优点。一个更加重要的方法是 iterator(),该方法对类集返回一个迭代程序。 当使用一个类集框架时,迭代程序对于成功的编程来说是至关重要的。

Collection:集合层次中的根接口,JDK 没有提供这个接口直接的实现类。

Set:不能包含重复的元素。

SortedSet 是一个按照升序排列元素的 Set。

List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式。

Map:包含了 key-value 对。Map 不能包含重复的 key。

SortedMap 是一个按照升序排列 key 的 Map。

4.List

List 接口扩展了 Collection 并声明存储一系列元素的类集的特性。使用一个基于零的下 标,元素可以通过它们在列表中的位置被插入和访问。一个列表可以包含重复元素。除了由 Collection 定义的方法之外,List 还定义了一些它自己的方法。再次注意当类集不能被修改时,其中的几种方法引发 UnsupportedOperationException 异常。当一个对象与另一个不兼容,例 如当企图将一个不兼容的对象加入一个类集中时,将产生 ClassCastException 异常。对于由 Collection 定义的 add()和 addAll()方法,List 增加了方法 add(int, Object)和 addAll(int, Collection)。 这些方法在指定的下标处插入元素。由 Collection 定义的 add(Object)和 addAll(Collection)的 语义也被 List 改变了,以便它们在列表的尾部增加元素。为了获得在指定位置存储的对象, 可以用对象的下标调用 get()方法。为了给类表中的一个元素赋值,可以调用 set()方法,指 定被改变的对象的下标。调用 indexOf()或 lastIndexOf()可以得到一个对象的下标。通过调用 subList()方法,可以获得列表的一个指定了开始下标和结束下标的子列表。subList()方法使得 列表处理十分方便。

5.Set

集合接口定义了一个集合。它扩展了 Collection 并说明了不允许复制元素的类集的特性。 因此,如果试图将复制元素加到集合中时,add()方法将返回 false。它本身并没有定义任何 附加的方法。

6.ShortSet

SortedSet 接口扩展了 Set 并说明了按升序排列的集合的特性。当没有项包含在调用集合 中时,其中的几种方法引发 NoSuchElementException 异常。当对象与调用集合中的元素不兼 容时,引发 ClassCastException 异常。如果试图使用 null 对象,而集合不允许 null 时,引发 NullPointerException 异常。SortedSet 定义了几种方法,使得对集合的处理更加方便。调用 first()方法,可以获得集 合中的第一个对象。调用 last()方法,可以获得集合中的最后一个元素。调用 subSet()方法,可以获得排序集合的一个指定了第一个和最后一个对象的子集合。如果需要得到从集合的第 一个元素开始的一个子集合,可以使用 headSet()方法。如果需要获得集合尾部的一个子集合,可以使用 tailSet()方法。

现在,你已经熟悉了类集接口,下面开始讨论实现它们的标准类。一些类提供了完整的 可以被使用的工具。另一些类是抽象的,提供主框架工具,作为创建具体类集的起始点。

7.ArrayList

ArrayList:我们可以将其看作是能够自动增长容量的数组。利用 ArrayList 的 toArray()返 回一个数组。Arrays.asList()返回一个列表。迭代器(Iterator) 给我们提供了一种通用的方式来 访问集合中的元素。

ArrayList 类扩展 AbstractList 并实现了 List 接口。ArrayList 支持可随需要而增长的动态数 组。在 Java 中,标准数组是定长的。在数组创建之后,它们不能被加长或缩短,这也就意味着你必须事先知道数组可以容纳多少元素。但是,你直到运行时才能知道需要多大的数组。为 了解决这个问题,类集框架定义了 ArrayList。本质上,ArrayList 是对象引用的一个变长数 组。也就是说,ArrayList 能够动态地增加或减小其大小。数组列表以一个原始大小被创建。 当超过了它的大小,类集自动增大。当对象被删除后,数组就可以缩小。

•ArrayList 有如下的构造函数

–ArrayList( )

–ArrayList(Collection c)

–ArrayList(int capacity)

–第一个构造函数建立一个空的数组列表。

–第二个构造函数建立一个数组列表,该数组列表由类集 c 中的元素初始化。

–第三个构造函数建立一个数组列表,该数组有指定的初始容量(capacity)。容量是用于存

储元素的基本数组的大小。当元素被追加到数组列表上时,容量会自动增加。 使用由 toString()方法提供的默认的转换显示类集的内容,toString()方法是从AbstractCollection 继承下来的。尽管它对简短的例子程序来说是足够了,然而很少使用这种 方法去显示实际中的类集的内容。通常编程者会提供自己的输出程序。尽管当对象被存储在 ArrayList 对象中时,其容量会自动增加。仍可以通过调用ensureCapacity()方法来人工地增加 ArrayList 的容量。如果事先知道将在当前能够容纳的类集 中存储许许多多的项时,你可能会想这样做。在开始时,通过一次性地增加它的容量,就能避免后面的再分配。因为再分配是很花时间的,避免不必要的处理可以改善性能。

•ensureCapacity()方法如下所示:

–void ensureCapacity(int cap)

–这里,cap 是新的容量

相反地,如果想要减小在 ArrayList 对象之下的数组的大小,以便它有正好容纳当前项的大 小,可以调用 trimToSize()方法。该方法如下:

–void trimToSize()

当 使用 ArrayList 时,有时想要获得一个实际的数组,这个数组包含了列表的内容。可 以通过调用方法 toArray()来实现它。下面是几个为什么可能想将类集转换成为数组的原因, a.对于特定的操作,可以获得更快的处理时间,b.为了给方法传递数组,而方法不必重载去 接收类集,c.为了将新的基于类集的程序与不认识类集的老程序集成。

Arrays.asList(),返回一个受指定数组支持的固定大小的列表。(对返回列表的更改会“直 写”到数组。)此方法同 Collection.toArray 一起,充当了基于数组的 API 与基于 collection 的API 之间的桥梁。

8.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 实现 List 接口,调用 add(Object)将项目追加到列表的尾部,如同 addLast() 方法所做的那样。使用 add()方法的 add(int, Object)形式,插入项目到指定的位置,如例子程 序中调用 add(1,“A2”)的举例。注意如何通过调用 get()和 set()方法而使得 LinkedList 中的 第三个元素发生了改变。为了获得一个元素的当前值,通过 get()方法传递存储该元素的下 标值。为了对这个下标位置赋一个新值,通过 set()方法传递下标和对应的新值。

LinkedList 是采用双向循环链表实现的。

利用 LinkedList 实现栈(stack)、队列(queue)、双向队列(double-ended queue )。

9.Date Structure

一般将数据结构分为两大类:线性数据结构和非线性数据结构。线性数据结构有线性表、 栈、队列、串、数组和文件;非线性数据结构有树和图。

9.1.linear list

线性表的逻辑结构是 n 个数据元素的有限序列:(a1, a2,a3,…an),n 为线性表的长度(n≥

0),n=0 的表称为空表。 数据元素呈线性关系。必存在唯一的称为“第一个”的数据元素;必存在唯一的称为“最

后一个”的数据元素;除第一个元素外,每个元素都有且只有一个前驱元素;除最后一个元 素外,每个元素都有且只有一个后继元素。所有数据元素在同一个线性表中必须是相同的数 据类型。

线性表按其存储结构可分为顺序表和链表。用顺序存储结构存储的线性表称为顺序表; 用链式存储结构存储的线性表称为链表。

将线性表中的数据元素依次存放在某个存储区域中,所形成的表称为顺序表。一维数组

就是用顺序方式存储的线性表。

9.2.Stack

栈 (Stack)也是一种特殊的线性表,是一种后进先出(LIFO: last in first out)的结构。栈是限 定仅在表尾进行插入和删除运算的线性表,表尾称为栈顶(top),表头称为栈底(bottom)。栈 的物理存储可以用顺序存储结构,也可以用链式存储结构。

9.3Queue

队列(Queue)是限定所有的插入只能在表的一端进行,而所有的删除都在表的另一端进 行的线性表。表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。队列 的操作是按先进先出(FIFO:first in first out)的原则进行的。队列的物理存储可以用顺序存储结 构,也可以用链式存储结构。

10.ArrayList VS LinkedList

ArrayList 底层采用数组完成,而 LinkedList 则是以一般的双向链表(double-linked list)完成,其内每个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素。如果我们经常在 List 的开始处增加元素,或者在 List 中进行插入和删除操作,我们应该使用 LinkedList , 否则的话,使用 ArrayList 将更加快速。

11.HashSet

HashSet 扩展 AbstractSet 并且实现 Set 接口。它创建一个类集,该类集使用散列表进行 存储。散列表通过使用称之为散列法的机制来存储信息。在散列(hashing)中,一个关键 字的信息内容被用来确定唯一的一个值,称为散列码(hash code)。而散列码被用来当做 与关键字相连的数据的存储下标。关键字到其散列码的转换是自动执行的——你看不到散列 码本身。你的程序代码也不能直接索引散列表。散列法的优点在于即使对于大的集合,它允 许一些基本操作如 add(),contains(),remove()和 size()方法的运行时间保持不变。

NOTE:

•散列表又称为哈希表。散列表算法的基本思想是: 以结点的关键字为自变量,通过一定的函数关系(散列函数)计算出对应的函数值,以这个值作为该结点存储在散列表中的地址。

• 当散列表中的元素存放太满,就必须进行再散列,将产生一个新的散列表,所有元素存放 到新的散列表中,原先的散列表将被删除。在 Java 语言中,通过负载因子(load factor)来决 定何时对散列表进行再散列。例如:如果负载因子是 0.75,当散列表中已经有 75%的位置已 经放满,那么将进行再散列。

•负载因子越高(越接近 1.0),内存的使用效率越高,元素的寻找时间越长。负载因子越低(越 接近 0.0),元素的寻找时间越短,内存浪费越多。

•HashSet 类的缺省负载因子是 0.75。

•下面的构造函数定义为:

–HashSet( )

–HashSet(Collection c)

–HashSet(int capacity)

–HashSet(int capacity, float fillRatio)

•第一种形式构造一个默认的散列集合。

•第二种形式用 c 中的元素初始化散列集合。

•第三种形式用 capacity 初始化散列集合的容量。

•第四种形式用它的参数初始化散列集合的容量和填充比(也称为加载容量)。填充比必须 介于 0.0 与 1.0 之间,它决定在散列集合向上调整大小之前,有多少能被充满。具体的说, 就是当元素的个数大于散列集合容量乘以它的填充比时,散列集合被扩大。对于没有获得填充比的构造函数,默认使用 0.75。

HashSet 没有定义更多的其他方法。 重要的是,注意散列集合并没有确保其元素的顺序,因为散列法的处理通常不让自己参与创建排序集合。如果需要排序存储,另一种类集—— TreeSet 将是一个更好的选择。

12.TreeSet

TreeSet 为使用树来进行存储的 Set 接口提供了一个工具,对象按升序存储。访问和检 索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet 是一个很好 的选择。

•下面的构造函数定义为:

–TreeSet( )

–TreeSet(Collection c)

–TreeSet(Comparator comp)

–TreeSet(SortedSet ss)

•第一种形式构造一个空的树集合,该树集合将根据其元素的自然顺序按升序排序。

•第二种形式构造一个包含了 c 的元素的树集合。

•第三种形式构造一个空的树集合,它按照由 comp 指定的比较函数进行排序(比较函数将 在后面介绍)。

•第四种形式构造一个包含了 ss 的元素的树集合。