自
Java1.2
之后
Java
版本统称为
Java2
,
Java2
中的容器类库才可以说是一种真正意义上的集合框架的实现。基本完全重新设计,但是又对
Java1
中的一些容器类库在新的设计上进行了保留,这主要是为了向下兼容的目的,当用
Java2
开发程序时,应尽量避免使用它们,
Java2
的集合框架已经完全可以满足你的需求。有一点需要提醒的是,在
Java1
中容器类库是同步化的,而
Java2
中的容器类库都是非同步化,这可能是对执行效率进行考虑的结果。
Java2
中的集合框架提供了一套设计优良的接口和类,使程序员操作成批的数据或对象元素极为方便。这些接口和类有很多对抽象数据类型操作的
API
,而这是我们常用的且在数据结构中熟知的。例如
Maps
,
Sets
,
Lists
,
Arrays
等。并且
Java
用面向对象的设计对这些数据结构和算法进行了封装,这就极大的减化了程序员编程时的负担。程序员也可以以这个集合框架为基础,定义更高级别的数据抽象,比如栈、队列和线程安全的集合等,从而满足自己的需要。
Java2
的集合框架,抽其核心,主要有三类:
List
、
Set
和
Map
。如下图所示:
从图上可以看出,
List
和
Set
继承了
Collection
,而
Map
则独成一体。初看上去可能会对
Map
独成一体感到不解,它为什么不也继承
Collection
呢?但是仔细想想,这种设计是合理的。一个
Map
提供了通过
Key
对
Map
中存储的
Value
进行访问,也就是说它操作的都是成对的对象元素,比如
put()
和
get()
方法,而这是一个
Set
或
List
所不就具备的。当然在需要时,你可以由
keySet()
方法或
values()
方法从一个
Map
中得到键的
Set
集或值的
Collection
集。
1
、
Collection
接口提供了一组操作成批对象的方法,用
UML
表示的方法列表如下:
它提供了基本操作如添加、删除。它也支持查询操作如是否为空
isEmpty()
方法等。为了支持对
Collection
进行独立操作,
Java
的集合框架给出了一个
Iterator
,它使得你可以泛型操作一个
Collection
,而不需知道这个
Collection
的具体实现类型是什么。它的功能与
Java1
中的
Enumeration
类似,只是更易掌握和使用,功能也更强大。在建立集合框架时,
Sun
的开发团队考虑到需要提供一些灵活的接口,用来操作成批的元素,又为了设计的简便,就把那些对集合进行可选操作的方法与基本方法放到了一起。因为一个接口的实现者必须提供对接口中定义的所有方法的实现,这就需要一种途径让调用者知道它正在调用
的可选方法当前不支持。最后开发团队选择使用一种信号,也即抛出一种不支持操作例外
(UnsupportedOperationException)
,如果你在使用一个
Collection
中遇到一个上述的例外,那就意味着你的操作失败,比如你对一个只读
Collection
添加一个元素时,你就会得到一个不支持操作例外。在你实现一个集合接口时,你可以很容易的在你不想让用户使用的方法中抛出
UnsupportOperationException
来告诉使用者这个方法当前没有实现,
UnsupportOperationException
是
RuntimeException
的一个扩展。
另外
Java2
的容器类库还有一种
Fail fast
的机制。比如你正在用一个
Iterator
遍历一个容器中的对象,这时另外一个线程或进程对那个容器进行了修改,那么再用
next()
方法时可能会有灾难性的后果,而这是你不愿看到的,这时就会引发一个
ConcurrentModificationException
例外。这就是
fail
-
fast
。
2
、
List
接口对
Collection
进行了简单的扩充,它的具体实现类常用的有
ArrayList
和
LinkedList
。你可以将任何东西放到一个
List
容器中,并在需要时从中取出。
ArrayList
从其命名中可以看出它是一种类似数组的形式进行存储,因此它的随机访问速度极快,而
LinkedList
的内部实现是链表,它适合于在链表中间需要频繁进行插入和删除操作。在具体应用时可以根据需要自由选择。前面说的
Iterator
只能对容器进行向前遍历,而
ListIterator
则继承了
Iterator
的思想,并提供了对
List
进行双向遍历的方法。
3
、
Set
接口也是
Collection
的一种扩展,而与
List
不同的时,在
Set
中的对象元素不能重复,也就是说你不能把同样的东西两次放入同一个
Set
容器中。它的常用具体实现有
HashSet
和
TreeSet
类。
HashSet
能快速定位一个元素,但是你放到
HashSet
中的对象需要实现
hashCode()
方法,它使用了前面说过的哈希码的算法。而
TreeSet
则将放入其中的元素按序存放,这就要求你放入其中的对象是可排序的,这就用到了集合框架提供的另外两个实用类
Comparable
和
Comparator
。一个类是可排序的,它就应该实现
Comparable
接口。有时多个类具有相同的排序算法,那就不需要在每分别重复定义相同的排序算法,只要实现
Comparator
接口即可。
集合框架中还有两个很实用的公用类:
Collections
和
Arrays
。
Collections
提供了对一个
Collection
容器进行诸如排序、复制、查找和填充等一些非常有用的方法,
Arrays
则是对一个数组进行类似的操作。
4
、
Map
是一种把键对象和值对象进行关联的容器,而一个值对象又可以是一个
Map
,依次类推,这样就可形成一个多级映射。对于键对象来说,像
Set
一样,一个
Map
容器中的键对象不允许重复,这是为了保持查找结果的一致性
;
如果有两个键对象一样,那你想得到那个键对象所对应的值对象时就有问题了,可能你得到的并不是你想的那个值对象,结果会造成混乱,所以键的唯一性很重要,也是符合集合的性质的。当然在使用过程中,某个键所对应的值对象可能会发生变化,这时会按照最后一次修改的值对象与键对应。对于值对象则没有唯一性的要求。你可以将任意多个键都映射到一个值对象上,这不会发生任何问题(不过对你的使用却可能会造成不便,你不知道你得到的到底是那一个键所对应的值对象)。
Map
有两种比较常用的实现:
HashMap
和
TreeMap
。
HashMap
也用到了哈希码的算法,以便快速查找一个键,
TreeMap
则是对键按序存放,因此它便有一些扩展的方法,比如
firstKey(),lastKey()
等,你还可以从
TreeMap
中指定一个范围以取得其子
Map
。键和值的关联很简单,用
pub(Object key,Object value)
方法即可将一个键与一个值对象相关联。用
get(Object key)
可得到与此
key
对象所对应的值对象。